/** @file SSDT Serial Port Fixup Library for X64. Copyright (c) 2019 - 2024, Arm Limited. All rights reserved.
Copyright (C) 2025 Advanced Micro Devices, Inc. All rights reserved. SPDX-License-Identifier: BSD-2-Clause-Patent @par Reference(s): - Arm Server Base Boot Requirements (SBBR), s4.2.1.8 "SPCR". - Microsoft Debug Port Table 2 (DBG2) Specification - December 10, 2015. - ACPI for Arm Components 1.0 - 2020 - Arm Generic Interrupt Controller Architecture Specification, Issue H, January 2022. (https://developer.arm.com/documentation/ihi0069/) **/ #include #include #include #include #include #include #include // Module specific include files. #include #include #include #include #include #include /** UART address range length. */ #define MIN_UART_ADDRESS_LENGTH 0x1000U /** Validate the Serial Port Information. @param [in] SerialPortInfoTable Table of CM_ARCH_COMMON_SERIAL_PORT_INFO. @param [in] SerialPortCount Count of SerialPort in the table. @retval EFI_SUCCESS Success. @retval EFI_INVALID_PARAMETER Invalid parameter. **/ EFI_STATUS EFIAPI ValidateSerialPortInfo ( IN CONST CM_ARCH_COMMON_SERIAL_PORT_INFO *SerialPortInfoTable, IN UINT32 SerialPortCount ) { UINT32 Index; CONST CM_ARCH_COMMON_SERIAL_PORT_INFO *SerialPortInfo; if ((SerialPortInfoTable == NULL) || (SerialPortCount == 0)) { ASSERT (0); return EFI_INVALID_PARAMETER; } for (Index = 0; Index < SerialPortCount; Index++) { SerialPortInfo = &SerialPortInfoTable[Index]; ASSERT (SerialPortInfo != NULL); if ((SerialPortInfo == NULL) || (SerialPortInfo->BaseAddress == 0)) { DEBUG (( DEBUG_ERROR, "ERROR: UART port base address is invalid. BaseAddress = 0x%llx\n", SerialPortInfo->BaseAddress )); return EFI_INVALID_PARAMETER; } if ((SerialPortInfo->PortSubtype != EFI_ACPI_DBG2_PORT_SUBTYPE_SERIAL_ARM_PL011_UART) && (SerialPortInfo->PortSubtype != EFI_ACPI_DBG2_PORT_SUBTYPE_SERIAL_ARM_SBSA_GENERIC_UART_2X) && (SerialPortInfo->PortSubtype != EFI_ACPI_DBG2_PORT_SUBTYPE_SERIAL_ARM_SBSA_GENERIC_UART) && (SerialPortInfo->PortSubtype != EFI_ACPI_DBG2_PORT_SUBTYPE_SERIAL_DCC) && (SerialPortInfo->PortSubtype != EFI_ACPI_DBG2_PORT_SUBTYPE_SERIAL_FULL_16550) && (SerialPortInfo->PortSubtype != EFI_ACPI_DBG2_PORT_SUBTYPE_SERIAL_16550_WITH_GAS)) { DEBUG (( DEBUG_ERROR, "ERROR: UART port subtype is invalid." " UART Base = 0x%llx, PortSubtype = 0x%x\n", SerialPortInfo->BaseAddress, SerialPortInfo->PortSubtype )); return EFI_INVALID_PARAMETER; } DEBUG ((DEBUG_INFO, "UART Configuration:\n")); DEBUG (( DEBUG_INFO, " UART Base = 0x%llx\n", SerialPortInfo->BaseAddress )); DEBUG (( DEBUG_INFO, " Length = 0x%llx\n", SerialPortInfo->BaseAddressLength )); DEBUG ((DEBUG_INFO, " Clock = %lu\n", SerialPortInfo->Clock)); DEBUG ((DEBUG_INFO, " BaudRate = %llu\n", SerialPortInfo->BaudRate)); DEBUG ((DEBUG_INFO, " Interrupt = %lu\n", SerialPortInfo->Interrupt)); } // for return EFI_SUCCESS; } /** Create the _CRS (Current Resource Settings) AML node for a serial port device. @param [in] SerialPortInfo Pointer to the serial port information structure. @param [in] Name The Name to give to the Device. Must be a NULL-terminated ASL NameString e.g.: "DEV0", "DV15.DEV0", etc. @param [in] DeviceNode AML device node handle. @retval EFI_SUCCESS The CRS node was created successfully. @retval EFI_INVALID_PARAMETER A parameter is invalid. @retval Others Failed to create CRS node. **/ STATIC EFI_STATUS EFIAPI CreateSerialPortCrs ( IN CONST CM_ARCH_COMMON_SERIAL_PORT_INFO *SerialPortInfo, IN CONST CHAR8 *Name, IN AML_OBJECT_NODE_HANDLE DeviceNode ) { AML_OBJECT_NODE_HANDLE CrsNode; EFI_STATUS Status; UINT8 IrqList[1]; Status = AmlCodeGenNameResourceTemplate ("_CRS", DeviceNode, &CrsNode); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "ERROR: SSDT-SERIAL-PORT-FIXUP: Failed to create AML _CRS Node." " Status = %r\n", Status )); return Status; } IrqList[0] = SerialPortInfo->Interrupt & MAX_UINT8; if (SerialPortInfo->PortSubtype == EFI_ACPI_DBG2_PORT_SUBTYPE_SERIAL_FULL_16550) { Status = AmlCodeGenRdIo ( TRUE, SerialPortInfo->BaseAddress & MAX_UINT16, SerialPortInfo->BaseAddress & MAX_UINT16, 1, 0x8, CrsNode, NULL ); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "ERROR: SSDT-SERIAL-PORT-FIXUP: Failed to generate IO RD node." " Status = %r\n", Status )); return Status; } // // Generate the IRQ() ASL macro. // This is used for legacy X86/X64/PC-AT compatible systems. // Status = AmlCodeGenRdIrq ( TRUE, TRUE, TRUE, IrqList, ARRAY_SIZE (IrqList), CrsNode, NULL ); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "ERROR: SSDT-SERIAL-PORT-FIXUP: Failed to generate IRQ RD node." " Status = %r\n", Status )); return Status; } // // Generate the UARTSerialBusV2() ASL macro. // This describes legacy COM port resources for X86/X64/PC-AT compatible systems. // Status = AmlCodeGenRdUartSerialBusV2 ( SerialPortInfo->BaudRate & MAX_UINT32, // BaudRate NULL, // Default 8 Bits Per Byte NULL, // Default 1 Stop Bit 0, // Lines in Use NULL, // Default is little endian NULL, // Default is no parity NULL, // Default is no flow control 0x1, // ReceiveBufferSize 0x1, // TransmitBufferSize (CHAR8 *)Name, // Serial Port Name (AsciiStrLen (Name) + 1) & MAX_UINT16, // Serial Port Name Length NULL, // Default resource index is zero NULL, // Default is consumer NULL, // Default is exclusive NULL, // vendor defined data 0, // VendorDefinedDataLength CrsNode, NULL ); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "ERROR: SSDT-SERIAL-PORT-FIXUP: Failed to generate UartSerialBus RD node." " Status = %r\n", Status )); return Status; } } if (SerialPortInfo->PortSubtype == EFI_ACPI_DBG2_PORT_SUBTYPE_SERIAL_16550_WITH_GAS) { Status = AmlCodeGenRdMemory32Fixed ( TRUE, SerialPortInfo->BaseAddress & MAX_UINT32, ((SerialPortInfo->BaseAddressLength > MIN_UART_ADDRESS_LENGTH) ? SerialPortInfo->BaseAddressLength : MIN_UART_ADDRESS_LENGTH) & MAX_UINT32, CrsNode, NULL ); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "ERROR: SSDT-SERIAL-PORT-FIXUP: Failed to generate MMIO RD node." " Status = %r\n", Status )); return Status; } Status = AmlCodeGenRdIrq ( TRUE, TRUE, TRUE, IrqList, 1, CrsNode, NULL ); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "ERROR: SSDT-SERIAL-PORT-FIXUP: Failed to generate IRQ RD node." " Status = %r\n", Status )); return Status; } } return EFI_SUCCESS; } /** Build a SSDT table describing the input serial port. The table created by this function must be freed by FreeSsdtSerialTable. @param [in] AcpiTableInfo Pointer to the ACPI table information. @param [in] SerialPortInfo Serial port to describe in the SSDT table. @param [in] Name The Name to give to the Device. Must be a NULL-terminated ASL NameString e.g.: "DEV0", "DV15.DEV0", etc. @param [in] Uid UID for the Serial Port. @param [out] Table If success, pointer to the created SSDT table. @retval EFI_SUCCESS Table generated successfully. @retval EFI_INVALID_PARAMETER A parameter is invalid. @retval EFI_NOT_FOUND Could not find information. @retval EFI_OUT_OF_RESOURCES Could not allocate memory. **/ EFI_STATUS EFIAPI BuildSsdtSerialPortTable ( IN CONST CM_STD_OBJ_ACPI_TABLE_INFO *AcpiTableInfo, IN CONST CM_ARCH_COMMON_SERIAL_PORT_INFO *SerialPortInfo, IN CONST CHAR8 *Name, IN CONST UINT64 Uid, OUT EFI_ACPI_DESCRIPTION_HEADER **Table ) { AML_OBJECT_NODE_HANDLE DeviceNode; AML_OBJECT_NODE_HANDLE ScopeNode; AML_ROOT_NODE_HANDLE RootNode; CONST CHAR8 *NonBsaHid; EFI_STATUS Status; EFI_STATUS Status1; UINT32 EisaId; ASSERT (AcpiTableInfo != NULL); ASSERT (SerialPortInfo != NULL); ASSERT (Name != NULL); ASSERT (Table != NULL); // Validate the Serial Port Info. Status = ValidateSerialPortInfo (SerialPortInfo, 1); if (EFI_ERROR (Status)) { return Status; } Status = AmlCodeGenDefinitionBlock ( "SSDT", "AMDINC", "SERIAL", 0x01, &RootNode ); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "ERROR: SSDT-SERIAL-PORT-FIXUP: Failed to create AML Definition Block." " Status = %r\n", Status )); ASSERT_EFI_ERROR (Status); return Status; } Status = AmlCodeGenScope ("\\_SB_", RootNode, &ScopeNode); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "ERROR: SSDT-SERIAL-PORT-FIXUP: Failed to create AML Scope Node." " Status = %r\n", Status )); goto exit_handler; } // Create the Device Node, COMx, where x is the Uid. Status = AmlCodeGenDevice (Name, ScopeNode, &DeviceNode); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "ERROR: SSDT-SERIAL-PORT-FIXUP: Failed to create AML Device Node." " Status = %r\n", Status )); goto exit_handler; } NonBsaHid = (CONST CHAR8 *)PcdGetPtr (PcdNonBsaCompliant16550SerialHid); if (SerialPortInfo->PortSubtype == EFI_ACPI_DBG2_PORT_SUBTYPE_SERIAL_16550_WITH_GAS) { if ((NonBsaHid != NULL) && (AsciiStrLen (NonBsaHid) != 0)) { if (!(IsValidPnpId (NonBsaHid) || IsValidAcpiId (NonBsaHid))) { DEBUG (( DEBUG_ERROR, "ERROR: SSDT-SERIAL-PORT-FIXUP: Invalid Supplied HID %a.\n", NonBsaHid )); goto exit_handler; } Status = AmlCodeGenNameString ( "_HID", NonBsaHid, DeviceNode, NULL ); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "ERROR: SSDT-SERIAL-PORT-FIXUP: Failed to create AML _HID Node." " Status = %r\n", Status )); goto exit_handler; } } } if ((SerialPortInfo->PortSubtype == EFI_ACPI_DBG2_PORT_SUBTYPE_SERIAL_FULL_16550) || ((SerialPortInfo->PortSubtype == EFI_ACPI_DBG2_PORT_SUBTYPE_SERIAL_16550_WITH_GAS) && ((NonBsaHid == NULL) || (AsciiStrLen (NonBsaHid) == 0)))) { Status = AmlGetEisaIdFromString ("PNP0501", &EisaId); if (EFI_ERROR (Status)) { goto exit_handler; } Status = AmlCodeGenNameInteger ("_HID", EisaId, DeviceNode, NULL); if (EFI_ERROR (Status)) { goto exit_handler; } Status = AmlGetEisaIdFromString ("PNP0500", &EisaId); if (EFI_ERROR (Status)) { goto exit_handler; } Status = AmlCodeGenNameInteger ("_CID", EisaId, DeviceNode, NULL); if (EFI_ERROR (Status)) { goto exit_handler; } } // _UID Status = AmlCodeGenNameInteger ("_UID", Uid, DeviceNode, NULL); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "ERROR: SSDT-SERIAL-PORT-FIXUP: Failed to create AML _UID Node." " Status = %r\n", Status )); goto exit_handler; } // _DDN Status = AmlCodeGenNameString ("_DDN", Name, DeviceNode, NULL); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "ERROR: SSDT-SERIAL-PORT-FIXUP: Failed to create AML _DDN Node." " Status = %r\n", Status )); goto exit_handler; } // _STA Status = AmlCodeGenMethodRetInteger ( "_STA", 0x0F, 0, FALSE, 0, DeviceNode, NULL ); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "ERROR: SSDT-SERIAL-PORT-FIXUP: Failed to create AML _STA Node." " Status = %r\n", Status )); goto exit_handler; } Status = CreateSerialPortCrs (SerialPortInfo, Name, DeviceNode); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "ERROR: SSDT-SERIAL-PORT-FIXUP: Failed to create _CRS for Serial Port." " Status = %r\n", Status )); goto exit_handler; } // Serialize the tree. Status = AmlSerializeDefinitionBlock ( RootNode, Table ); if (EFI_ERROR (Status)) { DEBUG (( DEBUG_ERROR, "ERROR: SSDT-SERIAL-PORT-FIXUP: Failed to Serialize SSDT Table Data." " Status = %r\n", Status )); } return EFI_SUCCESS; exit_handler: // Cleanup if (RootNode != NULL) { Status1 = AmlDeleteTree (RootNode); if (EFI_ERROR (Status1)) { DEBUG (( DEBUG_ERROR, "ERROR: SSDT-SERIAL-PORT-FIXUP: Failed to cleanup AML tree." " Status = %r\n", Status1 )); // If Status was success but we failed to delete the AML Tree // return Status1 else return the original error code, i.e. Status. if (!EFI_ERROR (Status)) { return Status1; } } } return Status; } /** Free an SSDT table previously created by the BuildSsdtSerialTable function. @param [in] Table Pointer to a SSDT table allocated by the BuildSsdtSerialTable function. @retval EFI_SUCCESS Success. **/ EFI_STATUS EFIAPI FreeSsdtSerialPortTable ( IN EFI_ACPI_DESCRIPTION_HEADER *Table ) { ASSERT (Table != NULL); FreePool (Table); return EFI_SUCCESS; }