Linux SPI 驱动框架 transfer 机制深度剖析:从 spi_message 到 DMA 传输的性能链路

发布时间:2026/7/9 16:20:17

Linux SPI 驱动框架 transfer 机制深度剖析:从 spi_message 到 DMA 传输的性能链路
Linux SPI 驱动框架 transfer 机制深度剖析从 spi_message 到 DMA 传输的性能链路一、从用户空间 write() 到硬件 MOSI/MISO一次 SPI 传输经历了什么嵌入式开发者常通过/dev/spidevX.Y操作 SPI 设备ioctl(SPI_IOC_MESSAGE)或write()调用看似简单。但当数据量超过 PIO FIFO 深度典型的 64 字节或在高速模式下50MHz出现数据错位时问题的根因往往隐藏在 SPI 子系统的层层抽象之下。以一个典型的 TFT LCD 驱动为例每次刷新一帧需要传输 320×240×2153.6KB 的数据。使用默认的 PIO 模式时CPU 占用率高达 60%且帧率被限制在 15fps。切换到 DMA 模式后CPU 占用率降至 5%帧率提升至 30fps。这两者之间的差距就隐藏在 SPI 子系统的transfer机制实现中。本文从spi_message数据结构出发逐层深入到spi_transfer的 PIO/DMA 双路径实现并通过实际的 DMA 传输代码展示性能优化的关键点。二、spi_message 队列模型与 transfer 执行路径Linux SPI 子系统的核心模型是消息队列。每次 SPI 通信被抽象为一个spi_message对象其中包含一个或多个spi_transfer链表节点。每个spi_transfer描述一次连续的数据传输同一 message 内的多个 transfer 共享片选信号CS实现保持片选有效期间的多段传输。flowchart TD subgraph Userspace [用户空间] U1[write(fd, buf, len)] -- U2[ioctl(SPI_IOC_MESSAGE)] end subgraph SPIDEV [spidev 驱动层] U2 -- S1[spidev_message()] S1 -- S2[构建 spi_message spi_transfer 链表] S2 -- S3[spi_async() / spi_sync()] end subgraph Core [SPI 核心层] S3 -- C1[__spi_queued_transfer()] C1 -- C2[插入控制器的传输队列 (kthread_worker)] C2 -- C3[spi_pump_messages() 从队列取出] C3 -- C4[__spi_pump_messages() 请求总线] C4 -- C5[master-transfer_one_message()] end subgraph Transfer [传输执行路径] C5 -- T1{can_dma() ?} T1 --|是| T2[DMA 路径\nspi_transfer_one_message_dma()] T1 --|否| T3[PIO 路径\nspi_transfer_one_message_pio()] T2 -- T4[等待 DMA 完成中断] T3 -- T5[CPU 轮询/中断逐字节传输] T4 -- T6[spi_finalize_current_message()] T5 -- T6 end T6 -- T7[向用户空间返回结果] style C3 fill:#0f3460,stroke:#16213e,color:#e0e0e0 style C5 fill:#533483,stroke:#3a2a6e,color:#e0e0e0关键数据结构spi_message和spi_transfer定义在linux/spi/spi.h中。// spi_message: 一次 SPI 通信事务的完整描述 struct spi_message { struct list_head transfers; // spi_transfer 链表头 struct spi_device *spi; unsigned is_dma_mapped:1; // 是否由调用者完成 DMA 映射 /* 完成回调 */ void (*complete)(void *context); void *context; unsigned frame_length; unsigned actual_length; // 实际传输字节数 int status; // 传输结果: 0成功, 负值错误码 struct list_head queue; // 控制器内部队列节点 void *state; // 控制器私有状态 }; // spi_transfer: 一段连续数据传输的描述 struct spi_transfer { const void *tx_buf; // 发送缓冲区 void *rx_buf; // 接收缓冲区 unsigned len; // 传输长度 dma_addr_t tx_dma; // DMA 发送地址 (is_dma_mapped1 时有效) dma_addr_t rx_dma; // DMA 接收地址 struct sg_table tx_sg; // 离散聚合发送表 struct sg_table rx_sg; // 离散聚合接收表 unsigned cs_change:1; // 传输后是否释放片选 unsigned cs_off:1; // 传输间是否关闭片选 u8 bits_per_word; // 字长 u16 delay_usecs; // 传输间延迟 u32 speed_hz; // 本次传输的时钟频率 struct list_head transfer_list; // 链表节点 };在标准传输路径中ISR 在transfer_one完成后调用spi_finalize_current_message()该函数唤醒等待中的用户空间调用者。三、DMA 模式 SPI 驱动实现与性能优化3.1 SPI 控制器驱动的 DMA 传输实现/** * file spi-dma-engine.c * brief 基于 DMA 的 SPI 控制器驱动核心传输逻辑。 * * 设计背景: * 基于 STM32MP1 的 SPI6 控制器, 支持 DMA 发送和接收。 * FIFO 深度为 16×32bit, 超过 64 字节的传输自动切换到 DMA 模式。 * * DMA 模式带来的核心优化: * 1. CPU 从逐字节搬运中解放, 可并行处理其他任务。 * 2. 双缓冲 (Ping-Pong) DMA 进一步消除传输间的配置延迟。 * 3. 使用 scatter-gather 支持非连续内存区域的一次性传输。 */ #include linux/spi/spi.h #include linux/dmaengine.h #include linux/dma-mapping.h #include linux/completion.h #include linux/interrupt.h /* 控制器私有数据结构 */ struct my_spi_master { struct spi_controller *ctlr; struct dma_chan *tx_chan; /* DMA 发送通道 */ struct dma_chan *rx_chan; /* DMA 接收通道 */ dma_addr_t tx_dma_addr; /* 发送缓冲区物理地址 */ dma_addr_t rx_dma_addr; /* 接收缓冲区物理地址 */ void __iomem *regs; /* SPI 寄存器基地址 */ struct completion dma_complete; /* DMA 完成信号 */ struct spi_transfer *cur_transfer; /* 当前正在处理的 transfer */ }; /* SPI 寄存器偏移 (示例, 实际由芯片手册定义) */ #define SPI_CR1 0x00 #define SPI_SR 0x08 #define SPI_DR 0x0C #define SPI_CR2 0x04 #define SPI_SR_RXNE BIT(0) /* 接收非空 */ #define SPI_SR_TXE BIT(1) /* 发送空 */ #define SPI_CR2_TXDMAEN BIT(1) /* 发送 DMA 使能 */ #define SPI_CR2_RXDMAEN BIT(0) /* 接收 DMA 使能 */ /** * brief DMA 传输完成回调: 通知 SPI 核心层传输结束。 * * 说明: 此回调在 DMA 中断上下文执行。 * 如果同时使用了 TX 和 RX DMA, 需要等待两者都完成后才触发完成信号。 * 此处通过 atomic 计数器跟踪完成状态。 */ static void my_spi_dma_callback(void *arg) { struct my_spi_master *spi_master (struct my_spi_master *)arg; /* 等待 RX DMA 也完成 (通过检查 SPI_SR 中的 BSY 位) */ unsigned long timeout jiffies msecs_to_jiffies(100); while (readl_relaxed(spi_master-regs SPI_SR) BIT(7)) { if (time_after(jiffies, timeout)) { dev_err(spi_master-ctlr-dev, DMA 传输超时: SPI 仍处于忙碌状态\n); break; } cpu_relax(); } /* 禁用 DMA 请求 */ u32 cr2 readl_relaxed(spi_master-regs SPI_CR2); cr2 ~(SPI_CR2_TXDMAEN | SPI_CR2_RXDMAEN); writel_relaxed(cr2, spi_master-regs SPI_CR2); complete(spi_master-dma_complete); } /** * brief DMA 模式下的 SPI 传输实现。 * * 工作流: * 1. 检查是否需要 DMA (len FIFO_DEPTH * 2)。 * 2. 为 TX 和 RX 缓冲区建立 DMA 映射。 * 3. 配置并启动 DMA 传输。 * 4. 使能 SPI 控制器的 DMA 请求。 * 5. 等待 DMA 完成中断。 * * return 0 成功, 负值 错误码 */ static int my_spi_transfer_one_dma( struct spi_controller *ctlr, struct spi_device *spi, struct spi_transfer *transfer) { struct my_spi_master *spi_master spi_controller_get_devdata(ctlr); struct dma_async_tx_descriptor *tx_desc NULL, *rx_desc NULL; enum dma_ctrl_flags dma_flags DMA_CTRL_ACK | DMA_PREP_INTERRUPT; int ret; if (transfer-len 0) { return 0; /* 空传输, 直接返回 */ } /* Step 1: TX DMA 映射与配置 */ if (transfer-tx_buf) { dma_addr_t tx_phys; if (transfer-tx_dma) { tx_phys transfer-tx_dma; /* 使用调用者提供的物理地址 */ } else { tx_phys dma_map_single( ctlr-dma_tx-device-dev, (void *)transfer-tx_buf, /* 虚拟地址 */ transfer-len, DMA_TO_DEVICE ); if (dma_mapping_error(ctlr-dma_tx-device-dev, tx_phys)) { dev_err(ctlr-dev, TX DMA 映射失败\n); return -ENOMEM; } } tx_desc dmaengine_prep_slave_single( spi_master-tx_chan, tx_phys, transfer-len, DMA_MEM_TO_DEV, dma_flags ); if (!tx_desc) { dev_err(ctlr-dev, TX DMA 描述符准备失败\n); return -EIO; } /* 设置 DMA 完成回调 */ tx_desc-callback my_spi_dma_callback; tx_desc-callback_param spi_master; } /* Step 2: RX DMA 映射与配置 */ if (transfer-rx_buf) { dma_addr_t rx_phys; if (transfer-rx_dma) { rx_phys transfer-rx_dma; } else { rx_phys dma_map_single( ctlr-dma_rx-device-dev, transfer-rx_buf, transfer-len, DMA_FROM_DEVICE ); if (dma_mapping_error(ctlr-dma_rx-device-dev, rx_phys)) { dev_err(ctlr-dev, RX DMA 映射失败\n); return -ENOMEM; } } rx_desc dmaengine_prep_slave_single( spi_master-rx_chan, rx_phys, transfer-len, DMA_DEV_TO_MEM, dma_flags ); if (!rx_desc) { dev_err(ctlr-dev, RX DMA 描述符准备失败\n); return -EIO; } } /* Step 3: 提交 DMA 传输 */ reinit_completion(spi_master-dma_complete); if (tx_desc) { dmaengine_submit(tx_desc); dma_async_issue_pending(spi_master-tx_chan); } if (rx_desc) { dmaengine_submit(rx_desc); dma_async_issue_pending(spi_master-rx_chan); } /* Step 4: 使能 SPI 控制器的 DMA 请求 */ u32 cr2 readl_relaxed(spi_master-regs SPI_CR2); if (tx_desc) cr2 | SPI_CR2_TXDMAEN; if (rx_desc) cr2 | SPI_CR2_RXDMAEN; writel_relaxed(cr2, spi_master-regs SPI_CR2); /* Step 5: 等待 DMA 完成 (带超时保护) */ if (!wait_for_completion_timeout( spi_master-dma_complete, msecs_to_jiffies(1000))) { dev_err(ctlr-dev, DMA 传输超时!\n); ret -ETIMEDOUT; /* 强制终止 DMA */ if (tx_desc) dmaengine_terminate_all(spi_master-tx_chan); if (rx_desc) dmaengine_terminate_all(spi_master-rx_chan); goto err_cleanup; } ret 0; err_cleanup: /* 取消 DMA 映射 (仅对自行映射的缓冲区) */ if (transfer-tx_buf !transfer-tx_dma) { dma_unmap_single(ctlr-dma_tx-device-dev, spi_master-tx_dma_addr, transfer-len, DMA_TO_DEVICE); } if (transfer-rx_buf !transfer-rx_dma) { dma_unmap_single(ctlr-dma_rx-device-dev, spi_master-rx_dma_addr, transfer-len, DMA_FROM_DEVICE); } return ret; }3.2 can_dma 决策函数的实现/** * brief 判断本次传输是否应使用 DMA。 * * 规则: * 1. 传输长度 FIFO 深度的 2 倍 (即 128 字节) → 使用 DMA * 2. 传输长度 ≤ FIFO 深度 → 使用 PIO (DMA 建立开销 PIO 开销) * 3. 边界对齐要求: DMA 缓冲区必须与 Cache Line (64 字节) 对齐, * 否则可能因 Cache 一致性回写导致数据损坏。 * * 测量依据: 在 400MHz STM32MP1 上, DMA 建立开销约 5μs, * PIO 传输一个字节约 0.12μs。平衡点约为 42 字节。 * 取 FIFO 深度 ×2 128 字节提供安全裕量。 */ static bool my_spi_can_dma( struct spi_controller *ctlr, struct spi_device *spi, struct spi_transfer *transfer) { /* 规则1: 长度阈值 */ if (transfer-len 128) { return false; } /* 规则2: 地址对齐检查 (dma_map_single 对非对齐地址行为与硬件相关) */ if (transfer-tx_buf ((unsigned long)transfer-tx_buf (ARCH_DMA_MINALIGN - 1))) { dev_dbg(ctlr-dev, TX 缓冲区未对齐, 回退到 PIO 模式 (addr%p)\n, transfer-tx_buf); return false; } if (transfer-rx_buf ((unsigned long)transfer-rx_buf (ARCH_DMA_MINALIGN - 1))) { return false; } /* 规则3: DMA 通道可用性 */ struct my_spi_master *spi_master spi_controller_get_devdata(ctlr); if (!spi_master-tx_chan || !spi_master-rx_chan) { return false; } return true; }四、PIO vs DMA 的边界效应与实时性权衡SPI DMA 传输并非在所有场景下都是最优选择。其适用边界和代价需要精确评估小数据量传输DMA 的配置开销DMA 描述符准备、Memory Barrier、Cache 刷新约 5~10μs。当传输长度小于约 40 字节时PIO 速度更快。这是因为dmaengine_prep_slave_single()至少需要完成 3 次 MMIO 写入来配置 DMA 引擎。实时性折衷DMA 完成中断的延迟Interrupt Latency约 1~3μs而 PIO 模式下数据立即可用。对于需要在 SPI 传输后立即读取计算结果的场景如读取 ADC 采样值后快速处理PIO 模式避免了中断延迟的不可预测性。Cache 一致性问题在带 Data Cache 的 CPU如 Cortex-A7上DMA 传输前后必须进行 Cache 刷新操作。dma_map_single()在DMA_TO_DEVICE方向会执行 Cache Clean将 Cache 内容写回DMA_FROM_DEVICE方向会执行 Cache Invalidate。这两步操作对 L1 Cache 的扫描时间与传输数据量成正比——对于 64KB 的数据块Cache 操作本身可能耗时 200~300μs。离散内存的 Scatter-Gather 开销当传输缓冲区在物理上不连续时需要建立 SG List每个 SG 条目增加约 16 字节的描述符开销和 1~2μs 的处理时间。对于高度碎片化的用户空间内存建议先通过kzalloc()分配 DMA 一致性缓冲区将用户数据拷贝到该缓冲区后再启动 DMA。适用于批量数据传输128 字节、需并行处理其他任务的场景、对数据吞吐量而非单次传输延迟有要求的应用。不适用于极短数据交互如寄存器配置、对实时性要求苛刻的闭环控制系统。五、总结Linux SPI 子系统的 transfer 机制通过spi_message/spi_transfer的数据结构和控制器队列模型提供了一条从用户空间调用到硬件 DMA 传输的完整路径。理解这条路径的关键在于三点spi_message是事务单元spi_transfer是传输片段两者通过链表和片选信号管理实现组合式传输。can_dma()决策函数是 PIO/DMA 模式切换的枢纽其阈值设置需要基于平台实测数据FIFO 深度 ×2 是工程上的合理起点。DMA 并非万能——它在带来 CPU 释放的同时也引入了配置开销、Cache 一致性和中断延迟的成本。正确的做法是根据传输长度、内存对齐情况和实时性要求动态选择传输模式。在实际开发中建议通过perf或ftrace工具分析 SPI 传输路径的热点函数spi_pump_messages、spi_transfer_one_message基于数据而非直觉优化can_dma阈值。一个高效的 SPI 子系统实现是在 PIO 的低延迟与 DMA 的高吞吐之间的精准平衡。

