第五阶段:SPI 数据通路
第五阶段SPI 数据通路分三个小节数据结构 → 入队 → 消息泵。5.1 数据结构spi_transfer 和 spi_messagespi_transfer —— 单个传输段structspi_transfer{constvoid*tx_buf;// 发什么NULL 发全零void*rx_buf;// 收到哪NULL 丢掉收到的数据unsignedlen;// 多少字节u32 speed_hz;// 本次传输的时钟0 用设备的默认值u8 bits_per_word;// 字长0 用设备的默认值unsignedcs_change:1;// 传输完后是否翻转 CSu16 delay_usecs;// 传输完后延时多少微秒structlist_headtransfer_list;// 知识点3挂在 spi_message.transfers 链表上};spi_message —— 一组 transfer 的原子包裹structspi_message{structlist_headtransfers;// 知识点3transfer 链表锚点structspi_device*spi;// 发给哪个设备void(*complete)(void*context);// 知识点2完成后的回调函数void*context;// 回调的参数unsignedframe_length;// 预计传输总字节数unsignedactual_length;// 实际传输了多少字节intstatus;// 0成功负数失败structlist_headqueue;// 知识点3挂在 master-queue 上的挂钩};两者的关系一个spi_message包含多个spi_transfer。CS 在一个 message 的所有 transfer 之间保持拉低原子操作。spi_message ┌──────────────────────────────────────────┐ │ transfers (锚点) │ │ ├── spi_transfer[0] │ │ │ tx_buf 命令(1字节) │ │ │ rx_buf NULL │ │ │ cs_change 0 ← CS 不变 │ │ │ │ │ ├── spi_transfer[1] │ │ │ tx_buf 地址(3字节) │ │ │ rx_buf NULL │ │ │ cs_change 0 ← CS 不变 │ │ │ │ │ └── spi_transfer[2] │ │ tx_buf NULL │ │ rx_buf 数据(256字节) │ │ cs_change 1 ← 传完翻转CS │ │ │ │ spi → spi0.0 (Flash) │ │ complete 完成回调 │ │ status -EINPROGRESS → 0 │ └──────────────────────────────────────────┘ CLK 波形: CS: ‾‾‾\_________________________/‾‾‾ transfer[0] transfer[1] transfer[2] 发送命令 发送地址 接收数据类比spi_message 是一个快递包裹。spi_transfer 是包裹里的每件物品。CS 是正在送货中的标志——所有物品搬完之前标志不撤。5.2 入队路径spi_sync → 消息挂到 master-queue完整路径spi_sync(spi,message)└──__spi_sync(spi,message)├── 构造 completion信号量 ├── message-completespi_complete ← 完成回调醒来的时候调这个 ├── message-contextdone ← 回调的参数 ├──__spi_queued_transfer(spi,msg)← 消息入队 ├──__spi_pump_messages(master)← 马上尝试发送 └──wait_for_completion(done)← 阻塞等传输完成 │ │...消息泵处理中...│ └── spi_finalize_current_message └── message-complete(context)spi_complete(done)└──complete(done)→ 唤醒 wait_for_completion逐行解读staticint__spi_sync(structspi_device*spi,structspi_message*message){DECLARE_COMPLETION_ONSTACK(done);intstatus;structspi_master*masterspi-master;unsignedlongflags;status__spi_validate(spi,message);if(status!0)returnstatus;message-completespi_complete;message-contextdone;message-spispi;SPI_STATISTICS_INCREMENT_FIELD(master-statistics,spi_sync);SPI_STATISTICS_INCREMENT_FIELD(spi-statistics,spi_sync);/* If were not using the legacy transfer method then we will * try to transfer in the calling context so special case. * This code would be less tricky if we could remove the * support for driver implemented message queues. */if(master-transferspi_queued_transfer){spin_lock_irqsave(master-bus_lock_spinlock,flags);trace_spi_message_submit(message);status__spi_queued_transfer(spi,message,false);spin_unlock_irqrestore(master-bus_lock_spinlock,flags);}else{statusspi_async_locked(spi,message);}if(status0){/* Push out the messages in the calling context if we * can. */if(master-transferspi_queued_transfer){SPI_STATISTICS_INCREMENT_FIELD(master-statistics,spi_sync_immediate);SPI_STATISTICS_INCREMENT_FIELD(spi-statistics,spi_sync_immediate);__spi_pump_messages(master,false);}wait_for_completion(done);statusmessage-status;}message-contextNULL;returnstatus;}第 1 步构造信号量DECLARE_COMPLETION_ONSTACK(done);// 栈上分配一个 completioncompletion 是一个信号量——用来让一个线程等待另一个线程完成某事。类比你快递寄出去了回家等。快递员送到后按门铃。你听到门铃去开门。——completion 就是这门铃。第 2 步设置完成回调message-completespi_complete;// 知识点2函数指针message-contextdone;// 传进回调的参数spi_complete内部就一行staticvoidspi_complete(void*arg){complete(arg);// 唤醒在当前 completion 上等待的线程}第 3 步消息入队__spi_queued_transfer(spi,message,false);// ↑ need_pump false下面手动触发第二阶段学过的函数——list_add_tail(msg-queue, master-queue)。第 4 步立即尝试发送__spi_pump_messages(master,false);不用等 kthread_worker 被唤醒——直接在调用者线程上下文中尝试发一次。如果传送带闲着!master-busy马上开始传输。如果正忙它入队了就行。第 5 步阻塞等待wait_for_completion(done);调用者在这里睡觉。等到消息泵处理完消息调用spi_complete才能醒来继续往下执行。// 醒来后statusmessage-status;// 0 成功负数 失败message-contextNULL;returnstatus;同步 vs 异步spi_sync: spi_async: 入队 入队 立即尝试发送 不等待 wait_for_completion阻塞 直接返回 传输完成 ← 醒来 传输完成 → 回调通知 返回结果spi_sync 内部调了 spi_async 的入队逻辑加了一个 completion 等待。异步是地基同步是异步 睡觉。__spi_queued_transfer —— 把消息挂到货架上staticint__spi_queued_transfer(structspi_device*spi,structspi_message*msg,bool need_pump){structspi_master*masterspi-master;spin_lock_irqsave(master-queue_lock,flags);// ① 拿锁关中断if(!master-running){// ② 队列停了spin_unlock_irqrestore(master-queue_lock,flags);return-ESHUTDOWN;// 快递站关门了}msg-actual_length0;// ③ 清零实际传输字节数msg-status-EINPROGRESS;// ④ 标记传输进行中list_add_tail(msg-queue,master-queue);// ⑤ 包裹放货架if(!master-busyneed_pump)// ⑥ 快递员闲着kthread_queue_work(master-kworker,// ⑦ 拍醒他master-pump_messages);spin_unlock_irqrestore(master-queue_lock,flags);// ⑧ 放锁return0;}逐行解读① spin_lock_irqsave拿自旋锁 关本地 CPU 中断。和之前 Q3 讲的一样——中断上下文可能同时调这个函数必须用 spinlock。② !master-running队列被关了比如控制器正在注销。拒绝新消息。③ actual_length 0还没传任何字节。④ status -EINPROGRESSwait_for_completion醒来后会读message-status。传输完成时spi_finalize_current_message把它改成 0成功或负数失败。现在是进行中。⑤ list_add_tail知识点 3——消息挂到master-queue链表尾部。FIFO 队列先来先发。⑥ !master-busy need_pumpbusy为 true 说明当前正在传输另一个消息。这时不需要唤醒快递员——他正在送别的包裹送完会在__spi_pump_messages里回头检查货架自动发现你刚放的新消息。⑦ kthread_queue_work提交pump_messages任务到kworker唤醒kworker_task线程。__spi_pump_messages —— 发动机段落 A入队检查lines 1104-1162spin_lock_irqsave(master-queue_lock,flags);// 检查1正在处理消息直接返回if(master-cur_msg){spin_unlock_irqrestore(master-queue_lock,flags);return;// 上一个包裹还没送完这次不用管}// 检查2正在空闲清理中延后到 kthread 处理if(master-idling){kthread_queue_work(master-kworker,master-pump_messages);return;}// 检查3队列空了if(list_empty(master-queue)||!master-running){if(!master-busy){return;// 货架空着我也闲着 → 直接走}// busytrue 但队列空了 → 传输刚完成做收尾工作master-busyfalse;// ... unprepare_transfer_hardware关掉时钟、释放硬件资源return;}关键逻辑busy和队列状态是两件事。busytrue队列空 最后一个消息刚传完需要收尾。段落 B从货架上取货lines 1164-1173// 取队列头最早挂上去的消息master-cur_msglist_first_entry(master-queue,structspi_message,queue);// 知识点13从 list_head 反推出 spi_message// 从队列中移除list_del_init(master-cur_msg-queue);// 标记传送带正在使用中if(master-busy)was_busytrue;// 上一个消息还在处理不太可能但兼容elsemaster-busytrue;// 传送带我占了spin_unlock_irqrestore(master-queue_lock,flags);段落 C发送消息lines 1175-1236mutex_lock(master-io_mutex);// 拿 IO 锁保护传输过程// ① 第一次传输准备硬件if(!was_busymaster-prepare_transfer_hardware)master-prepare_transfer_hardware(master);// 知识点2函数指针// 类比快递员出发前检查车胎、打火// ② 控制器驱动的准备回调if(master-prepare_message)master-prepare_message(master,master-cur_msg);// 知识点2控制器驱动可以在这里做消息级别的准备// 比如配置 DMA 描述符、设置 CS// ③ DMA 映射retspi_map_msg(master,master-cur_msg);// 把 tx_buf/rx_buf 映射成 DMA 地址// 如果有设备不支持 DMA走 PIO 路径跳过这个// ④ 传输核心中的核心retmaster-transfer_one_message(master,master-cur_msg);// 知识点2函数指针调用// 默认实现 spi_transfer_one_message// → 遍历 msg-transfers 链表// → 对每个 transfer 调用 master-transfer_one// → spi_imx_transfer → DMA/PIO → spi_imx_isr → spi_finalize_current_messageout:mutex_unlock(master-io_mutex);关键传输完成后谁再次调用消息泵master-transfer_one_message内部比如spi_imx_transfer在传输完成后调用spi_finalize_current_messagevoidspi_finalize_current_message(structspi_master*master){msgmaster-cur_msg;master-cur_msgNULL;master-busyfalse;// ← 传送带空闲了msg-status0;// 成功msg-actual_lengthxxx;// 实际传输字节数msg-complete(msg-context);// ← 知识点2调用 spi_complete → 唤醒等待者// ← 这里再次调用 __spi_pump_messages检查货架上有没有新包裹spi_idle_runtime_pm(master);// 内部可能调 __spi_pump_messages}这就是之前的 Q3 答案——快递员送完一个包裹回头检查货架发现有新包裹就继续送。货架空了才下班。完整时序线程1调用者 kworker_task快递员 ─────────── ──────────── spi_sync(msg) ├── __spi_queued_transfer → 入队 ├── __spi_pump_messages spi_pump_messages │ └── 取 msg → busytrue ← 被唤醒 │ └── transfer_one_message ├── spi_imx_transfer │ └── DMA开始 │ └── 写寄存器 → 等中断 │ │ └── wait_for_completion 睡觉 ← CPU 切走线程1睡眠 中断来了 → spi_imx_isr └── spi_finalize_current_message ├── msg-complete 唤醒线程1 └── __spi_pump_messages 检查下一条 队列空 → busyfalse → 下班睡觉 线程1醒来message-status 0 返回 spi_sync → 数据已传输完成Q1L1spi_transfer和spi_message是什么关系一个spi_message包含多个spi_transfer时CS 片选信号在 transfer 之间怎么变化哪个字段决定了这个行为spi_message包含多个spi_transferCS 在一个 message 的所有 transfer 之间保持拉低原子操作。cs_change:1决定这个行为cs_change 0默认CS 在一个 message 的所有 transfer 之间保持拉低。cs_change 1当前 transfer 传完后CS短暂拉高再拉低再传下一个 transfer。cs_change只在 transfer 结束时检查——在spi_transfer_one_message的第 1045 行if(xfer-cs_change){if(list_is_last(xfer-transfer_list,msg-transfers))keep_cstrue;// 最后一个 transferCS 保持拉低elsespi_set_cs(msg-spi,false);// CS 拉高udelay(10);spi_set_cs(msg-spi,true);// CS 拉低}Q2L2spi_sync中wait_for_completion(done)在睡觉等待。谁在什么时候唤醒它message-complete和message-context在这场睡觉和醒来中各自扮演什么角色在传数完数据后通过spi_finalize_current_message的mesg-complete(mesg-context);来唤醒它“这两个字段是谁设的”。答案在第 ② 步// __spi_sync 中设置message-completespi_complete;// ← 函数指针唤醒函数message-contextdone;// ← 传给唤醒函数的参数complete是怎么叫醒门铃context是叫醒哪个哪个 completion 对象。spi_complete被调用时拿到done调用complete(done)——这个done正是spi_sync在wait_for_completion(done)等的那个。Q3L3__spi_pump_messages发送完当前消息后什么时候会去取下一个消息假如队列里有 3 个消息在排队画出发送这 3 个消息的完整时序图标注每次上下文切换、每次__spi_pump_messages调用是谁触发的。时间 → msg1 到达(kworker) msg2 到达 msg3 到达 │ │ │ ├─ __spi_pump_messages │ │ │ └─ transfer_one(msg1) │ │ │ └─ DMA开始... │ │ │ │ │ │ DMA完成 → ISR │ │ │ └─ spi_finalize(msg1) │ │ │ ├─ ⑤ 提交 pump → __spi_pump_messages ← 第1次自动触发 │ │ ├─ transfer_one(msg2) │ │ │ └─ 发 msg2... │ │ │ │ └─ ⑥ complete(done1) → 唤醒线程A │ 线程A醒来msg1 done │ │ │ │ 线程B: spi_sync(msg3) │ │ ├─ 入队 msg3 │ │ ├─ __spi_pump_messages │ │ │ cur_msg ! NULL! → return不做事 │ │ └─ wait_for_completion(done3) 睡觉 │ │ │ │ DMA完成 → ISR │ │ └─ spi_finalize(msg2) │ │ ├─ ⑤ 提交 pump → __spi_pump_messages ← 第2次自动触发 │ │ │ ├─ 取 msg3! │ │ │ └─ transfer_one(msg3) │ │ └─ ⑥ complete(done2) → 唤醒线程B? NO │ │ done2 是谁msg2 的等待者 │ │ │ DMA完成 → ISR │ └─ spi_finalize(msg3) │ ├─ ⑤ 提交 pump → 队列空busyfalse下班 │ └─ ⑥ complete(done3) → 唤醒线程B 线程B醒来msg3 done关键点消息谁触发的__spi_pump_messagesmsg1spi_sync调用者线程手动触发msg2spi_finalize_current_message(msg1)里的kthread_queue_workmsg3spi_finalize_current_message(msg2)里的kthread_queue_work从 msg2 开始每处理完一个消息自动触发下一个。不需要调用者再手动触发。调用者在第二步__spi_pump_messages发现cur_msg ! NULL快递员正忙直接去睡觉。醒来时消息已经被自动泵处理完了。

相关新闻

Janus-Pro多模态模型本地部署与优化指南

Janus-Pro多模态模型本地部署与优化指南

1. Janus-Pro多模态模型概述Janus-Pro是DeepSeek最新开源的多模态模型,采用了一种创新的自回归框架设计,统一了视觉理解和生成能力。这个7B参数的模型通过独特的架构设计,在单一模型中实现了过去需要多个专用模型才能完成的任务。模型的核心创…

2026/7/18 5:01:10阅读更多 →
深入解析C++虚函数指针与虚函数表:多态底层实现与性能优化

深入解析C++虚函数指针与虚函数表:多态底层实现与性能优化

1. 项目概述:从“指针”到“多态”的认知跃迁如果你写过C,或者哪怕只是听说过这门语言,“多态”这个词你肯定不陌生。教科书上会告诉你,多态是面向对象编程的三大特性之一,它允许我们通过基类的指针或引用来调用派生类…

2026/7/18 5:01:10阅读更多 →
2026独立开发者AI增强开发环境实操指南

2026独立开发者AI增强开发环境实操指南

1. 这不是工具清单,而是一份独立开发者2026年生存实操地图“2026年最新个人AI编程工具推荐:独立开发者必看的全流程工具清单”——这个标题里藏着三个被多数人忽略的关键信号:时间锚点(2026)、角色限定(独立…

2026/7/18 5:01:10阅读更多 →
Rust交叉编译与条件编译实战指南

Rust交叉编译与条件编译实战指南

1. Rust交叉编译与条件编译概述作为一名长期使用Rust进行跨平台开发的工程师,我深刻体会到交叉编译和条件编译在实际项目中的重要性。Rust作为一门系统级编程语言,其强大的编译器和工具链为开发者提供了出色的跨平台支持能力。不同于其他语言的交叉编译方…

2026/7/18 6:11:14阅读更多 →
gRPC超时控制原理与Go实践指南

gRPC超时控制原理与Go实践指南

1. gRPC超时控制的核心价值在分布式系统中,超时控制是保障系统稳定性的第一道防线。想象一下这样的场景:一个订单服务调用库存服务时,如果库存服务因为数据库压力大响应变慢,没有超时控制的订单服务会一直等待,最终导致…

2026/7/18 6:11:14阅读更多 →
Docker Model Runner:用OCI标准统一管理本地大模型

Docker Model Runner:用OCI标准统一管理本地大模型

1. 项目概述:为什么本地跑大模型这件事,终于不那么“反人类”了我从2022年就开始在生产环境里折腾本地大模型——最早是用Python脚本硬拉llama.cpp,后来试过Ollama、Text Generation WebUI、甚至自己写Dockerfile打包transformersaccelerate。…

2026/7/18 6:11:14阅读更多 →
2024年Flutter生态现状与开发者实践指南

2024年Flutter生态现状与开发者实践指南

1. Flutter生态现状全景扫描:2024年末开发者必读数据报告2024年对Flutter而言是个关键转折年——这个曾经被质疑"能否扛住原生开发冲击"的跨平台框架,如今已经悄然占领了移动开发领域的战略高地。最近在整理团队技术栈时,我意外发现…

2026/7/18 6:11:14阅读更多 →
互联网核心技术演进与TCP/IP协议实战解析

互联网核心技术演进与TCP/IP协议实战解析

1. 互联网技术发展史的核心脉络2003年,当我在大学计算机实验室第一次接触56K调制解调器时,那个刺耳的拨号音和缓慢的网页加载速度,让我对互联网底层技术产生了浓厚兴趣。如今重读《Internet History, Technology and Security》这本经典教材&…

2026/7/18 6:11:14阅读更多 →
技术博客内容策划:从分类规范到质量评估的完整指南

技术博客内容策划:从分类规范到质量评估的完整指南

最近在技术社区看到不少关于《绝望的主妇》第三季第十一集的讨论,乍看似乎与技术无关,但深入分析后发现这背后其实反映了当前技术内容创作和知识管理中的一个典型问题:如何从看似无关的素材中提取技术价值,以及如何避免低质量内容…

2026/7/18 6:06:14阅读更多 →
VSCode TypeScript 环境配置对比:全局安装 vs 项目本地安装的4个关键差异

VSCode TypeScript 环境配置对比:全局安装 vs 项目本地安装的4个关键差异

VSCode TypeScript 环境配置对比:全局安装 vs 项目本地安装的4个关键差异当你在VSCode中启动一个新的TypeScript项目时,第一个技术决策往往从安装方式开始。这个看似简单的选择——全局安装还是项目本地安装——实际上会深刻影响你的开发流程、团队协作和…

2026/7/17 10:42:55阅读更多 →
智慧树刷课插件:5分钟实现自动化学习的智能助手

智慧树刷课插件:5分钟实现自动化学习的智能助手

智慧树刷课插件:5分钟实现自动化学习的智能助手 【免费下载链接】zhihuishu 智慧树刷课插件,自动播放下一集、1.5倍速度、无声 项目地址: https://gitcode.com/gh_mirrors/zh/zhihuishu 智慧树刷课插件是一款专为智慧树在线教育平台设计的Chrome浏…

2026/7/17 8:31:03阅读更多 →
Steam创意工坊下载器WorkshopDL:跨平台游戏模组获取的终极解决方案

Steam创意工坊下载器WorkshopDL:跨平台游戏模组获取的终极解决方案

Steam创意工坊下载器WorkshopDL:跨平台游戏模组获取的终极解决方案 【免费下载链接】WorkshopDL WorkshopDL - The Best Steam Workshop Downloader 项目地址: https://gitcode.com/gh_mirrors/wo/WorkshopDL 你是否在GOG或Epic Games Store购买了心仪的游戏…

2026/7/17 13:22:23阅读更多 →
从模糊意图到可执行指令:Claude PRD中Prompt Engineering与需求颗粒度的5级映射法则

从模糊意图到可执行指令:Claude PRD中Prompt Engineering与需求颗粒度的5级映射法则

更多请点击: https://kaifayun.com 第一章:从模糊意图到可执行指令:Claude PRD中Prompt Engineering与需求颗粒度的5级映射法则 在Claude驱动的产品需求文档(PRD)生成实践中,原始业务意图往往以自然语言片…

2026/7/18 0:00:14阅读更多 →
Cursor配置生成失效?3大隐藏陷阱+4行修复代码,资深工程师连夜整理的紧急补救清单

Cursor配置生成失效?3大隐藏陷阱+4行修复代码,资深工程师连夜整理的紧急补救清单

更多请点击: https://codechina.net 第一章:Cursor配置生成失效?3大隐藏陷阱4行修复代码,资深工程师连夜整理的紧急补救清单 Cursor 配置生成突然失效,是近期高频报障场景。表面看是 cursor.config.json 未更新或 LSP…

2026/7/18 0:00:14阅读更多 →
某智驾大牛创业

某智驾大牛创业

作者:钟声编辑:Mark出品:红色星际头图:智能驾驶图片据悉,国内某头部智驾公司端到端模型技术大牛Z投身创业,并且已经拿到融资。Z不仅是该头部公司内部最年轻的对标阿里P10级别技术负责⼈,更是业内…

2026/7/18 0:00:14阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/17 22:48:46阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/17 13:22:38阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/17 17:26:50阅读更多 →