mirror of https://github.com/wolfSSL/wolfBoot.git
203 lines
6.3 KiB
C
203 lines
6.3 KiB
C
/* unit-hifive1-flash-write.c
|
|
*
|
|
* Regression test for F-11035: hal_flash_write() in hal/hifive1.c
|
|
* selected the page path and sized the partial-page copy from the
|
|
* original total `len` instead of the bytes still remaining. A
|
|
* multi-page write that ends in a partial page therefore took the
|
|
* full-page branch on the last iteration, read past the end of the
|
|
* caller's buffer and programmed a whole page past the requested range.
|
|
*
|
|
* The real function is extracted by the Makefile and run against a
|
|
* mock fespi model: FLASH_BASE points at a flash image buffer,
|
|
* fespi_write_address()/fespi_sw_tx() program into it, and the
|
|
* read-modify-write path reads the current image back through
|
|
* FLASH_BASE, exactly as the hardware would.
|
|
*
|
|
* 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 <string.h>
|
|
|
|
#define RAMFUNCTION
|
|
#define FLASH_PAGE_SIZE 256
|
|
#define FESPI_PAGE_PROGRAM 0x02
|
|
|
|
#define NUM_PAGES 4
|
|
static uint8_t g_flash[NUM_PAGES * FLASH_PAGE_SIZE];
|
|
#define FLASH_BASE ((uintptr_t)g_flash)
|
|
|
|
static uint32_t g_txmark;
|
|
#define FESPI_REG_TXMARK (g_txmark)
|
|
|
|
/* ---- mock fespi model ------------------------------------------------ */
|
|
|
|
static uint32_t g_cur; /* byte offset in g_flash the next sw_tx lands at */
|
|
|
|
static void fespi_write_address(uint32_t address)
|
|
{
|
|
g_cur = address;
|
|
}
|
|
|
|
static void fespi_sw_tx(uint8_t b)
|
|
{
|
|
if (g_cur < sizeof(g_flash)) {
|
|
g_flash[g_cur++] = b;
|
|
}
|
|
}
|
|
|
|
static void fespi_hwmode(void) { }
|
|
static void fespi_swmode(void) { }
|
|
static void fespi_csmode_hold(void){ }
|
|
static void fespi_csmode_auto(void){ }
|
|
static void fespi_wait_txwm(void) { }
|
|
static void fespi_wait_flash_busy(void) { }
|
|
static void fespi_write_enable(void) { }
|
|
|
|
/* The real hal_flash_write() from hal/hifive1.c (extracted). */
|
|
#include "hifive1_flash_write_extract.h"
|
|
|
|
#define ERASED 0xEE
|
|
#define DATA_B 0xA1
|
|
#define CANARY 0x5A
|
|
#define REQ_LEN 356
|
|
#define CANARY_LEN 160
|
|
|
|
static void model_reset(void)
|
|
{
|
|
memset(g_flash, ERASED, sizeof(g_flash));
|
|
g_txmark = 0;
|
|
g_cur = 0;
|
|
}
|
|
|
|
/* The bug: a page-aligned multi-page write whose final page is partial.
|
|
* Pre-fix the last iteration took the full-page branch, read
|
|
* data[len..len+155] (past the buffer) and programmed 256 bytes where
|
|
* only 100 were requested. The tail of the last page must keep the
|
|
* erased pattern, and nothing past the requested bytes may change. */
|
|
START_TEST (test_final_partial_page_not_overread)
|
|
{
|
|
/* One contiguous buffer so the canary is guaranteed to sit right
|
|
* after the requested data: an out-of-range read lands on known
|
|
* bytes instead of unspecified stack layout. */
|
|
uint8_t buf[REQ_LEN + CANARY_LEN];
|
|
uint8_t *data = buf;
|
|
uint8_t *canary = buf + REQ_LEN;
|
|
int i;
|
|
int ret;
|
|
|
|
for (i = 0; i < REQ_LEN; i++)
|
|
data[i] = DATA_B;
|
|
for (i = 0; i < CANARY_LEN; i++)
|
|
canary[i] = CANARY;
|
|
|
|
/* Relative offsets are accepted as-is (address < FLASH_BASE), which
|
|
* keeps the 32-bit address parameter away from 64-bit host pointers. */
|
|
model_reset();
|
|
ret = hal_flash_write(0, data, REQ_LEN);
|
|
ck_assert_int_eq(ret, 0);
|
|
|
|
/* Page 0: full page of data. */
|
|
ck_assert_mem_eq(g_flash, data, FLASH_PAGE_SIZE);
|
|
|
|
/* Page 1: first 100 bytes are data, the remaining 156 must stay
|
|
* erased (a full-page branch would have written data/canary here). */
|
|
ck_assert_mem_eq(g_flash + FLASH_PAGE_SIZE, data + FLASH_PAGE_SIZE,
|
|
100);
|
|
for (i = 100; i < FLASH_PAGE_SIZE; i++) {
|
|
ck_assert_uint_eq(g_flash[FLASH_PAGE_SIZE + i], ERASED);
|
|
}
|
|
|
|
/* Nothing past the request was written in the buffer. */
|
|
for (i = 0; i < CANARY_LEN; i++) {
|
|
ck_assert_uint_eq(canary[i], CANARY);
|
|
}
|
|
}
|
|
END_TEST
|
|
|
|
/* A single unaligned write that stays within one page (the RMW path
|
|
* that already worked): leading and trailing bytes of the page are
|
|
* preserved. */
|
|
START_TEST (test_unaligned_single_page_rmw)
|
|
{
|
|
uint8_t data[64];
|
|
int i;
|
|
int ret;
|
|
|
|
for (i = 0; i < (int)sizeof(data); i++)
|
|
data[i] = (uint8_t)(i + 1);
|
|
|
|
model_reset();
|
|
ret = hal_flash_write(100, data, sizeof(data));
|
|
ck_assert_int_eq(ret, 0);
|
|
|
|
/* Offsets before and after the write keep the erased pattern. */
|
|
for (i = 0; i < 100; i++)
|
|
ck_assert_uint_eq(g_flash[i], ERASED);
|
|
ck_assert_mem_eq(g_flash + 100, data, sizeof(data));
|
|
for (i = 100 + (int)sizeof(data); i < FLASH_PAGE_SIZE; i++)
|
|
ck_assert_uint_eq(g_flash[i], ERASED);
|
|
}
|
|
END_TEST
|
|
|
|
/* A write of exactly one full page is unchanged by the fix. */
|
|
START_TEST (test_exact_full_page)
|
|
{
|
|
uint8_t data[FLASH_PAGE_SIZE];
|
|
int i;
|
|
int ret;
|
|
|
|
for (i = 0; i < (int)sizeof(data); i++)
|
|
data[i] = (uint8_t)(0xC0 ^ (i & 0x1F));
|
|
|
|
model_reset();
|
|
ret = hal_flash_write(0, data, sizeof(data));
|
|
ck_assert_int_eq(ret, 0);
|
|
ck_assert_mem_eq(g_flash, data, FLASH_PAGE_SIZE);
|
|
for (i = 0; i < FLASH_PAGE_SIZE; i++)
|
|
ck_assert_uint_eq(g_flash[FLASH_PAGE_SIZE + i], ERASED);
|
|
}
|
|
END_TEST
|
|
|
|
Suite *hifive1_flash_write_suite(void)
|
|
{
|
|
Suite *s = suite_create("hifive1 flash write");
|
|
TCase *tc = tcase_create("final-partial-page");
|
|
|
|
tcase_add_test(tc, test_final_partial_page_not_overread);
|
|
tcase_add_test(tc, test_unaligned_single_page_rmw);
|
|
tcase_add_test(tc, test_exact_full_page);
|
|
tcase_set_timeout(tc, 10);
|
|
suite_add_tcase(s, tc);
|
|
return s;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
int fails;
|
|
Suite *s = hifive1_flash_write_suite();
|
|
SRunner *sr = srunner_create(s);
|
|
|
|
srunner_run_all(sr, CK_NORMAL);
|
|
fails = srunner_ntests_failed(sr);
|
|
srunner_free(sr);
|
|
return fails;
|
|
}
|