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 */