From 8866935904becb7ed287c81d5b4bf6e196c75899 Mon Sep 17 00:00:00 2001 From: CYFS <2805686936@qq.com> Date: Thu, 2 Jul 2026 11:36:16 +0800 Subject: [PATCH] [components][spi] add utest cases --- .../.ci/attachconfig/ci.attachconfig.yml | 18 + .../board/linker_scripts/link.ld | 7 + .../.ci/attachconfig/ci.attachconfig.yml | 20 + components/drivers/spi/SConscript | 6 + components/drivers/spi/utest/Kconfig | 112 +++++ components/drivers/spi/utest/SConscript | 16 + components/drivers/spi/utest/spi_flash_tc.c | 431 +++++++++++++++++ .../drivers/spi/utest/spi_loopback_tc.c | 452 ++++++++++++++++++ components/utilities/utest/Kconfig | 1 + 9 files changed, 1063 insertions(+) create mode 100644 bsp/gd32/arm/gd32527I-eval/.ci/attachconfig/ci.attachconfig.yml create mode 100644 components/drivers/spi/utest/Kconfig create mode 100644 components/drivers/spi/utest/SConscript create mode 100644 components/drivers/spi/utest/spi_flash_tc.c create mode 100644 components/drivers/spi/utest/spi_loopback_tc.c diff --git a/bsp/gd32/arm/gd32527I-eval/.ci/attachconfig/ci.attachconfig.yml b/bsp/gd32/arm/gd32527I-eval/.ci/attachconfig/ci.attachconfig.yml new file mode 100644 index 00000000000..09a4c3c475f --- /dev/null +++ b/bsp/gd32/arm/gd32527I-eval/.ci/attachconfig/ci.attachconfig.yml @@ -0,0 +1,18 @@ +utest.spi: + kconfig: + - CONFIG_BSP_USING_SPI=y + - CONFIG_BSP_USING_SPI5=y + - CONFIG_RT_USING_UTESTCASES=y + - CONFIG_RT_CONSOLEBUF_SIZE=256 + - CONFIG_RT_UTEST_SPI_LOOPBACK=y + - CONFIG_RT_UTEST_SPI_LOOPBACK_BUS_NAME="spi5" + - CONFIG_RT_UTEST_SPI_LOOPBACK_DEV_NAME="spi_loop" + - CONFIG_RT_UTEST_SPI_LOOPBACK_DATA_SIZE=512 + - CONFIG_RT_UTEST_SPI_LOOPBACK_MAX_HZ=10000000 + - CONFIG_RT_UTEST_SPI_LOOPBACK_API_SMOKE=y + - CONFIG_RT_UTEST_SPI_FLASH=y + - CONFIG_RT_UTEST_SPI_FLASH_SPI_DEV_NAME="spi50" + - CONFIG_RT_UTEST_SPI_FLASH_BUS_NAME="spi5" + - CONFIG_RT_UTEST_SPI_FLASH_CS_PIN=136 + - CONFIG_RT_UTEST_SPI_FLASH_DEV_NAME="GD25Q16B" + - CONFIG_RT_UTEST_SPI_FLASH_MAX_HZ=10000000 diff --git a/bsp/gd32/arm/gd32527I-eval/board/linker_scripts/link.ld b/bsp/gd32/arm/gd32527I-eval/board/linker_scripts/link.ld index 6fbeea4e92d..2ef9902e0e5 100644 --- a/bsp/gd32/arm/gd32527I-eval/board/linker_scripts/link.ld +++ b/bsp/gd32/arm/gd32527I-eval/board/linker_scripts/link.ld @@ -39,6 +39,13 @@ SECTIONS __vsymtab_end = .; . = ALIGN(4); + /* section information for utest */ + . = ALIGN(4); + __rt_utest_tc_tab_start = .; + KEEP(*(UtestTcTab)) + __rt_utest_tc_tab_end = .; + . = ALIGN(4); + /* section information for initial. */ . = ALIGN(4); __rt_init_start = .; diff --git a/bsp/stm32/stm32f407-rt-spark/.ci/attachconfig/ci.attachconfig.yml b/bsp/stm32/stm32f407-rt-spark/.ci/attachconfig/ci.attachconfig.yml index 5492a642cf6..6b62ab697a4 100644 --- a/bsp/stm32/stm32f407-rt-spark/.ci/attachconfig/ci.attachconfig.yml +++ b/bsp/stm32/stm32f407-rt-spark/.ci/attachconfig/ci.attachconfig.yml @@ -202,6 +202,26 @@ utest.pin: - CONFIG_RT_USING_UTESTCASES=y - CONFIG_RT_CONSOLEBUF_SIZE=256 - CONFIG_RT_UTEST_PIN=y +utest.spi: + <<: *scons + kconfig: + - CONFIG_BSP_USING_SPI=y + - CONFIG_BSP_USING_SPI1=y + - CONFIG_BSP_USING_SPI2=y + - CONFIG_RT_USING_UTESTCASES=y + - CONFIG_RT_CONSOLEBUF_SIZE=256 + - CONFIG_RT_UTEST_SPI_LOOPBACK=y + - CONFIG_RT_UTEST_SPI_LOOPBACK_BUS_NAME="spi1" + - CONFIG_RT_UTEST_SPI_LOOPBACK_DEV_NAME="spi_loop" + - CONFIG_RT_UTEST_SPI_LOOPBACK_DATA_SIZE=512 + - CONFIG_RT_UTEST_SPI_LOOPBACK_MAX_HZ=10000000 + - CONFIG_RT_UTEST_SPI_LOOPBACK_API_SMOKE=y + - CONFIG_RT_UTEST_SPI_FLASH=y + - CONFIG_RT_UTEST_SPI_FLASH_SPI_DEV_NAME="spi20" + - CONFIG_RT_UTEST_SPI_FLASH_BUS_NAME="spi2" + - CONFIG_RT_UTEST_SPI_FLASH_CS_PIN=28 + - CONFIG_RT_UTEST_SPI_FLASH_DEV_NAME="W25Q64" + - CONFIG_RT_UTEST_SPI_FLASH_MAX_HZ=10000000 # ------ component CI ------ component.cherryusb_cdc: kconfig: diff --git a/components/drivers/spi/SConscript b/components/drivers/spi/SConscript index a48767cb25d..29499e7e4d7 100644 --- a/components/drivers/spi/SConscript +++ b/components/drivers/spi/SConscript @@ -1,5 +1,6 @@ from building import * from gcc import * +import os import rtconfig cwd = GetCurrentDir() @@ -46,4 +47,9 @@ src += src_device group = DefineGroup('DeviceDrivers', src, depend = ['RT_USING_SPI'], CPPPATH = CPPPATH, LOCAL_CFLAGS = LOCAL_CFLAGS) +list = os.listdir(cwd) +for item in list: + if os.path.isfile(os.path.join(cwd, item, 'SConscript')): + group = group + SConscript(os.path.join(item, 'SConscript')) + Return('group') diff --git a/components/drivers/spi/utest/Kconfig b/components/drivers/spi/utest/Kconfig new file mode 100644 index 00000000000..1638916ab5b --- /dev/null +++ b/components/drivers/spi/utest/Kconfig @@ -0,0 +1,112 @@ +menu "SPI Test" + +config RT_UTEST_SPI_LOOPBACK + bool "SPI MOSI/MISO Loopback Test" + select RT_USING_SPI + default n + help + This test validates full-duplex loopback data with sendrecv8, + transfer and transfer_message over length and clock matrices. + The hardware transfer mode, such as polling, interrupt or DMA, is + selected by the BSP SPI driver configuration. + +if RT_UTEST_SPI_LOOPBACK + +config RT_UTEST_SPI_LOOPBACK_BUS_NAME + string "SPI bus name for loopback test" + default "spi2" + +config RT_UTEST_SPI_LOOPBACK_DEV_NAME + string "Temporary SPI device name for loopback test" + default "spi_loop" + +config RT_UTEST_SPI_LOOPBACK_DATA_SIZE + int "SPI loopback maximum test data size" + range 1 65535 + default 512 + +config RT_UTEST_SPI_LOOPBACK_MAX_HZ + int "SPI loopback maximum clock frequency (Hz)" + range 1 100000000 + default 10000000 + help + The test automatically builds the clock matrix from standard SPI + frequencies up to this value, and always includes this maximum value. + +config RT_UTEST_SPI_LOOPBACK_API_SMOKE + bool "Enable SPI TX-only/RX-only API smoke test" + default n + help + Enable this to smoke-test TX-only and RX-only transfer APIs by return + length. These transfers do not validate MOSI/MISO loopback data and + may be unsupported or unstable on some BSP SPI controllers. + +config RT_UTEST_SPI_LOOPBACK_TIMEOUT + int "SPI loopback test timeout (s)" + range 1 3600 + default 60 + +endif + +config RT_UTEST_SPI_FLASH + bool "SPI Flash SFUD Read/Write Test" + select RT_USING_SPI + select RT_USING_SFUD + select RT_SFUD_USING_SFDP + select RT_SFUD_USING_FLASH_INFO_TABLE + select RT_USING_HEAP + default n + +if RT_UTEST_SPI_FLASH + +config RT_UTEST_SPI_FLASH_SPI_DEV_NAME + string "SPI device name for SFUD flash" + default "spi20" + help + The SPI device that connects to the flash, such as spi20. If it does + not exist yet, the test attaches it to the configured SPI bus and CS pin. + +config RT_UTEST_SPI_FLASH_BUS_NAME + string "SPI bus name for flash device attach" + default "spi2" + help + The SPI bus used when the test needs to attach the SPI flash device. + +config RT_UTEST_SPI_FLASH_CS_PIN + int "SPI flash CS pin index" + range -1 32767 + default -1 + help + Use the RT-Thread pin index used by the BSP, such as a GET_PIN() value + on STM32. -1 means PIN_NONE. + +config RT_UTEST_SPI_FLASH_DEV_NAME + string "SFUD flash model/device name" + default "W25Q64" + help + This is the SFUD flash device name used to find or register the flash, + and is usually set to the board flash model name, such as W25Q64 or + W25Q128. SFUD still verifies the actual chip by JEDEC ID and SFDP or + its flash information table when probing. + +config RT_UTEST_SPI_FLASH_MAX_HZ + int "SPI flash test maximum clock frequency (Hz)" + range 1 50000000 + default 10000000 + help + The SPI clock used while probing and testing the flash. Keep this + conservative for board-level utests because wiring, pinmux and flash + parts may vary. + +config RT_UTEST_SPI_FLASH_TEST_OFFSET + hex "SPI flash test offset" + default 0x0 + +config RT_UTEST_SPI_FLASH_TEST_SIZE + int "SPI flash test data size" + range 1 4096 + default 256 + +endif + +endmenu diff --git a/components/drivers/spi/utest/SConscript b/components/drivers/spi/utest/SConscript new file mode 100644 index 00000000000..8b7a049e5e2 --- /dev/null +++ b/components/drivers/spi/utest/SConscript @@ -0,0 +1,16 @@ +Import('rtconfig') +from building import * + +cwd = GetCurrentDir() +src = [] +CPPPATH = [cwd, cwd + '/..', cwd + '/../sfud/inc'] + +if GetDepend(['RT_UTEST_SPI_LOOPBACK']): + src += ['spi_loopback_tc.c'] + +if GetDepend(['RT_UTEST_SPI_FLASH']): + src += ['spi_flash_tc.c'] + +group = DefineGroup('utestcases', src, depend = ['RT_USING_UTESTCASES', 'RT_USING_SPI'], CPPPATH = CPPPATH) + +Return('group') diff --git a/components/drivers/spi/utest/spi_flash_tc.c b/components/drivers/spi/utest/spi_flash_tc.c new file mode 100644 index 00000000000..184e51b368e --- /dev/null +++ b/components/drivers/spi/utest/spi_flash_tc.c @@ -0,0 +1,431 @@ +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-07-02 CYFS add SPI flash SFUD read/write utest + */ + +#include +#include +#include "utest.h" + +#ifdef RT_UTEST_SPI_FLASH + +#undef DBG_TAG +#undef DBG_LVL +#include "dev_spi_flash_sfud.h" + +#define SPI_FLASH_SPI_DEV_NAME RT_UTEST_SPI_FLASH_SPI_DEV_NAME +#define SPI_FLASH_DEV_NAME RT_UTEST_SPI_FLASH_DEV_NAME +#define SPI_FLASH_BUS_NAME RT_UTEST_SPI_FLASH_BUS_NAME +#define SPI_FLASH_CS_PIN RT_UTEST_SPI_FLASH_CS_PIN +#define SPI_FLASH_MAX_HZ RT_UTEST_SPI_FLASH_MAX_HZ +#define SPI_FLASH_TEST_OFFSET RT_UTEST_SPI_FLASH_TEST_OFFSET +#define SPI_FLASH_TEST_SIZE RT_UTEST_SPI_FLASH_TEST_SIZE + +static rt_spi_flash_device_t spi_flash_probe_dev = RT_NULL; +static sfud_flash_t sfud_dev = RT_NULL; +static struct rt_spi_device spi_flash_attached_dev; +static rt_bool_t spi_flash_attached; +static rt_uint32_t test_addr; +static rt_uint32_t backup_addr; +static rt_uint32_t backup_size; +static rt_uint8_t *backup_buf = RT_NULL; +static rt_uint8_t *write_buf = RT_NULL; +static rt_uint8_t *read_buf = RT_NULL; + +static rt_uint32_t spi_flash_align_down(rt_uint32_t value, rt_uint32_t align) +{ + return value - value % align; +} + +static rt_uint32_t spi_flash_align_up(rt_uint32_t value, rt_uint32_t align) +{ + rt_uint64_t aligned = value; + + aligned = (aligned + align - 1) / align * align; + return (rt_uint32_t)aligned; +} + +static rt_err_t spi_flash_restore_backup(void) +{ + sfud_err result; + + if (sfud_dev == RT_NULL || backup_buf == RT_NULL || backup_size == 0) + { + return RT_EOK; + } + + result = sfud_erase_write(sfud_dev, backup_addr, backup_size, backup_buf); + if (result != SFUD_SUCCESS) + { + LOG_E("Restore SPI flash data failed, addr: 0x%08x, size: %u.", backup_addr, backup_size); + return -RT_ERROR; + } + + return RT_EOK; +} + +static void spi_flash_fill_pattern(rt_uint8_t *buf, rt_size_t size) +{ + rt_size_t i; + + for (i = 0; i < size; i++) + { + buf[i] = (rt_uint8_t)(0xA5 ^ (i * 13 + (i >> 2))); + } +} + +static void spi_flash_free_buffers(void) +{ + if (backup_buf != RT_NULL) + { + rt_free(backup_buf); + backup_buf = RT_NULL; + } + if (write_buf != RT_NULL) + { + rt_free(write_buf); + write_buf = RT_NULL; + } + if (read_buf != RT_NULL) + { + rt_free(read_buf); + read_buf = RT_NULL; + } +} + +static void spi_flash_delete_probe_dev(void) +{ + sfud_dev = RT_NULL; + if (spi_flash_probe_dev != RT_NULL) + { + rt_sfud_flash_delete(spi_flash_probe_dev); + spi_flash_probe_dev = RT_NULL; + } +} + +static void spi_flash_detach_device(void) +{ + if (spi_flash_attached) + { + rt_spi_bus_detach_device_cspin(&spi_flash_attached_dev); + spi_flash_attached = RT_FALSE; + } +} + +static rt_bool_t spi_flash_is_all_ff(const rt_uint8_t *buf, rt_size_t size) +{ + rt_size_t i; + + for (i = 0; i < size; i++) + { + if (buf[i] != 0xFF) + { + return RT_FALSE; + } + } + + return RT_TRUE; +} + +static sfud_flash_t spi_flash_find_existing(void) +{ + rt_device_t flash_dev; + rt_device_t spi_dev; + + flash_dev = rt_device_find(SPI_FLASH_DEV_NAME); + if (flash_dev != RT_NULL && flash_dev->type == RT_Device_Class_Block) + { + rt_spi_flash_device_t spi_flash_dev = (rt_spi_flash_device_t)flash_dev; + + if (spi_flash_dev->user_data != RT_NULL) + { + LOG_I("Use existing SFUD flash device %s.", SPI_FLASH_DEV_NAME); + return (sfud_flash_t)spi_flash_dev->user_data; + } + } + + spi_dev = rt_device_find(SPI_FLASH_SPI_DEV_NAME); + if (spi_dev != RT_NULL && spi_dev->type == RT_Device_Class_SPIDevice) + { + struct rt_spi_device *rt_spi_dev = (struct rt_spi_device *)spi_dev; + rt_spi_flash_device_t spi_flash_dev = (rt_spi_flash_device_t)rt_spi_dev->user_data; + + if (spi_flash_dev != RT_NULL && spi_flash_dev->user_data != RT_NULL) + { + sfud_flash_t flash = (sfud_flash_t)spi_flash_dev->user_data; + + LOG_I("Use existing SFUD flash %s on SPI device %s.", + flash->name, SPI_FLASH_SPI_DEV_NAME); + return flash; + } + } + + return RT_NULL; +} + +static rt_err_t spi_flash_attach_device(void) +{ + rt_base_t cs_pin = SPI_FLASH_CS_PIN; + rt_device_t spi_dev; + rt_device_t bus; + rt_err_t result; + + spi_dev = rt_device_find(SPI_FLASH_SPI_DEV_NAME); + if (spi_dev != RT_NULL) + { + if (spi_dev->type != RT_Device_Class_SPIDevice) + { + LOG_E("%s exists but is not an SPI device.", SPI_FLASH_SPI_DEV_NAME); + return -RT_ERROR; + } + return RT_EOK; + } + + bus = rt_device_find(SPI_FLASH_BUS_NAME); + if (bus == RT_NULL || bus->type != RT_Device_Class_SPIBUS) + { + LOG_E("SPI bus %s not found.", SPI_FLASH_BUS_NAME); + return -RT_ERROR; + } + + if (cs_pin < 0) + { + cs_pin = PIN_NONE; + } + + LOG_I("Try to attach SPI flash device %s to bus %s, CS pin %d.", + SPI_FLASH_SPI_DEV_NAME, SPI_FLASH_BUS_NAME, (int)cs_pin); + + rt_memset(&spi_flash_attached_dev, 0, sizeof(spi_flash_attached_dev)); + result = rt_spi_bus_attach_device_cspin(&spi_flash_attached_dev, + SPI_FLASH_SPI_DEV_NAME, + SPI_FLASH_BUS_NAME, + cs_pin, + RT_NULL); + if (result == RT_EOK) + { + spi_flash_attached = RT_TRUE; + LOG_I("Attach SPI flash device %s to bus %s success.", + SPI_FLASH_SPI_DEV_NAME, SPI_FLASH_BUS_NAME); + } + else + { + LOG_E("Attach SPI flash device %s to bus %s failed, result: %d.", + SPI_FLASH_SPI_DEV_NAME, SPI_FLASH_BUS_NAME, result); + } + + return result; +} + +static rt_err_t spi_flash_read_jedec_id(rt_uint8_t id[3]) +{ + rt_device_t spi_dev; + struct rt_spi_device *rt_spi_dev; + struct rt_spi_configuration cfg; + rt_uint8_t cmd = 0x9F; + rt_err_t result; + + spi_dev = rt_device_find(SPI_FLASH_SPI_DEV_NAME); + if (spi_dev == RT_NULL || spi_dev->type != RT_Device_Class_SPIDevice) + { + return -RT_ERROR; + } + + rt_spi_dev = (struct rt_spi_device *)spi_dev; + rt_memset(&cfg, 0, sizeof(cfg)); + cfg.mode = RT_SPI_MODE_0 | RT_SPI_MSB; + cfg.data_width = 8; + cfg.max_hz = SPI_FLASH_MAX_HZ; + + result = rt_spi_configure(rt_spi_dev, &cfg); + if (result != RT_EOK && result != -RT_EBUSY) + { + return result; + } + + return rt_spi_send_then_recv(rt_spi_dev, &cmd, sizeof(cmd), id, 3); +} + +static void spi_flash_probe_info(void) +{ + rt_uint8_t mf_id = sfud_dev->chip.mf_id; + rt_uint8_t type_id = sfud_dev->chip.type_id; + rt_uint8_t capacity_id = sfud_dev->chip.capacity_id; + + LOG_I("SPI flash %s JEDEC ID %02x %02x %02x, capacity %u bytes, erase granularity %u bytes.", + sfud_dev->name, mf_id, type_id, capacity_id, sfud_dev->chip.capacity, sfud_dev->chip.erase_gran); + + uassert_true(sfud_dev->chip.capacity > 0); + uassert_true(sfud_dev->chip.erase_gran > 0); + uassert_true(sfud_dev->chip.write_mode != 0); + uassert_true(sfud_dev->chip.erase_gran_cmd != 0); +} + +static void spi_flash_erase_write_read(void) +{ + sfud_err result; + + rt_memset(read_buf, 0, SPI_FLASH_TEST_SIZE); + result = sfud_erase(sfud_dev, backup_addr, backup_size); + uassert_int_equal(result, SFUD_SUCCESS); + + result = sfud_read(sfud_dev, test_addr, SPI_FLASH_TEST_SIZE, read_buf); + uassert_int_equal(result, SFUD_SUCCESS); + uassert_true(spi_flash_is_all_ff(read_buf, SPI_FLASH_TEST_SIZE)); + + spi_flash_fill_pattern(write_buf, SPI_FLASH_TEST_SIZE); + result = sfud_write(sfud_dev, test_addr, SPI_FLASH_TEST_SIZE, write_buf); + uassert_int_equal(result, SFUD_SUCCESS); + + rt_memset(read_buf, 0, SPI_FLASH_TEST_SIZE); + result = sfud_read(sfud_dev, test_addr, SPI_FLASH_TEST_SIZE, read_buf); + uassert_int_equal(result, SFUD_SUCCESS); + uassert_buf_equal(write_buf, read_buf, SPI_FLASH_TEST_SIZE); +} + +static rt_err_t utest_tc_init(void) +{ + rt_uint32_t erase_gran; + + sfud_dev = spi_flash_find_existing(); + if (sfud_dev == RT_NULL && spi_flash_attach_device() != RT_EOK) + { + LOG_E("Attach SPI flash device %s on bus %s failed.", + SPI_FLASH_SPI_DEV_NAME, SPI_FLASH_BUS_NAME); + return -RT_ERROR; + } + + if (sfud_dev == RT_NULL) + { + rt_uint8_t jedec_id[3] = {0}; + + if (spi_flash_read_jedec_id(jedec_id) == RT_EOK) + { + LOG_I("SPI flash raw JEDEC ID: %02x %02x %02x at %u Hz.", + jedec_id[0], jedec_id[1], jedec_id[2], (unsigned int)SPI_FLASH_MAX_HZ); + } + else + { + LOG_W("Read SPI flash raw JEDEC ID failed before SFUD probe."); + } + + LOG_I("Try to probe SFUD flash device %s by SPI device %s.", + SPI_FLASH_DEV_NAME, SPI_FLASH_SPI_DEV_NAME); + { + struct rt_spi_configuration cfg; + + rt_memset(&cfg, 0, sizeof(cfg)); + cfg.mode = RT_SPI_MODE_0 | RT_SPI_MSB; + cfg.data_width = 8; + cfg.max_hz = SPI_FLASH_MAX_HZ; + spi_flash_probe_dev = rt_sfud_flash_probe_ex(SPI_FLASH_DEV_NAME, + SPI_FLASH_SPI_DEV_NAME, + &cfg, + RT_NULL); + } + if (spi_flash_probe_dev != RT_NULL) + { + sfud_dev = (sfud_flash_t)spi_flash_probe_dev->user_data; + } + } + + if (sfud_dev == RT_NULL) + { + LOG_E("SFUD flash on SPI device %s not found.", SPI_FLASH_SPI_DEV_NAME); + spi_flash_detach_device(); + return -RT_ERROR; + } + + if (SPI_FLASH_TEST_SIZE <= 0) + { + LOG_E("SPI flash test size must be greater than 0."); + spi_flash_detach_device(); + return -RT_ERROR; + } + + erase_gran = sfud_dev->chip.erase_gran; + if (sfud_dev->chip.capacity == 0 || erase_gran == 0) + { + LOG_E("Invalid SPI flash geometry, capacity: %u, erase granularity: %u.", + sfud_dev->chip.capacity, erase_gran); + goto __error; + } + + test_addr = SPI_FLASH_TEST_OFFSET; + if (test_addr >= sfud_dev->chip.capacity || + SPI_FLASH_TEST_SIZE > sfud_dev->chip.capacity - test_addr) + { + LOG_E("SPI flash test range is out of bounds, offset: 0x%08x, size: %u, capacity: %u.", + test_addr, SPI_FLASH_TEST_SIZE, sfud_dev->chip.capacity); + goto __error; + } + + backup_addr = spi_flash_align_down(test_addr, erase_gran); + backup_size = spi_flash_align_up(test_addr + SPI_FLASH_TEST_SIZE - backup_addr, erase_gran); + if (backup_size > sfud_dev->chip.capacity - backup_addr) + { + LOG_E("SPI flash backup range is out of bounds, offset: 0x%08x, size: %u, capacity: %u.", + backup_addr, backup_size, sfud_dev->chip.capacity); + goto __error; + } + + backup_buf = (rt_uint8_t *)rt_malloc(backup_size); + write_buf = (rt_uint8_t *)rt_malloc(SPI_FLASH_TEST_SIZE); + read_buf = (rt_uint8_t *)rt_malloc(SPI_FLASH_TEST_SIZE); + if (backup_buf == RT_NULL || write_buf == RT_NULL || read_buf == RT_NULL) + { + LOG_E("Allocate SPI flash test buffer failed."); + goto __nomem; + } + + if (sfud_read(sfud_dev, backup_addr, backup_size, backup_buf) != SFUD_SUCCESS) + { + LOG_E("Backup SPI flash data failed, addr: 0x%08x, size: %u.", backup_addr, backup_size); + goto __error; + } + + LOG_I("SPI flash test uses %s on %s at offset 0x%08x, size %u.", + sfud_dev->name, SPI_FLASH_SPI_DEV_NAME, test_addr, SPI_FLASH_TEST_SIZE); + + return RT_EOK; + +__nomem: + spi_flash_free_buffers(); + spi_flash_delete_probe_dev(); + spi_flash_detach_device(); + return -RT_ENOMEM; + +__error: + spi_flash_free_buffers(); + spi_flash_delete_probe_dev(); + spi_flash_detach_device(); + return -RT_ERROR; +} + +static rt_err_t utest_tc_cleanup(void) +{ + rt_err_t result; + + result = spi_flash_restore_backup(); + + spi_flash_free_buffers(); + spi_flash_delete_probe_dev(); + spi_flash_detach_device(); + + return result; +} + +static void testcase(void) +{ + UTEST_UNIT_RUN(spi_flash_probe_info); + UTEST_UNIT_RUN(spi_flash_erase_write_read); +} + +UTEST_TC_EXPORT(testcase, "components.drivers.spi.flash", utest_tc_init, utest_tc_cleanup, 30); + +#endif /* RT_UTEST_SPI_FLASH */ diff --git a/components/drivers/spi/utest/spi_loopback_tc.c b/components/drivers/spi/utest/spi_loopback_tc.c new file mode 100644 index 00000000000..6f9b5505e2a --- /dev/null +++ b/components/drivers/spi/utest/spi_loopback_tc.c @@ -0,0 +1,452 @@ +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-07-02 CYFS add SPI MOSI/MISO loopback utest + */ + +#include +#include +#include "utest.h" + +#ifdef RT_UTEST_SPI_LOOPBACK + +#ifndef RT_UTEST_SPI_LOOPBACK_MAX_HZ +#define RT_UTEST_SPI_LOOPBACK_MAX_HZ 10000000 +#endif + +#define SPI_LOOPBACK_BUS_NAME RT_UTEST_SPI_LOOPBACK_BUS_NAME +#define SPI_LOOPBACK_DEV_NAME RT_UTEST_SPI_LOOPBACK_DEV_NAME +#define SPI_LOOPBACK_DATA_SIZE RT_UTEST_SPI_LOOPBACK_DATA_SIZE +#define SPI_LOOPBACK_MAX_HZ RT_UTEST_SPI_LOOPBACK_MAX_HZ +#define SPI_LOOPBACK_TIMEOUT RT_UTEST_SPI_LOOPBACK_TIMEOUT +#define SPI_LOOPBACK_CLK_ENTRY(max_hz) ((SPI_LOOPBACK_MAX_HZ > (max_hz)) ? (max_hz) : 0) +#define SPI_LOOPBACK_LEN_ENTRY(length) ((SPI_LOOPBACK_DATA_SIZE > (length)) ? (length) : 0) +#define SPI_LOOPBACK_CLK_TABLE \ +{ \ + SPI_LOOPBACK_CLK_ENTRY(100000), \ + SPI_LOOPBACK_CLK_ENTRY(400000), \ + SPI_LOOPBACK_CLK_ENTRY(1000000), \ + SPI_LOOPBACK_CLK_ENTRY(2000000), \ + SPI_LOOPBACK_CLK_ENTRY(5000000), \ + SPI_LOOPBACK_CLK_ENTRY(10000000), \ + SPI_LOOPBACK_CLK_ENTRY(50000000), \ + SPI_LOOPBACK_MAX_HZ, \ +} +#define SPI_LOOPBACK_LEN_TABLE \ +{ \ + SPI_LOOPBACK_LEN_ENTRY(1), \ + SPI_LOOPBACK_LEN_ENTRY(8), \ + SPI_LOOPBACK_LEN_ENTRY(31), \ + SPI_LOOPBACK_LEN_ENTRY(32), \ + SPI_LOOPBACK_LEN_ENTRY(64), \ + SPI_LOOPBACK_LEN_ENTRY(256), \ + SPI_LOOPBACK_LEN_ENTRY(512), \ + SPI_LOOPBACK_DATA_SIZE, \ +} + +static struct rt_spi_device spi_loopback_dev; +static rt_bool_t spi_loopback_attached; +static rt_uint8_t tx_buf[SPI_LOOPBACK_DATA_SIZE]; +static rt_uint8_t rx_buf[SPI_LOOPBACK_DATA_SIZE]; +static const rt_uint32_t spi_loopback_clk_table[] = SPI_LOOPBACK_CLK_TABLE; +static const rt_size_t spi_loopback_len_table[] = SPI_LOOPBACK_LEN_TABLE; + +static void spi_loopback_fill_pattern(rt_uint8_t *buf, rt_size_t size, rt_uint8_t seed) +{ + rt_size_t i; + + for (i = 0; i < size; i++) + { + buf[i] = (rt_uint8_t)(seed + i * 17 + (i >> 1)); + } +} + +static rt_err_t spi_loopback_configure(rt_uint32_t max_hz) +{ + rt_err_t result; + struct rt_spi_configuration cfg; + + rt_memset(&cfg, 0, sizeof(cfg)); + cfg.data_width = 8; + cfg.mode = RT_SPI_MODE_0 | RT_SPI_MSB | RT_SPI_NO_CS; + cfg.max_hz = max_hz; + + spi_loopback_dev.config.usage_freq = 0; + result = rt_spi_configure(&spi_loopback_dev, &cfg); + if (result == -RT_EBUSY) + { + result = RT_EOK; + } + + return result; +} + +static rt_bool_t spi_loopback_check_length(rt_size_t length) +{ + if (length == 0) + { + return RT_FALSE; + } + + if (length > sizeof(tx_buf)) + { + return RT_FALSE; + } + + return RT_TRUE; +} + +static void spi_loopback_validate_length_table(void) +{ + rt_size_t i; + rt_bool_t valid = RT_TRUE; + + for (i = 0; i < sizeof(spi_loopback_len_table) / sizeof(spi_loopback_len_table[0]); i++) + { + rt_size_t length = spi_loopback_len_table[i]; + + if (length == 0) + { + continue; + } + + if (length > sizeof(tx_buf)) + { + LOG_E("SPI loopback length matrix[%u] = %u exceeds data size %u.", + (unsigned int)i, (unsigned int)length, (unsigned int)sizeof(tx_buf)); + valid = RT_FALSE; + continue; + } + } + + uassert_true(valid); +} + +static rt_bool_t spi_loopback_check_buffer(const rt_uint8_t *tx, const rt_uint8_t *rx, + rt_size_t length, const char *stage, + rt_size_t clk_index, rt_size_t len_index) +{ + rt_size_t i; + + if (rt_memcmp(tx, rx, length) == 0) + { + uassert_buf_equal(tx, rx, length); + return RT_TRUE; + } + + for (i = 0; i < length; i++) + { + if (tx[i] != rx[i]) + { + LOG_E("SPI loopback %s mismatch: clk[%u], len[%u], offset %u, tx=0x%02x, rx=0x%02x.", + stage, + (unsigned int)clk_index, + (unsigned int)len_index, + (unsigned int)i, + tx[i], + rx[i]); + break; + } + } + + uassert_buf_equal(tx, rx, length); + return RT_FALSE; +} + +static rt_bool_t spi_loopback_transfer_full_duplex(rt_size_t length, rt_uint8_t seed, + rt_size_t clk_index, rt_size_t len_index) +{ + rt_ssize_t result; + + if (!spi_loopback_check_length(length)) + { + return RT_FALSE; + } + + spi_loopback_fill_pattern(tx_buf, length, seed); + rt_memset(rx_buf, 0, length); + + result = rt_spi_transfer(&spi_loopback_dev, tx_buf, rx_buf, length); + uassert_int_equal(result, (rt_ssize_t)length); + if (result != (rt_ssize_t)length) + { + return RT_FALSE; + } + + return spi_loopback_check_buffer(tx_buf, rx_buf, length, "transfer", clk_index, len_index); +} + +static rt_bool_t spi_loopback_sendrecv8(rt_uint8_t tx_data, rt_size_t clk_index) +{ + rt_uint8_t rx_data = 0; + rt_err_t result; + + result = rt_spi_sendrecv8(&spi_loopback_dev, tx_data, &rx_data); + uassert_int_equal(result, RT_EOK); + if (result != RT_EOK) + { + return RT_FALSE; + } + + if (rx_data != tx_data) + { + LOG_E("SPI loopback byte mismatch: clk[%u], tx=0x%02x, rx=0x%02x. Check the selected bus MOSI/MISO pins.", + (unsigned int)clk_index, + tx_data, + rx_data); + } + uassert_int_equal(rx_data, tx_data); + + return (rx_data == tx_data) ? RT_TRUE : RT_FALSE; +} + +static rt_bool_t spi_loopback_transfer_message(rt_size_t length, rt_uint8_t seed, + rt_size_t clk_index, rt_size_t len_index) +{ + struct rt_spi_message msg1; + struct rt_spi_message msg2; + struct rt_spi_message *result; + rt_size_t first_len; + + if (!spi_loopback_check_length(length)) + { + return RT_FALSE; + } + + if (length < 2) + { + return RT_TRUE; + } + + first_len = length / 2; + spi_loopback_fill_pattern(tx_buf, length, seed); + rt_memset(rx_buf, 0, length); + rt_memset(&msg1, 0, sizeof(msg1)); + rt_memset(&msg2, 0, sizeof(msg2)); + + msg1.send_buf = tx_buf; + msg1.recv_buf = rx_buf; + msg1.length = first_len; + msg1.cs_take = 1; + msg1.cs_release = 0; + msg1.next = &msg2; + + msg2.send_buf = tx_buf + first_len; + msg2.recv_buf = rx_buf + first_len; + msg2.length = length - first_len; + msg2.cs_take = 0; + msg2.cs_release = 1; + msg2.next = RT_NULL; + + result = rt_spi_transfer_message(&spi_loopback_dev, &msg1); + uassert_null(result); + if (result != RT_NULL) + { + return RT_FALSE; + } + + return spi_loopback_check_buffer(tx_buf, rx_buf, length, "message", clk_index, len_index); +} + +static rt_bool_t spi_loopback_data_length_matrix(rt_size_t clk_index) +{ + rt_size_t i; + rt_size_t tested = 0; + rt_bool_t passed = RT_TRUE; + + for (i = 0; i < sizeof(spi_loopback_len_table) / sizeof(spi_loopback_len_table[0]); i++) + { + rt_size_t length = spi_loopback_len_table[i]; + + if (length == 0) + { + continue; + } + + if (!spi_loopback_check_length(length)) + { + continue; + } + + tested++; + LOG_I("SPI loopback data matrix: clk[%u], len[%u] = %u bytes.", + (unsigned int)clk_index, (unsigned int)i, (unsigned int)length); + + if (!spi_loopback_transfer_full_duplex(length, (rt_uint8_t)(0x35 + clk_index + i), clk_index, i)) + { + passed = RT_FALSE; + break; + } + + if (!spi_loopback_transfer_message(length, (rt_uint8_t)(0x5A + clk_index + i), clk_index, i)) + { + passed = RT_FALSE; + break; + } + } + + uassert_true(tested > 0); + return passed; +} + +#ifdef RT_UTEST_SPI_LOOPBACK_API_SMOKE +static void spi_loopback_transfer_tx_only_smoke(rt_size_t length, rt_uint8_t seed) +{ + rt_ssize_t result; + + if (!spi_loopback_check_length(length)) + { + return; + } + + spi_loopback_fill_pattern(tx_buf, length, seed); + + result = rt_spi_transfer(&spi_loopback_dev, tx_buf, RT_NULL, length); + uassert_int_equal(result, (rt_ssize_t)length); +} + +static void spi_loopback_transfer_rx_only_smoke(rt_size_t length) +{ + rt_ssize_t result; + + if (!spi_loopback_check_length(length)) + { + return; + } + + rt_memset(rx_buf, 0, length); + + result = rt_spi_transfer(&spi_loopback_dev, RT_NULL, rx_buf, length); + uassert_int_equal(result, (rt_ssize_t)length); +} + +static void spi_loopback_api_smoke_length_matrix(rt_size_t clk_index) +{ + rt_size_t i; + rt_size_t tested = 0; + + for (i = 0; i < sizeof(spi_loopback_len_table) / sizeof(spi_loopback_len_table[0]); i++) + { + rt_size_t length = spi_loopback_len_table[i]; + + if (!spi_loopback_check_length(length)) + { + continue; + } + + tested++; + LOG_I("SPI transfer API smoke: clk[%u], len[%u] = %u bytes, tx-only/rx-only.", + (unsigned int)clk_index, (unsigned int)i, (unsigned int)length); + + spi_loopback_transfer_tx_only_smoke(length, (rt_uint8_t)(0xC3 + clk_index + i)); + spi_loopback_transfer_rx_only_smoke(length); + } + + uassert_true(tested > 0); +} +#endif /* RT_UTEST_SPI_LOOPBACK_API_SMOKE */ + +static void spi_loopback_clock_matrix(void) +{ + rt_size_t i; + rt_size_t tested = 0; + + spi_loopback_validate_length_table(); + + for (i = 0; i < sizeof(spi_loopback_clk_table) / sizeof(spi_loopback_clk_table[0]); i++) + { + rt_err_t result; + rt_uint32_t request_hz = spi_loopback_clk_table[i]; + rt_uint32_t actual_hz; + + if (request_hz == 0) + { + continue; + } + + tested++; + LOG_I("SPI loopback clock matrix[%u]: request %u Hz.", (unsigned int)i, request_hz); + + result = spi_loopback_configure(request_hz); + uassert_int_equal(result, RT_EOK); + if (result != RT_EOK) + { + continue; + } + + actual_hz = spi_loopback_dev.config.usage_freq; + if (actual_hz != 0) + { + LOG_I("SPI loopback clock matrix[%u]: actual %u Hz.", (unsigned int)i, actual_hz); + } + + if (!spi_loopback_sendrecv8((rt_uint8_t)(0xA5 ^ i), i)) + { + LOG_E("Skip longer transfers for clock[%u] because the byte loopback check failed.", + (unsigned int)i); + continue; + } + +#ifdef RT_UTEST_SPI_LOOPBACK_API_SMOKE + if (spi_loopback_data_length_matrix(i)) + { + spi_loopback_api_smoke_length_matrix(i); + } +#else + (void)spi_loopback_data_length_matrix(i); +#endif + } + + uassert_true(tested > 0); +} + +static rt_err_t utest_tc_init(void) +{ + rt_device_t bus; + + LOG_I("SPI loopback test: connect MOSI and MISO on bus %s.", SPI_LOOPBACK_BUS_NAME); + LOG_I("SPI loopback test: use the selected bus pinmux, not another SPI header."); + LOG_I("SPI loopback test: max clock %u Hz, max data %u bytes.", + (unsigned int)SPI_LOOPBACK_MAX_HZ, (unsigned int)SPI_LOOPBACK_DATA_SIZE); + + bus = rt_device_find(SPI_LOOPBACK_BUS_NAME); + if (bus == RT_NULL || bus->type != RT_Device_Class_SPIBUS) + { + LOG_E("SPI bus %s not found.", SPI_LOOPBACK_BUS_NAME); + return -RT_ERROR; + } + + rt_memset(&spi_loopback_dev, 0, sizeof(spi_loopback_dev)); + if (rt_spi_bus_attach_device(&spi_loopback_dev, SPI_LOOPBACK_DEV_NAME, SPI_LOOPBACK_BUS_NAME, RT_NULL) != RT_EOK) + { + LOG_E("Attach SPI loopback device %s failed.", SPI_LOOPBACK_DEV_NAME); + return -RT_ERROR; + } + spi_loopback_attached = RT_TRUE; + + return RT_EOK; +} + +static rt_err_t utest_tc_cleanup(void) +{ + if (spi_loopback_attached) + { + rt_spi_bus_detach_device(&spi_loopback_dev); + spi_loopback_attached = RT_FALSE; + } + + return RT_EOK; +} + +static void testcase(void) +{ + UTEST_UNIT_RUN(spi_loopback_clock_matrix); +} + +UTEST_TC_EXPORT(testcase, "components.drivers.spi.loopback", utest_tc_init, utest_tc_cleanup, SPI_LOOPBACK_TIMEOUT); + +#endif /* RT_UTEST_SPI_LOOPBACK */ diff --git a/components/utilities/utest/Kconfig b/components/utilities/utest/Kconfig index 742ec521e2c..af23e9e7899 100644 --- a/components/utilities/utest/Kconfig +++ b/components/utilities/utest/Kconfig @@ -19,6 +19,7 @@ menu "RT-Thread Utestcases" rsource "../../../components/drivers/pin/utest/Kconfig" rsource "../../../components/drivers/serial/utest/Kconfig" rsource "../../../components/drivers/smp_call/utest/Kconfig" + rsource "../../../components/drivers/spi/utest/Kconfig" endmenu rsource "../../../components/dfs/utest/Kconfig"