summaryrefslogtreecommitdiff
path: root/components
diff options
context:
space:
mode:
authorGUI <[email protected]>2026-08-14 12:42:01 +0800
committerGitHub <[email protected]>2026-08-14 12:42:01 +0800
commitf2c05253d11f0b5746ade4bdb7d8d838bb7a5df1 (patch)
tree63e8bc39e0391bd0e0587a96d59dc3a7b42a732f /components
parentc1418dbd2e3b9596b4082b111b80bd5f86e76028 (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')
-rw-r--r--components/drivers/dma/dma-pl330.c275
-rw-r--r--components/drivers/dma/dma_pool.c305
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: