/** @file | |
SSDT Serial Port Fixup Library. | |
Copyright (c) 2019 - 2024, Arm Limited. All rights reserved.<BR> | |
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 <IndustryStandard/DebugPort2Table.h> | |
#include <Library/AcpiLib.h> | |
#include <Library/BaseLib.h> | |
#include <Library/BaseMemoryLib.h> | |
#include <Library/DebugLib.h> | |
#include <Library/MemoryAllocationLib.h> | |
#include <Protocol/AcpiTable.h> | |
// Module specific include files. | |
#include <AcpiTableGenerator.h> | |
#include <ConfigurationManagerObject.h> | |
#include <ConfigurationManagerHelper.h> | |
#include <Library/AcpiHelperLib.h> | |
#include <Library/AmlLib/AmlLib.h> | |
#include <Protocol/ConfigurationManagerProtocol.h> | |
#if defined (MDE_CPU_ARM) || defined (MDE_CPU_AARCH64) | |
#include <Library/ArmGicArchLib.h> | |
#endif | |
/** C array containing the compiled AML template. | |
This symbol is defined in the auto generated C file | |
containing the AML bytecode array. | |
*/ | |
extern CHAR8 ssdtserialporttemplate_aml_code[]; | |
/** UART address range length. | |
*/ | |
#define MIN_UART_ADDRESS_LENGTH 0x1000U | |
/** Validate the Serial Port Information. | |
@param [in] SerialPortInfoTable Table of CM_ARM_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_ARM_SERIAL_PORT_INFO *SerialPortInfoTable, | |
IN UINT32 SerialPortCount | |
) | |
{ | |
UINT32 Index; | |
CONST CM_ARM_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; | |
} | |
#if defined (MDE_CPU_ARM) || defined (MDE_CPU_AARCH64) | |
// If an interrupt is not wired to the serial port, the Configuration | |
// Manager specifies the interrupt as 0. | |
// Any other value must be within the SPI or extended SPI range. | |
if ((SerialPortInfo->Interrupt != 0) && | |
!(((SerialPortInfo->Interrupt >= ARM_GIC_ARCH_SPI_MIN) && | |
(SerialPortInfo->Interrupt <= ARM_GIC_ARCH_SPI_MAX)) || | |
((SerialPortInfo->Interrupt >= ARM_GIC_ARCH_EXT_SPI_MIN) && | |
(SerialPortInfo->Interrupt <= ARM_GIC_ARCH_EXT_SPI_MAX)))) | |
{ | |
DEBUG (( | |
DEBUG_ERROR, | |
"ERROR: Invalid UART port interrupt ID. Interrupt = %lu\n", | |
SerialPortInfo->Interrupt | |
)); | |
return EFI_INVALID_PARAMETER; | |
} | |
#endif | |
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; | |
} | |
/** Fixup the Serial Port Ids (_UID, _HID, _CID). | |
@param [in] RootNodeHandle Pointer to the root of an AML tree. | |
@param [in] Uid UID for the Serial Port. | |
@param [in] SerialPortInfo Pointer to a Serial Port Information | |
structure. | |
Get the Serial Port Information from there. | |
@retval EFI_SUCCESS The function completed successfully. | |
@retval EFI_INVALID_PARAMETER Invalid parameter. | |
@retval EFI_NOT_FOUND Could not find information. | |
@retval EFI_OUT_OF_RESOURCES Out of resources. | |
**/ | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
FixupIds ( | |
IN AML_ROOT_NODE_HANDLE RootNodeHandle, | |
IN CONST UINT64 Uid, | |
IN CONST CM_ARM_SERIAL_PORT_INFO *SerialPortInfo | |
) | |
{ | |
EFI_STATUS Status; | |
AML_OBJECT_NODE_HANDLE NameOpIdNode; | |
CONST CHAR8 *HidString; | |
CONST CHAR8 *CidString; | |
CONST CHAR8 *NonBsaHid; | |
// Get the _CID and _HID value to write. | |
switch (SerialPortInfo->PortSubtype) { | |
case EFI_ACPI_DBG2_PORT_SUBTYPE_SERIAL_FULL_16550: | |
case EFI_ACPI_DBG2_PORT_SUBTYPE_SERIAL_16550_WITH_GAS: | |
{ | |
// If there is a non-BSA compliant HID, use that. | |
NonBsaHid = (CONST CHAR8 *)PcdGetPtr (PcdNonBsaCompliant16550SerialHid); | |
if ((NonBsaHid != NULL) && (AsciiStrLen (NonBsaHid) != 0)) { | |
if (!(IsValidPnpId (NonBsaHid) || IsValidAcpiId (NonBsaHid))) { | |
return EFI_INVALID_PARAMETER; | |
} | |
HidString = NonBsaHid; | |
CidString = ""; | |
} else { | |
HidString = "PNP0501"; | |
CidString = "PNP0500"; | |
} | |
break; | |
} | |
case EFI_ACPI_DBG2_PORT_SUBTYPE_SERIAL_ARM_PL011_UART: | |
{ | |
HidString = "ARMH0011"; | |
CidString = "ARMHB000"; | |
break; | |
} | |
case EFI_ACPI_DBG2_PORT_SUBTYPE_SERIAL_ARM_SBSA_GENERIC_UART: | |
case EFI_ACPI_DBG2_PORT_SUBTYPE_SERIAL_ARM_SBSA_GENERIC_UART_2X: | |
{ | |
HidString = "ARMHB000"; | |
CidString = ""; | |
break; | |
} | |
default: | |
{ | |
return EFI_INVALID_PARAMETER; | |
} | |
} // switch | |
// Get the _UID NameOp object defined by the "Name ()" statement, | |
// and update its value. | |
Status = AmlFindNode ( | |
RootNodeHandle, | |
"\\_SB_.COM0._UID", | |
&NameOpIdNode | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
Status = AmlNameOpUpdateInteger (NameOpIdNode, (UINT64)Uid); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
// Get the _HID NameOp object defined by the "Name ()" statement, | |
// and update its value. | |
Status = AmlFindNode ( | |
RootNodeHandle, | |
"\\_SB_.COM0._HID", | |
&NameOpIdNode | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
Status = AmlNameOpUpdateString (NameOpIdNode, HidString); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
// Get the _CID NameOp object defined by the "Name ()" statement, | |
// and update its value. | |
Status = AmlFindNode ( | |
RootNodeHandle, | |
"\\_SB_.COM0._CID", | |
&NameOpIdNode | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
// If we have a CID then update a _CID node else delete the node. | |
if (AsciiStrLen (CidString) != 0) { | |
Status = AmlNameOpUpdateString (NameOpIdNode, CidString); | |
} else { | |
// First detach the node from the tree. | |
Status = AmlDetachNode (NameOpIdNode); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
// Delete the detached node. | |
Status = AmlDeleteTree (NameOpIdNode); | |
} | |
return Status; | |
} | |
/** Fixup the Serial Port _CRS values (BaseAddress, ...). | |
@param [in] RootNodeHandle Pointer to the root of an AML tree. | |
@param [in] SerialPortInfo Pointer to a Serial Port Information | |
structure. | |
Get the Serial Port Information from there. | |
@retval EFI_SUCCESS The function completed successfully. | |
@retval EFI_INVALID_PARAMETER Invalid parameter. | |
@retval EFI_NOT_FOUND Could not find information. | |
@retval EFI_OUT_OF_RESOURCES Out of resources. | |
**/ | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
FixupCrs ( | |
IN AML_ROOT_NODE_HANDLE RootNodeHandle, | |
IN CONST CM_ARM_SERIAL_PORT_INFO *SerialPortInfo | |
) | |
{ | |
EFI_STATUS Status; | |
AML_OBJECT_NODE_HANDLE NameOpCrsNode; | |
AML_DATA_NODE_HANDLE QWordRdNode; | |
// Get the "_CRS" object defined by the "Name ()" statement. | |
Status = AmlFindNode ( | |
RootNodeHandle, | |
"\\_SB_.COM0._CRS", | |
&NameOpCrsNode | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
// Get the first Rd node in the "_CRS" object. | |
Status = AmlNameOpGetFirstRdNode (NameOpCrsNode, &QWordRdNode); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
if (QWordRdNode == NULL) { | |
return EFI_INVALID_PARAMETER; | |
} | |
// Update the Serial Port base address and length. | |
Status = AmlUpdateRdQWord ( | |
QWordRdNode, | |
SerialPortInfo->BaseAddress, | |
((SerialPortInfo->BaseAddressLength < MIN_UART_ADDRESS_LENGTH) ? | |
MIN_UART_ADDRESS_LENGTH : SerialPortInfo->BaseAddressLength) | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
// Generate an interrupt node as the second Resource Data element in the | |
// NameOpCrsNode, if the interrupt for the serial-port is a valid SPI from | |
// Table 2-1 in Arm Generic Interrupt Controller Architecture Specification. | |
Status = AmlCodeGenRdInterrupt ( | |
TRUE, // Resource Consumer | |
FALSE, // Level Triggered | |
FALSE, // Active High | |
FALSE, // Exclusive | |
(UINT32 *)&SerialPortInfo->Interrupt, | |
1, | |
NameOpCrsNode, | |
NULL | |
); | |
ASSERT_EFI_ERROR (Status); | |
return Status; | |
} | |
/** Fixup the Serial Port device name. | |
@param [in] RootNodeHandle Pointer to the root of an AML tree. | |
@param [in] SerialPortInfo Pointer to a Serial Port Information | |
structure. | |
Get the Serial Port Information from there. | |
@param [in] Name The Name to give to the Device. | |
Must be a NULL-terminated ASL NameString | |
e.g.: "DEV0", "DV15.DEV0", etc. | |
@retval EFI_SUCCESS The function completed successfully. | |
@retval EFI_INVALID_PARAMETER Invalid parameter. | |
@retval EFI_NOT_FOUND Could not find information. | |
@retval EFI_OUT_OF_RESOURCES Out of resources. | |
**/ | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
FixupName ( | |
IN AML_ROOT_NODE_HANDLE RootNodeHandle, | |
IN CONST CM_ARM_SERIAL_PORT_INFO *SerialPortInfo, | |
IN CONST CHAR8 *Name | |
) | |
{ | |
EFI_STATUS Status; | |
AML_OBJECT_NODE_HANDLE DeviceNode; | |
// Get the COM0 variable defined by the "Device ()" statement. | |
Status = AmlFindNode (RootNodeHandle, "\\_SB_.COM0", &DeviceNode); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
// Update the Device's name. | |
return AmlDeviceOpUpdateName (DeviceNode, (CHAR8 *)Name); | |
} | |
/** Fixup the Serial Port Information in the AML tree. | |
For each template value: | |
- find the node to update; | |
- update the value. | |
@param [in] RootNodeHandle Pointer to the root of the AML tree. | |
@param [in] SerialPortInfo Pointer to a Serial Port Information | |
structure. | |
Get the Serial Port Information from there. | |
@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, contains the serialized | |
SSDT table. | |
@retval EFI_SUCCESS The function completed successfully. | |
@retval EFI_INVALID_PARAMETER Invalid parameter. | |
@retval EFI_NOT_FOUND Could not find information. | |
@retval EFI_OUT_OF_RESOURCES Out of resources. | |
**/ | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
FixupSerialPortInfo ( | |
IN AML_ROOT_NODE_HANDLE RootNodeHandle, | |
IN CONST CM_ARM_SERIAL_PORT_INFO *SerialPortInfo, | |
IN CONST CHAR8 *Name, | |
IN CONST UINT64 Uid, | |
OUT EFI_ACPI_DESCRIPTION_HEADER **Table | |
) | |
{ | |
EFI_STATUS Status; | |
ASSERT (RootNodeHandle != NULL); | |
ASSERT (SerialPortInfo != NULL); | |
ASSERT (Name != NULL); | |
ASSERT (Table != NULL); | |
// Fixup the _UID, _HID and _CID values. | |
Status = FixupIds (RootNodeHandle, Uid, SerialPortInfo); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
// Fixup the _CRS values. | |
Status = FixupCrs (RootNodeHandle, SerialPortInfo); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
// Fixup the serial-port name. | |
// This MUST be done at the end, otherwise AML paths won't be valid anymore. | |
return FixupName (RootNodeHandle, SerialPortInfo, Name); | |
} | |
/** 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; | |
} | |
/** 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_ARM_SERIAL_PORT_INFO *SerialPortInfo, | |
IN CONST CHAR8 *Name, | |
IN CONST UINT64 Uid, | |
OUT EFI_ACPI_DESCRIPTION_HEADER **Table | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_STATUS Status1; | |
AML_ROOT_NODE_HANDLE RootNodeHandle; | |
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; | |
} | |
// Parse the SSDT Serial Port Template. | |
Status = AmlParseDefinitionBlock ( | |
(EFI_ACPI_DESCRIPTION_HEADER *)ssdtserialporttemplate_aml_code, | |
&RootNodeHandle | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG (( | |
DEBUG_ERROR, | |
"ERROR: SSDT-SERIAL-PORT-FIXUP:" | |
" Failed to parse SSDT Serial Port Template. Status = %r\n", | |
Status | |
)); | |
return Status; | |
} | |
// Fixup the template values. | |
Status = FixupSerialPortInfo ( | |
RootNodeHandle, | |
SerialPortInfo, | |
Name, | |
Uid, | |
Table | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG (( | |
DEBUG_ERROR, | |
"ERROR: SSDT-SERIAL-PORT-FIXUP: Failed to fixup SSDT Serial Port Table." | |
" Status = %r\n", | |
Status | |
)); | |
goto exit_handler; | |
} | |
// Serialize the tree. | |
Status = AmlSerializeDefinitionBlock ( | |
RootNodeHandle, | |
Table | |
); | |
if (EFI_ERROR (Status)) { | |
DEBUG (( | |
DEBUG_ERROR, | |
"ERROR: SSDT-SERIAL-PORT-FIXUP: Failed to Serialize SSDT Table Data." | |
" Status = %r\n", | |
Status | |
)); | |
} | |
exit_handler: | |
// Cleanup | |
if (RootNodeHandle != NULL) { | |
Status1 = AmlDeleteTree (RootNodeHandle); | |
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; | |
} |