Skip to content

Commit 9378734

Browse files
committed
imx95: add host unit tests for the ELE mailbox and disk_read
1 parent d97500e commit 9378734

4 files changed

Lines changed: 668 additions & 0 deletions

File tree

‎.gitignore‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,8 @@ tools/unit-tests/unit-flash-write-cc26x2
487487
tools/unit-tests/unit-ls1028a-xspi-write
488488
tools/unit-tests/unit-imx8qm-usdhc-shim
489489
tools/unit-tests/unit-imx8qm-flexspi-write
490+
tools/unit-tests/unit-imx95-ele
491+
tools/unit-tests/unit-imx95-disk
490492
tools/unit-tests/unit-nvm-cache-scrub
491493
tools/unit-tests/unit-p1021-fcm-bytes
492494
tools/unit-tests/unit-p1021-qe-firmware
@@ -511,6 +513,8 @@ tools/unit-tests/unit-sama5d3-ext-read
511513
# sources generated by the unit-test extraction rules
512514
tools/unit-tests/aurix_erased_extract.h
513515
tools/unit-tests/nvm_cache_scrub_extract.h
516+
tools/unit-tests/imx95_ele_extract.h
517+
tools/unit-tests/imx95_disk_extract.h
514518
tools/unit-tests/nxp_ls1028a_host.c
515519
tools/unit-tests/nxp_p1021_host.c
516520
tools/unit-tests/nxp_t10xx_fixup_extract.h

‎tools/unit-tests/Makefile‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ TESTS+=unit-ct-compare
130130
TESTS+=unit-versal-qspi-dma
131131
TESTS+=unit-imx8qm-usdhc-shim
132132
TESTS+=unit-imx8qm-flexspi-write
133+
TESTS+=unit-imx95-ele
134+
TESTS+=unit-imx95-disk
133135
TESTS+=unit-versal-ext-write
134136
TESTS+=unit-t10xx-qe-firmware
135137
TESTS+=unit-t10xx-flash-status
@@ -1404,6 +1406,48 @@ imx8qm_flexspi_extract.h: ../../hal/imx8qm.c
14041406
unit-imx8qm-flexspi-write: unit-imx8qm-flexspi-write.c imx8qm_flexspi_extract.h
14051407
gcc -o $@ unit-imx8qm-flexspi-write.c -I../../hal $(CFLAGS) $(LDFLAGS)
14061408

1409+
# unit-imx95-ele drives the real ELE mailbox transport from
1410+
# hal/imx95_a55_stage1.c against an emulated MU. Extracted rather than
1411+
# host-copied for the same reason as the i.MX8QM rules above; the barrier and
1412+
# the CNTFRQ_EL0 read are rewritten, since a host has neither.
1413+
imx95_ele_extract.h: ../../hal/imx95_a55_stage1.c
1414+
{ sed -n '/^#define ELE_MU_PAR /,/^#define ELE_CALL_BROKEN /p' $<; echo; \
1415+
sed -n '/^#define ELE_TIMER_PROBE_LOOPS /,/^#define ELE_MU_POLL_LOOPS /p' $<; echo; \
1416+
sed -n '/^\/\* Returns 0 when the system counter is not advancing/,/^static void ele_report(/p' $< \
1417+
| sed '/^static void ele_report(/d'; } \
1418+
| sed -e 's/__asm__ volatile("dsb sy" ::: "memory");/ELE_BARRIER();/' \
1419+
-e 's/__asm__ volatile("mrs %0, cntfrq_el0" : "=r"(hz));/hz = ELE_TEST_CNTFRQ;/' > $@
1420+
test -s $@
1421+
@! grep -q '__asm__' $@ || { echo "$@: unexpected asm in the extracted transport"; false; }
1422+
@for m in 'ELE_BARRIER();' 'hz = ELE_TEST_CNTFRQ;'; do \
1423+
grep -qF "$$m" $@ || { echo "$@: the rewrite for '$$m' no longer matches"; exit 1; }; \
1424+
done
1425+
@for f in 'static uint64_t timer_deadline(' 'static int mu_wait(' \
1426+
'static int ele_call(' '#define ELE_MU_PAR ' '#define ELE_MU_TR(' \
1427+
'#define ELE_MU_RR(' '#define ELE_TIMER_PROBE_LOOPS ' \
1428+
'#define ELE_MU_POLL_LOOPS ' '#define ELE_CALL_BROKEN '; do \
1429+
grep -qF "$$f" $@ || { echo "$@: '$$f' missing - the extraction anchors no longer bracket the whole transport"; exit 1; }; \
1430+
done
1431+
1432+
unit-imx95-ele: unit-imx95-ele.c imx95_ele_extract.h
1433+
gcc -o $@ unit-imx95-ele.c $(CFLAGS) $(LDFLAGS)
1434+
1435+
# unit-imx95-disk drives the real byte-addressed disk_read() from
1436+
# hal/imx95_a55.c against an emulated card, checking where each byte lands and
1437+
# the bounce-buffer paths rather than just the returned length.
1438+
imx95_disk_extract.h: ../../hal/imx95_a55.c
1439+
sed -n '/^\/\* Byte-addressed (src\/disk.c passes byte offsets)/,/^\/\* Not implemented: nothing in the boot path writes. \*\/$$/p' $< \
1440+
| sed '/^\/\* Not implemented: nothing in the boot path writes. \*\/$$/d' > $@
1441+
test -s $@
1442+
@! grep -q '__asm__' $@ || { echo "$@: unexpected asm in the extracted reader"; false; }
1443+
@for f in 'int disk_read(' 'card_ready' 'sd_read_blocks(' 'SD_BLOCK_SIZE' \
1444+
'SD_MAX_BLOCKS'; do \
1445+
grep -qF "$$f" $@ || { echo "$@: '$$f' missing - the extraction anchors no longer bracket disk_read"; exit 1; }; \
1446+
done
1447+
1448+
unit-imx95-disk: unit-imx95-disk.c imx95_disk_extract.h
1449+
gcc -o $@ unit-imx95-disk.c $(CFLAGS) $(LDFLAGS)
1450+
14071451
unit-versal-qspi-dma: unit-versal-qspi-dma.c versal_host.c
14081452
gcc -o $@ unit-versal-qspi-dma.c -DTARGET_versal -DEXT_FLASH -DARCH_AARCH64 -DARCH_64BIT $(CFLAGS) $(LDFLAGS)
14091453

