mirror of https://github.com/wolfSSL/wolfBoot.git
353 lines
11 KiB
C
353 lines
11 KiB
C
/* unit-sama5d3-ext-read.c
|
|
*
|
|
* Regression test: ext_flash_read() in hal/sama5d3.c never applied the
|
|
* intra-page offset of the start address (a partial read returned bytes
|
|
* from the head of the page instead of the requested column), copied
|
|
* sub-page reads in 32-bit words (dropping a sub-word tail), and wrote
|
|
* a full NAND page into the caller's buffer for a multi-page read
|
|
* ending mid-page (overrunning the buffer).
|
|
*
|
|
* The HAL cannot be built on the host, so the Makefile extracts
|
|
* ext_flash_read() verbatim along with the nand_flash geometry struct;
|
|
* the test provides host div_u()/mod() (the HAL's software-division
|
|
* wrappers exist only because the Cortex-A5 has no divider) and
|
|
* emulated nand_read_page()/nand_check_bad_block() backed by a
|
|
* deterministic byte array.
|
|
* 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>
|
|
|
|
/* sama5d3.h constants the extracted function needs. */
|
|
#define NAND_FLASH_PAGE_SIZE 0x800 /* 2KB */
|
|
#define NAND_FLASH_OOB_SIZE 0x40 /* 64B */
|
|
#define MAX_ECC_BYTES 8
|
|
#define wolfBoot_printf(...) do {} while (0)
|
|
|
|
/* Emulated NAND: 8 blocks x 16 pages x 2KB = 256KB. */
|
|
#define EMU_BLOCKS 8
|
|
#define EMU_PAGES_PER_BLK 16
|
|
#define EMU_PAGE_SIZE NAND_FLASH_PAGE_SIZE
|
|
#define EMU_BLOCK_SIZE (EMU_PAGES_PER_BLK * EMU_PAGE_SIZE)
|
|
#define EMU_TOTAL (EMU_BLOCKS * EMU_BLOCK_SIZE)
|
|
static uint8_t emu_nand[EMU_TOTAL];
|
|
|
|
/* Host equivalents of the HAL's software-division wrappers. */
|
|
static uint32_t div_u(uint32_t dividend, uint32_t divisor)
|
|
{
|
|
return dividend / divisor;
|
|
}
|
|
|
|
static uint32_t mod(uint32_t dividend, uint32_t divisor)
|
|
{
|
|
return dividend % divisor;
|
|
}
|
|
|
|
/* Emulated NAND primitives: a full page read from column 0, like the
|
|
* hardware path. Blocks marked in emu_bad[] report a bad-block marker,
|
|
* so the read has to relocate past them. */
|
|
static int emu_bad[EMU_BLOCKS];
|
|
|
|
static int nand_check_bad_block(uint32_t block)
|
|
{
|
|
if (block >= EMU_BLOCKS)
|
|
return 0; /* off the end of the emulated device: treat as good */
|
|
return emu_bad[block] ? -1 : 0;
|
|
}
|
|
|
|
static int nand_read_page(uint32_t block, uint32_t page, uint8_t *data)
|
|
{
|
|
uint32_t row = block * EMU_PAGES_PER_BLK + page;
|
|
|
|
memcpy(data, emu_nand + row * EMU_PAGE_SIZE, EMU_PAGE_SIZE);
|
|
return 0;
|
|
}
|
|
|
|
/* The real ext_flash_read() and nand_flash struct from hal/sama5d3.c
|
|
* (extracted by the Makefile). */
|
|
#include "sama5d3_read_extract.h"
|
|
|
|
/* Read buffer plus an adjacent canary region: a multi-page read ending
|
|
* mid-page used to write a full page past the requested length. */
|
|
#define SCRATCH_LEN (EMU_BLOCK_SIZE * 4)
|
|
static uint8_t scratch[SCRATCH_LEN + 64];
|
|
#define CANARY (scratch + SCRATCH_LEN)
|
|
#define CANARY_LEN 64
|
|
|
|
static void setup(void)
|
|
{
|
|
uint32_t i;
|
|
|
|
/* Deterministic pattern: every byte depends on row and column. */
|
|
for (i = 0; i < EMU_TOTAL; i++)
|
|
emu_nand[i] = (uint8_t)((i / EMU_PAGE_SIZE) * 7 + i);
|
|
|
|
nand_flash.page_size = EMU_PAGE_SIZE;
|
|
nand_flash.block_size = EMU_BLOCK_SIZE;
|
|
nand_flash.block_count = EMU_BLOCKS;
|
|
nand_flash.pages_per_block = EMU_PAGES_PER_BLK;
|
|
nand_flash.pages_per_device = EMU_BLOCKS * EMU_PAGES_PER_BLK;
|
|
nand_flash.total_size = EMU_TOTAL;
|
|
|
|
memset(emu_bad, 0, sizeof(emu_bad));
|
|
}
|
|
|
|
static void teardown(void)
|
|
{
|
|
}
|
|
|
|
static void fill_expected(uint8_t *dst, uint32_t address, uint32_t len)
|
|
{
|
|
uint32_t i;
|
|
|
|
for (i = 0; i < len; i++)
|
|
dst[i] = emu_nand[address + i];
|
|
}
|
|
|
|
/* Expected bytes when bad blocks are present: start at the block the
|
|
* address divides into, then walk forward page by page, skipping every
|
|
* bad block encountered. The skip is cumulative - once a block has been
|
|
* stepped over, the whole remainder of the read is shifted with it. */
|
|
static void fill_expected_skip(uint8_t *dst, uint32_t address, uint32_t len)
|
|
{
|
|
uint32_t block = address / EMU_BLOCK_SIZE;
|
|
uint32_t page_in_block = (address / EMU_PAGE_SIZE) % EMU_PAGES_PER_BLK;
|
|
uint32_t in_page = address % EMU_PAGE_SIZE;
|
|
uint32_t remaining = len;
|
|
int check_block = 1;
|
|
|
|
while (remaining > 0) {
|
|
uint32_t chunk = EMU_PAGE_SIZE - in_page;
|
|
uint32_t row;
|
|
|
|
if (check_block) {
|
|
while (nand_check_bad_block(block) < 0)
|
|
block++;
|
|
check_block = 0;
|
|
}
|
|
if (chunk > remaining)
|
|
chunk = remaining;
|
|
|
|
row = block * EMU_PAGES_PER_BLK + page_in_block;
|
|
memcpy(dst, emu_nand + row * EMU_PAGE_SIZE + in_page, chunk);
|
|
|
|
dst += chunk;
|
|
remaining -= chunk;
|
|
in_page += chunk;
|
|
if (in_page == EMU_PAGE_SIZE) {
|
|
in_page = 0;
|
|
if (++page_in_block == EMU_PAGES_PER_BLK) {
|
|
page_in_block = 0;
|
|
block++;
|
|
check_block = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Run one read and compare against the emulated device byte for byte. */
|
|
static void read_case(uint32_t address, int len)
|
|
{
|
|
static uint8_t expected[SCRATCH_LEN];
|
|
int i;
|
|
int ret;
|
|
|
|
ck_assert_int_lt(len, SCRATCH_LEN);
|
|
memset(scratch, 0xEE, sizeof(scratch));
|
|
fill_expected(expected, address, (uint32_t)len);
|
|
|
|
ret = ext_flash_read(address, scratch, len);
|
|
ck_assert_int_eq(ret, len);
|
|
if (len > 0)
|
|
ck_assert_mem_eq(scratch, expected, (size_t)len);
|
|
|
|
/* The canary must be untouched: nothing may be written past len. */
|
|
for (i = 0; i < CANARY_LEN; i++)
|
|
ck_assert_uint_eq(CANARY[i], 0xEE);
|
|
}
|
|
|
|
/* Same, but against the bad-block relocation model. */
|
|
static void read_case_skip(uint32_t address, int len)
|
|
{
|
|
static uint8_t expected[SCRATCH_LEN];
|
|
int i;
|
|
int ret;
|
|
|
|
ck_assert_int_lt(len, SCRATCH_LEN);
|
|
memset(scratch, 0xEE, sizeof(scratch));
|
|
fill_expected_skip(expected, address, (uint32_t)len);
|
|
|
|
ret = ext_flash_read(address, scratch, len);
|
|
ck_assert_int_eq(ret, len);
|
|
ck_assert_mem_eq(scratch, expected, (size_t)len);
|
|
|
|
for (i = 0; i < CANARY_LEN; i++)
|
|
ck_assert_uint_eq(CANARY[i], 0xEE);
|
|
}
|
|
|
|
START_TEST(test_read_zero_length)
|
|
{
|
|
memset(scratch, 0xEE, sizeof(scratch));
|
|
ck_assert_int_eq(ext_flash_read(0x4000, scratch, 0), 0);
|
|
ck_assert_uint_eq(scratch[0], 0xEE);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_read_aligned_full_page)
|
|
{
|
|
read_case(0, EMU_PAGE_SIZE);
|
|
read_case(0x10000, EMU_PAGE_SIZE);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_read_unaligned_small)
|
|
{
|
|
/* Mid-page starts: the head of the page must not be returned. */
|
|
read_case(1, 4);
|
|
read_case(0x7FC, 8);
|
|
read_case(0x400, 64);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_read_subword_lengths)
|
|
{
|
|
/* 1-3 byte reads copy nothing in a word-count loop. */
|
|
read_case(0x800, 1);
|
|
read_case(0x801, 2);
|
|
read_case(0x1800, 3);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_read_sha_block_pattern)
|
|
{
|
|
/* The integrity check hashes the image in 64-byte blocks from
|
|
* fw_base + offset: every block after the first in each page is
|
|
* an unaligned small read. */
|
|
uint32_t offset;
|
|
|
|
for (offset = 0; offset < EMU_PAGE_SIZE; offset += 0x40)
|
|
read_case(0x800 + offset, 64);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_read_page_boundaries)
|
|
{
|
|
read_case(0, EMU_PAGE_SIZE - 1);
|
|
read_case(0, EMU_PAGE_SIZE + 1);
|
|
/* Start near the end of a page and cross into the next. */
|
|
read_case(0x7F0, 0x20);
|
|
read_case(0x7FF, 0x101);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_read_multipage_partial_tail)
|
|
{
|
|
/* Multi-page reads whose tail is shorter than a page (and not a
|
|
* multiple of 4): the tail page must not be written in full. */
|
|
read_case(0x100, 0x903);
|
|
read_case(0, 0x1805);
|
|
read_case(0x40, 0x1F01);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_read_cross_block)
|
|
{
|
|
read_case(0x7F00, 0x120);
|
|
read_case(0x7000, 0x200);
|
|
read_case(0x7001, 0x1000);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_read_bad_block_skip_is_cumulative)
|
|
{
|
|
/* Block 1 is bad: logical block 1 must come from physical block 2,
|
|
* and - the part that regressed - so must every block after it in
|
|
* the same read. Recomputing the physical block from the current
|
|
* address on each page aliases logical block 2 back onto physical
|
|
* block 2, which is logical block 1's data. */
|
|
emu_bad[1] = 1;
|
|
|
|
read_case_skip(0, EMU_BLOCK_SIZE); /* before the skip */
|
|
read_case_skip(0, 2 * EMU_BLOCK_SIZE); /* across the skip */
|
|
read_case_skip(0, 3 * EMU_BLOCK_SIZE); /* two blocks past it */
|
|
read_case_skip(EMU_BLOCK_SIZE - 0x40, 0x80); /* straddling it */
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_read_bad_block_consecutive)
|
|
{
|
|
/* Two bad blocks in a row shift the remainder by two, not by one. */
|
|
emu_bad[1] = 1;
|
|
emu_bad[2] = 1;
|
|
|
|
read_case_skip(0, 3 * EMU_BLOCK_SIZE);
|
|
read_case_skip(EMU_PAGE_SIZE, 2 * EMU_BLOCK_SIZE);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_read_bad_block_partial_pages)
|
|
{
|
|
/* The column offset and the sub-page tail still have to be right on
|
|
* the far side of a relocation. */
|
|
emu_bad[1] = 1;
|
|
|
|
read_case_skip(EMU_BLOCK_SIZE + 0x123, 64);
|
|
read_case_skip(EMU_BLOCK_SIZE - 1, 3);
|
|
read_case_skip(0x40, EMU_BLOCK_SIZE + 0x905);
|
|
}
|
|
END_TEST
|
|
|
|
Suite *sama5d3_ext_read_suite(void)
|
|
{
|
|
Suite *s = suite_create("sama5d3-ext-read");
|
|
TCase *tc = tcase_create("sama5d3-ext-read");
|
|
|
|
tcase_add_checked_fixture(tc, setup, teardown);
|
|
tcase_add_test(tc, test_read_zero_length);
|
|
tcase_add_test(tc, test_read_aligned_full_page);
|
|
tcase_add_test(tc, test_read_unaligned_small);
|
|
tcase_add_test(tc, test_read_subword_lengths);
|
|
tcase_add_test(tc, test_read_sha_block_pattern);
|
|
tcase_add_test(tc, test_read_page_boundaries);
|
|
tcase_add_test(tc, test_read_multipage_partial_tail);
|
|
tcase_add_test(tc, test_read_cross_block);
|
|
tcase_add_test(tc, test_read_bad_block_skip_is_cumulative);
|
|
tcase_add_test(tc, test_read_bad_block_consecutive);
|
|
tcase_add_test(tc, test_read_bad_block_partial_pages);
|
|
suite_add_tcase(s, tc);
|
|
|
|
return s;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
int fails;
|
|
Suite *s = sama5d3_ext_read_suite();
|
|
SRunner *sr = srunner_create(s);
|
|
|
|
srunner_run_all(sr, CK_NORMAL);
|
|
fails = srunner_ntests_failed(sr);
|
|
srunner_free(sr);
|
|
|
|
return fails;
|
|
}
|