uart: replace the duplicated NS16550 console code with a shared driver

pull/906/head
David Garske 2026-09-15 12:20:08 -07:00 committed by Daniele Lacamera
parent fae9e53653
commit 4f33091f74
17 changed files with 818 additions and 147 deletions

1
.gitignore vendored
View File

@ -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

15
arch.mk
View File

@ -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
@ -1279,6 +1283,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

View File

@ -43,42 +43,25 @@
void hal_flash_init(void);
#ifdef DEBUG_UART
#include "ns16550.h"
static struct ns16550_dev uart_console;
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);
while (!(UART_LSR(UART_SEL) & UART_LSR_TEMT));
/* 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);
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 */

View File

@ -299,28 +299,11 @@
/* LS1028A PC16552D Dual UART */
#define UART_BASE(n) (0x21C0500 + (n * 100))
/* 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))
#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 */
/* 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)

View File

@ -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

View File

@ -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)
{
/* 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);
}
void uart_write(const char* buf, uint32_t sz)
{
(void)ns16550_write(&uart_console, buf, sz);
}
#else /* BUILD_LOADER_STAGE1 */
/* 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)
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);
while (!(get8(UART_LSR(UART_SEL)) & UART_LSR_TEMT))
while (!(get8(S1_LSR) & 0x40))
;
set8(UART_IER(UART_SEL), 0);
set8(UART_FCR(UART_SEL), (UART_FCR_TFR | UART_FCR_RFR | UART_FCR_FEN));
/* 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));
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 */

View File

@ -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

View File

@ -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);

View File

@ -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

189
hal/uart/ns16550.c 100644
View File

@ -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 <stdint.h>
#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;
}

105
include/ns16550.h 100644
View File

@ -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 <stdint.h>
#include <stddef.h>
/* 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 */

View File

@ -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 */

View File

@ -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 :=

View File

@ -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) $@ $<

View File

@ -549,6 +549,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

View File

@ -133,6 +133,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
@ -518,6 +519,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 \

View File

@ -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 <check.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#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;
}