diff --git a/.gitignore b/.gitignore index e2b174bf3d..3154bf170a 100644 --- a/.gitignore +++ b/.gitignore @@ -167,6 +167,7 @@ tools/unit-tests/unit-libwolfboot-extra tools/unit-tests/unit-mock-state tools/unit-tests/unit-nvm tools/unit-tests/unit-nvm-flagshome +tools/unit-tests/unit-ns16550 tools/unit-tests/unit-parser tools/unit-tests/unit-parser-large-header tools/unit-tests/unit-pci diff --git a/arch.mk b/arch.mk index 6d405f03a7..99c5b6437b 100644 --- a/arch.mk +++ b/arch.mk @@ -167,6 +167,10 @@ ifeq ($(ARCH),AARCH64) endif ifeq ($(TARGET),nxp_ls1028a) + # DUART console on the generic driver (plain MMIO on AArch64). + ifeq ($(DEBUG_UART),1) + OBJS+=hal/uart/ns16550.o + endif ARCH_FLAGS=-mcpu=cortex-a72+crypto -march=armv8-a+crypto -mtune=cortex-a72 CFLAGS+=$(ARCH_FLAGS) -DCORTEX_A72 # ZynqMP RVBAR (0xFD5C0040) does not exist on LS1028A -- the store faults @@ -1206,6 +1210,17 @@ ifeq ($(ARCH),PPC) CFLAGS+=-fno-builtin-printf endif + # QorIQ DUART console on the generic NS16550 driver. PowerPC MMIO needs + # get8()/set8() (sync/twi/isync, sync/eieio), not a plain volatile access. + # stage1 is size-constrained (P1021 gets 4KB total) and keeps its own + # copy, so the generic driver is only linked into the full loader. + ifeq ($(DEBUG_UART),1) + CFLAGS+=-DNS16550_IO_H='"nxp_ppc_io.h"' + ifneq ($(STAGE1),1) + OBJS+=hal/uart/ns16550.o + endif + endif + # Target-specific CPU flags ifeq ($(TARGET),nxp_t2080) CFLAGS+=-mcpu=e6500 -mno-altivec -mbss-plt diff --git a/hal/nxp_ls1028a.c b/hal/nxp_ls1028a.c index e493fd6316..4e29ca38e6 100644 --- a/hal/nxp_ls1028a.c +++ b/hal/nxp_ls1028a.c @@ -43,42 +43,25 @@ void hal_flash_init(void); #ifdef DEBUG_UART -void uart_init(void) -{ - /* calc divisor for UART - * example config values: - * clock_div, baud, base_clk 163 115200 400000000 - * +0.5 to round up - */ - uint32_t div = (((SYS_CLK / 2.0) / (16 * BAUD_RATE)) + 0.5); +#include "ns16550.h" - while (!(UART_LSR(UART_SEL) & UART_LSR_TEMT)); +static struct ns16550_dev uart_console; - /* set ier, fcr, mcr */ - UART_IER(UART_SEL) = 0; - UART_FCR(UART_SEL) = (UART_FCR_TFR | UART_FCR_RFR | UART_FCR_FEN); - - /* enable baud rate access (DLAB=1) - divisor latch access bit*/ - UART_LCR(UART_SEL) = (UART_LCR_DLAB | UART_LCR_WLS); - /* set divisor */ - UART_DLB(UART_SEL) = (div & 0xff); - UART_DMB(UART_SEL) = ((div>>8) & 0xff); - /* disable rate access (DLAB=0) */ - UART_LCR(UART_SEL) = (UART_LCR_WLS); +void uart_init(void) +{ + memset(&uart_console, 0, sizeof(uart_console)); + uart_console.base = (uintptr_t)UART_BASE(UART_SEL); + uart_console.io_width = 1; /* byte-spaced registers */ + /* Integer, not floating point: this is a bootloader. DUART is + * SYS_CLK/2. */ + uart_console.clk_hz = (uint32_t)(SYS_CLK / 2); + uart_console.crlf = 1; + (void)ns16550_init(&uart_console, BAUD_RATE); } void uart_write(const char* buf, uint32_t sz) { - uint32_t pos = 0; - while (sz-- > 0) { - char c = buf[pos++]; - if (c == '\n') { /* handle CRLF */ - while (!(UART_LSR(UART_SEL) & UART_LSR_THRE)); - UART_THR(UART_SEL) = '\r'; - } - while (!(UART_LSR(UART_SEL) & UART_LSR_THRE)); - UART_THR(UART_SEL) = c; - } + (void)ns16550_write(&uart_console, buf, sz); } #endif /* DEBUG_UART */ diff --git a/hal/nxp_ls1028a.h b/hal/nxp_ls1028a.h index d87360a32c..5a87177920 100644 --- a/hal/nxp_ls1028a.h +++ b/hal/nxp_ls1028a.h @@ -299,28 +299,11 @@ /* LS1028A PC16552D Dual UART */ -#define UART_BASE(n) (0x21C0500 + (n * 100)) - -#define UART_RBR(n) *((volatile uint8_t*)(UART_BASE(n) + 0)) /* receiver buffer register */ -#define UART_THR(n) *((volatile uint8_t*)(UART_BASE(n) + 0)) /* transmitter holding register */ -#define UART_IER(n) *((volatile uint8_t*)(UART_BASE(n) + 1)) /* interrupt enable register */ -#define UART_FCR(n) *((volatile uint8_t*)(UART_BASE(n) + 2)) /* FIFO control register */ -#define UART_IIR(n) *((volatile uint8_t*)(UART_BASE(n) + 2)) /* interrupt ID register */ -#define UART_LCR(n) *((volatile uint8_t*)(UART_BASE(n) + 3)) /* line control register */ -#define UART_LSR(n) *((volatile uint8_t*)(UART_BASE(n) + 5)) /* line status register */ -#define UART_SCR(n) *((volatile uint8_t*)(UART_BASE(n) + 7)) /* scratch register */ - -/* enabled when UART_LCR_DLAB set */ -#define UART_DLB(n) *((volatile uint8_t*)(UART_BASE(n) + 0)) /* divisor least significant byte register */ -#define UART_DMB(n) *((volatile uint8_t*)(UART_BASE(n) + 1)) /* divisor most significant byte register */ - -#define UART_FCR_TFR (0x04) /* Transmitter FIFO reset */ -#define UART_FCR_RFR (0x02) /* Receiver FIFO reset */ -#define UART_FCR_FEN (0x01) /* FIFO enable */ -#define UART_LCR_DLAB (0x80) /* Divisor latch access bit */ -#define UART_LCR_WLS (0x03) /* Word length select: 8-bits */ -#define UART_LSR_TEMT (0x40) /* Transmitter empty */ -#define UART_LSR_THRE (0x20) /* Transmitter holding register empty */ +/* DUART_STRIDE, not decimal 100: the DUARTs are 0x100 apart. Latent only + * because UART_SEL is 0. */ +#define UART_BASE(n) (0x21C0500 + ((n) * DUART_STRIDE)) + +/* Register layout and bit names live in include/ns16550.h. */ /* LS1028 XSPI Flex SPI Memory map - RM 18.7.2.1 */ #define XSPI_BASE (0x20C0000UL) diff --git a/hal/nxp_p1021.c b/hal/nxp_p1021.c index 09b05b66ca..d18a6817d5 100644 --- a/hal/nxp_p1021.c +++ b/hal/nxp_p1021.c @@ -154,32 +154,10 @@ /* P1021 PC16552D Dual UART */ #define BAUD_RATE 115200 #define UART_SEL 0 /* select UART 0 or 1 */ -#define UART_LCR_VAL (UART_LCR_WLS) /* data=8 bits, stop-1 bit, no parity */ #define UART_BASE(n) (CCSRBAR + 0x4500 + (n * 0x100)) -#define UART_RBR(n) ((volatile uint8_t*)(UART_BASE(n) + 0)) /* receiver buffer register */ -#define UART_THR(n) ((volatile uint8_t*)(UART_BASE(n) + 0)) /* transmitter holding register */ -#define UART_IER(n) ((volatile uint8_t*)(UART_BASE(n) + 1)) /* interrupt enable register */ -#define UART_IIR(n) ((volatile uint8_t*)(UART_BASE(n) + 2)) /* interrupt ID register */ -#define UART_FCR(n) ((volatile uint8_t*)(UART_BASE(n) + 2)) /* FIFO control register */ -#define UART_LCR(n) ((volatile uint8_t*)(UART_BASE(n) + 3)) /* line control register */ -#define UART_MCR(n) ((volatile uint8_t*)(UART_BASE(n) + 4)) /* modem control register */ -#define UART_LSR(n) ((volatile uint8_t*)(UART_BASE(n) + 5)) /* line status register */ - -/* enabled when UART_LCR_DLAB set */ -#define UART_DLB(n) ((volatile uint8_t*)(UART_BASE(n) + 0)) /* divisor least significant byte register */ -#define UART_DMB(n) ((volatile uint8_t*)(UART_BASE(n) + 1)) /* divisor most significant byte register */ - -#define UART_FCR_TFR (0x04) /* Transmitter FIFO reset */ -#define UART_FCR_RFR (0x02) /* Receiver FIFO reset */ -#define UART_FCR_FEN (0x01) /* FIFO enable */ -#define UART_LCR_DLAB (0x80) /* Divisor latch access bit */ -#define UART_LCR_WLS (0x03) /* Word length select: 8-bits */ -#define UART_LSR_TEMT (0x40) /* Transmitter empty */ -#define UART_LSR_THRE (0x20) /* Transmitter holding register empty */ - -/* P1021 eLBC (Enhanced Local Bus Controller) - RM 12.3 */ +/* Register layout and bit names live in include/ns16550.h. */ #define ELBC_BASE (CCSRBAR + 0x5000UL) #define ELBC_MAX_BANKS 8 #define ELBC_BANK_SZ 8192 diff --git a/hal/nxp_ppc.c b/hal/nxp_ppc.c index 830d113081..5c358705e2 100644 --- a/hal/nxp_ppc.c +++ b/hal/nxp_ppc.c @@ -67,30 +67,67 @@ static void RAMFUNCTION udelay(uint32_t delay_us) } #endif /* CORE_E5500 || CORE_E6500 */ -/* ---- Shared PC16552D-compatible DUART driver ---- - * Each target must define before including this file: - * UART_SEL, BAUD_RATE, UART_THR(n), UART_IER(n), UART_FCR(n), - * UART_LCR(n), UART_DLB(n), UART_DMB(n), UART_LSR(n), - * UART_FCR_TFR, UART_FCR_RFR, UART_FCR_FEN, - * UART_LCR_DLAB, UART_LCR_WLS, UART_LSR_TEMT, UART_LSR_THRE */ +/* ---- PC16552D DUART console ---- + * Each target defines UART_BASE(n), UART_SEL and BAUD_RATE. The full loader + * uses hal/uart/ns16550.c with get8()/set8() kept as the accessors via + * NS16550_IO_H, so MMIO ordering is unchanged. stage1 keeps a specialised + * copy: P1021 gives it 4KB of flash in total and the generic driver does + * not fit. */ #ifdef DEBUG_UART +#ifndef BUILD_LOADER_STAGE1 + +#include "ns16550.h" + +static struct ns16550_dev uart_console; + +/* The bus clock is only known once the PLLs have been read. */ +uint32_t ns16550_hal_clk_hz(void) +{ + return (uint32_t)hal_get_bus_clk(); +} + void uart_init(void) { - /* baud rate = bus_clk / (16 * div); round up */ - uint32_t div = (hal_get_bus_clk() + (8 * BAUD_RATE)) / (16 * BAUD_RATE); + /* Every field is set explicitly rather than memset first: this file is + * included as source by four HALs and not all of them pull in string.h. */ + uart_console.base = (uintptr_t)UART_BASE(UART_SEL); + uart_console.reg_shift = 0; /* byte-spaced registers */ + uart_console.reg_off = 0; + uart_console.clk_hz = 0; /* ask ns16550_hal_clk_hz() */ + uart_console.io_width = 1; + uart_console.crlf = 1; /* console duty */ + (void)ns16550_init(&uart_console, BAUD_RATE); +} - while (!(get8(UART_LSR(UART_SEL)) & UART_LSR_TEMT)) - ; +void uart_write(const char* buf, uint32_t sz) +{ + (void)ns16550_write(&uart_console, buf, sz); +} + +#else /* BUILD_LOADER_STAGE1 */ - set8(UART_IER(UART_SEL), 0); - set8(UART_FCR(UART_SEL), (UART_FCR_TFR | UART_FCR_RFR | UART_FCR_FEN)); +/* Minimal driver for the size-constrained first stage. */ +#define S1_UART(off) ((volatile unsigned char*)(UART_BASE(UART_SEL) + (off))) +#define S1_THR S1_UART(0) +#define S1_IER S1_UART(1) +#define S1_FCR S1_UART(2) +#define S1_LCR S1_UART(3) +#define S1_LSR S1_UART(5) +#define S1_DLL S1_UART(0) +#define S1_DLM S1_UART(1) - /* enable baud rate access (DLAB=1) */ - set8(UART_LCR(UART_SEL), (UART_LCR_DLAB | UART_LCR_WLS)); - set8(UART_DLB(UART_SEL), (div & 0xff)); - set8(UART_DMB(UART_SEL), ((div >> 8) & 0xff)); - /* disable baud rate access (DLAB=0) */ - set8(UART_LCR(UART_SEL), (UART_LCR_WLS)); +void uart_init(void) +{ + uint32_t div = (hal_get_bus_clk() + (8 * BAUD_RATE)) / (16 * BAUD_RATE); + + while (!(get8(S1_LSR) & 0x40)) + ; + set8(S1_IER, 0); + set8(S1_FCR, 0x07); /* FIFO enable + RX/TX reset */ + set8(S1_LCR, 0x83); /* DLAB | 8 data bits */ + set8(S1_DLL, (div & 0xff)); + set8(S1_DLM, ((div >> 8) & 0xff)); + set8(S1_LCR, 0x03); /* 8 data bits, DLAB clear */ } void uart_write(const char* buf, uint32_t sz) @@ -98,12 +135,14 @@ void uart_write(const char* buf, uint32_t sz) uint32_t pos = 0; while (sz-- > 0) { char c = buf[pos++]; - if (c == '\n') { /* handle CRLF */ - while ((get8(UART_LSR(UART_SEL)) & UART_LSR_THRE) == 0); - set8(UART_THR(UART_SEL), '\r'); + if (c == '\n') { + while ((get8(S1_LSR) & 0x20) == 0); + set8(S1_THR, '\r'); } - while ((get8(UART_LSR(UART_SEL)) & UART_LSR_THRE) == 0); - set8(UART_THR(UART_SEL), c); + while ((get8(S1_LSR) & 0x20) == 0); + set8(S1_THR, c); } } + +#endif /* !BUILD_LOADER_STAGE1 */ #endif /* DEBUG_UART */ diff --git a/hal/nxp_t10xx.c b/hal/nxp_t10xx.c index a43ad32686..c666a1d89a 100644 --- a/hal/nxp_t10xx.c +++ b/hal/nxp_t10xx.c @@ -457,29 +457,7 @@ static void hal_flash_unlock_sector(uint32_t sector); #define UART_BASE(n) (CCSRBAR + 0x11C500 + (n * 0x1000)) -#define UART_RBR(n) ((volatile uint8_t*)(UART_BASE(n) + 0)) /* receiver buffer register */ -#define UART_THR(n) ((volatile uint8_t*)(UART_BASE(n) + 0)) /* transmitter holding register */ -#define UART_IER(n) ((volatile uint8_t*)(UART_BASE(n) + 1)) /* interrupt enable register */ -#define UART_IIR(n) ((volatile uint8_t*)(UART_BASE(n) + 2)) /* interrupt ID register */ -#define UART_FCR(n) ((volatile uint8_t*)(UART_BASE(n) + 2)) /* FIFO control register */ -#define UART_LCR(n) ((volatile uint8_t*)(UART_BASE(n) + 3)) /* line control register */ -#define UART_MCR(n) ((volatile uint8_t*)(UART_BASE(n) + 4)) /* modem control register */ -#define UART_LSR(n) ((volatile uint8_t*)(UART_BASE(n) + 5)) /* line status register */ - -/* enabled when UART_LCR_DLAB set */ -#define UART_DLB(n) ((volatile uint8_t*)(UART_BASE(n) + 0)) /* divisor least significant byte register */ -#define UART_DMB(n) ((volatile uint8_t*)(UART_BASE(n) + 1)) /* divisor most significant byte register */ - -#define UART_FCR_TFR (0x04) /* Transmitter FIFO reset */ -#define UART_FCR_RFR (0x02) /* Receiver FIFO reset */ -#define UART_FCR_FEN (0x01) /* FIFO enable */ -#define UART_LCR_DLAB (0x80) /* Divisor latch access bit */ -#define UART_LCR_WLS (0x03) /* Word length select: 8-bits */ -#define UART_LSR_TEMT (0x40) /* Transmitter empty */ -#define UART_LSR_THRE (0x20) /* Transmitter holding register empty */ - - -/* T1024 IFC (Integrated Flash Controller) - RM 23.1 */ +/* Register layout and bit names live in include/ns16550.h. */ #define IFC_BASE (CCSRBAR + 0x00124000) #define IFC_MAX_BANKS 8 diff --git a/hal/nxp_t2080.c b/hal/nxp_t2080.c index d39f6a6be8..4e34ea6d5e 100644 --- a/hal/nxp_t2080.c +++ b/hal/nxp_t2080.c @@ -25,6 +25,9 @@ #include "image.h" /* for RAMFUNCTION */ #include "nxp_ppc.h" #include "nxp_t2080.h" +/* Register names for the DUART MCR poke in hal_flash_cache_disable_pre_os(); + * nxp_ppc.c pulls this in too, but only when DEBUG_UART is set. */ +#include "ns16550.h" #define ENABLE_IFC #define ENABLE_BUS_CLK_CALC @@ -1701,8 +1704,10 @@ void hal_prepare_boot(void) * * Also aligns small but observable pre-jump state items to CW U-Boot's * profile when chasing VxWorks 7 64-bit silent boot: - * - DUART1 MCR = 3 (DTR+RTS asserted; U-Boot sets this, our driver - * leaves it at the post-reset 0) + * - DUART1 MCR = 3 (DTR+RTS asserted, as U-Boot leaves it). The shared + * NS16550 driver already does this in ns16550_init(), + * but only when DEBUG_UART builds the console in, so + * the poke below still covers a console-less build. * - TCR = 0 (matches CW U-Boot's pre-bootm value; a nonzero WRC would let * the watchdog fire silently after VxWorks starts) */ void RAMFUNCTION hal_flash_cache_disable_pre_os(void) @@ -1710,8 +1715,9 @@ void RAMFUNCTION hal_flash_cache_disable_pre_os(void) hal_flash_cache_disable(); #ifdef ENABLE_OS64BIT /* DUART1 modem control: DTR+RTS asserted, matching CW U-Boot's - * pre-bootm value. */ - set8(UART_MCR(0), 0x03); + * pre-bootm value, which VxWorks 7 inherits. */ + set8((volatile unsigned char*)(UART_BASE(0) + NS16550_MCR), + NS16550_MCR_DTR | NS16550_MCR_RTS); /* TCR=0 matches CW U-Boot's pre-bootm value. WRC != 0 would let * the watchdog fire silently after VxWorks starts. */ mtspr(SPRN_TCR, 0); diff --git a/hal/nxp_t2080.h b/hal/nxp_t2080.h index 98b1d5c1d5..e431268127 100644 --- a/hal/nxp_t2080.h +++ b/hal/nxp_t2080.h @@ -52,29 +52,7 @@ #define UART_BASE(n) (CCSRBAR + 0x11C500 + (n * 0x1000)) -#define UART_RBR(n) ((volatile uint8_t*)(UART_BASE(n) + 0)) /* receiver buffer register */ -#define UART_THR(n) ((volatile uint8_t*)(UART_BASE(n) + 0)) /* transmitter holding register */ -#define UART_IER(n) ((volatile uint8_t*)(UART_BASE(n) + 1)) /* interrupt enable register */ -#define UART_IIR(n) ((volatile uint8_t*)(UART_BASE(n) + 2)) /* interrupt ID register */ -#define UART_FCR(n) ((volatile uint8_t*)(UART_BASE(n) + 2)) /* FIFO control register */ -#define UART_LCR(n) ((volatile uint8_t*)(UART_BASE(n) + 3)) /* line control register */ -#define UART_MCR(n) ((volatile uint8_t*)(UART_BASE(n) + 4)) /* modem control register */ -#define UART_LSR(n) ((volatile uint8_t*)(UART_BASE(n) + 5)) /* line status register */ - -/* enabled when UART_LCR_DLAB set */ -#define UART_DLB(n) ((volatile uint8_t*)(UART_BASE(n) + 0)) /* divisor least significant byte register */ -#define UART_DMB(n) ((volatile uint8_t*)(UART_BASE(n) + 1)) /* divisor most significant byte register */ - -#define UART_FCR_TFR (0x04) /* Transmitter FIFO reset */ -#define UART_FCR_RFR (0x02) /* Receiver FIFO reset */ -#define UART_FCR_FEN (0x01) /* FIFO enable */ -#define UART_LCR_DLAB (0x80) /* Divisor latch access bit */ -#define UART_LCR_WLS (0x03) /* Word length select: 8-bits */ -#define UART_LSR_TEMT (0x40) /* Transmitter empty */ -#define UART_LSR_THRE (0x20) /* Transmitter holding register empty */ - - -/* ---- IFC (Integrated Flash Controller) - T2080RM 13.3 ---- */ +/* Register layout and bit names live in include/ns16550.h. */ #define IFC_BASE (CCSRBAR + 0x00124000) #define IFC_MAX_BANKS 8 diff --git a/hal/uart/ns16550.c b/hal/uart/ns16550.c new file mode 100644 index 0000000000..9e83eca141 --- /dev/null +++ b/hal/uart/ns16550.c @@ -0,0 +1,189 @@ +/* ns16550.c + * + * Instance-based driver for NS16550-compatible UARTs. + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot 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 3 of the License, or + * (at your option) any later version. + * + * wolfBoot 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-1335, USA + */ + +#include +#include "ns16550.h" +#include "wolfboot/wolfboot.h" /* WEAKFUNCTION */ + +/* Bound on every busy-wait; only trips when the port is absent. */ +#define NS16550_SPIN_MAX 1000000UL + +static uint32_t ns16550_rd(const struct ns16550_dev* dev, uint32_t reg) +{ + uintptr_t addr = dev->base + dev->reg_off + ((uintptr_t)reg << dev->reg_shift); + + if (dev->io_width == 4) { + return *((volatile uint32_t*)addr); + } + if (dev->io_width == 2) { + return (uint32_t)(*((volatile uint16_t*)addr)); + } + return (uint32_t)NS16550_RD8(addr); +} + +static void ns16550_wr(const struct ns16550_dev* dev, uint32_t reg, uint32_t val) +{ + uintptr_t addr = dev->base + dev->reg_off + ((uintptr_t)reg << dev->reg_shift); + + if (dev->io_width == 4) { + *((volatile uint32_t*)addr) = val; + } + else if (dev->io_width == 2) { + *((volatile uint16_t*)addr) = (uint16_t)val; + } + else { + NS16550_WR8(addr, val); + } +} + +/* Word-spaced registers imply word access unless the caller says otherwise. */ +static void ns16550_fix_io_width(struct ns16550_dev* dev) +{ + if (dev->io_width == 1 || dev->io_width == 2 || dev->io_width == 4) { + return; + } + dev->io_width = (dev->reg_shift >= 2) ? 4 : 1; +} + +/* Weak default for ports that know their clock at build time. Returns 0, + * which ns16550_init() rejects, so leaving clk_hz 0 without overriding this + * is an error rather than a wrong baud rate. */ +uint32_t WEAKFUNCTION ns16550_hal_clk_hz(void) +{ + return 0; +} + +int ns16550_init(struct ns16550_dev* dev, uint32_t baud) +{ + uint32_t div; + uint32_t lcr; + uint32_t clk; + + /* The baud bound keeps baud * 16 from wrapping the divisor arithmetic + * below; 64-bit intermediates would pull a libgcc divide into 32-bit + * targets for a range no real port uses. */ + if (dev == NULL || dev->base == 0 || baud == 0 || + baud > (0xFFFFFFFFU / 16U)) { + return NS16550_ERR_ARG; + } + if (dev->reg_shift > 3) { + return NS16550_ERR_ARG; + } + ns16550_fix_io_width(dev); + + /* A port whose UART clock is only known at runtime leaves clk_hz 0. */ + clk = (dev->clk_hz != 0) ? dev->clk_hz : ns16550_hal_clk_hz(); + if (clk == 0) { + return NS16550_ERR_CLK; + } + + /* The latch is 16 bits and 0 means divide-by-65536, so out of range is a + * bad clock/baud pairing rather than something to clamp. Rounded, not + * truncated: 0.5% baud error instead of 1.4% at 115200 on 99.999 MHz. */ + div = (clk + (baud * 8U)) / (baud * 16U); + if (div == 0U || div > 0xFFFFU) { + return NS16550_ERR_CLK; + } + + /* Mask interrupts, then set the format with DLAB up to reach the latches. */ + ns16550_wr(dev, NS16550_IER, 0); + lcr = NS16550_LCR_8N1; + ns16550_wr(dev, NS16550_LCR, lcr | NS16550_LCR_DLAB); + ns16550_wr(dev, NS16550_DLL, div & 0xFFU); + ns16550_wr(dev, NS16550_DLM, (div >> 8) & 0xFFU); + ns16550_wr(dev, NS16550_LCR, lcr); + + /* Enable the FIFOs and drop anything a previous stage left. */ + ns16550_wr(dev, NS16550_FCR, + NS16550_FCR_ENABLE | NS16550_FCR_RXRST | NS16550_FCR_TXRST); + /* A receiver wired for hardware flow control needs DTR/RTS asserted. */ + ns16550_wr(dev, NS16550_MCR, NS16550_MCR_DTR | NS16550_MCR_RTS); + + return NS16550_OK; +} + +/* Spin until every bit in `mask` is set in LSR, or the bound expires. */ +static int ns16550_wait_lsr(const struct ns16550_dev* dev, uint32_t mask) +{ + uint32_t spin; + + for (spin = 0; spin < NS16550_SPIN_MAX; spin++) { + if ((ns16550_rd(dev, NS16550_LSR) & mask) == mask) { + return NS16550_OK; + } + } + return NS16550_ERR_TMO; +} + +int ns16550_write(struct ns16550_dev* dev, const char* buf, uint32_t len) +{ + uint32_t i; + int ret; + + if (dev == NULL || dev->base == 0 || (buf == NULL && len != 0)) { + return NS16550_ERR_ARG; + } + ns16550_fix_io_width(dev); + + for (i = 0; i < len; i++) { + if (dev->crlf != 0 && buf[i] == '\n') { + ret = ns16550_wait_lsr(dev, NS16550_LSR_THRE); + if (ret != NS16550_OK) { + return ret; + } + ns16550_wr(dev, NS16550_THR, (uint32_t)'\r'); + } + ret = ns16550_wait_lsr(dev, NS16550_LSR_THRE); + if (ret != NS16550_OK) { + return ret; + } + ns16550_wr(dev, NS16550_THR, (uint32_t)(uint8_t)buf[i]); + } + /* Drain: the next image may reset the port. */ + return ns16550_wait_lsr(dev, NS16550_LSR_THRE | NS16550_LSR_TEMT); +} + +int ns16550_can_read(struct ns16550_dev* dev) +{ + if (dev == NULL || dev->base == 0) { + return 0; + } + ns16550_fix_io_width(dev); + return (ns16550_rd(dev, NS16550_LSR) & NS16550_LSR_DR) ? 1 : 0; +} + +int ns16550_read(struct ns16550_dev* dev, uint8_t* c) +{ + int ret; + + if (dev == NULL || dev->base == 0 || c == NULL) { + return NS16550_ERR_ARG; + } + ns16550_fix_io_width(dev); + ret = ns16550_wait_lsr(dev, NS16550_LSR_DR); + if (ret != NS16550_OK) { + return ret; + } + *c = (uint8_t)(ns16550_rd(dev, NS16550_RBR) & 0xFF); + return NS16550_OK; +} diff --git a/include/ns16550.h b/include/ns16550.h new file mode 100644 index 0000000000..f4c56d7e0c --- /dev/null +++ b/include/ns16550.h @@ -0,0 +1,105 @@ +/* ns16550.h + * + * Instance-based driver for NS16550-compatible UARTs. + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot 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 3 of the License, or + * (at your option) any later version. + * + * wolfBoot 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-1335, USA + */ + +#ifndef WOLFBOOT_NS16550_H +#define WOLFBOOT_NS16550_H + +#include +#include + +/* MMIO access. A port whose bus needs more than a volatile access supplies + * these via -DNS16550_IO_H='"myport_io.h"' - PowerPC QorIQ must keep its + * sync/twi/isync and sync/eieio sequences or MMIO ordering is lost. + * 8-bit only: every port needing ordering so far is byte-wide. The 16- and + * 32-bit paths use plain volatile accesses. */ +#ifdef NS16550_IO_H +#include NS16550_IO_H +#endif +#ifndef NS16550_RD8 +#define NS16550_RD8(a) (*(volatile uint8_t*)(a)) +#endif +#ifndef NS16550_WR8 +#define NS16550_WR8(a, v) (*(volatile uint8_t*)(a) = (uint8_t)(v)) +#endif + +/* Register indices, before reg_shift is applied. */ +#define NS16550_RBR 0x00 /* read: receive buffer */ +#define NS16550_THR 0x00 /* write: transmit holding */ +#define NS16550_DLL 0x00 /* divisor low (LCR.DLAB set) */ +#define NS16550_IER 0x01 +#define NS16550_DLM 0x01 /* divisor high (LCR.DLAB set) */ +#define NS16550_FCR 0x02 /* write only */ +#define NS16550_LCR 0x03 +#define NS16550_MCR 0x04 +#define NS16550_LSR 0x05 + +#define NS16550_LCR_8N1 0x03 /* 8 data bits, no parity, 1 stop bit */ +#define NS16550_LCR_DLAB 0x80 + +#define NS16550_FCR_ENABLE 0x01 +#define NS16550_FCR_RXRST 0x02 +#define NS16550_FCR_TXRST 0x04 + +#define NS16550_MCR_DTR 0x01 +#define NS16550_MCR_RTS 0x02 + +#define NS16550_LSR_DR 0x01 /* receive data ready */ +#define NS16550_LSR_THRE 0x20 /* transmit holding register empty */ +#define NS16550_LSR_TEMT 0x40 /* transmitter fully empty (shift reg too) */ + +/* One NS16550-compatible port. Each field is the devicetree property of the + * same name; io_width is "reg-io-width", 0 derives it from reg_shift. */ +struct ns16550_dev { + uintptr_t base; + uint32_t reg_shift; + uint32_t reg_off; + uint32_t clk_hz; /* 0 = ask the HAL via ns16550_hal_clk_hz() */ + uint8_t io_width; + uint8_t crlf; /* console duty: expand '\n' to '\r\n' */ +}; + +/* Return codes. */ +#define NS16550_OK 0 +#define NS16550_ERR_ARG (-1) +#define NS16550_ERR_CLK (-2) /* clock/baud combination has no usable divisor */ +#define NS16550_ERR_TMO (-3) /* transmitter never drained; port likely absent */ + +/* 8N1 at `baud`, FIFOs enabled and reset. Returns a NS16550_* code. */ +int ns16550_init(struct ns16550_dev* dev, uint32_t baud); + +/* Write `len` bytes, expanding '\n' to '\r\n' when dev->crlf is set. Bounded + * waits, so an absent port returns NS16550_ERR_TMO rather than hanging. */ +int ns16550_write(struct ns16550_dev* dev, const char* buf, uint32_t len); + +/* Read one byte. Returns NS16550_OK with *c set, or NS16550_ERR_TMO when + * nothing arrived within the bound. */ +int ns16550_read(struct ns16550_dev* dev, uint8_t* c); + +/* Non-blocking: nonzero if a byte is waiting. */ +int ns16550_can_read(struct ns16550_dev* dev); + +/* Supplied by a port that leaves clk_hz 0 because its UART clock is only + * known at runtime (QorIQ derives it from the bus clock). */ +uint32_t ns16550_hal_clk_hz(void); + +#endif /* WOLFBOOT_NS16550_H */ diff --git a/include/nxp_ppc_io.h b/include/nxp_ppc_io.h new file mode 100644 index 0000000000..a715d52753 --- /dev/null +++ b/include/nxp_ppc_io.h @@ -0,0 +1,58 @@ +/* nxp_ppc_io.h + * + * MMIO accessors for the generic NS16550 driver on NXP QorIQ PowerPC. + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot 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 3 of the License, or + * (at your option) any later version. + * + * wolfBoot 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-1335, USA + */ + +#ifndef WOLFBOOT_NXP_PPC_IO_H +#define WOLFBOOT_NXP_PPC_IO_H + +/* PowerPC device access needs the enforced-ordering sequences; a plain + * volatile access is not enough. These match get8()/set8() in hal/nxp_ppc.h + * byte for byte, repeated rather than included so the driver builds the same + * from the stage1 and test-app sub-makes, which do not share the top-level + * include path. */ + +static inline uint8_t ns16550_ppc_rd8(uintptr_t addr) +{ + int ret; + __asm__ __volatile__( + "sync;\n" + "lbz%U1%X1 %0,%1;\n" + "twi 0,%0,0;\n" + "isync" + : "=r" (ret) : "m" (*(const volatile unsigned char*)addr) + ); + return (uint8_t)ret; +} + +static inline void ns16550_ppc_wr8(uintptr_t addr, uint8_t val) +{ + __asm__ __volatile__( + "stb%U0%X0 %1,%0;\n" + "eieio" + : "=m" (*(volatile unsigned char*)addr) : "r" ((int)val) + ); +} + +#define NS16550_RD8(a) ns16550_ppc_rd8((uintptr_t)(a)) +#define NS16550_WR8(a, v) ns16550_ppc_wr8((uintptr_t)(a), (uint8_t)(v)) + +#endif /* WOLFBOOT_NXP_PPC_IO_H */ diff --git a/options.mk b/options.mk index ce6915d098..90d800d7c5 100644 --- a/options.mk +++ b/options.mk @@ -1689,6 +1689,20 @@ ifeq ($(WOLFHSM_SERVER),1) endif +# NS16550-compatible UART, as an additional instance-based port (see +# include/ns16550.h). Independent of DEBUG_UART: that selects the single +# global debug console, this adds a driver for a second port whose base +# address may only be known at runtime (e.g. read from the device tree). +ifeq ($(NS16550),1) + CFLAGS += -DWOLFBOOT_NS16550 + # arch.mk (included first) already adds this object on targets that put + # their debug console on the same driver, so only add it when absent - + # naming it twice makes the link fail with multiple definitions. + ifeq (,$(filter hal/uart/ns16550.o,$(OBJS))) + OBJS += hal/uart/ns16550.o + endif +endif + # wolfBoot hooks framework # WOLFBOOT_HOOKS_FILE: path to a single .c file containing hook definitions WOLFBOOT_HOOKS_ENABLED := diff --git a/stage1/Makefile b/stage1/Makefile index 1a70031f60..cc5a5ea0e7 100644 --- a/stage1/Makefile +++ b/stage1/Makefile @@ -119,6 +119,7 @@ BUILD_DIR=. LS1_OBJS=$(addprefix $(BUILD_DIR)/, $(notdir $(OBJS))) vpath %.c ../src vpath %.c ../hal +vpath %.c ../hal/uart vpath %.c $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src $(WOLFBOOT_LIB_WOLFTPM)/src vpath %.c ../src/x86 vpath %.S ../src @@ -198,6 +199,10 @@ $(BUILD_DIR)/%.o: ../src/%.c $(BUILD_DIR)/%.o: ../hal/%.c @echo "\t[CC-$(ARCH)] $@" $(Q)$(CC) $(CFLAGS) -c $(OUTPUT_FLAG) $@ $< +# The QorIQ console driver lives in hal/uart/. +$(BUILD_DIR)/%.o: ../hal/uart/%.c + @echo "\t[CC-$(ARCH)] $@" + $(Q)$(CC) $(CFLAGS) -c $(OUTPUT_FLAG) $@ $< $(BUILD_DIR)/%.o: $(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/%.c @echo "\t[CC-$(ARCH)] $@" $(Q)$(CC) $(CFLAGS) -c $(OUTPUT_FLAG) $@ $< diff --git a/test-app/Makefile b/test-app/Makefile index 27753649be..9a4ad56553 100644 --- a/test-app/Makefile +++ b/test-app/Makefile @@ -538,6 +538,13 @@ else APP_OBJS+=../hal/wolfhal.o else APP_OBJS+=../hal/$(TARGET).o + # The test-app links the same HAL object, so HALs using the + # generic NS16550 console need it here too. + ifneq (,$(filter $(TARGET),nxp_ls1028a nxp_t2080 nxp_t1024 nxp_t1040 nxp_p1021)) + ifeq ($(DEBUG_UART),1) + APP_OBJS+=../hal/uart/ns16550.o + endif + endif endif endif diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index 9306819684..ec2c64cd32 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -129,6 +129,7 @@ TESTS+=unit-rp2350-flash-write TESTS+=unit-fwtpm-rsp-overrun TESTS+=unit-fwtpm-cmd-toctou TESTS+=unit-fdt-memrsv-wrap +TESTS+=unit-ns16550 TESTS+=unit-pkcs11_store-stalecache TESTS+=unit-aurix-erased-fill TESTS+=unit-aurix-erased-fill-invert @@ -510,6 +511,11 @@ unit-fwtpm-cmd-toctou: ../../include/target.h unit-fwtpm-cmd-toctou.c # unit-fdt-memrsv-wrap: layout validation in front of fdt_add_mem_rsv() # (F-11045). Links the real parser rather than extracting one function. +# unit-ns16550: generic NS16550 driver (divisor math, reg-shift/reg-offset +# arithmetic, CRLF, RX, bounded busy-waits) against an emulated register block +unit-ns16550: unit-ns16550.c ../../hal/uart/ns16550.c + gcc -o $@ unit-ns16550.c $(CFLAGS) $(LDFLAGS) + unit-fdt-memrsv-wrap:CFLAGS+=-DWOLFBOOT_FDT unit-fdt-memrsv-wrap: unit-fdt-memrsv-wrap.c ../../src/fdt.c gcc -o $@ unit-fdt-memrsv-wrap.c ../../src/fdt.c -I../../include \ diff --git a/tools/unit-tests/unit-ns16550.c b/tools/unit-tests/unit-ns16550.c new file mode 100644 index 0000000000..5674c099f0 --- /dev/null +++ b/tools/unit-tests/unit-ns16550.c @@ -0,0 +1,326 @@ +/* unit-ns16550.c + * + * Unit tests for the instance-based NS16550 UART driver. + * + * The driver is pointed at a plain malloc'd buffer standing in for the + * register block, so the register offsets, the reg-shift/reg-offset + * arithmetic, the divisor rounding and the timeout bound are all checked on + * the host. A "port" is emulated by pre-setting the LSR byte. + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot 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 3 of the License, or + * (at your option) any later version. + * + * wolfBoot 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-1335, USA + */ + +#include +#include +#include +#include + +#include "../../include/ns16550.h" +#include "../../hal/uart/ns16550.c" + +/* A register window big enough for the Xilinx layout (reg-offset 0x1000, + * reg-shift 2, eight registers). */ +#define REGS_SIZE 0x1100 + +static uint8_t *regs; + +/* Address of register `r`, derived from the device the same way the driver + * derives it, so the test tracks base + reg_off + (r << reg_shift). */ +static volatile uint32_t *reg32(const struct ns16550_dev *d, uint32_t r) +{ + return (volatile uint32_t *)((uint8_t *)d->base + d->reg_off + + (r << d->reg_shift)); +} +static volatile uint8_t *reg8(const struct ns16550_dev *d, uint32_t r) +{ + return (volatile uint8_t *)((uint8_t *)d->base + d->reg_off + + (r << d->reg_shift)); +} + +static void setup(void) +{ + regs = calloc(1, REGS_SIZE); + ck_assert_ptr_nonnull(regs); +} +static void teardown(void) +{ + free(regs); + regs = NULL; +} + +/* The customer's node: xlnx,xps-uart16550-2.00.a, reg-offset 0x1000, + * reg-shift 2, clock-frequency 0x5f5dd19 (99,999,001 Hz). */ +static void dev_xilinx(struct ns16550_dev *d) +{ + memset(d, 0, sizeof(*d)); + d->base = (uintptr_t)regs; + d->reg_off = 0x1000; + d->reg_shift = 2; + d->clk_hz = 0x5f5dd19; + d->io_width = 0; /* let the driver derive 32-bit from reg_shift */ +} + +/* A byte-spaced legacy port. */ +static void dev_legacy(struct ns16550_dev *d) +{ + memset(d, 0, sizeof(*d)); + d->base = (uintptr_t)regs; + d->reg_off = 0; + d->reg_shift = 0; + d->clk_hz = 1843200; /* the classic 1.8432 MHz crystal */ + d->io_width = 0; +} + +START_TEST(test_ns16550_init_programs_xilinx_divisor) +{ + struct ns16550_dev d; + uint32_t div; + + dev_xilinx(&d); + ck_assert_int_eq(ns16550_init(&d, 115200), NS16550_OK); + + /* reg_shift 2 must have selected 32-bit accesses */ + ck_assert_uint_eq(d.io_width, 4); + + /* 99999001 / (16 * 115200) = 54.25 -> rounds to 54 */ + div = (*reg32(&d, NS16550_DLM) << 8) | *reg32(&d, NS16550_DLL); + ck_assert_uint_eq(div, 54); + + /* DLAB must be back down so THR is addressable, and the format 8N1 */ + ck_assert_uint_eq(*reg32(&d, NS16550_LCR), NS16550_LCR_8N1); + /* interrupts off, FIFOs enabled and reset, DTR/RTS asserted */ + ck_assert_uint_eq(*reg32(&d, NS16550_IER), 0); + ck_assert_uint_eq(*reg32(&d, NS16550_FCR), + NS16550_FCR_ENABLE | NS16550_FCR_RXRST | NS16550_FCR_TXRST); + ck_assert_uint_eq(*reg32(&d, NS16550_MCR), + NS16550_MCR_DTR | NS16550_MCR_RTS); +} +END_TEST + +START_TEST(test_ns16550_init_legacy_byte_spaced) +{ + struct ns16550_dev d; + uint32_t div; + + dev_legacy(&d); + ck_assert_int_eq(ns16550_init(&d, 115200), NS16550_OK); + ck_assert_uint_eq(d.io_width, 1); + + /* 1843200 / (16 * 115200) = exactly 1 */ + div = ((uint32_t)*reg8(&d, NS16550_DLM) << 8) | *reg8(&d, NS16550_DLL); + ck_assert_uint_eq(div, 1); + ck_assert_uint_eq(*reg8(&d, NS16550_LCR), NS16550_LCR_8N1); + + /* 9600 on the same clock: 1843200 / 153600 = 12 */ + ck_assert_int_eq(ns16550_init(&d, 9600), NS16550_OK); + div = ((uint32_t)*reg8(&d, NS16550_DLM) << 8) | *reg8(&d, NS16550_DLL); + ck_assert_uint_eq(div, 12); + + /* a divisor above 8 bits must land in both latches */ + ck_assert_int_eq(ns16550_init(&d, 110), NS16550_OK); + div = ((uint32_t)*reg8(&d, NS16550_DLM) << 8) | *reg8(&d, NS16550_DLL); + ck_assert_uint_eq(div, 1047); + ck_assert_uint_gt(*reg8(&d, NS16550_DLM), 0); +} +END_TEST + +START_TEST(test_ns16550_init_rejects_bad_arguments) +{ + struct ns16550_dev d; + + dev_xilinx(&d); + ck_assert_int_eq(ns16550_init(NULL, 115200), NS16550_ERR_ARG); + ck_assert_int_eq(ns16550_init(&d, 0), NS16550_ERR_ARG); + + dev_xilinx(&d); + d.base = 0; + ck_assert_int_eq(ns16550_init(&d, 115200), NS16550_ERR_ARG); + + dev_xilinx(&d); + d.reg_shift = 4; + ck_assert_int_eq(ns16550_init(&d, 115200), NS16550_ERR_ARG); + + /* baud far above the clock rounds the divisor to zero */ + dev_xilinx(&d); + d.clk_hz = 100000; + ck_assert_int_eq(ns16550_init(&d, 115200), NS16550_ERR_CLK); + + /* and a clock far above the baud overflows the 16-bit latch */ + dev_xilinx(&d); + d.clk_hz = 0xF0000000U; + ck_assert_int_eq(ns16550_init(&d, 110), NS16550_ERR_CLK); + + /* A baud whose * 16 wraps 32 bits is refused up front. 0x10000000 is + * the first such value and would otherwise divide by zero. */ + dev_xilinx(&d); + ck_assert_int_eq(ns16550_init(&d, 0x10000000U), NS16550_ERR_ARG); + dev_xilinx(&d); + ck_assert_int_eq(ns16550_init(&d, 0xFFFFFFFFU), NS16550_ERR_ARG); +} +END_TEST + +START_TEST(test_ns16550_write_emits_bytes_when_thre_set) +{ + struct ns16550_dev d; + const char msg[] = "WOLFBOOT READY\r\n"; + + dev_xilinx(&d); + ck_assert_int_eq(ns16550_init(&d, 115200), NS16550_OK); + + /* emulate a port that is always ready and always fully drained */ + *reg32(&d, NS16550_LSR) = NS16550_LSR_THRE | NS16550_LSR_TEMT; + + ck_assert_int_eq(ns16550_write(&d, msg, sizeof(msg) - 1), NS16550_OK); + /* THR is write-only and not a FIFO here, so only the last byte lands */ + ck_assert_uint_eq(*reg32(&d, NS16550_THR), (uint32_t)(uint8_t)'\n'); + + /* a zero-length write is legal and still drains */ + ck_assert_int_eq(ns16550_write(&d, msg, 0), NS16550_OK); + ck_assert_int_eq(ns16550_write(&d, NULL, 0), NS16550_OK); +} +END_TEST + +START_TEST(test_ns16550_write_times_out_on_absent_port) +{ + struct ns16550_dev d; + + dev_xilinx(&d); + ck_assert_int_eq(ns16550_init(&d, 115200), NS16550_OK); + + /* LSR stuck at 0: an unprogrammed PL region. The write must give up + * rather than spin forever - this driver runs on the boot path. */ + *reg32(&d, NS16550_LSR) = 0; + ck_assert_int_eq(ns16550_write(&d, "x", 1), NS16550_ERR_TMO); + + /* THRE set but TEMT never: the bytes go out, the final drain times out */ + *reg32(&d, NS16550_LSR) = NS16550_LSR_THRE; + ck_assert_int_eq(ns16550_write(&d, "x", 1), NS16550_ERR_TMO); + + ck_assert_int_eq(ns16550_write(NULL, "x", 1), NS16550_ERR_ARG); + ck_assert_int_eq(ns16550_write(&d, NULL, 1), NS16550_ERR_ARG); +} +END_TEST + +/* reg-shift/reg-offset arithmetic must not alias two registers together. */ +START_TEST(test_ns16550_register_spacing_is_distinct) +{ + struct ns16550_dev d; + + dev_xilinx(&d); + ck_assert_int_eq(ns16550_init(&d, 115200), NS16550_OK); + + /* LCR (0x03) at reg-offset 0x1000, reg-shift 2 lands at 0x100C */ + ck_assert_ptr_eq((void *)reg32(&d, NS16550_LCR), (void *)(regs + 0x100C)); + /* and the block starts at reg-offset, not at base */ + ck_assert_ptr_eq((void *)reg32(&d, NS16550_THR), (void *)(regs + 0x1000)); + /* nothing was written below reg-offset */ + { + size_t i; + for (i = 0; i < 0x1000; i++) { + ck_assert_uint_eq(regs[i], 0); + } + } +} +END_TEST + +/* can_read reflects LSR.DR, and read() returns the byte in RBR. */ +START_TEST(test_ns16550_read_returns_byte_when_dr_set) +{ + struct ns16550_dev d; + uint8_t c = 0; + int ret; + + memset(&d, 0, sizeof(d)); + d.base = (uintptr_t)regs; + d.io_width = 1; + d.clk_hz = 100000000; + (void)ns16550_init(&d, 115200); + + /* No data yet. */ + *reg8(&d, NS16550_LSR) = 0; + ck_assert_int_eq(ns16550_can_read(&d), 0); + + /* Port presents a byte. */ + *reg8(&d, NS16550_RBR) = 0x5A; + *reg8(&d, NS16550_LSR) = NS16550_LSR_DR; + ck_assert_int_eq(ns16550_can_read(&d), 1); + + ret = ns16550_read(&d, &c); + ck_assert_int_eq(ret, NS16550_OK); + ck_assert_uint_eq(c, 0x5A); +} +END_TEST + +/* read() must bound its wait rather than spin forever on a dead port, and + * both entry points must reject bad arguments instead of dereferencing. */ +START_TEST(test_ns16550_read_times_out_and_checks_arguments) +{ + struct ns16550_dev d; + uint8_t c = 0; + + memset(&d, 0, sizeof(d)); + d.base = (uintptr_t)regs; + d.io_width = 1; + d.clk_hz = 100000000; + (void)ns16550_init(&d, 115200); + + /* DR never asserts: the call returns rather than hanging. */ + *reg8(&d, NS16550_LSR) = 0; + ck_assert_int_eq(ns16550_read(&d, &c), NS16550_ERR_TMO); + + ck_assert_int_eq(ns16550_read(NULL, &c), NS16550_ERR_ARG); + ck_assert_int_eq(ns16550_read(&d, NULL), NS16550_ERR_ARG); + ck_assert_int_eq(ns16550_can_read(NULL), 0); + + /* A device with no base is not a port. */ + memset(&d, 0, sizeof(d)); + ck_assert_int_eq(ns16550_can_read(&d), 0); + ck_assert_int_eq(ns16550_read(&d, &c), NS16550_ERR_ARG); +} +END_TEST + +static Suite *ns16550_suite(void) +{ + Suite *s = suite_create("ns16550"); + TCase *tc = tcase_create("ns16550"); + + tcase_add_checked_fixture(tc, setup, teardown); + tcase_add_test(tc, test_ns16550_init_programs_xilinx_divisor); + tcase_add_test(tc, test_ns16550_init_legacy_byte_spaced); + tcase_add_test(tc, test_ns16550_init_rejects_bad_arguments); + tcase_add_test(tc, test_ns16550_write_emits_bytes_when_thre_set); + tcase_add_test(tc, test_ns16550_write_times_out_on_absent_port); + tcase_add_test(tc, test_ns16550_register_spacing_is_distinct); + tcase_add_test(tc, test_ns16550_read_returns_byte_when_dr_set); + tcase_add_test(tc, test_ns16550_read_times_out_and_checks_arguments); + suite_add_tcase(s, tc); + return s; +} + +int main(void) +{ + int fails; + Suite *s = ns16550_suite(); + SRunner *sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + fails = srunner_ntests_failed(sr); + srunner_free(sr); + return fails; +}