From a4241b62ce5ed0c19dca6e9317698f9f84efbd0d Mon Sep 17 00:00:00 2001 From: GuEe-GUI <2991707448@qq.com> Date: Thu, 29 Oct 2026 16:14:46 +0800 Subject: [PATCH] [drivers][dma] Fix DMA boundaries and PL330/SDIO transfers - include the final fitting bitmap range when allocating from a DMA pool - match DMA pool ownership with exclusive region boundaries and allocation size - preserve mapping ownership when freeing cached and non-linear DMA memory - emit the correct PL330 DMALP loop-count byte - wait for DMAEND after DMAKILL before reusing a PL330 channel or releasing resources - honor the peripheral burst quirk when generating PL330 single-transfer programs - reject 32-bit DW SDIO IDMAC buffers and descriptor rings outside its DMA aperture - use one-shot timers for DW SDIO command and data timeouts Signed-off-by: GuEe-GUI <2991707448@qq.com> --- components/drivers/dma/dma-pl330.c | 68 ++++++++++++++++++++++++-- components/drivers/dma/dma_pool.c | 12 +++-- components/drivers/sdio/host/sdio-dw.c | 28 +++++++++-- 3 files changed, 98 insertions(+), 10 deletions(-) diff --git a/components/drivers/dma/dma-pl330.c b/components/drivers/dma/dma-pl330.c index 7a21d0e6e368..25e22d9ef532 100644 --- a/components/drivers/dma/dma-pl330.c +++ b/components/drivers/dma/dma-pl330.c @@ -264,6 +264,8 @@ /** @brief Maximum microcode program size in bytes (128 instructions) */ #define PL330_MICROCODE_SIZE 128 +/* Maximum time to drain DMAEND before reusing a channel. */ +#define PL330_STOP_TIMEOUT_US 1000 /** @brief Maximum number of AMBA IRQs */ #define AMBA_NR_IRQS 9 @@ -596,7 +598,7 @@ static int pl330_cmd_dmalp(rt_uint8_t *microcode, rt_uint8_t lc, rt_uint16_t loo * | iter[7:0] |0 0 1 0 0 0 lc 0 */ *microcode++ = PL330_CMD_DMALP | ((lc & 1) << 1); - *microcode |= loops - 1; + *microcode = loops - 1; LOG_D("DMALP %u (lc: %c)", loops - 1, lc ? '1' : '0'); @@ -714,6 +716,35 @@ static rt_uint32_t pl330_chan_id(struct pl330 *pl330, struct pl330_chan *pc) return pc - pl330->chans; } +static rt_err_t pl330_wait_stopped(struct pl330 *pl330, struct pl330_chan *pc) +{ + rt_uint32_t csr; + rt_uint32_t id = pl330_chan_id(pl330, pc); + rt_uint32_t timeout = PL330_STOP_TIMEOUT_US; + + /* DMASEV does not imply DMAEND has retired; the microcode is still in use. */ + for (;;) + { + csr = HWREG32(pl330->regs + PL330_REG_CSR(id)) & 0xf; + if (csr == PL330_CS_STOP) + { + return RT_EOK; + } + if (csr == PL330_CS_FAULT || csr == PL330_CS_FLTCMP) + { + return -RT_EIO; + } + if (!timeout) + { + LOG_E("Channel[%u] stop timeout, csr=%x", id, csr); + return -RT_ETIMEOUT; + } + + --timeout; + rt_hw_us_delay(1); + } +} + /** * @brief Request a DMA channel from the PL330 controller * @@ -792,10 +823,16 @@ static rt_err_t pl330_dma_release_chan(struct rt_dma_chan *chan) */ static rt_err_t pl330_dma_start(struct rt_dma_chan *chan) { + rt_err_t err; rt_uint32_t id, inst0; struct pl330_chan *pc = rt_container_of(chan, struct pl330_chan, parent); struct pl330 *pl330 = raw_to_pl330(chan->ctrl); + if ((err = pl330_wait_stopped(pl330, pc))) + { + return err; + } + while (HWREG32(pl330->regs + PL330_REG_DBGSTATUS) & PL330_DBGSTATUS_BUSY) { rt_hw_cpu_relax(); @@ -846,6 +883,7 @@ static rt_err_t pl330_dma_start(struct rt_dma_chan *chan) */ static rt_err_t pl330_dma_stop(struct rt_dma_chan *chan) { + rt_err_t err; rt_uint32_t id; struct pl330_chan *pc = rt_container_of(chan, struct pl330_chan, parent); struct pl330 *pl330 = raw_to_pl330(chan->ctrl); @@ -863,7 +901,10 @@ static rt_err_t pl330_dma_stop(struct rt_dma_chan *chan) HWREG32(pl330->regs + PL330_REG_DBGINST1) = 0; HWREG32(pl330->regs + PL330_REG_DBGCMD) = 0; - return RT_EOK; + /* Do not release peripheral clocks or buffers until DMAKILL has retired. */ + err = pl330_wait_stopped(pl330, pc); + HWREG32(pl330->regs + PL330_REG_INTCLR) = RT_BIT(id); + return err; } /** @@ -920,6 +961,11 @@ static rt_err_t pl330_dma_prep_memcpy(struct rt_dma_chan *chan, struct pl330 *pl330 = raw_to_pl330(chan->ctrl); struct rt_dma_slave_config *conf = &chan->conf; + if ((err = pl330_wait_stopped(pl330, pc))) + { + return err; + } + mc = pc->microcode; if ((err = pl330_ccr_config(conf, pl330->mode_ns, &value))) @@ -1039,6 +1085,11 @@ static rt_err_t pl330_dma_prep_cyclic(struct rt_dma_chan *chan, struct pl330 *pl330 = raw_to_pl330(chan->ctrl); struct rt_dma_slave_config *conf = &chan->conf; + if ((err = pl330_wait_stopped(pl330, pc))) + { + return err; + } + mc = pc->microcode; if ((err = pl330_ccr_config(conf, pl330->mode_ns, &ccr_val))) @@ -1149,6 +1200,11 @@ static rt_err_t pl330_dma_prep_single(struct rt_dma_chan *chan, struct rt_dma_slave_config *conf = &chan->conf; rt_uint32_t cond; + if ((err = pl330_wait_stopped(pl330, pc))) + { + return err; + } + mc = pc->microcode; if ((err = pl330_ccr_config(conf, pl330->mode_ns, &ccr_val))) @@ -1196,6 +1252,12 @@ static rt_err_t pl330_dma_prep_single(struct rt_dma_chan *chan, cond = conf->src_maxburst > 1 ? PL330_COND_BURST : PL330_COND_SINGLE; + /* Some PL330 integrations only expose peripheral requests as bursts. */ + if (pl330->quirk & PL330_QUIRK_PERIPH_BURST) + { + cond = PL330_COND_BURST; + } + if (pc->is_slave) { mc += pl330_cmd_dmawfp(mc, cond, pc->peri_id); @@ -1291,7 +1353,7 @@ static void pl330_isr(int irqno, void *params) break; default: - /* DMASEV may be observed before the following DMAEND executes. */ + /* Cyclic events report a period; only prep/start require STOP. */ rt_dma_chan_done(&pc->parent, pc->size); break; } diff --git a/components/drivers/dma/dma_pool.c b/components/drivers/dma/dma_pool.c index 4b3e2055e2de..5b6bc6b7f534 100644 --- a/components/drivers/dma/dma_pool.c +++ b/components/drivers/dma/dma_pool.c @@ -664,7 +664,8 @@ static rt_ubase_t dma_pool_alloc(struct rt_dma_pool *pool, rt_size_t size) return RT_NULL; } - max_bits = pool->bits - size; + /* The bitmap iterator's limit is exclusive; include the last fit. */ + max_bits = pool->bits - size + 1; rt_bitmap_for_each_clear_bit(pool->map, bit, max_bits) { @@ -834,10 +835,13 @@ static void dma_free(struct rt_device *dev, rt_size_t size, rt_list_for_each_entry(pool, &dma_pool_nodes, list) { - if (dma_handle >= pool->region.start && - dma_handle <= pool->region.end) + /* Adjacent pools share an exclusive end/start boundary. */ + if (dma_handle >= pool->start && dma_handle < pool->region.end && + size <= pool->region.end - dma_handle) { - if (pool->flags & RT_DMA_F_NOCACHE || !(pool->flags & RT_DMA_F_LINEAR)) + if (!(flags & RT_DMA_F_NOMAP) && + ((pool->flags & RT_DMA_F_NOCACHE) || + (flags & RT_DMA_F_NOCACHE) || !(pool->flags & RT_DMA_F_LINEAR))) { rt_iounmap(cpu_addr); } diff --git a/components/drivers/sdio/host/sdio-dw.c b/components/drivers/sdio/host/sdio-dw.c index 1980bad4ecf7..d3fa7a563f62 100755 --- a/components/drivers/sdio/host/sdio-dw.c +++ b/components/drivers/sdio/host/sdio-dw.c @@ -849,6 +849,19 @@ static rt_err_t sdio_dw_submit_data_dma(struct sdio_dw *sd, struct rt_mmcsd_data return -RT_ENOSYS; } + if (sd->use_dma == TRANS_MODE_IDMAC && !sd->dma_64bit_address) + { + rt_uint64_t addr = (rt_ubase_t)rt_kmem_v2p(sd->last_buf); + + /* Buffers can be above the controller's 32-bit DMA aperture. */ + if (!sd->last_remain || addr > 0xffffffffULL || + sd->last_remain - 1 > 0xffffffffULL - addr) + { + sd->dma_ops->stop(sd); + return -RT_ENOSYS; + } + } + sd->using_dma = RT_TRUE; /* @@ -1567,6 +1580,15 @@ static void sdio_dw_init_dma(struct sdio_dw *sd) goto _no_dma; } + if (!sd->dma_64bit_address && + ((rt_uint64_t)sd->dma_buf_phy > 0xffffffffULL || + DESC_RING_BUF_SZ - 1 > 0xffffffffULL - (rt_uint64_t)sd->dma_buf_phy)) + { + rt_dma_free_coherent(sd->bus_dev, DESC_RING_BUF_SZ, sd->dma_buf, sd->dma_buf_phy); + sd->dma_buf = RT_NULL; + goto _no_dma; + } + sd->dma_ops = &sdio_dw_idmac_ops; LOG_D("Using internal DMA controller"); } @@ -3217,15 +3239,15 @@ rt_err_t sdio_dw_probe(struct sdio_dw *sd) rt_strncpy(&dev_name[len], "-cmd11", sizeof(dev_name) - len); rt_timer_init(&sd->cmd11_timer, dev_name, sdio_dw_cmd11_timer, sd, - 0, RT_TIMER_FLAG_PERIODIC); + 0, RT_TIMER_FLAG_ONE_SHOT); rt_strncpy(&dev_name[len], "-cto", sizeof(dev_name) - len); rt_timer_init(&sd->cto_timer, dev_name, sdio_dw_cto_timer, sd, - 0, RT_TIMER_FLAG_PERIODIC); + 0, RT_TIMER_FLAG_ONE_SHOT); rt_strncpy(&dev_name[len], "-dto", sizeof(dev_name) - len); rt_timer_init(&sd->dto_timer, dev_name, sdio_dw_dto_timer, sd, - 0, RT_TIMER_FLAG_PERIODIC); + 0, RT_TIMER_FLAG_ONE_SHOT); mmcsd_change(sd->slot->host);