From 8d34051e6ee3aa15218902a6aca0ac3c2ef8163d Mon Sep 17 00:00:00 2001 From: David Garske Date: Tue, 20 Oct 2026 13:31:21 -0700 Subject: [PATCH] uart: add a non-blocking uart_rx() to the PL011 consoles --- docs/Targets.md | 6 +- hal/raspi3.c | 22 ++++ hal/versal.c | 27 ++++ hal/versal.h | 8 ++ tools/unit-tests/Makefile | 27 +++- tools/unit-tests/unit-raspi3-uart-rx.c | 135 ++++++++++++++++++++ tools/unit-tests/unit-versal-uart-rx.c | 164 +++++++++++++++++++++++++ 7 files changed, 387 insertions(+), 2 deletions(-) create mode 100644 tools/unit-tests/unit-raspi3-uart-rx.c create mode 100644 tools/unit-tests/unit-versal-uart-rx.c diff --git a/docs/Targets.md b/docs/Targets.md index 13275e6bb0..388fb6b468 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -5577,6 +5577,10 @@ xilinx-vmk180-20242 login: Inflating a 24 MB kernel takes about 7 s on the A72, spent entirely between the `Decompressing` and `Decompressed` lines with no intermediate output. Do not mistake that gap for a hang. +**Console input** + +`DEBUG_UART=1` provides the console output path, and `uart_rx()` the matching non-blocking receive: it returns 1 when a character was read and 0 when the receive FIFO is empty. This is the only input path, as wolfBoot links `-nostdlib` and there is no C library `getchar()` behind it. Declare it as `extern int uart_rx(uint8_t *c);` rather than including `uart_drv.h`, which also declares `uart_tx()` and `uart_init()` with signatures this HAL does not use. When wolfBoot runs at EL2 the port is left exactly as the PLM configured it, so `uart_rx()` sets `UARTCR.RXE` itself on first use and touches nothing else. Blocking on input from a hook such as `wolfBoot_hook_postinit()` is safe: wolfBoot imposes no timeout of its own at that point. + **Diagnosing a boot that stops with no message** wolfBoot runs at EL2 on Versal, where any abort is fatal. With `DEBUG_UART=1` (the default in both Versal example configs) the handlers print the syndrome: @@ -5589,7 +5593,7 @@ FAR_EL2: 0x00000000F9200000 *** SYSTEM HALTED *** ``` -`ESR_EL2[31:26]` is the exception class, `[5:0]` the fault status, and `FAR_EL2` the faulting address. A stop with no such banner is not an exception - check the PLM's `PMC EAM` output on the same UART. Error IDs `0xA`/`0xB` are `DDRMB_CR`/`DDRMB_NCR`, DDR controller correctable and uncorrectable errors, which the PLM logs without acting on. +`ESR_EL2[31:26]` is the exception class, `[5:0]` the fault status, and `FAR_EL2` the faulting address. An instruction abort (exception class `0x21`) reporting `ELR_EL2` and `FAR_EL2` as zero is a call through a null or unresolved function pointer rather than a bad data access. A stop with no such banner is not an exception - check the PLM's `PMC EAM` output on the same UART. Error IDs `0xA`/`0xB` are `DDRMB_CR`/`DDRMB_NCR`, DDR controller correctable and uncorrectable errors, which the PLM logs without acting on. **Boot Performance** diff --git a/hal/raspi3.c b/hal/raspi3.c index e78d75d7f4..99c6bb55cc 100644 --- a/hal/raspi3.c +++ b/hal/raspi3.c @@ -47,6 +47,7 @@ /* PL011 UART registers */ #define UART0_BASE GPIO_BASE + 0x1000 #define UART0_DR ((volatile unsigned int*)(UART0_BASE+0x00)) +#define UART0_RSR ((volatile unsigned int*)(UART0_BASE+0x04)) #define UART0_FR ((volatile unsigned int*)(UART0_BASE+0x18)) #define UART0_IBRD ((volatile unsigned int*)(UART0_BASE+0x24)) #define UART0_FBRD ((volatile unsigned int*)(UART0_BASE+0x28)) @@ -142,6 +143,27 @@ void uart_tx(char c) *UART0_DR = c; } +/* Non-blocking receive. Returns 1 when a character was read, 0 otherwise. */ +int uart_rx(uint8_t *c) +{ + unsigned int dr; + + /* receive fifo empty */ + if (*UART0_FR & 0x10) + return 0; + + dr = *UART0_DR; + if (dr & 0xF00) { + /* overrun, break, parity or framing error: drop the character and + * clear the sticky receive status */ + *UART0_RSR = 0; + return 0; + } + + *c = (uint8_t)(dr & 0xFF); + return 1; +} + char uart_read(void) { char c; diff --git a/hal/versal.c b/hal/versal.c index ed3e90ce25..2694e6344a 100644 --- a/hal/versal.c +++ b/hal/versal.c @@ -232,6 +232,33 @@ void uart_write(const char *buf, uint32_t len) ; } +/* Non-blocking receive. Returns 1 when a character was read, 0 otherwise. */ +int uart_rx(uint8_t *c) +{ + uint32_t dr; + + if ((UART_CR & UART_CR_RXE) == 0) { + /* At EL2 the port is left exactly as PLM configured it, and PLM only + * needs to transmit its own log. Enabling receive on its own does not + * disturb the baud rate or frame format already in place. */ + UART_CR |= UART_CR_RXE; + } + + if (UART_FR & UART_FR_RXFE) + return 0; + + dr = UART_DR; + if (dr & UART_DR_ERR_MASK) { + /* Overrun, break, parity or framing error: drop the character and + * clear the sticky receive status. */ + UART_RSR = 0; + return 0; + } + + *c = (uint8_t)(dr & UART_DR_DATA_MASK); + return 1; +} + #else #define uart_init() do {} while(0) #endif /* DEBUG_UART */ diff --git a/hal/versal.h b/hal/versal.h index e23e9aa9ea..89ceb3bfb9 100644 --- a/hal/versal.h +++ b/hal/versal.h @@ -190,6 +190,14 @@ #define UART_IMSC UART_REG(UART_IMSC_OFFSET) #define UART_ICR UART_REG(UART_ICR_OFFSET) +/* Data Register (UARTDR) bits - receive side */ +#define UART_DR_OE (1UL << 11) /* Overrun error */ +#define UART_DR_BE (1UL << 10) /* Break error */ +#define UART_DR_PE (1UL << 9) /* Parity error */ +#define UART_DR_FE (1UL << 8) /* Framing error */ +#define UART_DR_ERR_MASK (0xFUL << 8) +#define UART_DR_DATA_MASK 0xFFUL + /* Flag Register (UARTFR) bits - Status */ #define UART_FR_RI (1UL << 8) /* Ring indicator */ #define UART_FR_TXFE (1UL << 7) /* TX FIFO empty */ diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index f0c0fbf9c1..2c19083de1 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -140,6 +140,8 @@ TESTS+=unit-imx95-ele TESTS+=unit-imx95-disk TESTS+=unit-versal-ext-write TESTS+=unit-versal-wdt +TESTS+=unit-versal-uart-rx +TESTS+=unit-raspi3-uart-rx TESTS+=unit-t10xx-qe-firmware TESTS+=unit-t10xx-flash-status TESTS+=unit-p1021-erase-advance @@ -1599,6 +1601,28 @@ versal_wdt_extract.h: ../../hal/versal.c unit-versal-wdt: unit-versal-wdt.c versal_wdt_extract.h versal_host.h gcc -o $@ unit-versal-wdt.c -DTARGET_versal $(CFLAGS) $(LDFLAGS) +# unit-versal-uart-rx runs the real uart_rx() from hal/versal.c against +# an emulated PL011 register block. UART_REG() resolves to an absolute +# MMIO address, so the function is extracted verbatim and the test +# redefines the accessor over a local array. +versal_uart_rx_extract.h: ../../hal/versal.c + sed -n '/^int uart_rx(/,/^}/p' $< > $@ + test -s $@ + +unit-versal-uart-rx: unit-versal-uart-rx.c versal_uart_rx_extract.h versal_host.h + gcc -o $@ unit-versal-uart-rx.c -DARCH_AARCH64 -DARCH_64BIT $(CFLAGS) $(LDFLAGS) + +# unit-raspi3-uart-rx runs the real uart_rx() from hal/raspi3.c, the +# other PL011 console in the tree, against an emulated register block. +# Its accessors are absolute MMIO pointers, so the function is extracted +# verbatim and the test redefines them over a local array. +raspi3_uart_rx_extract.h: ../../hal/raspi3.c + sed -n '/^int uart_rx(/,/^}/p' $< > $@ + test -s $@ + +unit-raspi3-uart-rx: unit-raspi3-uart-rx.c raspi3_uart_rx_extract.h + gcc -o $@ unit-raspi3-uart-rx.c $(CFLAGS) $(LDFLAGS) + # unit-t10xx-qe-firmware runs the real qe_check_firmware() from # hal/nxp_t10xx.c (the per-microcode code_offset was never # bounded against the declared image, so a self-consistent blob could @@ -2246,7 +2270,8 @@ GENERATED_SRC:=aurix_erased_extract.h \ t2080_fman_extract.h \ ti_hercules_write_extract.h versal_ext_write_extract.h versal_host.c \ versal_wdt_extract.h \ - versal_host.h versal_qspidev_extract.h zynq_erase_extract.h \ + versal_host.h versal_qspidev_extract.h versal_uart_rx_extract.h \ + raspi3_uart_rx_extract.h zynq_erase_extract.h \ bbram_crc_extract.h efuse_guards_extract.h \ zynq_write_extract.h \ fat32-test.img fat32-test.img.tmp fat32-payload.bin fat32-nested.bin \ diff --git a/tools/unit-tests/unit-raspi3-uart-rx.c b/tools/unit-tests/unit-raspi3-uart-rx.c new file mode 100644 index 0000000000..725ebf035e --- /dev/null +++ b/tools/unit-tests/unit-raspi3-uart-rx.c @@ -0,0 +1,135 @@ +/* unit-raspi3-uart-rx.c + * + * Exercises the real uart_rx() from hal/raspi3.c against an emulated + * PL011 register block. The register accessors are absolute MMIO + * pointers, so the Makefile extracts the function verbatim and they are + * redefined over a local array below before it is included. + * + * 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 + +/* Emulated register block, indexed by register offset. */ +static unsigned int uart_regs[0x20]; + +#define UART0_DR ((volatile unsigned int*)&uart_regs[0x00 / 4]) +#define UART0_RSR ((volatile unsigned int*)&uart_regs[0x04 / 4]) +#define UART0_FR ((volatile unsigned int*)&uart_regs[0x18 / 4]) + +#define RXFE 0x10 + +#include "raspi3_uart_rx_extract.h" + +static void setup(void) +{ + memset(uart_regs, 0, sizeof(uart_regs)); +} + +static void teardown(void) +{ +} + +START_TEST(test_empty_fifo_returns_zero) +{ + uint8_t c = 0xA5; + + *UART0_FR = RXFE; + *UART0_DR = 'x'; + + ck_assert_int_eq(uart_rx(&c), 0); + ck_assert_uint_eq(c, 0xA5); +} +END_TEST + +START_TEST(test_character_received) +{ + uint8_t c = 0; + + *UART0_FR = 0; + *UART0_DR = 'a'; + + ck_assert_int_eq(uart_rx(&c), 1); + ck_assert_uint_eq(c, 'a'); +} +END_TEST + +/* Bits above the data field must not reach the caller. The receive + * error field at [11:8] is covered by test_receive_error_dropped. */ +START_TEST(test_status_bits_masked_off) +{ + uint8_t c = 0; + + *UART0_FR = 0; + *UART0_DR = 0xF000U | 'b'; + + ck_assert_int_eq(uart_rx(&c), 1); + ck_assert_uint_eq(c, 'b'); +} +END_TEST + +START_TEST(test_receive_error_dropped) +{ + const unsigned int err[4] = { 0x800, 0x400, 0x200, 0x100 }; + uint8_t c; + int i; + + for (i = 0; i < 4; i++) { + c = 0xA5; + *UART0_FR = 0; + *UART0_DR = err[i] | 'c'; + *UART0_RSR = 0xF; + + ck_assert_int_eq(uart_rx(&c), 0); + ck_assert_uint_eq(c, 0xA5); + ck_assert_uint_eq(*UART0_RSR, 0); + } +} +END_TEST + +Suite *raspi3_uart_rx_suite(void) +{ + Suite *s = suite_create("raspi3-uart-rx"); + TCase *tc = tcase_create("raspi3-uart-rx"); + + tcase_add_checked_fixture(tc, setup, teardown); + tcase_add_test(tc, test_empty_fifo_returns_zero); + tcase_add_test(tc, test_character_received); + tcase_add_test(tc, test_status_bits_masked_off); + tcase_add_test(tc, test_receive_error_dropped); + suite_add_tcase(s, tc); + + return s; +} + +int main(void) +{ + int fails; + Suite *s = raspi3_uart_rx_suite(); + SRunner *sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + fails = srunner_ntests_failed(sr); + srunner_free(sr); + + return fails; +} diff --git a/tools/unit-tests/unit-versal-uart-rx.c b/tools/unit-tests/unit-versal-uart-rx.c new file mode 100644 index 0000000000..0c832b112b --- /dev/null +++ b/tools/unit-tests/unit-versal-uart-rx.c @@ -0,0 +1,164 @@ +/* unit-versal-uart-rx.c + * + * Exercises the real uart_rx() from hal/versal.c against an emulated + * PL011 register block. The receive path cannot be exercised on the + * VMK180 on hand, whose console FT4232H does not enumerate. + * + * UART_REG() resolves to an absolute MMIO address, so the Makefile + * extracts the function verbatim and it is redefined over a local + * array below before the function is included. + * + * 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 + +/* The PL011 register offsets and bit masks, from hal/versal.h. */ +#include "versal_host.h" + +/* Emulated register block, indexed by register offset. */ +static uint32_t uart_regs[0x20]; + +#undef UART_REG +#define UART_REG(offset) (uart_regs[(offset) / 4]) + +#include "versal_uart_rx_extract.h" + +static void setup(void) +{ + memset(uart_regs, 0, sizeof(uart_regs)); + UART_CR = UART_CR_UARTEN | UART_CR_TXE | UART_CR_RXE; +} + +static void teardown(void) +{ +} + +/* PLM configures the port for its own log output, which needs transmit + * only, and at EL2 uart_init() leaves it that way. */ +START_TEST(test_receive_enabled_without_disturbing_port) +{ + uint8_t c = 0; + + UART_CR = UART_CR_UARTEN | UART_CR_TXE; + UART_IBRD = 81; + UART_FBRD = 24; + UART_LCR = UART_LCR_WLEN_8 | UART_LCR_FEN; + UART_FR = UART_FR_RXFE; + + ck_assert_int_eq(uart_rx(&c), 0); + ck_assert(UART_CR & UART_CR_RXE); + ck_assert(UART_CR & UART_CR_UARTEN); + ck_assert(UART_CR & UART_CR_TXE); + ck_assert_uint_eq(UART_IBRD, 81); + ck_assert_uint_eq(UART_FBRD, 24); + ck_assert_uint_eq(UART_LCR, UART_LCR_WLEN_8 | UART_LCR_FEN); +} +END_TEST + +START_TEST(test_empty_fifo_returns_zero) +{ + uint8_t c = 0xA5; + + UART_FR = UART_FR_RXFE; + UART_DR = 'x'; + + ck_assert_int_eq(uart_rx(&c), 0); + ck_assert_uint_eq(c, 0xA5); +} +END_TEST + +START_TEST(test_character_received) +{ + uint8_t c = 0; + + UART_FR = 0; + UART_DR = 'a'; + + ck_assert_int_eq(uart_rx(&c), 1); + ck_assert_uint_eq(c, 'a'); +} +END_TEST + +/* Bits above the data field must not reach the caller. The receive + * error field at [11:8] is covered by test_receive_error_dropped. */ +START_TEST(test_status_bits_masked_off) +{ + uint8_t c = 0; + + UART_FR = 0; + UART_DR = 0xF000UL | 'b'; + + ck_assert_int_eq(uart_rx(&c), 1); + ck_assert_uint_eq(c, 'b'); +} +END_TEST + +START_TEST(test_receive_error_dropped) +{ + const uint32_t err[4] = { + UART_DR_OE, UART_DR_BE, UART_DR_PE, UART_DR_FE + }; + uint8_t c; + int i; + + for (i = 0; i < 4; i++) { + c = 0xA5; + UART_FR = 0; + UART_DR = err[i] | 'c'; + UART_RSR = 0xF; + + ck_assert_int_eq(uart_rx(&c), 0); + ck_assert_uint_eq(c, 0xA5); + ck_assert_uint_eq(UART_RSR, 0); + } +} +END_TEST + +Suite *versal_uart_rx_suite(void) +{ + Suite *s = suite_create("versal-uart-rx"); + TCase *tc = tcase_create("versal-uart-rx"); + + tcase_add_checked_fixture(tc, setup, teardown); + tcase_add_test(tc, test_receive_enabled_without_disturbing_port); + tcase_add_test(tc, test_empty_fifo_returns_zero); + tcase_add_test(tc, test_character_received); + tcase_add_test(tc, test_status_bits_masked_off); + tcase_add_test(tc, test_receive_error_dropped); + suite_add_tcase(s, tc); + + return s; +} + +int main(void) +{ + int fails; + Suite *s = versal_uart_rx_suite(); + SRunner *sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + fails = srunner_ntests_failed(sr); + srunner_free(sr); + + return fails; +}