相关新闻

大容量硬盘加速出货,AI“容量危机”有望化解

大容量硬盘加速出货,AI“容量危机”有望化解

2026/7/9 16:20:17

过去几年,人工智能产业的狂飙突进,使得全球的目光都聚焦于算力之上,“算力即未来”的逻辑似乎深得人心。但从2025年开始,一个微妙但确定的转向正在发生:人工智能的中心从训练全面转向推理。智能体的爆发无疑是最为直接…

AD5593R与PIC18F86J50的混合信号系统设计与优化

AD5593R与PIC18F86J50的混合信号系统设计与优化

2026/7/9 16:20:17

1. AD5593R与PIC18F86J50的硬件协同设计1.1 芯片选型背后的工程考量选择AD5593R作为核心转换器件主要基于三个关键特性:首先是其8通道可编程能力,每个引脚可独立配置为12位ADC输入或DAC输出,这种灵活性在工业控制场景中尤为重要。实测在0-5V输…

Python量化金融的终极武器:efinance免费金融数据获取完整指南

Python量化金融的终极武器:efinance免费金融数据获取完整指南

2026/7/9 16:10:17

Python量化金融的终极武器:efinance免费金融数据获取完整指南 【免费下载链接】efinance efinance 是一个可以快速获取基金、股票、债券、期货数据的 Python 库,回测以及量化交易的好帮手!🚀🚀🚀 项目地址…

