STM32H562 GPDMA: leftover CxCR.SUSP after an abort on a non-active channel permanently blocks SPI DMA
Environment
- Device: STM32H562ZI
- Reference manual: RM0481 Rev 5
- HAL: STM32Cube H5 HAL Driver V1.5.0
- Peripherals: GPDMA used for SPI slave Tx/Rx. SPI in slave mode, normal (non-circular) transfers, no linked-list.
Summary
On our SPI slave DMA path, the DMA channel becomes permanently stuck after an SPI error, and SPI communication never recovers until the device is power-cycled.
The cause is that `GPDMA_CxCR.SUSP` gets written to a channel that has **already completed its transfer and stopped** (EN=0, IDLEF=1). In that state `GPDMA_CxSR.SUSPF` is never set, so `GPDMA_CxCR.RESET` is never written and **SUSP stays set**. Setting `EN=1` afterwards suspends the channel immediately after start, and the driver can no longer escape that state.
Observed register sequence (Tx channel, real measurements)
```
1) Block transfer completes. Hardware clears EN.
CxCR = 0x00805D00 (EN=0, SUSP=0, SUSPIE=0)
CxSR = 0x00000001 (IDLEF=1)
2) An SPI error occurs right after this point.
SPI ErrorCode = 0x84 (UDR + OVR)
registers unchanged - the channel is stopped
3) HAL_SPI_IRQHandler() error path calls HAL_DMA_Abort_IT(),
which writes SUSP and SUSPIE.
CxCR = 0x00807D04 (EN=0, SUSP=1, SUSPIE=1)
SUSPF is NEVER set <-- RESET is therefore never written
4) EN is set to 1 to arm the next transfer.
CxCR = 0x00807D05 (EN=1, SUSP=1)
the channel is suspended immediately after start
5) The suspend interrupt fires and the HAL state becomes
HAL_DMA_STATE_SUSPEND.
```
From here the driver cannot recover:
SUSP is left set
-> setting EN=1 suspends immediately, HAL state becomes SUSPEND
-> HAL_DMA_Abort() only accepts State == BUSY, so it is rejected
with HAL_DMA_ERROR_NO_XFER
-> CxCR.RESET is never written
-> SUSP is never cleared (loop)
Note that when the same abort runs while the channel is genuinely active (EN=1), it completes normally and `CxCR` returns cleanly to `0x00805D00`. The deciding factor is not which abort function is used, but whether the channel was active at the moment SUSP was written.
What we understand from RM0481
The manual seems to restrict SUSP to active channels:
Section 16.8.9, GPDMA_CxCR, SUSP bit:
> Software must write 1 in order to suspend **an active channel** (channel with an ongoing GPDMA transfer over its master ports)
Section 16.4.4 "GPDMA channel abort and restart" (p.690-691, Figure 78):
> the software can abort, on its own, **a still active channel** with the following sequence
So step 3 above writes SUSP to a non-active channel, which we understand to be outside what the manual specifies.
---
Q1. Is the immediate suspension at step 4 the intended behavior?
When `EN` is set to 1 while `SUSP=1` is still set, the channel is suspended right after start (observed `CxCR = 0x00807D05`, then SUSPF=1). Is this by design?
---
Q2. Is our workaround correct practice?
When we detect a leftover SUSP, we call `HAL_DMA_Init()` to recover the channel. Internally that calls `__HAL_DMA_DISABLE()`, which writes **SUSP and RESET together** and then waits for EN to clear:
#define __HAL_DMA_DISABLE(__HANDLE__) \
((__HANDLE__)->Instance->CCR |= (DMA_CCR_SUSP | DMA_CCR_RESET))
This writes RESET without waiting for SUSPF, which the 16.4.4 sequence does not describe. We do it this way because the documented sequence cannot complete when SUSPF never arrives.
- Is this safe?
- Is it also safe when `EN` happens to be 1 at that moment?
In our test (88 cycles of firmware update + cold reboot) this recovery path triggered many times with zero failures and zero lockups, but we would like confirmation that it is correct practice rather than merely working.
If there is a better way to clear a leftover SUSP - for example writing `CxCR.SUSP=0` instead - we would like to know which is recommended.
---
Q3. Should HAL_DMA_Abort_IT() check whether the channel is still active?
`HAL_DMA_Abort_IT()` only checks the **HAL state**, not whether the hardware has already finished (EN=0 / IDLEF=1):
/* stm32h5xx_hal_dma.c V1.5.0 */
if (hdma->State != HAL_DMA_STATE_BUSY)
{
hdma->ErrorCode = HAL_DMA_ERROR_NO_XFER;
return HAL_ERROR;
}
else
{
hdma->State = HAL_DMA_STATE_ABORT;
hdma->Instance->CCR |= (DMA_CCR_SUSP | DMA_CCR_SUSPIE); /* written even if not active */
}
Since the DMA runs in normal mode, the **hardware clears EN on completion**, while the HAL state stays BUSY until the completion interrupt is serviced. An SPI error inside that window takes the `else` branch above and writes SUSP to a stopped channel.
The blocking `HAL_DMA_Abort()` has the same issue in a different form: it writes SUSP and then polls SUSPF with a 5 ms timeout (`HAL_TIMEOUT_DMA_ABORT`), so on a stopped channel it blocks for 5 ms, returns `HAL_ERROR`, and still leaves SUSP set.
- Is this a known issue?
- Should the driver check `CxSR.IDLEF` / `CxCR.EN` before writing SUSP?
- Is a fix planned?
---
Additional note on impact
A leftover SUSP also suppresses the error notification itself. The HAL SPI error callback is invoked from the DMA abort completion callback, so it is never called if the abort does not complete. In our instrumentation the SPI error callback was never invoked even once. From the application side, SPI simply goes silent, which made this problem difficult to diagnose. We believe other users with a similar configuration could be affected.
