DynamicTablesPkg: FdtHwInfoParser: Add Serial port parser

The Microsoft Debug Port Table 2 (DBG2), the Serial Port Console
Redirector (SPCR) table are mandatory tables required for booting
a standards-based operating system. The DBG2 table is used by the
OS debugger while the SPCR table is used to configure the serial
terminal. Additionally, the serial ports available on a platform
for generic use also need to be described in DSDT/SSDT for an OS
to be able to use the serial ports.

The Arm Base System Architecture 1.0 specification a lists of
supported serial port hardware for Arm Platforms. This list
includes the following serial port UARTs:
 - SBSA/Generic UART
 - a fully 16550 compatible UART.
Along, with these the PL011 UART is the most commonly used serial
port hardware on Arm platforms.

The serial port hardware information is described in the platform
Device Tree, the bindings for which can be found at:
 - 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

The FdtHwInfoParser implements a Serial Port Parser that parses
the platform Device Tree to create CM_ARM_SERIAL_PORT_INFO objects
with the following IDs:
 - EArmObjSerialConsolePortInfo (for use by SPCR)
 - EArmObjSerialDebugPortInfo (for use by DBG2)
 - EArmObjSerialPortInfo (for use as generic Serial Ports)

The Serial Port for use by SPCR is selected by parsing the Device
Tree for the '/chosen' node with the 'stdout-path' property. The
next Serial Port is selected for use as the Debug Serial Port and
the remaining serial ports are used as generic serial ports.

The CM_ARM_SERIAL_PORT_INFO objects are encapsulated in Configuration
Manager descriptor objects with the respective IDs and are added to
the platform information repository.

The platform Configuration Manager can then utilise this information
when generating the DBG2, SPCR and the SSDT serial port tables.

Signed-off-by: Pierre Gondois <Pierre.Gondois@arm.com>
Reviewed-by: Sami Mujawar <sami.mujawar@arm.com>
This commit is contained in:
Pierre Gondois 2021-12-09 10:31:59 +01:00 committed by mergify[bot]
parent 3ebe1ff5c9
commit 51941f7558
2 changed files with 680 additions and 0 deletions

View File

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

View File

@ -0,0 +1,47 @@
/** @file
Arm Serial Port Parser.
Copyright (c) 2021, 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
**/
#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_