/** @file
Arm Serial Port Parser.
Copyright (c) 2021 - 2023, 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 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;
}