ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

RK3588 PCIe初始化卡死?DTS设备树修改实战排查指南

RK3588 PCIe初始化卡死?DTS设备树修改实战排查指南 如果你手头也有一块RK3588开发板并且正在被PCIe初始化卡死问题折磨那这篇DTS修改实战笔记应该能帮你省下至少一周的排查时间。我踩这个坑的起因很简单想给板子挂一张PCIe接口的扩展卡结果内核启动到PCIe驱动probe阶段直接卡死看门狗超时后整板重启周而复始——那个Duang的声响与其说是蜂鸣器不如说是我内心的崩溃声。先说现象。串口日志一路正常输出从bootrom到U-Boot到内核解压驱动逐个初始化一直到出现rockchip-pcie或者dw-pcie相关的打印时一切戛然而止。没有panic、没有oops、没有堆栈回溯就是单纯地停住。这时候如果手边有示波器你会发现SoC的PCIe参考时钟管脚有波形但对应的复位管脚PERST#一直处于无效电平说明链路训练压根没跑起来。再等几秒硬件看门狗把系统踢了重启后又卡在同一个地方。很多第一次遇到这个问题的人会以为是内核配置不对或者U-Boot里没初始化好甚至怀疑是不是芯片本身有问题。但根据我在RK3588多个板卡上反复验证的经验这类静默卡死有九成以上是DTS设备树对PCIe控制器、PHY、电源和复位这几个资源的描述不到位导致的。这篇文章我就从现象出发把排查链路、DTS修改实战和硬件配合项完整地过一遍。1. 卡死现场还原串口日志停在哪一行1.1 静默卡死和普通异常崩掉的本质区别嵌入式Linux开发里系统异常通常分两种一种是有明确的错误输出比如kernel panic、oops、栈回溯这种问题定位起来相对容易打印里通常直接指明了出错的函数和调用链另一种就是这种静默卡死系统没有任何报错线程直接卡在某处不再往下走CPU占用率可能还在跳动中断也可能还在触发但关键路径上的逻辑已经停摆。PCIe初始化卡死属于典型的第二种。RK3588的PCIe控制器驱动在probe时要完成以下几步使能PHY、等待PHY的PLL锁定、配置控制器寄存器、拉高PERST#释放端点复位、启动LTSSM链路训练、轮询等待linkup。这中间的任何一个环节卡住现象都是一样的日志停住不动系统看似还活着但PCIe子系统永远初始化不完。我遇到的情况是日志停在[ 2.183723] rockchip-pcie 3c0000000.pcie: host bridge /pcie3c0000000 ranges: [ 2.190803] rockchip-pcie 3c0000000.pcie: Parsing ranges...然后就没了下文。这个位置说明驱动已经识别到了PCIe控制器并且开始解析地址范围但接下来要做的PHY power-on或者link up操作没能完成。整个系统就这么卡着直到看门狗超时。1.2 为什么这种卡死最让人头疼这种问题的麻烦之处在于它不像编译错误那样有个明确的报错行也不像驱动加载失败那样在日志里打一句failed然后继续。它就是一个停的动作没有任何解释。你甚至不确定到底是驱动代码的哪个while循环在里面死转还是等待某个硬件事件永远等不到。更头疼的是RK3588上PCIe的初始化卡死往往不是单一原因。我后来复盘发现我当时那块板子的DTS里同时存在三个问题电源节点缺失、PHY模式没有显式声明、pinctrl引脚复用不对。这三个问题单独看都不致命但叠加在一起正好把PCIe枚举过程堵死在半路上。所以不要指望有一个万能补丁能解决所有人的问题你需要的是系统化的排查方法。这就是我写这篇文章的核心目的。2. RK3588的PCIe控制器矩阵与DTS节点对应关系动手改DTS之前必须先搞清楚RK3588这颗SoC里PCIe资源是怎么分布的。这也是很多教程没说透的地方——很多人拿着一份公版DTS就开改改完发现现象没变原因就是改错了节点。2.1 四个控制器、三种PHY别搞混了RK3588一共有4个PCIe控制器它们使用的物理层PHY资源各不相同控制器节点协议/宽度使用的PHY典型用途pcie3x4PCIe 3.0 x4pcie30phy独立PHYNVMe SSD、高性能加速卡pcie3x2PCIe 3.0 x2combphy2_ps显卡/网卡pcie2x1l0PCIe 2.0 x1combphy0_ps低速外设pcie2x1l1PCIe 2.0 x1combphy1_ps低速外设这里有两个容易踩的坑。第一pcie3x4用的是独立的PCIe3 PHY和另外三个控制器使用的Combo PHY不是一回事前者的配置入口在DTS里对应的是单独的PHY节点。第二combphy0_ps、combphy1_ps、combphy2_ps是三合一PHY同一时刻只能工作在PCIe、SATA或USB3.0中的一种模式。2.2 Combo PHY的协议互斥SATA和PCIe只能二选一这个互斥特性在实际板卡上非常容易踩雷。很多RK3588开发板把PCIe和SATA设计成物理复用的——同一个接口通过拨码开关或者DTS配置来决定走SATA还是走PCIe。如果你在DTS里同时把SATA0和pcie2x1l0都配成使用combphy0_ps就会出现两种结果有的内核版本启动时打错误日志但继续跑有的版本直接在PHY初始化环节卡死表现就是卡在PCIe probe不动了。排查方法很简单用cat /sys/kernel/debug/phy/phy_register查看当前PHY的注册和使用状态或者直接在DTS里搜索各个combphy节点的引用关系看看有没有被多个控制器同时引用。2.3 DTS里PCIe节点的核心字段一个典型的PCIe控制器节点在DTS里长这样pcie2x1l0 { num-lanes 1; phys combphy0_ps PHY_TYPE_PCIE; phy-names pcie-phy; reset-gpios gpio4 RK_PA2 GPIO_ACTIVE_HIGH; vpcie3v3-supply vcc3v3_pcie; pinctrl-names default; pinctrl-0 pcie2x1l0_reset; status okay; };一个PCIe控制器节点牵扯到PHY引用、PHY模式、复位GPIO、引脚复用、电源供给和状态开关。任何一个字段和实际硬件对不上初始化就会出问题。下面一节我会讲排查链路你会看到这几个字段到底各自扮演什么角色。3. 根因定位链路从内核打印到硬件管脚的一路排除这一节我按照自己当时的排查顺序写你可以照着走一遍。3.1 第一步用initcall_debug锁定卡死位置先用带initcall_debug参数的内核启动或者在内核命令行里加上loglevel8把日志级别拉到最高。启动后观察最后一个有效的内核打印。如果日志停在了PHY初始化附近搜索rockchip-combphy相关打印问题大概率出在PHY配置上如果PHY初始化都过了卡在rockchip-pcie的probe里那就要往控制器本身、复位时序和电源上查。我当时拿到的关键日志就是第一节里贴的那两行Parsing ranges...之后就没有任何输出了。这说明驱动在解析完地址映射之后去执行某项硬件操作时没能返回。3.2 第二步排查Combphy模式冲突由于我那块板子的PCIe接口走的是combphy0_ps我先检查了DTS里是否有其他节点同时引用它。一查果然有问题SATA0节点是okay状态同样引用了combphy0_ps虽然SATA0在物理上没接设备但驱动还是会去初始化PHY这就和PCIe抢占了同一个PHY资源。解决方式是给SATA0加status disabled或者把它的phys引用去掉把PHY让给PCIe。这一步做完日志前进了几行但又卡在了新的位置。3.3 第三步示波器量复位和参考时钟别急着改代码硬件排查这一步很多人会跳过但这恰恰是最快区分软件问题和硬件问题的方法示波器探头接PCIe参考时钟差分对的其中一个管脚REFCLKP或REFCLKN上电后应该能看到100MHz的正弦波或方波。如果没有波形说明PHY的PLL没有输出参考时钟或者板上的时钟芯片/晶振没起振。找PERST#信号测量它从电源稳定到拉高释放复位的延时。PCIe规范要求PERST#必须在所有电源轨稳定之后才能释放如果板子的复位逻辑不满足这个时序端点设备还没准备好链路训练就会反复失败。如果是转接卡或者扩展槽用万用表量一下3.3V和12V供电是否到位。我当时测量后发现参考时钟有、PERST#释放也正常但链路无论如何都训练不起来。这就基本排除了外部硬件的问题焦点回到了SoC内部的PHY配置和DTS描述上。3.4 第四步翻驱动源码确认卡死的具体函数到这一步我打开了drivers/pci/controller/dwc/pcie-designware.c和Rockchip的适配层rockchip-dw-pcie.c在关键打印后面补了调试信息最终定位到卡在dw_pcie_wait_for_link这个函数里——驱动启动了链路训练然后进入轮询循环等待linkup信号但一直没有等到且这个等待在某些条件下不会按时退出。这里引出DTS里一个很重要的参数max-link-speed。如果你的PCIe设备和主控之间信号质量不好走线过长、耦合电容位置不对、连接器接触不良Gen3速率下训练成功率会很低。把速率限制到Gen2甚至Gen1链路反而能稳定跑起来。这是一个性能和稳定性之间的权衡但在初始化卡死这个场景下先让系统跑起来永远是第一优先级。4. 实战修改完整DTS补丁及逐字段说明下面给出我当时的完整修改分四块PHY节点、PCIe控制器节点、电源节点和pinctrl配置。4.1 PHY节点显式声明PCIe模式combphy0_ps { rockchip,phy-mode pcie; status okay; }; combphy1_ps { status disabled; }; combphy2_ps { status okay; };这里把combphy0_ps显式设置为PCIe模式。这个rockchip,phy-mode属性在部分BSP内核里不是必须的驱动会根据phys引用的PHY_TYPE_PCIE自动判断但显式写上可以避免某些版本的内核在PHY初始化顺序上的不确定性。4.2 PCIe控制器节点电源、复位、速率逐一补齐pcie2x1l0 { num-lanes 1; max-link-speed 2; phys combphy0_ps PHY_TYPE_PCIE; phy-names pcie-phy; reset-gpios gpio4 RK_PA2 GPIO_ACTIVE_HIGH; vpcie3v3-supply vcc3v3_pcie; pinctrl-names default; pinctrl-0 pcie2x1l0_reset; status okay; };逐字段解释一下num-lanes 1声明这个控制器只使用1条lane。如果板子只接了一根lane但DTS里默认写成x2甚至x4主控会在多条lane上做接收检测没接的lane会拖慢甚至阻塞整个训练过程。max-link-speed 2把最高速率限制在PCIe 2.0Gen25GT/s。如果你用的是PCIe 3.0设备且确定信号完整性没问题可以去掉这一行但如果你正处在莫名其妙卡死的阶段先加上它把变量降到最少。phys combphy0_ps PHY_TYPE_PCIE这里的PHY_TYPE_PCIE是枚举常量用来告诉combphy驱动把PHY切到PCIe模式。这个宏定义在include/dt-bindings/phy/phy.h里。很多DTS模板文件漏掉了这第二个参数导致PHY驱动用默认的PHY_TYPE_SATA去初始化链路自然起不来。reset-gpios对应PERST#信号。要确认引脚号和你的原理图一致。我见过好几块板子DTS里写的GPIO和原理图差了一两个pin导致复位一直没释放。vpcie3v3-supply端点设备的3.3V主电源。如果这个regulator没有使能端点设备上电不完整接收检测永远失败。4.3 电源节点确保regulator默认使能vcc3v3_pcie: vcc3v3-pcie-regulator { compatible regulator-fixed; regulator-name vcc3v3_pcie; regulator-always-on; regulator-boot-on; gpio gpio3 RK_PC3 GPIO_ACTIVE_HIGH; enable-active-high; };regulator-always-on和regulator-boot-on是简单粗暴但有效的写法确保电源在PCIe驱动probe之前就已经稳定输出。如果你的板子有复杂的电源管理比如通过PMIC控制则需要按照PMIC的regulator节点来配置核心目标是在PCIe驱动开始做链路训练之前电压必须稳定、PERST#必须处于正确状态、参考时钟必须有效。这三个条件缺一个训练就起不来。4.4 pinctrl别忘了引脚复用pinctrl { pcie { pcie2x1l0_reset: pcie2x1l0-reset { rockchip,pins 4 RK_PA2 0 pcfg_pull_none; }; }; };这一节容易被忽略。RK3588的引脚默认状态不一定是GPIO功能如果对应引脚没有被复用为GPIO输出reset-gpios拉高拉低就是不生效的。检查方法是看原理图上PERST#连到SoC的哪个bank的哪个pin然后在pinctrl里严格对应。改完这四块重新编译内核和设备树设备树可以单独编成dtb文件通过U-Boot的tftp加载或者直接烧到boot分区重启后PCIe链路终于训练起来了。串口里能看到类似这样的日志[ 3.152381] rockchip-pcie 3c0800000.pcie: Link up, gen: 2, speed: 5GT/s, width: x1那一刻的感觉就俩字解脱。5. 验证与避坑链路训练超时、耦合电容和那些容易犯的错链路起来之后别急着庆祝。PCIe这个玩意儿训练成功只是第一步后面还有稳定性、带宽、功耗管理一堆事情等着你。5.1 枚举和带宽验证确认链路真正可用系统起来后用lspci -vvv看设备枚举情况和链路状态lspci -vvv -s 01:00.0重点看LnkSta这一行确认速率和宽度和你预期一致LnkSta: Speed 5GT/s (downgraded), Width x1 (downgraded)如果显示downgraded说明最终协商出来的速率低于设备能力多半是信号完整性还有余量问题。带宽实测推荐两个工具挂NVMe SSD就用dd直接读写比如dd if/dev/nvme0n1 of/dev/null bs1M count1000挂PCIe网卡就用iperf3打流。实测下来PCIe 3.0 x1链路大约能跑到700~800MB/s的实际吞吐如果只测出几十MB/s检查一下是不是ASPM省电把链路降速了先在内核命令行加pcie_aspmoff排除。5.2 耦合电容摆放信号完整性的第一道关做硬件的朋友对pcie耦合电容摆放位置这个问题应该不陌生。PCIe的每条TX差分对上都要求串联AC耦合电容典型值是100nF作用是隔离收发双方的直流偏置。摆放位置的核心要求是尽量靠近发送端TX放置业界参考设计一般建议耦合电容到发送端引脚的距离控制在12.7mm500mil以内越近越好。如果电容放得离发送端太远中间那一小段走线就成了短桩在5GT/s甚至8GT/s的速率下会引入严重的信号反射轻则链路降速重则训练完全失败。如果你自己画了RK3588的板子遇到PCIe训练问题先拿游标卡尺量一下耦合电容到SoC引脚的距离再回来做软件侧调整。我见过一块板子就是因为耦合电容放在了连接器旁边而不是SoC旁边Gen3死活训练不起来改成Gen2就稳定了——典型的信号完整性问题。注意RK3588作为主控Root Complex时它发出的TX信号线上必须有耦合电容这个电容可能已经做在了核心板或者SoC模组内部如果你拿到的是核心板底板的结构要确认底板上的PCIe插槽前端是否还需要再放一对耦合电容参考你的核心板硬件手册。5.3 ASPM、训练超时和其他进阶坑RK3588的PCIe驱动对ASPMActive State Power Management的支持在部分内核版本上不够完善。当端点设备支持ASPM L1 substate时可能出现进入低功耗状态后无法正常唤醒的情况表现是系统休眠后PCIe设备消失或者高负载时突然卡死。遇到这类问题先在内核命令行加pcie_aspmoff验证稳定后再逐个开启L0s/L1找到具体是哪个状态有问题。链路训练超时方面dw_pcie_wait_for_link的默认超时时间大概在100ms这个量级。但如果你的端点设备初始化特别慢比如某些FPGA加速卡需要几百毫秒就可能出现主控已经放弃等待设备才刚准备好的竞态。这种情况下的解法不是改DTS而是需要调大驱动里的超时时间。不过这些都是后话——先把DTS修正让系统稳定跑起来再回来做性能层面的优化。最后给个善意的提醒RK3588的PCIe调试一定不要一上来就怀疑芯片或者内核版本。绝大多数情况下问题出在DTS对硬件资源的描述和物理连接不一致上。先理清控制器矩阵再用示波器确认三个关键信号参考时钟、PERST#、电源最后动DTS。每一步都稳扎稳打那个Duang的声音自然就会离你远去。类似的排查思路其实也适用于RK3588上其他外设的调试比如GMAC网卡的DTS调试——核心都是先确认硬件连接再核对DTS的资源描述最后才考虑代码层面的问题。
返回列表