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