当前位置: 首页 > news >正文

Linux内核学习17--SPI子系统

1 Linux下SPI子系统简介

关于SPI协议细节,之前写过:总线学习3--SPI_spi a0-CSDN博客

这次就不多写了,这次主要关注Linux下的使用,使用的环境是树莓派5。还是先看图吧,毕竟无图无真相。这个图B格看起来有点高。

不过对于BSP这边的开发,我感觉下面这个图明了一些。实用性强得多。

来自:https://www.eet-china.com/mp/a131804.html

一共分为用户层 → SPI 核心框架 → 控制器驱动 → 硬件寄存器。

用户空间层(spidev.c)

用户层,就是spidev.c提供。说白点就是给上层提供了ioctl接口,这样可以在应用层使用节点或者编程来控制。
/dev/spidevX.Y 字符设备:X=SPI 主机号,Y = 片选号
提供标准 ioctl 接口,应用可直接收发 SPI 数据,无需写内核驱动
工具:spi-config、spi-pipe、spidev_test 测试程序。
SPI 核心层(spi.c/spi.h)

在这一层,就是内核接口层,说白点就是提供了SPI 内核标准接口实现文件,spi_write()、spi_read()、spi_sync()。驱动程序接口层。
同时在这里内核中间调度层,定义通用数据结构、传输队列、消息处理、总线注册 / 注销、锁机制,是上下层桥梁,不绑定任何硬件。
SPI 控制器驱动(master 驱动)
对接 SoC 内置 SPI 控制器(如 STM32 SPI、高通 QCOM SPI、全志 SPI、树莓派 BCM2835 SPI),实现底层时序、时钟、片选、DMA、中断收发。
SPI 外设设备驱动(slave 驱动)
针对外接芯片:Flash (W25Q)、ADC、显示屏、传感器、射频模块等,调用核心层接口发起传输。

最后的两层其实基本不用怎么关心。

2 内核节点

2.1 原始状态

在树莓派 5(RPi5,BCM2712),SPI0 默认关闭,不会自动生成 spidev 节点。

此时状态如下:

tom@raspberrypi:~$ ls /dev/spide* /dev/spidev10.0 tom@raspberrypi:~$ lsmod | grep spi spidev 49152 0 spi_bcm2835 49152 0

此时虽然有一个spidev10.0。但是并不是40pin接口的SPI。

树莓派 5(BCM2712)有两套 SPI 硬件控制器内核驱动:
传统 spi-bcm2835:对应老 BCM2711/2837 架构的 SPI0、SPI1(40 针排针那两组)
新 DW(DesignWare)SPI 控制器 spi_dw:RP1 辅助芯片自带多路 SPI,内核枚举时编号排到了 10,所以生成 /dev/spidev10.0

设备节点控制器驱动物理引脚位置用途
spidev0.0 / spidev0.1spi_bcm283540 针排针:GPIO9/10/11/7/8(Pin19/21/23/26/24)外接 OLED、W25Q、ADC、射频模块(最常用)
spidev10.0spi_dw(RP1)RP1 芯片内部引出,不在 40 针 GPIO板载内部外设、HAT 扩展底板专用,普通外设不接这里

驱动解释如下:

spidev 49152 0 # 通用用户透传驱动

spi_bcm2835 49152 0 # 40针GPIO标准SPI0/SPI1主机驱动

spi_dw_mmio + spi_dw # RP1扩展SPI(spi10总线来源)

2.2 SPIDEV状态

要使用spidev,必须手动开启;


开启dtparam=spi=on后,这里默认DTS子节点compatible="spidev",内核会自动加载 spidev 驱动,直接生成/dev/spidev0.0、/dev/spidev0.1;

这时效果如下:

tom@raspberrypi:~$ ls /dev/spide* /dev/spidev0.0 /dev/spidev0.1 /dev/spidev10.0 tom@raspberrypi:~$ lsmod | grep spi spidev 49152 0 spi_dw_mmio 49152 0 spi_bcm2835 49152 0 spi_dw 49152 1 spi_dw_mmio

此时,在设备树下生成两个节点:

/sys/firmware/devicetree/base/axi/pcie@1000120000/rp1/spi@50000/spidev@0 /sys/firmware/devicetree/base/axi/pcie@1000120000/rp1/spi@50000/spidev@1

