--- /dev/null
+/*
+ * 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 );
--- /dev/null
+#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 */