diff --git a/DynamicTablesPkg/Library/FdtHwInfoParserLib/Serial/ArmSerialPortParser.c b/DynamicTablesPkg/Library/FdtHwInfoParserLib/Serial/ArmSerialPortParser.c new file mode 100644 index 0000000000..0557e416b4 --- /dev/null +++ b/DynamicTablesPkg/Library/FdtHwInfoParserLib/Serial/ArmSerialPortParser.c @@ -0,0 +1,633 @@ +/** @file + Arm Serial Port Parser. + + Copyright (c) 2021, ARM Limited. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + + @par Reference(s): + - linux/Documentation/devicetree/bindings/serial/serial.yaml + - linux/Documentation/devicetree/bindings/serial/8250.txt + - linux/Documentation/devicetree/bindings/serial/arm_sbsa_uart.txt + - linux/Documentation/devicetree/bindings/serial/pl011.yaml +**/ + +#include + +#include "CmObjectDescUtility.h" +#include "FdtHwInfoParser.h" +#include "Serial/ArmSerialPortParser.h" + +/** List of "compatible" property values for serial port nodes. + + Any other "compatible" value is not supported by this module. +*/ +STATIC CONST COMPATIBILITY_STR SerialCompatibleStr[] = { + { "ns16550a" }, + { "arm,sbsa-uart" }, + { "arm,pl011" } +}; + +/** COMPATIBILITY_INFO structure for the SerialCompatible. +*/ +CONST COMPATIBILITY_INFO SerialCompatibleInfo = { + ARRAY_SIZE (SerialCompatibleStr), + SerialCompatibleStr +}; + +/** 16550 UART compatible strings. + + Any string of this list must be part of SerialCompatible. +*/ +STATIC CONST COMPATIBILITY_STR Serial16550CompatibleStr[] = { + { "ns16550a" } +}; + +/** COMPATIBILITY_INFO structure for the Serial16550Compatible. +*/ +CONST COMPATIBILITY_INFO Serial16550CompatibleInfo = { + ARRAY_SIZE (Serial16550CompatibleStr), + Serial16550CompatibleStr +}; + +/** SBSA UART compatible strings. + + Include PL011 as SBSA uart is a subset of PL011. + + Any string of this list must be part of SerialCompatible. +*/ +STATIC CONST COMPATIBILITY_STR SerialSbsaCompatibleStr[] = { + { "arm,sbsa-uart" }, + { "arm,pl011" } +}; + +/** COMPATIBILITY_INFO structure for the SerialSbsaCompatible. +*/ +CONST COMPATIBILITY_INFO SerialSbsaCompatibleInfo = { + ARRAY_SIZE (SerialSbsaCompatibleStr), + SerialSbsaCompatibleStr +}; + +/** Parse a serial port node. + + @param [in] Fdt Pointer to a Flattened Device Tree (Fdt). + @param [in] SerialPortNode Offset of a serial-port node. + @param [in] SerialPortInfo The CM_ARM_SERIAL_PORT_INFO to populate. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_UNSUPPORTED Unsupported. +**/ +STATIC +EFI_STATUS +EFIAPI +SerialPortNodeParser ( + IN CONST VOID *Fdt, + IN INT32 SerialPortNode, + IN CM_ARM_SERIAL_PORT_INFO *SerialPortInfo + ) +{ + EFI_STATUS Status; + INT32 IntcNode; + CONST UINT8 *SizeValue; + + INT32 AddressCells; + INT32 SizeCells; + INT32 IntCells; + + CONST UINT8 *Data; + INT32 DataSize; + UINT8 AccessSize; + + if ((Fdt == NULL) || + (SerialPortInfo == NULL)) + { + ASSERT (0); + return EFI_INVALID_PARAMETER; + } + + Status = FdtGetParentAddressInfo ( + Fdt, + SerialPortNode, + &AddressCells, + &SizeCells + ); + if (EFI_ERROR (Status)) { + ASSERT (0); + return Status; + } + + // Don't support more than 64 bits and less than 32 bits addresses. + if ((AddressCells < 1) || + (AddressCells > 2) || + (SizeCells < 1) || + (SizeCells > 2)) + { + ASSERT (0); + return EFI_ABORTED; + } + + Data = fdt_getprop (Fdt, SerialPortNode, "reg", &DataSize); + if ((Data == NULL) || + (DataSize < (INT32)(sizeof (UINT32) * + GET_DT_REG_ADDRESS_OFFSET (1, AddressCells, SizeCells)) - 1)) + { + // If error or not enough space. + ASSERT (0); + return EFI_ABORTED; + } + + if (AddressCells == 2) { + SerialPortInfo->BaseAddress = fdt64_to_cpu (*(UINT64 *)Data); + } else { + SerialPortInfo->BaseAddress = fdt32_to_cpu (*(UINT32 *)Data); + } + + SizeValue = Data + (sizeof (UINT32) * + GET_DT_REG_SIZE_OFFSET (0, AddressCells, SizeCells)); + if (SizeCells == 2) { + SerialPortInfo->BaseAddressLength = fdt64_to_cpu (*(UINT64 *)SizeValue); + } else { + SerialPortInfo->BaseAddressLength = fdt32_to_cpu (*(UINT32 *)SizeValue); + } + + // Get the associated interrupt-controller. + Status = FdtGetIntcParentNode (Fdt, SerialPortNode, &IntcNode); + if (EFI_ERROR (Status)) { + ASSERT (0); + if (Status == EFI_NOT_FOUND) { + // Should have found the node. + Status = EFI_ABORTED; + } + + return Status; + } + + // Get the number of cells used to encode an interrupt. + Status = FdtGetInterruptCellsInfo (Fdt, IntcNode, &IntCells); + if (EFI_ERROR (Status)) { + ASSERT (0); + return Status; + } + + Data = fdt_getprop (Fdt, SerialPortNode, "interrupts", &DataSize); + if ((Data == NULL) || (DataSize != (IntCells * sizeof (UINT32)))) { + // If error or not 1 interrupt. + ASSERT (0); + return EFI_ABORTED; + } + + SerialPortInfo->Interrupt = FdtGetInterruptId ((CONST UINT32 *)Data); + + // Note: clock-frequency is optional for SBSA UART. + Data = fdt_getprop (Fdt, SerialPortNode, "clock-frequency", &DataSize); + if (Data != NULL) { + if (DataSize < sizeof (UINT32)) { + // If error or not enough space. + ASSERT (0); + return EFI_ABORTED; + } else if (fdt_node_offset_by_phandle (Fdt, fdt32_to_cpu (*Data)) >= 0) { + // "clock-frequency" can be a "clocks phandle to refer to the clk used". + // This is not supported. + ASSERT (0); + return EFI_UNSUPPORTED; + } + + SerialPortInfo->Clock = fdt32_to_cpu (*(UINT32 *)Data); + } + + if (FdtNodeIsCompatible (Fdt, SerialPortNode, &Serial16550CompatibleInfo)) { + SerialPortInfo->PortSubtype = + EFI_ACPI_DBG2_PORT_SUBTYPE_SERIAL_16550_WITH_GAS; + + /* reg-io-width: + description: | + The size (in bytes) of the IO accesses that should be performed on the + device. There are some systems that require 32-bit accesses to the + UART. + */ + Data = fdt_getprop (Fdt, SerialPortNode, "reg-io-width", &DataSize); + if (Data != NULL) { + if (DataSize < sizeof (UINT32)) { + // If error or not enough space. + ASSERT (0); + return EFI_ABORTED; + } + + AccessSize = fdt32_to_cpu (*(UINT32 *)Data); + if (AccessSize > EFI_ACPI_6_3_QWORD) { + ASSERT (0); + return EFI_INVALID_PARAMETER; + } + + SerialPortInfo->AccessSize = AccessSize; + } else { + // 8250/16550 defaults to byte access. + SerialPortInfo->AccessSize = EFI_ACPI_6_3_BYTE; + } + } else if (FdtNodeIsCompatible ( + Fdt, + SerialPortNode, + &SerialSbsaCompatibleInfo + )) + { + SerialPortInfo->PortSubtype = + EFI_ACPI_DBG2_PORT_SUBTYPE_SERIAL_ARM_SBSA_GENERIC_UART; + } else { + ASSERT (0); + return EFI_UNSUPPORTED; + } + + // Set Baudrate to 115200 by default + SerialPortInfo->BaudRate = 115200; + return EFI_SUCCESS; +} + +/** Find the console serial-port node in the DT. + + This function fetches the node referenced in the "stdout-path" + property of the "chosen" node. + + @param [in] Fdt Pointer to a Flattened Device Tree (Fdt). + @param [out] SerialConsoleNode If success, contains the node offset + of the console serial-port node. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_NOT_FOUND Not found. +**/ +STATIC +EFI_STATUS +EFIAPI +GetSerialConsoleNode ( + IN CONST VOID *Fdt, + OUT INT32 *SerialConsoleNode + ) +{ + CONST CHAR8 *Prop; + INT32 PropSize; + CONST CHAR8 *Path; + INT32 PathLen; + INT32 ChosenNode; + + if ((Fdt == NULL) || + (SerialConsoleNode == NULL)) + { + ASSERT (0); + return EFI_INVALID_PARAMETER; + } + + // The "chosen" node resides at the the root of the DT. Fetch it. + ChosenNode = fdt_path_offset (Fdt, "/chosen"); + if (ChosenNode < 0) { + return EFI_NOT_FOUND; + } + + Prop = fdt_getprop (Fdt, ChosenNode, "stdout-path", &PropSize); + if ((Prop == NULL) || (PropSize < 0)) { + return EFI_NOT_FOUND; + } + + // Determine the actual path length, as a colon terminates the path. + Path = ScanMem8 (Prop, ':', PropSize); + if (Path == NULL) { + PathLen = (UINT32)AsciiStrLen (Prop); + } else { + PathLen = (INT32)(Path - Prop); + } + + // Aliases cannot start with a '/', so it must be the actual path. + if (Prop[0] == '/') { + *SerialConsoleNode = fdt_path_offset_namelen (Fdt, Prop, PathLen); + return EFI_SUCCESS; + } + + // Lookup the alias, as this contains the actual path. + Path = fdt_get_alias_namelen (Fdt, Prop, PathLen); + if (Path == NULL) { + return EFI_NOT_FOUND; + } + + *SerialConsoleNode = fdt_path_offset (Fdt, Path); + return EFI_SUCCESS; +} + +/** CM_ARM_SERIAL_PORT_INFO dispatcher function (for a generic serial-port). + + @param [in] FdtParserHandle A handle to the parser instance. + @param [in] GenericSerialInfo Pointer to a serial port info list. + @param [in] NodeCount Count of serial ports to dispatch. + @param [in] SerialObjectId Serial port object ID. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_NOT_FOUND Not found. + @retval EFI_UNSUPPORTED Unsupported. +**/ +STATIC +EFI_STATUS +EFIAPI +ArmSerialPortInfoDispatch ( + IN CONST FDT_HW_INFO_PARSER_HANDLE FdtParserHandle, + IN CM_ARM_SERIAL_PORT_INFO *GenericSerialInfo, + IN INT32 NodeCount, + IN EARM_OBJECT_ID SerialObjectId + ) +{ + EFI_STATUS Status; + CM_OBJ_DESCRIPTOR *NewCmObjDesc; + + if ((GenericSerialInfo == NULL) || (NodeCount == 0)) { + ASSERT (0); + return EFI_INVALID_PARAMETER; + } + + if ((SerialObjectId != EArmObjSerialPortInfo) && + (SerialObjectId != EArmObjSerialDebugPortInfo) && + (SerialObjectId != EArmObjSerialConsolePortInfo)) + { + ASSERT (0); + return EFI_INVALID_PARAMETER; + } + + // Dispatch the Generic Serial ports + Status = CreateCmObjDesc ( + CREATE_CM_ARM_OBJECT_ID (SerialObjectId), + NodeCount, + GenericSerialInfo, + sizeof (CM_ARM_SERIAL_PORT_INFO) * NodeCount, + &NewCmObjDesc + ); + if (EFI_ERROR (Status)) { + ASSERT (0); + return Status; + } + + // Add all the CmObjs to the Configuration Manager. + Status = AddMultipleCmObj (FdtParserHandle, NewCmObjDesc, 0, NULL); + ASSERT_EFI_ERROR (Status); + FreeCmObjDesc (NewCmObjDesc); + return Status; +} + +/** CM_ARM_SERIAL_PORT_INFO parser function (for debug/console serial-port). + + This parser expects FdtBranch to be the debug serial-port node. + At most one CmObj is created. + The following structure is populated: + typedef struct CmArmSerialPortInfo { + UINT64 BaseAddress; // {Populated} + UINT32 Interrupt; // {Populated} + UINT64 BaudRate; // {default} + UINT32 Clock; // {Populated} + UINT16 PortSubtype; // {Populated} + UINT64 BaseAddressLength // {Populated} + } CM_ARM_SERIAL_PORT_INFO; + + A parser parses a Device Tree to populate a specific CmObj type. None, + one or many CmObj can be created by the parser. + The created CmObj are then handed to the parser's caller through the + HW_INFO_ADD_OBJECT interface. + This can also be a dispatcher. I.e. a function that not parsing a + Device Tree but calling other parsers. + + @param [in] FdtParserHandle A handle to the parser instance. + @param [in] FdtBranch When searching for DT node name, restrict + the search to this Device Tree branch. + @param [in] SerialObjectId ArmNamespace Object ID for the serial port. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_NOT_FOUND Not found. + @retval EFI_UNSUPPORTED Unsupported. +**/ +STATIC +EFI_STATUS +EFIAPI +ArmSerialPortInfoParser ( + IN CONST FDT_HW_INFO_PARSER_HANDLE FdtParserHandle, + IN INT32 FdtBranch, + IN EARM_OBJECT_ID SerialObjectId + ) +{ + EFI_STATUS Status; + CM_ARM_SERIAL_PORT_INFO SerialInfo; + + if ((SerialObjectId != EArmObjSerialDebugPortInfo) && + (SerialObjectId != EArmObjSerialConsolePortInfo)) + { + ASSERT (0); + return EFI_INVALID_PARAMETER; + } + + ZeroMem (&SerialInfo, sizeof (SerialInfo)); + + Status = SerialPortNodeParser ( + FdtParserHandle->Fdt, + FdtBranch, + &SerialInfo + ); + if (EFI_ERROR (Status)) { + ASSERT (0); + return Status; + } + + Status = ArmSerialPortInfoDispatch ( + FdtParserHandle, + &SerialInfo, + 1, + SerialObjectId + ); + ASSERT_EFI_ERROR (Status); + return Status; +} + +/** SerialPort dispatcher. + + This disptacher populates the CM_ARM_SERIAL_PORT_INFO structure for + the following CM_OBJ_ID: + - EArmObjSerialConsolePortInfo + - EArmObjSerialDebugPortInfo + - EArmObjSerialPortInfo + + A parser parses a Device Tree to populate a specific CmObj type. None, + one or many CmObj can be created by the parser. + The created CmObj are then handed to the parser's caller through the + HW_INFO_ADD_OBJECT interface. + This can also be a dispatcher. I.e. a function that not parsing a + Device Tree but calling other parsers. + + @param [in] FdtParserHandle A handle to the parser instance. + @param [in] FdtBranch When searching for DT node name, restrict + the search to this Device Tree branch. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_NOT_FOUND Not found. + @retval EFI_UNSUPPORTED Unsupported. +**/ +EFI_STATUS +EFIAPI +SerialPortDispatcher ( + IN CONST FDT_HW_INFO_PARSER_HANDLE FdtParserHandle, + IN INT32 FdtBranch + ) +{ + EFI_STATUS Status; + INT32 SerialConsoleNode; + INT32 SerialDebugNode; + INT32 SerialNode; + UINT32 Index; + UINT32 SerialNodeCount; + UINT32 SerialNodesRemaining; + CM_ARM_SERIAL_PORT_INFO *GenericSerialInfo; + UINT32 GenericSerialIndex; + VOID *Fdt; + + if (FdtParserHandle == NULL) { + ASSERT (0); + return EFI_INVALID_PARAMETER; + } + + Fdt = FdtParserHandle->Fdt; + + // Count the number of serial-ports. + Status = FdtCountCompatNodeInBranch ( + Fdt, + FdtBranch, + &SerialCompatibleInfo, + &SerialNodeCount + ); + if (EFI_ERROR (Status)) { + ASSERT (0); + return Status; + } + + if (SerialNodeCount == 0) { + return EFI_NOT_FOUND; + } + + // Track remaining nodes separately as SerialNodeCount + // is used in for loop below and reducing SerialNodeCount + // would result in the Generic Serial port nodes not + // being found if the serial console port node is among + // the first few serial nodes. + SerialNodesRemaining = SerialNodeCount; + + // Identify the serial console port. + Status = GetSerialConsoleNode (Fdt, &SerialConsoleNode); + if (Status == EFI_NOT_FOUND) { + // No serial console. + SerialConsoleNode = -1; + } else if (EFI_ERROR (Status)) { + ASSERT (0); + return Status; + } else { + // Parse the console serial-port. + Status = ArmSerialPortInfoParser ( + FdtParserHandle, + SerialConsoleNode, + EArmObjSerialConsolePortInfo + ); + if (EFI_ERROR (Status)) { + ASSERT (0); + return Status; + } + + SerialNodesRemaining--; + } + + GenericSerialInfo = NULL; + if (SerialNodesRemaining > 1) { + // We have more than one serial port remaining. + // This means that the first serial port will + // be reserved as a debug port, and the remaining + // will be for general purpose use. + SerialNodesRemaining--; + GenericSerialInfo = AllocateZeroPool ( + SerialNodesRemaining * + sizeof (CM_ARM_SERIAL_PORT_INFO) + ); + if (GenericSerialInfo == NULL) { + ASSERT (0); + return EFI_OUT_OF_RESOURCES; + } + } + + SerialNode = FdtBranch; + SerialDebugNode = -1; + GenericSerialIndex = 0; + for (Index = 0; Index < SerialNodeCount; Index++) { + // Search the next serial-port node in the branch. + Status = FdtGetNextCompatNodeInBranch ( + Fdt, + FdtBranch, + &SerialCompatibleInfo, + &SerialNode + ); + if (EFI_ERROR (Status)) { + ASSERT (0); + if (Status == EFI_NOT_FOUND) { + // Should have found the node. + Status = EFI_ABORTED; + } + + goto exit_handler; + } + + // Ignore the serial console node. + if (SerialNode == SerialConsoleNode) { + continue; + } else if (SerialDebugNode == -1) { + // The first serial-port node, not being the console serial-port, + // will be the debug serial-port. + SerialDebugNode = SerialNode; + Status = ArmSerialPortInfoParser ( + FdtParserHandle, + SerialDebugNode, + EArmObjSerialDebugPortInfo + ); + if (EFI_ERROR (Status)) { + ASSERT (0); + goto exit_handler; + } + } else { + if (GenericSerialInfo == NULL) { + // Should not be possible. + ASSERT (0); + Status = EFI_ABORTED; + goto exit_handler; + } + + Status = SerialPortNodeParser ( + Fdt, + SerialNode, + &GenericSerialInfo[GenericSerialIndex++] + ); + if (EFI_ERROR (Status)) { + ASSERT (0); + goto exit_handler; + } + } + } // for + + if (GenericSerialIndex > 0) { + Status = ArmSerialPortInfoDispatch ( + FdtParserHandle, + GenericSerialInfo, + GenericSerialIndex, + EArmObjSerialPortInfo + ); + } + +exit_handler: + if (GenericSerialInfo != NULL) { + FreePool (GenericSerialInfo); + } + + return Status; +} diff --git a/DynamicTablesPkg/Library/FdtHwInfoParserLib/Serial/ArmSerialPortParser.h b/DynamicTablesPkg/Library/FdtHwInfoParserLib/Serial/ArmSerialPortParser.h new file mode 100644 index 0000000000..de08e57e6c --- /dev/null +++ b/DynamicTablesPkg/Library/FdtHwInfoParserLib/Serial/ArmSerialPortParser.h @@ -0,0 +1,47 @@ +/** @file + Arm Serial Port Parser. + + Copyright (c) 2021, ARM Limited. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + + @par Reference(s): + - linux/Documentation/devicetree/bindings/serial/serial.yaml + - linux/Documentation/devicetree/bindings/serial/8250.txt +**/ + +#ifndef ARM_SERIAL_PORT_PARSER_H_ +#define ARM_SERIAL_PORT_PARSER_H_ + +/** SerialPort dispatcher. + + This disptacher populates the CM_ARM_SERIAL_PORT_INFO structure for + the following CM_OBJ_ID: + - EArmObjSerialConsolePortInfo + - EArmObjSerialDebugPortInfo + - EArmObjSerialPortInfo + + A parser parses a Device Tree to populate a specific CmObj type. None, + one or many CmObj can be created by the parser. + The created CmObj are then handed to the parser's caller through the + HW_INFO_ADD_OBJECT interface. + This can also be a dispatcher. I.e. a function that not parsing a + Device Tree but calling other parsers. + + @param [in] FdtParserHandle A handle to the parser instance. + @param [in] FdtBranch When searching for DT node name, restrict + the search to this Device Tree branch. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_NOT_FOUND Not found. + @retval EFI_UNSUPPORTED Unsupported. +**/ +EFI_STATUS +EFIAPI +SerialPortDispatcher ( + IN CONST FDT_HW_INFO_PARSER_HANDLE FdtParserHandle, + IN INT32 FdtBranch + ); + +#endif // ARM_SERIAL_PORT_PARSER_H_