Linux驱动:SPI子系统
Linux SPI驱动框架层次如上图除开硬件和用户态应用程序由上到下分成3层设备驱动层: spi框架使用者核心层spi框架搭建者控制器驱动层 spi框架适配者drivers/spi/spi.c spi-mem.c spi-mux.cinclude/linux/spi/spi.hspi.c一方面对SPI子系统进行初始化工作注册spi bus注册spi_master class同时提供spi设备驱动对spi总线进行操作的API。另一方面SPI子系统对spi控制器层提供注册控制器的api和回调操作函数。spi.h包含了spi核心层的一些重要数据结构struct spi_master; struct spi_transfer; struct spi_message以及一些实现比较简单的函数等。spi-gpio.cSPI GPIO框架SPI子系统提供了一个名为spi-gpio的框架可使用GPIO引脚模拟SPI总线gpio模拟spi代码在drivers/spi/spi-gpio.c中。这个框架允许将GPIO引脚配置为SPI总线的时钟、片选、输入和输出信号并提供了对应的接口函数供驱动程序使用。spi-bitbangspi-bitbang是Linux内核中提供的一个通用框架用于在没有硬件SPI控制器或需要灵活控制SPI时序和配置的系统中模拟SPI总线的通信。代码在spi-bitbang.c中核心层的作用:对上层的使用者也就是SPI设备驱动提供标准的spi收发API以及设备注册函数。 对底下的适配者也就是控制器驱动层提供注册控制器接口并提供一些需要控制器驱动实现的回调函数。SPI驱动匹配流程首先使用spi_register_driver函数向SPI总线注册一个驱动driver:static struct spi_driver icm20608_driver {.probe icm20608_probe,.remove icm20608_remove,.id_table icm20608_id,.driver {.name icm20608,.owner THIS_MODULE,.of_match_table of_match_ptr(icm20608_of_match),},};spi_register_driver(icm20608_driver);spi_register_driver进行以下执行逻辑spi_register_driver(icm20608_driver);-driver_register(sdrv-driver);-bus_add_driver(drv);// 将驱动添加到总线-driver_attach(drv); //自动探测并绑定设备核心操作-bus_for_each_dev(drv-bus, NULL, drv, __driver_attach);-while ((dev next_device(i)) !error) //遍历循环 从迭代器中获取下一个设备error fn(dev, data); //调用用户提供的回调函数也就是__driver_attach-driver_match_device(drv, dev)// 匹配检查 调用总线的 match 函数-drv-bus-match(dev, drv)//也就是spi_bus_type中的spi_match_device-spi_match_device(struct device *dev, struct device_driver *drv)这里进行四种匹配方式具体实现如下框在这里执行__driver_attach中的driver_probe_device-driver_probe_device(drv, dev);-really_probe(dev, drv);// 执行实际的探测核心操作 // 优先调用总线的 probe 函数if (dev-bus-probe) {// 对于 SPI 总线调用 spi_drv_probe()// 对于 I2C 总线调用 i2c_device_probe()// 对于 Platform 总线调用 platform_drv_probe()ret dev-bus-probe(dev);-probe(to_spi_device(dev));-to_spi_device(dev)-container_of(dev, struct spi_device, dev)// container_of 计算方式// (char *)dev - offsetof(struct spi_device, dev)// 即成员地址 - 成员在结构体中的偏移量// 得到的是 spi_device 结构体的起始地址在这里执行自己定义的icm20608_driver中的icm20608_probe函数-module_add_driver(drv-owner, drv);建立驱动与模块的关联并在 sysfs 中创建符号链接/** * spi_match_device - SPI 总线的设备和驱动匹配函数 * dev: 要匹配的设备SPI 设备 * drv: 要匹配的驱动SPI 驱动 * * 返回值 * 1 → 匹配成功 * 0 → 匹配失败 * * 这是 SPI 总线类型的 match 回调函数在 driver_match_device() 中被调用 * 它按照优先级顺序尝试多种匹配方式来找到合适的驱动 */ static int spi_match_device(struct device *dev, struct device_driver *drv) { const struct spi_device *spi to_spi_device(dev); const struct spi_driver *sdrv to_spi_driver(drv); /* Attempt an OF style match */ // 设备树示例 // spi1 { // icm206080 { // compatible invensense,icm20608; ← 匹配这个 // reg 0; // spi-max-frequency 10000000; // }; if (of_driver_match_device(dev, drv)) // 设备树匹配最高优先级 return 1; /* Then try ACPI */ // acpi_driver_match_device() 检查 ACPI 表匹配 // 主要用于 x86 和 ARM 服务器平台嵌入式 Linux 较少使用 if (acpi_driver_match_device(dev, drv)) //ACPI 匹配 return 1; // 检查驱动是否定义了 id_table // id_table 是传统 SPI 驱动的匹配方式主要用于非设备树的平台 if (sdrv-id_table) return !!spi_match_id(sdrv-id_table, spi); //SPI ID 表匹配 // 如果所有方式都失败了尝试简单的字符串比较 // spi-modaliasSPI 设备的模块别名 // 通常从设备树或板级代码中获取 // 例如icm20608 // drv-name驱动的名称 // 在 struct device_driver 中定义 // 例如.name icm20608 return strcmp(spi-modalias, drv-name) 0; //名称匹配最后的后备方案 }我使用的是of_driver_match_device(dev, drv)设备树匹配最高优先级of_driver_match_device(dev, drv)-of_match_device(drv-of_match_table, dev)-of_match_node(matches, dev-of_node)-__of_match_node(matches, node);-__of_device_is_compatible(node, matches-compatible,matches-type, matches-name);-of_compat_cmp(cp, compat, strlen(compat)#define of_compat_cmp(s1, s2, l) strcasecmp((s1), (s2))到这里进行最后的比较strcasecmp//不区分大小写的字符串比较函数 int strcasecmp(const char *s1, const char *s2) { int c1, c2; do { c1 tolower(*s1); //将大写字母转换为小写字母其他字符保持不变。 c2 tolower(*s2); //将大写字母转换为小写字母其他字符保持不变。 } while (c1 c2 c1 ! 0); return c1 - c2; }比较完成后返回到上面的spi_match_device函数中最终返回到bus_add_driver函数中的driver_attach(drv); //自动探测并绑定设备核心操作后面然后继续执行module_add_driver建立驱动与模块的关联并在 sysfs 中创建符号链接完成注册接下来执行icm20608_probe函数具体实现如下static int icm20608_probe(struct spi_device *spi) { int ret; struct icm20608_data *data; uint8_t test_id; pr_info(ICM-20608G: probe started\n); pr_info(SPI bus%d, chip_select%d, max_speed%d\n, spi-master-bus_num, spi-chip_select, spi-max_speed_hz); if (!spi-dev.of_node) { pr_err(No device tree node found\n); return -EINVAL; } // 测试SPI通信 pr_info(Testing SPI communication...\n); ret icm20608_read_reg(spi, ICM20608_WHO_AM_I, test_id); if (ret 0) { pr_err(SPI communication test failed: %d\n, ret); return ret; } pr_info(WHO_AM_I 0x%02X\n, test_id); // 分配设备数据结构 data devm_kzalloc(spi-dev, sizeof(*data), GFP_KERNEL); if (!data) { pr_err(Failed to allocate memory\n); return -ENOMEM; } >// SPI 读写函数 static int icm20608_read_reg(struct spi_device *spi, uint8_t reg, uint8_t *val) { uint8_t tx_buf[2]; uint8_t rx_buf[2]; struct spi_transfer t; struct spi_message m; int ret; tx_buf[0] reg | 0x80;发送的第一个字节要读或者要写的寄存器的地址 这里需要说明一下这里发送的是表示读还是写的最高位和剩下的7位寄存器地址位这是由icm20608规定的而不是SPI tx_buf[1] 0xFF;发送一个空字节SPI中读就是写、写就是读 memset(t, 0, sizeof(t)); t.tx_buf tx_buf; t.rx_buf rx_buf; t.len 2; spi_message_init(m); //初始化消息 spi_message_add_tail(t, m);//添加传输到消息 ret spi_sync(spi, m); if (ret 0) { pr_err(SPI read failed: %d\n, ret); return ret; } *val rx_buf[1]; return 0; }SPI的读写操作读就是写、写就是读可以将SOC的SPI的数据寄存器和传感器的数据寄存器理解为两个环形移位寄存器SPI 硬件本质上是两个环形移位寄存器关键- 主设备和从设备的移位寄存器构成一个环形电路- 每个 SCLK 时钟周期数据在两个移位寄存器之间流动- 发送数据会挤出接收数据对于最关键的以下三句spi_message_init(m);spi_message_add_tail(t, m);ret spi_sync(spi, m);spi_message_init(m)- 初始化消息// include/linux/spi/spi.h static inline void spi_message_init(struct spi_message *m) { memset(m, 0, sizeof(*m)); // ① 清零所有字段 INIT_LIST_HEAD(m-transfers); // ② 初始化传输链表头指向自己 }执行后的内存状态struct spi_message m (初始化后)spi_message_add_tail(t, m)- 添加传输到消息// include/linux/spi/spi.h static inline void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m) { list_add_tail(t-transfer_list, m-transfers); }执行后的内存状态struct spi_message m (添加传输后)链表结构m.transfers ←→ t.transfer_list↑ ↑prev/next prev/next这样就构成了一个双向循环链表消息通过这个链表管理所有传输。ret spi_sync(spi, m)- 执行同步传输这是最核心的一行我们来深入看spi_sync的实现// drivers/spi/spi.c int spi_sync(struct spi_device *spi, struct spi_message *message) { DECLARE_COMPLETION_ONSTACK(done); // ① 创建完成信号 int ret; // ② 关联设备 message-spi spi; // ③ 设置完成回调 message-complete spi_complete; message-context done; // ④ 发起异步传输 ret spi_async(spi, message); if (ret) return ret; // ⑤ 等待传输完成 wait_for_completion(done); // ⑥ 返回传输状态 return message-status; }完整的执行流程spi_sync(spi, m)│├─ 1. 创建完成信号 (done)│├─ 2. m.spi spi ← 告诉内核要操作哪个设备│├─ 3. m.complete spi_complete│ m.context done ← 设置完成回调│├─ 4. spi_async(spi, m) ← 发起异步传输│ ││ ├─ __spi_validate() // 验证参数│ ├─ __spi_map_msg() // 映射 DMA 缓冲区│ ├─ __spi_queued_transfer() // 加入队列│ └─ spi_pump_messages() // 实际硬件传输│ ││ ├─ 设置 CS片选│ ├─ 发送第一个字节│ ├─ 接收第一个字节│ ├─ 发送第二个字节│ ├─ 接收第二个字节│ ├─ 释放 CS│ └─ m.complete(m.context) // 调用完成回调│├─ 5. wait_for_completion(done) ← 等待完成│ ↑│ └─ spi_complete() 唤醒│└─ 6. return m.status ← 返回传输结果 为什么需要消息和传输两层答一个消息包含多个传输// 场景读取传感器数据命令 地址 数据struct spi_transfer t1 {.tx_buf cmd, // 发送命令.len 1,.cs_change 0, // 保持片选};struct spi_transfer t2 {.tx_buf addr, // 发送地址.len 1,.cs_change 0, // 保持片选};struct spi_transfer t3 {.rx_buf data, // 接收数据.len 10,.cs_change 1, // 完成后释放片选};spi_message_init(m);spi_message_add_tail(t1, m);spi_message_add_tail(t2, m);spi_message_add_tail(t3, m);// 执行后// CS 在整个消息期间保持有效// 三个传输连续执行中间不断开片选

