Lazy loaded image
Words 0Read Time 1 min
Invalid Date
🧱
核心判断
写 DMA 驱动时,最蠢也最常见的死法,就是还没把骨架立住,就急着写数据路径。正确顺序恰恰相反:先把 probe、资源、状态、接口、清理路径搭成一套能自洽的最小骨架,再往里塞 DMA 逻辑。

这一章解决什么问题

这一章不追求“功能豪华”,只追求一件事:
搭出一套最小可运行、可扩展、可收尾的 DMA 驱动骨架。
 
最小骨架至少要回答:
  • probe 时初始化什么
  • remove 时清理什么
  • 哪些资源跟设备生命周期绑定
  • 哪些资源跟文件句柄或用户上下文绑定
  • 中断、wait queue、mmapioctl 的位置怎么预留

最小驱动骨架长什么样

这图看起来朴素,但它给出一个核心顺序:
先有资源与状态,后有运行态逻辑。

设备私有结构体:别把状态撒得到处都是

一个最小骨架,建议先收敛到一个私有结构体里。比如:
这里最重要的不是字段名,而是观念:
  • DMA 资源要集中管理
  • ring 状态要集中管理
  • 运行态标志要集中管理
 
别把状态散在全局变量、回调上下文和神秘静态数组里。那不叫灵活,那叫给未来的自己挖坑。

probe 阶段最少要做哪些事

1. 识别并保存设备上下文

  • 绑定 struct device
  • 初始化私有数据
  • 建立必要的锁和等待队列

2. 声明 DMA 地址能力

这一步的核心不是形式,而是确认设备能看多大地址空间。
如果硬件支持 64-bit,就别装 32-bit 设备;如果只支持 32-bit,也别假装系统会替你兜底。

3. 分配 DMA 相关资源

最小集合通常包括:
  • 数据 buffer
  • descriptor 或 slot 元数据
  • ring 管理结构
 
这里先不用追求极致抽象,先把生命周期边界画清楚:
  • 谁分配
  • 谁释放
  • 失败时如何回滚

4. 初始化状态机和 ring 元数据

比如:
  • 每个 slot 初始状态
  • prod_idx / cons_idx
  • in-flight 计数
  • completion 标志

5. 注册中断与完成路径

至少要明确:
  • IRQ 入口在哪里
  • 完成后唤醒谁
  • 是在中断里直接收尾,还是交给下半部/线程化处理

6. 暴露用户态入口

哪怕你还没把所有语义写完,也应该先把这些位置留出来:
  • open
  • release
  • unlocked_ioctl
  • mmap
  • poll
 
别等数据路径写完才发现:原来用户态接口没地儿挂,或者清理路径根本没设计。

一个非常典型的最小骨架:coherent ring

如果你一开始只是想先把控制面骨架站住,一个很常见的起手式就是:
  • ring / descriptor 用 coherent
  • payload 暂时也先简单处理
 
它的优点不是“最强”,而是:
  • 地址和一致性语义更稳
  • 容易验证 probe / remove / irq / ring 结构是否正确
  • 适合第一版把系统先跑起来
这个模式特别适合:
  • ring buffer
  • descriptor 区
  • 长生命周期控制结构

如果 descriptor 很多又很小,早点考虑 DMA pool

小块高频对象如果直接反复 dma_alloc_coherent(),往往既笨又重。
 
更合理的方式是:
  • 大块 payload 和 ring 分开
  • 小 descriptor / command buffer 用 dma_pool
 
这不是“高级优化”,而是骨架阶段就该有的资源模型意识。

一个最小的 probe 骨架

这段代码最值钱的部分不是函数名,而是回滚结构:
每成功一步,就必须知道失败时怎么退回来。

remove 路径为什么不能敷衍

很多人把 remove 当成“收尾代码”,实际上它是检验骨架是否成熟的照妖镜。
 
remove 至少要处理:
  • 停止硬件 DMA
  • 禁止新请求进入
  • 等待或强制回收 in-flight buffer
  • 注销字符设备/接口
  • 释放 IRQ
  • 释放 DMA 资源
 
如果 remove 写不干净,说明你整个生命周期其实都没设计完整。

open / release:文件句柄不是摆设

open 阶段要想什么

  • 是共享设备上下文,还是为每个 fd 建独立 session?
  • 设备忙时是否允许多开?
  • 是否要记录当前 owner?

release 阶段要想什么

  • 这个 fd 是否仍持有未 consume 的 slot?
  • 是否需要强制归还 mmap 相关上下文?
  • 是否需要在最后一个 fd 关闭时停机或清理 ring?
 
很多 DMA 驱动不是死在传输逻辑,而是死在用户态关闭文件后,驱动还假装一切岁月静好。

中断与完成路径先占位,不要后补

一个最小完成路径,至少要做到:
  1. 确认哪一个 slot / descriptor 完成
  1. 更新状态到 READY_FOR_CPU 或完成队列
  1. 唤醒等待者
  1. 必要时安排后续处理
 
一个常见的坑是:
  • 中断先来了
  • 但软件侧没有清晰的 slot 状态机
  • 于是完成到了,谁也不知道该交给谁

ioctl / mmap / poll 的角色预留

即使这章还不展开,也必须先给它们定位:
接口
主要职责
ioctl
提交、查询、消费、控制命令
mmap
暴露数据面共享区
poll
等待完成事件或可读状态
如果你不提前给这三者分工,后面很容易出现一个经典惨案:
  • mmap 被拿来偷渡状态
  • ioctl 被拿来传数据
  • poll 只剩装饰意义

最小资源检查清单

probe 成功前必须确认

  • DMA mask 设置成功
  • buffer / descriptor 分配成功
  • ring 元数据初始化完成
  • 中断注册成功
  • 接口节点注册成功

remove 前必须确认

  • 新请求已阻止进入
  • 硬件不再继续 DMA
  • in-flight buffer 已回收或作废
  • 等待队列与会话已释放
  • 资源释放顺序正确

本章结论

  1. DMA 驱动先搭骨架,再填数据路径;别反过来。
  1. probe / remove 不是模板活,而是生命周期治理的起点和终点。
  1. coherent ring 是一个很适合起步的骨架形态,但别误以为它能替你解决后面所有数据面问题。
  1. 设备私有结构体、ring 元数据、中断完成路径、用户态入口都应在最小骨架阶段就站住位置。
  1. 回滚路径写不顺,说明资源模型还没设计好。

配套阅读

如果你正在把骨架真正落成代码,建议和下面几章配套看:

下一章要干什么

下一章开始把骨架跑起来:
  • 内核态收发闭环
  • prepare / submit / complete / reclaim
  • timeout / reset / error path
 
也就是说,从下一步开始,系统不只是“站起来”,而是要开始“跑起来”。
上一篇
Data Structure and Algorithm
下一篇
用面试拷问嵌入式技术栈

Comments
Loading...