Skip to content

feat(spi): Add support for SPI slave interface - #125

Open
vojt444 wants to merge 1 commit into
masterfrom
feat/spi-slave-interface
Open

feat(spi): Add support for SPI slave interface#125
vojt444 wants to merge 1 commit into
masterfrom
feat/spi-slave-interface

Conversation

@vojt444

@vojt444 vojt444 commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Description

  • Adds a SPI slave (download-boot) transport to esp-stub-lib, so a stub can talk to the host over the ESP SPI-slave download interface in addition to the existing UART, USB-OTG, USB-Serial/JTAG, and SDIO paths. The public API in include/esp-stub-lib/spi.h exposes the usual transport surface: stub_lib_spi_is_active, _init, _take_rx_frame, _rearm, and _tx_frame.
  • Structures the driver as a thin, target-agnostic wrapper (src/spi.c) that validates arguments (non-NULL, non-zero length, 4-byte DMA alignment, TX size limit) and delegates to weak per-target hooks. Each target drives the ROM's shared W0..W3 handshake registers (VER / RXSTA / TXSTA / CMD) and offloads the actual receive/transmit DMA to ROM SPI-slave helper routines. The design is fully polled (no interrupts): the command handshake is serviced by polling, receive completion is detected via the DMA-done flag, and a host reboot request is handled through the CMD register.
  • Implements the transport for esp32c2, esp32c3, esp32p4, esp32s2, and esp32s3. Because the ROM SPI-slave helpers are not part of the stock ROM API table, their addresses are PROVIDEd per target in *.rom.extra.ld. Targets without an implementation keep the common weak stubs, which simply report SPI as inactive.
  • Keeps the shared protocol constants in a private header that must stay in lockstep with the host-side driver
    (esp-serial-flasher/src/protocol_spi.c), wires src/spi.c into the core, common, and per-target CMake builds, and adds a test_spi smoke path to the example stub. Most of the +6585 line count is generated SPI register headers (spi_reg.h) for c2/c3/p4.

Speed measurement

image

Related

Testing


Checklist

Before submitting a Pull Request, please ensure the following:

  • 🚨 This PR does not introduce breaking changes.
  • All CI checks (GH Actions) pass.
  • Documentation is updated as needed.
  • Tests are updated or added as necessary.
  • Code is well-commented, especially in complex areas.
  • Git history is clean — commits are squashed to the minimum necessary.

@vojt444
vojt444 requested a review from Dzarda7 July 17, 2026 12:00
@vojt444
vojt444 marked this pull request as draft July 20, 2026 07:25
@vojt444
vojt444 force-pushed the feat/spi-slave-interface branch 3 times, most recently from 9a90f16 to 9c63500 Compare July 24, 2026 11:20
@vojt444
vojt444 marked this pull request as ready for review July 24, 2026 12:42
Comment thread src/target/esp32p4/src/spi.c Outdated
@Dzarda7

Dzarda7 commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

It is hard for me to decide if this feature is worth the additional 1kB of stub, but when we make it ECO version independent it might be nice for parallel flashing so I believe we can proceed. If stub size becomes an issue we can build stubs with only one interface.

If we do not need spi_slave_rom_init_hw, I would just use our DMA descriptor and probably get rid of the ROM functions. If we decide to add this to the stub (and now I am for it), I believe we should not support it only on one ECO revision if possible.

This is just the high level review of the functionality, when we decide on this, I will check the small things, but overall great work, thanks. WDYT about this?

BTW we might consider something like DRAM_DMA_ALIGNED_ATTR and DMA_ATTR or something like that to avoid writing aligned(4) aligned(64) with #ifdefs all over the code when P4 now requires cache alignment.

@vojt444
vojt444 force-pushed the feat/spi-slave-interface branch from 9c63500 to e9bdf63 Compare July 29, 2026 15:05

@Dzarda7 Dzarda7 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for the effort, left a few comments. I would also suggest renaming spi to something like slave_spi (I should have done it for sdio too, but for spi it is even more confusing as we use it for flashing).

One extra thing, I meant by DRAM_DMA_ALIGNED_ATTR and DRAM_ATTR is something like DMA_ALIGNED_ATTR __attribute__((aligned(4))) for most and L1_CACHE_LINE_SIZE for the chips with L1 cache. If you do not need it here, I think you will definitely need it in esp-flasher-stub for the buffer definition without #ifdefs

