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