Lazy loaded image
Words 0Read Time 1 min
Invalid Date
🔍
核心判断
源码拆解的重点不是“把代码读完”,而是把设计取舍读出来:它为什么这么分层,为什么这样切状态,为什么这里用 coherent、那里用 streaming,为什么 reset 放在这个点做。看不出这些,读再多源码也只是路过文本。

这一章解决什么问题

前面九章已经把 DMA 的核心原则都立住了。现在该回到真实代码世界,看成熟驱动是怎么把这些原则落下去的。
 
这一章不追求覆盖所有驱动,而是建立一套 源码阅读抓手。以后你不管看音频、网络、视频还是采集驱动,都能按同一套问题框架下刀。

看 DMA 源码,先别急着读函数细节

很多人一打开源码就开始:
  • 顺着调用栈看
  • 看见 API 就查文档
  • 看见结构体就往下翻
 
然后十分钟后脑内一团浆糊。
 
正确姿势应该先问四个问题:
  1. 数据面在哪里
  1. 控制面在哪里
  1. 生命周期怎么推进
  1. 异常路径怎么收口
 
这四个问题一旦立住,源码就从“树林”变成“地图”。

推荐拆解顺序:先骨架,后血肉

第一步:看 probe / init

先确认:
  • 设备上下文怎么建
  • DMA 能力怎么声明
  • buffer / descriptor 怎么分配
  • 中断和完成路径在哪里挂上
 
probe 是整套系统的建筑图纸,不是无聊样板间。

第二步:看 runtime data path

也就是:
  • submit 在哪
  • completion 在哪
  • reclaim 在哪
  • ring 怎么推进
 
这里要抓的不是每一行代码,而是:
谁在什么时候接手 buffer。

第三步:看 error path

包括:
  • timeout
  • reset
  • stop / close
  • remove
 
如果一个驱动连 error path 都没写像样,那它再能跑,也只是 happy path 表演艺术。

一套你可以直接复用的源码阅读模板

阅读维度
要问什么
资源初始化
DMA 资源、ring、irq、上下文在哪里建立?
地址与内存
哪些结构用 coherent,哪些路径用 streaming?
状态机
slot / descriptor 的 owner 和 done 语义在哪?
提交路径
什么时候把 buffer 正式交给设备?
完成路径
完成后如何通知上层、如何回收?
异常路径
超时、reset、stop 时怎么作废旧状态?
用户态边界
数据如何暴露给上层?谁负责消费确认?

为什么是这三类案例,而不是随便挑三篇源码

之所以选 音频周期流 + 网卡 ring + 持续采集 这三类,不是为了凑数量,而是为了把 DMA 世界里三种最关键的工程矛盾拆开看:
案例类型
最值得看的矛盾
你会学到什么
音频周期流
节拍稳定性 vs 吞吐
period、watermark、cyclic 语义怎么落到真实硬件节奏
网卡 ring
高吞吐并发 vs 回收秩序
descriptor ring、批量 completion、producer/consumer 推进
持续采集
长期运行 vs 消费协议
circular DMA、采样窗口、old_pos -> pos 消费边界
也就是说,这三类案例分别对应:
  • 节拍型系统
  • 吞吐型系统
  • 持续输入型系统
 
把这三类都看透,你对 DMA 的理解才不会只停在某一个垂直场景里自我感动。

推荐选 2~3 类不同风格的驱动拆

1. 周期流驱动

比如音频、ADC、持续采样类。
 
重点看:
  • cyclic / period 语义
  • completion 节奏
  • ring / period 边界

2. 高吞吐 ring 驱动

比如网络、视频采集、私有 ring 设备。
 
重点看:
  • descriptor queue
  • ownership 迁移
  • producer / consumer 指针
  • 批量 completion / reclaim

3. 持续采集类驱动

比如 ADC 周期采样、UART circular DMA 接收、各类持续输入型采集设备。
 
重点看:
  • trigger / sampling window / latency budget
  • IDLE / HT / TC 这类事件点怎么参与交接
  • 环形缓冲区回卷与 old_pos -> pos 的消费协议

具体案例 A:i.MX6UL fsl-sai 应该怎么读

你现有专题里已经有一篇旧稿可作为案例入口:
 
这个案例特别值钱,因为它天然能讲清三件事:
  1. 真实 DMA 和“软件模拟 DMA”不是一个量级的问题
  1. 音频类周期流为什么高度依赖 cyclic / period 语义
  1. CPU DAI 驱动与 Platform DMA 层为什么要分工

看这个驱动时先抓角色定位

fsl-sai 不是“包办一切的 DMA 驱动”,它更像:
  • CPU DAI 驱动
  • SAI 硬件接口配置者
  • DMA 参数提供者
 
真正的数据搬运常常落在:
  • i.MX 平台私有 DMA 路径
  • 或 DMAEngine / PCM 框架层
 
这个观察很重要,因为很多人第一次看音频驱动,最大误解就是:以为看到一个驱动文件,就以为所有 DMA 语义都在里面。

再抓数据面和控制面的边界

在 fsl-sai 这种驱动里:
  • 控制面 往往是寄存器配置、FIFO watermark、DMA 参数组织
  • 数据面 则由 DMA 控制器和 PCM buffer 通路去搬
 
也就是说,它的关键价值不是“亲手 memcpy 数据”,而是把:
  • FIFO 寄存器地址
  • burst 大小
  • 通道方向
  • 时钟与格式配置
正确交给 DMA 生态里的下一层

重点问题 1:为什么 FIFO watermark 这么重要

在音频这种周期流场景里,watermark 决定的是:
  • 什么时候发 DMA 请求
  • 一次搬多少
  • 软件多久收到一次完成节奏
 
