mirror of https://github.com/wolfSSL/wolfBoot.git
F-11033: propagate program/erase timeouts from the T10xx flash HAL
hal_flash_status_wait() returns -1 when the NOR does not settle its status bits within the poll budget, but hal_flash_write() and hal_flash_erase() discarded the result and returned 0 unconditionally: a stuck program or erase reported success, and the update flow continued as if the flash held the new image. Capture every wait result and return it to the caller on the first failure. No state-restore command is needed: this driver works through the memory-mapped QPI window, where reads are plain loads and the controller issues the read command per access, so there is no device command state to recover. unit-t10xx-flash-status runs the real extracted functions against a mock QPI status model (offset 0 reports the DQ status byte: toggling while busy, 0x44 after a program, 0x4C after an erase). A stuck device burns the full 200 ms / 1.1 s poll budget with a no-op udelay, so the timeout path runs in milliseconds. Verification: - Built: powerpc-linux-gnu-gcc -fsyntax-only -Wall with TARGET_nxp_t1024: clean. - Tested: unit-t10xx-flash-status 4/4; pre-fix both timeout tests got ret == 0 from a stuck device, now -1. Success paths (write lands in the model, erase completes) unchanged. - Pitfalls: the first failing page/sector now aborts the rest of the operation, which is the desired behavior (the caller aborts the update); no callers depended on the unconditional 0. - Style: cstyle-check.sh flag count on hal/nxp_t10xx.c unchanged from the pre-change file; the new test trips only the uncrustify pointer-alignment class the sibling unit tests trip. - Message: F-11033: prefix, no co-author trailers. - Unverified: no T10xx board execution.pull/870/head
parent
f573e97e76
commit
1ae15e3abb
|
|
@ -3249,6 +3249,7 @@ static int hal_flash_status_wait(uint32_t sector, uint16_t mask, uint32_t timeou
|
||||||
int hal_flash_write(uint32_t address, const uint8_t *data, int len)
|
int hal_flash_write(uint32_t address, const uint8_t *data, int len)
|
||||||
{
|
{
|
||||||
uint32_t i, pos, sector, offset, xfer, nwords;
|
uint32_t i, pos, sector, offset, xfer, nwords;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
/* adjust for flash base */
|
/* adjust for flash base */
|
||||||
if (address >= FLASH_BASE_ADDR)
|
if (address >= FLASH_BASE_ADDR)
|
||||||
|
|
@ -3288,7 +3289,9 @@ int hal_flash_write(uint32_t address, const uint8_t *data, int len)
|
||||||
FLASH_IO16_WRITE(sector, offset, 0);
|
FLASH_IO16_WRITE(sector, offset, 0);
|
||||||
FLASH_IO16_WRITE(sector, offset, word);
|
FLASH_IO16_WRITE(sector, offset, word);
|
||||||
FLASH_IO8_WRITE(sector, offset, AMD_CMD_WRITE_BUFFER_CONFIRM);
|
FLASH_IO8_WRITE(sector, offset, AMD_CMD_WRITE_BUFFER_CONFIRM);
|
||||||
hal_flash_status_wait(sector, 0x44, 200*1000);
|
ret = hal_flash_status_wait(sector, 0x44, 200*1000);
|
||||||
|
if (ret != 0)
|
||||||
|
return ret;
|
||||||
address++;
|
address++;
|
||||||
pos++;
|
pos++;
|
||||||
len--;
|
len--;
|
||||||
|
|
@ -3317,7 +3320,9 @@ int hal_flash_write(uint32_t address, const uint8_t *data, int len)
|
||||||
/* Typical 410us */
|
/* Typical 410us */
|
||||||
|
|
||||||
/* poll for program completion - max 200ms */
|
/* poll for program completion - max 200ms */
|
||||||
hal_flash_status_wait(sector, 0x44, 200*1000);
|
ret = hal_flash_status_wait(sector, 0x44, 200*1000);
|
||||||
|
if (ret != 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
address += xfer;
|
address += xfer;
|
||||||
len -= xfer;
|
len -= xfer;
|
||||||
|
|
@ -3328,6 +3333,7 @@ int hal_flash_write(uint32_t address, const uint8_t *data, int len)
|
||||||
int hal_flash_erase(uint32_t address, int len)
|
int hal_flash_erase(uint32_t address, int len)
|
||||||
{
|
{
|
||||||
uint32_t sector;
|
uint32_t sector;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
/* adjust for flash base */
|
/* adjust for flash base */
|
||||||
if (address >= FLASH_BASE_ADDR)
|
if (address >= FLASH_BASE_ADDR)
|
||||||
|
|
@ -3350,7 +3356,9 @@ int hal_flash_erase(uint32_t address, int len)
|
||||||
/* Typical is 200ms (max 1100ms) */
|
/* Typical is 200ms (max 1100ms) */
|
||||||
|
|
||||||
/* poll for erase completion - max 1.1 sec */
|
/* poll for erase completion - max 1.1 sec */
|
||||||
hal_flash_status_wait(sector, 0x4C, 1100*1000);
|
ret = hal_flash_status_wait(sector, 0x4C, 1100*1000);
|
||||||
|
if (ret != 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
address += FLASH_SECTOR_SIZE;
|
address += FLASH_SECTOR_SIZE;
|
||||||
len -= FLASH_SECTOR_SIZE;
|
len -= FLASH_SECTOR_SIZE;
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,7 @@ TESTS+=unit-zynq-ext-write
|
||||||
TESTS+=unit-versal-qspi-dma
|
TESTS+=unit-versal-qspi-dma
|
||||||
TESTS+=unit-versal-ext-write
|
TESTS+=unit-versal-ext-write
|
||||||
TESTS+=unit-t10xx-qe-firmware
|
TESTS+=unit-t10xx-qe-firmware
|
||||||
|
TESTS+=unit-t10xx-flash-status
|
||||||
TESTS+=unit-aurix-erased-fill
|
TESTS+=unit-aurix-erased-fill
|
||||||
TESTS+=unit-aurix-erased-fill-invert
|
TESTS+=unit-aurix-erased-fill-invert
|
||||||
TESTS+=unit-t2080-fman-loader
|
TESTS+=unit-t2080-fman-loader
|
||||||
|
|
@ -1005,6 +1006,19 @@ stm32g4_write_extract.h: ../../hal/stm32g4.c
|
||||||
unit-stm32g4-write: unit-stm32g4-write.c stm32g4_write_extract.h
|
unit-stm32g4-write: unit-stm32g4-write.c stm32g4_write_extract.h
|
||||||
gcc -o $@ unit-stm32g4-write.c $(CFLAGS) $(LDFLAGS)
|
gcc -o $@ unit-stm32g4-write.c $(CFLAGS) $(LDFLAGS)
|
||||||
|
|
||||||
|
# unit-t10xx-flash-status runs the real hal_flash_write()/hal_flash_erase()
|
||||||
|
# and hal_flash_status_wait() from hal/nxp_t10xx.c against a mock QPI
|
||||||
|
# status model (F-11033: a timed-out program/erase used to report
|
||||||
|
# success because the wait result was discarded).
|
||||||
|
t10xx_flash_status_extract.h: ../../hal/nxp_t10xx.c
|
||||||
|
sed -n '/^static void hal_flash_unlock_sector/,/^}/p' $< > $@
|
||||||
|
sed -n '/^static int hal_flash_status_wait/,/^}/p' $< >> $@
|
||||||
|
sed -n '/^int hal_flash_write/,/^}/p' $< >> $@
|
||||||
|
sed -n '/^int hal_flash_erase/,/^}/p' $< >> $@
|
||||||
|
|
||||||
|
unit-t10xx-flash-status: unit-t10xx-flash-status.c t10xx_flash_status_extract.h
|
||||||
|
gcc -o $@ unit-t10xx-flash-status.c $(CFLAGS) $(LDFLAGS)
|
||||||
|
|
||||||
# unit-ecc-raw-der runs the real wolfCrypt raw-to-DER conversion and
|
# unit-ecc-raw-der runs the real wolfCrypt raw-to-DER conversion and
|
||||||
# verification (F-11024: the wolfHSM verify path in src/image.c passed
|
# verification (F-11024: the wolfHSM verify path in src/image.c passed
|
||||||
# minimal field sizes with field-start pointers to
|
# minimal field sizes with field-start pointers to
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,251 @@
|
||||||
|
/* unit-t10xx-flash-status.c
|
||||||
|
*
|
||||||
|
* Regression test for F-11033: hal_flash_write() and hal_flash_erase()
|
||||||
|
* in hal/nxp_t10xx.c discarded the return value of
|
||||||
|
* hal_flash_status_wait(), so a program or erase that timed out (the
|
||||||
|
* NOR stuck mid-operation) still reported success to the caller.
|
||||||
|
*
|
||||||
|
* The real functions are extracted by the Makefile and run against a
|
||||||
|
* mock QPI status model: offset 0 of a sector reports the DQ status
|
||||||
|
* byte (toggling while busy, 0x44 after a program, 0x4C after an
|
||||||
|
* erase). udelay() is a no-op, so a stuck device burns the full poll
|
||||||
|
* budget (200 ms / 1.1 s worth of iterations) in milliseconds.
|
||||||
|
*
|
||||||
|
* 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>
|
||||||
|
|
||||||
|
/* 16-bit QPI configuration, as used by the T10xx targets. */
|
||||||
|
#define FLASH_BASE_ADDR 0
|
||||||
|
#define FLASH_PAGE_SIZE 1024
|
||||||
|
#define FLASH_SECTOR_SIZE (128 * 1024)
|
||||||
|
#define FLASH_CFI_WIDTH 16
|
||||||
|
|
||||||
|
#define AMD_CMD_ERASE_START 0x80
|
||||||
|
#define AMD_CMD_ERASE_SECTOR 0x30
|
||||||
|
#define AMD_CMD_UNLOCK_START 0xAA
|
||||||
|
#define AMD_CMD_UNLOCK_ACK 0x55
|
||||||
|
#define AMD_CMD_WRITE_TO_BUFFER 0x25
|
||||||
|
#define AMD_CMD_WRITE_BUFFER_CONFIRM 0x29
|
||||||
|
#define AMD_STATUS_TOGGLE 0x40
|
||||||
|
|
||||||
|
#define FLASH_UNLOCK_ADDR1 0x555
|
||||||
|
#define FLASH_UNLOCK_ADDR2 0x2AA
|
||||||
|
|
||||||
|
void udelay(uint32_t us);
|
||||||
|
|
||||||
|
/* ---- mock QPI status model ------------------------------------------- */
|
||||||
|
|
||||||
|
typedef enum t10xx_mock_state {
|
||||||
|
ST_IDLE = 0,
|
||||||
|
ST_PROGRAMMING,
|
||||||
|
ST_ERASING,
|
||||||
|
ST_PROG_DONE,
|
||||||
|
ST_ERASE_DONE,
|
||||||
|
} t10xx_mock_state;
|
||||||
|
|
||||||
|
#define MOCK_SECTOR_BYTES 64
|
||||||
|
#define NSECTORS 4
|
||||||
|
|
||||||
|
static uint8_t g_flash[NSECTORS * MOCK_SECTOR_BYTES];
|
||||||
|
static int g_state[NSECTORS];
|
||||||
|
static int g_stuck[NSECTORS];
|
||||||
|
static int g_unlocks;
|
||||||
|
|
||||||
|
static void mock_reset(int stuck)
|
||||||
|
{
|
||||||
|
int s;
|
||||||
|
|
||||||
|
memset(g_flash, 0, sizeof(g_flash));
|
||||||
|
for (s = 0; s < NSECTORS; s++) {
|
||||||
|
g_state[s] = ST_IDLE;
|
||||||
|
g_stuck[s] = stuck;
|
||||||
|
}
|
||||||
|
g_unlocks = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The real macros dereference the memory-mapped flash window; the test
|
||||||
|
* routes them through the model so command writes drive the state.
|
||||||
|
* 16-bit variant: every index is a word index (byte offset = 2 * n),
|
||||||
|
* and the 8-bit write replicates the byte across the 16-bit word. */
|
||||||
|
static uint8_t mock_read8(uint32_t sector, uint32_t n)
|
||||||
|
{
|
||||||
|
uint32_t byte_off = 2 * n;
|
||||||
|
|
||||||
|
if (n == 0 &&
|
||||||
|
((g_state[sector] == ST_PROGRAMMING) ||
|
||||||
|
(g_state[sector] == ST_ERASING))) {
|
||||||
|
if (!g_stuck[sector]) {
|
||||||
|
/* Complete on the first status poll after the command. */
|
||||||
|
g_state[sector] = (g_state[sector] == ST_PROGRAMMING) ?
|
||||||
|
ST_PROG_DONE : ST_ERASE_DONE;
|
||||||
|
return (g_state[sector] == ST_PROG_DONE) ? 0x44 : 0x4C;
|
||||||
|
}
|
||||||
|
return AMD_STATUS_TOGGLE; /* busy, never completes */
|
||||||
|
}
|
||||||
|
if (n == 0) {
|
||||||
|
return 0x4C; /* idle: satisfies program (0x44) and erase (0x4C) */
|
||||||
|
}
|
||||||
|
if (byte_off < MOCK_SECTOR_BYTES) {
|
||||||
|
return g_flash[sector * MOCK_SECTOR_BYTES + byte_off];
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16_t mock_read16(uint32_t sector, uint32_t n)
|
||||||
|
{
|
||||||
|
uint32_t byte_off = 2 * n;
|
||||||
|
|
||||||
|
return (uint16_t)(g_flash[sector * MOCK_SECTOR_BYTES + byte_off] |
|
||||||
|
((uint16_t)g_flash[sector * MOCK_SECTOR_BYTES +
|
||||||
|
byte_off + 1] << 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mock_write8(uint32_t sector, uint32_t n, uint8_t val)
|
||||||
|
{
|
||||||
|
uint32_t byte_off = 2 * n;
|
||||||
|
|
||||||
|
if (n == FLASH_UNLOCK_ADDR1 && val == AMD_CMD_UNLOCK_START) {
|
||||||
|
g_unlocks++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (n == FLASH_UNLOCK_ADDR2 && val == AMD_CMD_UNLOCK_ACK) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (n == 0 && val == AMD_CMD_ERASE_SECTOR) {
|
||||||
|
g_state[sector] = ST_ERASING;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (val == AMD_CMD_WRITE_BUFFER_CONFIRM) {
|
||||||
|
g_state[sector] = ST_PROGRAMMING;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (byte_off < MOCK_SECTOR_BYTES) {
|
||||||
|
g_flash[sector * MOCK_SECTOR_BYTES + byte_off] = val;
|
||||||
|
g_flash[sector * MOCK_SECTOR_BYTES + byte_off + 1] = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mock_write16(uint32_t sector, uint32_t n, uint16_t val)
|
||||||
|
{
|
||||||
|
uint32_t byte_off = 2 * n;
|
||||||
|
|
||||||
|
if (byte_off + 1 < MOCK_SECTOR_BYTES) {
|
||||||
|
g_flash[sector * MOCK_SECTOR_BYTES + byte_off] = (uint8_t)(val & 0xFF);
|
||||||
|
g_flash[sector * MOCK_SECTOR_BYTES + byte_off + 1] =
|
||||||
|
(uint8_t)(val >> 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define FLASH_IO8_READ(sec, n) mock_read8((sec), (n))
|
||||||
|
#define FLASH_IO8_WRITE(sec, n, val) mock_write8((sec), (n), (val))
|
||||||
|
#define FLASH_IO16_READ(sec, n) mock_read16((sec), (n))
|
||||||
|
#define FLASH_IO16_WRITE(sec, n, val) mock_write16((sec), (n), (val))
|
||||||
|
|
||||||
|
/* The real functions from hal/nxp_t10xx.c (extracted by the Makefile). */
|
||||||
|
static void hal_flash_unlock_sector(uint32_t sector);
|
||||||
|
#include "t10xx_flash_status_extract.h"
|
||||||
|
|
||||||
|
void udelay(uint32_t us)
|
||||||
|
{
|
||||||
|
(void)us; /* no real delay: a stuck device burns the poll budget fast */
|
||||||
|
}
|
||||||
|
|
||||||
|
START_TEST (test_flash_write_success)
|
||||||
|
{
|
||||||
|
uint8_t data[8];
|
||||||
|
int i;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
mock_reset(0);
|
||||||
|
for (i = 0; i < 8; i++)
|
||||||
|
data[i] = (uint8_t)(0xA0 + i);
|
||||||
|
|
||||||
|
ret = hal_flash_write(0, data, sizeof(data));
|
||||||
|
ck_assert_int_eq(ret, 0);
|
||||||
|
ck_assert_int_gt(g_unlocks, 0);
|
||||||
|
ck_assert_mem_eq(g_flash, data, 8);
|
||||||
|
}
|
||||||
|
END_TEST
|
||||||
|
|
||||||
|
START_TEST (test_flash_write_timeout_returns_error)
|
||||||
|
{
|
||||||
|
uint8_t data[8] = { 0 };
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* Device stuck mid-program: the status never settles. */
|
||||||
|
mock_reset(1);
|
||||||
|
|
||||||
|
ret = hal_flash_write(0, data, sizeof(data));
|
||||||
|
ck_assert_int_eq(ret, -1);
|
||||||
|
}
|
||||||
|
END_TEST
|
||||||
|
|
||||||
|
START_TEST (test_flash_erase_success)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
mock_reset(0);
|
||||||
|
|
||||||
|
ret = hal_flash_erase(0, FLASH_SECTOR_SIZE);
|
||||||
|
ck_assert_int_eq(ret, 0);
|
||||||
|
ck_assert_int_gt(g_unlocks, 0);
|
||||||
|
}
|
||||||
|
END_TEST
|
||||||
|
|
||||||
|
START_TEST (test_flash_erase_timeout_returns_error)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* Device stuck mid-erase: the status never settles. */
|
||||||
|
mock_reset(1);
|
||||||
|
|
||||||
|
ret = hal_flash_erase(0, FLASH_SECTOR_SIZE);
|
||||||
|
ck_assert_int_eq(ret, -1);
|
||||||
|
}
|
||||||
|
END_TEST
|
||||||
|
|
||||||
|
Suite *t10xx_flash_status_suite(void)
|
||||||
|
{
|
||||||
|
Suite *s = suite_create("t10xx flash status");
|
||||||
|
TCase *tc = tcase_create("flash-op-timeout");
|
||||||
|
|
||||||
|
tcase_add_test(tc, test_flash_write_success);
|
||||||
|
tcase_add_test(tc, test_flash_write_timeout_returns_error);
|
||||||
|
tcase_add_test(tc, test_flash_erase_success);
|
||||||
|
tcase_add_test(tc, test_flash_erase_timeout_returns_error);
|
||||||
|
tcase_set_timeout(tc, 30);
|
||||||
|
suite_add_tcase(s, tc);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
int fails;
|
||||||
|
Suite *s = t10xx_flash_status_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