@@ -1996,6 +2040,7 @@ covclean:
19962040
# so "clean" removes them and so there is one place that names them.
19972041
GENERATED_SRC:=aurix_erased_extract.h \
19982042
imx8qm_usdhc_extract.h imx8qm_flexspi_extract.h \
2043+
imx95_ele_extract.h imx95_disk_extract.h \
19992044
hifive1_flash_write_extract.h nvm_cache_scrub_extract.h \
20002045
nxp_ls1028a_host.c nxp_p1021_host.c nxp_t10xx_fixup_extract.h \
20012046
p1021_erase_extract.h p1021_erase_fn_extract.h \

‎tools/unit-tests/unit-imx95-disk.c‎

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
/* unit-imx95-disk.c
2+
*
3+
* Host test for the i.MX95 byte-addressed disk_read() shim.
4+
*
5+
* disk_read() is extracted verbatim from hal/imx95_a55.c into
6+
* imx95_disk_extract.h, so this exercises the shipped code. The card below it
7+
* is modelled: sd_read_blocks() serves blocks from a synthetic image whose
8+
* every byte is derived from its own offset, so a misplaced copy shows up as
9+
* wrong data rather than merely the wrong length.
10+
*
11+
* The destination sits inside a canaried buffer, because the interesting
12+
* failure for a bounce-buffer path is a write past the requested range.
13+
*
14+
* Copyright (C) 2026 wolfSSL Inc.
15+
*
16+
* This file is part of wolfBoot.
17+
*
18+
* wolfBoot is free software; you can redistribute it and/or modify
19+
* it under the terms of the GNU General Public License as published by
20+
* the Free Software Foundation; either version 2 of the License, or
21+
* (at your option) any later version.
22+
*
23+
* wolfBoot is distributed in the hope that it will be useful,
24+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
25+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26+
* GNU General Public License for more details.
27+
*
28+
* You should have received a copy of the GNU General Public License
29+
* along with this program; if not, write to the Free Software
30+
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA
31+
*/
32+
33+
#include <stdio.h>
34+
#include <stdint.h>
35+
#include <limits.h>
36+
#include <string.h>
37+
38+
#define SD_BLOCK_SIZE 512
39+
#define SD_MAX_BLOCKS 1024
40+
41+
static int card_ready;
42+
static int sd_read_blocks(uint32_t lba, uint32_t blocks, uint8_t *buf);
43+
44+
#include "imx95_disk_extract.h"
45+
46+
/* ---- the card ----------------------------------------------------------- */
47+
48+
#define CARD_BLOCKS (SD_MAX_BLOCKS + 16U)
49+
50+
static struct {
51+
uint32_t calls;
52+
uint32_t max_blocks; /* largest 'blocks' any one call asked for */
53+
uint32_t fail_after; /* 0 = never fail */
54+
int unaligned; /* a destination sd_read_blocks would fault on */
55+
} card;
56+
57+
/* Distinct per absolute byte offset, so a block placed at the wrong offset or
58+
* read from the wrong LBA cannot coincidentally match. */
59+
static uint8_t card_byte(uint64_t off)
60+
{
61+
return (uint8_t)((off * 31U) ^ (off >> 8) ^ 0xA5U);
62+
}
63+
64+
static int sd_read_blocks(uint32_t lba, uint32_t blocks, uint8_t *buf)
65+
{
66+
uint32_t i;
67+
68+
card.calls++;
69+
if (blocks > card.max_blocks)
70+
card.max_blocks = blocks;
71+
if (blocks == 0U || blocks > SD_MAX_BLOCKS)
72+
return -1;
73+
if (((uintptr_t)buf & 3U) != 0U)
74+
card.unaligned = 1; /* would fault on the target's -mstrict-align */
75+
if (card.fail_after != 0U && card.calls >= card.fail_after)
76+
return -1;
77+
if ((uint64_t)lba + blocks > CARD_BLOCKS)
78+
return -1;
79+
for (i = 0; i < blocks * SD_BLOCK_SIZE; i++)
80+
buf[i] = card_byte((uint64_t)lba * SD_BLOCK_SIZE + i);
81+
return 0;
82+
}
83+
84+
static void card_reset(void)
85+
{
86+
memset(&card, 0, sizeof(card));
87+
card_ready = 1;
88+
}
89+
90+
/* ---- destination, canaried both sides ----------------------------------- */
91+
92+
#define PAD 64U
93+
#define DSTMAX (CARD_BLOCKS * SD_BLOCK_SIZE)
94+
95+
static uint8_t arena[PAD + DSTMAX + PAD];
96+
97+
static uint8_t *dst_at(uint32_t skew)
98+
{
99+
memset(arena, 0xCC, sizeof(arena));
100+
return arena + PAD + skew;
101+
}
102+
103+
static int canaries_intact(uint8_t *dst, uint32_t len)
104+
{
105+
uint32_t i;
106+
107+
for (i = 0; i < sizeof(arena); i++) {
108+
uint8_t *p = arena + i;
109+
if (p >= dst && p < dst + len)
110+
continue;
111+
if (*p != 0xCC)
112+
return 0;
113+
}
114+
return 1;
115+
}
116+
117+
/* ---- helpers ------------------------------------------------------------ */
118+
119+
static int fails;
120+
121+
static void check(int cond, const char *what)
122+
{
123+
printf(" %-58s %s\n", what, cond ? "ok" : "FAILED");
124+
if (!cond)
125+
fails++;
126+
}
127+
128+
/* One read, fully verified: return value, every byte, and the canaries. */
129+
static void read_case(const char *what, uint64_t start, uint32_t count,
130+
uint32_t skew)
131+
{
132+
uint8_t *dst = dst_at(skew);
133+
uint32_t i;
134+
int ret, bad = -1;
135+
136+
card_reset();
137+
ret = disk_read(0, start, count, dst);
138+
printf("%s (start=%llu count=%u skew=%u)\n", what,
139+
(unsigned long long)start, count, skew);
140+
check(ret == (int)count, "returns the byte count requested");
141+
for (i = 0; i < count; i++) {
142+
if (dst[i] != card_byte(start + i)) {
143+
bad = (int)i;
144+
break;
145+
}
146+
}
147+
if (bad >= 0)
148+
printf(" first wrong byte at +%d\n", bad);
149+
check(bad < 0, "every byte comes from the right card offset");
150+
check(canaries_intact(dst, count), "nothing written outside the range");
151+
check(!card.unaligned, "sd_read_blocks never gets an unaligned buffer");
152+
check(card.max_blocks <= SD_MAX_BLOCKS, "no request exceeds SD_MAX_BLOCKS");
153+
}
154+
155+
/* ---- tests -------------------------------------------------------------- */
156+
157+
static void test_shapes(void)
158+
{
159+
read_case("aligned, one whole block", 0, SD_BLOCK_SIZE, 0);
160+
read_case("aligned, several whole blocks", 0, 4U * SD_BLOCK_SIZE, 0);
161+
read_case("head straddles a block boundary", 3, SD_BLOCK_SIZE, 0);
162+
read_case("tail shorter than a block", 0, 100, 0);
163+
read_case("head and tail both partial", 511, SD_BLOCK_SIZE + 2U, 0);
164+
read_case("a single byte", 1234, 1, 0);
165+
read_case("misaligned destination", 0, 4U * SD_BLOCK_SIZE, 1);
166+
read_case("misaligned destination and offset start", 7, 2000, 3);
167+
}
168+
169+
/* More blocks than one command carries, so disk_read() must split the transfer.
170+
* The assertions on call count and request size are what keep this case honest:
171+
* a count that quietly fits in one command would exercise nothing. */
172+
static void test_chunking(void)
173+
{
174+
uint32_t count = (SD_MAX_BLOCKS + 8U) * SD_BLOCK_SIZE;
175+
176+
read_case("spans more blocks than one command carries", 0, count, 0);
177+
check(card.calls >= 2, "the transfer was split across commands");
178+
check(card.max_blocks == SD_MAX_BLOCKS,
179+
"the largest request is exactly SD_MAX_BLOCKS");
180+
printf(" %u commands, largest %u blocks\n", card.calls, card.max_blocks);
181+
}
182+
183+
static void test_zero_count(void)
184+
{
185+
uint8_t *dst = dst_at(0);
186+
187+
puts("a zero-length read touches nothing");
188+
card_reset();
189+
check(disk_read(0, 0, 0, dst) == 0, "returns 0");
190+
check(card.calls == 0, "issues no card command");
191+
check(canaries_intact(dst, 0), "writes nothing");
192+
}
193+
194+
static void test_no_card(void)
195+
{
196+
uint8_t *dst = dst_at(0);
197+
198+
puts("a read before the card is ready fails");
199+
card_reset();
200+
card_ready = 0;
201+
check(disk_read(0, 0, SD_BLOCK_SIZE, dst) == -1, "returns -1");
202+
check(card.calls == 0, "issues no card command");
203+
}
204+
205+
static void test_error_propagates(void)
206+
{
207+
uint8_t *dst = dst_at(0);
208+
209+
puts("a card error is propagated, not partially reported");
210+
card_reset();
211+
card.fail_after = 1;
212+
check(disk_read(0, 0, 4U * SD_BLOCK_SIZE, dst) == -1, "returns -1");
213+
214+
card_reset();
215+
card.fail_after = 2;
216+
check(disk_read(0, 3, 4U * SD_BLOCK_SIZE, dst) == -1,
217+
"returns -1 when the failure is mid-transfer");
218+
}
219+
220+
/* The card command argument is 32-bit. An offset whose block number does not
221+
* fit must be refused rather than wrapped into an unrelated sector. */
222+
static void test_lba_limit(void)
223+
{
224+
uint8_t *dst = dst_at(0);
225+
uint64_t past = (0xFFFFFFFFULL + 1ULL) * SD_BLOCK_SIZE;
226+
227+
puts("an offset past the 32-bit block limit is refused");
228+
card_reset();
229+
check(disk_read(0, past, SD_BLOCK_SIZE, dst) == -1, "returns -1");
230+
check(card.calls == 0, "issues no card command");
231+
232+
card_reset();
233+
check(disk_read(0, past - 1ULL, 8, dst) == -1,
234+
"refused rather than wrapped when the range straddles the limit");
235+
}
236+
237+
/* The count is reported through an int return, so one that cannot be expressed
238+
* must be refused rather than answered with what looks like an error code. */
239+
static void test_unreportable_count(void)
240+
{
241+
uint8_t *dst = dst_at(0);
242+
243+
puts("a count too large to report is refused");
244+
card_reset();
245+
check(disk_read(0, 0, 0x80000000U, dst) == -1, "returns -1");
246+
check(card.calls == 0, "issues no card command");
247+
check(canaries_intact(dst, 0), "writes nothing");
248+
}
249+
250+
/* start + done is formed inside the loop, so a range whose inclusive end is not
251+
* representable would wrap there and read some unrelated low sector. */
252+
static void test_end_overflow(void)
253+
{
254+
uint8_t *dst = dst_at(0);
255+
256+
puts("a byte range whose end is not representable is refused");
257+
card_reset();
258+
check(disk_read(0, UINT64_MAX - 4ULL, 64, dst) == -1,
259+
"returns -1 when start + count wraps");
260+
check(card.calls == 0, "issues no card command");
261+
card_reset();
262+
check(disk_read(0, UINT64_MAX, 1, dst) == -1,
263+
"returns -1 for the last representable byte plus one");
264+
check(card.calls == 0, "still issues no card command");
265+
}
266+
267+
int main(void)
268+
{
269+
test_shapes();
270+
test_chunking();
271+
test_zero_count();
272+
test_no_card();
273+
test_error_propagates();
274+
test_lba_limit();
275+
test_unreportable_count();
276+
test_end_overflow();
277+
278+
printf("\n%s\n", fails ? "FAILURES" : "all checks passed");
279+
return fails ? 1 : 0;
280+
}

0 commit comments

Comments
 (0)