SPI子系统研究框架
一、核心思想SPI 子系统的核心设计思想可以概括为一句话把怎么发数据和发什么数据彻底分离中间用一套标准化的回调接口和队列机制衔接。具体来说它围绕三个解耦展开1. 控制器驱动 ←→ 设备驱动的解耦没有 SPI 子系统时一个 SPI 触摸屏驱动要想发数据得知道控制器的寄存器地址、FIFO 怎么操作、中断怎么处理。换一个 SoC所有设备驱动都要重写。SPI 子系统的做法是在中间加一个核心层设备驱动触摸屏、spidev、ADC... ↓ 只调 spi_sync / spi_async ↓ 只传 struct spi_device struct spi_message SPI 核心层spi.c ↓ 只调 master-transfer_one / transfer_one_message ↓ 只传 struct spi_transfer 控制器驱动spi-imx.c、spi-gpio.c...设备驱动完全不知道底下是 i.MX 的 ECSPI 还是 GPIO 模拟的 bitbang——它只跟spi_device和spi_sync打交道。控制器驱动也完全不知道上面挂的是触摸屏还是 Flash——它只看到抽象的spi_transfer。这就是接口层存在的意义struct spi_master里的回调指针就是双方之间的合约。2. 传输调度 ←→ 硬件操作的解耦如果没有消息泵机制每个设备驱动调spi_sync时直接操作控制器寄存器并发场景下就乱了——A 驱动正在发数据B 驱动突然改了控制器的片选寄存器。SPI 子系统的做法是引入一个队列 kthread 的消息泵设备驱动 A 调 spi_sync → msg入队 设备驱动 B 调 spi_sync → msg入队 设备驱动 C 调 spi_async → msg入队 ↓ kthread spi0 spi_pump_messages() 从队列取 msg逐个执行 调 transfer_one → 硬件操作 完成 → 唤醒等待者或调回调这样并发调用变成了顺序执行controller 驱动不需要操心同步问题。它只需实现transfer_one——core 保证同一时间只有一个 message 在被处理。3. 同步路径 ←→ 异步路径的统一许多子系统同步和异步是两套完全不同的 API 和实现。SPI 子系统的做法是用同一个 message completion 机制统一两者// 同步spi_sync 内部msg.completespi_sync_caller_自己的等待函数 msg.context指向栈上的 completionspi_async(spi,msg)// 实际上调的是同一个 spi_asyncwait_for_completion(completion)// 阻塞等待returnmsg-status// 异步外部直接调msg.complete调用者自定义的回调函数 msg.context调用者自定义参数spi_async(spi,msg)// 入队即返回// 传输完成后 → 消息泵调 msg.complete(msg.context)两种用法走的是同一套入队→消息泵→完成的路径区别只在于msg.complete指向的是内部等待函数还是外部回调。4. 设备发现的统一抽象SPI 设备可以通过三种方式被系统知道设备树、board_info 静态表、ACPI。SPI 子系统的做法是把这三种方式统一收敛到同一个函数of_register_spi_devices → 解析 DT → spi_add_device board_info 匹配 → 查表 → spi_add_device acpi_register_spi_devices → 解析 ACPI → spi_add_device三种入口最终都走到spi_add_device→spi_setup→device_add。设备驱动的 probe 函数不用关心设备是怎么被发现的——不管是 DT 还是 board_info它得到的就是一个已经配好参数、挂在正确 master 下的spi_device。一张图总结设计思想┌──────────────────────────────────────────────────┐ │ SPI 子系统的三块基石 │ │ | │ │ ① 分层解耦 │ │ 设备驱动 ←→ 核心层(回调接口) ←→ 控制器驱动 │ │ 发什么数据 合约 怎么发数据 │ │ │ │ ② 调度标准化 │ │ 并发入队 → kthread 消息泵 → 顺序执行 → 完成 │ │ 把排队和干活分开 │ │ │ │ ③ 同步/异步统一 │ │ 同一条路径同一个结构体 │ │ 区别只在 complete 回调指向谁 │ │ │ │ ④ 设备发现统一 │ │ DT / board_info / ACPI │ │ 三条路 → 汇聚到 spi_add_device │ └──────────────────────────────────────────────────┘最终效果写 SPI 设备驱动的人只需要知道spi_sync/spi_write/spi_read写 SPI 控制器驱动的人只需要实现transfer_one/set_cs/setup两边的人不需要知道对方的存在。这就是 SPI 子系统最核心的设计思想。##**二、 核心数据结构定义在 spi.h**数据结构一句话作用struct spi_masterSPI 控制器的抽象持有回调、队列、DMA 通道、统计信息struct spi_deviceSPI 从设备的抽象通过master指针关联控制器struct spi_message一次原子传输事务包含多个 transfer 和完成回调struct spi_transfer单次全双工数据段描述发什么、收什么、多快struct spi_statistics传输统计消息数、transfer 数、错误数、字节数struct spi_board_info板级设备描述用于 board_info 方式注册设备struct spi_driverSPI 设备驱动的结构体包含 probe/remove/id_tablestruct spi_res传输过程中的资源管理类似 devres 但生命周期绑定 message三、核心函数定义在 spi.c按功能分六类① 初始化与总线函数作用spi_init()入口。postcore_initcall等级注册spi_bus_type和spi_master_classbus_register(spi_bus_type)创建/sys/bus/spi/让 SPI 设备和驱动可以匹配class_register(spi_master_class)创建/sys/class/spi_master/给 master 一个 class 归属② Master 生命周期函数作用spi_alloc_master()分配并初始化一个spi_master设dev.class spi_master_classspi_register_master()注册 master分配 bus_num、device_add、启动消息泵、扫描设备spi_unregister_master()反向注销停队列、删除设备、device_unregisterspi_master_suspend/resume()电源管理停/启消息泵③ 设备管理函数作用spi_alloc_device()分配一个spi_device设dev.bus spi_bus_typespi_add_device()校验片选 →spi_setup→device_add→ 触发 probespi_new_device()board_info →spi_alloc_device 复制参数 spi_add_devicespi_unregister_device()注销单个 SPI 设备spi_register_board_info()注册板级设备信息到全局board_listspi_setup()配置 SPI 设备的模式、速率等硬件参数调master-setup④ 消息泵传输调度核心函数作用spi_pump_messages()kthread 工作函数从队列取 message送 controller 执行__spi_pump_messages()实际执行逻辑prepare → transfer_one_message → unpreparespi_init_queue()创建 kthread初始化 worker 和 pump_messages workspi_start_queue()设置running true触发第一次 pumpspi_stop_queue()等待队列清空设置running falsespi_destroy_queue()停队列 停 kthreadspi_master_initialize_queue()上述三者的组合替换 transfer 回调、init、start__spi_queued_transfer()入队操作把 message 加入master-queue触发 pumpspi_finalize_current_message()完成当前 messageunmap、unprepare、调 complete 回调spi_finalize_current_transfer()完成单个 transfer给 transfer_one 用spi_get_next_queued_message()从队列头部取下一个 message⑤ 传输 API函数作用spi_sync()同步传输入队 →wait_for_completion→ 返回 status__spi_sync()spi_sync的内部实现spi_async()异步传输入队即返回complete 回调通知结果__spi_async()spi_async的内部实现spi_write()同步写的简单封装spi_read()同步读的简单封装spi_write_then_read()先写后读设备寄存器读写的典型模式⑥ 辅助功能函数作用spi_match_device()设备-驱动匹配规则DT → ACPI → id_table → namespi_uevent()生成 uevent发送MODALIASspi:xxxspi_register_driver()注册 SPI 设备驱动spi_map_msg()/spi_unmap_msg()DMA 映射管理spi_transfer_one_message()默认的 message 处理遍历 transfers 链表逐个调transfer_one__spi_validate()检查 message 和 transfer 参数的合法性spi_replace_transfers()替换/插入/拆分 transfersspi_split_transfers_maxsize()自动拆分过大的 transfer一张图看核心层的职责边界设备驱动层 spi_sync / spi_async / spi_write / spi_read │ ▼ ┌────────────────────────────────────┐ │ SPI 核心层 │ │ │ │ 数据结构定义 │ │ spi_master / spi_device │ │ spi_message / spi_transfer │ │ │ │ 总线管理 │ │ spi_bus_type / bus_register │ │ spi_match_device / spi_uevent │ │ │ │ Master 管理 │ │ spi_alloc_master / spi_register │ │ spi_add_device │ │ │ │ 传输调度 │ │ 消息泵 / kthread / queue │ │ spi_pump_messages │ │ spi_finalize_current_message │ │ │ │ 传输 API │ │ spi_sync / spi_async │ │ spi_write_then_read │ └──────────┬─────────────────────────┘ │ transfer_one / set_cs / setup ▼ HOST 层spi-xxx.c核心层不碰硬件寄存器HOST 层不碰队列调度。核心层提供的是一套完整的框架从总线注册到 master 管理从设备发现到传输调度从同步/异步 API 到 DMA 映射。Controller 驱动只需要实现几个回调设备驱动只需要调几个 API中间的一切都由核心层串联起来。