相关新闻

k8s- Health Check

k8s- Health Check

Kubernetes Health Check 学习参考:配置存活、就绪和启动探针 环境准备 rootmaster30:~# kubectl create ns health rootmaster30:~# kubectl config set-context --current --namespace healthHealth Check 应用可能会因为各种问题,变的 unhealthy&am…

2026/8/26 16:10:17 阅读更多 →
WebSocket实时通信:从协议到实现

WebSocket实时通信:从协议到实现

TL;DR 核心要点速览 Gin框架是Go最流行的Web框架 gRPC适合内部服务,REST适合对外API JWT Token是无状态认证的标准方案 Go标准库net/http可直接构建HTTP服务 Swagger/OpenAPI可自动生成API文档 本篇是Go Web开发模块,含完整项目代码 摘要:本文详细介绍从协议到实现,涵盖核心原…

2026/8/26 16:02:29 阅读更多 →
餐饮行业为何需要独立的原材料库存管理系统?

餐饮行业为何需要独立的原材料库存管理系统?

很多餐饮老板以为收银系统自带“库存管理”就够了,实际上两者存在根本性差异: 收银系统的“库存”本质是“成品/半成品数量”——它记录的是“卖了多少份红烧肉”“还剩多少瓶可乐”,目的是前台结账和出品控制。而原材料库存管理系统管的是“…