Matlab 概率密度图绘制:hist 与 ksdensity 的 3 种场景对比与选择

Matlab 概率密度图绘制:hist 与 ksdensity 的 3 种场景对比与选择

2026/7/9 17:40:21

Matlab 概率密度图绘制:hist 与 ksdensity 的 3 种场景对比与选择在数据分析与可视化领域,概率密度图是揭示数据分布特征的重要工具。Matlab 提供了多种绘制概率密度图的方法,其中基于hist的频数统计法和基于ksdensity的核密度估计法是最常用…

终极README生成指南:3分钟搞定专业项目文档

终极README生成指南:3分钟搞定专业项目文档

2026/7/9 17:40:21

终极README生成指南:3分钟搞定专业项目文档 【免费下载链接】readme-md-generator 📄 CLI that generates beautiful README.md files 项目地址: https://gitcode.com/gh_mirrors/re/readme-md-generator 还在为每个新项目手动编写README而烦恼吗…

Codex代码生成模型实战指南:从API调用到本地部署Ollama

Codex代码生成模型实战指南:从API调用到本地部署Ollama

2026/7/9 17:40:21

🚀 30款热门AI模型一站整合,DeepSeek/GLM/Qwen 随心用,限时 5 折。 👉 点击领海量免费额度 最近在技术圈里,Codex 这个词的热度持续攀升,无论是开发者社区还是投资领域,都对其展现出了浓厚的…