@erhankur could you please help explain proper DMA aligning? I found that not only esp32p4 might need L1 cache alignment https://github.com/search?q=repo%3Aespressif%2Fesp-idf%20CACHE_L1_CACHE_LINE_SIZE&type=code

* The ROM DMA loaders (spi_slave_rom_txdma_load / _rxdma_load) reject any
* length above LLDESC_SPI_MAX_BUFFER_SIZE (4096 - 4), so this is the true
* single-descriptor limit, not the 12-bit descriptor field max (0xFFF). */
#define SPI_DMA_DESC_MAX_LEN (4096U - 4U)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this still relevant when we have our own descriptor? Isn't it 16KB?

* If the receive DMA is already armed, this is a no-op. Call after freeing or
* claiming a frame buffer to provide the next DMA destination.
*
* @param buf Writable 4-byte-aligned receive buffer.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does not alignment depend on the cache also? For P4 I believe it might be 64 bytes but I might be wrong here.

Comment on lines +67 to +70
* @param data Pointer to frame bytes (must be 4-byte aligned).
* @param len Number of frame bytes, up to the DMA descriptor limit (4092).
* @return STUB_LIB_OK on success, STUB_LIB_ERR_* on failure.
*/

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, also with descriptor limit

Comment on lines +2 to +6
* Manually added ROM symbols for esp32c2
* These symbols are not in the auto-generated ROM linker scripts
* but are needed for ECO version handling
***************************************/

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this file is not necessary now right?

Comment on lines +167 to +170
break;
case SPI_SLV_CMD_REBOOT:
esp_rom_software_reset_cpu(0);
break;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we plan to use this kind of reset? If not, maybe nice place to save some IRAM.

Comment on lines +70 to +89
int stub_target_spi_rearm(uint8_t *buf, size_t max_size)
{
if (s_rx_armed) {
return STUB_LIB_OK;
}
if (buf == NULL || max_size == 0) {
return STUB_LIB_ERR_INVALID_ARG;
}

WRITE_PERI_REG(SPI_DMA_INT_CLR_REG, SPI_SLV_RX_DONE);
spi_slave_rom_rxdma_load(SPI_SLV_HW, buf, (uint32_t)max_size);

s_seq_rx ^= SPI_SLV_STA_TOGGLE;
uint32_t rxsta = s_seq_rx | ((uint32_t)max_size << SPI_SLV_STA_LEN_SHIFT);
s_seq_rx &= ~SPI_SLV_STA_INIT;
WRITE_PERI_REG(SPI_SLV_REG_RXSTA, rxsta);

s_rx_armed = true;
return STUB_LIB_OK;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't S2 now then limited to the ROM descriptor number instead of the one defined for others?

Comment thread src/spi.c

int stub_lib_spi_rearm(uint8_t *buf, size_t max_size)
{
if (buf == NULL || max_size == 0 || !IS_ALIGNED((uintptr_t)buf, SPI_DMA_ALIGN)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is SPI_DMA_ALIGN even for P4? I am not really sure about it, I think it might be 64 bytes.

Comment on lines +114 to +116
uint32_t aligned_len = (chunk + 3U) & ~3U; /* RX needs word-aligned length */
dmadesc_rx[desc_idx].size = aligned_len & LLDESC_SPI_SIZE_MASK;
dmadesc_rx[desc_idx].length = aligned_len & LLDESC_SPI_SIZE_MASK;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this potential buffer overflow? Anyway this should not be necessary because the upper function does if (buf == NULL || max_size == 0 || !IS_ALIGNED((uintptr_t)buf, SPI_DMA_ALIGN))

uint32_t desc_idx = 0;
while (remaining) {
uint32_t chunk = (remaining > LLDESC_SPI_MAX_BUFFER_SIZE) ? LLDESC_SPI_MAX_BUFFER_SIZE : remaining;
uint32_t aligned_len = (chunk + 3U) & ~3U; /* 4-byte DMA granularity */

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I am still not sure about the 4 byte alignment, if it does not have to be 64.

@Dzarda7
Dzarda7 requested a review from erhankur August 4, 2026 22:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants