mirror of https://github.com/wolfSSL/wolfBoot.git
Fix spi_flash_write_page to stop at len or page boundary
+ Added unit tests for spi_flash.cpull/690/head
parent
3be63df3f3
commit
d9fe0f42df
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
Loading…
Reference in New Issue