From: Michael Brown Date: Tue, 4 Nov 2025 16:19:03 +0000 (+0000) Subject: [ioapi] Provide combined MMIO and port I/O accessors X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=f7de1b53dc60ed3262181841e8cd845a50e72863;p=thirdparty%2Fipxe.git [ioapi] Provide combined MMIO and port I/O accessors Some devices (such as a 16550 UART) may be accessed via either MMIO or port I/O. This is currently forced to be a compile-time decision. For example: we currently access a 16550 UART via port I/O on x86 and via MMIO on any other platform. PCI UARTs with MMIO BARs do exist but are not currently supported in an x86 build of iPXE. Some AWS EC2 systems (observed on a c6i.metal instance in eu-west-2) provide only a PCI MMIO UART, and it is therefore currently impossible to get serial output from iPXE on these instance types. Add ioread8(), ioread16(), etc accessors that will select between MMIO and port I/O at the point of use. For non-x86 platforms where we currently have no port I/O support, these simply become wrappers around the corresponding readb(), readw(), etc MMIO accessors. On x86, we use the fairly well-known trick of treating any 16-bit address (below 64kB) as a port I/O address. This trick works even in the i386 BIOS build of iPXE (where virtual addresses are offset from physical addresses by a runtime constant), since the first 64kB of the virtual address space will correspond to the iPXE binary itself (along with its uninitialised-data space), and so must be RAM rather than a valid MMIO address range. Signed-off-by: Michael Brown --- diff --git a/src/arch/x86/core/x86_io.c b/src/arch/x86/core/x86_io.c index 6c6b6e1e7..270ed7bef 100644 --- a/src/arch/x86/core/x86_io.c +++ b/src/arch/x86/core/x86_io.c @@ -32,6 +32,69 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); * */ +/** Threshold for port I/O-mapped addresses + * + * On x86, port I/O instructions (inb/outb/etc) can take only an 8-bit + * or 16-bit address (in %dx). All I/O ports must therefore have a + * value in the first 64kB of the address space. + * + * Virtual addresses below 64kB can never be MMIO addresses: + * + * - In the UEFI memory model and x86_64 BIOS memory model, virtual + * addresses below 64kB are identity-mapped to the corresponding + * physical address. Since the first 64kB of address space is + * always RAM, no MMIO device can exist within this region. + * + * - In the i386 BIOS memory model, virtual addresses below 64kB cover + * the iPXE binary itself (which starts at address zero). Since the + * size of .textdata can never realistically be below 64kB (not + * least since the heap alone is 512kB), and since iPXE is placed + * into RAM as a contiguous block, no MMIO device can exist within + * this region. + * + * We therefore know that any (virtual) address returned by ioremap() + * must be outside the first 64kB of the address space. We can + * therefore use this as a threshold to determine whether a given + * address is a port I/O address or an MMIO address. + */ +#define PIO_THRESHOLD 0x10000 + +/** + * Read from I/O-mapped or memory-mapped device + * + * @v io_addr I/O address + * @ret data Value read + */ +#define X86_IOREADX( _api_func, _suffix, _type ) \ +static _type x86_ ## _api_func ( volatile _type *io_addr ) { \ + if ( ( ( intptr_t ) io_addr ) < PIO_THRESHOLD ) { \ + return in ## _suffix ( io_addr ); \ + } else { \ + return read ## _suffix ( io_addr ); \ + } \ +} +X86_IOREADX ( ioread8, b, uint8_t ); +X86_IOREADX ( ioread16, w, uint16_t ); +X86_IOREADX ( ioread32, l, uint32_t ); + +/** + * Write to I/O-mapped or memory-mapped device + * + * @v data Value to write + * @v io_addr I/O address + */ +#define X86_IOWRITEX( _api_func, _suffix, _type ) \ +static void x86_ ## _api_func ( _type data, volatile _type *io_addr ) { \ + if ( ( ( intptr_t ) io_addr ) < PIO_THRESHOLD ) { \ + out ## _suffix ( data, io_addr ); \ + } else { \ + write ## _suffix ( data, io_addr ); \ + } \ +} +X86_IOWRITEX ( iowrite8, b, uint8_t ); +X86_IOWRITEX ( iowrite16, w, uint16_t ); +X86_IOWRITEX ( iowrite32, l, uint32_t ); + /** * Read 64-bit qword from memory-mapped device * @@ -101,3 +164,9 @@ PROVIDE_IOAPI_INLINE ( x86, writeq ); PROVIDE_IOAPI ( x86, readq, i386_readq ); PROVIDE_IOAPI ( x86, writeq, i386_writeq ); #endif +PROVIDE_IOAPI ( x86, ioread8, x86_ioread8 ); +PROVIDE_IOAPI ( x86, ioread16, x86_ioread16 ); +PROVIDE_IOAPI ( x86, ioread32, x86_ioread32 ); +PROVIDE_IOAPI ( x86, iowrite8, x86_iowrite8 ); +PROVIDE_IOAPI ( x86, iowrite16, x86_iowrite16 ); +PROVIDE_IOAPI ( x86, iowrite32, x86_iowrite32 ); diff --git a/src/include/ipxe/dummy_pio.h b/src/include/ipxe/dummy_pio.h index e7a4cabef..7c80cdf35 100644 --- a/src/include/ipxe/dummy_pio.h +++ b/src/include/ipxe/dummy_pio.h @@ -39,6 +39,19 @@ IOAPI_INLINE ( _prefix, outs ## _suffix ) ( volatile _type *io_addr __unused, \ /* Do nothing */ \ } +#define DUMMY_IOREADX( _prefix, _width, _suffix, _type ) \ +static inline __always_inline _type \ +IOAPI_INLINE ( _prefix, ioread ## _width ) ( volatile _type *io_addr ) { \ + return IOAPI_INLINE ( _prefix, read ## _suffix ) ( io_addr ); \ +} + +#define DUMMY_IOWRITEX( _prefix, _width, _suffix, _type ) \ +static inline __always_inline void \ +IOAPI_INLINE ( _prefix, iowrite ## _width ) ( _type data, \ + volatile _type *io_addr ) { \ + IOAPI_INLINE ( _prefix, write ## _suffix ) ( data, io_addr ); \ +} + #define DUMMY_IODELAY( _prefix ) \ static inline __always_inline void \ IOAPI_INLINE ( _prefix, iodelay ) ( void ) { \ @@ -52,6 +65,12 @@ IOAPI_INLINE ( _prefix, iodelay ) ( void ) { \ DUMMY_OUTX ( _prefix, b, uint8_t ); \ DUMMY_OUTX ( _prefix, w, uint16_t ); \ DUMMY_OUTX ( _prefix, l, uint32_t ); \ + DUMMY_IOREADX ( _prefix, 8, b, uint8_t ); \ + DUMMY_IOREADX ( _prefix, 16, w, uint16_t ); \ + DUMMY_IOREADX ( _prefix, 32, l, uint32_t ); \ + DUMMY_IOWRITEX ( _prefix, 8, b, uint8_t ); \ + DUMMY_IOWRITEX ( _prefix, 16, w, uint16_t ); \ + DUMMY_IOWRITEX ( _prefix, 32, l, uint32_t ); \ DUMMY_IODELAY ( _prefix ); #define PROVIDE_DUMMY_PIO( _prefix ) \ @@ -61,6 +80,12 @@ IOAPI_INLINE ( _prefix, iodelay ) ( void ) { \ PROVIDE_IOAPI_INLINE ( _prefix, outb ); \ PROVIDE_IOAPI_INLINE ( _prefix, outw ); \ PROVIDE_IOAPI_INLINE ( _prefix, outl ); \ + PROVIDE_IOAPI_INLINE ( _prefix, ioread8 ); \ + PROVIDE_IOAPI_INLINE ( _prefix, ioread16 ); \ + PROVIDE_IOAPI_INLINE ( _prefix, ioread32 ); \ + PROVIDE_IOAPI_INLINE ( _prefix, iowrite8 ); \ + PROVIDE_IOAPI_INLINE ( _prefix, iowrite16 ); \ + PROVIDE_IOAPI_INLINE ( _prefix, iowrite32 ); \ PROVIDE_IOAPI_INLINE ( _prefix, iodelay ); #endif /* _IPXE_DUMMY_PIO_H */ diff --git a/src/include/ipxe/io.h b/src/include/ipxe/io.h index 41ee48ffb..ee2b7e156 100644 --- a/src/include/ipxe/io.h +++ b/src/include/ipxe/io.h @@ -329,6 +329,66 @@ void outl ( uint32_t data, volatile uint32_t *io_addr ); #define outl( data, io_addr ) \ IOAPI_WRITE ( outl, uint32_t, data, io_addr, "IO", 8 ) +/** + * Read byte from I/O-mapped or memory-mapped device + * + * @v io_addr I/O address + * @ret data Value read + */ +uint8_t ioread8 ( volatile uint8_t *io_addr ); +#define ioread8( io_addr ) \ + IOAPI_READ ( ioread8, uint8_t, io_addr, "IO/MEM", 2 ) + +/** + * Read 16-bit word from I/O-mapped or memory-mapped device + * + * @v io_addr I/O address + * @ret data Value read + */ +uint16_t ioread16 ( volatile uint16_t *io_addr ); +#define ioread16( io_addr ) \ + IOAPI_READ ( ioread16, uint16_t, io_addr, "IO/MEM", 4 ) + +/** + * Read 32-bit dword from I/O-mapped or memory-mapped device + * + * @v io_addr I/O address + * @ret data Value read + */ +uint32_t ioread32 ( volatile uint32_t *io_addr ); +#define ioread32( io_addr ) \ + IOAPI_READ ( ioread32, uint32_t, io_addr, "IO/MEM", 8 ) + +/** + * Write byte to I/O-mapped or memory-mapped device + * + * @v data Value to write + * @v io_addr I/O address + */ +void iowrite8 ( uint8_t data, volatile uint8_t *io_addr ); +#define iowrite8( data, io_addr ) \ + IOAPI_WRITE ( iowrite8, uint8_t, data, io_addr, "IO/MEM", 2 ) + +/** + * Write 16-bit word to I/O-mapped or memory-mapped device + * + * @v data Value to write + * @v io_addr I/O address + */ +void iowrite16 ( uint16_t data, volatile uint16_t *io_addr ); +#define iowrite16( data, io_addr ) \ + IOAPI_WRITE ( iowrite16, uint16_t, data, io_addr, "IO/MEM", 4 ) + +/** + * Write 32-bit dword to I/O-mapped or memory-mapped device + * + * @v data Value to write + * @v io_addr I/O address + */ +void iowrite32 ( uint32_t data, volatile uint32_t *io_addr ); +#define iowrite32( data, io_addr ) \ + IOAPI_WRITE ( iowrite32, uint32_t, data, io_addr, "IO/MEM", 8 ) + /** * Read bytes from I/O-mapped device *