From 23e56be7d2870772637849d6cf2a39044d3a4d92 Mon Sep 17 00:00:00 2001 From: Ahmad Alhmmori Date: Sat, 28 Feb 2026 16:27:14 +0000 Subject: [PATCH 1/2] hw/ssi/esp32_spi: fix RX bounds check and add dummy cycle handling The SPI TX/RX loop incorrectly used the transmitted byte value to guard buffer access instead of the loop index. This caused received bytes to be dropped when the payload value exceeded rx_bytes, breaking flash read operations. This resulted in NVS reads returning ESP_ERR_NVS_NOT_FOUND under ESP-IDF v5.5.3 when running on QEMU. Additionally, inject dummy cycles between address and data phases when SPI_USER.DUMMY is enabled to match flash fast-read behaviour expected by ESP-IDF. Reproducer: - ESP-IDF v5.5.3 - NVS set/commit/get sequence under QEMU - Fails before patch, succeeds after patch Signed-off-by: Ahmad Alhmmori --- hw/ssi/esp32_spi.c | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/hw/ssi/esp32_spi.c b/hw/ssi/esp32_spi.c index f47bfca4a822e..2c69d35fe1bd2 100644 --- a/hw/ssi/esp32_spi.c +++ b/hw/ssi/esp32_spi.c @@ -146,6 +146,7 @@ typedef struct Esp32SpiTransaction { uint32_t cmd; int addr_bytes; uint32_t addr; + int dummy_bytes; int data_tx_bytes; int data_rx_bytes; uint32_t* data; @@ -157,11 +158,11 @@ static void esp32_spi_txrx_buffer(Esp32SpiState *s, void *buf, int tx_bytes, int uint8_t *c_buf = (uint8_t*) buf; for (int i = 0; i < bytes; ++i) { uint8_t byte = 0; - if (byte < tx_bytes) { + if (i < tx_bytes) { memcpy(&byte, c_buf + i, 1); } uint32_t res = ssi_transfer(s->spi, byte); - if (byte < rx_bytes) { + if (i < rx_bytes) { memcpy(c_buf + i, &res, 1); } } @@ -179,6 +180,9 @@ static void esp32_spi_transaction(Esp32SpiState *s, Esp32SpiTransaction *t) esp32_spi_cs_set(s, 0); esp32_spi_txrx_buffer(s, &t->cmd, t->cmd_bytes, 0); esp32_spi_txrx_buffer(s, &t->addr, t->addr_bytes, 0); + for (int d = 0; d < t->dummy_bytes; d++) { + ssi_transfer(s->spi, 0); + } esp32_spi_txrx_buffer(s, t->data, t->data_tx_bytes, t->data_rx_bytes); esp32_spi_cs_set(s, 1); } @@ -285,6 +289,23 @@ static void esp32_spi_do_command(Esp32SpiState* s, uint32_t cmd_reg) t.addr_bytes = bitlen_to_bytes(FIELD_EX32(s->user1_reg, SPI_USER1, ADDR_BITLEN)); t.addr = bswap32(s->addr_reg); } + /* Inject 1 dummy byte for SPI flash fast-read commands when DUMMY + * phase is enabled. The m25p80 flash model expects this byte + * between address and data for DOR/QOR/FastRead etc. + * PSRAM commands handle dummy internally - don't inject for them. */ + if (FIELD_EX32(s->user_reg, SPI_USER, DUMMY)) { + switch (FIELD_EX32(s->user2_reg, SPI_USER2, COMMAND_VALUE)) { + case 0x0b: case 0x0c: /* Fast Read / Fast Read 4B */ + case 0x3b: case 0x3c: /* DOR / DOR 4B */ + case 0x6b: case 0x6c: /* QOR / QOR 4B */ + case 0xbb: case 0xbc: /* DIOR / DIOR 4B */ + case 0xeb: case 0xec: /* QIOR / QIOR 4B */ + t.dummy_bytes = 1; + break; + default: + break; + } + } if (FIELD_EX32(s->user_reg, SPI_USER, MOSI)) { t.data = &s->data_reg[0]; t.data_tx_bytes = bitlen_to_bytes(s->mosi_dlen_reg); From cb39a40269ea503a89d202071a22113c55d5df90 Mon Sep 17 00:00:00 2001 From: Ahmad Alhmmori Date: Sat, 28 Feb 2026 16:39:07 +0000 Subject: [PATCH 2/2] Align implementation with esp32c2_spic and esp32s3_spi files Signed-off-by: Ahmad Alhmmori --- hw/ssi/esp32_spi.c | 11 ++++++++--- hw/ssi/esp32c3_spi.c | 4 ++-- hw/ssi/esp32s3_spi.c | 4 ++-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/hw/ssi/esp32_spi.c b/hw/ssi/esp32_spi.c index 2c69d35fe1bd2..e40e9c9863838 100644 --- a/hw/ssi/esp32_spi.c +++ b/hw/ssi/esp32_spi.c @@ -175,14 +175,19 @@ static void esp32_spi_cs_set(Esp32SpiState *s, int value) } } +static void esp32_spi_dummy_cycles(Esp32SpiState *s, int dummy_bytes) +{ + for (int i = 0; i < dummy_bytes; i++) { + ssi_transfer(s->spi, 0); + } +} + static void esp32_spi_transaction(Esp32SpiState *s, Esp32SpiTransaction *t) { esp32_spi_cs_set(s, 0); esp32_spi_txrx_buffer(s, &t->cmd, t->cmd_bytes, 0); esp32_spi_txrx_buffer(s, &t->addr, t->addr_bytes, 0); - for (int d = 0; d < t->dummy_bytes; d++) { - ssi_transfer(s->spi, 0); - } + esp32_spi_dummy_cycles(s, t->dummy_bytes); esp32_spi_txrx_buffer(s, t->data, t->data_tx_bytes, t->data_rx_bytes); esp32_spi_cs_set(s, 1); } diff --git a/hw/ssi/esp32c3_spi.c b/hw/ssi/esp32c3_spi.c index e8bd33aad07c6..a5b6f3784a8bc 100644 --- a/hw/ssi/esp32c3_spi.c +++ b/hw/ssi/esp32c3_spi.c @@ -127,11 +127,11 @@ static void esp32c3_spi_txrx_buffer(ESP32C3SpiState *s, int bytes = MAX(tx_bytes, rx_bytes); for (int i = 0; i < bytes; ++i) { uint8_t byte = 0; - if (byte < tx_bytes) { + if (i < tx_bytes) { memcpy(&byte, tx + i, 1); } uint32_t res = ssi_transfer(s->spi, byte); - if (byte < rx_bytes) { + if (i < rx_bytes) { memcpy(rx + i, &res, 1); } } diff --git a/hw/ssi/esp32s3_spi.c b/hw/ssi/esp32s3_spi.c index 90250e7e254ce..735d2cf5ebb76 100644 --- a/hw/ssi/esp32s3_spi.c +++ b/hw/ssi/esp32s3_spi.c @@ -142,11 +142,11 @@ static void esp32s3_spi_txrx_buffer(ESP32S3SpiState *s, int bytes = MAX(tx_bytes, rx_bytes); for (int i = 0; i < bytes; ++i) { uint8_t byte = 0; - if (byte < tx_bytes) { + if (i < tx_bytes) { memcpy(&byte, tx + i, 1); } uint32_t res = ssi_transfer(s->spi, byte); - if (byte < rx_bytes) { + if (i < rx_bytes) { memcpy(rx + i, &res, 1); } }