| /** @file | |
| Arm Serial Port Parser. | |
| Copyright (c) 2021 - 2023, Arm Limited. All rights reserved.<BR> | |
| 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 <IndustryStandard/DebugPort2Table.h> | |
| #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 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; | |
| } |