mirror of https://github.com/wolfSSL/wolfBoot.git
305 lines
8.8 KiB
C
305 lines
8.8 KiB
C
/* unit-zynq-ext-write.c
|
|
*
|
|
* Regression test: ext_flash_write() in hal/zynq.c chunked by length
|
|
* alone, ignoring the start address's offset inside the device page. A
|
|
* write starting mid-page sent a full-page Page Program across the
|
|
* boundary, and NOR wraps the write pointer, so the excess clobbered
|
|
* the start of the page.
|
|
*
|
|
* As with unit-zynq-erase-loop the HAL cannot be built on the host, so
|
|
* the Makefile extracts ext_flash_write() and runs it against emulated
|
|
* qspi_* calls. The emulated NOR models the wrap, so pre-fix the
|
|
* corruption is visible in the flash image.
|
|
* 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 extracted function is a RAMFUNCTION; on the host that is nothing. */
|
|
#define RAMFUNCTION
|
|
|
|
/* Xilinx / board constants the function uses (board build tree values).
|
|
* QSPI NOR page program size is 256. */
|
|
#define GQSPI_CODE_SUCCESS 0
|
|
#define GQSPI_GEN_FIFO_MODE_SPI 0
|
|
#define PAGE_PROG_CMD 0x02U
|
|
#define FLASH_PAGE_SIZE 256
|
|
#define GQPI_USE_4BYTE_ADDR 0
|
|
#define wolfBoot_printf(...) do {} while (0)
|
|
|
|
/* The driver keeps its device state in a static mDev; the only member
|
|
* the write loop reads is stripe (dual-parallel address scaling). */
|
|
typedef struct {
|
|
int stripe;
|
|
} QspiDev_t;
|
|
static QspiDev_t mDev;
|
|
|
|
/* Emulated QSPI device: records every operation and programs an
|
|
* emulated NOR that wraps the write pointer at page boundaries, like
|
|
* real NOR flash. */
|
|
#define EMU_MAX_OPS 64
|
|
enum {
|
|
EMU_WEN,
|
|
EMU_XFER,
|
|
EMU_WAIT,
|
|
EMU_WDIS,
|
|
};
|
|
struct emu_op {
|
|
int kind;
|
|
uint8_t cmd[16];
|
|
uint32_t cmdsz;
|
|
uint32_t txsz;
|
|
};
|
|
static struct emu_op g_ops[EMU_MAX_OPS];
|
|
static int g_ops_n;
|
|
/* Force the operation at this index to fail (one-shot, -1 = none). */
|
|
static int g_fail_at_op = -1;
|
|
|
|
#define NOR_SIZE (4 * 1024)
|
|
static uint8_t g_nor[NOR_SIZE];
|
|
|
|
static int emu_ret(void)
|
|
{
|
|
if (g_fail_at_op >= 0 && (g_ops_n - 1) == g_fail_at_op) {
|
|
g_fail_at_op = -1;
|
|
return -1;
|
|
}
|
|
return GQSPI_CODE_SUCCESS;
|
|
}
|
|
|
|
static int qspi_write_enable(QspiDev_t *dev)
|
|
{
|
|
(void)dev;
|
|
g_ops[g_ops_n].kind = EMU_WEN;
|
|
g_ops_n++;
|
|
return emu_ret();
|
|
}
|
|
|
|
static int qspi_transfer(QspiDev_t *pDev,
|
|
const uint8_t *cmdData, uint32_t cmdSz,
|
|
const uint8_t *txData, uint32_t txSz,
|
|
uint8_t *rxData, uint32_t rxSz, uint32_t dummySz,
|
|
uint32_t mode)
|
|
{
|
|
(void)pDev; (void)rxData; (void)rxSz; (void)dummySz; (void)mode;
|
|
|
|
if (g_ops_n < EMU_MAX_OPS) {
|
|
uint32_t n = cmdSz < sizeof(g_ops[0].cmd) ? cmdSz
|
|
: sizeof(g_ops[0].cmd);
|
|
|
|
g_ops[g_ops_n].kind = EMU_XFER;
|
|
memcpy(g_ops[g_ops_n].cmd, cmdData, n);
|
|
g_ops[g_ops_n].cmdsz = cmdSz;
|
|
g_ops[g_ops_n].txsz = txSz;
|
|
|
|
/* Model the Page Program: the write pointer starts at the
|
|
* command address and wraps at the physical page boundary,
|
|
* clobbering the page start for a crossing program. */
|
|
if (cmdSz >= 4 && cmdData[0] == PAGE_PROG_CMD) {
|
|
uint32_t addr = ((uint32_t)cmdData[1] << 16) |
|
|
((uint32_t)cmdData[2] << 8) |
|
|
(uint32_t)cmdData[3];
|
|
uint32_t base = addr & ~(uint32_t)(FLASH_PAGE_SIZE - 1);
|
|
uint32_t i;
|
|
|
|
for (i = 0; i < txSz; i++)
|
|
g_nor[base + ((addr + i) & (FLASH_PAGE_SIZE - 1))] =
|
|
txData[i];
|
|
}
|
|
}
|
|
g_ops_n++;
|
|
return emu_ret();
|
|
}
|
|
|
|
static int qspi_wait_ready(QspiDev_t *dev)
|
|
{
|
|
(void)dev;
|
|
g_ops[g_ops_n].kind = EMU_WAIT;
|
|
g_ops_n++;
|
|
return emu_ret();
|
|
}
|
|
|
|
static void qspi_write_disable(QspiDev_t *dev)
|
|
{
|
|
(void)dev;
|
|
if (g_ops_n < EMU_MAX_OPS)
|
|
g_ops[g_ops_n].kind = EMU_WDIS;
|
|
g_ops_n++;
|
|
}
|
|
|
|
/* The real ext_flash_write() from hal/zynq.c (extracted by the
|
|
* Makefile). */
|
|
#include "zynq_write_extract.h"
|
|
|
|
static void setup(void)
|
|
{
|
|
memset(g_ops, 0, sizeof(g_ops));
|
|
g_ops_n = 0;
|
|
g_fail_at_op = -1;
|
|
mDev.stripe = 0;
|
|
memset(g_nor, 0xFF, sizeof(g_nor));
|
|
}
|
|
|
|
static void teardown(void)
|
|
{
|
|
}
|
|
|
|
static void fill(uint8_t *buf, size_t len, uint8_t base)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < len; i++)
|
|
buf[i] = (uint8_t)(base + i);
|
|
}
|
|
|
|
/* Page-aligned multi-page write: three well-formed Page Programs,
|
|
* data lands, nothing else is touched. */
|
|
START_TEST(test_write_page_aligned)
|
|
{
|
|
uint8_t data[600];
|
|
uint32_t xfers = 0;
|
|
int i;
|
|
|
|
fill(data, sizeof(data), 0x10);
|
|
|
|
ck_assert_int_eq(ext_flash_write(0, data, 600), GQSPI_CODE_SUCCESS);
|
|
|
|
for (i = 0; i < g_ops_n; i++) {
|
|
if (g_ops[i].kind == EMU_XFER) {
|
|
xfers++;
|
|
ck_assert_uint_eq(g_ops[i].cmd[0], PAGE_PROG_CMD);
|
|
}
|
|
}
|
|
ck_assert_uint_eq(xfers, 3);
|
|
ck_assert_uint_eq(g_nor[599], data[599]);
|
|
for (i = 0; i < 600; i++)
|
|
ck_assert_uint_eq(g_nor[i], data[i]);
|
|
for (i = 600; i < NOR_SIZE; i++)
|
|
ck_assert_uint_eq(g_nor[i], 0xFF);
|
|
}
|
|
END_TEST
|
|
|
|
/* A write that crosses page boundaries must not clobber the starts
|
|
* of the pages it wraps into. Pre-fix, the 256-byte program starting
|
|
* 56 bytes before the boundary wrapped and overwrote g_nor[0..55]. */
|
|
START_TEST(test_write_crossing_page_no_wrap)
|
|
{
|
|
uint8_t data[300];
|
|
uint32_t xfers = 0;
|
|
int i;
|
|
|
|
/* Pre-seed the whole flash with a pattern. */
|
|
memset(g_nor, 0x5A, sizeof(g_nor));
|
|
fill(data, sizeof(data), 0x60);
|
|
|
|
ck_assert_int_eq(ext_flash_write(200, data, 300), GQSPI_CODE_SUCCESS);
|
|
|
|
/* Every Page Program must fit in its physical page. */
|
|
for (i = 0; i < g_ops_n; i++) {
|
|
if (g_ops[i].kind == EMU_XFER) {
|
|
uint32_t addr = ((uint32_t)g_ops[i].cmd[1] << 16) |
|
|
((uint32_t)g_ops[i].cmd[2] << 8) |
|
|
(uint32_t)g_ops[i].cmd[3];
|
|
|
|
xfers++;
|
|
ck_assert_int_le((int)(addr % FLASH_PAGE_SIZE) +
|
|
(int)g_ops[i].txsz, FLASH_PAGE_SIZE);
|
|
}
|
|
}
|
|
/* Chunks: (200, 56) to the page edge, then the remaining 244
|
|
* bytes in one program at the next page start. */
|
|
ck_assert_uint_eq(xfers, 2);
|
|
ck_assert_uint_eq(g_ops[1].txsz, 56);
|
|
ck_assert_uint_eq(g_ops[5].txsz, 244);
|
|
ck_assert_uint_eq((((uint32_t)g_ops[5].cmd[1] << 16) |
|
|
((uint32_t)g_ops[5].cmd[2] << 8) | (uint32_t)g_ops[5].cmd[3]),
|
|
256);
|
|
|
|
/* The written bytes. */
|
|
for (i = 0; i < 300; i++)
|
|
ck_assert_uint_eq(g_nor[200 + i], data[i]);
|
|
|
|
/* The page starts a wrapping program would clobber. */
|
|
for (i = 0; i < 200; i++)
|
|
ck_assert_uint_eq(g_nor[i], 0x5A);
|
|
for (i = 500; i < NOR_SIZE; i++)
|
|
ck_assert_uint_eq(g_nor[i], 0x5A);
|
|
}
|
|
END_TEST
|
|
|
|
/* A failed write-enable must stop the loop and report the error. */
|
|
START_TEST(test_write_stops_on_write_enable_failure)
|
|
{
|
|
uint8_t data[600];
|
|
|
|
/* ops: WEN(0) XFER(1) WAIT(2) WDIS(3) WEN(4) <- fails here */
|
|
g_fail_at_op = 4;
|
|
|
|
ck_assert_int_eq(ext_flash_write(0, data, 600), -1);
|
|
ck_assert_int_eq(g_ops_n, 5);
|
|
}
|
|
END_TEST
|
|
|
|
/* A failed transfer must stop the loop and report the error. */
|
|
START_TEST(test_write_stops_on_transfer_failure)
|
|
{
|
|
/* ops: WEN(0) XFER(1) <- fails here; the loop breaks without a
|
|
* further operation (the next write re-issues Write Enable). */
|
|
uint8_t data[600];
|
|
|
|
g_fail_at_op = 1;
|
|
|
|
ck_assert_int_eq(ext_flash_write(0, data, 600), -1);
|
|
ck_assert_int_eq(g_ops_n, 2);
|
|
ck_assert_int_eq(g_ops[0].kind, EMU_WEN);
|
|
ck_assert_int_eq(g_ops[1].kind, EMU_XFER);
|
|
}
|
|
END_TEST
|
|
|
|
Suite *zynq_ext_write_suite(void)
|
|
{
|
|
Suite *s = suite_create("zynq-ext-write");
|
|
TCase *tc = tcase_create("zynq-ext-write");
|
|
|
|
tcase_add_checked_fixture(tc, setup, teardown);
|
|
tcase_add_test(tc, test_write_page_aligned);
|
|
tcase_add_test(tc, test_write_crossing_page_no_wrap);
|
|
tcase_add_test(tc, test_write_stops_on_write_enable_failure);
|
|
tcase_add_test(tc, test_write_stops_on_transfer_failure);
|
|
suite_add_tcase(s, tc);
|
|
|
|
return s;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
int fails;
|
|
Suite *s = zynq_ext_write_suite();
|
|
SRunner *sr = srunner_create(s);
|
|
|
|
srunner_run_all(sr, CK_NORMAL);
|
|
fails = srunner_ntests_failed(sr);
|
|
srunner_free(sr);
|
|
|
|
return fails;
|
|
}
|