uart: add a non-blocking uart_rx() to the PL011 consoles

pull/946/head
David Garske 2026-10-20 13:31:21 -07:00
parent aef57a113d
commit 8d34051e6e
7 changed files with 387 additions and 2 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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