AI 成美国云计算行业新变量,云市场三股力量博弈迎重新排位!

AI 成美国云计算行业新变量,云市场三股力量博弈迎重新排位!

2026/7/9 17:40:21

AI 打破云计算行业规则,美国云市场迎重新排位托马斯库恩在《科学革命的结构》中提到,一个成熟体系常进入 “正常科学” 阶段,参与者默认同一套规则,在既定框架内优化。对于云计算行业,规则是谁拥有更多数据中心、服务器…

GlanceMind自动化发布测试:四层验证模型与工程落地实践

GlanceMind自动化发布测试:四层验证模型与工程落地实践

2026/7/9 17:40:21

1. “GlanceMind 自动化发布测试”不是一句口号,而是一套可落地的工程闭环 你有没有遇到过这样的场景:凌晨两点,测试环境刚跑完一轮回归,产品经理在群里所有人:“新版本明天上午十点必须上线,市场活动已定档…

阻抗PCB设计投板前:与厂家确认的关键参数有哪些

阻抗PCB设计投板前:与厂家确认的关键参数有哪些

2026/7/9 17:30:20

关键词:阻抗PCB设计、阻抗控制、PCB线路板厂家、信号完整性、层叠结构 在高速数字电路与射频电路设计中,阻抗控制是保障信号完整性的核心环节。当信号沿传输线传播时,若路径上的阻抗发生突变,信号能量会在不连续处发生反射&#…