2026/8/25 12:51:13 阅读更多 →

最新新闻

前端现在只会玩框架,原生JS全忘光了,行业集体退化到令人发指

前端现在只会玩框架,原生JS全忘光了,行业集体退化到令人发指

当下的前端圈子已然糟糕到了极点, 每个人都在追逐框架, 比拼语法, 炫耀工程化, 然而却连最为基础的原生 JS 都没办法写明白。整个行业都呈现出集体退化, 集体摆烂的态势。Vue、React、TS、Vite 一股脑儿叠加起来, 人人都感觉自己是高级工程师, 可是真要是叫他们脱离框架去写些内…

2026/8/26 19:27:18 阅读更多 →
JSON Web Token (JWT)入门教程

JSON Web Token (JWT)入门教程

JSON Web Token, 其缩写为JWT, 是当下最为流行的跨域认证解决方案, 本文会对它的原理以及用法予以介绍。一、跨域认证的问题互联网服务离不开用户认证。一般流程是下面这样。1、用户向服务器发送用户名和密码。2、于服务器验证得以通过之后, 于当前对话的括号之内, 去保存诸如用…

2026/8/26 19:27:18 阅读更多 →
Linux Devfreq 深度解析:GPU/DDR/ISP外设动态调频、架构原理、与CPUFreq区别、实操调优

