[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 <port>" 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 <mcb30@ipxe.org>
This commit is contained in:
Michael Brown
2025-06-17 14:28:18 +01:00
parent 60e167c00b
commit 6c8fb4b89d
15 changed files with 689 additions and 320 deletions
+31 -27
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2014 Michael Brown <mbrown@fensystems.co.uk>.
* 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
@@ -29,41 +29,45 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
*
*/
#include <errno.h>
#include <ipxe/uart.h>
#include <string.h>
#include <ipxe/serial.h>
#include <ipxe/ns16550.h>
/** 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;
}
+60
View File
@@ -0,0 +1,60 @@
#ifndef _BITS_NS16550_H
#define _BITS_NS16550_H
/** @file
*
* 16550-compatible UART
*
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <stdint.h>
#include <ipxe/io.h>
/**
* 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 */
-41
View File
@@ -1,41 +0,0 @@
#ifndef _BITS_UART_H
#define _BITS_UART_H
/** @file
*
* 16550-compatible UART
*
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <stdint.h>
#include <ipxe/io.h>
/**
* 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 */
+11 -6
View File
@@ -37,6 +37,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
#include <ipxe/posix_io.h>
#include <ipxe/process.h>
#include <ipxe/serial.h>
#include <ipxe/ns16550.h>
#include <ipxe/init.h>
#include <ipxe/image.h>
#include <ipxe/version.h>
@@ -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 ));