diff options
| author | GUI <[email protected]> | 2026-08-14 12:42:01 +0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-08-14 12:42:01 +0800 |
| commit | f2c05253d11f0b5746ade4bdb7d8d838bb7a5df1 (patch) | |
| tree | 63e8bc39e0391bd0e0587a96d59dc3a7b42a732f /components/drivers | |
| parent | c1418dbd2e3b9596b4082b111b80bd5f86e76028 (diff) | |
[dm][dma] Update DMA (#11678)
* [dm][dma] update DMA interface
Extend DMA mapping to honor OFW dma-ranges even
when no reserved memory pool is present,
and add PCI host-bridge DMA-region translation for PCI devices.
Prefer 32-bit-addressable allocations for translated devices,
improve pool allocation fallbacks and synchronization,
and establish a linear mapping for installed DMA pools when possible.
Signed-off-by: GuEe-GUI <[email protected]>
* [dm][dma] fixup PL330 work flow
Fix PL330 channel allocation so device-tree cells
select peripheral request lines rather than execution channels.
Generate proper peripheral DMA microcode using DMAWFP, DMALDP, DMASTP, and DMAFLUSHP,
encode burst lengths correctly,
honor transfer direction and non-secure attributes,
and allocate microcode from DMA-coherent memory.
Signed-off-by: GuEe-GUI <[email protected]>
* style: format code with clang-format
* style: format complete changed files with clang-format
* drivers: dma: fix diagnostic format types
* style: format DMA changes for current master
---------
Signed-off-by: GuEe-GUI <[email protected]>
Diffstat (limited to 'components/drivers')
| -rw-r--r-- | components/drivers/dma/dma-pl330.c | 275 | ||||
| -rw-r--r-- | components/drivers/dma/dma_pool.c | 305 |
2 files changed, 488 insertions, 92 deletions
diff --git a/components/drivers/dma/dma-pl330.c b/components/drivers/dma/dma-pl330.c index 8fa207a94d..7a21d0e6e3 100644 --- a/components/drivers/dma/dma-pl330.c +++ b/components/drivers/dma/dma-pl330.c @@ -213,25 +213,25 @@ #define PL330_CMD_DMAWMB 0x13 /** @brief Microcode instruction sizes */ -#define PL330_SIZE_DMAADDH 3 -#define PL330_SIZE_DMAEND 1 +#define PL330_SIZE_DMAADDH 3 +#define PL330_SIZE_DMAEND 1 #define PL330_SIZE_DMAFLUSHP 2 -#define PL330_SIZE_DMALD 1 -#define PL330_SIZE_DMALDP 2 -#define PL330_SIZE_DMALP 2 -#define PL330_SIZE_DMALPEND 2 -#define PL330_SIZE_DMAKILL 1 -#define PL330_SIZE_DMAMOV 6 -#define PL330_SIZE_DMANOP 1 -#define PL330_SIZE_DMARMB 1 -#define PL330_SIZE_DMASEV 2 -#define PL330_SIZE_DMAST 1 -#define PL330_SIZE_DMASTP 2 -#define PL330_SIZE_DMASTZ 1 -#define PL330_SIZE_DMAWFE 2 -#define PL330_SIZE_DMAWFP 2 -#define PL330_SIZE_DMAWMB 1 -#define PL330_SIZE_DMAGO 6 +#define PL330_SIZE_DMALD 1 +#define PL330_SIZE_DMALDP 2 +#define PL330_SIZE_DMALP 2 +#define PL330_SIZE_DMALPEND 2 +#define PL330_SIZE_DMAKILL 1 +#define PL330_SIZE_DMAMOV 6 +#define PL330_SIZE_DMANOP 1 +#define PL330_SIZE_DMARMB 1 +#define PL330_SIZE_DMASEV 2 +#define PL330_SIZE_DMAST 1 +#define PL330_SIZE_DMASTP 2 +#define PL330_SIZE_DMASTZ 1 +#define PL330_SIZE_DMAWFE 2 +#define PL330_SIZE_DMAWFP 2 +#define PL330_SIZE_DMAWMB 1 +#define PL330_SIZE_DMAGO 6 /** @brief DMAMOV to Source Address Register */ #define PL330_DIR_SAR 0 @@ -242,12 +242,14 @@ /** @brief Source address increment flag in CCR */ #define PL330_SRC_INC RT_BIT(0) +#define PL330_SRC_NS RT_BIT(9) /** @brief Source burst size shift in CCR */ #define PL330_SRC_BURST_SIZE_SHIFT 1 /** @brief Source burst length shift in CCR */ #define PL330_SRC_BURST_LEN_SHIFT 4 /** @brief Destination address increment flag in CCR */ #define PL330_DST_INC RT_BIT(14) +#define PL330_DST_NS RT_BIT(23) /** @brief Destination burst size shift in CCR */ #define PL330_DST_BURST_SIZE_SHIFT 15 /** @brief Destination burst length shift in CCR */ @@ -269,20 +271,22 @@ * @brief PL330 DMA channel descriptor * * Extends the generic DMA channel with microcode buffer management. - * The microcode is generated at prep time and stored in microcode_raw - * with alignment padding. + * The microcode is generated at prep time in DMA-coherent memory. */ struct pl330_chan { struct rt_dma_chan parent; /**< Generic DMA channel */ rt_bool_t enabled; /**< Channel is currently in use */ + rt_bool_t is_slave; /**< Channel serves a peripheral request */ + rt_bool_t start_logged; /**< First-start diagnostic emitted */ + rt_bool_t irq_logged; /**< First-IRQ diagnostic emitted */ + rt_uint8_t peri_id; /**< PL330 peripheral request ID */ rt_size_t size; /**< Transfer size for callback reporting */ void *microcode; /**< Aligned microcode buffer pointer */ rt_size_t microcode_len; /**< Microcode length in bytes */ rt_ubase_t microcode_dma; /**< Physical address of microcode buffer */ - rt_uint8_t microcode_raw[PL330_MICROCODE_SIZE + 4]; /**< Raw microcode storage (+4 for alignment) */ }; /** @@ -331,7 +335,7 @@ static void pl330_read_config(struct pl330 *pl330) void *regs = pl330->regs; value = HWREG32(regs + PL330_REG_CR(0)); - pl330->mode_ns = !!PL330_CR0_NUM_EVENTS(value); + pl330->mode_ns = PL330_CR0_MGR_NS_AT_RST(value); pl330->num_chan = PL330_CR0_NUM_CHNLS(value) + 1; pl330->num_events = PL330_CR0_NUM_EVENTS(value) + 1; @@ -358,11 +362,32 @@ static void pl330_read_config(struct pl330 *pl330) * * @return RT_EOK on success, -RT_EINVAL if address width is unsupported */ -static rt_err_t pl330_ccr_config(struct rt_dma_slave_config *conf, rt_uint32_t *ccr) +static rt_err_t pl330_ccr_config(struct rt_dma_slave_config *conf, + rt_bool_t nonsecure, rt_uint32_t *ccr) { - *ccr = PL330_SRC_INC | PL330_DST_INC; - *ccr |= conf->src_maxburst << PL330_SRC_BURST_LEN_SHIFT; - *ccr |= conf->dst_maxburst << PL330_DST_BURST_LEN_SHIFT; + if (!conf->src_maxburst || conf->src_maxburst > 16 || + !conf->dst_maxburst || conf->dst_maxburst > 16) + { + return -RT_EINVAL; + } + + *ccr = (conf->src_maxburst - 1) << PL330_SRC_BURST_LEN_SHIFT; + *ccr |= (conf->dst_maxburst - 1) << PL330_DST_BURST_LEN_SHIFT; + + if (conf->direction != RT_DMA_DEV_TO_MEM) + { + *ccr |= PL330_SRC_INC; + } + + if (conf->direction != RT_DMA_MEM_TO_DEV) + { + *ccr |= PL330_DST_INC; + } + + if (nonsecure) + { + *ccr |= PL330_SRC_NS | PL330_DST_NS; + } switch (conf->src_addr_width) { @@ -512,6 +537,45 @@ static int pl330_cmd_dmast(rt_uint8_t *microcode, rt_uint32_t cond) return PL330_SIZE_DMAST; } +static int pl330_cmd_dmaflushp(rt_uint8_t *microcode, rt_uint8_t peri) +{ + *microcode++ = PL330_CMD_DMAFLUSHP; + *microcode = (peri & 0x1f) << 3; + + return PL330_SIZE_DMAFLUSHP; +} + +static int pl330_cmd_dmaldp(rt_uint8_t *microcode, rt_uint32_t cond, + rt_uint8_t peri) +{ + *microcode++ = PL330_CMD_DMALDP | + (cond == PL330_COND_BURST ? RT_BIT(1) : 0); + *microcode = (peri & 0x1f) << 3; + + return PL330_SIZE_DMALDP; +} + +static int pl330_cmd_dmastp(rt_uint8_t *microcode, rt_uint32_t cond, + rt_uint8_t peri) +{ + *microcode++ = PL330_CMD_DMASTP | + (cond == PL330_COND_BURST ? RT_BIT(1) : 0); + *microcode = (peri & 0x1f) << 3; + + return PL330_SIZE_DMASTP; +} + +static int pl330_cmd_dmawfp(rt_uint8_t *microcode, rt_uint32_t cond, + rt_uint8_t peri) +{ + *microcode++ = PL330_CMD_DMAWFP | + (cond == PL330_COND_BURST ? RT_BIT(1) : 0) | + (cond == PL330_COND_ALWAYS ? RT_BIT(0) : 0); + *microcode = (peri & 0x1f) << 3; + + return PL330_SIZE_DMAWFP; +} + /** * @brief Emit a DMALP (loop begin) microcode instruction * @@ -653,9 +717,8 @@ static rt_uint32_t pl330_chan_id(struct pl330 *pl330, struct pl330_chan *pc) /** * @brief Request a DMA channel from the PL330 controller * - * If fw_data (device tree args) specifies a channel number, that - * specific channel is requested. Otherwise any unallocated channel - * is used. Marks the channel as enabled and unmasks its IRQ. + * Device-tree arguments identify a peripheral request line, while the + * controller allocates any free execution channel. * * @param[in] ctrl DMA controller * @param[in] slave Requesting device @@ -666,46 +729,34 @@ static rt_uint32_t pl330_chan_id(struct pl330 *pl330, struct pl330_chan *pc) static struct rt_dma_chan *pl330_dma_request_chan(struct rt_dma_controller *ctrl, struct rt_device *slave, void *fw_data) { - int idx = -1; + int peri_id = -1; struct pl330_chan *pc; struct pl330 *pl330 = raw_to_pl330(ctrl); struct rt_ofw_cell_args *args = fw_data; - if (args && (idx = args->args[0]) >= pl330->num_chan) + if (args && (peri_id = args->args[0]) >= pl330->num_peri) { return rt_err_ptr(-RT_EINVAL); } - if (idx >= 0) + for (int i = 0; i < pl330->num_chan; ++i) { - pc = &pl330->chans[idx]; + pc = &pl330->chans[i]; - if (pc->enabled) + if (!pc->enabled) { - return rt_err_ptr(-RT_EBUSY); + goto _found; } } - else - { - /* - * Memory to Memory is often called after after DM has been initialized, - * So just return unused chan - */ - for (int i = 0; i < pl330->num_chan; ++i) - { - pc = &pl330->chans[i]; - if (!pc->enabled) - { - goto _found; - } - } - - return RT_NULL; - } + return RT_NULL; _found: pc->enabled = RT_TRUE; + pc->is_slave = peri_id >= 0; + pc->start_logged = RT_FALSE; + pc->irq_logged = RT_FALSE; + pc->peri_id = peri_id >= 0 ? peri_id : 0; HWREG32(pl330->regs + PL330_REG_INTEN) |= RT_BIT(pl330_chan_id(pl330, pc)); return &pc->parent; @@ -723,6 +774,7 @@ static rt_err_t pl330_dma_release_chan(struct rt_dma_chan *chan) struct pl330_chan *pc = rt_container_of(chan, struct pl330_chan, parent); pc->enabled = RT_FALSE; + pc->is_slave = RT_FALSE; return RT_EOK; } @@ -740,6 +792,7 @@ 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_uint32_t id, inst0; struct pl330_chan *pc = rt_container_of(chan, struct pl330_chan, parent); struct pl330 *pl330 = raw_to_pl330(chan->ctrl); @@ -750,10 +803,34 @@ static rt_err_t pl330_dma_start(struct rt_dma_chan *chan) rt_hw_cpu_dcache_ops(RT_HW_CACHE_FLUSH, pc->microcode, pc->microcode_len); - HWREG32(pl330->regs + PL330_REG_DBGINST0) = ((pl330_chan_id(pl330, pc) + 1) << 8) | (PL330_CMD_DMAGO << 16); + id = pl330_chan_id(pl330, pc); + inst0 = ((PL330_CMD_DMAGO | (pl330->mode_ns ? RT_BIT(1) : 0)) << 16) | + (id << 24); + + /* DMAGO is issued by the manager thread; its second byte selects a channel. */ + HWREG32(pl330->regs + PL330_REG_DBGINST0) = inst0; HWREG32(pl330->regs + PL330_REG_DBGINST1) = pc->microcode_dma; HWREG32(pl330->regs + PL330_REG_DBGCMD) = 0; + if (!pc->start_logged) + { + pc->start_logged = RT_TRUE; + LOG_D("ch%u start peri=%u csr=%x inten=%08x mc=%08x", id, pc->peri_id, + HWREG32(pl330->regs + PL330_REG_CSR(id)) & 0xf, + HWREG32(pl330->regs + PL330_REG_INTEN), + (rt_uint32_t)pc->microcode_dma); + + if ((HWREG32(pl330->regs + PL330_REG_CSR(id)) & 0xf) == PL330_CS_FAULT) + { + LOG_E("ch%u fault ftr=%08x fsrc=%08x fsrd=%08x dsr=%08x cpc=%08x", + id, HWREG32(pl330->regs + PL330_REG_FTR(id)), + HWREG32(pl330->regs + PL330_REG_FSRC), + HWREG32(pl330->regs + PL330_REG_FSRD), + HWREG32(pl330->regs + PL330_REG_DSR), + HWREG32(pl330->regs + PL330_REG_CPC(id))); + } + } + return RT_EOK; } @@ -769,6 +846,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_uint32_t id; struct pl330_chan *pc = rt_container_of(chan, struct pl330_chan, parent); struct pl330 *pl330 = raw_to_pl330(chan->ctrl); @@ -777,7 +855,11 @@ static rt_err_t pl330_dma_stop(struct rt_dma_chan *chan) rt_hw_cpu_relax(); } - HWREG32(pl330->regs + PL330_REG_DBGINST0) = ((pl330_chan_id(pl330, pc) + 1) << 8) | PL330_CMD_DMAKILL; + id = pl330_chan_id(pl330, pc); + + /* DMAKILL is executed in the selected channel debug thread. */ + HWREG32(pl330->regs + PL330_REG_DBGINST0) = + (PL330_CMD_DMAKILL << 16) | RT_BIT(0) | (id << 8); HWREG32(pl330->regs + PL330_REG_DBGINST1) = 0; HWREG32(pl330->regs + PL330_REG_DBGCMD) = 0; @@ -840,7 +922,7 @@ static rt_err_t pl330_dma_prep_memcpy(struct rt_dma_chan *chan, mc = pc->microcode; - if ((err = pl330_ccr_config(conf, &value))) + if ((err = pl330_ccr_config(conf, pl330->mode_ns, &value))) { return err; } @@ -959,7 +1041,7 @@ static rt_err_t pl330_dma_prep_cyclic(struct rt_dma_chan *chan, mc = pc->microcode; - if ((err = pl330_ccr_config(conf, &ccr_val))) + if ((err = pl330_ccr_config(conf, pl330->mode_ns, &ccr_val))) { return err; } @@ -1065,10 +1147,11 @@ static rt_err_t pl330_dma_prep_single(struct rt_dma_chan *chan, struct pl330_chan *pc = rt_container_of(chan, struct pl330_chan, parent); struct pl330 *pl330 = raw_to_pl330(chan->ctrl); struct rt_dma_slave_config *conf = &chan->conf; + rt_uint32_t cond; mc = pc->microcode; - if ((err = pl330_ccr_config(conf, &ccr_val))) + if ((err = pl330_ccr_config(conf, pl330->mode_ns, &ccr_val))) { return err; } @@ -1086,6 +1169,11 @@ static rt_err_t pl330_dma_prep_single(struct rt_dma_chan *chan, mc += pl330_cmd_dmamov(mc, PL330_DIR_DAR, dma_buf_addr); } + if (pc->is_slave && !(pl330->quirk & PL330_QUIRK_BROKEN_NO_FLUSHP)) + { + mc += pl330_cmd_dmaflushp(mc, pc->peri_id); + } + burst_bytes = conf->src_addr_width * conf->src_maxburst; if (!burst_bytes || !buf_len) { @@ -1106,8 +1194,28 @@ static rt_err_t pl330_dma_prep_single(struct rt_dma_chan *chan, mc += pl330_cmd_dmalp(mc, 0 /* LC0 */, loop); rt_uint8_t ljmp = mc - pc->microcode; - mc += pl330_cmd_dmald(mc, PL330_COND_ALWAYS); - mc += pl330_cmd_dmast(mc, PL330_COND_ALWAYS); + cond = conf->src_maxburst > 1 ? PL330_COND_BURST : PL330_COND_SINGLE; + + if (pc->is_slave) + { + mc += pl330_cmd_dmawfp(mc, cond, pc->peri_id); + + if (dir == RT_DMA_MEM_TO_DEV) + { + mc += pl330_cmd_dmald(mc, cond); + mc += pl330_cmd_dmastp(mc, cond, pc->peri_id); + } + else + { + mc += pl330_cmd_dmaldp(mc, cond, pc->peri_id); + mc += pl330_cmd_dmast(mc, cond); + } + } + else + { + mc += pl330_cmd_dmald(mc, PL330_COND_ALWAYS); + mc += pl330_cmd_dmast(mc, PL330_COND_ALWAYS); + } mc += pl330_cmd_dmalpend(mc, PL330_COND_ALWAYS, @@ -1140,11 +1248,8 @@ static const struct rt_dma_controller_ops pl330_dma_ops = { /** * @brief PL330 interrupt service routine * - * Handles all channel interrupts. Reads the Interrupt Status Register - * to identify which channel triggered, then checks the Channel Status - * Register: - * - PL330_CS_CMPLT / PL330_CS_STOP: transfer completed successfully - * - PL330_CS_FAULT / PL330_CS_FLTCMP: transfer faulted + * Handles completion events and reports channel faults. DMASEV can be + * observed before the following DMAEND instruction is executed. * * @param[in] irqno Hardware IRQ number * @param[in] params Controller instance pointer @@ -1170,13 +1275,15 @@ static void pl330_isr(int irqno, void *params) csr = HWREG32(pl330->regs + PL330_REG_CSR(i)) & 0xf; - switch (csr) + if (!pc->irq_logged) { - case PL330_CS_CMPLT: - case PL330_CS_STOP: - rt_dma_chan_done(&pc->parent, pc->size); - break; + pc->irq_logged = RT_TRUE; + LOG_D("ch%d irq mis=%08x csr=%x cpc=%08x", i, isr, csr, + HWREG32(pl330->regs + PL330_REG_CPC(i))); + } + switch (csr) + { case PL330_CS_FAULT: case PL330_CS_FLTCMP: LOG_E("Channel[%d] fault", i); @@ -1184,7 +1291,8 @@ static void pl330_isr(int irqno, void *params) break; default: - LOG_E("Unhandle CSR = %x", csr); + /* DMASEV may be observed before the following DMAEND executes. */ + rt_dma_chan_done(&pc->parent, pc->size); break; } } @@ -1225,6 +1333,17 @@ static void pl330_free(struct pl330 *pl330) if (pl330->chans) { + for (int i = 0; i < pl330->num_chan; ++i) + { + struct pl330_chan *chan = &pl330->chans[i]; + + if (chan->microcode) + { + rt_dma_free_coherent(pl330->parent.dev, PL330_MICROCODE_SIZE, + chan->microcode, chan->microcode_dma); + } + } + rt_free(pl330->chans); } @@ -1262,6 +1381,8 @@ static rt_err_t pl330_probe(struct rt_platform_device *pdev) return -RT_ENOMEM; } + pl330->parent.dev = dev; + pl330->regs = rt_dm_dev_iomap(dev, 0); if (!pl330->regs) @@ -1357,17 +1478,17 @@ static rt_err_t pl330_probe(struct rt_platform_device *pdev) for (int i = 0; i < pl330->num_chan; ++i) { - int offset = 0; struct pl330_chan *chan = &pl330->chans[i]; - chan->microcode_dma = (rt_ubase_t)rt_kmem_v2p(chan->microcode_raw); - offset = RT_ALIGN(chan->microcode_dma, 4) - chan->microcode_dma; - - chan->microcode = chan->microcode_raw + offset; - chan->microcode_dma += offset; + chan->microcode = rt_dma_alloc_coherent(dev, PL330_MICROCODE_SIZE, + &chan->microcode_dma); + if (!chan->microcode) + { + err = -RT_ENOMEM; + goto _fail; + } } - pl330->parent.dev = dev; pl330->parent.ops = &pl330_dma_ops; rt_dma_controller_add_direction(&pl330->parent, RT_DMA_MEM_TO_MEM); rt_dma_controller_add_direction(&pl330->parent, RT_DMA_MEM_TO_DEV); diff --git a/components/drivers/dma/dma_pool.c b/components/drivers/dma/dma_pool.c index 79b89e39a2..75e28cced6 100644 --- a/components/drivers/dma/dma_pool.c +++ b/components/drivers/dma/dma_pool.c @@ -31,6 +31,7 @@ #define DBG_LVL DBG_INFO #include <rtdbg.h> +#include <mmu.h> #include <mm_aspace.h> #include <mm_memblock.h> #include <dt-bindings/size.h> @@ -39,6 +40,7 @@ static RT_DEFINE_SPINLOCK(dma_pools_lock); static rt_list_t dma_pool_nodes = RT_LIST_OBJECT_INIT(dma_pool_nodes); static struct rt_dma_pool *dma_pool_install(rt_region_t *region); +static rt_err_t dma_pool_map_linear(struct rt_dma_pool *pool); static void *dma_alloc(struct rt_device *dev, rt_size_t size, rt_ubase_t *dma_handle, rt_ubase_t flags); @@ -202,7 +204,10 @@ rt_inline rt_ubase_t ofw_addr_dma2cpu(struct rt_device *dev, rt_ubase_t addr) static void *ofw_dma_map_alloc(struct rt_device *dev, rt_size_t size, rt_ubase_t *dma_handle, rt_ubase_t flags) { - void *cpu_addr = dma_alloc(dev, size, dma_handle, flags); + void *cpu_addr; + + flags |= RT_DMA_F_32BITS; + cpu_addr = dma_alloc(dev, size, dma_handle, flags); if (cpu_addr && dma_handle) { @@ -302,17 +307,37 @@ static const struct rt_dma_map_ops ofw_dma_map_ops = { .sync_in_data = ofw_dma_map_sync_in_data, }; +static rt_bool_t ofw_node_has_dma_ranges(struct rt_ofw_node *np) +{ + struct rt_ofw_node *bus; + + for (bus = np ? np->parent : RT_NULL; bus; bus = bus->parent) + { + rt_ssize_t len; + + if (rt_ofw_get_prop(bus, "dma-ranges", &len) && len) + { + return RT_TRUE; + } + } + + return RT_FALSE; +} + /** - * @brief Determine DMA operations for a device tree device with memory-region + * @brief Determine DMA operations for a device tree device * * Parses the "memory-region" property to install DMA pools from * reserved-memory nodes. Sets RT_DMA_F_NOMAP if "no-map" is set, * and RT_DMA_F_NOCACHE if the device is not DMA-coherent. * + * Also installs ofw_dma_map_ops when the parent bus chain provides + * "dma-ranges", so CPU physical addresses are translated for DMA. + * * @param[in] dev Device to configure DMA operations for * - * @return Pointer to DMA map ops (ofw_dma_map_ops) if regions were found, - * RT_NULL otherwise + * @return Pointer to DMA map ops (ofw_dma_map_ops) when translation + * or memory-region pools apply, RT_NULL otherwise */ static const struct rt_dma_map_ops *ofw_device_dma_ops(struct rt_device *dev) { @@ -332,12 +357,7 @@ static const struct rt_dma_map_ops *ofw_device_dma_ops(struct rt_device *dev) if (!(mem_np = rt_ofw_find_node_by_phandle(phandle))) { - if (region_nr == 0) - { - return RT_NULL; - } - - break; + continue; } if ((err = rt_ofw_get_address(mem_np, 0, &addr, &size))) @@ -374,7 +394,7 @@ static const struct rt_dma_map_ops *ofw_device_dma_ops(struct rt_device *dev) ++region_nr; } - if (region_nr) + if (region_nr || ofw_node_has_dma_ranges(np)) { ops = &ofw_dma_map_ops; } @@ -383,14 +403,197 @@ static const struct rt_dma_map_ops *ofw_device_dma_ops(struct rt_device *dev) } #endif /* RT_USING_OFW */ +#ifdef RT_USING_PCI +static struct rt_pci_host_bridge *pci_device_host_bridge(struct rt_device *dev) +{ + struct rt_pci_device *pdev = rt_container_of(dev, struct rt_pci_device, parent); + + return rt_pci_find_host_bridge(pdev->bus); +} + +static rt_uint64_t pci_host_cpu_to_bus(struct rt_pci_host_bridge *host_bridge, rt_uint64_t cpu_addr) +{ + if (!host_bridge || !host_bridge->dma_regions) + { + return ~0ULL; + } + + for (int i = 0; i < host_bridge->dma_regions_nr; ++i) + { + struct rt_pci_bus_region *region = &host_bridge->dma_regions[i]; + + if (cpu_addr >= region->cpu_addr && + cpu_addr < region->cpu_addr + region->size) + { + return region->phy_addr + (cpu_addr - region->cpu_addr); + } + } + + return ~0ULL; +} + +static rt_uint64_t pci_host_bus_to_cpu(struct rt_pci_host_bridge *host_bridge, rt_uint64_t pci_addr) +{ + if (!host_bridge || !host_bridge->dma_regions) + { + return ~0ULL; + } + + for (int i = 0; i < host_bridge->dma_regions_nr; ++i) + { + struct rt_pci_bus_region *region = &host_bridge->dma_regions[i]; + + if (pci_addr >= region->phy_addr && pci_addr < region->phy_addr + region->size) + { + return region->cpu_addr + (pci_addr - region->phy_addr); + } + } + + return ~0ULL; +} + +static rt_ubase_t pci_addr_cpu2dma(struct rt_device *dev, rt_ubase_t addr) +{ + rt_uint64_t pci_dma; + struct rt_pci_host_bridge *host_bridge = pci_device_host_bridge(dev); + + if (!host_bridge) + { + return addr; + } + + pci_dma = pci_host_cpu_to_bus(host_bridge, addr); + + return pci_dma == ~0ULL ? addr : (rt_ubase_t)pci_dma; +} + +static rt_ubase_t pci_addr_dma2cpu(struct rt_device *dev, rt_ubase_t addr) +{ + rt_uint64_t cpu_addr; + struct rt_pci_host_bridge *host_bridge = pci_device_host_bridge(dev); + + if (!host_bridge) + { + return addr; + } + + cpu_addr = pci_host_bus_to_cpu(host_bridge, addr); + + return cpu_addr == ~0ULL ? addr : (rt_ubase_t)cpu_addr; +} + +static void *pci_dma_map_alloc(struct rt_device *dev, rt_size_t size, + rt_ubase_t *dma_handle, rt_ubase_t flags) +{ + void *cpu_addr; + + flags |= RT_DMA_F_32BITS; + cpu_addr = dma_alloc(dev, size, dma_handle, flags); + + if (cpu_addr && dma_handle) + { + *dma_handle = pci_addr_cpu2dma(dev, *dma_handle); + } + + return cpu_addr; +} + +static void pci_dma_map_free(struct rt_device *dev, rt_size_t size, + void *cpu_addr, rt_ubase_t dma_handle, rt_ubase_t flags) +{ + dma_handle = pci_addr_dma2cpu(dev, dma_handle); + + dma_free(dev, size, cpu_addr, dma_handle, flags); +} + +static rt_err_t pci_dma_map_sync_out_data(struct rt_device *dev, + void *data, rt_size_t size, + rt_ubase_t *dma_handle, rt_ubase_t flags) +{ + rt_err_t err; + + if (flags & RT_DMA_F_NOCACHE) + { + err = dma_map_nocoherent_sync_out_data(dev, data, size, dma_handle, flags); + } + else + { + err = dma_map_coherent_sync_out_data(dev, data, size, dma_handle, flags); + } + + if (!err && dma_handle) + { + *dma_handle = pci_addr_cpu2dma(dev, *dma_handle); + } + + return err; +} + +static rt_err_t pci_dma_map_sync_in_data(struct rt_device *dev, + void *out_data, rt_size_t size, + rt_ubase_t dma_handle, rt_ubase_t flags) +{ + dma_handle = pci_addr_dma2cpu(dev, dma_handle); + + if (flags & RT_DMA_F_NOCACHE) + { + return dma_map_nocoherent_sync_in_data(dev, out_data, size, dma_handle, flags); + } + + return dma_map_coherent_sync_in_data(dev, out_data, size, dma_handle, flags); +} + +static const struct rt_dma_map_ops pci_dma_map_ops = { + .alloc = pci_dma_map_alloc, + .free = pci_dma_map_free, + .sync_out_data = pci_dma_map_sync_out_data, + .sync_in_data = pci_dma_map_sync_in_data, +}; + +static const struct rt_dma_map_ops *pci_device_dma_ops(struct rt_device *dev) +{ + struct rt_bus *bus = dev->bus; + struct rt_pci_host_bridge *host_bridge; + + if (!bus || rt_strcmp(bus->name, "pci")) + { + return RT_NULL; + } + + host_bridge = pci_device_host_bridge(dev); + + if (!host_bridge || !host_bridge->dma_regions_nr) + { + return RT_NULL; + } + + return &pci_dma_map_ops; +} +#endif /* RT_USING_PCI */ + +#ifdef RT_USING_OFW +const struct rt_dma_map_ops *rt_dma_ofw_device_map_ops(struct rt_device *dev) +{ + if (!dev || !dev->ofw_node) + { + return RT_NULL; + } + + return ofw_device_dma_ops(dev); +} +#endif /* RT_USING_OFW */ + /** * @brief Select DMA operations for a device * * Priority: * 1. Device-specific dma_ops (if already set) - * 2. Device tree memory-region ops (if device has ofw_node) + * 2. Device tree ops (memory-region pools and/or dma-ranges translation) * 3. Coherent or non-coherent fallback based on device property * + * PCI devices with OFW bind dma_ops early in rt_pci_ofw_device_init(); + * other PCI devices fall back to pci_device_dma_ops() here. + * * The result is cached in dev->dma_ops for subsequent calls. * * @param[in] dev Device to query @@ -409,6 +612,15 @@ static const struct rt_dma_map_ops *device_dma_ops(struct rt_device *dev) #ifdef RT_USING_OFW if (dev->ofw_node && (ops = ofw_device_dma_ops(dev))) { + dev->dma_ops = ops; + return ops; + } +#endif + +#ifdef RT_USING_PCI + if ((ops = pci_device_dma_ops(dev))) + { + dev->dma_ops = ops; return ops; } #endif @@ -443,6 +655,15 @@ static rt_ubase_t dma_pool_alloc(struct rt_dma_pool *pool, rt_size_t size) rt_size_t bit, next_bit, end_bit, max_bits; size = RT_DIV_ROUND_UP(size, ARCH_PAGE_SIZE); + + if (!size || size > pool->bits) + { + LOG_W("%s: request %u page(s) exceeds pool capacity %u page(s)", + pool->region.name ? pool->region.name : "<anon>", + (unsigned)size, (unsigned)pool->bits); + return RT_NULL; + } + max_bits = pool->bits - size; rt_bitmap_for_each_clear_bit(pool->map, bit, max_bits) @@ -549,10 +770,19 @@ static void *dma_alloc(struct rt_device *dev, rt_size_t size, if (*dma_handle && !(flags & RT_DMA_F_NOMAP)) { - if (flags & RT_DMA_F_NOCACHE) + if (pool->flags & RT_DMA_F_NOCACHE) { dma_buffer = rt_ioremap_nocache((void *)*dma_handle, size); } + else if (flags & RT_DMA_F_NOCACHE) + { + /* Honor caller NOCACHE before LINEAR pool shortcut. */ + dma_buffer = rt_ioremap_nocache((void *)*dma_handle, size); + } + else if (pool->flags & RT_DMA_F_LINEAR) + { + dma_buffer = (void *)(*dma_handle - PV_OFFSET); + } else if (flags & RT_DMA_F_WT) { dma_buffer = rt_ioremap_wt((void *)*dma_handle, size); @@ -607,7 +837,10 @@ static void dma_free(struct rt_device *dev, rt_size_t size, if (dma_handle >= pool->region.start && dma_handle <= pool->region.end) { - rt_iounmap(cpu_addr); + if (pool->flags & RT_DMA_F_NOCACHE || !(pool->flags & RT_DMA_F_LINEAR)) + { + rt_iounmap(cpu_addr); + } dma_pool_free(pool, dma_handle, size); @@ -767,11 +1000,48 @@ rt_err_t rt_dma_sync_in_data(struct rt_device *dev, void *out_data, rt_size_t si } /** + * @brief Map a linear DMA pool into the kernel address space + * + * Reserved pool memory is excluded from rt_memblock_setup_memory_environment(). + * Install a cached linear mapping so RT_DMA_F_LINEAR pools are CPU-accessible. + */ +static rt_err_t dma_pool_map_linear(struct rt_dma_pool *pool) +{ + rt_region_t *region = &pool->region; + rt_size_t start = RT_ALIGN_DOWN(region->start, ARCH_PAGE_SIZE); + rt_size_t end = RT_ALIGN(region->end, ARCH_PAGE_SIZE); + void *va; + + if (!(pool->flags & RT_DMA_F_LINEAR) || start >= end) + { + return RT_EOK; + } + + struct rt_mm_va_hint hint = { + .flags = MMF_MAP_FIXED, + .limit_start = rt_kernel_space.start, + .limit_range_size = rt_kernel_space.size, + .map_size = end - start, + .prefer = (void *)(start - PV_OFFSET), + }; + + if (rt_aspace_map_phy(&rt_kernel_space, &hint, MMU_MAP_K_RWCB, + start >> MM_PAGE_SHIFT, &va)) + { + LOG_E("map %s [0x%lx, 0x%lx] failed", region->name, + (unsigned long)start, (unsigned long)end); + return -RT_ERROR; + } + + return RT_EOK; +} + +/** * @brief Install a DMA memory pool from a region descriptor * * Creates a new DMA pool covering the given memory region. Allocates * a bitmap for page tracking. Pools under 4GB are marked RT_DMA_F_32BITS. - * Pools are automatically marked RT_DMA_F_LINEAR. + * Pools are automatically marked RT_DMA_F_LINEAR and mapped into kernel VA. * * Must be called with the DMA pools lock NOT held. * @@ -824,6 +1094,11 @@ static struct rt_dma_pool *dma_pool_install(rt_region_t *region) rt_list_insert_before(&dma_pool_nodes, &pool->list); region_pool_unlock(); + if (dma_pool_map_linear(pool)) + { + LOG_W("linear map failed for pool %s", region->name); + } + return pool; _fail: |
