Skip to content

Commit 8d34051

Browse files
committed
uart: add a non-blocking uart_rx() to the PL011 consoles
1 parent aef57a1 commit 8d34051

7 files changed

Lines changed: 387 additions & 2 deletions

File tree

‎docs/Targets.md‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5577,6 +5577,10 @@ xilinx-vmk180-20242 login:
55775577

55785578
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.
55795579

5580+
**Console input**
5581+
5582+
`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.
5583+
55805584
**Diagnosing a boot that stops with no message**
55815585

55825586
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
55895593
*** SYSTEM HALTED ***
55905594
```
55915595

5592-
`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.
5596+
`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.
55935597

55945598
**Boot Performance**
55955599

‎hal/raspi3.c‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
/* PL011 UART registers */
4848
#define UART0_BASE GPIO_BASE + 0x1000
4949
#define UART0_DR ((volatile unsigned int*)(UART0_BASE+0x00))
50+
#define UART0_RSR ((volatile unsigned int*)(UART0_BASE+0x04))
5051
#define UART0_FR ((volatile unsigned int*)(UART0_BASE+0x18))
5152
#define UART0_IBRD ((volatile unsigned int*)(UART0_BASE+0x24))
5253
#define UART0_FBRD ((volatile unsigned int*)(UART0_BASE+0x28))
@@ -142,6 +143,27 @@ void uart_tx(char c)
142143
*UART0_DR = c;
143144
}
144145

146+
/* Non-blocking receive. Returns 1 when a character was read, 0 otherwise. */
147+
int uart_rx(uint8_t *c)
148+
{
149+
unsigned int dr;
150+
151+
/* receive fifo empty */
152+
if (*UART0_FR & 0x10)
153+
return 0;
154+
155+
dr = *UART0_DR;
156+
if (dr & 0xF00) {
157+
/* overrun, break, parity or framing error: drop the character and
158+
* clear the sticky receive status */
159+
*UART0_RSR = 0;
160+
return 0;
161+
}
162+
163+
*c = (uint8_t)(dr & 0xFF);
164+
return 1;
165+
}
166+
145167
char uart_read(void)
146168
{
147169
char c;

‎hal/versal.c‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,33 @@ void uart_write(const char *buf, uint32_t len)
232232
;
233233
}
234234

235+
/* Non-blocking receive. Returns 1 when a character was read, 0 otherwise. */
236+
int uart_rx(uint8_t *c)
237+
{
238+
uint32_t dr;
239+
240+
if ((UART_CR & UART_CR_RXE) == 0) {
241+
/* At EL2 the port is left exactly as PLM configured it, and PLM only
242+
* needs to transmit its own log. Enabling receive on its own does not
243+
* disturb the baud rate or frame format already in place. */
244+
UART_CR |= UART_CR_RXE;
245+
}
246+
247+
if (UART_FR & UART_FR_RXFE)
248+
return 0;
249+
250+
dr = UART_DR;
251+
if (dr & UART_DR_ERR_MASK) {
252+
/* Overrun, break, parity or framing error: drop the character and
253+
* clear the sticky receive status. */
254+
UART_RSR = 0;
255+
return 0;
256+
}
257+
258+
*c = (uint8_t)(dr & UART_DR_DATA_MASK);
259+
return 1;
260+
}
261+
235262
#else
236263
#define uart_init() do {} while(0)
237264
#endif /* DEBUG_UART */

‎hal/versal.h‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,14 @@
190190
#define UART_IMSC UART_REG(UART_IMSC_OFFSET)
191191
#define UART_ICR UART_REG(UART_ICR_OFFSET)
192192

193+
/* Data Register (UARTDR) bits - receive side */
194+
#define UART_DR_OE (1UL << 11) /* Overrun error */
195+
#define UART_DR_BE (1UL << 10) /* Break error */
196+
#define UART_DR_PE (1UL << 9) /* Parity error */
197+
#define UART_DR_FE (1UL << 8) /* Framing error */
198+
#define UART_DR_ERR_MASK (0xFUL << 8)
199+
#define UART_DR_DATA_MASK 0xFFUL
200+
193201
/* Flag Register (UARTFR) bits - Status */
194202
#define UART_FR_RI (1UL << 8) /* Ring indicator */
195203
#define UART_FR_TXFE (1UL << 7) /* TX FIFO empty */

