| /* | |
| * 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: | |
| case SPCR_TYPE_16550_GAS: | |
| 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 ); |