资讯动态

SPI从机NSS引脚Claim失败的设备树配置冲突排查

发布时间:2026/8/30 15:53:01 来源:尧图企业网站定制
1. 问题初现SPI3从机模式下PB1始终“Claim”不上最近在STM32MP257F-EV1这块板子上调SPI3从机模式卡了一个非常典型的引脚问题NSS引脚PB1怎么都claim不上。整条链路是MP257作为SPI3从机外部主机通过片选信号NSS选中它PB1负责接收片选电平。现象很直接——从机端的中断触发了但每次尝试claim总线时都会失败日志里反复出现NSS pin claim失败的信息。先说明一下“claim”这个词在SPI从机场景下不是抽象概念它指的是从机确认自己已经被主机选中、可以开始收发数据的状态。如果NSS引脚没有正确配置成输入监听或者被别的外设占用这个claim动作就永远落不了地。复现的条件不难搭EV1板上的SPI3接到另一个主机的SPI控制器主机正常发起通信但MP257这边从机状态起不来。单独看引脚电平是正常的PB1也能测到片选信号拉低但软件层就是认为这个NSS没有归属权直接放弃后续的DMA收发。这里最迷惑人的地方在于你用示波器量PB1波形完全正常你用GPIO读取寄存器电平状态也对但一旦让SPI驱动去claim这个NSS它就会报错。后来我意识到问题不在硬件电平而在于这个引脚在软件内核里被赋予了多重身份而这些身份互相冲突导致驱动层拿不到它。日志最初的样子大概是这样的spi3: failed to claim NSS pin (PB1) spi3: slave mode disabled第一次看到这条日志时我第一反应是GPIO子系统里PB1被别的驱动申请了。因为通常“claim失败”就是gpiod_request返回-EBUSY。但查了一圈之后发现不是这么简单。SPI控制器在设备树里同时存在两种使用方式——普通GPIO和复用功能。PB1如果被pinctrl设成了SPI3_NSS的AF功能那么从GPIO子系统的角度看它就不属于“可claim”的状态如果驱动尝试把它当普通GPIO来用内核就会拒绝。这个看似边缘的问题实际上把设备树里引脚复用、SPI从机控制器、GPIO中断监听三块机制全部串了一遍。接下来的内容就是这次排查的完整记录包括为什么会出现这种冲突、我是怎么一层一层定位到根的、最终怎么改才真正修好以及以后再遇到类似问题时可以快速复用的判断思路。2. 从硬件到软件PB1在MP257F-EV1上的真实身份2.1 硬件侧PB1在板子上的实际走线先回到板子本身。STM32MP257F-EV1的SPI3对应的NSS硬件引脚就是PB1这在芯片数据手册的alternate function表里写得很明确PB1可以复用为SPI3_NSS。但要注意EV1板是一块功能非常多的评估板PB1并不是只连到了SPI3的片选接口它同时可能被板载的其它外设占用比如某个扩展排针、某个电平转换电路、甚至是板载ST-LINK部分的调试辅助功能。我仔细翻了一遍板子的原理图确认了这块板上PB1在默认状态下会连接到另一个MCU的GPIO或者某个外设。虽然默认跳帽没有接通但在某些配置组合下这条信号线会同时被两边的驱动电路影响。这个硬件背景很重要因为我最开始排查时只在软件层找问题后来发现即使软件配置完全正确只要板子上跳线帽的位置不对也会导致NSS电平被强制拉低或拉高进而让从机产生误触发。所以在排查“claim失败”时第一件值得做的事永远是确认原理图上PB1的附属连接而不是直接冲进设备树改配置。这块板子的用户手册里有一个跳线配置表里面专门列出了PB1在不同模式下需要断开的连接点。2.2 软件侧设备树里SPI3从机模式的三层配置MP257F-EV1跑的是标准Linux内核SPI控制器由设备树描述。SPI3要从机模式工作涉及设备树里至少三处配置。第一处是控制器节点的mode属性要明确标注slave模式第二处是pinctrl引脚的复用状态PB1必须被选为SPI3_NSS的AF功能或者作为GPIO输入第三处是中断资源因为从机模式下NSS的下降沿通常要触发中断来通知驱动开始准备收发。这三层缺一不可而且它们之间还会互相牵制。我当时最初写的设备树是这样的spi3 { pinctrl-names default; pinctrl-0 spi3_slave_pins; spi-slave; status okay; };spi3_slave_pins在pinctrl节点里将PB1复用为SPI3_NSSspi3_slave_pins: spi3-slave-pins { pins1 { pinmux STM32MP_PINMUX(B, 1, AF5); /* SPI3_NSS */ bias-pull-up; }; /* SCLK, MISO, MOSI 等 */ };这样配置之后从硬件功能上看PB1确实是SPI3_NSS由SPI控制器内部的NSS逻辑来解析片选信号。但问题来了Linux内核的SPI从机框架在处理slave模式时对NSS的处理方式并不是单纯依赖控制器硬件。它需要驱动能感知到NSS电平的变化才能正确执行总线claim操作。如果你的驱动通过GPIO中断来检测片选信号那PB1就不能被设成AF功能而应该作为普通GPIO输入。一旦AF和GPIO两种角色同时被不同模块要求claim失败就是必然结果。2.3 角色冲突的根源AF、GPIO、EXTI三方抢同一个引脚PB1这个引脚在内核里主要有三种使用模式。第一种是作为IP的复用功能AF由SPI3_NSS硬件逻辑接管第二种是作为普通GPIO输入由gpiolib管理第三种是作为EXTI外部中断输入用于在NSS电平跳变时产生中断。这三种模式互不兼容同一个时刻一个引脚只能处于其中一种状态。MP257的SPI3从机驱动在NSS claim这件事上通常有两种实现路径。一种是完全依赖硬件NSS驱动读SPI_SR寄存器里的FRLVL或NSS状态位来判断是否被选中另一种是用GPIO中断监听NSS引脚在中断回调里完成claim动作。第二种方式在Linux SPI slave框架下更常见因为从机驱动要主动地把DMA准备好必须有一个明确的中断触发点来启动整个流程。而当时设备树里把PB1做成了AF功能驱动却试图用gpiod_get来申请这个引脚作为中断输入。gpiolib发现引脚已经被pinctrl子系统设成了AF模式直接拒绝请求返回-EBUSY。驱动在申请失败后就会打印“failed to claim NSS pin”。这不是SPI控制器的问题而是引脚所有权归属混乱导致的系统性问题。3. 逐层排查从日志到寄存器定位问题根因的完整链路3.1 第一步确认pinctrl实际生效状态一开始我先确认设备树里的pinctrl是否真的生效。Linux内核的pinctrl子系统已经接管了引脚复用单纯看设备树不一定准确要看运行时状态。通过debugfs可以查到引脚的实时配置cat /sys/kernel/debug/pinctrl/pinctrl-handles cat /sys/kernel/debug/pinctrl/pinctrl-devices更直接的命令是查看某个引脚当前的function和groupcat /sys/kernel/debug/pinctrl/48000000.pinctrl/pinmux-pins | grep PB1结果让我明确了问题PB1确实处于AF5功能状态也就是SPI3_NSS复用模式。这跟设备树配置一致说明pinctrl侧没有问题。但问题恰恰就出在这里系统里另一个驱动SPI3从机驱动试图把PB1当作GPIO来申请这显然会和当前状态冲突。查日志也能看到类似这样的信息gpiochip: GPIO 33 (PB1) has status 0x1, claimed by pinctrlGPIO编号在不同芯片上映射不同但意思很明确引脚被pinctrl占用了gpiolib无法再分配。3.2 第二步确认SPI3控制器进入了slave模式为了排除控制器本身没有配置成slave模式的可能我检查了spi3控制器节点的运行时状态。Linux内核的SPI框架有一个判断逻辑如果节点里有spi-slave属性控制器会注册为slave控制器否则默认是master控制器。查看内核打印或者sysfs都能确认cat /sys/class/spi_slave/spi3/device/uevent或者直接看内核里注册的spi_controller类型ls /sys/bus/spi/drivers/spi-slave-time/这几步确认下来控制器本身确实是以slave模式注册的。那问题就进一步缩小到了NSS引脚的所有权上。控制器能进slave模式但驱动缺一个能用的片选监听引脚。3.3 第三步用gpiochip信息排查PB1被谁占用既然怀疑引脚所有权冲突就直接用gpiolib的用户空间接口来查。在debugfs里有一个非常关键的文件cat /sys/kernel/debug/gpio输出会列出所有gpiochip下面每个引脚的使用状态。PB1对应的GPIO如果被某个驱动申请过会出现类似“gpio-33 (spi3-nss) held”这样的标记。如果被pinctrl占用则会显示为“pinmux”或者直接被sysfs指认为不可用状态。我当时的输出里PB1根本没有出现在gpiolib的占用列表里因为pinctrl接管之后它根本不会被注册为GPIO请求对象。这时基本可以断定驱动里调用gpiod_get(GPIO-based NSS)时内核返回了-ENODEV或者-EBUSY导致claim失败。3.4 第四步根因浮出水面——硬件NSS和软件NSS被混用了整个链路查到这里真相已经清楚了。设备树里PB1被配置成SPI3_NSS的AF功能这本身没有错如果是纯裸机HAL开发这种配置完全没问题。但在Linux内核的SPI从机框架下驱动对NSS的处理走的是GPIO中断路径。两条路径不能共享同一个引脚。这个问题的本质是硬件上PB1只能属于一种模式但软件逻辑里同时需要“硬件SPI3_NSS信号”和“GPIO中断信号”两种身份。这两个需求无法同时满足只能二选一。实际场景中大多数SPI从机驱动都倾向于使用GPIO中断来感知NSS变化因为这样可以在中断回调里灵活地把DMA缓冲准备好而不是依赖硬件NSS标志位的轮询。所以修复方向也明确了要么把PB1配置为GPIO输入并触发中断让驱动用软件方式处理NSS claim要么完全依赖硬件NSS让驱动修改成轮询SPI状态寄存器的方式。考虑到现有驱动架构和DMA收发流程前者改动量最小也最稳定。4. 修改方案把PB1还给GPIO让驱动真正“Claim”成功4.1 设备树最终写法正确的方式是把PB1从AF功能改为GPIO输入并允许它作为EXTI中断引脚。这样SPI控制器本身仍工作在slave模式但NSS的claim判断全部交给GPIO中断来完成。设备树应改成spi3 { pinctrl-names default; pinctrl-0 spi3_slave_pins_sck_miso_mosi; spi-slave; status okay; slave { compatible some-spi-slave-driver; reg 0; spi-max-frequency 1000000; interrupt-parent gpioa; interrupts 11 IRQ_TYPE_EDGE_FALLING; /* 根据PB1实际EXTI中断号调整 */ }; };这里有一个容易踩坑的细节从机节点的reg值。SPI从机模式下没有真正意义的片选地址但内核框架要求必须有reg属性一般填0即可。同时驱动会自己监听NSS引脚中断所以不需要在SPI子节点里声明cs-gpios也不应该再声明spi-cs-high之类的属性。对应的pinctrl只保留时钟和数据的复用功能spi3_slave_pins_sck_miso_mosi: spi3-slave-pins { pins1 { pinmux STM32MP_PINMUX(B, 0, AF5), /* SPI3_SCK */ STM32MP_PINMUX(B, 4, AF5), /* SPI3_MISO */ STM32MP_PINMUX(B, 5, AF5); /* SPI3_MOSI */ bias-disable; drive-push-pull; slew-rate 1; }; };PB1不再出现在pinctrl里它作为GPIO输入由驱动单独管理。为了确保输入电平稳定需要在驱动初始化时把PB1配置为带上拉的输入模式防止片选信号浮空导致误中断。4.2 驱动侧NSS claim逻辑调整驱动里原来的代码大概是这样做的nss_gpio gpiod_get(dev, nss, GPIOD_IN); if (IS_ERR(nss_gpio)) { dev_err(dev, failed to claim NSS pin\n); return PTR_ERR(nss_gpio); }前面已经分析了这行代码在PB1被pinctrl占用时必然会失败。修改后PB1不再被pinctrl声明gpiod_get就能成功拿到引脚。然后需要申请中断nss_irq gpiod_to_irq(nss_gpio); ret request_irq(nss_irq, nss_irq_handler, IRQF_TRIGGER_FALLING | IRQF_NO_SUSPEND | IRQF_ONESHOT, spi3-nss, spi3_slave_dev);中断处理函数里做两件事一是关掉后续的NSS中断防止重复触发二是唤醒等待claim的工作队列让DMA和SPI传输逻辑继续执行。释放claim时再打开中断等待下一次片选信号。这里有个很小的细节IRQF_ONESHOT标志是必须的因为中断处理线程在运行期间片选信号可能已经拉高如果不在线程开始时把中断屏蔽掉很容易产生中断风暴。处理完成后重新使能中断刚好能捕捉下一次NSS下降沿。4.3 实测效果和验证修改完成后重新编译内核设备树和驱动上电加载。这次日志干净得多spi3: successfully claimed NSS pin (PB1) spi3: slave mode enabled, waiting for NSS falling edge然后给主机那边发送一组测试数据从机通过中断检测到NSS拉低进入claim状态执行DMA接收接收完成后释放claim。来回跑了一千多次没有出现一次claim失败。用示波器观察PB1的波形可以看到片选信号的边沿非常干净没有毛刺和中断触发时刻完全对齐。DMA传输的数据也逐一对比过收发完全一致。这说明整个“GPIO中断检测NSS → claim → DMA收发 → 释放claim”的链路是稳定可靠的。5. 避坑总结这类问题的通用排查思路5.1 从这次问题里提炼出的几条经验第一SPI从机模式下NSS的处理方式必须先确定好是硬件NSS还是软件NSS。这个选择决定了PB1在设备树里的角色。如果你用Linux内核软件NSS通常是更友好的方案因为内核的SPI slave框架天然支持通过GPIO中断来感知片选信号。硬件NSS也能工作但你需要自行轮询状态寄存器实时性和CPU占用率都不如中断方案。第二设备树里pinctrl和GPIO的冲突极其隐蔽。你很难一眼看出PB1已经被pinctrl设置为AF功能因为这种配置不会在启动日志里报错只有当你尝试用gpiod_get去申请它时才会暴露。遇到这种问题先去查debugfs下的pinmux-pins和gpio两个文件确认引脚的所有权状态。第三不要被示波器上的正常波形迷惑。PB1电平有变化不代表软件层能正确处理。片选信号的物理变化和Linux内核里claim状态是两码事前者是模拟世界的事后者是gpiolib、pinctrl、interrupt子系统协作的结果。任何一个环节没对齐都会导致“电平正常但claim失败”这种诡异现象。第四EV1这类评估板尤其要注意硬件跳线和板载外设。很多时候问题不是出在你写的软件上而是板子上一根跳线帽把PB1接到了别的地方导致即使你软件完全正确也测不出期望行为。排查时先把原理图打开把PB1相关的所有网络看一遍。5.2 快速自检清单再遇到类似的SPI从机NSS问题我建议直接按照这个顺序检查。第一用示波器或逻辑分析仪确认PB1实际有没有收到片选信号确认电平极性是否符合预期。第二看内核日志有没有gpiod相关的告警比如“cant claim GPIO”或“failed to request”之类的字样。第三打开debugfs查pinmux状态确认PB1是否被复用为其它功能。第四确认设备树里是否有spi-slave属性控制器是否真的以从机模式注册。第五确认GPIO中断号是否正确EXTI映射是否覆盖PB1。这几项检查通常在十分钟内就能完成能过滤掉绝大多数“假故障”。真正刁钻的问题反而在最后一步就是中断处理函数里有没有正确屏蔽和恢复中断、有没有在nss下降沿和DMA准备之间处理好时间竞争。这次调通之后我把这套检查流程固化成了一个脚本以后每块新板子要调SPI从机直接在串口终端跑一遍输出结果一目了然。最后再分享一个小技巧。如果你的SPI从机驱动对实时性要求比较高建议在DMA描述符里提前预置好接收缓冲区同时把DMA的传输方向设置成双缓冲模式。这样NSS中断到来时DMA已经在等待数据了可以做到片上延迟只取决于SPI时钟周期和数据长度而不是软件调度延迟。实测下来吞吐率比中断后手动启动DMA高了将近一倍。这次我在MP257F-EV1上跑通之后又顺手测了1MHz到20MHz之间几档SPI时钟从机都能稳定响应NSS claim再也没有掉过链子。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价