Add SPI driver, block device layer, and SDHC-SPI SD card driver#18
Add SPI driver, block device layer, and SDHC-SPI SD card driver#18AlexLanzano merged 2 commits intowolfSSL:mainfrom
Conversation
… Implement SPI loopback test
There was a problem hiding this comment.
Pull request overview
This PR introduces a generic block-device abstraction and an SDHC/SDXC-over-SPI block driver, alongside a SPI API update to support session-based configuration (StartCom/EndCom) and corresponding board/test integration.
Changes:
- Add
whal_Block/whal_BlockDriverabstraction plus dispatch layer implementation. - Update the SPI API to session-based configuration (
StartCom/EndCom) and adapt the STM32WB SPI driver and tests. - Add an SD card (SDHC/SDXC) block driver over SPI plus a peripheral-board configuration and hardware tests.
Reviewed changes
Copilot reviewed 29 out of 29 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| wolfHAL/wolfHAL.h | Exposes the new block layer via the umbrella header. |
| wolfHAL/spi/stm32wb_spi.h | Updates STM32WB SPI driver API to session-based model. |
| wolfHAL/spi/spi.h | Adds whal_Spi_ComCfg and StartCom/EndCom; removes Send/Recv. |
| wolfHAL/block/sdhc_spi.h | Declares the SDHC/SDXC-over-SPI block driver interface. |
| wolfHAL/block/block.h | Defines the generic block device abstraction and vtable. |
| src/spi/stm32wb_spi.c | Implements StartCom/EndCom and updated SendRecv behavior. |
| src/spi/spi.c | Updates SPI dispatch functions to new vtable/API. |
| src/block/block.c | Implements block dispatch wrappers (Init/Read/Write/Erase). |
| src/block/sdhc_spi.c | Adds SD card over SPI implementation for the block interface. |
| docs/writing_a_driver.md | Documents the updated SPI model and new Block driver category. |
| docs/getting_started.md | Adds block dispatch source file to the getting-started list. |
| boards/stm32wb55xx_nucleo/board.h | Adds SPI + timeout externs and pin enum expansion. |
| boards/stm32wb55xx_nucleo/board.c | Adds SPI init/deinit and initializes peripheral block devices. |
| boards/stm32wb55xx_nucleo/Makefile.inc | Adds block tests, SPI/block sources, peripheral include. |
| boards/pic32cz_curiosity_ultra/board.c | Minor clock-count naming update for consistency. |
| boards/pic32cz_curiosity_ultra/Makefile.inc | Adds block dispatch source to board build. |
| boards/peripheral/peripheral.h | Introduces peripheral block-device test configuration struct. |
| boards/peripheral/peripheral.c | Defines g_peripheralBlock[] list (with sentinel). |
| boards/peripheral/block/sdhc_spi_sdcard32gb.h | Declares a concrete SD-over-SPI block device instance. |
| boards/peripheral/block/sdhc_spi_sdcard32gb.c | Defines the SD-over-SPI block device instance/config. |
| boards/peripheral/Makefile.inc | Adds optional peripheral SD-over-SPI sources and define. |
| tests/test.h | Improves assertion diagnostics (prints file:line, evaluates args once). |
| tests/main.c | Adds optional SPI loopback + block test suite dispatch. |
| tests/Makefile | Adds peripheral include path for tests build. |
| tests/core/Makefile | Adds block dispatch source to core tests build. |
| tests/core/test_dispatch.c | Adds SPI and Block dispatch tests with mock drivers. |
| tests/spi_loopback/test_spi_loopback.c | Adds a generic hardware SPI loopback test. |
| tests/flash/test_flash.c | Refactors flash tests to run via configured device/address. |
| tests/block/test_block.c | Adds block-device hardware tests (read/write/erase, multiblock). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
b6f38ca to
da6025c
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 30 out of 30 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…-SPI driver. Rework SPI API.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 30 out of 30 changed files in this pull request and generated 6 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| err = SdhcSpi_CsAssert(cfg); | ||
| if (err) | ||
| goto cleanup; | ||
| err = SdhcSpi_SendCmd(cfg, CMD0, 0x00000000, 0x95); | ||
| if (!err) | ||
| err = SdhcSpi_RecvResp(cfg, &r1); | ||
| SdhcSpi_CsDeassert(cfg); | ||
| if (err) | ||
| goto cleanup; |
There was a problem hiding this comment.
SdhcSpi_CsDeassert() returns a whal_Error but its result is ignored here. If deasserting CS fails (GPIO error), initialization will proceed as if the bus is deselected, which can break subsequent command framing. Consider checking/propagating the return value (and potentially prioritizing it if no earlier error is present).
| err = SdhcSpi_CsAssert(cfg); | |
| if (err) | |
| goto cleanup; | |
| err = SdhcSpi_SendCmd(cfg, CMD0, 0x00000000, 0x95); | |
| if (!err) | |
| err = SdhcSpi_RecvResp(cfg, &r1); | |
| SdhcSpi_CsDeassert(cfg); | |
| if (err) | |
| goto cleanup; | |
| { | |
| whal_Error csErr; | |
| err = SdhcSpi_CsAssert(cfg); | |
| if (err) | |
| goto cleanup; | |
| err = SdhcSpi_SendCmd(cfg, CMD0, 0x00000000, 0x95); | |
| if (!err) | |
| err = SdhcSpi_RecvResp(cfg, &r1); | |
| csErr = SdhcSpi_CsDeassert(cfg); | |
| if (!err && csErr) | |
| err = csErr; | |
| } | |
| if (err) | |
| goto cleanup; |
| /* Fill block 0 with pattern A, block 1 with pattern B */ | ||
| for (i = 0; i < g_testBlockSz; i++) | ||
| g_testBuf[i] = patternA[i % sizeof(patternA)]; | ||
| for (i = 0; i < g_testBlockSz; i++) | ||
| g_testBuf[g_testBlockSz + i] = patternB[i % sizeof(patternB)]; | ||
|
|
There was a problem hiding this comment.
The test writes/reads up to 2 blocks via g_testBuf (e.g., indexes up to g_testBlockSz2), but it never validates that the configured buffer is large enough. Since the peripheral config also provides blockBufSz, consider asserting blockBufSz >= blockSz2 (or at least >= blockSz*blockCount used by the test) before using the buffer to avoid out-of-bounds accesses when a new peripheral is added.
| /* DS field encodes word size as (bits - 1); valid range 4-16 */ | ||
| if (comCfg->wordSz < 4 || comCfg->wordSz > 16) { | ||
| return WHAL_EINVAL; | ||
| } | ||
|
|
||
| if (!spiDev || !spiDev->cfg || !spiComCfg) { | ||
| if (comCfg->mode > 3 || comCfg->dataLines != 1 || comCfg->freq == 0) { | ||
| return WHAL_EINVAL; | ||
| } |
There was a problem hiding this comment.
whal_Stm32wbSpi_StartCom accepts word sizes 4–16 and programs DS/FRXTH accordingly, but whal_Stm32wbSpi_SendRecv always performs 8-bit DR accesses and counts in bytes. If StartCom is called with wordSz > 8 this will transfer/consume the wrong number of bits and may not properly drain RX (potentially causing overruns). Either restrict this driver to wordSz == 8 (reject others) or implement word-size-aware transfers (16-bit DR access, lengths in words, etc.).
| if (!spiDev || !spiDev->driver || !spiDev->driver->SendRecv) { | ||
| return WHAL_EINVAL; | ||
| } | ||
|
|
There was a problem hiding this comment.
The SPI API contract (spi.h) requires txLen==0 when tx==NULL and rxLen==0 when rx==NULL. whal_Spi_SendRecv currently forwards parameters without enforcing this, so invalid combinations can reach platform drivers that may not validate and could misbehave. Consider adding common argument validation here (e.g., reject (!tx && txLen) and (!rx && rxLen)) to make behavior consistent across SPI drivers.
| if ((!tx && txLen) || (!rx && rxLen)) { | |
| return WHAL_EINVAL; | |
| } |
| #define WHAL_ASSERT_EQ(a, b) \ | ||
| do { \ | ||
| if ((a) != (b)) { \ | ||
| whal_Test_Printf(" ASSERT_EQ failed at line %d\n", __LINE__); \ | ||
| whal_Test_Printf(" got: %d, expected: %d\n", \ | ||
| (int)(a), (int)(b)); \ | ||
| g_whalTestCurFailed = 1; \ | ||
| int _a = (int)(a); \ | ||
| int _b = (int)(b); \ | ||
| if (_a != _b) { \ | ||
| whal_Test_Printf(" ASSERT_EQ failed at %s:%d\n", \ | ||
| __FILE__, __LINE__); \ | ||
| whal_Test_Printf(" got: %d, expected: %d\n", \ | ||
| _a, _b); \ |
There was a problem hiding this comment.
WHAL_ASSERT_EQ now casts both operands to int and prints them with %d. This can truncate or sign-change values (e.g., size_t/uint32_t/uintptr_t on 64-bit core tests) and can hide differences that exist only in the upper bits. Consider using a wider integral type for capture/printing (e.g., int64_t/uint64_t with corresponding format support in whal_Test_Printf) or providing separate ASSERTs for signed/unsigned/pointer-sized values.
| SdhcSpi_SendCmd(cfg, CMD12, 0x00000000, 0x01); | ||
| SdhcSpi_RecvResp(cfg, &r1); | ||
| whal_Spi_SendRecv(cfg->spiDev, &dummy, 1, NULL, 0); | ||
| SdhcSpi_WaitReady(cfg); |
There was a problem hiding this comment.
Multi-block stop/cleanup ignores return values from CMD12/response reads, dummy clocks, and WaitReady. If any of these fail, the card can remain in a busy or multi-block state but the function may still return WHAL_SUCCESS (or only the earlier error). Consider capturing and propagating errors from these calls when err is still WHAL_SUCCESS, and ensuring the card is reliably returned to idle state before deasserting CS.
| SdhcSpi_SendCmd(cfg, CMD12, 0x00000000, 0x01); | |
| SdhcSpi_RecvResp(cfg, &r1); | |
| whal_Spi_SendRecv(cfg->spiDev, &dummy, 1, NULL, 0); | |
| SdhcSpi_WaitReady(cfg); | |
| whal_Error stopErr = WHAL_SUCCESS; | |
| if (!err) { | |
| stopErr = SdhcSpi_SendCmd(cfg, CMD12, 0x00000000, 0x01); | |
| if (!stopErr) | |
| stopErr = SdhcSpi_RecvResp(cfg, &r1); | |
| if (!stopErr) | |
| stopErr = whal_Spi_SendRecv(cfg->spiDev, &dummy, 1, NULL, 0); | |
| if (!stopErr) | |
| stopErr = SdhcSpi_WaitReady(cfg); | |
| if (!err && stopErr) | |
| err = stopErr; | |
| } else { | |
| /* Best-effort attempt to stop multi-block read even after error */ | |
| (void)SdhcSpi_SendCmd(cfg, CMD12, 0x00000000, 0x01); | |
| (void)SdhcSpi_RecvResp(cfg, &r1); | |
| (void)whal_Spi_SendRecv(cfg->spiDev, &dummy, 1, NULL, 0); | |
| (void)SdhcSpi_WaitReady(cfg); | |
| } |
No description provided.