ChainloadApp hands the payload a hardcoded 0x3F8 port-I/O serial descriptor on X64 and a zeroed one everywhere else, so the payload's console is wrong on any platform whose UART is not the legacy PC one. On AArch64 the UART is MMIO-mapped and its base varies per platform, so a hardcoded address is right on QEMU virt and wrong on everything else, for example a platform that exposes an MMIO 16550 instead of a PL011.
The outer firmware already describes its console. Derive the serial HOB from the ACPI SPCR table, which carries the base address, whether that address is MMIO or port I/O, the access width and the baud rate. Keep the legacy 0x3F8 I/O port as the X64 fallback for when no usable SPCR is present. Only 16550-compatible interface types are translated, because that is what the payload's SerialPortLib speaks; anything else takes the same fallback. On an architecture with no fallback the descriptor stays zeroed, no serial HOB is emitted and the payload uses its built-in PcdSerialRegisterBase. That is the intended behaviour, but it also leaves a user with no console and no explanation, so print a warning on the outer firmware's console before that console goes away. SPCR encodes the baud rate as an enumeration, and carries no register stride at all. The stride is inferred from the ACPI GAS AccessSize, which is the width of a single access and not the spacing between registers, so the inference is ours and not something SPCR states. It is the only signal SPCR carries, and Linux makes the same inference: its SPCR handling selects the mmio32 variant when access_width is 3 or more. Neither the RSDP nor the SPCR is trusted. The table is located through AcpiFindTableFromRsdp(), which validates the RSDP signature and the XSDT/RSDT signature and length before walking anything, and the SPCR's own Length is checked to cover every field read out of it before the first one is touched. When the SPCR UART is MMIO-mapped, its page is also published as a reserved memory-map record carrying MEM_MAP_FLAG_MMIO. The outer firmware's GCD map does not always cover the UART, and a serial write to an unmapped address once the payload has enabled its own translation aborts. Cc: Benjamin Doron <[email protected]> Cc: Gua Guo <[email protected]> Cc: Guo Dong <[email protected]> Cc: James Lu <[email protected]> Cc: Sean Rhodes <[email protected]> Cc: Shuo Liu <[email protected]> Cc: Ard Biesheuvel <[email protected]> Cc: Leif Lindholm <[email protected]> Cc: Sami Mujawar <[email protected]> Cc: Vishal Oliyil Kunnnil <[email protected]> Assisted-by: claude-opus-5 Signed-off-by: Alexander Graf <[email protected]> --- UefiPayloadPkg/ChainloadApp/ChainloadApp.c | 117 +++++++++++++++++-- UefiPayloadPkg/ChainloadApp/ChainloadApp.inf | 1 + 2 files changed, 108 insertions(+), 10 deletions(-) diff --git a/UefiPayloadPkg/ChainloadApp/ChainloadApp.c b/UefiPayloadPkg/ChainloadApp/ChainloadApp.c index ef1e9c1f51..91735bc737 100644 --- a/UefiPayloadPkg/ChainloadApp/ChainloadApp.c +++ b/UefiPayloadPkg/ChainloadApp/ChainloadApp.c @@ -25,6 +25,7 @@ #include <Library/CacheMaintenanceLib.h> #include <Library/PeCoffGetEntryPointLib.h> #include <Library/PeCoffLib.h> +#include <Library/AcpiTableWalkLib.h> #include <Guid/Acpi.h> #include <Guid/SmBios.h> #include <Guid/MemoryMapInfoGuid.h> @@ -32,6 +33,7 @@ #include <UniversalPayload/UniversalPayload.h> #include <UniversalPayload/SerialPortInfo.h> #include <IndustryStandard/Acpi.h> +#include <IndustryStandard/SerialPortConsoleRedirectionTable.h> #include <IndustryStandard/PeImage.h> #include <UniversalPayload/ExtraData.h> #include <UniversalPayload/SmbiosTable.h> @@ -314,6 +316,90 @@ GetPhysicalAddressBits ( #endif } +/** + Derive the payload's serial-port HOB fields from the ACPI SPCR. + + Only 16550-compatible interface types are handled; anything else + falls back to the caller's default (0x3F8 port I/O on X64, + PcdSerialRegisterBase on AArch64). + + @param[in] Rsdp Physical address of the ACPI RSDP. + @param[out] Serial Populated on success. + + @retval TRUE SPCR found and translated. + @retval FALSE No SPCR or unsupported interface type. +**/ +STATIC +BOOLEAN +ParseSpcrSerial ( + IN EFI_PHYSICAL_ADDRESS Rsdp, + OUT UNIVERSAL_PAYLOAD_SERIAL_PORT_INFO *Serial + ) +{ + EFI_ACPI_SERIAL_PORT_CONSOLE_REDIRECTION_TABLE *Spcr; + + Spcr = (EFI_ACPI_SERIAL_PORT_CONSOLE_REDIRECTION_TABLE *)AcpiFindTableFromRsdp ( + Rsdp, + EFI_ACPI_2_0_SERIAL_PORT_CONSOLE_REDIRECTION_TABLE_SIGNATURE + ); + if (Spcr == NULL) { + return FALSE; + } + + // + // Nothing requires Spcr->Header.Length to cover the fields read + // below, so check it before touching any of them. BaudRate is the + // last field this function reads. + // + if (Spcr->Header.Length < + (OFFSET_OF (EFI_ACPI_SERIAL_PORT_CONSOLE_REDIRECTION_TABLE, BaudRate) + sizeof (Spcr->BaudRate))) + { + return FALSE; + } + + switch (Spcr->InterfaceType) { + case EFI_ACPI_SERIAL_PORT_CONSOLE_REDIRECTION_TABLE_INTERFACE_TYPE_16550: + case EFI_ACPI_SERIAL_PORT_CONSOLE_REDIRECTION_TABLE_INTERFACE_TYPE_16450: + case EFI_ACPI_SERIAL_PORT_CONSOLE_REDIRECTION_TABLE_INTERFACE_TYPE_NVIDIA_16550_UART: + case EFI_ACPI_SERIAL_PORT_CONSOLE_REDIRECTION_TABLE_INTERFACE_TYPE_16550_WITH_GAS: + break; + default: + return FALSE; + } + + ZeroMem (Serial, sizeof (*Serial)); + Serial->Header.Revision = UNIVERSAL_PAYLOAD_SERIAL_PORT_INFO_REVISION; + Serial->Header.Length = sizeof (*Serial); + Serial->UseMmio = (BOOLEAN)(Spcr->BaseAddress.AddressSpaceId == EFI_ACPI_6_5_SYSTEM_MEMORY); + Serial->RegisterBase = Spcr->BaseAddress.Address; + // + // ACPI GAS AccessSize is the width of a single access, not the spacing + // between registers, so treating it as the 16550 register stride is an + // inference rather than something SPCR states. It is the only signal + // SPCR carries, and it is the same inference Linux makes: its SPCR + // handling selects the mmio32 variant when access_width >= 3. + // + Serial->RegisterStride = (Spcr->BaseAddress.AccessSize > 1) ? + (UINT8)(1U << (Spcr->BaseAddress.AccessSize - 1)) : 1; + // + // SPCR encodes baud rate as an enumeration. + // + switch (Spcr->BaudRate) { + case 3: Serial->BaudRate = 9600; + break; + case 4: Serial->BaudRate = 19200; + break; + case 6: Serial->BaudRate = 57600; + break; + case 7: Serial->BaudRate = 115200; + break; + default: Serial->BaudRate = 115200; + break; + } + + return TRUE; +} + /** Check whether a range is described by the outer firmware's memory-map snapshot as EfiReservedMemoryType. @@ -451,20 +537,31 @@ BuildPayloadHobList ( DescCount = MemoryMapSize / DescriptorSize; // - // Serial console. Default to the legacy 0x3F8 I/O port on X64. On - // other architectures Serial stays zeroed, neither serial HOB is - // emitted below, and the payload falls back to its built-in - // PcdSerialRegisterBase. + // Serial console. Prefer the ACPI SPCR: on AArch64 the UART is + // MMIO-mapped and its base varies per platform, so a hardcoded + // address is wrong on anything but QEMU virt. When no SPCR is + // found fall back to the legacy 0x3F8 I/O port on X64; on other + // architectures the payload falls back to PcdSerialRegisterBase. // ZeroMem (&Serial, sizeof (Serial)); + if (!ParseSpcrSerial (AcpiRsdp, &Serial)) { #if defined (MDE_CPU_X64) - Serial.Header.Revision = UNIVERSAL_PAYLOAD_SERIAL_PORT_INFO_REVISION; - Serial.Header.Length = sizeof (Serial); - Serial.UseMmio = FALSE; - Serial.RegisterStride = 1; - Serial.BaudRate = 115200; - Serial.RegisterBase = 0x3F8; + Serial.Header.Revision = UNIVERSAL_PAYLOAD_SERIAL_PORT_INFO_REVISION; + Serial.Header.Length = sizeof (Serial); + Serial.UseMmio = FALSE; + Serial.RegisterStride = 1; + Serial.BaudRate = 115200; + Serial.RegisterBase = 0x3F8; + #else + // + // The 0x3F8 fallback is x86-only, so Serial stays zeroed, neither + // serial HOB is emitted below, and the payload uses its built-in + // PcdSerialRegisterBase. That is the intent, but it is also the case + // where a user gets no console and no explanation, so say so. + // + Print (L"ChainloadApp: no usable SPCR; payload will use its built-in serial defaults\n"); #endif + } // // Handoff HOB. EfiFreeMemoryTop/Bottom are fixed up after all HOBs diff --git a/UefiPayloadPkg/ChainloadApp/ChainloadApp.inf b/UefiPayloadPkg/ChainloadApp/ChainloadApp.inf index fc110ddb34..51c4d53f12 100644 --- a/UefiPayloadPkg/ChainloadApp/ChainloadApp.inf +++ b/UefiPayloadPkg/ChainloadApp/ChainloadApp.inf @@ -50,6 +50,7 @@ CacheMaintenanceLib PeCoffGetEntryPointLib PeCoffLib + AcpiTableWalkLib [LibraryClasses.AARCH64] ArmLib -- 2.47.3 -=-=-=-=-=-=-=-=-=-=-=- Groups.io Links: You receive all messages sent to this group. View/Reply Online (#122097): https://edk2.groups.io/g/devel/message/122097 Mute This Topic: https://groups.io/mt/120797280/21656 Group Owner: [email protected] Unsubscribe: https://edk2.groups.io/g/devel/unsub [[email protected]] -=-=-=-=-=-=-=-=-=-=-=-
