From: Michael Brown Date: Wed, 5 Nov 2025 14:12:57 +0000 (+0000) Subject: [spcr] Add support for the ACPI Serial Port Console Redirection table X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=595ff240304e98a725624c71a5735c2013562810;p=thirdparty%2Fipxe.git [spcr] Add support for the ACPI Serial Port Console Redirection table The BIOS may provide an ACPI Serial Port Console Redirection (SPCR) table to describe the serial port to be used for early boot messages. Add support for parsing the SPCR and instantiating a 16550-based UART. We do not currently attempt to support other types of UART, since iPXE does not yet have drivers for other types. Signed-off-by: Michael Brown --- diff --git a/src/core/serial.c b/src/core/serial.c index 34ae4a17b..728ad7785 100644 --- a/src/core/serial.c +++ b/src/core/serial.c @@ -73,7 +73,7 @@ struct uart *serial_console = NULL; * * @ret uart Serial console UART, or NULL */ -static struct uart * serial_comconsole ( void ) { +struct uart * fixed_serial_console ( void ) { struct uart *uart = COMCONSOLE; unsigned int baud = COMSPEED; @@ -201,4 +201,4 @@ struct startup_fn serial_startup_fn __startup_fn ( STARTUP_EARLY ) = { }; PROVIDE_SERIAL_INLINE ( null, default_serial_console ); -PROVIDE_SERIAL ( fixed, default_serial_console, serial_comconsole ); +PROVIDE_SERIAL ( fixed, default_serial_console, fixed_serial_console ); diff --git a/src/core/spcr.c b/src/core/spcr.c new file mode 100644 index 000000000..88914cac7 --- /dev/null +++ b/src/core/spcr.c @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2025 Michael Brown . + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include +#include +#include +#include +#include + +/** @file + * + * ACPI Serial Port Console Redirection (SPCR) + * + */ + +#ifdef SERIAL_SPCR +#define SERIAL_PREFIX_spcr +#else +#define SERIAL_PREFIX_spcr __spcr_ +#endif + +/** SPCR-defined UART */ +static struct uart spcr_uart = { + .refcnt = REF_INIT ( ref_no_free ), + .name = "SPCR", +}; + +/** SPCR-defined 16550 UART */ +static struct ns16550_uart spcr_ns16550 = { + .clock = NS16550_CLK_DEFAULT, +}; + +/** Base baud rate for SPCR divisors */ +#define SPCR_BAUD_BASE 115200 + +/** SPCR baud rate divisors */ +static const uint8_t spcr_baud_divisor[SPCR_BAUD_MAX] = { + [SPCR_BAUD_2400] = ( SPCR_BAUD_BASE / 2400 ), + [SPCR_BAUD_4800] = ( SPCR_BAUD_BASE / 4800 ), + [SPCR_BAUD_9600] = ( SPCR_BAUD_BASE / 9600 ), + [SPCR_BAUD_19200] = ( SPCR_BAUD_BASE / 19200 ), + [SPCR_BAUD_38400] = ( SPCR_BAUD_BASE / 38400 ), + [SPCR_BAUD_57600] = ( SPCR_BAUD_BASE / 57600 ), + [SPCR_BAUD_115200] = ( SPCR_BAUD_BASE / 115200 ), +}; + +/** + * Configure 16550-based serial console + * + * @v spcr SPCR table + * @v uart UART to configure + * @ret rc Return status code + */ +static int spcr_16550 ( struct spcr_table *spcr, struct uart *uart ) { + struct ns16550_uart *ns16550 = &spcr_ns16550; + + /* Set base address */ + ns16550->base = acpi_ioremap ( &spcr->base, NS16550_LEN ); + if ( ! ns16550->base ) { + DBGC ( uart, "SPCR could not map registers\n" ); + return -ENODEV; + } + + /* Set clock frequency, if specified */ + if ( spcr->clock ) + ns16550->clock = le32_to_cpu ( spcr->clock ); + + /* Configure UART as a 16550 */ + uart->op = &ns16550_operations; + uart->priv = ns16550; + + return 0; +} + +/** + * Identify default serial console + * + * @ret uart Default serial console UART, or NULL + */ +static struct uart * spcr_console ( void ) { + struct uart *uart = &spcr_uart; + struct spcr_table *spcr; + unsigned int baud; + int rc; + + /* Locate SPCR table */ + spcr = container_of ( acpi_table ( SPCR_SIGNATURE, 0 ), + struct spcr_table, acpi ); + if ( ! spcr ) { + DBGC ( uart, "SPCR found no table\n" ); + goto err_table; + } + DBGC2 ( uart, "SPCR found table:\n" ); + DBGC2_HDA ( uart, 0, spcr, sizeof ( *spcr ) ); + DBGC ( uart, "SPCR is type %d at %02x:%08llx\n", + spcr->type, spcr->base.type, + ( ( unsigned long long ) le64_to_cpu ( spcr->base.address ) ) ); + if ( spcr->pci_vendor_id != cpu_to_le16 ( PCI_ANY_ID ) ) { + DBGC ( uart, "SPCR is PCI " PCI_FMT " (%04x:%04x)\n", + spcr->pci_segment, spcr->pci_bus, spcr->pci_dev, + spcr->pci_func, le16_to_cpu ( spcr->pci_vendor_id ), + le16_to_cpu ( spcr->pci_device_id ) ); + } + + /* Get baud rate */ + baud = 0; + if ( le32_to_cpu ( spcr->acpi.length ) >= + ( offsetof ( typeof ( *spcr ), precise ) + + sizeof ( spcr->precise ) ) ) { + baud = le32_to_cpu ( spcr->precise ); + if ( baud ) + DBGC ( uart, "SPCR has precise baud rate %d\n", baud ); + } + if ( ( ! baud ) && spcr->baud && ( spcr->baud < SPCR_BAUD_MAX ) ) { + baud = ( SPCR_BAUD_BASE / spcr_baud_divisor[spcr->baud] ); + DBGC ( uart, "SPCR has baud rate %d\n", baud ); + } + uart->baud = baud; + + /* Initialise according to type */ + switch ( spcr->type ) { + case SPCR_TYPE_16550: + case SPCR_TYPE_16450: + if ( ( rc = spcr_16550 ( spcr, uart ) ) != 0 ) + goto err_type; + break; + default: + DBGC ( uart, "SPCR unsupported type %d\n", spcr->type ); + goto err_type; + } + + return uart; + + err_type: + err_table: + /* Fall back to using fixed serial console */ + return fixed_serial_console(); +} + +PROVIDE_SERIAL ( spcr, default_serial_console, spcr_console ); diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index 1a8bddbcf..0c28810ea 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -88,6 +88,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_efi_table ( ERRFILE_CORE | 0x00300000 ) #define ERRFILE_efi_connect ( ERRFILE_CORE | 0x00310000 ) #define ERRFILE_gpio ( ERRFILE_CORE | 0x00320000 ) +#define ERRFILE_spcr ( ERRFILE_CORE | 0x00330000 ) #define ERRFILE_eisa ( ERRFILE_DRIVER | 0x00000000 ) #define ERRFILE_isa ( ERRFILE_DRIVER | 0x00010000 ) diff --git a/src/include/ipxe/ns16550.h b/src/include/ipxe/ns16550.h index 693094866..156249292 100644 --- a/src/include/ipxe/ns16550.h +++ b/src/include/ipxe/ns16550.h @@ -11,6 +11,9 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include +/** Length of register region */ +#define NS16550_LEN 8 + /** Transmitter holding register */ #define NS16550_THR 0x00 diff --git a/src/include/ipxe/serial.h b/src/include/ipxe/serial.h index 1eb58bbb8..04347a89e 100644 --- a/src/include/ipxe/serial.h +++ b/src/include/ipxe/serial.h @@ -67,4 +67,6 @@ struct uart * default_serial_console ( void ); extern struct uart *serial_console; +extern struct uart * fixed_serial_console ( void ); + #endif /* _IPXE_SERIAL_H */ diff --git a/src/include/ipxe/spcr.h b/src/include/ipxe/spcr.h new file mode 100644 index 000000000..ff41a4b89 --- /dev/null +++ b/src/include/ipxe/spcr.h @@ -0,0 +1,90 @@ +#ifndef _IPXE_SPCR_H +#define _IPXE_SPCR_H + +/** @file + * + * ACPI Serial Port Console Redirection (SPCR) + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include +#include + +/** Serial Port Console Redirection table signature */ +#define SPCR_SIGNATURE ACPI_SIGNATURE ( 'S', 'P', 'C', 'R' ) + +/** A Serial Port Console Redirection table */ +struct spcr_table { + /** ACPI header */ + struct acpi_header acpi; + /** Interface type */ + uint8_t type; + /** Reserved */ + uint8_t reserved_a[3]; + /** Base address */ + struct acpi_address base; + /** Reserved */ + uint8_t reserved_b[6]; + /** Baud rate + * + * 0: leave unchanged + * 1: 2400 = 115200 / 48 (not defined in standard) + * 2: 4800 = 115200 / 24 (not defined in standard) + * 3: 9600 = 115200 / 12 + * 4: 19200 = 115200 / 6 + * 5: 38400 = 115200 / 3 (not defined in standard) + * 6: 57600 = 115200 / 2 + * 7: 115200 = 115200 / 1 + */ + uint8_t baud; + /** Parity */ + uint8_t parity; + /** Stop bits */ + uint8_t stop; + /** Flow control */ + uint8_t flow; + /** Terminal type */ + uint8_t terminal; + /** Language */ + uint8_t lang; + /** PCI device ID */ + uint16_t pci_device_id; + /** PCI vendor ID */ + uint16_t pci_vendor_id; + /** PCI bus number */ + uint8_t pci_bus; + /** PCI device number */ + uint8_t pci_dev; + /** PCI function number */ + uint8_t pci_func; + /** Reserved */ + uint8_t reserved_c[4]; + /** PCI segment */ + uint8_t pci_segment; + /** Clock frequency */ + uint32_t clock; + /** Precise baud rate */ + uint32_t precise; + /** Reserved */ + uint8_t reserved_d[4]; +} __attribute__ (( packed )); + +/* SPCR interface types */ +#define SPCR_TYPE_16550 0x0000 /**< 16550-compatible */ +#define SPCR_TYPE_16450 0x0001 /**< 16450-compatible */ + +/** SPCR baud rates */ +enum spcr_baud { + SPCR_BAUD_2400 = 1, + SPCR_BAUD_4800 = 2, + SPCR_BAUD_9600 = 3, + SPCR_BAUD_19200 = 4, + SPCR_BAUD_38400 = 5, + SPCR_BAUD_57600 = 6, + SPCR_BAUD_115200 = 7, + SPCR_BAUD_MAX +}; + +#endif /* _IPXE_SPCR_H */