尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

RK3588 PCIe初始化卡死排查:设备树DTS配置实战指南

RK3588 PCIe初始化卡死排查:设备树DTS配置实战指南 干嵌入式的朋友应该都有过这种经历RK3588开发板镜像烧好了上电那一瞬间还挺期待结果U-Boot日志刚开始滚动没几行屏幕就定格了。再等几秒系统又自动重启反反复复卡在同一个地方。串口最后一条有效打印总是跟PCIe相关。那种感觉就像走在路上突然被一口锅砸中Duang一下画面直接静止。这个“PCIe初始化卡死”的坑我前前后后踩了好几个版本从PCIe3.0x4口接NVMe盘到PCIe Switch级联再到PCIe2.0接无线网卡基本都碰到过。折腾到最后发现90%的情况都不是硬件烧了也不是内核源码编译错了而是DTSDevice Tree Source里的PCIe节点配置没有调对。这玩意儿表面上看只是几个文本文件但在RK3588这种多控制器、多PHY复用的SoC上它直接决定了复位时序、时钟配置、地址窗口和供电控制任何一个环节不对启动阶段就是死给你看。这篇文章就把我实际排查和处理RK3588 PCIe初始化卡死的过程完整写出来从现象定位、DTS节点解读到修改实战最后附上排查速查表。适合正在用RK3588做核心板/官方开发板二次开发、被PCIe设备识别问题折磨的朋友直接照着文章里的思路去查大概率能省下几个通宵。1. 先说现象这个坑是怎么踩进去的1.1 启动到一半就定格的现场我遇到的最典型一次是给一块RK3588板子接了一个PCIe3.0x4的NVMe SSD。现象很统一开发板上电后串口打印正常U-Boot开始初始化外设。等日志走到PCIe相关初始化处整个系统就卡死了——屏幕最后一行内容停在某个地址读取或链路训练相关打印串口也没有任何后续输出。更麻烦的是有时候不是每次都卡死。冷启动大概率挂在同一位置但按一下复位键偶尔又能过去。这种“间歇性卡死”在PCIe问题里非常典型它不是某行代码写错导致必现错误而是时序、供电、复位时钟这些模拟层面的东西出了问题表现出来就是概率性失败。另一个常见现场是系统能正常进入Linuxlspci也能看到设备但一进行I/O访问或者DMA读写整个系统就像被按了暂停键任何中断响应都没有了只能断电重启。这种“访问即死机”的现象比起启动卡死还难排查因为它涉及的不只是设备树配置还有中断映射、地址空间分配和MSI配置全链路都得查一遍。1.2 先定位卡死的位置再谈修改DTS遇到卡死第一件事不是去翻DTS而是先确定“卡死发生在哪个阶段”。RK3588从开机到Linux跑起来PCIe的初始化大致会经过三个阶段U-Boot阶段U-Boot会做一次PCIe控制器的基本初始化包括PHY上电、链路协商、设备扫描和配置空间读取。这个过程在U-Boot环境变量里可以控制如果U-Boot没有使能PCIe的扫描这一阶段会跳过。Kernel启动早期内核在设备树里匹配到PCIe节点后会执行probe流程把PHY、时钟、复位、电源全部拉起来然后进行总线枚举分配总线号、内存窗口和中断资源。驱动运行阶段PCIe设备驱动加载后开始配置BAR空间、申请中断MSI或INTx、执行DMA读写。一旦驱动动起来之后卡死多半是中断或地址窗口的问题。我自己调试时有个习惯先把U-Boot环境变量pcie_scan_mode之类关掉或者在内核启动参数里加上pcinoaer、pcinomsi来缩小范围。如果关掉某项后系统不再卡死那就能大致确认问题出在哪一块。这个方法比直接对着DTS改参数要靠谱得多。定位到阶段之后再回头去分析DTS里对应的节点就不会像无头苍蝇一样乱试了。2. 搞懂RK3588的PCIe控制器修改DTS才不会瞎改2.1 三路PCIe控制器先分清你在用哪一路RK3588这颗SoC上总共提供了三路PCIe控制器但它们并不是一样的控制器对应PHY通道数速率常见用途pcie2x1l0pcie2phy1 lanePCIe 2.0M.2 Key E插槽、WiFi/蓝牙模块pcie3x2pcie3phy2 lanesPCIe 3.0低速网卡、SATA转接卡、部分扩展槽pcie3x4pcie3phy4 lanesPCIe 3.0NVMe SSD、万兆网卡、PCIe Switch注意看前面两行pcie3x2和pcie3x4用的都是pcie3phy这个PHY而pcie3phy是一个Combo PHY它内部可以配置成PCIe、SATA或USB3等不同模式。这就意味着如果你在DTS里同时打开了pcie3x4和某个SATA接口但实际上硬件上把PHY分配给了PCIe另一边就会初始化失败甚至反过来把PCIe带崩。我见过一个比较隐蔽的坑板子上既有PCIe3.0x4插槽又有两个SATA接口DTS里两边的status都设成了okay结果PCIe链路训练一直失败但硬件上查复位和时钟又都正常。最后查来查去是PHY资源冲突——同一个PHY通道被解析时抢占了导致PCIe侧等不到PHY ready信号。所以改DTS之前先对照芯片手册看看自己用的那个PCIe口和哪些外设共享PHY、哪些引脚是复用的这一步省不了。另外板子上的PCIe插槽形态也很关键。很多开发板的M.2 Key M插槽物理上是x4的但实际只连了x2的线或者反过来设计的是x2但DTS里配了x4。遇到这种“配置大于物理连接”的情况链路训练会因为等待剩余lane的接收检测而超时表现就是卡在初始化阶段。遇到这类问题先把num-lanes改成实际引出的lane数量或者把max-link-speed降到2.0试试往往能快速定位。2.2 DTS里PCIe节点关键属性逐个拆解RK3588的DTS里PCIe节点的名字很像比如pcie3x4: pciefe150000、pcie3x2: pciefe160000第一次看可能会搞混。这里先把每个节点的关键属性解释清楚后文改起来才有方向。compatible通常为rockchip,rk3588-pcie内核和U-Boot就是靠它来匹配具体的驱动实现不同SoC的PCIe驱动差异很大这个字符串一般不用动。reg包含控制器的寄存器基地址空间和配置空间地址段。配置空间的地址范围如果被内核错误映射也会导致枚举失败。一般不需要改但要留意是否有内存地址重叠。phys / phy-names指定这个PCIe控制器使用哪个PHY比如phys pcie3_phy、phy-names pcie-phy。这里对应的PHY节点必须在DTS里也被正确使能否则驱动上电时就会报phy_power_on失败直接卡死。resets / reset-names控制器的复位信号定义。RK3588的PCIe控制器复位时序要求比较严格有些版本的内核驱动要求先释放pipe复位再释放apb复位顺序反了会导致链路始终起不来。vpcie3v3-supply / vpcie12v-supplyPCIe插槽的供电控制。很多开发板通过GPIO控制一个负载开关给PCIe插槽供电如果这个属性没配或者对应的GPIO被别的设备占用了插槽上没电设备自然不被识别。max-link-speed取值1Gen1、2Gen2、3Gen3直接限制链路协商的最大速率。num-lanes控制器期望的lane数量和硬件设计强相关。ranges描述PCIe总线地址域与CPU地址域之间的映射范围。这个属性一旦配错设备枚举时BAR空间分配就会异常轻则设备识别不了重则访问时总线错误直接系统挂死。msi-map把PCIe设备的MSI中断映射到SoC的中断控制器。RK3588上如果这个映射配错设备驱动申请MSI中断会失败部分驱动会退化成轮询模式但有些nvme驱动就直接罢工。简单说DTS里这些属性就是告诉内核“这路PCIe怎么用、接到哪里、谁能给它供电、中断怎么上报”。每一项都有实际的硬件依据不能靠猜。修改时最忌讳的就是对着别人家的dts文件抄不同板子的GPIO编号、PHY分配、复位逻辑差异很大抄过来十有八九更糟。3. 手把手改DTS三个典型卡死场景的完整解法3.1 场景一PCIe3.0x4接NVMeU-Boot启动即卡死有块RK3588板子M.2接口是Key M理论上支持PCIe3.0x4 NVMe但一接上SSD开机就卡在U-Boot的PCIe扫描阶段。串口最后的打印类似pcimap: 0xfe150000, read 0x0 ... pcie link training timeout看到link training timeout先别急着怀疑SSD坏了因为多数情况是链路训练没有完成。我当时排查分了三步第一步检查硬件。用示波器确认REFCLK100MHz参考时钟是否正常量复位信号的时序是否满足要求。这里特别说一下耦合电容PCIe的发送和接收差分对都需要串联交流耦合电容而且必须靠近发送端放置。有些Layout为了走线方便把电容放得离连接器很远或者电容容值不对都会让信号质量变差导致链路训练不稳定。如果硬件上这一步有问题改DTS是救不回来的。第二步确认DTS节点配置和实际硬件一致。我那块板子问题就出在DTS里把num-lanes 4但实际上M.2插槽只连了2对差分线。PCIe在链路训练时如果某一对lane收不到对端信号整个训练过程会一直等待然后超时表现就是卡死。把num-lanes改成2之后启动就不再卡了。第三步如果确认是多lane配置没问题但仍然卡死可以尝试把max-link-speed降级。因为Gen3链路训练的信号完整性要求比Gen2高很多如果布线质量一般或者连接器/转接卡阻抗控制不好Gen3协商就容易失败。临时改成max-link-speed 2先验证是不是速率因素导致的。等系统能正常跑起来再回头处理信号质量问题而不是长时间卡死在启动阶段。修改后的DTS片段类似这样pcie3x4 { status okay; reset-gpios gpio4 RK_PB6 GPIO_ACTIVE_HIGH; vpcie3v3-supply vcc3v3_pcie; max-link-speed 2; num-lanes 2; };这里reset-gpios的GPIO编号一定要跟原理图对应。如果复位引脚被复用成了别的功能或者另一个驱动先把它拉低了PCIe控制器就一直处于复位状态后面怎么配都没用。3.2 场景二Kernel跑起来了PCIe设备却访问即死机这个场景我自己排查了最久。现象是U-Boot阶段没问题Linux能启动lspci也能列出设备但一访问设备的BAR空间比如用dd去读NVMe系统就彻底卡死。这种问题在PCIe调试里属于“枚举OK但数据通路异常”基本指向地址范围或中断映射配置不对。当时我是接了PCIe Switch连接多个设备然后发现分配到的总线号范围和BAR空间映射有问题。PCIe总线是分层次的CPU通过Root Port访问下游设备需要把下游设备的BAR空间映射到CPU的物理地址空间中这个映射关系就是由DTS里的ranges属性来定义的。RK3588的PCIe3x4节点里ranges一般类似ranges 0x81000000 0x0 0xf8100000 0x0 0xf8100000 0x0 0x100000 0x82000000 0x0 0xf8200000 0x0 0xf8200000 0x0 0x800000;第一行0x81000000表示这是一个32位非预取内存窗口第二行0x82000000是64位预取窗口。如果你接的PCIe设备BAR空间很大比如某些万兆网卡和GPU32位非预取窗口不够用设备驱动可能会申请失败。而如果两个窗口映射的地址段有重叠或者跟系统其他外设的物理地址冲突访问时就会触发总线错误导致整个系统卡死。另一个常见原因是MSI中断映射错误。RK3588设备树里PCIe节点通常会通过msi-map把下游设备的设备ID映射到GIC的SPI中断上。比如msi-map 0x0 gic 0 0x140 0x100;这段的意义是PCIe设备的Request ID低8位从0开始映射到GIC SPI 0x140即中断号320开始的连续中断。如果映射范围设小了设备数量多的时候某些设备会拿不到中断号驱动在申请MSI时失败也可能会出现“一访问就死”的情况。针对这个场景我的处理办法是先把pcinomsi加到内核启动参数里强制走INTx中断。如果系统不再卡死就锁定是MSI映射问题如果仍然卡死再回头看ranges和BAR分配。这种方式能快速缩小排查范围比反复改DTS重编译要高效得多。3.3 场景三PCIe Switch级联复位时序混乱导致系统反复重启第三个场景是我最近才处理的RK3588通过PCIe3.0x4口接了一个PCIe Switch再从Switch分出几个设备。系统上电后不是每次都卡死而是概率性卡死而且一旦卡死看门狗超时后反复重启。这种问题百分之八九十是复位时序和供电时序没做好。PCIe Switch作为下游设备它自己内部也有复杂的电源和复位管理。上游Root Port的复位释放之后Switch需要经过一段延时才能完成内部初始化。如果DTS里给Switch的供电没有提前拉起来或者复位GPIO释放得太早Switch就会处于不确定状态链路训练自然失败。在设备树层面我们能做的修改主要是把Switch的电源和复位控制显式声明出来。如果你用的是GPIO控制的电源开关和复位引脚可以加到PCIe节点的属性里或者定义一个固定电源节点vcc3v3_pcie_switch: vcc3v3-pcie-switch-regulator { compatible regulator-fixed; regulator-name vcc3v3_pcie_switch; regulator-min-microvolt 3300000; regulator-max-microvolt 3300000; gpio gpio1 RK_PA5 GPIO_ACTIVE_HIGH; enable-active-high; regulator-boot-on; regulator-always-on; };然后在PCIe节点里引用pcie3x4 { status okay; vpcie3v3-supply vcc3v3_pcie_switch; };另一个做法是在驱动的probe流程中确认时序延时。有些DTS支持reset-delay-us之类的属性或者在驱动源码中调整rockchip,pcie-reset-delay-ms。如果你用的SDK版本里驱动不支持这种调参可以在U-Boot环境变量或驱动源码里临时加延时先把时序窗口试出来再固化到设备树和驱动里。PCIe Switch级联还有一个特别容易踩的坑上游端口的max-link-speed不能低于Switch的上行端口能力否则协商速率不一致链路就UP不起来。比如Switch上行是Gen3但DTS里Root Port设成了max-link-speed 2那么链路最高只能跑Gen2虽然也能工作但如果Switch固件对于降速兼容性不太好也会出现初始化挂起的情况。最稳妥的做法是先把Root Port设为max-link-speed 3让链路按最高能力协商启动稳定后再考虑降速降功耗。4. 如何验证修改是否有效日志和系统状态检查4.1 U-Boot阶段的PCIe扫描日志怎么看U-Boot阶段打印的信息量虽然不如Linux内核详细但位置很关键因为最早期的卡死就发生在这里。要看U-Boot是否做了PCIe初始化可以先看启动日志里有没有类似PCIE、pcie的打印或者在U-Boot命令行里执行pci命令。如果你用的是Rockchip官方SDKU-Boot下可以进入命令行执行pci enum主动触发扫描。正常枚举成功后再执行pci 0或pci info能看到设备列表。如果执行pci enum时卡住不动说明要么是硬件链路问题要么是U-Boot的设备树配置有问题。U-Boot的PCIe配置一般在arch/arm/dts/rk3588-u-boot.dtsi里里面会对pcie3x4做进一步配置。有时候Linux侧的DTS没问题但U-Boot侧DTS里PCIe节点status是okay而U-Boot版本的驱动对某些属性的处理不够完备也在早期阶段卡死。遇到这种情况可以先在U-Boot的DTS里把对应的status改成disabled跳过PCIe扫描确认后续启动流程正常。等Linux起来后再启用PCIe如果Linux侧驱动能正常初始化那问题就在U-Boot驱动版本而不是硬件。4.2 进入Linux后用lspci和dmesg做最终确认Linux起来之后第一件事就是看lspci输出。lspci -vvv-vvv会输出非常详细的设备能力、链路状态和中断配置。重点关注链路状态部分比如LnkSta显示的速率和宽度LnkSta: Speed 8GT/s (downgraded), Width x4如果显示downgraded说明链路协商到了更低的速率但不代表有问题只要稳定工作就行。如果显示Speed 2.5GT/s说明只协商到了Gen1多半是时钟或信号质量问题。再看dmesg里的PCIe相关打印。排查时我一般重点关注这几个关键词pcie所有PCIe控制器的probe日志。phy_power_onPHY上电是否成功。link is up链路协商是否成功。out of resources总线号、地址资源分配不足。msiMSI中断分配是否正常。barBAR空间分配情况。比如出现这一行rockchip-pcie fe150000.pcie: PCIe Link up, LTSSM is ACTIVE STATE说明链路层面已经OK了。但如果后面跟着rockchip-pcie fe150000.pcie: Error: Invalid BAR setup那就说明地址映射和BAR空间配置还有问题需要检查ranges属性。另外内核的PCIe调试节点也很有用。挂载debugfs后可以查看每个PCIe设备的配置空间mount -t debugfs none /sys/kernel/debug ls /sys/kernel/debug/pci直观读一下配置空间确认设备ID、厂商ID、状态寄存器是否正常。我在调试PCIe Switch时就靠debugfs确认了Switch的配置空间能被CPU正常读取从而把问题范围缩小到了中断映射上。5. 避坑速查表常见卡死原因与DTS对策5.1 高频故障原因与排查对照表调试PCIe卡死最忌讳的就是毫无章法地乱改DTS。下面这张表是我把几个典型case整理出来的对照表遇到类似现象可以直接按表排查。现象可能原因检查方法DTS修改方向U-Boot阶段持续卡死日志停在链路训练lane数配置过多、REFCLK异常、耦合电容问题示波器量100MHz时钟检查M.2实际接线临时降lane数调整num-lanes先降到实际lane数或max-link-speed 2Linux启动时phy_power_on失败PHY节点未使能、Combo PHY被其他接口占用dmesg | grep phy查看pcie3phy状态确认pcie3_phy { status okay; }关闭冲突的SATA/USB节点能枚举设备但访问资源时死机地址窗口配置错误、BAR空间冲突lspci -vvv查看BAR范围核对ranges修改ranges扩大或调整非预取/预取窗口地址段设备驱动申请中断失败MSI映射范围不足或GIC中断号冲突启动参数加pcinomsi验证查看/proc/interrupts核对msi-map属性确认映射范围和中断号区间概率性卡死断电重启后有时正常复位时序、电源时序不稳定用示波器对比复位和供电波形增加供电/复位延时显式声明reset-gpios和vpcie*-supply接PCIe Switch后反复重启Switch供电不足、上游速率协商异常测量Switch供电电压查看Switch内部初始化日志增加独立电源节点调整max-link-speed与Switch能力匹配这张表里列的原因基本覆盖了RK3588平台PCIe初始化的绝大多数卡死情况。但要注意有时候是多个原因叠加比如既复位时序不对又带了设备树配置冲突需要逐个排除。5.2 DTS修改后的编译、烧录与回滚技巧修改DTS不是改完就完事还得知道怎么让修改生效。RK3588的SDK一般支持单独编译dtb不需要每次都完整编译整个固件。以Rockchip Linux SDK为例编译单个dtb的命令大致是./make.sh rk3588-evb1-lp4-v10-linux.dts如果只需要生成dtb文件某些SDK版本也支持直接进入kernel目录手动执行make ARCHarm64 rockchip/rk3588-evb1-lp4-v10-linux.dtb生成的dtb文件路径一般在kernel/boot/resource.img或者单独的dtb分区里具体要看你的分区表。烧录时不能只替换dtb还要确认你的升级工具/脚本是否会把新的dtb打包进resource.img或boot.img。我曾经犯过一个低级错误只烧录了kernel image忘了重新打包resource结果改了半天的DTS根本没生效。另外强烈建议修改DTS之前先把原厂dtb备份好。可以用dd命令直接读取dtb分区dd if/dev/mmcblk0pX of/root/original_dtb.img一旦改崩了随时能刷回去。PCIe卡死的调试过程很折磨人如果连回滚通道都没有压力会更大。对于内核里同时存在多个DTS的情况可以先通过U-Boot的bootcmd指定加载不同的dtb文件做A/B对比验证。这样就不用每次改动都去烧整机调试效率能提升不少。6. 实操中积累的几个额外心得最后再写几个个人感觉很有用的经验不算教科书内容但实战中确实救过我很多次。第一个心得调试PCIe初始化问题不要一上来就怀疑DTS。PCIe是高速接口硬件层面的问题占比非常高。REFCLK的质量、复位信号的时序、供电的纹波、PCB走线阻抗这些都可能导致卡死。设备树只是软件侧的描述文件它只能把硬件设计意图传达给驱动但传达不了信号质量。先用示波器把关键节点量一遍确认硬件没问题再动DTS省下的时间远比想象中多。第二个心得改DTS时一次只改一个变量。我在调试过程中吃过“一次改多个参数”的亏同时改了max-link-speed、reset-gpios和ranges结果系统跑通了但根本不知道是哪个改动起了作用。后续一旦换一块板子又卡死还是两眼一抹黑。正确做法是每次只改一个属性重新验证记录结果再改下一个。这个习惯能让调试过程变得有迹可循。第三个心得RK3588的PCIe问题排查不要孤立地只看PCIe相关的DTS片段。PCIe PHY可能和SATA、USB共享复位引脚可能和其他外设的GPIO复用。把整个设备树中与PHY、电源、复位相关的节点都扫一遍看有没有外设之间“打架”的情况往往能发现一些隐藏的冲突。这种全局视角在PCIe Switch级联和多功能扩展时尤其重要。PCIe初始化卡死这个问题的本质其实是嵌入式系统里“软硬件协同”的典型代表。表面上是DTS改几个参数的事背后却涉及时钟、复位、电源、中断、地址映射等多条链路。希望这篇文章能帮你快速定位问题少走弯路。下一次看到开发板Duang一声直接死机的时候心里能有个清晰的排查路线。
返回列表