mirror of https://github.com/wolfSSL/wolfBoot.git
uart: replace the duplicated NS16550 console code with a shared driver
parent
fae9e53653
commit
4f33091f74
|
|
@ -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
15
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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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 */
|
||||
|
|
@ -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 */
|
||||
14
options.mk
14
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 :=
|
||||
|
|
|
|||
|
|
@ -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) $@ $<
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
Loading…
Reference in New Issue