]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[spcr] Add support for the ACPI Serial Port Console Redirection table
authorMichael Brown <mcb30@ipxe.org>
Wed, 5 Nov 2025 14:12:57 +0000 (14:12 +0000)
committerMichael Brown <mcb30@ipxe.org>
Wed, 5 Nov 2025 14:12:57 +0000 (14:12 +0000)
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 <mcb30@ipxe.org>
src/core/serial.c
src/core/spcr.c [new file with mode: 0644]
src/include/ipxe/errfile.h
src/include/ipxe/ns16550.h
src/include/ipxe/serial.h
src/include/ipxe/spcr.h [new file with mode: 0644]

index 34ae4a17b31322f1911e7c839ff25be22451b0b9..728ad7785dbcc57d23604b7fd911d13c0d34d659 100644 (file)
@@ -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 (file)
index 0000000..88914ca
--- /dev/null
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2025 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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 <errno.h>
+#include <ipxe/serial.h>
+#include <ipxe/pci.h>
+#include <ipxe/ns16550.h>
+#include <ipxe/spcr.h>
+
+/** @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 );
index 1a8bddbcff09443af60f0f8255276341a77a44b6..0c28810ea733f4657269c09495f809814c99fc87 100644 (file)
@@ -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 )
index 693094866a73ce6b6411c0e97a5e1826efa05ea3..156249292c754ed77f58f11bf35a1d530a3bcf6d 100644 (file)
@@ -11,6 +11,9 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
 
 #include <ipxe/uart.h>
 
+/** Length of register region */
+#define NS16550_LEN 8
+
 /** Transmitter holding register */
 #define NS16550_THR 0x00
 
index 1eb58bbb8b5baf9b7c86c9522e9e741e1615d1bc..04347a89e45c137a2d462c9409ca0e2d4cdc54a5 100644 (file)
@@ -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 (file)
index 0000000..ff41a4b
--- /dev/null
@@ -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 <stdint.h>
+#include <ipxe/acpi.h>
+
+/** 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 */