‎tools/unit-tests/Makefile‎

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ TESTS+=unit-imx95-ele
140140
TESTS+=unit-imx95-disk
141141
TESTS+=unit-versal-ext-write
142142
TESTS+=unit-versal-wdt
143+
TESTS+=unit-versal-uart-rx
144+
TESTS+=unit-raspi3-uart-rx
143145
TESTS+=unit-t10xx-qe-firmware
144146
TESTS+=unit-t10xx-flash-status
145147
TESTS+=unit-p1021-erase-advance
@@ -1599,6 +1601,28 @@ versal_wdt_extract.h: ../../hal/versal.c
15991601
unit-versal-wdt: unit-versal-wdt.c versal_wdt_extract.h versal_host.h
16001602
gcc -o $@ unit-versal-wdt.c -DTARGET_versal $(CFLAGS) $(LDFLAGS)
16011603

1604+
# unit-versal-uart-rx runs the real uart_rx() from hal/versal.c against
1605+
# an emulated PL011 register block. UART_REG() resolves to an absolute
1606+
# MMIO address, so the function is extracted verbatim and the test
1607+
# redefines the accessor over a local array.
1608+
versal_uart_rx_extract.h: ../../hal/versal.c
1609+
sed -n '/^int uart_rx(/,/^}/p' $< > $@
1610+
test -s $@
1611+
1612+
unit-versal-uart-rx: unit-versal-uart-rx.c versal_uart_rx_extract.h versal_host.h
1613+
gcc -o $@ unit-versal-uart-rx.c -DARCH_AARCH64 -DARCH_64BIT $(CFLAGS) $(LDFLAGS)
1614+
1615+
# unit-raspi3-uart-rx runs the real uart_rx() from hal/raspi3.c, the
1616+
# other PL011 console in the tree, against an emulated register block.
1617+
# Its accessors are absolute MMIO pointers, so the function is extracted
1618+
# verbatim and the test redefines them over a local array.
1619+
raspi3_uart_rx_extract.h: ../../hal/raspi3.c
1620+
sed -n '/^int uart_rx(/,/^}/p' $< > $@
1621+
test -s $@
1622+
1623+
unit-raspi3-uart-rx: unit-raspi3-uart-rx.c raspi3_uart_rx_extract.h
1624+
gcc -o $@ unit-raspi3-uart-rx.c $(CFLAGS) $(LDFLAGS)
1625+
16021626
# unit-t10xx-qe-firmware runs the real qe_check_firmware() from
16031627
# hal/nxp_t10xx.c (the per-microcode code_offset was never
16041628
# bounded against the declared image, so a self-consistent blob could
@@ -2246,7 +2270,8 @@ GENERATED_SRC:=aurix_erased_extract.h \
22462270
t2080_fman_extract.h \
22472271
ti_hercules_write_extract.h versal_ext_write_extract.h versal_host.c \
22482272
versal_wdt_extract.h \
2249-
versal_host.h versal_qspidev_extract.h zynq_erase_extract.h \
2273+
versal_host.h versal_qspidev_extract.h versal_uart_rx_extract.h \
2274+
raspi3_uart_rx_extract.h zynq_erase_extract.h \
22502275
bbram_crc_extract.h efuse_guards_extract.h \
22512276
zynq_write_extract.h \
22522277
fat32-test.img fat32-test.img.tmp fat32-payload.bin fat32-nested.bin \
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/* unit-raspi3-uart-rx.c
2+
*
3+
* Exercises the real uart_rx() from hal/raspi3.c against an emulated
4+
* PL011 register block. The register accessors are absolute MMIO
5+
* pointers, so the Makefile extracts the function verbatim and they are
6+
* redefined over a local array below before it is included.
7+
*
8+
* Copyright (C) 2026 wolfSSL Inc.
9+
*
10+
* This file is part of wolfBoot.
11+
*
12+
* wolfBoot is free software; you can redistribute it and/or modify
13+
* it under the terms of the GNU General Public License as published by
14+
* the Free Software Foundation; either version 3 of the License, or
15+
* (at your option) any later version.
16+
*
17+
* wolfBoot is distributed in the hope that it will be useful,
18+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
19+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20+
* GNU General Public License for more details.
21+
*
22+
* You should have received a copy of the GNU General Public License
23+
* along with this program; if not, write to the Free Software
24+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
25+
*/
26+
27+
#include <check.h>
28+
#include <stdint.h>
29+
#include <stdio.h>
30+
#include <string.h>
31+
32+
/* Emulated register block, indexed by register offset. */
33+
static unsigned int uart_regs[0x20];
34+
35+
#define UART0_DR ((volatile unsigned int*)&uart_regs[0x00 / 4])
36+
#define UART0_RSR ((volatile unsigned int*)&uart_regs[0x04 / 4])
37+
#define UART0_FR ((volatile unsigned int*)&uart_regs[0x18 / 4])
38+
39+
#define RXFE 0x10
40+
41+
#include "raspi3_uart_rx_extract.h"
42+
43+
static void setup(void)
44+
{
45+
memset(uart_regs, 0, sizeof(uart_regs));
46+
}
47+
48+
static void teardown(void)
49+
{
50+
}
51+
52+
START_TEST(test_empty_fifo_returns_zero)
53+
{
54+
uint8_t c = 0xA5;
55+
56+
*UART0_FR = RXFE;
57+
*UART0_DR = 'x';
58+
59+
ck_assert_int_eq(uart_rx(&c), 0);
60+
ck_assert_uint_eq(c, 0xA5);
61+
}
62+
END_TEST
63+
64+
START_TEST(test_character_received)
65+
{
66+
uint8_t c = 0;
67+
68+
*UART0_FR = 0;
69+
*UART0_DR = 'a';
70+
71+
ck_assert_int_eq(uart_rx(&c), 1);
72+
ck_assert_uint_eq(c, 'a');
73+
}
74+
END_TEST
75+
76+
/* Bits above the data field must not reach the caller. The receive
77+
* error field at [11:8] is covered by test_receive_error_dropped. */
78+
START_TEST(test_status_bits_masked_off)
79+
{
80+
uint8_t c = 0;
81+
82+
*UART0_FR = 0;
83+
*UART0_DR = 0xF000U | 'b';
84+
85+
ck_assert_int_eq(uart_rx(&c), 1);
86+
ck_assert_uint_eq(c, 'b');
87+
}
88+
END_TEST
89+
90+
START_TEST(test_receive_error_dropped)
91+
{
92+
const unsigned int err[4] = { 0x800, 0x400, 0x200, 0x100 };
93+
uint8_t c;
94+
int i;
95+
96+
for (i = 0; i < 4; i++) {
97+
c = 0xA5;
98+
*UART0_FR = 0;
99+
*UART0_DR = err[i] | 'c';
100+
*UART0_RSR = 0xF;
101+
102+
ck_assert_int_eq(uart_rx(&c), 0);
103+
ck_assert_uint_eq(c, 0xA5);
104+
ck_assert_uint_eq(*UART0_RSR, 0);
105+
}
106+
}
107+
END_TEST
108+
109+
Suite *raspi3_uart_rx_suite(void)
110+
{
111+
Suite *s = suite_create("raspi3-uart-rx");
112+
TCase *tc = tcase_create("raspi3-uart-rx");
113+
114+
tcase_add_checked_fixture(tc, setup, teardown);
115+
tcase_add_test(tc, test_empty_fifo_returns_zero);
116+
tcase_add_test(tc, test_character_received);
117+
tcase_add_test(tc, test_status_bits_masked_off);
118+
tcase_add_test(tc, test_receive_error_dropped);
119+
suite_add_tcase(s, tc);
120+
121+
return s;
122+
}
123+
124+
int main(void)
125+
{
126+
int fails;
127+
Suite *s = raspi3_uart_rx_suite();
128+
SRunner *sr = srunner_create(s);
129+
130+
srunner_run_all(sr, CK_NORMAL);
131+
fails = srunner_ntests_failed(sr);
132+
srunner_free(sr);
133+
134+
return fails;
135+
}

0 commit comments

Comments
 (0)