基于 Linux 7.1 源码
目录
- DMA 的地址问题:物理地址、总线地址与 dma_addr_t
1. 什么是 DMA
DMA(Direct Memory Access,直接内存访问) 是一种让外设不经过 CPU、直接读写主内存的硬件机制。
1.1 没有 DMA 的世界:PIO 模式
在没有 DMA 的情况下(PIO, Programmed I/O),设备与内存之间的每一个字节都要 CPU 亲自搬运:
设备 ──> CPU 寄存器 ──> 内存 (每个字至少两条 CPU 指令)
内存 ──> CPU 寄存器 ──> 设备
以 10 Gbps 网卡为例,每秒约 12.5 亿字节,若按 4 字节一次 MMIO 计算,CPU 需要执行超过 6 亿次访存指令——一个核全部时间都在搬数据,什么也干不了。
1.2 有 DMA 的世界
CPU:配置好 "源地址、目的地址、长度" → 告诉 DMA 引擎 → 去做别的事
DMA 引擎:设备 <──── 直接搬运 ────> 内存
搬完后:中断通知 CPU "干完了"
CPU 的角色从搬运工变成了调度员:只负责描述"搬什么、搬到哪",实际传输由 DMA 硬件完成。这个"描述"就是后面反复出现的描述符(descriptor)。
1.3 两类 DMA 硬件
| | | | |
|---|
| Bus Master DMA | | 集成在设备内部(PCIe 网卡、NVMe、GPU) | | |
| System DMA / Slave DMA | | SoC 内独立的 DMA 引擎(如 ARM PL330) | | |
这个区分是理解内核 DMA 体系的钥匙:内核用两套 API 分别服务这两类硬件,后文第 4、5 节分别展开。
2. 为什么需要 DMA
- 解放 CPU:搬运数据是纯粹的机械劳动,交给专用硬件后 CPU 可以跑协议栈、跑应用。NAPI 收包、零拷贝(zero-copy)、XDP 高速转发全部建立在 DMA 之上。
- 带宽匹配:CPU 的 load/store 流水线不是为外设突发流量设计的;DMA 引擎支持 burst 传输、scatter-gather 聚合,能吃满 PCIe / 内存总线带宽。
- 降低功耗与延迟:拷贝路径变短,设备到内存一跳直达;CPU 可以进入深度睡眠,只在完成中断时被唤醒。
- 并发性:CPU 提交多个 DMA 描述符后继续工作,传输在后台并行进行,通过完成中断/回调回收结果。
但 DMA 引入了三个内核必须解决的问题,这也正是内核 DMA 子系统存在的意义:
| | |
|---|
| Cache 一致性 | CPU 读的是 cache,DMA 改的是内存,两者可能不一致 | 方向标志驱动的 cache flush/invalidate |
| 地址转换 | 设备看到的总线地址 ≠ CPU 物理地址(IOMMU、偏移、加密位) | dma_addr_t |
| 寻址范围 | | DMA mask + SWIOTLB bounce buffer |
3. DMA 的地址问题
3.1 三种地址空间
一次 DMA 传输涉及三个"地址",初学者最容易在这里翻车:
CPU 虚拟地址 (void *) —— 驱动代码里用的指针
│ virt_to_phys()
CPU 物理地址 (phys_addr_t) —— 内存的真实物理编号
│ phys_to_dma() / IOMMU 页表
DMA/总线地址 (dma_addr_t) —— 设备在总线上看到的地址 ← 填给硬件的只能是它
官方文档的定义非常直白(Documentation/core-api/dma-api.rst L22-25):
"A dma_addr_t can hold any valid DMA address for the platform... A CPU cannot reference a dma_addr_t directly because there may be translation between its physical address space and the DMA address space."
工程铁律:
- 交给硬件寄存器/描述符的地址,必须是
dma_alloc_coherent() 或 dma_map_*() 返回的 dma_addr_t,绝不能用 virt_to_phys() 手工换算(没有 IOMMU 的平台上看似能跑,上了 IOMMU 立刻数据损坏)。 dma_map_*() 可能失败(IOMMU 映射资源耗尽、SWIOTLB 池满),必须用 dma_mapping_error() 检查:
/* include/linux/dma-mapping.h L152-159 */
static inline int dma_mapping_error(struct device *dev, dma_addr_t dma_addr)
{
debug_dma_mapping_error(dev, dma_addr);
if (unlikely(dma_addr == DMA_MAPPING_ERROR)) /* ~(dma_addr_t)0 */
return -ENOMEM;
return 0;
}
3.2 DMA mask:告诉内核设备能"看到"多大内存
设备寻址能力有限(老式 PCI 设备只有 32 位),驱动在 probe 阶段必须声明:
/* drivers/net/ethernet/intel/e1000/e1000_main.c e1000_probe() L994-1004 */
pci_using_dac = 0;
if ((hw->bus_type == e1000_bus_type_pcix) &&
!dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64))) {
pci_using_dac = 1; /* 优先 64 位(DAC) */
} else {
err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
if (err) {
pr_err("No usable DMA config, aborting\n");
goto err_dma;
}
}
dma_set_mask_and_coherent() 一次性设置流式映射与一致性映射两个掩码(include/linux/dma-mapping.h L641-647)。内核随后为每次映射/分配检查 DMA 地址是否落在 mask 内,够不到就走第 4.4 节的 SWIOTLB 兜底。
4. 内核实现之 DMA Mapping API
这是服务 Bus Master 设备的通用层,核心文件:
| |
|---|
include/linux/dma-mapping.h | |
kernel/dma/mapping.c | 分发核心:direct / IOMMU / SWIOTLB 三路选择 |
kernel/dma/direct.c | |
kernel/dma/swiotlb.c | |
kernel/dma/coherent.c | |
mm/dmapool.c | |
4.1 两类映射:一致性 vs 流式
DMA Mapping API 按生命周期和 cache 策略分为两类,这是使用时最重要的决策点:
| | 流式映射 dma_map_single / dma_map_sg |
|---|
| 内核分配(direct/CMA/SWIOTLB/atomic pool) | |
| | map/unmap 时按方向 flush/invalidate |
| | |
| 描述符环 | 数据 payload |
| | |
一致性映射入口(include/linux/dma-mapping.h L614-625):
static inline void *dma_alloc_coherent(struct device *dev, size_t size,
dma_addr_t *dma_handle, gfp_t gfp)
{
return dma_alloc_attrs(dev, size, dma_handle, gfp,
(gfp & __GFP_NOWARN) ? DMA_ATTR_NO_WARN : 0);
}
流式映射入口(L519-529,注意它拒绝 vmalloc 地址——vmalloc 内存物理不连续,DMA 无法直接使用):
if (dev_WARN_ONCE(dev, is_vmalloc_addr(ptr),
"rejecting DMA map of vmalloc memory\n"))
return DMA_MAPPING_ERROR;
return dma_map_page_attrs(dev, virt_to_page(ptr), offset_in_page(ptr),
size, dir, attrs);
4.2 方向标志:cache 一致性的开关
流式映射必须声明方向(dma-api.rst L218-223),它直接决定 map/unmap 时内核做哪种 cache 维护:
| | | | |
|---|
DMA_TO_DEVICE | | | | |
DMA_FROM_DEVICE | | | | |
DMA_BIDIRECTIONAL | | | | |
DMA_NONE | | | | |
方向写错的后果是隐蔽的数据损坏:该 flush 没 flush,设备读到旧数据;该 invalidate 没 invalidate,CPU 读到 DMA 前的旧 cache 行。这类 bug 在 x86(硬件一致性)上不暴露,到 ARM/RISC-V 上才炸——这是驱动可移植性最经典的坑。
4.3 分发核心:mapping.c 的三分支骨架
DMA Mapping API 最精妙的设计在 kernel/dma/mapping.c:所有映射请求在这里被分发到三条路径之一。
判定函数 dma_go_direct()(L120-153):
static bool dma_go_direct(struct device *dev, dma_addr_t mask,
const struct dma_map_ops *ops)
{
if (use_dma_iommu(dev))
return false;
if (likely(!ops))
return true;
#ifdef CONFIG_DMA_OPS_BYPASS
if (dev->dma_ops_bypass)
return min_not_zero(mask, dev->bus_dma_limit) >=
dma_direct_get_required_mask(dev);
#endif
return false;
}
流式映射的实际分发(dma_map_phys(),L155-180)——dma_map_single/dma_map_page 的共同底层:
if (dma_map_direct(dev, ops) ||
(!is_mmio && !is_cc_shared &&
arch_dma_map_phys_direct(dev, phys + size)))
addr = dma_direct_map_phys(dev, phys, size, dir, attrs, true);
else if (is_cc_shared)
return DMA_MAPPING_ERROR;
else if (use_dma_iommu(dev))
addr = iommu_dma_map_phys(dev, phys, size, dir, attrs);
else if (ops->map_phys)
addr = ops->map_phys(dev, phys, size, dir, attrs);
一致性分配 dma_alloc_attrs()(L643-662)结构完全对称:先查设备私有 coherent 池,再 direct / iommu / ops 三分支。
三条路径对比:
| | | |
|---|
| Direct | 无 IOMMU(!ops && !dev->dma_iommu) | phys_to_dma() | |
| SWIOTLB | direct 内兜底:地址超 mask 或强制 bounce | | |
| IOMMU | dev->dma_iommu = true(Intel VT-d / AMD-Vi / SMMU) | | |
x86 上 IOMMU 的探测在 arch/x86/kernel/pci-dma.c 的 pci_iommu_alloc()(L98-107):依次 pci_swiotlb_detect → gart_iommu_detect → amd_iommu_detect → detect_intel_iommu。现代内核检测到 IOMMU 后不再替换全局 dma_ops,而是设置 dev->dma_iommu = true,让 mapping.c 走 drivers/iommu/dma-iommu.c 的 iommu_dma_* 路径。
4.4 SWIOTLB:设备够不到内存时的救生艇
当 32 位设备要 DMA 到高于 4GB 的内存时,direct 路径里的兜底逻辑(kernel/dma/direct.hdma_direct_map_phys() L85-136):
dma_addr = phys_to_dma(dev, phys);
if (unlikely(!dma_capable(dev, dma_addr, size, true)) ||
dma_kmalloc_needs_bounce(dev, size, dir)) {
if (is_swiotlb_active(dev) &&
!(attrs & DMA_ATTR_REQUIRE_COHERENT))
return swiotlb_map(dev, phys, size, dir, attrs);
goto err_overflow;
}
swiotlb_map()(kernel/dma/swiotlb.c L1594-1623)从系统启动时预留的低地址 IO TLB 池中分配槽位,把数据拷贝进去,返回池内地址;unmap 时反向拷贝回来(bounce buffering 一词的来源)。它保证了任何设备都能 DMA 到任意内存,代价是一次额外拷贝——所以它是兜底而不是常态。机密计算(TDX/SEV)下 is_swiotlb_force_bounce() 强制所有 DMA 走 bounce,因为设备不能直接访问加密内存。
4.5 dma_pool:小块一致性内存的 slab
USB 驱动等需要频繁分配小块一致性内存(如几十字节的 URB 控制块),dma_alloc_coherent 按页分配太浪费。mm/dmapool.c 提供 slab 化的 dma_pool:
dma_pool_create_node()(L226):创建池,底层页仍由 dma_alloc_coherent 供应;dma_pool_alloc()(L407-442):池内切分,内部持 spin_lock_irqsave,可在中断上下文使用;- 一致性分配的另一来源是
kernel/dma/pool.c 的三个全局 atomic pool(atomic_pool_dma/dma32/kernel,L16-20),为不可阻塞的原子上下文兜底。
5. 内核实现之 DMAEngine 框架
服务 Slave DMA(SoC 系统 DMA 控制器) 的框架,三层结构:
客户端 API(include/linux/dmaengine.h 内联函数) ← UART/SPI/I2S/音频驱动调用
│ 纯转发
核心层(drivers/dma/dmaengine.c + dmaengine.h) ← 通道注册、cookie 管理
│ device_* 虚函数表
Provider 驱动(drivers/dma/pl330.c 等) ← 具体 DMA 控制器硬件
5.1 关键数据结构(include/linux/dmaengine.h)
enum dma_status {/* L37 */
DMA_COMPLETE, DMA_IN_PROGRESS, DMA_PAUSED, DMA_ERROR, DMA_OUT_OF_ORDER,
};
enum dma_transaction_type {/* L51 */
DMA_MEMCPY, ..., DMA_SLAVE, DMA_CYCLIC, DMA_INTERLEAVE, ...,
};
enum dma_transfer_direction { /* L79 */
DMA_MEM_TO_MEM, DMA_MEM_TO_DEV, DMA_DEV_TO_MEM, DMA_DEV_TO_DEV,
};
事务描述符(L614-632)——一次 DMA 传输的句柄:
struct dma_async_tx_descriptor {
dma_cookie_t cookie; /* 事务序号,见 5.3 */
dma_addr_t phys;
struct dma_chan *chan;
dma_cookie_t (*tx_submit)(struct dma_async_tx_descriptor *tx);
dma_async_tx_callback callback; /* 完成回调 */
void *callback_param;
...
};
Provider 虚函数表struct dma_device(L868-960)是客户端 API 的最终落点:device_alloc_chan_resources、device_prep_slave_sg、device_config、device_issue_pending、device_tx_status 等。
5.2 客户端六步流程
官方文档 Documentation/driver-api/dmaengine/client.rst L17-27 定义的标准流程:
| | | |
|---|
| dma_request_chan(dev, name) | | |
| dmaengine_slave_config(chan, &cfg) | | |
| dmaengine_prep_slave_single/sg/cyclic | dmaengine.h L977/1013/1039 | |
| dmaengine_submit(desc) | | |
| dma_async_issue_pending(chan) | | |
| callback 或 dma_async_is_tx_complete() 轮询 | | |
职责划分的关键点:slave DMA 场景下,框架不做 DMA 映射,客户端必须自己映射且用 DMA 控制器的 device(client.rst L122-138):
struct device *dma_dev = dmaengine_get_dma_device(chan);
nr_sg = dma_map_sg(dma_dev, sgl, sg_len); /* 客户端自己映射 */
desc = dmaengine_prep_slave_sg(chan, sgl, nr_sg, direction, flags);
证据在 provider 侧:pl330 只消费已映射的地址(drivers/dma/pl330.cpl330_prep_slave_sg() L2882-2888 直接取 sg_dma_address(sg)/sg_dma_len(sg))。
5.3 Cookie 机制:轻量级事务跟踪
dmaengine_submit() 不返回描述符指针的所有权,而是返回一个单调递增的序号(drivers/dma/dmaengine.h L29):
static inline dma_cookie_t dma_cookie_assign(struct dma_async_tx_descriptor *tx)
{
struct dma_chan *chan = tx->chan;
dma_cookie_t cookie = chan->cookie + 1;
if (cookie < DMA_MIN_COOKIE)
cookie = DMA_MIN_COOKIE;
tx->cookie = chan->cookie = cookie; /* 发号 */
return cookie;
}
完成侧推进水位线 chan->completed_cookie(dma_cookie_complete() L52),客户端用 dma_cookie_status()/dma_async_is_complete() 通过区间比较判定自己的事务是否完成(处理了序号回绕)。这个设计让客户端无需持有描述符引用即可跟踪进度。
5.4 Provider 侧:以 ARM PL330 为例
drivers/dma/pl330.c 的 probe 填充虚函数表并注册(L3125-3141):
pd->device_alloc_chan_resources = pl330_alloc_chan_resources;
pd->device_prep_dma_cyclic = pl330_prep_dma_cyclic;
pd->device_tx_status = pl330_tx_status;
pd->device_prep_slave_sg = pl330_prep_slave_sg;
pd->device_config = pl330_config;
pd->device_issue_pending = pl330_issue_pending;
ret = dma_async_device_register(pd);
完成路径:pl330_irq_handler(L2901)→ 标记 DONE 并 tasklet_schedule → pl330_tasklet(L2068-2137)中 dma_cookie_complete() 推进水位,然后先释放自旋锁再调回调(L2126-2130)——防止客户端回调里再次提交描述符造成死锁:
if (dmaengine_desc_callback_valid(&cb)) {
spin_unlock_irqrestore(&pch->lock, flags);
dmaengine_desc_callback_invoke(&cb, NULL);
spin_lock_irqsave(&pch->lock, flags);
}
5.5 dmatest:最好的客户端参考实现
drivers/dma/dmatest.c 浓缩了完整流程(dmatest_thread):dma_request_channel 按能力请通道 → dmaengine_get_dma_device → dma_map_page(L764/782)→ device_prep_dma_memcpy → 设置 tx->callback → tx->tx_submit(tx) → dma_async_issue_pending(L842)→ 回调或轮询等待 → dmaengine_terminate_sync 收尾。写 DMAEngine 客户端代码前值得通读一遍。
6. 实战:网络驱动中的 DMA 使用模式
以 Intel e1000 为例,它把两类映射用得泾渭分明,堪称教科书。
6.1 描述符环:一致性映射(长期持有)
e1000_setup_tx_resources()(e1000_main.c L1515):
txdr->size = txdr->count * sizeof(struct e1000_tx_desc);
txdr->size = ALIGN(txdr->size, 4096);
txdr->desc = dma_alloc_coherent(&pdev->dev, txdr->size, &txdr->dma,
GFP_KERNEL);
返回的虚拟地址txdr->desc 给 CPU 读写描述符,总线地址txdr->dma 写入网卡寄存器 TDBAL/TDBAH 供硬件 DMA 访问。这对指针在 struct e1000_tx_ring(e1000.h L143-162)中配对保存:
struct e1000_tx_ring {
void *desc; /* CPU 虚拟地址 */
dma_addr_t dma; /* 总线地址,写给硬件 */
unsigned int size;
struct e1000_tx_buffer *buffer_info;
...
};
6.2 数据缓冲区:流式映射(用完即解除)
发包路径 e1000_tx_map()(L2877),方向 DMA_TO_DEVICE(CPU 写、设备读):
buffer_info->dma = dma_map_single(&pdev->dev, skb->data + offset,
size, DMA_TO_DEVICE);
if (dma_mapping_error(&pdev->dev, buffer_info->dma))
goto dma_error;
发送完成回收时对称解除(e1000_unmap_and_free_tx_resource() L1956-1968),注意按映射方式区分 dma_unmap_single/dma_unmap_page。
收包路径方向相反(DMA_FROM_DEVICE):e1000_alloc_rx_buffers()(L4621)map,e1000_clean_rx_irq()(L4400)在 skb 上送协议栈前 unmap。小包走 copybreak 优化时只做同步不解除(L4341):
dma_sync_single_for_cpu(&pdev->dev, buffer_info->dma, length,
DMA_FROM_DEVICE);
6.3 演进:page_pool 的"长期持有 + 按需 sync"
现代高性能驱动(尤其 XDP 场景)发现:收包方向每次都 map/unmap,cache 操作开销巨大。net/core/page_pool.c 把流式映射固化——映射在页的整个池化生命周期内保持(page_pool_dma_map() L530-558):
dma = dma_map_page_attrs(pool->p.dev, netmem_to_page(netmem), 0,
(PAGE_SIZE << pool->p.order), pool->p.dma_dir,
DMA_ATTR_SKIP_CPU_SYNC |
DMA_ATTR_WEAK_ORDERING);
两个属性的工程含义:
DMA_ATTR_SKIP_CPU_SYNC:map/unmap 时跳过全页 cache 同步;同步推迟到每次收发报文前后,由 page_pool_dma_sync_for_device/cpu() 按实际报文长度精确执行——把 O(页大小) 的 cache 操作降为 O(报文大小)。DMA_ATTR_WEAK_ORDERING:允许设备弱序访问(ARM 等弱内存序架构省去昂贵的一致性屏障),可见性由显式 sync 点保证。
页真正离开池时才 unmap(__page_pool_release_netmem_dma() L730-751)。这套"映射常驻 + 细粒度 sync"的组合是 page_pool 在高 PPS 收包路径上性能的关键,也是流式映射 API 的最高阶用法。
7. 使用方法速查与示例代码
7.1 决策树
要给设备 DMA 一块内存?
├─ 设备自带 bus-master 能力(PCIe 网卡/NVMe)→ DMA Mapping API
│ ├─ 长期共享、双向访问(描述符环) → dma_alloc_coherent
│ ├─ 小块高频(USB 控制块) → dma_pool
│ └─ 单次传输(skb/bio) → dma_map_single / dma_map_sg
│ └─ 高频复用收包 buffer → page_pool(SKIP_CPU_SYNC)
└─ 借用 SoC 系统 DMA 控制器(UART/SPI/I2S)→ DMAEngine 六步流程
7.2 一致性映射示例(描述符环模式)
struct my_ring {
void *desc;
dma_addr_t dma;
size_t size;
};
static int my_ring_alloc(struct device *dev, struct my_ring *ring, int count)
{
ring->size = ALIGN(count * sizeof(struct my_desc), 4096);
ring->desc = dma_alloc_coherent(dev, ring->size, &ring->dma,
GFP_KERNEL);
if (!ring->desc)
return -ENOMEM;
/* ring->dma 写入硬件寄存器;ring->desc 由 CPU 访问 */
return 0;
}
static void my_ring_free(struct device *dev, struct my_ring *ring)
{
dma_free_coherent(dev, ring->size, ring->desc, ring->dma);
}
7.3 流式映射示例(skb 发包模式)
static int my_tx_map(struct device *dev, struct sk_buff *skb,
dma_addr_t *out)
{
dma_addr_t dma = dma_map_single(dev, skb->data, skb->len,
DMA_TO_DEVICE);
if (dma_mapping_error(dev, dma))
return -ENOMEM;
*out = dma;
return 0;
}
static void my_tx_unmap(struct device *dev, dma_addr_t dma, unsigned int len)
{
dma_unmap_single(dev, dma, len, DMA_TO_DEVICE); /* 与 map 参数严格对称 */
}
7.4 DMAEngine slave 六步完整示例
#include <linux/dmaengine.h>
static int my_slave_xfer(struct device *dev, void *buf, size_t len)
{
struct dma_chan *chan;
struct dma_slave_config cfg = {0};
struct dma_async_tx_descriptor *desc;
struct device *dma_dev;
dma_addr_t dma;
dma_cookie_t cookie;
/* 1. 申请通道(DT 中 dmas = <&dmac 0>; dma-names = "rx";) */
chan = dma_request_chan(dev, "rx");
if (IS_ERR(chan))
return PTR_ERR(chan);
/* 2. 配置外设参数 */
cfg.direction = DMA_DEV_TO_MEM;
cfg.src_addr = MY_FIFO_PHYS; /* 外设 FIFO 物理地址 */
cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
cfg.src_maxburst = 8;
dmaengine_slave_config(chan, &cfg);
/* 3a. 客户端自行映射(用 DMA 控制器的 device!) */
dma_dev = dmaengine_get_dma_device(chan);
dma = dma_map_single(dma_dev, buf, len, DMA_FROM_DEVICE);
if (dma_mapping_error(dma_dev, dma))
return -ENOMEM;
/* 3b. 准备描述符 */
desc = dmaengine_prep_slave_single(chan, dma, len,
DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT);
if (!desc)
return -ENOMEM;
desc->callback = my_dma_done; /* tasklet 上下文执行 */
desc->callback_param = dev;
/* 4. 提交(此后描述符归引擎所有,不得再访问) */
cookie = dmaengine_submit(desc);
/* 5. 触发硬件 */
dma_async_issue_pending(chan);
/* 6. 等回调,或轮询: */
/* while (dma_async_is_tx_complete(chan, cookie, NULL, NULL)
* != DMA_COMPLETE) cpu_relax(); */
return 0;
}
7.5 常见错误清单
| | |
|---|
| | 只用 dma_map_*() 返回的 dma_addr_t |
| | |
| cache 同步缺失,隐蔽数据损坏(ARM 上才暴露) | TO_DEVICE=发包,FROM_DEVICE=收包 |
| map/unmap 的 size/direction 不对称 | DMA API debug 报错、IOMMU 页表泄漏 | |
| | |
用 dma_map_single 映射 vmalloc 内存 | WARN_ONCE | |
| | provider 已先解锁再回调,但客户端也应避免持锁回调 |
8. 总结
- DMA 的本质是把数据搬运从 CPU 卸载到专用硬件,CPU 从搬运工变成调度员——描述符 + 完成中断是它的基本工作模式。
- 内核 DMA 子系统解决三件事:cache 一致性(方向标志驱动)、地址转换(
dma_addr_t 抽象)、寻址限制(DMA mask + SWIOTLB)。 - 两套 API 对应两类硬件:Bus Master 设备用 DMA Mapping API(一致性映射管描述符环,流式映射管数据);Slave DMA 用 DMAEngine 六步流程(request → config → prep → submit → issue → callback)。
kernel/dma/mapping.c 的三分支分发(direct / SWIOTLB / IOMMU)是理解整个体系的核心骨架——同一个 dma_map_single(),在无 IOMMU 平台上是零开销的地址换算,在有 IOMMU 平台上是 IOVA 分配 + 页表建立。- 性能工程的最高阶形态在 page_pool:把流式映射固化到页的生命周期,用
SKIP_CPU_SYNC + 细粒度 sync 把 cache 维护成本压到最低——这正是 e1000 经典模式在 10G/100G 时代的演进方向。
参考源码索引
| |
|---|
| Documentation/core-api/dma-api.rst |
| include/linux/dma-mapping.h |
| kernel/dma/mapping.c(dma_map_phys L155、dma_alloc_attrs L643) |
| kernel/dma/direct.h L85、kernel/dma/swiotlb.c L1594 |
| mm/dmapool.c |
| Documentation/driver-api/dmaengine/client.rst |
| include/linux/dmaengine.h |
| drivers/dma/dmaengine.c、内部头 drivers/dma/dmaengine.h |
| drivers/dma/pl330.c |
| drivers/dma/dmatest.c |
| drivers/net/ethernet/intel/e1000/e1000_main.c |
| net/core/page_pool.c |