Fix spi_flash_write_page to stop at len or page boundary

+ Added unit tests for spi_flash.c
pull/690/head
Daniele Lacamera 2026-02-23 17:56:27 +01:00
parent 3be63df3f3
commit d9fe0f42df
4 changed files with 416 additions and 6 deletions

24
.gitignore vendored
View File

@ -121,7 +121,6 @@ tools/check_config/check_config
tools/test-expect-version/test-expect-version
tools/test-update-server/server
tools/uart-flash-server/ufserver
tools/unit-tests/unit-parser
tools/bin-assemble/bin-assemble
tools/elf-parser/elf-parser
tools/fdt-parser/fdt-parser
@ -135,6 +134,29 @@ config/*.ld
test-lib
lib-fs
#unit tests
tools/unit-tests/unit-aes128
tools/unit-tests/unit-aes256
tools/unit-tests/unit-chacha20
tools/unit-tests/unit-delta
tools/unit-tests/unit-enc-nvm
tools/unit-tests/unit-enc-nvm-flagshome
tools/unit-tests/unit-extflash
tools/unit-tests/unit-image
tools/unit-tests/unit-libwolfboot-extra
tools/unit-tests/unit-mock-state
tools/unit-tests/unit-nvm
tools/unit-tests/unit-nvm-flagshome
tools/unit-tests/unit-parser
tools/unit-tests/unit-pci
tools/unit-tests/unit-pkcs11_store
tools/unit-tests/unit-sectorflags
tools/unit-tests/unit-spi-flash
tools/unit-tests/unit-update-flash
tools/unit-tests/unit-update-ram
# Elf preprocessing tools
tools/squashelf/**
!tools/squashelf/squashelf.c

View File

@ -131,7 +131,7 @@ static int RAMFUNCTION spi_flash_write_page(uint32_t address, const void *data,
address++;
spi_read();
len--;
} while ((address & (SPI_FLASH_PAGE_SIZE - 1)) != 0);
} while (len > 0 && (address & (SPI_FLASH_PAGE_SIZE - 1)) != 0);
spi_cs_off(SPI_CS_PIO_BASE, SPI_CS_FLASH);
}
wait_busy();

View File

@ -25,10 +25,10 @@ LDFLAGS+=-ftest-coverage
TESTS:=unit-parser unit-extflash unit-aes128 unit-aes256 unit-chacha20 unit-pci \
unit-mock-state unit-sectorflags unit-image unit-nvm unit-nvm-flagshome \
unit-enc-nvm unit-enc-nvm-flagshome unit-delta unit-update-flash \
unit-update-ram unit-pkcs11_store
TESTS:=unit-parser unit-extflash unit-spi-flash unit-aes128 unit-aes256 \
unit-chacha20 unit-pci unit-mock-state unit-sectorflags unit-image \
unit-nvm unit-nvm-flagshome unit-enc-nvm unit-enc-nvm-flagshome \
unit-delta unit-update-flash unit-update-ram unit-pkcs11_store
all: $(TESTS)
@ -91,6 +91,9 @@ unit-parser: ../../include/target.h unit-parser.c
unit-extflash: ../../include/target.h unit-extflash.c
gcc -o $@ $^ $(CFLAGS) $(LDFLAGS)
unit-spi-flash: ../../include/target.h unit-spi-flash.c
gcc -o $@ $^ $(CFLAGS) $(LDFLAGS)
unit-aes128: ../../include/target.h unit-extflash.c
gcc -o $@ $^ $(CFLAGS) $(LDFLAGS)

View File

@ -0,0 +1,385 @@
/* unit-spi-flash.c
*
* Unit tests for spi_flash.c.
*
*
* Copyright (C) 2025 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
*/
#define SPI_FLASH
#include <check.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "spi_flash.c"
enum spi_phase {
SPI_PHASE_IDLE = 0,
SPI_PHASE_CMD,
SPI_PHASE_ADDR,
SPI_PHASE_DATA,
SPI_PHASE_OTHER
};
static int cs_asserted;
static enum spi_phase phase;
static uint8_t current_cmd;
static int addr_bytes;
static uint32_t current_addr;
static uint32_t addr_accum;
static int data_writes;
static int expected_data_writes;
static int chip_erase_count;
static int sector_erase_count;
static uint32_t last_sector_erase_addr;
static int rdsr_reads;
static uint8_t manuf_id;
static uint8_t product_id;
static int read_armed;
static int spi_release_called;
#define MOCK_FLASH_SIZE (4 * 1024)
static uint8_t mock_flash[MOCK_FLASH_SIZE];
static void reset_spi_mock(int expected_len)
{
cs_asserted = 0;
phase = SPI_PHASE_IDLE;
current_cmd = 0;
addr_bytes = 0;
current_addr = 0;
addr_accum = 0;
data_writes = 0;
expected_data_writes = expected_len;
chip_erase_count = 0;
sector_erase_count = 0;
last_sector_erase_addr = 0;
rdsr_reads = 0;
manuf_id = 0xEF;
product_id = 0x00;
read_armed = 0;
spi_release_called = 0;
memset(mock_flash, 0xFF, sizeof(mock_flash));
}
void spi_init(int polarity, int phase_in)
{
(void)polarity;
(void)phase_in;
}
void spi_release(void)
{
spi_release_called++;
}
void spi_cs_on(uint32_t base, int pin)
{
(void)base;
(void)pin;
cs_asserted = 1;
phase = SPI_PHASE_CMD;
current_cmd = 0;
addr_bytes = 0;
addr_accum = 0;
current_addr = 0;
rdsr_reads = 0;
read_armed = 0;
}
void spi_cs_off(uint32_t base, int pin)
{
(void)base;
(void)pin;
cs_asserted = 0;
phase = SPI_PHASE_IDLE;
current_cmd = 0;
addr_bytes = 0;
}
void spi_write(const char byte)
{
ck_assert_msg(cs_asserted, "SPI write without CS asserted");
if (phase == SPI_PHASE_CMD) {
current_cmd = (uint8_t)byte;
if (current_cmd == BYTE_WRITE || current_cmd == BYTE_READ ||
current_cmd == SECTOR_ERASE) {
phase = SPI_PHASE_ADDR;
addr_bytes = 0;
addr_accum = 0;
return;
}
if (current_cmd == CHIP_ERASE) {
chip_erase_count++;
}
phase = SPI_PHASE_OTHER;
return;
}
if (phase == SPI_PHASE_ADDR) {
addr_accum = (addr_accum << 8) | (uint8_t)byte;
addr_bytes++;
if (addr_bytes >= 3) {
current_addr = addr_accum;
if (current_cmd == SECTOR_ERASE) {
sector_erase_count++;
last_sector_erase_addr = current_addr;
phase = SPI_PHASE_OTHER;
return;
}
phase = SPI_PHASE_DATA;
}
return;
}
if (phase == SPI_PHASE_DATA) {
if (current_cmd == BYTE_WRITE) {
ck_assert_msg(data_writes < expected_data_writes,
"SPI write exceeded expected data length");
ck_assert_msg(current_addr < MOCK_FLASH_SIZE,
"SPI write exceeded mock flash");
mock_flash[current_addr++] = (uint8_t)byte;
data_writes++;
} else if (current_cmd == BYTE_READ) {
read_armed = 1;
}
return;
}
}
uint8_t spi_read(void)
{
if (!cs_asserted) {
return 0;
}
if (phase == SPI_PHASE_OTHER && current_cmd == MDID) {
rdsr_reads++;
if (rdsr_reads == 1) {
return 0;
}
if (rdsr_reads == 2) {
return manuf_id;
}
return product_id;
}
if (phase == SPI_PHASE_OTHER && current_cmd == RDSR) {
rdsr_reads++;
if (rdsr_reads == 1) {
return 0;
}
return ST_WEL;
}
if (phase == SPI_PHASE_DATA && current_cmd == BYTE_READ && read_armed) {
read_armed = 0;
ck_assert_msg(current_addr < MOCK_FLASH_SIZE,
"SPI read exceeded mock flash");
return mock_flash[current_addr++];
}
return 0;
}
START_TEST(test_write_page_midpage_short_len)
{
uint8_t buf[10];
uint32_t address = 0x80;
int ret;
memset(buf, 0xA5, sizeof(buf));
reset_spi_mock(sizeof(buf));
ret = spi_flash_write(address, buf, sizeof(buf));
ck_assert_int_eq(ret, 0);
ck_assert_int_eq(data_writes, (int)sizeof(buf));
}
END_TEST
START_TEST(test_write_page_cross_page)
{
uint8_t buf[32];
uint32_t address = SPI_FLASH_PAGE_SIZE - 16;
int ret;
memset(buf, 0x3C, sizeof(buf));
reset_spi_mock(sizeof(buf));
ret = spi_flash_write(address, buf, sizeof(buf));
ck_assert_int_eq(ret, 0);
ck_assert_int_eq(data_writes, (int)sizeof(buf));
ck_assert_int_eq(0, memcmp(&mock_flash[address], buf, sizeof(buf)));
}
END_TEST
START_TEST(test_write_page_exact_to_boundary)
{
uint8_t buf[128];
uint32_t address = 0x80;
int ret;
memset(buf, 0x5A, sizeof(buf));
reset_spi_mock(sizeof(buf));
ret = spi_flash_write(address, buf, sizeof(buf));
ck_assert_int_eq(ret, 0);
ck_assert_int_eq(data_writes, (int)sizeof(buf));
ck_assert_int_eq(0, memcmp(&mock_flash[address], buf, sizeof(buf)));
}
END_TEST
START_TEST(test_write_len_zero_returns_error)
{
uint8_t buf[4];
int ret;
memset(buf, 0x00, sizeof(buf));
reset_spi_mock(0);
ret = spi_flash_write(0x00, buf, 0);
ck_assert_int_eq(ret, -1);
ck_assert_int_eq(data_writes, 0);
}
END_TEST
START_TEST(test_write_singlebyte_mode)
{
uint8_t buf[3] = { 0x11, 0x22, 0x33 };
uint32_t address = 0x10;
int ret;
reset_spi_mock(sizeof(buf));
manuf_id = 0xBF;
product_id = 0x01;
spi_flash_probe();
ret = spi_flash_write(address, buf, sizeof(buf));
ck_assert_int_eq(ret, 0);
ck_assert_int_eq(0, memcmp(&mock_flash[address], buf, sizeof(buf)));
}
END_TEST
START_TEST(test_read_basic)
{
uint8_t out[16];
uint8_t expected[16];
uint32_t address = 0x40;
int ret;
int i;
reset_spi_mock(0);
for (i = 0; i < (int)sizeof(expected); i++) {
expected[i] = (uint8_t)(0xA0 + i);
mock_flash[address + i] = expected[i];
}
ret = spi_flash_read(address, out, sizeof(out));
ck_assert_int_eq(ret, (int)sizeof(out));
ck_assert_int_eq(0, memcmp(out, expected, sizeof(out)));
}
END_TEST
START_TEST(test_sector_erase_aligns_address)
{
uint32_t address = 0x1234;
uint32_t expected_addr = address & ~(SPI_FLASH_SECTOR_SIZE - 1);
int ret;
reset_spi_mock(0);
ret = spi_flash_sector_erase(address);
ck_assert_int_eq(ret, 0);
ck_assert_int_eq(sector_erase_count, 1);
ck_assert_int_eq(last_sector_erase_addr, expected_addr);
}
END_TEST
START_TEST(test_chip_erase_command)
{
int ret;
reset_spi_mock(0);
ret = spi_flash_chip_erase();
ck_assert_int_eq(ret, 0);
ck_assert_int_eq(chip_erase_count, 1);
}
END_TEST
START_TEST(test_spi_flash_release_calls_spi_release)
{
reset_spi_mock(0);
spi_flash_release();
ck_assert_int_eq(spi_release_called, 1);
}
END_TEST
Suite *spi_flash_suite(void)
{
Suite *s = suite_create("SPI Flash");
TCase *tcase_write = tcase_create("Write");
TCase *tcase_read = tcase_create("Read");
TCase *tcase_erase = tcase_create("Erase");
TCase *tcase_errors = tcase_create("Errors");
TCase *tcase_misc = tcase_create("Misc");
tcase_add_test(tcase_write, test_write_page_midpage_short_len);
tcase_add_test(tcase_write, test_write_page_cross_page);
tcase_add_test(tcase_write, test_write_page_exact_to_boundary);
tcase_add_test(tcase_write, test_write_singlebyte_mode);
tcase_add_test(tcase_read, test_read_basic);
tcase_add_test(tcase_erase, test_sector_erase_aligns_address);
tcase_add_test(tcase_erase, test_chip_erase_command);
tcase_add_test(tcase_errors, test_write_len_zero_returns_error);
tcase_add_test(tcase_misc, test_spi_flash_release_calls_spi_release);
suite_add_tcase(s, tcase_write);
suite_add_tcase(s, tcase_read);
suite_add_tcase(s, tcase_erase);
suite_add_tcase(s, tcase_errors);
suite_add_tcase(s, tcase_misc);
return s;
}
int main(void)
{
int fails;
Suite *s = spi_flash_suite();
SRunner *sr = srunner_create(s);
srunner_run_all(sr, CK_NORMAL);
fails = srunner_ntests_failed(sr);
srunner_free(sr);
return fails;
}