From: Michael Brown Date: Tue, 17 Jun 2025 13:28:18 +0000 (+0100) Subject: [uart] Allow for the existence of non-16550 UARTs X-Git-Url: http://git.ipfire.org/gitweb/gitweb.cgi?a=commitdiff_plain;h=6c8fb4b89d49c40339fe61b7ec549d90f1ce9480;p=thirdparty%2Fipxe.git [uart] Allow for the existence of non-16550 UARTs Remove the assumption that all platforms use a fixed number of 16550 UARTs identifiable by a simple numeric index. Create an abstraction allowing for dynamic instantiation and registration of any number of arbitrary UART models. The common case of the serial console on x86 uses a single fixed UART specified at compile time. Avoid unnecessarily dragging in the dynamic instantiation code in this use case by allowing COMCONSOLE to refer to a single static UART object representing the relevant port. When selecting a UART by command-line argument (as used in the "gdbstub serial " command), allow the UART to be specified as either a numeric index (to retain backwards compatiblity) or a case-insensitive port name such as "COM2". Signed-off-by: Michael Brown --- diff --git a/src/Makefile b/src/Makefile index 6367b13ea..f3d378e05 100644 --- a/src/Makefile +++ b/src/Makefile @@ -94,6 +94,7 @@ SRCDIRS += drivers/infiniband/mlx_utils/mlx_lib/mlx_link_speed SRCDIRS += drivers/infiniband/mlx_utils/mlx_lib/mlx_mtu SRCDIRS += drivers/infiniband/mlx_nodnic/src SRCDIRS += drivers/usb +SRCDIRS += drivers/uart SRCDIRS += interface/pxe interface/efi interface/smbios SRCDIRS += interface/bofm SRCDIRS += interface/xen diff --git a/src/arch/x86/core/x86_uart.c b/src/arch/x86/core/x86_uart.c index e455775bf..2580b931b 100644 --- a/src/arch/x86/core/x86_uart.c +++ b/src/arch/x86/core/x86_uart.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 Michael Brown . + * 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 @@ -29,41 +29,45 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); * */ -#include -#include +#include +#include +#include -/** UART port bases */ -static uint16_t uart_base[] = { - [COM1] = 0x3f8, - [COM2] = 0x2f8, - [COM3] = 0x3e8, - [COM4] = 0x2e8, -}; +/** Define a fixed ISA UART */ +#define ISA_UART( NAME, BASE ) \ + struct ns16550_uart NAME = { \ + .uart = { \ + .refcnt = REF_INIT ( ref_no_free ), \ + .name = #NAME, \ + .op = &ns16550_operations, \ + }, \ + .base = ( ( void * ) (BASE) ), \ + } + +/* Fixed ISA UARTs */ +ISA_UART ( com1, COM1_BASE ); +ISA_UART ( com2, COM2_BASE ); +ISA_UART ( com3, COM3_BASE ); +ISA_UART ( com4, COM4_BASE ); /** - * Select UART port + * Register fixed ISA UARTs * - * @v uart UART - * @v port Port number, or 0 to disable * @ret rc Return status code */ -int uart_select ( struct uart *uart, unsigned int port ) { +int uart_register_fixed ( void ) { + static struct uart *ports[] = { COM1, COM2, COM3, COM4 }; + unsigned int i; int rc; - /* Set new UART base */ - if ( port >= ( sizeof ( uart_base ) / sizeof ( uart_base[0] ) ) ) { - rc = -ENODEV; - goto err; + /* Register all fixed ISA UARTs */ + for ( i = 0 ; i < ( sizeof ( ports ) / sizeof ( ports[0] ) ) ; i++ ) { + if ( ( rc = uart_register ( ports[i] ) ) != 0 ) { + DBGC ( ports[i], "UART could not register %s: %s\n", + ports[i]->name, strerror ( rc ) ); + return rc; + } } - uart->base = ( ( void * ) ( intptr_t ) uart_base[port] ); - - /* Check that UART exists */ - if ( ( rc = uart_exists ( uart ) ) != 0 ) - goto err; return 0; - - err: - uart->base = NULL; - return rc; } diff --git a/src/arch/x86/include/bits/ns16550.h b/src/arch/x86/include/bits/ns16550.h new file mode 100644 index 000000000..9f7d741ae --- /dev/null +++ b/src/arch/x86/include/bits/ns16550.h @@ -0,0 +1,60 @@ +#ifndef _BITS_NS16550_H +#define _BITS_NS16550_H + +/** @file + * + * 16550-compatible UART + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include +#include + +/** + * Write to UART register + * + * @v ns16550 16550 UART + * @v address Register address + * @v data Data + */ +static inline __attribute__ (( always_inline )) void +ns16550_write ( struct ns16550_uart *ns16550, unsigned int address, + uint8_t data ) { + + outb ( data, ( ns16550->base + address ) ); +} + +/** + * Read from UART register + * + * @v ns16550 16550 UART + * @v address Register address + * @ret data Data + */ +static inline __attribute__ (( always_inline )) uint8_t +ns16550_read ( struct ns16550_uart *ns16550, unsigned int address ) { + + return inb ( ns16550->base + address ); +} + +/* Fixed ISA serial port base addresses */ +#define COM1_BASE 0x3f8 +#define COM2_BASE 0x2f8 +#define COM3_BASE 0x3e8 +#define COM4_BASE 0x2e8 + +/* Fixed ISA serial ports */ +extern struct ns16550_uart com1; +extern struct ns16550_uart com2; +extern struct ns16550_uart com3; +extern struct ns16550_uart com4; + +/* Fixed ISA serial port names */ +#define COM1 &com1.uart +#define COM2 &com2.uart +#define COM3 &com3.uart +#define COM4 &com4.uart + +#endif /* _BITS_NS16550_H */ diff --git a/src/arch/x86/include/bits/uart.h b/src/arch/x86/include/bits/uart.h deleted file mode 100644 index e09cd3f4c..000000000 --- a/src/arch/x86/include/bits/uart.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef _BITS_UART_H -#define _BITS_UART_H - -/** @file - * - * 16550-compatible UART - * - */ - -FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); - -#include -#include - -/** - * Write to UART register - * - * @v uart UART - * @v addr Register address - * @v data Data - */ -static inline __attribute__ (( always_inline )) void -uart_write ( struct uart *uart, unsigned int addr, uint8_t data ) { - outb ( data, ( uart->base + addr ) ); -} - -/** - * Read from UART register - * - * @v uart UART - * @v addr Register address - * @ret data Data - */ -static inline __attribute__ (( always_inline )) uint8_t -uart_read ( struct uart *uart, unsigned int addr ) { - return inb ( uart->base + addr ); -} - -extern int uart_select ( struct uart *uart, unsigned int port ); - -#endif /* _BITS_UART_H */ diff --git a/src/arch/x86/interface/syslinux/comboot_call.c b/src/arch/x86/interface/syslinux/comboot_call.c index f7cceb30f..97bdaeae3 100644 --- a/src/arch/x86/interface/syslinux/comboot_call.c +++ b/src/arch/x86/interface/syslinux/comboot_call.c @@ -37,6 +37,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include #include #include +#include #include #include #include @@ -253,8 +254,8 @@ static __asmcall __used void int21 ( struct i386_all_regs *ix86 ) { break; case 0x04: /* Write Character to Serial Port */ - if ( serial_console.base ) { - uart_transmit ( &serial_console, ix86->regs.dl ); + if ( serial_console ) { + uart_transmit ( serial_console, ix86->regs.dl ); ix86->flags &= ~CF; } break; @@ -445,9 +446,13 @@ static __asmcall __used void int22 ( struct i386_all_regs *ix86 ) { break; case 0x000B: /* Get Serial Console Configuration */ - if ( serial_console.base ) { - ix86->regs.dx = ( ( intptr_t ) serial_console.base ); - ix86->regs.cx = serial_console.divisor; + if ( serial_console ) { + struct ns16550_uart *comport = + container_of ( serial_console, + struct ns16550_uart, uart ); + + ix86->regs.dx = ( ( intptr_t ) comport->base ); + ix86->regs.cx = comport->divisor; ix86->regs.bx = 0; ix86->flags &= ~CF; } @@ -685,4 +690,4 @@ void unhook_comboot_interrupts ( ) { } /* Avoid dragging in serial console support unconditionally */ -struct uart serial_console __attribute__ (( weak )); +struct uart *serial_console __attribute__ (( weak )); diff --git a/src/core/gdbserial.c b/src/core/gdbserial.c index 1edc28109..0f74e5fe0 100644 --- a/src/core/gdbserial.c +++ b/src/core/gdbserial.c @@ -25,20 +25,12 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include -#include #include #include #include #include #include -/* UART port number */ -#ifdef COMCONSOLE -#define GDBSERIAL_PORT COMCONSOLE -#else -#define GDBSERIAL_PORT 0 -#endif - /* UART baud rate */ #ifdef COMPRESERVE #define GDBSERIAL_BAUD 0 @@ -47,37 +39,30 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #endif /** GDB serial UART */ -static struct uart gdbserial_uart; +static struct uart *gdbserial_uart; struct gdb_transport serial_gdb_transport __gdb_transport; static size_t gdbserial_recv ( char *buf, size_t len ) { assert ( len > 0 ); - while ( ! uart_data_ready ( &gdbserial_uart ) ) {} - buf[0] = uart_receive ( &gdbserial_uart ); + while ( ! uart_data_ready ( gdbserial_uart ) ) {} + buf[0] = uart_receive ( gdbserial_uart ); return 1; } static void gdbserial_send ( const char *buf, size_t len ) { while ( len-- > 0 ) { - uart_transmit ( &gdbserial_uart, *buf++ ); + uart_transmit ( gdbserial_uart, *buf++ ); } } static int gdbserial_init ( int argc, char **argv ) { - unsigned int port; - char *endp; - - if ( argc == 0 ) { - port = GDBSERIAL_PORT; - } else if ( argc == 1 ) { - port = strtoul ( argv[0], &endp, 10 ); - if ( *endp ) { - printf ( "serial: invalid port\n" ); - return 1; - } + const char *port; + + if ( argc == 1 ) { + port = argv[0]; } else { printf ( "serial: syntax \n" ); return 1; @@ -98,14 +83,19 @@ struct gdb_transport serial_gdb_transport __gdb_transport = { .send = gdbserial_send, }; -struct gdb_transport * gdbserial_configure ( unsigned int port, +struct gdb_transport * gdbserial_configure ( const char *name, unsigned int baud ) { int rc; - if ( ( rc = uart_select ( &gdbserial_uart, port ) ) != 0 ) + uart_put ( gdbserial_uart ); + gdbserial_uart = NULL; + + gdbserial_uart = uart_find ( name ); + if ( ! gdbserial_uart ) return NULL; + uart_get ( gdbserial_uart ); - if ( ( rc = uart_init ( &gdbserial_uart, baud ) ) != 0 ) + if ( ( rc = uart_init ( gdbserial_uart, baud ) ) != 0 ) return NULL; return &serial_gdb_transport; diff --git a/src/core/serial.c b/src/core/serial.c index 2866681a8..e718f0d42 100644 --- a/src/core/serial.c +++ b/src/core/serial.c @@ -35,6 +35,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include #include +#include #include #include @@ -44,22 +45,21 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define CONSOLE_SERIAL ( CONSOLE_USAGE_ALL & ~CONSOLE_USAGE_LOG ) #endif -/* UART port number */ -#ifdef COMCONSOLE -#define CONSOLE_PORT COMCONSOLE -#else -#define CONSOLE_PORT 0 +/* Serial console UART */ +#ifndef COMCONSOLE +#define COMCONSOLE NULL #endif -/* UART baud rate */ -#ifdef COMPRESERVE -#define CONSOLE_BAUD 0 -#else -#define CONSOLE_BAUD COMSPEED +/* Serial console baud rate */ +#ifndef COMSPEED +#define COMSPEED 0 #endif -/** Serial console UART */ -struct uart serial_console; +/** Default serial console UART */ +static struct uart * const default_serial_console = COMCONSOLE; + +/** Active serial console UART */ +struct uart *serial_console; /** * Print a character to serial console @@ -69,11 +69,11 @@ struct uart serial_console; static void serial_putchar ( int character ) { /* Do nothing if we have no UART */ - if ( ! serial_console.base ) + if ( ! serial_console ) return; /* Transmit character */ - uart_transmit ( &serial_console, character ); + uart_transmit ( serial_console, character ); } /** @@ -85,14 +85,14 @@ static int serial_getchar ( void ) { uint8_t data; /* Do nothing if we have no UART */ - if ( ! serial_console.base ) + if ( ! serial_console ) return 0; /* Wait for data to be ready */ - while ( ! uart_data_ready ( &serial_console ) ) {} + while ( ! uart_data_ready ( serial_console ) ) {} /* Receive data */ - data = uart_receive ( &serial_console ); + data = uart_receive ( serial_console ); /* Strip any high bit and convert DEL to backspace */ data &= 0x7f; @@ -111,11 +111,11 @@ static int serial_getchar ( void ) { static int serial_iskey ( void ) { /* Do nothing if we have no UART */ - if ( ! serial_console.base ) + if ( ! serial_console ) return 0; /* Check UART */ - return uart_data_ready ( &serial_console ); + return uart_data_ready ( serial_console ); } /** Serial console */ @@ -128,25 +128,23 @@ struct console_driver serial_console_driver __console_driver = { /** Initialise serial console */ static void serial_init ( void ) { + struct uart *uart = default_serial_console; int rc; /* Do nothing if we have no default port */ - if ( ! CONSOLE_PORT ) + if ( ! uart ) return; - /* Select UART */ - if ( ( rc = uart_select ( &serial_console, CONSOLE_PORT ) ) != 0 ) { - DBG ( "Could not select UART %d: %s\n", - CONSOLE_PORT, strerror ( rc ) ); - return; - } - /* Initialise UART */ - if ( ( rc = uart_init ( &serial_console, CONSOLE_BAUD ) ) != 0 ) { - DBG ( "Could not initialise UART %d baud %d: %s\n", - CONSOLE_PORT, CONSOLE_BAUD, strerror ( rc ) ); + if ( ( rc = uart_init ( uart, COMSPEED ) ) != 0 ) { + DBGC ( uart, "SERIAL could not initialise %s baud %d: %s\n", + uart->name, COMSPEED, strerror ( rc ) ); return; } + + /* Record UART as serial console */ + serial_console = uart; + DBGC ( uart, "SERIAL using %s\n", uart->name ); } /** @@ -157,11 +155,11 @@ static void serial_init ( void ) { static void serial_shutdown ( int flags __unused ) { /* Do nothing if we have no UART */ - if ( ! serial_console.base ) + if ( ! serial_console ) return; /* Flush any pending output */ - uart_flush ( &serial_console ); + uart_flush ( serial_console ); /* Leave console enabled; it's still usable */ } diff --git a/src/core/uart.c b/src/core/uart.c index 4dc307fce..a645bd398 100644 --- a/src/core/uart.c +++ b/src/core/uart.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 Michael Brown . + * 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 @@ -25,125 +25,139 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** @file * - * 16550-compatible UART + * Generic UARTs * */ -#include +#include +#include #include #include -/** Timeout for transmit holding register to become empty */ -#define UART_THRE_TIMEOUT_MS 100 +/** List of registered UARTs */ +LIST_HEAD ( uarts ); -/** Timeout for transmitter to become empty */ -#define UART_TEMT_TIMEOUT_MS 1000 +static void null_uart_transmit ( struct uart *uart __unused, + uint8_t byte __unused ) { +} + +static int null_uart_data_ready ( struct uart *uart __unused ) { + return 0; +} + +static uint8_t null_uart_receive ( struct uart *uart __unused ) { + return 0; +} + +static int null_uart_init ( struct uart *uart __unused, + unsigned int baud __unused ) { + return 0; +} + +static void null_uart_flush ( struct uart *uart __unused ) { +} + +/** Null UART operations */ +struct uart_operations null_uart_operations = { + .transmit = null_uart_transmit, + .data_ready = null_uart_data_ready, + .receive = null_uart_receive, + .init = null_uart_init, + .flush = null_uart_flush, +}; /** - * Transmit data + * Allocate UART * - * @v uart UART - * @v data Data + * @v priv_len Length of private data + * @ret uart UART, or NULL on error */ -void uart_transmit ( struct uart *uart, uint8_t data ) { - unsigned int i; - uint8_t lsr; - - /* Wait for transmitter holding register to become empty */ - for ( i = 0 ; i < UART_THRE_TIMEOUT_MS ; i++ ) { - lsr = uart_read ( uart, UART_LSR ); - if ( lsr & UART_LSR_THRE ) - break; - mdelay ( 1 ); - } +struct uart * alloc_uart ( size_t priv_len ) { + struct uart *uart; + + /* Allocate and initialise UART */ + uart = zalloc ( sizeof ( *uart ) + priv_len ); + if ( ! uart ) + return NULL; + uart->priv = ( ( ( void * ) uart ) + sizeof ( *uart ) ); - /* Transmit data (even if we timed out) */ - uart_write ( uart, UART_THR, data ); + return uart; } /** - * Flush data + * Register fixed UARTs (when not provided by platform) * - * @v uart UART + * @ret rc Return status code */ -void uart_flush ( struct uart *uart ) { - unsigned int i; - uint8_t lsr; - - /* Wait for transmitter and receiver to become empty */ - for ( i = 0 ; i < UART_TEMT_TIMEOUT_MS ; i++ ) { - uart_read ( uart, UART_RBR ); - lsr = uart_read ( uart, UART_LSR ); - if ( ( lsr & UART_LSR_TEMT ) && ! ( lsr & UART_LSR_DR ) ) - break; - } +__weak int uart_register_fixed ( void ) { + + return 0; } /** - * Check for existence of UART + * Register UART * * @v uart UART * @ret rc Return status code */ -int uart_exists ( struct uart *uart ) { - - /* Fail if no UART port is defined */ - if ( ! uart->base ) - return -ENODEV; +int uart_register ( struct uart *uart ) { - /* Fail if UART scratch register seems not to be present */ - uart_write ( uart, UART_SCR, 0x18 ); - if ( uart_read ( uart, UART_SCR ) != 0x18 ) - return -ENODEV; - uart_write ( uart, UART_SCR, 0xae ); - if ( uart_read ( uart, UART_SCR ) != 0xae ) - return -ENODEV; + /* Add to list of registered UARTs */ + uart_get ( uart ); + list_add_tail ( &uart->list, &uarts ); + DBGC ( uart, "UART %s registered\n", uart->name ); return 0; } /** - * Initialise UART + * Unregister UART * * @v uart UART - * @v baud Baud rate, or zero to leave unchanged - * @ret rc Return status code */ -int uart_init ( struct uart *uart, unsigned int baud ) { - uint8_t dlm; - uint8_t dll; +void uart_unregister ( struct uart *uart ) { + + /* Remove from list of registered UARTs */ + list_del ( &uart->list ); + uart_put ( uart ); +} + +/** + * Find named UART + * + * @v name UART name + * @ret uart UART, or NULL if not found + */ +struct uart * uart_find ( const char *name ) { + struct uart *uart; + unsigned int index; + char *endp; int rc; - /* Check for existence of UART */ - if ( ( rc = uart_exists ( uart ) ) != 0 ) - return rc; - - /* Configure divisor and line control register, if applicable */ - uart_write ( uart, UART_LCR, ( UART_LCR_8N1 | UART_LCR_DLAB ) ); - if ( baud ) { - uart->divisor = ( UART_MAX_BAUD / baud ); - dlm = ( ( uart->divisor >> 8 ) & 0xff ); - dll = ( ( uart->divisor >> 0 ) & 0xff ); - uart_write ( uart, UART_DLM, dlm ); - uart_write ( uart, UART_DLL, dll ); - } else { - dlm = uart_read ( uart, UART_DLM ); - dll = uart_read ( uart, UART_DLL ); - uart->divisor = ( ( dlm << 8 ) | dll ); + /* Register fixed platform UARTs if not already registered */ + if ( list_empty ( &uarts ) ) { + if ( ( rc = uart_register_fixed() ) != 0 ) { + DBGC ( &uarts, "UART could not register fixed UARTs: " + "%s\n", strerror ( rc ) ); + /* Continue anyway */ + } } - uart_write ( uart, UART_LCR, UART_LCR_8N1 ); - /* Disable interrupts */ - uart_write ( uart, UART_IER, 0 ); + /* Try parsing name as a numeric index */ + index = strtoul ( name, &endp, 10 ); - /* Enable FIFOs */ - uart_write ( uart, UART_FCR, UART_FCR_FE ); + /* Find matching UART, if any */ + list_for_each_entry ( uart, &uarts, list ) { - /* Assert DTR and RTS */ - uart_write ( uart, UART_MCR, ( UART_MCR_DTR | UART_MCR_RTS ) ); + /* Check for a matching name */ + if ( strcasecmp ( name, uart->name ) == 0 ) + return uart; - /* Flush any stale data */ - uart_flush ( uart ); + /* Check for a matching numeric index */ + if ( ( *endp == '\0' ) && ( index-- == 0 ) ) + return uart; + } - return 0; + DBGC ( &uarts, "UART %s not found\n", name ); + return NULL; } diff --git a/src/drivers/uart/ns16550.c b/src/drivers/uart/ns16550.c new file mode 100644 index 000000000..58a71261b --- /dev/null +++ b/src/drivers/uart/ns16550.c @@ -0,0 +1,179 @@ +/* + * 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 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 ); + +/** @file + * + * 16550-compatible UART + * + */ + +#include +#include +#include +#include + +/** Timeout for transmit holding register to become empty */ +#define NS16550_THRE_TIMEOUT_MS 100 + +/** Timeout for transmitter to become empty */ +#define NS16550_TEMT_TIMEOUT_MS 1000 + +/** + * Transmit data + * + * @v uart UART + * @v data Data + */ +static void ns16550_transmit ( struct uart *uart, uint8_t data ) { + struct ns16550_uart *ns16550 = + container_of ( uart, struct ns16550_uart, uart ); + unsigned int i; + uint8_t lsr; + + /* Wait for transmitter holding register to become empty */ + for ( i = 0 ; i < NS16550_THRE_TIMEOUT_MS ; i++ ) { + lsr = ns16550_read ( ns16550, NS16550_LSR ); + if ( lsr & NS16550_LSR_THRE ) + break; + mdelay ( 1 ); + } + + /* Transmit data (even if we timed out) */ + ns16550_write ( ns16550, NS16550_THR, data ); +} + +/** + * Check if data is ready + * + * @v uart UART + * @ret ready Data is ready + */ +static int ns16550_data_ready ( struct uart *uart ) { + struct ns16550_uart *ns16550 = + container_of ( uart, struct ns16550_uart, uart ); + uint8_t lsr; + + /* Check for receive data ready */ + lsr = ns16550_read ( ns16550, NS16550_LSR ); + return ( lsr & NS16550_LSR_DR ); +} + +/** + * Receive data + * + * @v uart UART + * @ret data Data + */ +static uint8_t ns16550_receive ( struct uart *uart ) { + struct ns16550_uart *ns16550 = + container_of ( uart, struct ns16550_uart, uart ); + uint8_t rbr; + + /* Receive byte */ + rbr = ns16550_read ( ns16550, NS16550_RBR ); + return rbr; +} + +/** + * Flush transmitted data + * + * @v uart UART + */ +static void ns16550_flush ( struct uart *uart ) { + struct ns16550_uart *ns16550 = + container_of ( uart, struct ns16550_uart, uart ); + unsigned int i; + uint8_t lsr; + + /* Wait for transmitter to become empty */ + for ( i = 0 ; i < NS16550_TEMT_TIMEOUT_MS ; i++ ) { + lsr = ns16550_read ( ns16550, NS16550_LSR ); + if ( lsr & NS16550_LSR_TEMT ) + break; + } +} + +/** + * Initialise UART + * + * @v uart UART + * @v baud Baud rate, or zero to leave unchanged + * @ret rc Return status code + */ +static int ns16550_init ( struct uart *uart, unsigned int baud ) { + struct ns16550_uart *ns16550 = + container_of ( uart, struct ns16550_uart, uart ); + uint8_t dlm; + uint8_t dll; + + /* Fail if UART scratch register seems not to be present */ + ns16550_write ( ns16550, NS16550_SCR, 0x18 ); + if ( ns16550_read ( ns16550, NS16550_SCR ) != 0x18 ) + return -ENODEV; + ns16550_write ( ns16550, NS16550_SCR, 0xae ); + if ( ns16550_read ( ns16550, NS16550_SCR ) != 0xae ) + return -ENODEV; + + /* Configure divisor and line control register, if applicable */ + ns16550_write ( ns16550, NS16550_LCR, + ( NS16550_LCR_8N1 | NS16550_LCR_DLAB ) ); + if ( baud ) { + ns16550->divisor = ( NS16550_MAX_BAUD / baud ); + dlm = ( ( ns16550->divisor >> 8 ) & 0xff ); + dll = ( ( ns16550->divisor >> 0 ) & 0xff ); + ns16550_write ( ns16550, NS16550_DLM, dlm ); + ns16550_write ( ns16550, NS16550_DLL, dll ); + } else { + dlm = ns16550_read ( ns16550, NS16550_DLM ); + dll = ns16550_read ( ns16550, NS16550_DLL ); + ns16550->divisor = ( ( dlm << 8 ) | dll ); + } + ns16550_write ( ns16550, NS16550_LCR, NS16550_LCR_8N1 ); + + /* Disable interrupts */ + ns16550_write ( ns16550, NS16550_IER, 0 ); + + /* Enable FIFOs */ + ns16550_write ( ns16550, NS16550_FCR, NS16550_FCR_FE ); + + /* Assert DTR and RTS */ + ns16550_write ( ns16550, NS16550_MCR, + ( NS16550_MCR_DTR | NS16550_MCR_RTS ) ); + + /* Flush any stale received data */ + while ( ns16550_data_ready ( uart ) ) + ns16550_receive ( uart ); + + return 0; +} + +/** 16550 UART operations */ +struct uart_operations ns16550_operations = { + .transmit = ns16550_transmit, + .data_ready = ns16550_data_ready, + .receive = ns16550_receive, + .init = ns16550_init, + .flush = ns16550_flush, +}; diff --git a/src/include/bits/uart.h b/src/include/bits/ns16550.h similarity index 56% rename from src/include/bits/uart.h rename to src/include/bits/ns16550.h index e132d5c3d..4b3e30c76 100644 --- a/src/include/bits/uart.h +++ b/src/include/bits/ns16550.h @@ -1,9 +1,9 @@ -#ifndef _BITS_UART_H -#define _BITS_UART_H +#ifndef _BITS_NS16550_H +#define _BITS_NS16550_H /** @file * - * Dummy architecture-specific UART + * Dummy architecture-specific 16550-compatible UART * * This file is included only if the architecture does not provide its * own version of this file. @@ -12,4 +12,4 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); -#endif /* _BITS_UART_H */ +#endif /* _BITS_NS16550_H */ diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index d00191fbe..1a39af43f 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -109,6 +109,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_spi_bit ( ERRFILE_DRIVER | 0x00130000 ) #define ERRFILE_nvsvpd ( ERRFILE_DRIVER | 0x00140000 ) #define ERRFILE_uart ( ERRFILE_DRIVER | 0x00150000 ) +#define ERRFILE_ns16550 ( ERRFILE_DRIVER | 0x00160000 ) #define ERRFILE_3c509 ( ERRFILE_DRIVER | 0x00200000 ) #define ERRFILE_bnx2 ( ERRFILE_DRIVER | 0x00210000 ) diff --git a/src/include/ipxe/gdbserial.h b/src/include/ipxe/gdbserial.h index 166eb4f0d..62cc16014 100644 --- a/src/include/ipxe/gdbserial.h +++ b/src/include/ipxe/gdbserial.h @@ -11,7 +11,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); struct gdb_transport; -extern struct gdb_transport * gdbserial_configure ( unsigned int port, +extern struct gdb_transport * gdbserial_configure ( const char *port, unsigned int baud ); #endif /* _IPXE_GDBSERIAL_H */ diff --git a/src/include/ipxe/ns16550.h b/src/include/ipxe/ns16550.h new file mode 100644 index 000000000..f7bb55a84 --- /dev/null +++ b/src/include/ipxe/ns16550.h @@ -0,0 +1,112 @@ +#ifndef _IPXE_NS16550_H +#define _IPXE_NS16550_H + +/** @file + * + * 16550-compatible UART + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include + +/** Transmitter holding register */ +#define NS16550_THR 0x00 + +/** Receiver buffer register */ +#define NS16550_RBR 0x00 + +/** Interrupt enable register */ +#define NS16550_IER 0x01 + +/** FIFO control register */ +#define NS16550_FCR 0x02 +#define NS16550_FCR_FE 0x01 /**< FIFO enable */ + +/** Line control register */ +#define NS16550_LCR 0x03 +#define NS16550_LCR_WLS0 0x01 /**< Word length select bit 0 */ +#define NS16550_LCR_WLS1 0x02 /**< Word length select bit 1 */ +#define NS16550_LCR_STB 0x04 /**< Number of stop bits */ +#define NS16550_LCR_PEN 0x08 /**< Parity enable */ +#define NS16550_LCR_EPS 0x10 /**< Even parity select */ +#define NS16550_LCR_DLAB 0x80 /**< Divisor latch access bit */ + +#define NS16550_LCR_WORD_LEN(x) ( ( (x) - 5 ) << 0 ) /**< Word length */ +#define NS16550_LCR_STOP_BITS(x) ( ( (x) - 1 ) << 2 ) /**< Stop bits */ +#define NS16550_LCR_PARITY(x) ( ( (x) - 0 ) << 3 ) /**< Parity */ + +/** + * Calculate line control register value + * + * @v word_len Word length (5-8) + * @v parity Parity (0=none, 1=odd, 3=even) + * @v stop_bits Stop bits (1-2) + * @ret lcr Line control register value + */ +#define NS16550_LCR_WPS( word_len, parity, stop_bits ) \ + ( NS16550_LCR_WORD_LEN ( (word_len) ) | \ + NS16550_LCR_PARITY ( (parity) ) | \ + NS16550_LCR_STOP_BITS ( (stop_bits) ) ) + +/** Default LCR value: 8 data bits, no parity, one stop bit */ +#define NS16550_LCR_8N1 NS16550_LCR_WPS ( 8, 0, 1 ) + +/** Modem control register */ +#define NS16550_MCR 0x04 +#define NS16550_MCR_DTR 0x01 /**< Data terminal ready */ +#define NS16550_MCR_RTS 0x02 /**< Request to send */ + +/** Line status register */ +#define NS16550_LSR 0x05 +#define NS16550_LSR_DR 0x01 /**< Data ready */ +#define NS16550_LSR_THRE 0x20 /**< Transmitter holding reg. empty */ +#define NS16550_LSR_TEMT 0x40 /**< Transmitter empty */ + +/** Scratch register */ +#define NS16550_SCR 0x07 + +/** Divisor latch (least significant byte) */ +#define NS16550_DLL 0x00 + +/** Divisor latch (most significant byte) */ +#define NS16550_DLM 0x01 + +/** Maximum baud rate */ +#define NS16550_MAX_BAUD 115200 + +/** A 16550-compatible UART */ +struct ns16550_uart { + /** Generic UART */ + struct uart uart; + /** Register base address */ + void *base; + /** Baud rate divisor */ + uint16_t divisor; +}; + +#include + +/** Dummy COM1 UART for non-x86 platforms + * + * The architecture-independent config/serial.h header has long + * included the line + * + * #define COMCONSOLE COM1 + * + * which is meaningless on non-x86 platforms where there is no COM1 + * port. Allow COM1 to be treated as equivalent to "no UART" on + * non-x86 platforms, to avoid breaking existing build configurations. + */ +#ifndef COM1 +#define COM1 NULL +#endif + +void ns16550_write ( struct ns16550_uart *ns16550, unsigned int address, + uint8_t data ); +uint8_t ns16550_read ( struct ns16550_uart *ns16550, unsigned int address ); + +extern struct uart_operations ns16550_operations; + +#endif /* _IPXE_NS16550_H */ diff --git a/src/include/ipxe/serial.h b/src/include/ipxe/serial.h index 83be59c31..86a618670 100644 --- a/src/include/ipxe/serial.h +++ b/src/include/ipxe/serial.h @@ -11,6 +11,6 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include -extern struct uart serial_console; +extern struct uart *serial_console; #endif /* _IPXE_SERIAL_H */ diff --git a/src/include/ipxe/uart.h b/src/include/ipxe/uart.h index 86583ee2f..15adfa932 100644 --- a/src/include/ipxe/uart.h +++ b/src/include/ipxe/uart.h @@ -3,128 +3,174 @@ /** @file * - * 16550-compatible UART + * Generic UART * */ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include +#include +#include -/** Transmitter holding register */ -#define UART_THR 0x00 - -/** Receiver buffer register */ -#define UART_RBR 0x00 - -/** Interrupt enable register */ -#define UART_IER 0x01 +/** A generic UART */ +struct uart { + /** Reference count */ + struct refcnt refcnt; + /** Name */ + const char *name; + /** List of registered UARTs */ + struct list_head list; + + /** UART operations */ + struct uart_operations *op; + /** Driver-private data */ + void *priv; +}; -/** FIFO control register */ -#define UART_FCR 0x02 -#define UART_FCR_FE 0x01 /**< FIFO enable */ +/** UART operations */ +struct uart_operations { + /** + * Transmit byte + * + * @v uart UART + * @v byte Byte to transmit + * @ret rc Return status code + */ + void ( * transmit ) ( struct uart *uart, uint8_t byte ); + /** + * Check if data is ready + * + * @v uart UART + * @ret ready Data is ready + */ + int ( * data_ready ) ( struct uart *uart ); + /** + * Receive byte + * + * @v uart UART + * @ret byte Received byte + */ + uint8_t ( * receive ) ( struct uart *uart ); + /** + * Initialise UART + * + * @v uart UART + * @v baud Baud rate, or zero to leave unchanged + * @ret rc Return status code + */ + int ( * init ) ( struct uart *uart, unsigned int baud ); + /** + * Flush transmitted data + * + * @v uart UART + */ + void ( * flush ) ( struct uart *uart ); +}; -/** Line control register */ -#define UART_LCR 0x03 -#define UART_LCR_WLS0 0x01 /**< Word length select bit 0 */ -#define UART_LCR_WLS1 0x02 /**< Word length select bit 1 */ -#define UART_LCR_STB 0x04 /**< Number of stop bits */ -#define UART_LCR_PEN 0x08 /**< Parity enable */ -#define UART_LCR_EPS 0x10 /**< Even parity select */ -#define UART_LCR_DLAB 0x80 /**< Divisor latch access bit */ +/** + * Transmit byte + * + * @v uart UART + * @v byte Byte to transmit + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) void +uart_transmit ( struct uart *uart, uint8_t byte ) { -#define UART_LCR_WORD_LEN(x) ( ( (x) - 5 ) << 0 ) /**< Word length */ -#define UART_LCR_STOP_BITS(x) ( ( (x) - 1 ) << 2 ) /**< Stop bits */ -#define UART_LCR_PARITY(x) ( ( (x) - 0 ) << 3 ) /**< Parity */ + uart->op->transmit ( uart, byte ); +} /** - * Calculate line control register value + * Check if data is ready * - * @v word_len Word length (5-8) - * @v parity Parity (0=none, 1=odd, 3=even) - * @v stop_bits Stop bits (1-2) - * @ret lcr Line control register value + * @v uart UART + * @ret ready Data is ready */ -#define UART_LCR_WPS( word_len, parity, stop_bits ) \ - ( UART_LCR_WORD_LEN ( (word_len) ) | \ - UART_LCR_PARITY ( (parity) ) | \ - UART_LCR_STOP_BITS ( (stop_bits) ) ) - -/** Default LCR value: 8 data bits, no parity, one stop bit */ -#define UART_LCR_8N1 UART_LCR_WPS ( 8, 0, 1 ) +static inline __attribute__ (( always_inline )) int +uart_data_ready ( struct uart *uart ) { -/** Modem control register */ -#define UART_MCR 0x04 -#define UART_MCR_DTR 0x01 /**< Data terminal ready */ -#define UART_MCR_RTS 0x02 /**< Request to send */ + return uart->op->data_ready ( uart ); +} -/** Line status register */ -#define UART_LSR 0x05 -#define UART_LSR_DR 0x01 /**< Data ready */ -#define UART_LSR_THRE 0x20 /**< Transmitter holding register empty */ -#define UART_LSR_TEMT 0x40 /**< Transmitter empty */ +/** + * Receive byte + * + * @v uart UART + * @ret byte Received byte + */ +static inline __attribute__ (( always_inline )) uint8_t +uart_receive ( struct uart *uart ) { -/** Scratch register */ -#define UART_SCR 0x07 + return uart->op->receive ( uart ); +} -/** Divisor latch (least significant byte) */ -#define UART_DLL 0x00 +/** + * Initialise UART + * + * @v uart UART + * @v baud Baud rate, or zero to leave unchanged + * @ret rc Return status code + */ +static inline __attribute__ (( always_inline )) int +uart_init ( struct uart *uart, unsigned int baud ) { -/** Divisor latch (most significant byte) */ -#define UART_DLM 0x01 + return uart->op->init ( uart, baud ); +} -/** Maximum baud rate */ -#define UART_MAX_BAUD 115200 +/** + * Flush transmitted data + * + * @v uart UART + */ +static inline __attribute__ (( always_inline )) void +uart_flush ( struct uart *uart ) { -/** A 16550-compatible UART */ -struct uart { - /** I/O port base address */ - void *base; - /** Baud rate divisor */ - uint16_t divisor; -}; + uart->op->flush ( uart ); +} -/** Symbolic names for port indexes */ -enum uart_port { - COM1 = 1, - COM2 = 2, - COM3 = 3, - COM4 = 4, -}; +extern struct list_head uarts; +extern struct uart_operations null_uart_operations; -#include +/** + * Get reference to UART + * + * @v uart UART + * @ret uart UART + */ +static inline __attribute__ (( always_inline )) struct uart * +uart_get ( struct uart *uart ) { -void uart_write ( struct uart *uart, unsigned int addr, uint8_t data ); -uint8_t uart_read ( struct uart *uart, unsigned int addr ); -int uart_select ( struct uart *uart, unsigned int port ); + ref_get ( &uart->refcnt ); + return uart; +} /** - * Check if received data is ready + * Drop reference to UART * * @v uart UART - * @ret ready Data is ready */ -static inline int uart_data_ready ( struct uart *uart ) { - uint8_t lsr; +static inline __attribute__ (( always_inline )) void +uart_put ( struct uart *uart ) { - lsr = uart_read ( uart, UART_LSR ); - return ( lsr & UART_LSR_DR ); + ref_put ( &uart->refcnt ); } /** - * Receive data + * Nullify UART * * @v uart UART - * @ret data Data */ -static inline uint8_t uart_receive ( struct uart *uart ) { +static inline __attribute__ (( always_inline )) void +uart_nullify ( struct uart *uart ) { - return uart_read ( uart, UART_RBR ); + uart->op = &null_uart_operations; } -extern void uart_transmit ( struct uart *uart, uint8_t data ); -extern void uart_flush ( struct uart *uart ); -extern int uart_exists ( struct uart *uart ); -extern int uart_init ( struct uart *uart, unsigned int baud ); +extern struct uart * alloc_uart ( size_t priv_len ); +extern int uart_register ( struct uart *uart ); +extern int uart_register_fixed ( void ); +extern void uart_unregister ( struct uart *uart ); +extern struct uart * uart_find ( const char *name ); #endif /* _IPXE_UART_H */