/** @file
SSDT Serial Port Fixup Library.
Copyright (c) 2019 - 2024, Arm Limited. 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
#if defined (MDE_CPU_ARM) || defined (MDE_CPU_AARCH64)
#include
#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;
}