Linux Devfreq 深度解析:GPU/DDR/ISP外设动态调频、架构原理、与CPUFreq区别、实操调优

专栏系列位置:外设功耗管理 第五篇 系列前置回顾 本专栏已构建完整CPU核心功耗体系: CPU架构+三级缓存(硬件性能基础) CPUIdle C-State(CPU空闲休眠) Thermal温控系统(高温保护降频) CPUFreq/DVFS/OPP(CPU运行时调频调压) 前面四篇只覆盖了CPU。 但手机、嵌入式SO…

2026/8/26 19:27:18 阅读更多 →
web前端开发前景分析

web前端开发前景分析

一、市场需求持续扩大1.数字化转型驱动需求金融行业、医疗行业、教育等行业加快进行线上化, 企业对于用户体验的要求有所提升, 前端身为用户交互的关键环节, 人才方面的缺口十分显著 , 2025年的时候Web前端岗位的增速达到了214%, 每天的岗位缺口超过40万。随着需求急剧增长, 跨…

2026/8/26 19:27:17 阅读更多 →
自托管文件处理 WebUI,一站式完成格式转换、OCR 和语音转文字

自托管文件处理 WebUI,一站式完成格式转换、OCR 和语音转文字

某特定的File, 它属于那种自托管的, 并且是基于浏览器的, 具备多功能的文件处理工具。其核心思路呈现出相当直接的状态: 要将众多的、诸如数十种知名命令行工具, 以及诸多的库, 予以封装, 使其进入一个一体化的、直观的、深色主题的Web界面之中。无需用户记住任何命令, 通过打开…