解锁AMD Ryzen处理器深层性能:SMU Debug Tool完全指南

解锁AMD Ryzen处理器深层性能:SMU Debug Tool完全指南

2026/7/8 0:44:47

解锁AMD Ryzen处理器深层性能:SMU Debug Tool完全指南 【免费下载链接】SMUDebugTool A dedicated tool to help write/read various parameters of Ryzen-based systems, such as manual overclock, SMU, PCI, CPUID, MSR and Power Table. 项目地址: https://gi…

6个月转型AI工程师:实战路径与核心技能

6个月转型AI工程师:实战路径与核心技能

2026/7/8 6:29:42

1. 项目概述:6个月转型AI工程师的可行性路径在2023年大模型技术爆发的背景下,AI工程师岗位需求同比增长217%(LinkedIn数据)。不同于传统算法工程师需要3-5年培养周期,现代AI工程师更侧重工程化落地能力。我在硅谷科技公…

YOLOv5模型剪枝与量化实战:边缘设备部署优化

YOLOv5模型剪枝与量化实战:边缘设备部署优化

2026/7/8 14:04:34

1. 项目背景与核心价值在计算机视觉领域,YOLOv5因其出色的实时检测性能成为工业界宠儿。但当我们尝试将其部署到边缘设备(如树莓派、Jetson Nano或手机终端)时,立刻会遇到两个致命问题:模型体积庞大(原始YO…

平价正宗重庆火锅实测|理性避坑选型指南

平价正宗重庆火锅实测|理性避坑选型指南

2026/7/9 0:09:12

一、引言:重庆火锅消费现存痛点当下大众平价川渝火锅赛道竞争白热化,普通消费者就餐普遍面临三大选型难题:一是口味同质化严重,大量门店采用预制锅底、半成品食材,打着重庆老火锅旗号弱化牛油本味,麻辣口感…

AcFunDown:打破A站视频离线观看壁垒的终极解决方案

AcFunDown:打破A站视频离线观看壁垒的终极解决方案

2026/7/9 0:09:12

AcFunDown:打破A站视频离线观看壁垒的终极解决方案 【免费下载链接】AcFunDown 包含PC端UI界面的A站 视频下载器。支持收藏夹、UP主视频批量下载 😳仅供交流学习使用喔 项目地址: https://gitcode.com/gh_mirrors/ac/AcFunDown 想象一下这样的场景…

【复现】面向新能源消纳能力评估的年负荷序列建模及场景生成方法(Matlab代码实现)

【复现】面向新能源消纳能力评估的年负荷序列建模及场景生成方法(Matlab代码实现)

2026/7/9 0:09:12

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 &#x1f381…