为什么是两个,因为在原始的spi0中定义了两路片选。

rp1_spi0: spi@50000 { reg = <0xc0 0x40050000 0x0 0x130>; compatible = "snps,dw-apb-ssi"; interrupts = <RP1_INT_SPI0 IRQ_TYPE_LEVEL_HIGH>; clocks = <&rp1_clocks RP1_CLK_SYS>; clock-names = "ssi_clk"; #address-cells = <1>; #size-cells = <0>; num-cs = <2>; dmas = <&rp1_dma RP1_DMA_SPI0_TX>, <&rp1_dma RP1_DMA_SPI0_RX>; dma-names = "tx", "rx"; status = "disabled";

此时可以看到这些节点:

tom@raspberrypi:/sys/bus/spi/devices$ ls spi0.0 spi0.1 spi10.0

这个部分是由spi.c创建,不是spidev驱动本身。所以操作的方法比较通用,所有的驱动都可以这么搞。

节点下面大概内容有这些:

tom@raspberrypi:/sys/bus/spi/devices/spi0.0$ ls -l total 0 lrwxrwxrwx 1 root root 0 Jun 12 10:00 driver -> ../../../../../../../../bus/spi/drivers/spidev -rw-r--r-- 1 root root 16384 Jun 12 10:42 driver_override -r--r--r-- 1 root root 16384 Jun 12 10:42 modalias lrwxrwxrwx 1 root root 0 Jun 12 10:42 of_node -> ../../../../../../../../firmware/devicetree/base/axi/pcie@1000120000/rp1/spi@50000/spidev@0 drwxr-xr-x 2 root root 0 Jun 12 10:42 power drwxr-xr-x 3 root root 0 Jun 12 10:00 spidev drwxr-xr-x 2 root root 0 Jun 12 10:42 statistics lrwxrwxrwx 1 root root 0 Jun 12 10:00 subsystem -> ../../../../../../../../bus/spi -rw-r--r-- 1 root root 16384 Jun 12 10:00 uevent

作用如下:

目录/文件名称类型核心作用(内核层)通俗解释(应用层)
spidev目录绑定了 Linux 通用 SPI 驱动的标志只要它在,就能在/dev/下找到spidev0.0,从而能用代码直接读写 SPI 硬件
driver符号链接指向当前正在控制该设备的内核驱动程序告诉现在是谁在管这个设备。如果指向spidev,说明是通用驱动;如果指向特定芯片驱动(如某个屏幕驱动),说明被专属驱动占用了。
driver_override文件允许手动强制指定该设备的驱动程序戏称“强扭的瓜”。可以往里写特定驱动的名字,强行让系统用指定的驱动去管这个设备。
modalias文件显示设备的内核模块别名设备的“身份证号”,内核靠它来自动匹配并加载正确的驱动程序。
of_node符号链接指向树莓派设备树(Device Tree)中的对应节点连向硬件的“出厂配置单”,能看到硬件引脚分配、最大波特率等底层配置。
statistics目录存放该 SPI 设备的通信统计数据相当于“计费账单”,里面记录了发了多少字节、报错了多少次,调优和排错时很有用。
power目录硬件电源管理接口控制设备的省电、休眠和唤醒模式。
subsystem符号链接指向设备所属的内核子系统分类认祖归宗,这里指向sys/bus/spi,表明它属于 SPI 总线家族
uevent文件内核与用户空间热插拔管理器的通信接口“大喇叭”,当硬件启动或移除时,内核通过它通知系统(如udev)去自动创建或删除/dev/下的设备文件

此时可以手动灌数据。

while true; do echo -ne "\xff\xff\xff\xff" > /dev/spidev0.0; sleep 0.01; done

2.3 自定义SPI驱动

一旦用overlay绑定其他外设驱动(w25q、mmc_spi、自研屏驱),对应 CS 通道的spidev就会消失,二者互斥。

要记得使用官方接口注册

module_spi_driver(ecx335c_driver);

可以使用的接口大概是:

函数名称功能描述核心参数说明
spi_write()纯发送数据。向 SPI 设备写入一段指定长度的数据缓冲区。

(spi, buf, len)

buf: 发送数据指针

len: 字节数

spi_read()纯接收数据。从 SPI 设备读取一段指定长度的数据到缓冲区。

(spi, buf, len)

buf: 接收缓存指针

spi_write_then_read()先发后收(半双工)。常用于“发送寄存器地址 -> 读取寄存器值”的场景。中间片选不拉高

(spi, txbuf, txlen, rxbuf, rxlen)

txbuf/txlen: 发送缓存与长度

rxbuf/rxlen: 接收缓存与长度

spi_w8r8()spi_write_then_read的精简版。发送 8 位数据,紧接着读取 8 位数据。返回值即为读取到的 8 位数据(或负数错误码)。

(spi, cmd)

cmd: 要发送的 8 位指令/地址

spi_w8r16()发送 8 位数据,紧接着读取 16 位数据。内部会自动处理大端/小端字节序的转换。

(spi, cmd)

• 返回值: 读取到的 16 位数据

还有很多就不列举了。。。

2.4 片选

在 Linux 的 SPI 命名规范中,spiX.Y的格式是这样定义的:

  • X:代表SPI 控制器的编号(总线号)。spi0意味着它们共享树莓派 5 同一个 SPI0 硬件总线(共享时钟线 CLK、主出从入线 MOSI、主入从出线 MISO)。

  • Y:代表片选引脚的编号(Chip Select)。

    • spi0.1占用的是 SPI0 的片选 1(通常对应树莓派引脚上的SPI0_CE1)。

    • spi0.2占用的是 SPI0 的片选 2(通常对应树莓派引脚上的SPI0_CE2)。

此时可以加载两个设备,两个驱动。

3 调试方法

3.1 spidev_test 环回测试

  1. 在外设驱动没调通前,先把设备树里的兼容性临时改成标准的虚拟节点:

    compatible = "rohm,dh2228fv"; /* 或者直接是 generic 的 "linux,spidev" */
  2. 重启后,系统会在/dev/下吐出一个/dev/spidev0.0的裸设备节点。

  3. 拿一根跳线或者镊子,把树莓派或板子引脚上的 SPI_MOSI 和 SPI_MISO 短接(短路在一起)

  4. 运行 Linux 内核自带的测试工具(内核源码tools/spi/spidev_test.c编译出来的二进制)

    ./spidev_test -D /dev/spidev0.0 -v -p "Hello Tom!"
  • 如果终端里成功打印出发送和接收完全一致的Hello Tom!,说明SoC、内核核心层、控制器驱动、DMA、引脚复用全部 100% 完好。皮球直接踢给硬件工程师(去查外设芯片断线或供电问题)或应用驱动(去查初始化序列)。如果读出来全是00,说明总线自己都没通。

3.2 ftrace 传输追踪

cd /sys/kernel/tracing/ echo 0 > tracing_on echo > trace # 追踪标准 Linux SPI 核心层的两大骨架事件 echo "spi:*" > set_event # 开启内核 SPI 核心层自带的 tracepoint(极其精准) echo 1 > tracing_on # 执行你的读写测试 echo 0 > tracing_on # 查看账本 cat trace | head -n 30
# 1. 切换到 root sudo su # 2. 擦亮内核的追踪器镜片 echo 0 > /sys/kernel/tracing/tracing_on echo "" > /sys/kernel/tracing/trace # 3. 开启 SPI 模块搬运数据的事件监听(抓取发送和接收) echo 1 > /sys/kernel/tracing/events/spi/spi_transfer_start/enable # 4. 打开总开关 echo 1 > /sys/kernel/tracing/tracing_on # 5. 此时去运行你的调屏测试程序(或者往 /dev/spidev10.0 灌数据) # 6. 见证奇迹的时刻,查看内核抓到的 SPI 裸波形流 cat /sys/kernel/tracing/trace | tail -n 50

3.3 动态打印控制(Dynamic Debug)

Linux 内核有一个神级机制叫dynamic_debug。你不需要重新编译高通内核,就能在运行时一键开启SPI 核心层的所有隐藏pr_debug级日志!

# 1. 挂载 debugfs(安卓通常默认挂载了) mount -t debugfs none /sys/kernel/debug/ # 2. 一键命令内核:把所有属于 spi 核心框架和高通 geni-spi 驱动里的调试日志全部激活! echo "file drivers/spi/* +p" > /sys/kernel/debug/dynamic_debug/control echo "file drivers/platform/msm/gpi/* +p" > /sys/kernel/debug/dynamic_debug/control # 高通DMA传输日志 # 3. 此时去看 dmesg,你会看到每一次 SPI 传输的长度、片选切换、甚至中断状态都被吐了出来 dmesg -w

3.4 查看SPI状态

查看当前被哪个驱动占用

tom@raspberrypi:~$ ls -l /sys/bus/spi/devices/spi0.0/driver lrwxrwxrwx 1 root root 0 Jun 13 09:41 /sys/bus/spi/devices/spi0.0/driver -> ../../../../../../../../bus/spi/drivers/spidev

查看Pin脚复用

tom@raspberrypi:/sys/bus/spi/devices$ cat /sys/kernel/debug/pinctrl/1f000d0000.gpio-pinctrl-rp1/pinmux-pins Pinmux settings per pin Format: pin (name): mux_owner gpio_owner hog? pin 0 (gpio0): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 1 (gpio1): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 2 (gpio2): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 3 (gpio3): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 4 (gpio4): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 5 (gpio5): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 6 (gpio6): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 7 (gpio7): 1f00050000.spi pinctrl-rp1:578 function spi0 group gpio7 pin 8 (gpio8): 1f00050000.spi pinctrl-rp1:579 function spi0 group gpio8 pin 9 (gpio9): 1f00050000.spi (GPIO UNCLAIMED) function spi0 group gpio9 pin 10 (gpio10): 1f00050000.spi (GPIO UNCLAIMED) function spi0 group gpio10 pin 11 (gpio11): 1f00050000.spi (GPIO UNCLAIMED) function spi0 group gpio11 pin 12 (gpio12): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 13 (gpio13): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 14 (gpio14): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 15 (gpio15): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 16 (gpio16): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 17 (gpio17): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 18 (gpio18): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 19 (gpio19): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 20 (gpio20): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 21 (gpio21): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 22 (gpio22): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 23 (gpio23): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 24 (gpio24): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 25 (gpio25): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 26 (gpio26): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 27 (gpio27): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 28 (gpio28): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 29 (gpio29): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 30 (gpio30): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 31 (gpio31): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 32 (gpio32): (MUX UNCLAIMED) pinctrl-rp1:603 pin 33 (gpio33): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 34 (gpio34): (MUX UNCLAIMED) pinctrl-rp1:605 pin 35 (gpio35): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 36 (gpio36): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 37 (gpio37): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 38 (gpio38): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 39 (gpio39): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 40 (gpio40): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 41 (gpio41): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 42 (gpio42): 1f00200000.usb (GPIO UNCLAIMED) function vbus1 group gpio42 pin 43 (gpio43): 1f00200000.usb (GPIO UNCLAIMED) function vbus1 group gpio43 pin 44 (gpio44): (MUX UNCLAIMED) pinctrl-rp1:615 pin 45 (gpio45): 1f0009c000.pwm (GPIO UNCLAIMED) function pwm1 group gpio45 pin 46 (gpio46): (MUX UNCLAIMED) pinctrl-rp1:617 pin 47 (gpio47): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 48 (gpio48): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 49 (gpio49): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 50 (gpio50): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 51 (gpio51): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 52 (gpio52): (MUX UNCLAIMED) (GPIO UNCLAIMED) pin 53 (gpio53): (MUX UNCLAIMED) (GPIO UNCLAIMED)

此时可以看到gpio7,gpio8,gpio9,gpio10,gpio11已经配置成了SPI。

3.5 动态换驱动

如果直接换会报错

tom@raspberrypi:~/dttest$ sudo dtoverlay ecx335c-overlay.dtbo [sudo] password for tom: * Failed to apply overlay '0_ecx335c-overlay' (kernel)

查肯内核报错:

[ 2637.268644] OF: overlay: WARNING: memory leak will occur if overlay removed, property: /axi/pcie@1000120000/rp1/spi@50000/status [ 2637.268735] spidev spi0.0: chipselect 0 already in use [ 2637.268738] spi_master spi0: spi_device register error /axi/pcie@1000120000/rp1/spi@50000/my_oled_panel@0 [ 2637.268744] of_spi_notify: failed to create for '/axi/pcie@1000120000/rp1/spi@50000/my_oled_panel@0' [ 2637.268748] OF: changeset notifier error @/axi/pcie@1000120000/rp1/spi@50000/my_oled_panel@0 [ 2637.268753] OF: overlay: overlay apply changeset entry notify error -16

核心操作:bind /unbind 切换驱动(最常用调试)

驱动目录统一路径:/sys/bus/spi/drivers/[驱动名]/可用驱动列表查看:ls /sys/bus/spi/drivers/(spidev、w25qxx 等)

解绑当前驱动(触发驱动 remove 函数)

把设备名写入对应驱动的 unbind 文件

# 解绑spidev echo spi0.0 | sudo tee /sys/bus/spi/drivers/spidev/unbind # 解绑w25q闪存驱动 echo spi0.0 | sudo tee /sys/bus/spi/drivers/w25qxx/unbind

4 高通平台

查了一下基本也没啥特别的,还是用标准的接口弄吧。

可能会涉及到检查时钟(Clock)状态,通过 debugfs 查看该 QUP 分组的 SPI 核心时钟和外设时钟(如 gcc_qup_spi_clk)是否真正使能,以及频率是否正确。

cat /sys/kernel/debug/clk/clk_summary | grep -i qup
http://www.gsyq.cn/news/1519504.html

相关文章:

  • Harness Engineering:智能体行为合规审计
  • 如何快速解锁加密音乐:Unlock Music完整使用指南
  • FSICEBASE仿真器实战:从硬件连接到总线分析,深入HC08/S08调试
  • 中国大模型价格战背后的AI基础设施重构
  • APK Installer:在Windows电脑上运行安卓应用的终极指南
  • 温州龙湾手机店top5实践分享,这家必看! - 速递信息
  • 2026科技前沿香港EMBA客观测评与理性选型指南 - 品牌2026推荐
  • ARM Cortex-M0+调试实战:CoreSight架构、SWD接口与MTB追踪解析
  • 2026年上海正规犬舍推荐排名TOP5,新手必看攻略 - 速递信息
  • 如何用开源工具WeChatMsg永久珍藏你的微信记忆?完整指南来了!
  • [实战] 2026年制造业数字化质量审核 (Quality Audit) 深度解析
  • MC68SZ328时钟与电源管理:从PLL配置到低功耗模式实战
  • 海口三亚黄金奢品回收哪家靠谱?跨城上门、无套路变现,海南居民可参考这家! - 同城好物推荐官
  • UVa 473 Raucous Rockers
  • 怎么编写一个 Shell 脚本,从 `/var/log/nginx/access.log` 中统计访问量最高的前 3 个 IP,并按访问次数从高到低输出
  • 3分钟搞定Axure中文界面:告别英文烦恼的终极指南
  • WechatBakTool终极指南:3步轻松备份你的微信聊天记录
  • 别再死记真值表了!通过Multisim仿真,直观理解74LS148优先编码器的工作原理
  • MC68341芯片选与RTC配置实战:从寄存器原理到嵌入式系统稳定设计
  • Windows窗口置顶神器:3步解决多任务窗口遮挡难题的完整指南
  • 怎么编写一个shell脚本,用户输入软件包自动识别系统,然后安装
  • 2026手机端外语口音语音克隆工具实测:口音还原、语种覆盖选型指南 - GrowthUME
  • FPGA时序收敛实战:手把手教你用Vivado正确处理时钟域与生成时钟
  • 5G URLLC低时延保障:深入解析PUSCH Repetition Type B与无效符号处理机制
  • 2026科技驱动型EMBA客观测评:理性选型与项目对比 - 品牌2026推荐
  • 别再只盯着准确率了!手把手教你用颜色矩+SVM做图像分类时的模型调优与评估陷阱
  • MyBatis-Plus动态查询实战:用QueryWrapper的and()和or()优雅构建商品筛选与权限查询
  • 高数期末救命!72道不定积分题里,这5类‘换元法’套路必须掌握(附解题模板)
  • 终端与IDE形态的vibe coding实测:两款AI编程工具迭代能力对比
  • 深度解析发酵饲料:核心原理、应用价值与养殖实践 - 速递信息