2026/8/26 19:26:17 阅读更多 →
HarmonyOS 7.0 / API 26 WindowStage 恢复顺序:窗口回来后为什么先算尺寸再拉数据

HarmonyOS 7.0 / API 26 WindowStage 恢复顺序:窗口回来后为什么先算尺寸再拉数据

HarmonyOS 7.0 / API 26 WindowStage 恢复顺序:窗口回来后为什么先算尺寸再拉数据 这篇只拆一个具体点:WindowStage 恢复顺序。版本边界先放前面:下面的写法面向 HarmonyOS 7.0 / API 26。工程里如果还在混用旧 SDK、旧模拟器镜像或旧设备系统…

2026/8/26 19:26:17 阅读更多 →

日新闻

Python random 模块常用函数详解:从入门到实战

Python random 模块常用函数详解:从入门到实战

目录 1. 引言2. 准备工作3. 基础随机函数4. 序列相关函数5. 随机种子与复现6. 实战案例7. 注意事项8. 常见问题与排查9. 总结 1. 引言 摘要: 本文系统介绍 Python 标准库 random 模块中最常用的随机数生成函数。内容涵盖基础随机函数(random()、unifor…

2026/8/26 0:00:40 阅读更多 →
《Microsoft Sql server 2008 Internals》读书笔记--第三章Databases and Database Files(2)

《Microsoft Sql server 2008 Internals》读书笔记--第三章Databases and Database Files(2)

《Microsoft Sql server 2008 Internals》索引目录: 《Microsoft Sql server 2008 Internals》读书笔记--目录索引 在上篇文章中,主要介绍了创建数据库的基本语法和FileGroup的初步知识。需要注意的是: 关于FileGroup 如果你的系统是用Raid设备直接存…

2026/8/26 1:18:18 阅读更多 →
政务AI智能体怎么建?三种模式、三步路径与四个误区

政务AI智能体怎么建?三种模式、三步路径与四个误区

政务AI智能体已经从概念试点阶段,转入了政务服务的常态化落地应用;在实际使用过程中,它能自主理解办事需求、辅助完成填报申报、开展材料预审,并联动多个系统协同作业,真正嵌入到政务办理的全流程当中。但在落地推进过…

2026/8/26 1:18:18 阅读更多 →

周新闻

[光学原理与应用-521]:对光的错误理解与纠偏

[光学原理与应用-521]:对光的错误理解与纠偏

首先光是一种能量的载体和形态,宏观上观察到的光是由无数个微观的光量子组成的,每个光子在产生的瞬间,其在真空的空间中以确定不变的速度沿着一个初始的方向一直向前,在微观层面,每个光量子的运动轨迹是以波函数所展现…

2026/8/26 14:45:33 阅读更多 →
SIP通话转接原理与REFER方法实战解析

SIP通话转接原理与REFER方法实战解析

1. 通话转接不是“挂断再拨号”,而是SIP会话的动态重定向你有没有遇到过这样的场景:客服坐席A正在和客户通电话,突然需要把这通对话无缝转给专家坐席B,客户完全感知不到中间的断连——既没听到忙音,也没被要求重新拨号…

2026/8/26 17:46:43 阅读更多 →
Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

1. 为什么选择Kolla-ansible来部署单节点OpenStack?如果你正在寻找一种能把OpenStack从“概念”快速变成“可用的实验环境”的方法,那么Kolla-ansible几乎是当前最主流、最省心的选择。我见过太多人卡在手动编译依赖、配置服务、处理版本冲突的泥潭里&am…

2026/8/26 14:46:37 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/26 3:50:20 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/26 17:46:39 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/26 1:24:05 阅读更多 →