它背后的工程问题不是“寄存器写多少”,而是:
period 语义和实时性如何落到真实硬件节拍上。

重点问题 2:为什么这类驱动要高度关心 burst / maxburst

因为 burst 不只是吞吐参数,它还会影响:
  • FIFO 触发节奏
  • 总线访问颗粒度
  • period 回调稳定性
  • underrun / overrun 风险
 
所以看到 maxburst 这类参数,别把它当数字细节,要把它理解成“数据流拍点控制”。

重点问题 3:为什么“真实 DMA vs 模拟 DMA”这个对比特别值钱

因为它能把两个世界一下拉开:
维度
模拟 DMA / 软件定时推进
真实 DMA
数据搬运
CPU / 定时器模拟推进
DMA 控制器真实搬运
CPU 占用
节奏来源
软件时钟
硬件 FIFO / DMA 请求
适用场景
学习 / 仿真 / 测试
生产环境
这个对比能帮你真正理解:
真实 DMA 不是“把定时器回调换个名字”,而是把数据搬运责任交给了独立硬件通路。

具体案例 B:网卡 ring DMA 应该怎么读

作为对照案例,新增了一页:
 
它最适合补上另一种思维:
  • 音频案例强调 周期流节拍
  • 网卡案例强调 descriptor ring 并发推进、批量回收和 NAPI poll

看网卡案例时优先抓什么

  • TX / RX ring 分别如何组织
  • descriptor 上有哪些状态位
  • producer / consumer 指针谁推进
  • TX completion 后如何 unmap / free skb
  • RX buffer 用完后如何补回 ring
  • reset 后 ring 如何重建,旧 completion 如何作废
 
也就是说,网卡案例更像 DMA 世界里的 高吞吐并发治理课

从案例里提炼的阅读抓手

当你看类似 fsl-sai 或网卡 ring 驱动这样的成熟实现时,优先抓这些问题:
  • DMA 参数在哪里组出来
  • FIFO / watermark 或 ring / tail 如何驱动 DMA 节奏
  • completion 的节拍是谁提供
  • 驱动层次分工边界在哪里
  • stop / suspend / resume / reset 时数据流如何停稳

看源码时不要只盯 API,要盯“设计信号”

信号 1:为什么这块内存这么分

如果你看到:
  • descriptor 一套
  • payload 一套
  • 状态页又一套
 
不要只问“它们在哪定义”,要问:
为什么作者不把它们混在一起。

信号 2:为什么 completion 在这里推进

如果 completion 到来后:
  • 立刻更新状态
  • 唤醒等待者
  • 延迟回收 payload
 
要看的是背后的生命周期划分,而不是表面上的函数调用。

信号 3:为什么 reset 这么写

成熟驱动在 reset 前后,往往会:
  • 停新请求
  • 清旧状态
  • 重建 ring
  • 丢弃旧 completion
 
这不是作者啰嗦,而是因为 DMA 世界里“旧状态残留”足以把新会话一起带进沟里。

一个推荐的阅读姿势

第 1 遍:只看结构

  • 设备结构体
  • probe / remove
  • irq handler
  • 提交函数
  • reset / stop 函数

第 2 遍:只画状态图

  • 哪些状态存在
  • 哪些迁移合法
  • 何处切 owner

第 3 遍:只看异常路径

  • 超时怎么收
  • reset 怎么收
  • remove 怎么收
 
不要三遍混在一遍里读。那样最容易把自己读成缓存不一致。

一套“实现 → 设计原因”映射法

看到一段实现时,别停在“它做了什么”,继续问:
看到的实现
要追问的设计原因
先写 descriptor 再 doorbell
是不是在保证设备不会看到半成品?
completion 后先同步再通知
是不是在保证 CPU / 用户读到最新数据?
remove 前先停 DMA
是不是在防止资源释放后硬件仍继续写?
reset 后重建 ring
是不是在消灭旧世界残留状态?
watermark 触发 DMA 请求
是不是在把硬件 FIFO 节奏映射成软件可控 period?
NAPI poll 批量回收 descriptor
是不是在用吞吐换更复杂的 completion 边界?
只要你开始这样读源码,就不会再把代码看成杂乱操作,而会看到背后的工程逻辑链。

最容易犯的源码阅读错误

错误 1:只盯函数,不盯状态机

结果读完一堆函数,还是不知道 slot 到底归谁。

错误 2:只盯 happy path,不看 reset / remove

结果你会误以为驱动写得很优雅,直到真实系统出异常时才发现作者根本没把坑填平。

错误 3:把所有奇怪写法都当历史包袱

有些代码确实是包袱,但很多“看起来绕”的写法,恰恰是在处理 DMA 的隐蔽边界问题。

本章结论

  1. 读 DMA 源码的重点,不是追函数,而是抓数据面、控制面、状态机和异常路径。
  1. probe、submit、completion、reset、remove 是最值得优先读的五个点。
  1. 像 fsl-sai 这类音频驱动,额外要抓 FIFO watermark、period 节拍和层次分工。
  1. 像网卡 ring 这类驱动,额外要抓 descriptor ring、批量回收和并发推进。
  1. 真正有价值的阅读方式,是把“实现细节”反推成“设计理由”。

下一章要干什么

下一章开始把这些知识沉淀成可表达的工程判断:
  • 面试高频问答
  • 场景题
  • 高分回答框架
 
因为最终目标不只是“会写”,还要“会解释为什么这么写”。
 
i.MX6UL fsl-sai 驱动分析:真实 DMA vs 模拟 DMA
🌐
案例 B|网卡 ring DMA 驱动应该怎么看
📡
案例 C|ADC / UART 这类持续采集 DMA 驱动应该怎么看
 
上一篇
Data Structure and Algorithm
下一篇
用面试拷问嵌入式技术栈

Comments
Loading...