/** @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; }