mirror of https://github.com/wolfSSL/wolfBoot.git
uart: add a non-blocking uart_rx() to the PL011 consoles
parent
aef57a113d
commit
8d34051e6e
|
|
@ -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**
|
||||
|
||||
|
|
|
|||
22
hal/raspi3.c
22
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;
|
||||
|
|
|
|||
27
hal/versal.c
27
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 */
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
Loading…
Reference in New Issue