stmqspi: OCTOSPI DR 8-bit access is unreliable on STM32H7R7/XSPI, and unconditional ABORT may wedge BUSY
Nobody has claimed this yet.
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 68/100
Research direction
Start in src/flash/nor/stmqspi.c, focusing on stmqspi_dr_read8(), stmqspi_dr_write8(), stmqspi_abort(), and stmqspi_auto_probe(). Review the existing QSPI/OCTOSPI status and data-register paths, then build OpenOCD and run the provided STM32H7R7 configuration. Done means the listed DR operations and idle-abort behavior work on the target without regressing non-OCTOSPI handling.
Written by the indexing model from the issue text.
Description
Environment
OpenOCD version tested:
commit 0a084c29345576911fb8d10fcbd158d0361b6186 (HEAD -> openocd-cubeide-r7, tag: openocd-cubeide-v1.19.0, origin/openocd-cubeide-r7, origin/HEAD)
Author: Antonio Borneo <borneo.antonio@gmail.com>
Date: Wed Nov 5 14:45:29 2025 +0100
Target:
- STM32H7R7
- XSPI / OCTOSPI external NOR flash
- Board: RT-Thread STM32H7R7 minimal system
- Probe: ST-LINK
- Transport:
dapdirect_swd
I found two problems in src/flash/nor/stmqspi.c on this target.
After applying the patch below, the board works correctly in my setup.
Problem 1: 8-bit access to OCTOSPI_DR is unreliable
Observed behavior
On STM32H7R7 / XSPI, direct 8-bit accesses to OCTOSPI_DR (offset 0x50) are not reliable in my setup.
The current code uses target_read_u8() / target_write_u8() on SPI_DR.
This causes incorrect behavior when reading status / SFDP / flash ID or when exchanging command data through DR on OCTOSPI.
I also observed a state where a 1-byte read completed with TCF=1 while FTF=0, so waiting only for FTF is not sufficient before draining DR.
Proposed fix
Introduce wrappers for DR byte access:
stmqspi_dr_read8()stmqspi_dr_write8()
Behavior:
- For non-OCTOSPI: keep the original
target_read_u8()/target_write_u8() - For OCTOSPI: use
target_read_u32()/target_write_u32()onOCTOSPI_DR, and only consume / drive the low 8 bits - Before read, wait until either
FTForTCFis set
This fixes the problem on my STM32H7R7 board.
Problem 2: unconditional ABORT can wedge BUSY when controller is already idle
Observed behavior
In some STM32H7R7 states, calling stmqspi_abort() while the controller is already idle can cause BUSY to get stuck.
Current behavior:
stmqspi_abort()always writesABORT
This appears unsafe on H7RS in at least some OCTOSPI states.
Proposed fix
In stmqspi_abort():
- read
SRfirst (QSPI_SRorOCTOSPI_SR) - if
BUSY == 0, returnERROR_OKimmediately - only assert
ABORTwhenBUSY == 1
This avoids wedging the controller in my setup.
Patch summary
Main changes in src/flash/nor/stmqspi.c:
-
Wrap DR byte accesses:
stmqspi_dr_read8()stmqspi_dr_write8()
-
For OCTOSPI DR accesses:
- use 32-bit target accesses instead of byte accesses
- read/write only the low 8 bits
-
Before DR read:
- wait for
FTForTCF
- wait for
-
In
stmqspi_abort():- do not force
ABORTwhenBUSY == 0
- do not force
-
Minor correctness fix:
stmqspi_auto_probe()should returnstmqspi_probe(bank)result instead of always returningERROR_OK
Diff
diff --git a/src/flash/nor/stmqspi.c b/src/flash/nor/stmqspi.c
index a1e1d3411..e5fda2a31 100644
--- a/src/flash/nor/stmqspi.c
+++ b/src/flash/nor/stmqspi.c
@@ -173,6 +173,64 @@ struct stmqspi_flash_bank {
unsigned int sfdp_dummy2; /* number of dummy bytes for SFDP read for flash2 */
};
+/*
+ * Some probe/targets are unreliable for byte accesses on OCTOSPI_DR (offset 0x50).
+ * Use 32-bit accesses for OCTOSPI and extract/inject low byte.
+ */
+static inline int stmqspi_dr_read8(struct flash_bank *bank, uint8_t *data)
+{
+ struct target *target = bank->target;
+ const struct stmqspi_flash_bank *stmqspi_info = bank->driver_priv;
+ const uint32_t io_base = stmqspi_info->io_base;
+ long long endtime = timeval_ms() + SPI_CMD_TIMEOUT;
+ bool ready = false;
+ uint32_t last_sr = 0;
+
+ /* Wait until RX FIFO has at least one byte. */
+ do {
+ uint32_t sr;
+ int retval = target_read_u32(target, io_base + (stmqspi_info->octo ? OCTOSPI_SR : QSPI_SR), &sr);
+ if (retval != ERROR_OK)
+ return retval;
+ last_sr = sr;
+ /* On some STM32H7R/XSPI states, 1-byte reads complete with TCF set
+ * while FTF stays clear. Accept either flag before draining DR. */
+ if (sr & (BIT(SPI_FTF) | BIT(SPI_TCF))) {
+ ready = true;
+ break;
+ }
+ alive_sleep(1);
+ } while (timeval_ms() < endtime);
+
+ if (!ready) {
+ LOG_DEBUG("timeout waiting DR data (SR=0x%08" PRIx32 ")", last_sr);
+ return ERROR_TIMEOUT_REACHED;
+ }
+
+ if (stmqspi_info->octo) {
+ uint32_t v;
+ int retval = target_read_u32(target, io_base + OCTOSPI_DR, &v);
+ if (retval != ERROR_OK)
+ return retval;
+ *data = (uint8_t)(v & 0xFFU);
+ return ERROR_OK;
+ }
+
+ return target_read_u8(target, io_base + QSPI_DR, data);
+}
+
+static inline int stmqspi_dr_write8(struct flash_bank *bank, uint8_t data)
+{
+ struct target *target = bank->target;
+ const struct stmqspi_flash_bank *stmqspi_info = bank->driver_priv;
+ const uint32_t io_base = stmqspi_info->io_base;
+
+ if (stmqspi_info->octo)
+ return target_write_u32(target, io_base + OCTOSPI_DR, (uint32_t)data);
+
+ return target_write_u8(target, io_base + QSPI_DR, data);
+}
+
static inline int octospi_cmd(struct flash_bank *bank, uint32_t mode,
uint32_t ccr, uint32_t ir)
{
@@ -264,12 +322,21 @@ static int stmqspi_abort(struct flash_bank *bank)
const struct stmqspi_flash_bank *stmqspi_info = bank->driver_priv;
const uint32_t io_base = stmqspi_info->io_base;
uint32_t cr;
+ uint32_t sr;
+ int retval;
- int retval = target_read_u32(target, io_base + SPI_CR, &cr);
+ retval = target_read_u32(target, io_base + SPI_CR, &cr);
if (retval != ERROR_OK)
cr = 0;
+ /* H7RS: avoid asserting ABORT while controller is already idle.
+ * Forcing ABORT on idle can wedge BUSY in some states. */
+ retval = target_read_u32(target,
+ io_base + (stmqspi_info->octo ? OCTOSPI_SR : QSPI_SR), &sr);
+ if (retval == ERROR_OK && (sr & BIT(SPI_BUSY)) == 0)
+ return ERROR_OK;
+
return target_write_u32(target, io_base + SPI_CR, cr | BIT(SPI_ABORT));
}
@@ -370,7 +437,7 @@ static int read_status_reg(struct flash_bank *bank, uint16_t *status)
if ((stmqspi_info->saved_cr & (BIT(SPI_DUAL_FLASH) | BIT(SPI_FSEL_FLASH)))
!= BIT(SPI_FSEL_FLASH)) {
/* get status of flash 1 in dual mode or flash 1 only mode */
- retval = target_read_u8(target, io_base + SPI_DR, &data);
+ retval = stmqspi_dr_read8(bank, &data);
if (retval != ERROR_OK)
goto err;
*status |= data;
@@ -378,7 +445,7 @@ static int read_status_reg(struct flash_bank *bank, uint16_t *status)
if ((stmqspi_info->saved_cr & (BIT(SPI_DUAL_FLASH) | BIT(SPI_FSEL_FLASH))) != 0) {
/* get status of flash 2 in dual mode or flash 2 only mode */
- retval = target_read_u8(target, io_base + SPI_DR, &data);
+ retval = stmqspi_dr_read8(bank, &data);
if (retval != ERROR_OK)
goto err;
*status |= ((uint16_t)data) << 8;
@@ -862,7 +929,7 @@ COMMAND_HANDLER(stmqspi_handle_cmd)
for (count = 3; count < CMD_ARGC; count++) {
COMMAND_PARSE_NUMBER(u8, CMD_ARGV[count], data);
snprintf(temp, sizeof(temp), "%02" PRIx8 " ", data);
- retval = target_write_u8(target, io_base + SPI_DR, data);
+ retval = stmqspi_dr_write8(bank, data);
if (retval != ERROR_OK)
goto err;
strncat(output, temp, sizeof(output) - strlen(output) - 1);
@@ -905,7 +972,7 @@ COMMAND_HANDLER(stmqspi_handle_cmd)
/* read response bytes */
for ( ; num_read > 0; num_read--) {
- retval = target_read_u8(target, io_base + SPI_DR, &data);
+ retval = stmqspi_dr_read8(bank, &data);
if (retval != ERROR_OK)
goto err;
snprintf(temp, sizeof(temp), "%02" PRIx8 " ", data);
@@ -1769,12 +1836,12 @@ static int find_sfdp_dummy(struct flash_bank *bank, int len)
for (count = 0 ; count < max_bytes; count++) {
if ((dual != 0) && !flash1) {
/* discard even byte in dual flash-mode if flash2 */
- retval = target_read_u8(target, io_base + SPI_DR, &data);
+ retval = stmqspi_dr_read8(bank, &data);
if (retval != ERROR_OK)
goto err;
}
- retval = target_read_u8(target, io_base + SPI_DR, &data);
+ retval = stmqspi_dr_read8(bank, &data);
if (retval != ERROR_OK)
goto err;
@@ -1790,7 +1857,7 @@ static int find_sfdp_dummy(struct flash_bank *bank, int len)
if ((dual != 0) && flash1) {
/* discard odd byte in dual flash-mode if flash1 */
- retval = target_read_u8(target, io_base + SPI_DR, &data);
+ retval = stmqspi_dr_read8(bank, &data);
if (retval != ERROR_OK)
goto err;
}
@@ -1888,7 +1955,7 @@ static int read_sfdp_block(struct flash_bank *bank, uint32_t addr,
/* dummy clocks */
for (count = *dummy << dual; count > 0; --count) {
- retval = target_read_u8(target, io_base + SPI_DR, (uint8_t *)buffer);
+ retval = stmqspi_dr_read8(bank, (uint8_t *)buffer);
if (retval != ERROR_OK)
goto err;
}
@@ -2016,7 +2083,7 @@ static int read_flash_id(struct flash_bank *bank, uint32_t *id1, uint32_t *id2)
for (len1 = 0, len2 = 0; count > 0; --count) {
if ((stmqspi_info->saved_cr & (BIT(SPI_DUAL_FLASH) |
BIT(SPI_FSEL_FLASH))) != BIT(SPI_FSEL_FLASH)) {
- retval = target_read_u8(target, io_base + SPI_DR, &byte);
+ retval = stmqspi_dr_read8(bank, &byte);
if (retval != ERROR_OK)
goto err;
/* collect 3 bytes without continuation codes */
@@ -2027,7 +2094,7 @@ static int read_flash_id(struct flash_bank *bank, uint32_t *id1, uint32_t *id2)
}
if ((stmqspi_info->saved_cr & (BIT(SPI_DUAL_FLASH) |
BIT(SPI_FSEL_FLASH))) != 0) {
- retval = target_read_u8(target, io_base + SPI_DR, &byte);
+ retval = stmqspi_dr_read8(bank, &byte);
if (retval != ERROR_OK)
goto err;
/* collect 3 bytes without continuation codes */
@@ -2369,8 +2436,8 @@ static int stmqspi_auto_probe(struct flash_bank *bank)
if (stmqspi_info->probed)
return ERROR_OK;
- stmqspi_probe(bank);
- return ERROR_OK;
+
+ return stmqspi_probe(bank);
}
rt-thread stm32h7r board config used for validation
source [find interface/stlink-dap.cfg]
transport select dapdirect_swd
set CHIPNAME stm32h7r7
set WORKAREASIZE 0x20000
source [find target/stm32h7rx.cfg]
flash bank ${CHIPNAME}.xspi2 stmqspi 0x70000000 0 0 0 ${CHIPNAME}.cpu0 0x5200A000
proc qspi_init { } {
mmw 0x5802480c 0x00008000 0x0
mmw 0x58024534 0x00005000 0x0
mmw 0x58024540 0x00002000 0x0
mmw 0x58024554 0x00000002 0x0
mmw 0x58000510 0x00040010 0x0
mww 0x5200B400 0x00000010
mmw 0x58023400 0x00AA2AAA 0x00FF3FFF
mmw 0x58023408 0x00FF3FFF 0x00FF3FFF
mmw 0x5802340C 0x00000000 0x00FF3FFF
mmw 0x58023420 0x09999999 0x0FFFFFFF
mmw 0x58023424 0x00009999 0x0000FFFF
mww 0x5200A130 0x00001000
mww 0x5200A008 0x01190200
mww 0x5200A00C 0x00000004
mww 0x5200A108 0x00000000
mww 0x5200A100 0x01003101
mww 0x5200A110 0x00000013
mww 0x5200A000 0x30400009
flash probe 1
stmqspi set 1 w35t51nwtbie 0x4000000 0x100 0x13 0x0c 0x12 0x60 0x10000 0xdc
}
${CHIPNAME}.cpu0 configure -event reset-init {
qspi_init
}
Notes
-
The patch is validated on my STM32H7R7 + XSPI setup.
-
I cannot guarantee yet that all STM32 OCTOSPI variants behave the same way.
-
But at least on this target:
- byte accesses to
OCTOSPI_DRare problematic - unconditional
ABORTwhile idle is unsafe
- byte accesses to
-
The patch keeps the original path for non-OCTOSPI users.
If needed, I can also help split this into separate commits / MRs.
- Dominant language
- C
- Stars
- 202
- Forks
- 57
- PR merge metrics
- No merged PRs in 30d
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from STMicroelectronics/OpenOCD
-
Difficulty 4/5 3-5 days Newbie friendliness 55/100
-
Difficulty 3/5 1-2 days Newbie friendliness 67/100
-
Difficulty 4/5 3-5 days Newbie friendliness 45/100
-
bug
STMicroelectronics/OpenOCD#44 · 1 comment · 1 assignee ·
-
Difficulty 5/5 Over a week Newbie friendliness 25/100
STMicroelectronics/OpenOCD#42 · 9 comments · 5 reactions ·
All issues in STMicroelectronics/OpenOCD
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
BasedHardware/omi#15662 · 1 comment ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
level/task module/gcp type/bug
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
-
Difficulty 1/5 Under an hour Newbie friendliness 86/100
hapostgres/pg_auto_failover#1190 ·