Linux DMA 子系统学习笔记
适用源码:
rk3588-kernel-5.10.198-open-rk3588(Linux 5.10.198 Rockchip BSP)阅读目标:不仅会调用 DMA API,还能解释地址、缓存一致性、DMAEngine 框架、PL330 执行过程及常见故障。
0. 阅读主线:先解决"谁搬、搬哪里、怎样搬、搬完怎么办"
DMA 的全部知识可以顺着一笔 SPI RX 传输展开:
#mermaid-svg-aQ9gMwxnvZmsDa4z{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-aQ9gMwxnvZmsDa4z .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-aQ9gMwxnvZmsDa4z .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-aQ9gMwxnvZmsDa4z .error-icon{fill:#552222;}#mermaid-svg-aQ9gMwxnvZmsDa4z .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-aQ9gMwxnvZmsDa4z .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-aQ9gMwxnvZmsDa4z .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-aQ9gMwxnvZmsDa4z .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-aQ9gMwxnvZmsDa4z .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-aQ9gMwxnvZmsDa4z .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-aQ9gMwxnvZmsDa4z .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-aQ9gMwxnvZmsDa4z .marker{fill:#333333;stroke:#333333;}#mermaid-svg-aQ9gMwxnvZmsDa4z .marker.cross{stroke:#333333;}#mermaid-svg-aQ9gMwxnvZmsDa4z svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-aQ9gMwxnvZmsDa4z p{margin:0;}#mermaid-svg-aQ9gMwxnvZmsDa4z .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-aQ9gMwxnvZmsDa4z .cluster-label text{fill:#333;}#mermaid-svg-aQ9gMwxnvZmsDa4z .cluster-label span{color:#333;}#mermaid-svg-aQ9gMwxnvZmsDa4z .cluster-label span p{background-color:transparent;}#mermaid-svg-aQ9gMwxnvZmsDa4z .label text,#mermaid-svg-aQ9gMwxnvZmsDa4z span{fill:#333;color:#333;}#mermaid-svg-aQ9gMwxnvZmsDa4z .node rect,#mermaid-svg-aQ9gMwxnvZmsDa4z .node circle,#mermaid-svg-aQ9gMwxnvZmsDa4z .node ellipse,#mermaid-svg-aQ9gMwxnvZmsDa4z .node polygon,#mermaid-svg-aQ9gMwxnvZmsDa4z .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-aQ9gMwxnvZmsDa4z .rough-node .label text,#mermaid-svg-aQ9gMwxnvZmsDa4z .node .label text,#mermaid-svg-aQ9gMwxnvZmsDa4z .image-shape .label,#mermaid-svg-aQ9gMwxnvZmsDa4z .icon-shape .label{text-anchor:middle;}#mermaid-svg-aQ9gMwxnvZmsDa4z .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-aQ9gMwxnvZmsDa4z .rough-node .label,#mermaid-svg-aQ9gMwxnvZmsDa4z .node .label,#mermaid-svg-aQ9gMwxnvZmsDa4z .image-shape .label,#mermaid-svg-aQ9gMwxnvZmsDa4z .icon-shape .label{text-align:center;}#mermaid-svg-aQ9gMwxnvZmsDa4z .node.clickable{cursor:pointer;}#mermaid-svg-aQ9gMwxnvZmsDa4z .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-aQ9gMwxnvZmsDa4z .arrowheadPath{fill:#333333;}#mermaid-svg-aQ9gMwxnvZmsDa4z .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-aQ9gMwxnvZmsDa4z .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-aQ9gMwxnvZmsDa4z .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-aQ9gMwxnvZmsDa4z .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-aQ9gMwxnvZmsDa4z .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-aQ9gMwxnvZmsDa4z .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-aQ9gMwxnvZmsDa4z .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-aQ9gMwxnvZmsDa4z .cluster text{fill:#333;}#mermaid-svg-aQ9gMwxnvZmsDa4z .cluster span{color:#333;}#mermaid-svg-aQ9gMwxnvZmsDa4z div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-aQ9gMwxnvZmsDa4z .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-aQ9gMwxnvZmsDa4z rect.text{fill:none;stroke-width:0;}#mermaid-svg-aQ9gMwxnvZmsDa4z .icon-shape,#mermaid-svg-aQ9gMwxnvZmsDa4z .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-aQ9gMwxnvZmsDa4z .icon-shape p,#mermaid-svg-aQ9gMwxnvZmsDa4z .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-aQ9gMwxnvZmsDa4z .icon-shape .label rect,#mermaid-svg-aQ9gMwxnvZmsDa4z .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-aQ9gMwxnvZmsDa4z .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-aQ9gMwxnvZmsDa4z .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-aQ9gMwxnvZmsDa4z :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 1. CPU 不逐字节搬
为什么用 DMA
2. 准备设备可访问的地址
mapping / IOMMU / Cache
3. 找到搬运硬件
DMAEngine / channel / request
4. 描述怎样搬
方向 / 宽度 / burst / SG
5. 启动并等待完成
submit / issue / IRQ
6. 消费数据并回收
sync / unmap / terminate
后文不是概念清单:第 1 节回答"为什么",第 2~4 节回答"设备访问哪块内存",第 5~8 节回答"谁来搬以及如何启动",第 9~10 节回答"完成、并发与排错"。附件中的 6.1~6.15 对应关系如下:
| 题号 | 内容位置 |
|---|---|
| 6.1、6.6 | 第 2 节:四类地址与为什么不能 virt_to_phys() |
| 6.2、6.8 | 第 5 节:mapping 与 DMAEngine、slave DMA 流程 |
| 6.3~6.5、38 | 第 3 节:coherent/streaming、方向、map/sync/unmap、一致性 |
| 6.7 | 第 3.3 节:scatter-gather |
| 6.9 | 第 5.4 节:cyclic DMA |
| 6.10 | 第 10 节:损坏、超时与地址受限排查 |
| 6.11 | 第 4.1 节:DMA-BUF |
| 6.12~6.14 | 第 7 节:多设备竞争、控制器结构、配置参数 |
| 6.15 | 第 8.1 节:UART DMA 环形接收与解析 |
0.1 先用"SPI 收 1024 字节"认识所有角色
先不看 API。假设 SPI 外设即将收到 1024 字节,目标是放进 DDR:
text
CPU:管理员,只下任务,不逐字节搬
SPI RX FIFO:收货口,外部数据先到这里
DDR buffer:仓库货架,1024 字节最终放这里
PL330 DMAC:搬运工,从 FIFO 搬到 DDR
DMA channel:分给这次任务的一辆搬运车
DMA request line:FIFO 发给搬运工的"现在有货"铃声
descriptor:任务单,写明源、目的、长度、宽度
IRQ/callback:搬完后的回执
完整过程只有六步:
- CPU 准备 1024 字节 buffer;
- DMA mapping API 给这块 buffer 贴上"设备能识别的地址标签";
- SPI 驱动向 DMAEngine 申请连接 SPI RX request 的 channel;
- 驱动填写"SPI FIFO → buffer,共 1024 字节"的任务单;
- PL330 根据 FIFO request 搬运,CPU 同时可以做别的事;
- PL330 中断通知完成,CPU 取回 buffer 所有权并解析数据。
后文出现一个术语时,都问它属于哪一步。例如 IOMMU 属于第 2 步,burst 属于第 4~5 步,callback 属于第 6 步。
0.2 初学者第一遍怎样读
第一遍只读第 1~5 节和第 8 节,目标是讲清"为什么用 DMA、两套地址、mapping 与 DMAEngine 的区别、一次传输怎样完成"。第 6~7 节是 RK3588/PL330 源码实现,第 9~10 节是并发和调试;DMA-BUF、cyclic 和 provider 内部结构可以第二遍再读。
0.3 六个术语的一句话版本
| 术语 | 第一次阅读只需记住 |
|---|---|
| DMA | 硬件替 CPU 搬数据 |
| DMA buffer | CPU 与设备交换数据的内存 |
| DMA address | 写给设备的 buffer 地址 |
| DMA mapping | 为设备准备地址并处理一致性 |
| DMAEngine | Linux 操作通用 DMAC 的统一接口 |
| DMA-BUF | 让多个设备共享同一块 buffer 的框架 |
1. 先回答最根本的问题:为什么需要 DMA
假设 SPI 每秒收到 20 MB 数据。如果 CPU 每收到 4 字节就执行一次"读 SPI FIFO、写内存",需要处理约 500 万次搬运动作。CPU 做的事情没有计算价值,只是在两个地址间倒数据。
DMA(Direct Memory Access,直接内存访问)的核心是:让一个专用硬件主设备替 CPU 发起总线读写。
#mermaid-svg-gHS7ctGk3yTSh2uP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-gHS7ctGk3yTSh2uP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-gHS7ctGk3yTSh2uP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-gHS7ctGk3yTSh2uP .error-icon{fill:#552222;}#mermaid-svg-gHS7ctGk3yTSh2uP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-gHS7ctGk3yTSh2uP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-gHS7ctGk3yTSh2uP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-gHS7ctGk3yTSh2uP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-gHS7ctGk3yTSh2uP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-gHS7ctGk3yTSh2uP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-gHS7ctGk3yTSh2uP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-gHS7ctGk3yTSh2uP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-gHS7ctGk3yTSh2uP .marker.cross{stroke:#333333;}#mermaid-svg-gHS7ctGk3yTSh2uP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-gHS7ctGk3yTSh2uP p{margin:0;}#mermaid-svg-gHS7ctGk3yTSh2uP .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-gHS7ctGk3yTSh2uP .cluster-label text{fill:#333;}#mermaid-svg-gHS7ctGk3yTSh2uP .cluster-label span{color:#333;}#mermaid-svg-gHS7ctGk3yTSh2uP .cluster-label span p{background-color:transparent;}#mermaid-svg-gHS7ctGk3yTSh2uP .label text,#mermaid-svg-gHS7ctGk3yTSh2uP span{fill:#333;color:#333;}#mermaid-svg-gHS7ctGk3yTSh2uP .node rect,#mermaid-svg-gHS7ctGk3yTSh2uP .node circle,#mermaid-svg-gHS7ctGk3yTSh2uP .node ellipse,#mermaid-svg-gHS7ctGk3yTSh2uP .node polygon,#mermaid-svg-gHS7ctGk3yTSh2uP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-gHS7ctGk3yTSh2uP .rough-node .label text,#mermaid-svg-gHS7ctGk3yTSh2uP .node .label text,#mermaid-svg-gHS7ctGk3yTSh2uP .image-shape .label,#mermaid-svg-gHS7ctGk3yTSh2uP .icon-shape .label{text-anchor:middle;}#mermaid-svg-gHS7ctGk3yTSh2uP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-gHS7ctGk3yTSh2uP .rough-node .label,#mermaid-svg-gHS7ctGk3yTSh2uP .node .label,#mermaid-svg-gHS7ctGk3yTSh2uP .image-shape .label,#mermaid-svg-gHS7ctGk3yTSh2uP .icon-shape .label{text-align:center;}#mermaid-svg-gHS7ctGk3yTSh2uP .node.clickable{cursor:pointer;}#mermaid-svg-gHS7ctGk3yTSh2uP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-gHS7ctGk3yTSh2uP .arrowheadPath{fill:#333333;}#mermaid-svg-gHS7ctGk3yTSh2uP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-gHS7ctGk3yTSh2uP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-gHS7ctGk3yTSh2uP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gHS7ctGk3yTSh2uP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-gHS7ctGk3yTSh2uP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gHS7ctGk3yTSh2uP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-gHS7ctGk3yTSh2uP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-gHS7ctGk3yTSh2uP .cluster text{fill:#333;}#mermaid-svg-gHS7ctGk3yTSh2uP .cluster span{color:#333;}#mermaid-svg-gHS7ctGk3yTSh2uP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-gHS7ctGk3yTSh2uP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-gHS7ctGk3yTSh2uP rect.text{fill:none;stroke-width:0;}#mermaid-svg-gHS7ctGk3yTSh2uP .icon-shape,#mermaid-svg-gHS7ctGk3yTSh2uP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gHS7ctGk3yTSh2uP .icon-shape p,#mermaid-svg-gHS7ctGk3yTSh2uP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-gHS7ctGk3yTSh2uP .icon-shape .label rect,#mermaid-svg-gHS7ctGk3yTSh2uP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gHS7ctGk3yTSh2uP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-gHS7ctGk3yTSh2uP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-gHS7ctGk3yTSh2uP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 请求/握手
总线读 FIFO
总线写 RAM
完成中断
预先配置源、目的、长度
外设 FIFO
例如 SPI RX
DMA 控制器
PL330
DDR 中的缓冲区
CPU
CPU 仍然负责"管理":
- 准备可被设备访问的内存;
- 配置源地址、目的地址、长度、方向和突发参数;
- 启动 DMA;
- 在完成中断或回调里处理结果。
因此 DMA 不是"完全不需要 CPU",而是把 O(N) 次数据搬运 变成大致 O(1) 次配置 + 1 次完成通知。
1.1 DMA 何时反而不划算
DMA 有固定成本:申请通道、映射缓存、制作描述符、编程硬件和处理中断。搬 4 字节时,CPU 的一次 load/store 可能更快。因此驱动通常设置阈值:小包用 PIO,大包才用 DMA。
1.2 DMA、DMAEngine、dma-buf 是三件不同的事
| 名称 | 解决的问题 | 典型接口 |
|---|---|---|
| DMA mapping API | "这块内存怎样安全地交给设备访问?" | dma_map_single()、dma_alloc_coherent() |
| DMAEngine | "怎样统一使用独立 DMA 控制器搬数据?" | dma_request_chan()、dmaengine_prep_*() |
| dma-buf | "怎样在多个设备/驱动间共享同一缓冲区?" | exporter/importer、attachment、fence |
调用 DMAEngine 前往往还要使用 DMA mapping API;使用 dma-buf 的设备最终也需要 DMA 映射。三层相关,但不能互相替代。
1.3 DMA 会节约总线资源吗
一句话结论:DMA 不会减少有效数据必须占用的总线带宽,但会减少 CPU 中转造成的额外事务,并通过 burst 提高总线利用率。
假设把 1 MiB 数据从外设搬到内存:
- PIO:CPU 读外设 1 MiB,再写内存 1 MiB,数据在总线上经历两段 CPU 事务;
- DMA:DMA master 直接完成外设到内存的 1 MiB 搬运,并可把许多小访问合并为 burst。
所以 DMA 节约的是 CPU 指令、仲裁次数和中转开销,不是让 1 MiB 数据"零带宽"到达内存。多个 DMA 同时运行仍会争用 AXI/DDR;长 burst 虽提高吞吐,却可能增加其他 master 的等待时间。实际优化要在吞吐和延迟之间选择 burst、优先级、QoS 与 FIFO 水位。
2. 地址:虚拟地址、物理地址、总线地址与 DMA 地址(6.1、6.6)
同一块内存可能同时存在三种地址:
#mermaid-svg-UzTxHL5LrWntTNHi{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-UzTxHL5LrWntTNHi .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UzTxHL5LrWntTNHi .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UzTxHL5LrWntTNHi .error-icon{fill:#552222;}#mermaid-svg-UzTxHL5LrWntTNHi .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UzTxHL5LrWntTNHi .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UzTxHL5LrWntTNHi .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UzTxHL5LrWntTNHi .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UzTxHL5LrWntTNHi .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UzTxHL5LrWntTNHi .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UzTxHL5LrWntTNHi .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UzTxHL5LrWntTNHi .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UzTxHL5LrWntTNHi .marker.cross{stroke:#333333;}#mermaid-svg-UzTxHL5LrWntTNHi svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UzTxHL5LrWntTNHi p{margin:0;}#mermaid-svg-UzTxHL5LrWntTNHi .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-UzTxHL5LrWntTNHi .cluster-label text{fill:#333;}#mermaid-svg-UzTxHL5LrWntTNHi .cluster-label span{color:#333;}#mermaid-svg-UzTxHL5LrWntTNHi .cluster-label span p{background-color:transparent;}#mermaid-svg-UzTxHL5LrWntTNHi .label text,#mermaid-svg-UzTxHL5LrWntTNHi span{fill:#333;color:#333;}#mermaid-svg-UzTxHL5LrWntTNHi .node rect,#mermaid-svg-UzTxHL5LrWntTNHi .node circle,#mermaid-svg-UzTxHL5LrWntTNHi .node ellipse,#mermaid-svg-UzTxHL5LrWntTNHi .node polygon,#mermaid-svg-UzTxHL5LrWntTNHi .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UzTxHL5LrWntTNHi .rough-node .label text,#mermaid-svg-UzTxHL5LrWntTNHi .node .label text,#mermaid-svg-UzTxHL5LrWntTNHi .image-shape .label,#mermaid-svg-UzTxHL5LrWntTNHi .icon-shape .label{text-anchor:middle;}#mermaid-svg-UzTxHL5LrWntTNHi .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-UzTxHL5LrWntTNHi .rough-node .label,#mermaid-svg-UzTxHL5LrWntTNHi .node .label,#mermaid-svg-UzTxHL5LrWntTNHi .image-shape .label,#mermaid-svg-UzTxHL5LrWntTNHi .icon-shape .label{text-align:center;}#mermaid-svg-UzTxHL5LrWntTNHi .node.clickable{cursor:pointer;}#mermaid-svg-UzTxHL5LrWntTNHi .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-UzTxHL5LrWntTNHi .arrowheadPath{fill:#333333;}#mermaid-svg-UzTxHL5LrWntTNHi .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-UzTxHL5LrWntTNHi .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-UzTxHL5LrWntTNHi .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UzTxHL5LrWntTNHi .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-UzTxHL5LrWntTNHi .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UzTxHL5LrWntTNHi .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-UzTxHL5LrWntTNHi .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-UzTxHL5LrWntTNHi .cluster text{fill:#333;}#mermaid-svg-UzTxHL5LrWntTNHi .cluster span{color:#333;}#mermaid-svg-UzTxHL5LrWntTNHi div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-UzTxHL5LrWntTNHi .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-UzTxHL5LrWntTNHi rect.text{fill:none;stroke-width:0;}#mermaid-svg-UzTxHL5LrWntTNHi .icon-shape,#mermaid-svg-UzTxHL5LrWntTNHi .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UzTxHL5LrWntTNHi .icon-shape p,#mermaid-svg-UzTxHL5LrWntTNHi .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-UzTxHL5LrWntTNHi .icon-shape .label rect,#mermaid-svg-UzTxHL5LrWntTNHi .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UzTxHL5LrWntTNHi .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-UzTxHL5LrWntTNHi .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-UzTxHL5LrWntTNHi :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} CPU 页表
IOMMU/总线窗口
也可能恒等映射
CPU 虚拟地址 X
驱动解引用
物理地址 Y
DDR 位置
设备 DMA 地址 Z
写入设备寄存器
- CPU 虚拟地址:
void *,例如kmalloc()的返回值; - CPU 物理地址:
phys_addr_t,描述实际物理资源; - 总线地址:设备在 AXI、PCIe 等互连上发起事务时携带的地址;
- DMA 地址:
dma_addr_t,是设备看到的地址。
Linux 把驱动真正需要写给设备的总线视角地址统一称为 DMA 地址。存在 IOMMU 时它通常是 IOVA;无 IOMMU 且总线窗口简单时,它可能恰好等于 CPU 物理地址,但驱动不能依赖这个巧合。正确关系是:
c
void *cpu_addr = kmalloc(size, GFP_KERNEL);
dma_addr_t dma_addr = dma_map_single(dev, cpu_addr, size, DMA_TO_DEVICE);
/* 写给硬件的是 dma_addr,不是 cpu_addr,也不应自行 virt_to_phys() */
用一组假想数字检查自己是否理解:
text
CPU 指针 X = 0xffff800012340000 → CPU 代码读写它
物理内存位置 Y = 0x0000000112340000 → DDR 中的实际页
设备 DMA 地址 Z = 0x0000000080000000 → IOMMU 把 Z 翻译到 Y
驱动保存 X 和 Z:memcpy()使用 X,设备 DMA 地址寄存器写 Z。Y 是内核和 IOMMU 建立映射时关心的中间事实,普通驱动不应自行推导。
2.1 为什么不能随便 virt_to_phys()
virt_to_phys()只回答 CPU 页表/线性映射问题,它不会:
- 建立 IOMMU 映射;
- 创建 bounce buffer;
- 做非一致性平台需要的 cache 同步;
- 检查设备 DMA mask;
- 返回设备总线真正可见的地址。
因此"把 virt_to_phys(buf) 写入 DMA 寄存器"是典型的不可移植错误。
2.2 MMIO 地址与 DMA 地址也不同
ioremap()把外设寄存器的 CPU 物理地址映射成 CPU 可访问的 __iomem 地址,用于 readl()/writel();它不是给设备访问 RAM 用的 DMA 映射。
3. 缓存一致性与映射生命周期(6.3~6.5、题 38)
CPU 通常先读写 cache,而 DMA 直接访问内存/互连。如果两边没有一致性协议,可能出现:
DMA_TO_DEVICE:CPU 新数据还在 cache,设备从 DDR 读到旧数据;DMA_FROM_DEVICE:设备已写 DDR,CPU 仍从 cache 读旧数据;- CPU 和设备同时改同一个 cache line,产生数据覆盖。
DMA API 的价值不只是"算地址",也包括处理这类架构差异。
3.1 coherent 与 streaming 映射(6.3)
一致性内存(coherent)
c
void *cpu_addr;
dma_addr_t dma_handle;
cpu_addr = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL);
/* CPU 用 cpu_addr;设备用 dma_handle */
...
dma_free_coherent(dev, size, cpu_addr, dma_handle);
适合长期由 CPU 和设备共享的控制结构,如 descriptor ring、doorbell shadow。它保证双方对内存内容的可见性,但不等于提供访问顺序 。例如应先填描述符内容,再用 dma_wmb() 确保内容可见,最后置 OWN 位。
流式映射(streaming)
c
dma_addr_t addr = dma_map_single(dev, buf, len, DMA_TO_DEVICE);
if (dma_mapping_error(dev, addr))
return -EIO;
program_device(addr, len);
...
dma_unmap_single(dev, addr, len, DMA_TO_DEVICE);
适合一次或一段时间内单方拥有的数据缓冲区,映射应尽量短。若映射期间 CPU 与设备轮流访问,使用:
dma_sync_single_for_cpu():把所有权交回 CPU;dma_sync_single_for_device():把所有权交回设备。
本 5.10 BSP 也提供 dma_alloc_noncoherent(),它分配设备可寻址但需要显式所有权同步的内存,使用者仍要调用 dma_sync_single_for_device()/dma_sync_single_for_cpu(),并用 dma_free_noncoherent()成对释放。它不是"更灵活所以默认更好",不清楚平台 cache 行为时应优先使用 coherent 或标准 streaming API。
3.2 DMA 方向必须从设备视角理解(6.4)
| 参数 | 数据流 | 常见场景 |
|---|---|---|
DMA_TO_DEVICE |
内存 → 设备 | 网卡发送、SPI TX |
DMA_FROM_DEVICE |
设备 → 内存 | 网卡接收、SPI RX |
DMA_BIDIRECTIONAL |
双向 | 只有无法确定时才用 |
方向会影响 cache 维护和权限,不能为了省事一律写 DMA_BIDIRECTIONAL。
3.3 scatter-gather:为什么 map 后条目会减少(6.7)
一个逻辑大缓冲区可能由许多不连续物理页组成。SG 列表用多段 (地址, 长度) 描述它:
#mermaid-svg-cfrFwhlj3dpCACcO{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cfrFwhlj3dpCACcO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cfrFwhlj3dpCACcO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cfrFwhlj3dpCACcO .error-icon{fill:#552222;}#mermaid-svg-cfrFwhlj3dpCACcO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cfrFwhlj3dpCACcO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cfrFwhlj3dpCACcO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cfrFwhlj3dpCACcO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cfrFwhlj3dpCACcO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cfrFwhlj3dpCACcO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cfrFwhlj3dpCACcO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cfrFwhlj3dpCACcO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cfrFwhlj3dpCACcO .marker.cross{stroke:#333333;}#mermaid-svg-cfrFwhlj3dpCACcO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cfrFwhlj3dpCACcO p{margin:0;}#mermaid-svg-cfrFwhlj3dpCACcO .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-cfrFwhlj3dpCACcO .cluster-label text{fill:#333;}#mermaid-svg-cfrFwhlj3dpCACcO .cluster-label span{color:#333;}#mermaid-svg-cfrFwhlj3dpCACcO .cluster-label span p{background-color:transparent;}#mermaid-svg-cfrFwhlj3dpCACcO .label text,#mermaid-svg-cfrFwhlj3dpCACcO span{fill:#333;color:#333;}#mermaid-svg-cfrFwhlj3dpCACcO .node rect,#mermaid-svg-cfrFwhlj3dpCACcO .node circle,#mermaid-svg-cfrFwhlj3dpCACcO .node ellipse,#mermaid-svg-cfrFwhlj3dpCACcO .node polygon,#mermaid-svg-cfrFwhlj3dpCACcO .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cfrFwhlj3dpCACcO .rough-node .label text,#mermaid-svg-cfrFwhlj3dpCACcO .node .label text,#mermaid-svg-cfrFwhlj3dpCACcO .image-shape .label,#mermaid-svg-cfrFwhlj3dpCACcO .icon-shape .label{text-anchor:middle;}#mermaid-svg-cfrFwhlj3dpCACcO .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-cfrFwhlj3dpCACcO .rough-node .label,#mermaid-svg-cfrFwhlj3dpCACcO .node .label,#mermaid-svg-cfrFwhlj3dpCACcO .image-shape .label,#mermaid-svg-cfrFwhlj3dpCACcO .icon-shape .label{text-align:center;}#mermaid-svg-cfrFwhlj3dpCACcO .node.clickable{cursor:pointer;}#mermaid-svg-cfrFwhlj3dpCACcO .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-cfrFwhlj3dpCACcO .arrowheadPath{fill:#333333;}#mermaid-svg-cfrFwhlj3dpCACcO .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-cfrFwhlj3dpCACcO .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-cfrFwhlj3dpCACcO .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cfrFwhlj3dpCACcO .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-cfrFwhlj3dpCACcO .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cfrFwhlj3dpCACcO .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-cfrFwhlj3dpCACcO .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-cfrFwhlj3dpCACcO .cluster text{fill:#333;}#mermaid-svg-cfrFwhlj3dpCACcO .cluster span{color:#333;}#mermaid-svg-cfrFwhlj3dpCACcO div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-cfrFwhlj3dpCACcO .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-cfrFwhlj3dpCACcO rect.text{fill:none;stroke-width:0;}#mermaid-svg-cfrFwhlj3dpCACcO .icon-shape,#mermaid-svg-cfrFwhlj3dpCACcO .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cfrFwhlj3dpCACcO .icon-shape p,#mermaid-svg-cfrFwhlj3dpCACcO .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-cfrFwhlj3dpCACcO .icon-shape .label rect,#mermaid-svg-cfrFwhlj3dpCACcO .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cfrFwhlj3dpCACcO .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-cfrFwhlj3dpCACcO .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-cfrFwhlj3dpCACcO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 一个逻辑数据包
页 A:2 KiB
页 F:4 KiB
页 C:1 KiB
DMA SG 描述符链
关键规则:
c
int mapped_nents = dma_map_sg(dev, sgl, orig_nents, dir);
/* 编程硬件时遍历 mapped_nents 对应的 DMA 段 */
...
dma_unmap_sg(dev, sgl, orig_nents, dir); /* 注意这里传原始数量 */
映射层可能合并相邻段,所以 mapped_nents <= orig_nents。
3.4 map、sync、unmap 的正确顺序(6.5)
streaming buffer 的主线是"谁拥有,谁访问":
Device DMA API CPU Device DMA API CPU #mermaid-svg-rpqOKo0i7vQMseio{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-rpqOKo0i7vQMseio .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-rpqOKo0i7vQMseio .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-rpqOKo0i7vQMseio .error-icon{fill:#552222;}#mermaid-svg-rpqOKo0i7vQMseio .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rpqOKo0i7vQMseio .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-rpqOKo0i7vQMseio .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rpqOKo0i7vQMseio .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rpqOKo0i7vQMseio .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-rpqOKo0i7vQMseio .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rpqOKo0i7vQMseio .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rpqOKo0i7vQMseio .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rpqOKo0i7vQMseio .marker.cross{stroke:#333333;}#mermaid-svg-rpqOKo0i7vQMseio svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rpqOKo0i7vQMseio p{margin:0;}#mermaid-svg-rpqOKo0i7vQMseio .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-rpqOKo0i7vQMseio text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-rpqOKo0i7vQMseio .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-rpqOKo0i7vQMseio .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-rpqOKo0i7vQMseio .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-rpqOKo0i7vQMseio .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-rpqOKo0i7vQMseio #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-rpqOKo0i7vQMseio .sequenceNumber{fill:white;}#mermaid-svg-rpqOKo0i7vQMseio #sequencenumber{fill:#333;}#mermaid-svg-rpqOKo0i7vQMseio #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-rpqOKo0i7vQMseio .messageText{fill:#333;stroke:none;}#mermaid-svg-rpqOKo0i7vQMseio .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-rpqOKo0i7vQMseio .labelText,#mermaid-svg-rpqOKo0i7vQMseio .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-rpqOKo0i7vQMseio .loopText,#mermaid-svg-rpqOKo0i7vQMseio .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-rpqOKo0i7vQMseio .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-rpqOKo0i7vQMseio .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-rpqOKo0i7vQMseio .noteText,#mermaid-svg-rpqOKo0i7vQMseio .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-rpqOKo0i7vQMseio .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-rpqOKo0i7vQMseio .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-rpqOKo0i7vQMseio .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-rpqOKo0i7vQMseio .actorPopupMenu{position:absolute;}#mermaid-svg-rpqOKo0i7vQMseio .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-rpqOKo0i7vQMseio .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-rpqOKo0i7vQMseio .actor-man circle,#mermaid-svg-rpqOKo0i7vQMseio line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-rpqOKo0i7vQMseio :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 所有权交给设备,CPU 不再随意访问 所有权回到 CPU dma_map_single(..., direction) DMA address DMA 传输 完成中断 dma_unmap_single()
若同一映射要反复复用:
text
map
→ 设备访问
→ dma_sync_single_for_cpu()
→ CPU 访问/修改
→ dma_sync_single_for_device()
→ 设备再次访问
→ unmap
不要直接调用附件示例中的 dma_flush_write_buffer()或dma_flush_read_buffer():它们不是通用 Linux DMA mapping API。驱动应使用带方向的 dma_map_*()、dma_sync_*()与dma_unmap_*(),由架构代码决定是否真的执行 cache clean/invalidate。
4. DMA mask:设备到底能看多大的地址
设备地址寄存器若只有 32 位,就只能表达 0~4 GiB 的 DMA 地址。初始化时必须声明能力:
c
ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
if (ret)
ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
if (ret)
return ret;
两个 mask 的含义:
- streaming mask:
dma_map_*()返回地址的范围; - coherent mask:
dma_alloc_coherent()返回地址的范围。
若设备只能访问低地址,DMA 层可能借助 IOMMU 或 SWIOTLB bounce buffer;如果都无法满足,映射会失败。因此每次 dma_map_single() 后都要检查 dma_mapping_error()。
4.1 DMA-BUF:把同一缓冲区安全交给多个设备(6.11)
前面解决的是"一个驱动怎样映射自己的 buffer";摄像头采集后若要交给 GPU、VPU 和显示器,逐级 memcpy 会浪费带宽,因此需要 DMA-BUF。
#mermaid-svg-hTlyi77zq2KCfedv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-hTlyi77zq2KCfedv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-hTlyi77zq2KCfedv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-hTlyi77zq2KCfedv .error-icon{fill:#552222;}#mermaid-svg-hTlyi77zq2KCfedv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-hTlyi77zq2KCfedv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-hTlyi77zq2KCfedv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-hTlyi77zq2KCfedv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-hTlyi77zq2KCfedv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-hTlyi77zq2KCfedv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-hTlyi77zq2KCfedv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-hTlyi77zq2KCfedv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-hTlyi77zq2KCfedv .marker.cross{stroke:#333333;}#mermaid-svg-hTlyi77zq2KCfedv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-hTlyi77zq2KCfedv p{margin:0;}#mermaid-svg-hTlyi77zq2KCfedv .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-hTlyi77zq2KCfedv .cluster-label text{fill:#333;}#mermaid-svg-hTlyi77zq2KCfedv .cluster-label span{color:#333;}#mermaid-svg-hTlyi77zq2KCfedv .cluster-label span p{background-color:transparent;}#mermaid-svg-hTlyi77zq2KCfedv .label text,#mermaid-svg-hTlyi77zq2KCfedv span{fill:#333;color:#333;}#mermaid-svg-hTlyi77zq2KCfedv .node rect,#mermaid-svg-hTlyi77zq2KCfedv .node circle,#mermaid-svg-hTlyi77zq2KCfedv .node ellipse,#mermaid-svg-hTlyi77zq2KCfedv .node polygon,#mermaid-svg-hTlyi77zq2KCfedv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-hTlyi77zq2KCfedv .rough-node .label text,#mermaid-svg-hTlyi77zq2KCfedv .node .label text,#mermaid-svg-hTlyi77zq2KCfedv .image-shape .label,#mermaid-svg-hTlyi77zq2KCfedv .icon-shape .label{text-anchor:middle;}#mermaid-svg-hTlyi77zq2KCfedv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-hTlyi77zq2KCfedv .rough-node .label,#mermaid-svg-hTlyi77zq2KCfedv .node .label,#mermaid-svg-hTlyi77zq2KCfedv .image-shape .label,#mermaid-svg-hTlyi77zq2KCfedv .icon-shape .label{text-align:center;}#mermaid-svg-hTlyi77zq2KCfedv .node.clickable{cursor:pointer;}#mermaid-svg-hTlyi77zq2KCfedv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-hTlyi77zq2KCfedv .arrowheadPath{fill:#333333;}#mermaid-svg-hTlyi77zq2KCfedv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-hTlyi77zq2KCfedv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-hTlyi77zq2KCfedv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hTlyi77zq2KCfedv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-hTlyi77zq2KCfedv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hTlyi77zq2KCfedv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-hTlyi77zq2KCfedv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-hTlyi77zq2KCfedv .cluster text{fill:#333;}#mermaid-svg-hTlyi77zq2KCfedv .cluster span{color:#333;}#mermaid-svg-hTlyi77zq2KCfedv div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-hTlyi77zq2KCfedv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-hTlyi77zq2KCfedv rect.text{fill:none;stroke-width:0;}#mermaid-svg-hTlyi77zq2KCfedv .icon-shape,#mermaid-svg-hTlyi77zq2KCfedv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hTlyi77zq2KCfedv .icon-shape p,#mermaid-svg-hTlyi77zq2KCfedv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-hTlyi77zq2KCfedv .icon-shape .label rect,#mermaid-svg-hTlyi77zq2KCfedv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hTlyi77zq2KCfedv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-hTlyi77zq2KCfedv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-hTlyi77zq2KCfedv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Exporter
创建并拥有真实内存
dma-buf fd
跨进程传递句柄
Importer:GPU
attachment + map
Importer:VPU
attachment + map
Importer:DRM
attachment + map
- exporter 决定内存来自 GEM、CMA、DMA heap 或设备私有内存,并负责最终生命周期;
- importer 通过
dma_buf_get()取得对象,dma_buf_attach()绑定自己的struct device,再用dma_buf_map_attachment()得到适合该设备的 SG 表; - 每个设备的 DMA 地址可能不同,因为每个 attachment 可能属于不同 IOMMU domain;
- CPU 要访问共享 buffer 时必须走
dma_buf_begin_cpu_access()/dma_buf_end_cpu_access(); - fence 解决"何时能访问",DMA-BUF 解决"共享哪块内存",两者职责不同。
简化的 importer 生命周期:
c
dmabuf = dma_buf_get(fd);
attach = dma_buf_attach(dmabuf, dev);
sgt = dma_buf_map_attachment(attach, DMA_BIDIRECTIONAL);
/* 设备使用映射后的 SG 表 */
dma_buf_unmap_attachment(attach, sgt, DMA_BIDIRECTIONAL);
dma_buf_detach(dmabuf, attach);
dma_buf_put(dmabuf);
5. DMAEngine:从"地址可访问"走到"让控制器开始搬"(6.2、6.8、6.9)
DMAEngine 把驱动分为两侧:
- provider:DMA 控制器驱动,如 RK3588 的
drivers/dma/pl330.c; - client:需要搬数据的外设驱动,如
drivers/spi/spi-rockchip.c。
因此两套 API 的边界是:mapping API 产生设备能访问的地址,DMAEngine API 把这些地址和传输参数提交给通用 DMA 控制器。 网卡、NVMe 等设备自身就是 DMA master,通常只用 mapping API;SPI、UART、I2S 借助 SoC DMAC,通常两者都用。
渲染错误: Mermaid 渲染失败: Parse error on line 9: ..._request_chan(\"rx\")"| API API --> -----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'STR'
5.1 重要对象
| 对象 | 含义 |
|---|---|
struct dma_device |
一个 DMA 控制器及其能力、回调函数表 |
struct dma_chan |
一个逻辑 DMA 通道 |
struct dma_slave_config |
FIFO 地址、总线宽度、burst 等外设配置 |
struct dma_async_tx_descriptor |
一笔待提交传输的通用描述符 |
dma_cookie_t |
提交后的单调递增事务编号,用来查状态 |
"检测 DMA 能力"也要分层:设备手册、compatible/ID 表说明硬件是否自带 DMA;dma_set_mask*()验证它能使用多宽的 DMA 地址;DMAEngine client 则通过 provider 的 capability mask 和 prep 回调判断是否支持 slave、cyclic、memcpy 等传输类型。不存在一个能凭空让不支持 DMA 的设备获得 DMA 能力的通用开关。
5.2 slave DMA 标准 client 流程(6.8)
DMA 硬件 PL330 provider DMAEngine core 外设驱动 DMA 硬件 PL330 provider DMAEngine core 外设驱动 #mermaid-svg-v0JjqjrBOwpQD9XL{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-v0JjqjrBOwpQD9XL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-v0JjqjrBOwpQD9XL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-v0JjqjrBOwpQD9XL .error-icon{fill:#552222;}#mermaid-svg-v0JjqjrBOwpQD9XL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-v0JjqjrBOwpQD9XL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-v0JjqjrBOwpQD9XL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-v0JjqjrBOwpQD9XL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-v0JjqjrBOwpQD9XL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-v0JjqjrBOwpQD9XL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-v0JjqjrBOwpQD9XL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-v0JjqjrBOwpQD9XL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-v0JjqjrBOwpQD9XL .marker.cross{stroke:#333333;}#mermaid-svg-v0JjqjrBOwpQD9XL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-v0JjqjrBOwpQD9XL p{margin:0;}#mermaid-svg-v0JjqjrBOwpQD9XL .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-v0JjqjrBOwpQD9XL text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-v0JjqjrBOwpQD9XL .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-v0JjqjrBOwpQD9XL .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-v0JjqjrBOwpQD9XL .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-v0JjqjrBOwpQD9XL .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-v0JjqjrBOwpQD9XL #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-v0JjqjrBOwpQD9XL .sequenceNumber{fill:white;}#mermaid-svg-v0JjqjrBOwpQD9XL #sequencenumber{fill:#333;}#mermaid-svg-v0JjqjrBOwpQD9XL #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-v0JjqjrBOwpQD9XL .messageText{fill:#333;stroke:none;}#mermaid-svg-v0JjqjrBOwpQD9XL .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-v0JjqjrBOwpQD9XL .labelText,#mermaid-svg-v0JjqjrBOwpQD9XL .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-v0JjqjrBOwpQD9XL .loopText,#mermaid-svg-v0JjqjrBOwpQD9XL .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-v0JjqjrBOwpQD9XL .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-v0JjqjrBOwpQD9XL .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-v0JjqjrBOwpQD9XL .noteText,#mermaid-svg-v0JjqjrBOwpQD9XL .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-v0JjqjrBOwpQD9XL .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-v0JjqjrBOwpQD9XL .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-v0JjqjrBOwpQD9XL .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-v0JjqjrBOwpQD9XL .actorPopupMenu{position:absolute;}#mermaid-svg-v0JjqjrBOwpQD9XL .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-v0JjqjrBOwpQD9XL .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-v0JjqjrBOwpQD9XL .actor-man circle,#mermaid-svg-v0JjqjrBOwpQD9XL line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-v0JjqjrBOwpQD9XL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} submit 只入队,不启动 dma_request_chan(dev, "rx") 根据 DT 解析并取得通道 dmaengine_slave_config(chan, cfg) dmaengine_prep_slave_sg(...) tx descriptor dmaengine_submit(desc) dma_async_issue_pending(chan) 编程并启动通道 完成中断 tasklet / 完成 cookie callback
最小伪代码:
c
chan = dma_request_chan(dev, "rx");
if (IS_ERR(chan))
return PTR_ERR(chan);
cfg.src_addr = fifo_phys;
cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
cfg.src_maxburst = 8;
dmaengine_slave_config(chan, &cfg);
mapped = dma_map_sg(dev, sgl, nents, DMA_FROM_DEVICE);
if (!mapped)
goto release;
desc = dmaengine_prep_slave_sg(chan, sgl, mapped,
DMA_DEV_TO_MEM,
DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
if (!desc)
goto unmap;
desc->callback = rx_done;
desc->callback_param = ctx;
cookie = dmaengine_submit(desc);
if (dma_submit_error(cookie))
goto unmap;
dma_async_issue_pending(chan);
这段流程适用于"外设借助 PL330 搬运"。如果网卡、NVMe 或自定义 PCIe 设备自带 DMA engine,驱动通常不调用 dma_request_chan(),而是:
c
ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
buf = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL);
/* 把 dma_handle 写入该设备自己的 DMA descriptor/register */
/* 启动设备,等待它的完成中断 */
dma_alloc_coherent()只分配并映射内存,不会自动启动传输。buffer 也不应在每次完成 IRQ 中随手释放:通常在 probe/open 时分配,在 stop/remove 的"停止硬件 → synchronize IRQ/DMA → free"阶段释放。附件示例在 IRQ 中 free、remove 中不 free,既无法复用 buffer,也容易和硬件或 remove 并发。
三个高频陷阱:
dmaengine_submit()只入队,漏掉dma_async_issue_pending()会永远不启动;- submit 后 descriptor 所有权已交给 DMAEngine,client 不应继续改它;
- callback 通常运行在 tasklet/软中断语境,不能做会睡眠的操作。
5.3 传输类型
DMA_MEMCPY:内存到内存;DMA_SLAVE:内存和外设 FIFO 间;DMA_CYCLIC:环形搬运,音频最典型;DMA_INTERLEAVE:带行跨度/帧结构的数据;- SG:一次事务由多个内存段构成。
音频 cyclic DMA 可以形象理解为:
text
环形缓冲区:[ period0 ][ period1 ][ period2 ][ period3 ]
DMA 指针: ---> 周而复始 --->
每完成一个 period 回调一次,ALSA 填下一段或取走下一段。
5.4 cyclic DMA 怎样处理 period callback(6.9)
普通 DMA 完成后整笔事务结束;音频流和连续 UART RX 没有天然终点,所以把环形 buffer 切成多个 period,DMA 每跨过一个 period 边界通知一次,随后继续运行。
text
buffer = [P0][P1][P2][P3]
DMA: → → → → 回到 P0
CPU:完成 P0 时处理 P0,但不能踩到 DMA 正在写的下一个 period
使用 dmaengine_prep_dma_cyclic()时,buf_len必须是period_len的整数倍。callback 只应记录位置、唤醒线程或通知上层,不能假定整个 cyclic 事务已结束;停止时必须 terminate 并同步 callback 后再释放环形 buffer。
6. 设备树如何把 client 接到 PL330
RK3588 BSP 在 arch/arm64/boot/dts/rockchip/rk3588s.dtsi 中定义了 3 个 PL330:
dts
dmac0: dma-controller@fea10000 {
compatible = "arm,pl330", "arm,primecell";
reg = <0x0 0xfea10000 0x0 0x4000>;
interrupts = <GIC_SPI 86 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 87 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cru ACLK_DMAC0>;
#dma-cells = <1>;
arm,pl330-periph-burst;
};
dmac1 位于 0xfea30000,dmac2 位于 0xfed10000。#dma-cells = <1> 表示 phandle 后有一个参数,这里是外设 request ID。
SPI0 的连接是:
dts
spi0: spi@feb00000 {
reg = <0x0 0xfeb00000 0x0 0x1000>;
dmas = <&dmac0 14>, <&dmac0 15>;
dma-names = "tx", "rx";
};
含义不是"固定占用物理 channel 14/15",而是 SPI0 TX/RX 对应 PL330 的外设请求线 14/15。client 中:
c
ctlr->dma_tx = dma_request_chan(rs->dev, "tx");
ctlr->dma_rx = dma_request_chan(rs->dev, "rx");
"tx"/"rx"先匹配 dma-names 的下标,再取得 dmas 同下标的 controller phandle 和 request ID。
6.1 申请通道调用关系
渲染错误: Mermaid 渲染失败: Parse error on line 2: ...est_chan(dev, \"rx\")"] B["dma_reque -----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'STR'
7. RK3588 PL330:多设备竞争、控制器结构与参数约束(6.12~6.14)
核心文件:drivers/dma/pl330.c。
PL330 的特殊之处是它不只是一个简单"源/目的/长度"寄存器组。驱动会生成一小段 DMAC 微指令,例如 load、store、loop、wait-for-peripheral、send-event,再让通道线程执行。
7.1 DMA controller 内部有什么(6.13)
#mermaid-svg-8ewTFZuCowzHoDaP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8ewTFZuCowzHoDaP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8ewTFZuCowzHoDaP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8ewTFZuCowzHoDaP .error-icon{fill:#552222;}#mermaid-svg-8ewTFZuCowzHoDaP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8ewTFZuCowzHoDaP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8ewTFZuCowzHoDaP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8ewTFZuCowzHoDaP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8ewTFZuCowzHoDaP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8ewTFZuCowzHoDaP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8ewTFZuCowzHoDaP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8ewTFZuCowzHoDaP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8ewTFZuCowzHoDaP .marker.cross{stroke:#333333;}#mermaid-svg-8ewTFZuCowzHoDaP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8ewTFZuCowzHoDaP p{margin:0;}#mermaid-svg-8ewTFZuCowzHoDaP .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8ewTFZuCowzHoDaP .cluster-label text{fill:#333;}#mermaid-svg-8ewTFZuCowzHoDaP .cluster-label span{color:#333;}#mermaid-svg-8ewTFZuCowzHoDaP .cluster-label span p{background-color:transparent;}#mermaid-svg-8ewTFZuCowzHoDaP .label text,#mermaid-svg-8ewTFZuCowzHoDaP span{fill:#333;color:#333;}#mermaid-svg-8ewTFZuCowzHoDaP .node rect,#mermaid-svg-8ewTFZuCowzHoDaP .node circle,#mermaid-svg-8ewTFZuCowzHoDaP .node ellipse,#mermaid-svg-8ewTFZuCowzHoDaP .node polygon,#mermaid-svg-8ewTFZuCowzHoDaP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8ewTFZuCowzHoDaP .rough-node .label text,#mermaid-svg-8ewTFZuCowzHoDaP .node .label text,#mermaid-svg-8ewTFZuCowzHoDaP .image-shape .label,#mermaid-svg-8ewTFZuCowzHoDaP .icon-shape .label{text-anchor:middle;}#mermaid-svg-8ewTFZuCowzHoDaP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8ewTFZuCowzHoDaP .rough-node .label,#mermaid-svg-8ewTFZuCowzHoDaP .node .label,#mermaid-svg-8ewTFZuCowzHoDaP .image-shape .label,#mermaid-svg-8ewTFZuCowzHoDaP .icon-shape .label{text-align:center;}#mermaid-svg-8ewTFZuCowzHoDaP .node.clickable{cursor:pointer;}#mermaid-svg-8ewTFZuCowzHoDaP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8ewTFZuCowzHoDaP .arrowheadPath{fill:#333333;}#mermaid-svg-8ewTFZuCowzHoDaP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8ewTFZuCowzHoDaP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8ewTFZuCowzHoDaP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8ewTFZuCowzHoDaP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8ewTFZuCowzHoDaP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8ewTFZuCowzHoDaP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8ewTFZuCowzHoDaP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8ewTFZuCowzHoDaP .cluster text{fill:#333;}#mermaid-svg-8ewTFZuCowzHoDaP .cluster span{color:#333;}#mermaid-svg-8ewTFZuCowzHoDaP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8ewTFZuCowzHoDaP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8ewTFZuCowzHoDaP rect.text{fill:none;stroke-width:0;}#mermaid-svg-8ewTFZuCowzHoDaP .icon-shape,#mermaid-svg-8ewTFZuCowzHoDaP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8ewTFZuCowzHoDaP .icon-shape p,#mermaid-svg-8ewTFZuCowzHoDaP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8ewTFZuCowzHoDaP .icon-shape .label rect,#mermaid-svg-8ewTFZuCowzHoDaP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8ewTFZuCowzHoDaP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8ewTFZuCowzHoDaP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8ewTFZuCowzHoDaP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 外设 request lines
SPI/UART/I2S FIFO 请求
请求路由/匹配
channel 仲裁器
优先级与轮转
channel 0
源/目的/长度/状态
channel 1
channel N
AXI master 接口
完成/错误事件与 IRQ
PL330 的"描述符"最终会被编译成微指令;另一些 DMAC 则直接从内存读取硬件 descriptor 链。两种设计目的相同:让硬件连续处理多段任务,减少 CPU 每段重新编程。
7.2 多外设申请 DMA 会冲突吗(6.12)
会竞争,但正常情况下不应"串错数据":
dma_request_chan()为 slave client 分配独占逻辑通道;- request ID 把特定外设握手线绑定到正确通道;
- 通道不足时申请失败或延迟,而不是两个 client 同时拥有同一独占通道;
- 多个活跃 channel 仍会竞争 DMAC 执行槽、AXI 和 DDR 带宽,所以可能互相增加延迟。
软件层防止错误共享,硬件仲裁器解决同时运行;实时音频与大块 memcpy 并发时还要考虑优先级、burst 和 QoS,不能只看"通道申请成功"。
7.3 slave DMA 常用参数(6.14)
以 UART RX 为例,数据从固定 FIFO 流向递增内存:
c
struct dma_slave_config cfg = { 0 };
cfg.direction = DMA_DEV_TO_MEM;
cfg.src_addr = uart_phys_base + UART_RX_FIFO_REG; /* 总线地址 */
cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
cfg.src_maxburst = 8;
cfg.dst_maxburst = 8;
ret = dmaengine_slave_config(rx_chan, &cfg);
| 参数 | 决定什么 | 配错的典型后果 |
|---|---|---|
| direction | 哪侧是源/目的 | cache 操作和硬件流向错误 |
| FIFO address | 访问哪个外设寄存器 | 数据异常或总线错误 |
| address width | 每个 beat 几字节 | 错位、截断、控制器拒绝 |
| maxburst | 一次连续多少 beat | FIFO overrun/underrun 或延迟过高 |
| length/period | 一笔及一次回调的数据量 | 越界、尾字节丢失、回调过频 |
| request ID | 哪条外设握手线触发 | 永不启动或连错外设 |
burst 应与 FIFO threshold 配合:例如 RX FIFO 积累 8 字节才发请求,DMA burst 配 8 个 byte beat 才能一次高效取走;配得大于可用数据可能让通道等待,具体行为由外设握手和 DMAC flow-control 决定。
7.4 probe:怎样注册为 DMAEngine provider
pl330_probe()的关键工作:
- 读取 AMBA/硬件配置,取得通道数、外设数、事件数;
- 初始化每个
dma_pl330_chan及 submitted/work/completed 链表; - 声明能力:
DMA_MEMCPY、DMA_SLAVE、DMA_CYCLIC、DMA_INTERLEAVE等; - 填充
struct dma_device回调; dma_async_device_register(pd)注册到 DMAEngine core;of_dma_controller_register(..., of_dma_pl330_xlate, pl330)注册 DT 翻译函数。
关键回调映射:
| DMAEngine 操作 | PL330 实现 |
|---|---|
| 分配资源 | pl330_alloc_chan_resources() |
| 配置 slave | pl330_config() |
| 准备 SG | pl330_prep_slave_sg() |
| 准备 memcpy | pl330_prep_dma_memcpy() |
| 准备 cyclic | pl330_prep_dma_cyclic() |
| 提交后的启动 | pl330_issue_pending() |
| 查询状态 | pl330_tx_status() |
| 暂停 | pl330_pause() |
| 终止 | pl330_terminate_all() |
7.5 描述符状态流转
#mermaid-svg-hFHAk6aYH5K1LDNt{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-hFHAk6aYH5K1LDNt .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-hFHAk6aYH5K1LDNt .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-hFHAk6aYH5K1LDNt .error-icon{fill:#552222;}#mermaid-svg-hFHAk6aYH5K1LDNt .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-hFHAk6aYH5K1LDNt .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-hFHAk6aYH5K1LDNt .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-hFHAk6aYH5K1LDNt .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-hFHAk6aYH5K1LDNt .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-hFHAk6aYH5K1LDNt .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-hFHAk6aYH5K1LDNt .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-hFHAk6aYH5K1LDNt .marker{fill:#333333;stroke:#333333;}#mermaid-svg-hFHAk6aYH5K1LDNt .marker.cross{stroke:#333333;}#mermaid-svg-hFHAk6aYH5K1LDNt svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-hFHAk6aYH5K1LDNt p{margin:0;}#mermaid-svg-hFHAk6aYH5K1LDNt defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-hFHAk6aYH5K1LDNt g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-hFHAk6aYH5K1LDNt g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-hFHAk6aYH5K1LDNt g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-hFHAk6aYH5K1LDNt g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-hFHAk6aYH5K1LDNt g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-hFHAk6aYH5K1LDNt .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-hFHAk6aYH5K1LDNt .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-hFHAk6aYH5K1LDNt .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-hFHAk6aYH5K1LDNt .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-hFHAk6aYH5K1LDNt .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-hFHAk6aYH5K1LDNt .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-hFHAk6aYH5K1LDNt .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-hFHAk6aYH5K1LDNt .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hFHAk6aYH5K1LDNt .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-hFHAk6aYH5K1LDNt .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hFHAk6aYH5K1LDNt .edgeLabel .label text{fill:#333;}#mermaid-svg-hFHAk6aYH5K1LDNt .label div .edgeLabel{color:#333;}#mermaid-svg-hFHAk6aYH5K1LDNt .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-hFHAk6aYH5K1LDNt .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-hFHAk6aYH5K1LDNt .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-hFHAk6aYH5K1LDNt .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-hFHAk6aYH5K1LDNt .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-hFHAk6aYH5K1LDNt .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-hFHAk6aYH5K1LDNt .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-hFHAk6aYH5K1LDNt #statediagram-barbEnd{fill:#333333;}#mermaid-svg-hFHAk6aYH5K1LDNt .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-hFHAk6aYH5K1LDNt .cluster-label,#mermaid-svg-hFHAk6aYH5K1LDNt .nodeLabel{color:#131300;}#mermaid-svg-hFHAk6aYH5K1LDNt .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-hFHAk6aYH5K1LDNt .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-hFHAk6aYH5K1LDNt .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-hFHAk6aYH5K1LDNt .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-hFHAk6aYH5K1LDNt .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-hFHAk6aYH5K1LDNt .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-hFHAk6aYH5K1LDNt .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-hFHAk6aYH5K1LDNt .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-hFHAk6aYH5K1LDNt .note-edge{stroke-dasharray:5;}#mermaid-svg-hFHAk6aYH5K1LDNt .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-hFHAk6aYH5K1LDNt .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-hFHAk6aYH5K1LDNt .statediagram-note text{fill:black;}#mermaid-svg-hFHAk6aYH5K1LDNt .statediagram-note .nodeLabel{color:black;}#mermaid-svg-hFHAk6aYH5K1LDNt .statediagram .edgeLabel{color:red;}#mermaid-svg-hFHAk6aYH5K1LDNt #dependencyStart,#mermaid-svg-hFHAk6aYH5K1LDNt #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-hFHAk6aYH5K1LDNt .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-hFHAk6aYH5K1LDNt :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} prep_* 取描述符并生成请求
dmaengine_submit
issue_pending / fill_queue
PL330 完成事件
tasklet 完成 cookie
unmap + callback + 回收
FREE
PREP
submitted_list
work_list
DONE
completed_list
源码中的链表名字非常适合调试:submitted 有但 work 没有,多半没 issue 或通道没被调度;work 一直不完成,则看 request 信号、DMAC 指令、IRQ 与硬件状态。
7.6 启动与完成调用链
#mermaid-svg-nSjI7jYMiw5u13nw{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-nSjI7jYMiw5u13nw .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-nSjI7jYMiw5u13nw .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-nSjI7jYMiw5u13nw .error-icon{fill:#552222;}#mermaid-svg-nSjI7jYMiw5u13nw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-nSjI7jYMiw5u13nw .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-nSjI7jYMiw5u13nw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-nSjI7jYMiw5u13nw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-nSjI7jYMiw5u13nw .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-nSjI7jYMiw5u13nw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-nSjI7jYMiw5u13nw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-nSjI7jYMiw5u13nw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-nSjI7jYMiw5u13nw .marker.cross{stroke:#333333;}#mermaid-svg-nSjI7jYMiw5u13nw svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-nSjI7jYMiw5u13nw p{margin:0;}#mermaid-svg-nSjI7jYMiw5u13nw .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-nSjI7jYMiw5u13nw .cluster-label text{fill:#333;}#mermaid-svg-nSjI7jYMiw5u13nw .cluster-label span{color:#333;}#mermaid-svg-nSjI7jYMiw5u13nw .cluster-label span p{background-color:transparent;}#mermaid-svg-nSjI7jYMiw5u13nw .label text,#mermaid-svg-nSjI7jYMiw5u13nw span{fill:#333;color:#333;}#mermaid-svg-nSjI7jYMiw5u13nw .node rect,#mermaid-svg-nSjI7jYMiw5u13nw .node circle,#mermaid-svg-nSjI7jYMiw5u13nw .node ellipse,#mermaid-svg-nSjI7jYMiw5u13nw .node polygon,#mermaid-svg-nSjI7jYMiw5u13nw .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-nSjI7jYMiw5u13nw .rough-node .label text,#mermaid-svg-nSjI7jYMiw5u13nw .node .label text,#mermaid-svg-nSjI7jYMiw5u13nw .image-shape .label,#mermaid-svg-nSjI7jYMiw5u13nw .icon-shape .label{text-anchor:middle;}#mermaid-svg-nSjI7jYMiw5u13nw .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-nSjI7jYMiw5u13nw .rough-node .label,#mermaid-svg-nSjI7jYMiw5u13nw .node .label,#mermaid-svg-nSjI7jYMiw5u13nw .image-shape .label,#mermaid-svg-nSjI7jYMiw5u13nw .icon-shape .label{text-align:center;}#mermaid-svg-nSjI7jYMiw5u13nw .node.clickable{cursor:pointer;}#mermaid-svg-nSjI7jYMiw5u13nw .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-nSjI7jYMiw5u13nw .arrowheadPath{fill:#333333;}#mermaid-svg-nSjI7jYMiw5u13nw .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-nSjI7jYMiw5u13nw .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-nSjI7jYMiw5u13nw .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nSjI7jYMiw5u13nw .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-nSjI7jYMiw5u13nw .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nSjI7jYMiw5u13nw .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-nSjI7jYMiw5u13nw .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-nSjI7jYMiw5u13nw .cluster text{fill:#333;}#mermaid-svg-nSjI7jYMiw5u13nw .cluster span{color:#333;}#mermaid-svg-nSjI7jYMiw5u13nw div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-nSjI7jYMiw5u13nw .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-nSjI7jYMiw5u13nw rect.text{fill:none;stroke-width:0;}#mermaid-svg-nSjI7jYMiw5u13nw .icon-shape,#mermaid-svg-nSjI7jYMiw5u13nw .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nSjI7jYMiw5u13nw .icon-shape p,#mermaid-svg-nSjI7jYMiw5u13nw .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-nSjI7jYMiw5u13nw .icon-shape .label rect,#mermaid-svg-nSjI7jYMiw5u13nw .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nSjI7jYMiw5u13nw .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-nSjI7jYMiw5u13nw .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-nSjI7jYMiw5u13nw :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} dma_async_issue_pending()
pl330_issue_pending()
fill_queue()
pl330_submit_req()
_setup_req()
生成 PL330 微码
pl330_start_thread()
PL330 执行
pl330_irq_handler()
pl330_update()
pl330_tasklet()
dmaengine_desc_callback_invoke()
PL330 中硬中断只完成必要的状态更新并调度后续处理;client callback 在 tasklet 中调用。这也是为什么 provider 在调用 callback 前必须释放可能导致递归/死锁的锁。
8. 贯穿实例:RK3588 SPI RX
drivers/spi/spi-rockchip.c 中的实际流程可归纳为:
- probe 阶段通过
dma_request_chan(rs->dev, "tx"/"rx")拿通道; - 传输时构造
dma_slave_config,FIFO 物理地址来自 SPI 寄存器资源; dmaengine_prep_slave_sg()制作 RX/TX 描述符;- 安装完成回调;
dmaengine_submit();dma_async_issue_pending();- PL330 收到 SPI DMA request 后从/向 FIFO 搬运;
- DMA 回调与 SPI 控制器状态共同决定一次 SPI message 完成。
SPI FIFO PL330 spi-rockchip SPI core 用户/spidev SPI FIFO PL330 spi-rockchip SPI core 用户/spidev #mermaid-svg-u1IAJrZOBpt5K9Y0{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-u1IAJrZOBpt5K9Y0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-u1IAJrZOBpt5K9Y0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-u1IAJrZOBpt5K9Y0 .error-icon{fill:#552222;}#mermaid-svg-u1IAJrZOBpt5K9Y0 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-u1IAJrZOBpt5K9Y0 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-u1IAJrZOBpt5K9Y0 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-u1IAJrZOBpt5K9Y0 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-u1IAJrZOBpt5K9Y0 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-u1IAJrZOBpt5K9Y0 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-u1IAJrZOBpt5K9Y0 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-u1IAJrZOBpt5K9Y0 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-u1IAJrZOBpt5K9Y0 .marker.cross{stroke:#333333;}#mermaid-svg-u1IAJrZOBpt5K9Y0 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-u1IAJrZOBpt5K9Y0 p{margin:0;}#mermaid-svg-u1IAJrZOBpt5K9Y0 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-u1IAJrZOBpt5K9Y0 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-u1IAJrZOBpt5K9Y0 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-u1IAJrZOBpt5K9Y0 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-u1IAJrZOBpt5K9Y0 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-u1IAJrZOBpt5K9Y0 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-u1IAJrZOBpt5K9Y0 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-u1IAJrZOBpt5K9Y0 .sequenceNumber{fill:white;}#mermaid-svg-u1IAJrZOBpt5K9Y0 #sequencenumber{fill:#333;}#mermaid-svg-u1IAJrZOBpt5K9Y0 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-u1IAJrZOBpt5K9Y0 .messageText{fill:#333;stroke:none;}#mermaid-svg-u1IAJrZOBpt5K9Y0 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-u1IAJrZOBpt5K9Y0 .labelText,#mermaid-svg-u1IAJrZOBpt5K9Y0 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-u1IAJrZOBpt5K9Y0 .loopText,#mermaid-svg-u1IAJrZOBpt5K9Y0 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-u1IAJrZOBpt5K9Y0 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-u1IAJrZOBpt5K9Y0 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-u1IAJrZOBpt5K9Y0 .noteText,#mermaid-svg-u1IAJrZOBpt5K9Y0 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-u1IAJrZOBpt5K9Y0 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-u1IAJrZOBpt5K9Y0 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-u1IAJrZOBpt5K9Y0 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-u1IAJrZOBpt5K9Y0 .actorPopupMenu{position:absolute;}#mermaid-svg-u1IAJrZOBpt5K9Y0 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-u1IAJrZOBpt5K9Y0 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-u1IAJrZOBpt5K9Y0 .actor-man circle,#mermaid-svg-u1IAJrZOBpt5K9Y0 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-u1IAJrZOBpt5K9Y0 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} loop 外设请求 SPI_IOC_MESSAGE transfer_one 配置 SPI 时钟、位宽、阈值 config + prep SG + submit issue_pending 启动 SPI RX request / TX request 读/写 FIFO 搬运到/从 DDR callback finalize transfer 完成
这个例子同时说明:外设驱动控制协议状态机,DMA 驱动只负责搬数据。PL330 不理解 SPI 的片选、CPOL/CPHA,也不决定一条 SPI message 是否完整。
8.1 UART 使用 DMA 时如何高效解析不定长数据(6.15)
UART 字节流没有"包长寄存器",每字节中断又太贵,常用方案是 cyclic DMA 环形接收 + 空闲/超时事件 + 软件读写指针:
#mermaid-svg-bHmKtRDnuMRmf0tf{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bHmKtRDnuMRmf0tf .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bHmKtRDnuMRmf0tf .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bHmKtRDnuMRmf0tf .error-icon{fill:#552222;}#mermaid-svg-bHmKtRDnuMRmf0tf .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bHmKtRDnuMRmf0tf .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bHmKtRDnuMRmf0tf .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bHmKtRDnuMRmf0tf .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bHmKtRDnuMRmf0tf .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bHmKtRDnuMRmf0tf .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bHmKtRDnuMRmf0tf .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bHmKtRDnuMRmf0tf .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bHmKtRDnuMRmf0tf .marker.cross{stroke:#333333;}#mermaid-svg-bHmKtRDnuMRmf0tf svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bHmKtRDnuMRmf0tf p{margin:0;}#mermaid-svg-bHmKtRDnuMRmf0tf .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-bHmKtRDnuMRmf0tf .cluster-label text{fill:#333;}#mermaid-svg-bHmKtRDnuMRmf0tf .cluster-label span{color:#333;}#mermaid-svg-bHmKtRDnuMRmf0tf .cluster-label span p{background-color:transparent;}#mermaid-svg-bHmKtRDnuMRmf0tf .label text,#mermaid-svg-bHmKtRDnuMRmf0tf span{fill:#333;color:#333;}#mermaid-svg-bHmKtRDnuMRmf0tf .node rect,#mermaid-svg-bHmKtRDnuMRmf0tf .node circle,#mermaid-svg-bHmKtRDnuMRmf0tf .node ellipse,#mermaid-svg-bHmKtRDnuMRmf0tf .node polygon,#mermaid-svg-bHmKtRDnuMRmf0tf .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-bHmKtRDnuMRmf0tf .rough-node .label text,#mermaid-svg-bHmKtRDnuMRmf0tf .node .label text,#mermaid-svg-bHmKtRDnuMRmf0tf .image-shape .label,#mermaid-svg-bHmKtRDnuMRmf0tf .icon-shape .label{text-anchor:middle;}#mermaid-svg-bHmKtRDnuMRmf0tf .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-bHmKtRDnuMRmf0tf .rough-node .label,#mermaid-svg-bHmKtRDnuMRmf0tf .node .label,#mermaid-svg-bHmKtRDnuMRmf0tf .image-shape .label,#mermaid-svg-bHmKtRDnuMRmf0tf .icon-shape .label{text-align:center;}#mermaid-svg-bHmKtRDnuMRmf0tf .node.clickable{cursor:pointer;}#mermaid-svg-bHmKtRDnuMRmf0tf .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-bHmKtRDnuMRmf0tf .arrowheadPath{fill:#333333;}#mermaid-svg-bHmKtRDnuMRmf0tf .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-bHmKtRDnuMRmf0tf .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-bHmKtRDnuMRmf0tf .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bHmKtRDnuMRmf0tf .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-bHmKtRDnuMRmf0tf .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bHmKtRDnuMRmf0tf .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-bHmKtRDnuMRmf0tf .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-bHmKtRDnuMRmf0tf .cluster text{fill:#333;}#mermaid-svg-bHmKtRDnuMRmf0tf .cluster span{color:#333;}#mermaid-svg-bHmKtRDnuMRmf0tf div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-bHmKtRDnuMRmf0tf .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-bHmKtRDnuMRmf0tf rect.text{fill:none;stroke-width:0;}#mermaid-svg-bHmKtRDnuMRmf0tf .icon-shape,#mermaid-svg-bHmKtRDnuMRmf0tf .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bHmKtRDnuMRmf0tf .icon-shape p,#mermaid-svg-bHmKtRDnuMRmf0tf .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-bHmKtRDnuMRmf0tf .icon-shape .label rect,#mermaid-svg-bHmKtRDnuMRmf0tf .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bHmKtRDnuMRmf0tf .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-bHmKtRDnuMRmf0tf .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-bHmKtRDnuMRmf0tf :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} UART RX FIFO
cyclic DMA
环形 buffer
DMA 写指针
period callback
或 UART idle/timeout
工作线程
从软件读指针解析帧
处理步骤:
- DMA 持续写环形 buffer;
- period callback 或 UART idle timeout 只记录"有新数据"并唤醒线程;
- 线程用 residue 或硬件指针计算 DMA 当前写位置;
- 从软件读指针解析帧头、长度、payload、校验;
- 不完整帧留到下次,完整帧交上层,最后推进读指针;
- 写指针追上读指针前必须处理 overrun,可丢旧帧、丢新帧或扩大 buffer。
例如长度协议 [0xAA][len][payload][crc]不能假设一次 callback 正好对应一帧:一帧可能跨环形尾部,也可能一次到达多帧。高效的关键是 DMA 负责连续收字节,解析线程按协议边界消费,二者通过指针而不是 memcpy 大块数据协作。
9. 并发、内存屏障和生命周期
9.1 完成顺序
DMA 完成 IRQ 表明 DMAC 认为事务结束,但驱动还要考虑:
- 外设 FIFO 是否真的排空;
- 总线写是否已经对 CPU 可见;
- callback 与 timeout/remove 是否并发;
- descriptor 和 buffer 是否仍然有效。
9.2 停止的正确姿势
dmaengine_terminate_async():可在原子上下文发起,但返回时 callback 可能仍在运行;- 随后必须
dmaengine_synchronize(),才能安全释放 buffer/callback 上下文; dmaengine_terminate_sync():等待停止和 callback 完成,不能在原子上下文或 callback 内调用。
设备 remove 的安全顺序通常是:
text
禁止产生新请求
→ 停止外设
→ terminate/synchronize DMA
→ unmap/free DMA buffer
→ release channel
→ 关闭时钟和电源
先 free buffer 再停 DMA 会造成 use-after-free 或静默内存破坏。
10. DMA 损坏、超时与地址受限:从现象倒推层级(6.10)
10.1 基础信息
bash
dmesg | grep -Ei 'dma|pl330'
cat /sys/kernel/debug/dmaengine/summary
cat /proc/interrupts | grep -Ei 'dma|pl330'
该 BSP 的 PL330 probe 正常会打印类似:
text
Loaded driver for PL330 DMAC-...
DBUFF-... Num_Chans-... Num_Peri-... Num_Events-...
初学者可以按下面的最小闭环观察,而不是一开始就修改驱动:
text
1. dmesg 出现 Loaded driver for PL330
→ provider 已注册
2. rk3588s.dtsi 中 SPI0 有 dmas 和 dma-names
→ client 与 request line 已描述
3. spi-rockchip.c 中能找到 request → config → prep → submit → issue
→ 软件调用链闭合
4. /proc/interrupts 中 DMA IRQ 在传输后增长
→ 硬件确实运行并完成
若 debugfs 已挂载,PL330 驱动还有自身 debugfs 状态。
10.2 dmatest
内核提供 drivers/dma/dmatest.c,主要验证 DMAEngine 的内存搬运能力:
bash
modprobe dmatest
echo 1 > /sys/module/dmatest/parameters/iterations
echo 1 > /sys/module/dmatest/parameters/run
dmesg | tail
限制:通过 memcpy 测试不代表某条外设 request、FIFO 地址和握手配置正确。
10.3 分层定位表
| 现象 | 优先检查 |
|---|---|
dma_request_chan() 返回 -EPROBE_DEFER |
provider 尚未 probe、时钟/电源依赖未就绪 |
返回 -ENODEV/-ENXIO |
dmas、dma-names、request ID、provider 注册 |
| submit 后完全没动作 | 是否调用 issue_pending、外设 DMA enable、request 信号 |
| DMA 中断不增长 | IRQ、DMAC 时钟、通道启动、硬件 request |
| 数据全 0/旧数据 | 方向、map/unmap、cache 同步、FIFO 地址 |
| 数据错位或丢字节 | bus width、burst、FIFO threshold、长度对齐 |
| 只在大包失败 | SG、最大 segment、描述符池、边界限制 |
| remove/超时偶发崩溃 | terminate 与 callback 同步、buffer 生命周期 |
10.4 读源码时建立证据链
text
板级 DTS 是否 status = "okay"
→ client 节点 dmas/dma-names
→ client 的 dma_request_chan 名字
→ dma_slave_config 的 FIFO 地址/宽度/burst
→ prep/submit/issue
→ provider 的 request ID 翻译
→ PL330 IRQ 与 tasklet
→ callback 和资源回收
11. 常见错误代码审查清单
- 是否调用了
dma_set_mask*()并检查返回值; - 是否把
dma_addr_t与 CPU 指针严格分开; - 每次 map 是否检查
dma_mapping_error(); - map/unmap 的方向和长度是否完全一致;
- SG 编程使用映射后的数量,unmap 使用原始数量;
-
dma_alloc_coherent()的 handle 是否原样传给 free; - 是否误把 coherent 当作不需要内存屏障;
- submit 后是否调用 issue_pending;
- submit 后是否还访问 descriptor;
- callback 是否做了睡眠操作;
- timeout/remove 是否先同步停止 DMA 再释放缓冲区;
- cyclic DMA 的 period、buffer size 是否满足硬件限制。
12. 面试常见问题与参考回答
Q1:DMA 为什么能提高性能?是不是传输一定更快?
DMA 把重复的 load/store 从 CPU 转交给专用总线主设备,减少 CPU 指令、中断频率和上下文切换,并能与 CPU 计算并行。但 DMA 有映射、描述符、启动和完成中断的固定开销,小数据不一定比 PIO 快。
Q2:DMA 地址、物理地址、虚拟地址有什么区别?
CPU 用虚拟地址,页表把它翻译到物理地址;设备使用 DMA 地址,DMA 地址可能再经 IOMMU 翻译到物理内存。驱动必须使用 dma_map_*()返回的 dma_addr_t编程设备,不能假设它等于物理地址,更不能把 CPU 虚拟地址直接写给硬件。
Q3:dma_alloc_coherent() 与 dma_map_single()怎么选?
长期共享、频繁由 CPU 和设备交互的控制结构常用 coherent;大块 payload 的阶段性传输常用 streaming mapping。coherent 保证内容可见性但不替代内存屏障,streaming 必须遵守 map/sync/unmap 和所有权规则。
Q4:为什么 map 时要指定方向?
方向影响 cache 维护、IOMMU 权限和可能的优化。DMA_TO_DEVICE 是内存到设备,DMA_FROM_DEVICE 是设备到内存。方向写错可能导致旧数据、数据破坏或权限错误。
Q5:dmaengine_submit()为什么不立即启动?
submit 只把描述符放入 pending 队列并分配 cookie,允许一次准备多笔事务;dma_async_issue_pending()才通知 provider 将队列提交硬件。把"排队"和"敲门"分开能批量化启动。
Q6:DMAEngine 中 provider 和 client 分别是什么?
provider 是 DMA 控制器驱动,实现 struct dma_device回调;client 是 SPI、I2S 等外设驱动,使用统一 DMAEngine API。设备树把 client 的逻辑名字映射到 provider 和硬件 request ID。
Q7:scatter-gather 的价值是什么?
它允许设备一次处理物理不连续的多个内存段,减少把数据先复制到连续大缓冲区的开销。映射后段可能被合并,编程硬件用映射后的段数,解除映射仍传原始段数。
Q8:cache coherent 是否表示不需要 dma_wmb()?
不是。coherent 解决"最终看见相同内容",内存屏障解决"看见的先后顺序"。例如描述符字段必须先于 OWN 位对设备可见,需要 dma_wmb()约束顺序。
Q9:RK3588 上外设 DMA 的主要实现是什么?
该 BSP 的 rk3588s.dtsi定义了 dmac0、dmac1、dmac2,compatible 为 arm,pl330。provider 是 drivers/dma/pl330.c,通过 DMAEngine 和 OF DMA 注册;SPI/I2S 等节点用 dmas与 dma-names连接相应 request 线。
Q10:DMA 完成后偶发崩溃,怎么查?
先查生命周期竞争:timeout/remove 是否在 DMA 或 callback 尚未结束时释放 buffer/context。正确做法是阻止新事务、停止外设、terminate DMA,并通过 synchronize 或 sync terminate 等待回调退出,再 unmap/free。
Q11:IOMMU 对 DMA 有什么作用?
它把设备 DMA 地址翻译为物理地址,可以让有限寻址设备访问高端内存、把分散页组织成连续 IOVA,并进行设备级隔离。驱动使用通用 DMA API,通常无需知道底层是直通映射还是 IOMMU。
Q12:为什么 DMA memcpy 测试通过,SPI DMA 仍可能失败?
memcpy 只证明 DMAC 的内存到内存通路可用;SPI slave DMA 还依赖 request ID、FIFO 地址、方向、位宽、burst、FIFO threshold、外设 DMA enable 和握手时序。
Q13:DMA 会节约总线资源吗?
不会减少有效数据本身需要的带宽,但会省掉 CPU 中转事务,并用 burst 降低仲裁和协议开销;多个 DMA 并发仍会争用互连和 DDR。
Q14:DMA mapping 与 DMAEngine 有什么区别?
mapping 解决"这块内存以什么地址和一致性规则交给设备",DMAEngine 解决"怎样驱动通用 DMAC 搬运";自带 DMA master 的网卡通常只用前者,借助 PL330 的 SPI/UART 通常两者都用。
Q15:DMA-BUF 为什么可以零拷贝?
exporter 把同一底层内存导出,多个 importer 各自 attach 并映射到自己的 DMA 地址域,数据不必逐级 memcpy;零拷贝不等于零同步,仍需处理 ownership、cache 和 fence。
Q16:多个外设申请 DMA 会冲突吗?
DMAEngine 用独占逻辑 channel 和 request ID 防止串用,但通道数量有限且活跃通道仍竞争 DMAC、AXI 与 DDR,因此可能申请失败或吞吐/时延互相影响。
Q17:UART DMA 为什么常用环形缓冲区?
UART 是无天然包边界的连续字节流;cyclic DMA 负责持续收数,idle/period 事件只唤醒解析线程,线程通过读写指针处理粘包、拆包和跨环,既降低中断率又不假设一次回调等于一帧。
13. 源码阅读索引
本工程关键文件
Documentation/core-api/dma-api-howto.rst:DMA mapping 入门;Documentation/core-api/dma-api.rst:DMA API 定义;Documentation/driver-api/dmaengine/client.rst:DMAEngine client;Documentation/driver-api/dmaengine/provider.rst:DMAEngine provider;include/linux/dma-mapping.h:mapping API;include/linux/dmaengine.h:DMAEngine 对象和内联调用;drivers/dma/dmaengine.c:DMAEngine core;drivers/dma/of-dma.c:设备树通道匹配;drivers/dma/pl330.c:RK3588 使用的 PL330 provider;drivers/dma/dmatest.c:DMAEngine 测试;drivers/spi/spi-rockchip.c:真实 client 示例;arch/arm64/boot/dts/rockchip/rk3588s.dtsi:dmac 与 client 连接。
在线参考
- Linux Dynamic DMA mapping Guide
- Linux DMA API
- Linux DMAEngine client API
- Linux DMAEngine provider API
- Kernel Tour:dma-mapping:用"设备访问 DDR 需要设备视角地址"组织概念。
- Kernel Tour:DMA Engine:区分 provider、client 与 mapping user。
- AlanTu:Dynamic DMA mapping Guide 中文整理:辅助理解地址视角;API 结论仍以本 BSP 文档为准。
14. 一页复习
text
DMA 本质:专用硬件替 CPU 成为总线 master 搬数据
地址:
CPU VA --页表--> PA
DMA address --IOMMU/总线--> PA
设备只能拿 dma_map_* 返回的 dma_addr_t
内存:
coherent:常驻共享控制结构,仍需顺序屏障
streaming:payload,遵守 map → device → unmap/sync
DMAEngine:
request_chan → slave_config → prep → submit → issue
→ IRQ → tasklet → callback
RK3588:
3 个 arm,pl330 控制器
provider = drivers/dma/pl330.c
client 示例 = drivers/spi/spi-rockchip.c
DTS 用 dmas + dma-names + request ID 连接
排障:
先地址/方向/缓存,再 DT/request,再 prep-submit-issue,
最后查 IRQ/callback/生命周期。