☰
RK3568 PCIe3.0x2调试实战:从硬件设计到NVMe满速
2026/9/28 2:07:39 网站建设 项目流程

先说结论:这块板子的PCIe3.0x2最终跑到了8GT/s x2,顺序读稳定在1.5GB/s以上。但过程远没有这么顺利——插上NVMe盘启动,内核日志里连PCIe控制器都没有枚举,第一反应是设备树写错了,翻来覆去改了几天,最后发现根因在硬件电路的PERST#复位时序上。从那之后我养成了一个习惯:遇到PCIe问题,先按“电源→时钟→复位→引脚复用→设备树”的顺序排查,而不是一上来就怀疑软件。

RK3568的PCIe3.0x2是很多嵌入式Linux项目的刚需接口,常见的用法就是接M.2 NVMe SSD、5G模组或者PCIe转千兆网卡。这个控制器用的是Synopsys DesignWare IP,驱动在Linux内核里对应rockchip-pcie,设备树节点本质上是在告诉驱动“控制器在哪、有几条Lane、时钟复位怎么控制、引脚复用怎么切”。软件配置看起来不复杂,但它和硬件设计的耦合度比I2C、SPI这类接口高得多——链路训练是物理层的事,哪一环没做到位,结果就是link不起来。

这篇文章不是逐行分析TRM,而是把从拿到原理图到最终跑通性能的完整过程拆开讲:哪些硬件设计点必须先自查、设备树里x2模式到底要配置哪些内容、内核和U-Boot侧要打开什么选项、链路训练失败时怎么一步一步定位,最后给出实际压测数据和我踩过的坑。

1. 硬件底子决定调试天花板:RK3568 PCIe3.0x2电路设计自查清

先强调一个观点:设备树里写的num-lanes = <2>、max-link-speed = <Gen3>只是软件期望值。如果硬件设计本身有问题,软件再怎么配,链路协商结果也会默默降级,甚至完全训练失败。所以我拿到一块新板子,第一件事不是看内核日志,而是翻原理图。

1.1 电源轨:PCIe PHY对纹波和瞬态跌落敏感

RK3568 PCIe PHY需要一组0.85V左右的模拟电源(PCIE_AVDD_0V85),以及1.8V域的逻辑电源。这两路电源需要注意的点不一样:

  • 0.85V这一路主要给PHY的高速收发器供电,对纹波和瞬态响应很敏感。若纹波超过规范要求,轻则链路协商速率从Gen3降到Gen2,重则无故丢包、CRC错误频发。
  • 1.8V域给控制和接口逻辑供电,电流需求没有0.85V那么大,但同样要关注去耦电容布局。

我在实际项目里踩过这样的坑:0.85V这路的DC-DC芯片选型正常,但输出端的反馈采样电阻走得离电感太近,导致负载瞬变时电压跌落超过5%。PCIe在低温下测试没什么问题,一跑高负载或温度升高就出现链路重训练。后来在靠近PHY的位置补了高容值MLCC,问题明显好转。

自查时关注三点:电源芯片的峰值电流余量是否大于PCIe PHY的瞬态需求;PCB上从电源芯片到PHY的过孔和铜皮载流是否足够;PHY供电引脚的本地去耦电容是否按datasheet要求摆放(一般有0.1uF、1uF、10uF组合)。

1.2 100MHz参考时钟:差分阻抗和AC耦合电容不能想当然

PCIe的参考时钟是100MHz差分时钟,电平标准通常支持HCSL和LVDS。RK3568的REFCLK差分对,一定要遵守PCIe的阻抗要求——PCIe数据线和时钟线的差分阻抗都是85欧姆,不是90欧姆也不是100欧姆。这一点我特别想强调,因为很多画过USB(90欧姆)或以太网(100欧姆)的工程师,默认按100欧姆画,结果链路也能link上,但Gen3眼图裕量很差,速率提不上去。

另外,参考时钟源与PCIe控制器之间的AC耦合电容不能省。PCIe规范要求在源端放置AC耦合电容,典型值是0.1uF。走线时注意时钟差分对要保持等长,且与其他高速信号尽量拉开间距。若是时钟buffer方案,看buffer的时钟精度是否满足300MHz±300ppm(PCIe需要的是100MHz时钟,不同参考架构有一定差异,查TRM确认就行)。

自查手段很简单:示波器探头点REFCLK差分对,验证频率是100MHz,用眼图模式看交叉点电压和抖动。没有示波器时,至少确认晶振/时钟芯片的型号和精度。

1.3 PERST#复位时序:最容易背锅的一环

PCIe链路训练是在PERST#释放后开始的。规范要求:电源稳定后,等REFCLK稳定,PERST#至少再保持低电平100us,然后才能拉高释放。如果PERST#拉高太快,PHY可能还没完成初始化,链路训练就会失败。

RK3568方案里常见的做法有两种:一种是SoC的GPIO控制PERST#,由驱动在初始化时拉高;另一种是RC电路直接延时。我倾向于前一种,调试灵活,可以精确控制时序。但要注意,如果PERST#被某个GPIO控制,而这个GPIO在设备树里没有配成输出高电平,或者被默认初始化成低电平,那链路永远起不来。

排查时用逻辑分析仪采样PERST#和REFCLK,对照时序。很多问题都出在这:RESET#上掌门在软件里被别的驱动抢先设置为低,或者U-Boot阶段没释放。

1.4 Lane分配与TX/RX方向:接反的后果不只是没信号

PCIe3.0x2通常对应Lane0和Lane1。控制器TX必须连接设备RX,控制器RX连接设备TX,这是常识,但实际项目中翻过原理图,真的遇到过某根差分对在封装里被交换导致RX和TX接反的情况。后果是链路状态一直卡在Detect或Polling阶段,因为物理层没法完成对端检测。

另外注意,x2模式是否要求Lane0和Lane1同时使用?RK3568的PCIe3.0控制器如果做x2,两个lane都要接。如果只使用x1模式,另一个lane可以悬空。硬件设计时若想跑x2但只接了一个lane,链路协商宽度最高只能到x1。

信号完整性方面,PCIe Gen3(8GT/s)对走线损耗、过孔stub长度、连接器质量都比较敏感。我在自己的项目checklist里写的是:数据线尽量短,同一lane的TX差分对和RX差分对等长控制在一个合理范围;避免多余的过孔和直角走线。

1.5 引脚复用:SATA/USB3.0/PCIe经常共用物理引脚

RK3568是典型的SoC多路复用架构,PCIe信号和部分SATA、USB3.0信号可能共用引脚组。若硬件原理图把某个引脚接给了PCIe设备,但软件还在设备树里使能了SATA控制器,或者pinctrl里没有把引脚切换到PCIe功能,结果就是接口完全不工作。

这一条严格来说算软硬件配置问题,但引脚复用错误在现象上很像硬件问题。排查时先在硬件层面确认原理图,看这个接口是否和其他控制器共用引脚,然后在设备树里关闭冲突的控制器,并确保PCIe节点的pinctrl正确。

我习惯把硬件自查结果整理成一张表,方便交叉验证:

自查项期望值主要排查手段
0.85V PHY电源纹波合理,无瞬态跌落超限示波器、电源分析仪
100MHz REFCLK频率和抖动符合规范示波器/眼图仪
差分阻抗85欧姆差分,尽量等长PCB设计检查、TDR
PERST#时序电源和时钟稳定后延时释放逻辑分析仪
TX/RX连接控制器RX对设备TX,控制器TX对设备RX原理图审查、万用表
引脚复用与SATA/USB3.0的复用无冲突原理图 + 设备树pinctrl

硬件这些点确认一遍,大约需要半天到一天。板上没有明显物理缺陷的前提下,再往下走设备树就更有底气。

2. 设备树不是玄学:x2模式从节点属性到PHY初始化的完整映射

硬件确认没问题以后,才进入到设备树配置环节。RK3568的PCIe3.0x2在设备树里通常包含两个节点:一个PCIe控制器节点,一个PCIe PHY节点。两者任何一个状态不是“okay”,驱动都不会正常初始化。

2.1 PCIe控制器节点骨架:先看懂SDK默认配置

不同SDK版本写法和地址有差异,但整体结构类似。下面是一段基于Rockchip官方SDK风格的示意dts,实际以你手上的源码为准:

pcie3x2: pcie@fe280000 { compatible = "rockchip,rk3568-pcie"; reg = <0x0 0xfe280000 0x0 0x10000>, /* dbi */ <0x0 0xf0000000 0x0 0x100000>, /* apb? 或 config空间,具体看SDK */ <0x0 0xf1000000 0x0 0x100000>; reg-names = "dbi", "apb", "config"; device_type = "pci"; linux,pci-domain = <0>; bus-range = <0x0 0xf>; interrupts = <GIC_SPI 52 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 53 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 54 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>; interrupt-names = "sys", "pmc", "msi", "legacy"; clocks = <&cru ACLK_PCIE30X2_MST>, <&cru ACLK_PCIE30X2_SLV>, <&cru ACLK_PCIE30X2_DBI>, <&cru PCLK_PCIE30X2>; clock-names = "aclk_mst", "aclk_slv", "aclk_dbi", "pclk"; resets = <&cru SRST_PCIE30X2_POWER_UP>, <&cru SRST_PCIE30X2_PIPE>, ... reset-names = "power", "pipe", ...; num-lanes = <2>; max-link-speed = <3>; /* 3 表示 Gen3, 8GT/s */ pinctrl-names = "default"; pinctrl-0 = <&pcie30x2m0_pins>; /* 按原理图选择m0或m1 */ status = "okay"; }; pcie30phy: phy@fe8a0000 { compatible = "rockchip,rk3568-pcie-phy"; reg = <0x0 0xfe8a0000 0x0 0x10000>; clocks = <&cru PCLK_PCIE_PHY>; clock-names = "pclk"; resets = <&cru SRST_PCIE_PHY>; reset-names = "phy"; #phy-cells = <0>; status = "okay"; };

这段代码里面有几个属性很关键:num-lanes决定Lane数量,max-link-speed决定链路速率上限,interrupt-names里的“msi”和“legacy”关系到MSI中断能否正常工作,pinctrl-0负责把物理引脚从GPIO或其他复用功能切换到PCIe。

2.2 num-lanes和max-link-speed:写的只是“能力上限”

num-lanes = <2>的作用是告诉驱动,这个控制器最多支持2条Lane。最终协商出来的实际链路宽度是硬件和终端设备共同确定的,即使这里写的是2,如果对端设备只支持x1,协商结果就是x1。

max-link-speed的值对应PCIe速率:1是2.5GT/s,2是5GT/s,3是8GT/s。RK3568的PCIe3.0控制器硬件支持Gen3,所以通常配3。但是,这个属性在某些SDK里可能缺省,驱动会做兜底处理。我的建议是显式写出来,逻辑更清晰。

调试阶段有个技巧:如果链路训练总是失败,先把max-link-speed = <1>改成Gen1。Gen1速率低,对信号完整性要求宽松,若Gen1能稳定枚举,大概率是Gen3眼图裕量不足,而不是设备树本身写错。

2.3 时钟和复位属性:不只是让驱动时钟“转”起来

PCIe控制器本质上是一个高速外设,需要给它提供AHB/APB接口的工作时钟,以及用于高速SerDes的Pipe时钟。设备树里用clocks和resets把这些依赖关系映射到CRU(Clock and Reset Unit)的对应输出。

调试时如果驱动probe报错说clock或reset获取失败,用dmesg看具体是哪个时钟获取失败,再对照CRU的dtsi确认节点名称是否一致。这类问题在SDK升级后容易出现,因为CRU的宏定义可能变化,设备树没同步更新。

2.4 PHY节点:Link训练的前置条件

RK3568的PCIe PHY相对独立,单独挂在PHY节点上。有些方案里PHY需要额外配置PLL、校准参数,这些都封装在驱动里。设备树里能控制的不多,重点是确保它的status = "okay",且使用的时钟和复位资源没有被其他节点独占。

另外注意#phy-cells的值,RK3568 PCIe PHY一般是0,表示PHY全部由控制器独占。如果#phy-cells = <1>,控制器节点可能还需要通过phys和phy-names属性引用具体PHY通道。具体写法在SDK的dtsi里都能查到,我给的建议是先用SDK默认的组合,不要轻易改。

2.5 写完设备树后确认“真的生效了”

修改完设备树,重新编译boot.img或dtb,烧录启动后不要只看现象,要确认配置确实进入了内核。最简单的办法:

# 查看设备树里的节点是否可用 ls /proc/device-tree/pcie@fe280000/ # 查看status属性值 cat /proc/device-tree/pcie@fe280000/status

如果status不是“okay”,说明板级dts里有节点将其覆盖或者你没改对文件。另一个常见问题是编译时使用了缓存的旧dtb,烧录后设备树没更新,这种情况我想很多人都遇到过。

2.6 SATA/USB3.0复用冲突的设备树处理

如果硬件上PCIe和SATA共用引脚,设备树里需要主动关闭另一个控制器。例如在板级dts里加上:

&sata0 { status = "disabled"; };

顺序问题也要注意:如果PCIe和SATA在同一个pinctrl组里,两者同时使能会导致驱动申请资源失败。关闭冲突节点后,建议也检查一下内核日志里有没有对应的resource busy信息。

3. 内核与Bootloader协同:让PCIe控制器在启动初期就进入正确状态

设备树配好,不代表内核就能自动把PCIe跑起来。还得看内核编译选项、U-Boot阶段是否初始化过PCIe、MSI中断是否正常路由。这一节讲的是软件层面除了设备树之外,还需要检查的几件事。

3.1 内核Kconfig:这些选项缺一不可

RK3568 PCIe依赖的内核选项主要有几个:

CONFIG_PCI=y CONFIG_PCIE_ROCKCHIP=y CONFIG_PCI_MSI=y CONFIG_PCIEPORTBUS=y CONFIG_PCI_HOST_GENERIC=y (视SDK版本而定)

我把CONFIG_PCIE_ROCKCHIP编入内核,而不是编成模块。原因很实际:PCIe在启动早期就要枚举总线,如果驱动是模块,启动时rootfs还没挂载,或者模块加载顺序不对,PCIe设备可能已经错过枚举时机,你会看到bus已经扫描过但没有设备。虽然部分场景下可以手动echo 1 > /sys/bus/pci/rescan补救,但不如直接编译进内核省心。

用zcat /proc/config.gz | grep PCI可以快速确认这些选项是否生效。

3.2 MSI中断:NVMe设备经常依赖它

MSI(Message Signaled Interrupt)是PCIe设备向CPU发送中断的主要方式。NVMe协议强烈依赖MSI/MSI-X,如果设备树里MSI中断配置不正确,会出现“系统识别到了NVMe盘,但一读写就卡死”的诡异现象。

RK3568的设备树里,MSI中断通常在控制器的interrupts属性中,对应interrupt-names = "msi"。如果你用的是设计较为特殊的RK3568板卡,还需要确认GIC的msi-map或msi-parent是否指向正确的中断控制器。调试时用cat /proc/interrupts查看有没有对应的PCIe MSI中断计数在增加,如果没有,考虑是不是设备树里interrupt-map没配对。

3.3 U-Boot阶段:从NVMe启动时的双刃剑

Rockchip的U-Boot支持PCIe初始化,用于从NVMe SSD引导内核。如果项目不依赖NVMe启动,我的建议是U-Boot里可以不做复杂初始化,让内核干净地接管。但要注意,如果U-Boot已经初始化了PCIe设备,进入内核后驱动会尝试重新初始化,个别设备在“二次初始化”时状态不好,就会出现内核日志里找不到设备的情况。

遇到这种情况,先在U-Boot命令行里执行pci enum,看看U-Boot能否扫描到设备。U-Boot能扫到而内核扫不到,通常说明U-Boot初始化后把设备置入了一个特殊状态,内核的复位流程没有把它拉回来。可以通过设置内核启动参数pci=realloc或pci=nomsi来做交叉测试,确定是不是资源分配或MSI问题。

3.4 EP模式与RC模式:别拿EP模式跑宿主设备

RK3568的PCIe控制器同时支持RC(Root Complex)和EP(Endpoint)两种模式。默认SDK配置一般用作RC设备,也就是我们常说的“主板插槽”。如果你要做PCIe转接卡、或者把RK3568作为PCIe从设备,需要修改设备树中相关属性变成EP模式——但这个话题和本文主线不同,我只提一个点:调试前先确认设备树里控制器的模式和你的使用场景一致。假如你拿的默认dts配置碰巧是EP模式,插上NVMe盘后它只会等主机来枚举自己,链路当然起不来。

4. 链路训练失败的逐层排查:别急着怀疑设备树,先让LTSSM告诉你答案

这是本文的重头戏。链路训练失败时,内核日志往往只有一句话,但它背后可能藏着完全不同的问题。我一般分五步走:看日志定性、读LTSSM状态、查PHY、查时钟复位时序、用配置降级做二分。

4.1 第一步:从dmesg区分失败类别

常见日志有三类:

  • 控制器probe失败:一般表现为rockchip-pcie fe280000.pcie: failed to initialize或failed to get clock。这种和链路训练无关,是设备树资源/时钟/复位配置问题。
  • 链路训练超时:典型日志是rockchip-pcie fe280000.pcie: PCIe link doesn't start。这是最常见的一种,表示物理层一直没有进入L0状态。
  • 设备枚举失败:链路已经link上,但访问配置空间时出错,比如pci 0000:01:00.0: BAR 0: no space for resource。这类问题一般出在地址窗口分配、BDF扫描顺序或设备端配置空间访问异常。

用dmesg | grep -i pci看一遍,基本能确定从哪一层开始查。

4.2 第二步:读LTSSM状态,直接看卡在哪个阶段

LTSSM(Link Training and Status State Machine)是PCIe物理层的核心状态机。链路训练失败时,它的状态能直接告诉你在哪个环节断了。

在RK3568上,如果驱动编译时带了调试支持,可以通过/sys/kernel/debug/pcie/.../ltssm这类接口读取。也可以手动读取控制器寄存器,DesignWare IP的LTSSM状态一般能在一个固定偏移处读到,具体偏移要对照TRM或内核源码dw-pcie的寄存器定义确认。

LTSSM状态取值范围和含义参照PCIe规范,一般低数值表示还在物理层初始状态,高阶数值表示进入L0工作态。下面是我排查时常用的一张状态表:

LTSSM状态含义常见问题方向
Detect物理层正在检测对端TX/RX接反、引脚复用错误、链路无设备
Polling正在交换位级同步信号参考时钟问题、信号质量太差、PHY未锁定
Configuration正在协商链路宽度和lane编号Lane映射错误、对端设备不支持配置
L0正常工作状态无需排查
Recovery从错误或低功耗恢复中信号完整性问题、电源抖动

如果反复卡在Detect阶段,优先查物理层:示波器看REFCLK有没有、PERST是否释放、TX/RX是否接反。如果卡在Polling或Configuration,多考虑信号质量和设备兼容性。

4.3 第三步:确认PHY状态和PLL锁定

PHY没初始化好,LTSSM同样可能卡在最开始的地方。RK3568 PHY驱动probe成功后,会配置PLL并提供稳定时钟。排查时看dmesg里PHY相关日志,比如rockchip-pcie-phy是否报错,或者在驱动里增加对应寄存器打印,确认PHY ready标志位被置位。

如果PHY一直报错,回到设备树检查PHY节点的时钟和复位是否完整。有些板子的PHY节点被其他程序或早于PCIe的控制逻辑复用,导致驱动probe失败。

4.4 第四步:用示波器/逻辑分析仪验证REFCLK和PERST时序

这一步是硬件问题定位的“实锤”环节。实测要点:

  • REFCLK频率是否稳定在100MHz,差分摆幅是否符合设备端输入要求;
  • PERST#释放时刻是否在REFCLK稳定之后,且满足延时要求;
  • 用双通道示波器同时抓PERST#和REFCLK,数一下时间差。

我在一块返修板上就遇到过:PERST#网络的RC电容贴错容值,导致释放慢了好几毫秒,看起来没什么问题,但PCIe控制器等了超时都没等到设备准备好,最终报link失败。

4.5 第五步:用降级配置做二分定位

当所有物理检查都没发现明显问题时,我习惯用“降级法”缩小范围:

  • 把max-link-speed改为<1>(Gen1),判断是否是Gen3信号完整性问题;
  • 把num-lanes改为<1>,判断是否某一根Lane的硬件连接有问题;
  • 如果Gen1 x1能稳定工作,再逐步提升到Gen1 x2、Gen2 x2、Gen3 x2。

每次修改后都要断电重启。原因在于PCIe PHY的PLL状态在上电后锁定,如果只是软件reboot,部分PHY寄存器仍然保留之前的状态,可能掩盖问题。这个坑很隐蔽——我有一段时间总改设备树然后reboot,链路死活起不来,后来断电重新上电,一切正常。

4.6 常见错误日志与排查方向对照

内核日志/现象可能原因优先排查方向
PCIe link doesn't start链路训练超时PERST时序、REFCLK、TX/RX接线
failed to request PCIe config space设备树reg-names写错对照SDK dtsi
cannot enable device电源/时钟没开或依赖缺失clocks、resets、pinctrl
枚举后读写卡死MSI中断配置异常检查设备树msi相关属性
协商速率只有Gen1信号完整性差/PHY配置不对眼图测试、检查AC耦合电容
协商宽度只有x1某一根Lane没接好硬件连通性测试

5. 上板实测:确认链路速率、宽度与真实带宽的四件套

设备能被识别,只是第一步。我见过很多系统能识别NVMe盘,但链路协商在Gen1 x1,速度惨不忍睹。所以上板验证必须数据说话。

5.1 lspci -vvv:看LnkCap和LnkSta

系统起来后,先用lspci -vvv查看PCIe控制器和终端设备的链路能力与状态。

# 查看总线和设备列表 lspci -tv # 查看链路详情 lspci -vvv

重点关注输出里的这一段:

LnkCap: Port #0, Speed 8GT/s, Width x2 LnkSta: Speed 8GT/s, Width x2

LnkCap是硬件能力上限,LnkSta是实际协商结果。如果LnkSta显示Speed 2.5GT/s, Width x1,说明链路没有跑到预期状态,回到排查流程继续查。

5.2 setpci和配置空间:用底层方式验证状态

lspci太方便,但如果你想在设备树调优或驱动开发场景下快速探测,可以用setpci直接读写PCI配置空间。链路状态寄存器一般位于PCIe扩展配置空间,不同设备的偏移不同,典型情况下链路状态在配置空间0x72位置(Link Status Register),可以通过setpci -s 00:00.0 0x72.w读取。但不同平台和控制器可能不一样,我建议以lspci -xxx输出的原始数据为准来判断。

在RK3568上调试时,我更常用的是内核自带的一些接口:

# 查看PCIe控制器链路状态(如果驱动支持) cat /sys/kernel/debug/pcie/*/link # 查看设备错误计数 cat /sys/kernel/debug/pcie/*/errors

5.3 NVMe盘性能测试:测出真实带宽

链路协商正确,还要验证数据传输是否稳定。用NVMe盘最直接:

# 简单看下顺序读速度 hdparm -t /dev/nvme0n1 # 用dd做大块读 dd if=/dev/nvme0n1 of=/dev/null bs=1M count=4096 # 更严谨的测试用fio fio --name=seqread --rw=read --bs=1M --size=2G --numjobs=1 \ --filename=/dev/nvme0n1 --ioengine=libaio --direct=1 --iodepth=32

PCIe Gen3 x2单向有效带宽的理论上限约1.97GB/s(128b/130b编码,实际用户数据加上协议开销会略低一点)。我实测RK3568平台上,NVMe顺序读在1.4-1.8GB/s都算正常,顺序写在1.2-1.6GB/s。如果数值差太多,比如只有400MB/s,重点检查是否协商降级,或者盘本身性能就那样。

5.4 稳定性和错误计数:高温/长时间压测必须做

PCIe信号完整性问题最典型的特征就是“平时挺好,跑一段时间就不行”。我会做一轮长时间压测,比如fio持续读写30分钟到1小时,同时观察:

  • 有没有链路重训练(表现为系统瞬间卡顿后恢复);
  • /sys/kernel/debug/pcie/*/errors里CRC错误计数是否增长;
  • 芯片和SSD温度是否过高。

如果SSD温度超过85℃还长时间高负载运行,可能会出现热降速或链路不稳定。这也是硬件散热设计的一部分,调试时容易被忽略。

6. 我在这块板子上反复踩过的坑与最终落地的参数组合

这一节写一些真实案例。这些坑不一定每个项目都会踩到,但遇到时特别费时间,我自己花了三四个晚上才理清楚。

6.1 SATA和USB3.0的复用冲突让我以为硬件挂了

第一版板子,PCIe3.0x2接口一开始怎么都不工作,连检测信号都看不到。我怀疑过原理图、怀疑过PCB焊接、怀疑过设备树,最后发现是板级dts里SATA节点是enabled状态,占用了和PCIe相同的引脚组。内核启动时SATA驱动先抢占了PHY引脚,PCIe驱动再去申请pinctrl时只能失败。关闭SATA节点后,PCIe一次成功。

这个教训是:拿到全新板卡时,先全局搜索一遍dts里有没有和PCIe引脚冲突的节点,再开调。

6.2 Gen1 x2先排除设备端兼容性

另一个板卡现象是:插上NVMe盘能识别,但只能协商到Gen2,怎么调设备树都上不了Gen3。后来把同一个M.2插槽接到一台标准PC上测试,发现这张SSD在这个PC上也只能跑到Gen2。原来是SSD固件的兼容性问题,而不是RK3568的问题。这个案例说明,遇到速率上不去时,用一个已知在PC上能跑Gen3的设备做替换测试,能帮你快速甩开“设备端本身有问题”这个变量。

所以我建议调试时手边常备两块盘:一块是经过多次验证的老牌Gen3 SSD(兼容性好),另一块是你要量产适配的SSD。分别测一遍,交叉对比很能说明问题。

6.3 低速模式是排查信号完整性最有效的手段

有一次Gen3死活上不去,用示波器看眼图,发现PCIe时钟芯片输出的REFCLK抖动超标。当时没有更好更快的时钟源,于是把max-link-speed降为<2>(Gen2)作为临时方案,并在硬件下一版迭代中更换了时钟buffer。这个妥协在生产上有时候是必要的,但一定要记录在案,产品宣传里也得写清楚最高支持Gen2。

6.4 最终落地的dts关键片段

下面是一段以官方SDK为基础的示例,包含我验证过的关键配置思路,不是完整dts,只展示核心属性:

&pcie3x2 { num-lanes = <2>; max-link-speed = <3>; pinctrl-names = "default"; pinctrl-0 = <&pcie30x2m0_pins>; reset-gpios = <&gpio3 RK_PB5 GPIO_ACTIVE_LOW>; /* PERST#, 按实际原理图调整 */ status = "okay"; }; &pcie30phy { status = "okay"; }; /* 如果和SATA复用引脚,必须在板级dts里关闭 */ &sata0 { status = "disabled"; }; &sata1 { status = "disabled"; };

reset-gpios的写法不是所有SDK默认都有,有些版本里叫phys或者由驱动内建逻辑控制,具体看驱动代码。加了之后可以更灵活地控制PERST#时序,尤其适合做链路恢复测试。

6.5 编译内核时的坑:CONFIG_PCIE_ROCKCHIP编成模块踩过的雷

有一版测试固件,我把CONFIG_PCIE_ROCKCHIP=m,结果启动后系统已经扫描完PCIe总线,但驱动模块还没加载,导致NVMe盘没出现。手动modprobe之后再rescan也能用,但这种流程不适合量产固件。建议直接CONFIG_PCIE_ROCKCHIP=y。

另外,如果项目同时使用了PCIe网卡,还要打开对应的网卡驱动。RK3568上接Intel I210或RTL8111这类网卡比较常见,确认对应的CONFIG_E1000E、CONFIG_R8169也是编译进去的。

6.6 一个新板子回来后我的全流程检查单

最后把流程串一遍,这也是我现在带新人时的标准流程:

  1. 拿到原理图,先翻电源、REFCLK、PERST#、Lane接线和引脚复用,确认硬件设计没有明显问题;
  2. 对照原理图检查板级dts里的pinctrl和冲突节点,确保SATA/USB3.0/PCIe复用正确;
  3. 编译烧录后,dmesg | grep -i pci看控制器是否正常probe;
  4. 读LTSSM状态或看日志判断卡在物理层还是枚举层;
  5. 用降级配置(Gen1 x1逐步升到Gen3 x2)做二分定位;
  6. 全速稳定后,跑一轮fio压测和错误计数检查,记录数据归档。

这套流程帮我解决过不少看似无解的PCIe问题。说句实在话,PCIe调试难就难在“硬件现象”和“软件配置”交织在一起,但只要你把每一层拆开验证,老板催得再急也稳得住。我手头这块板子最终就是按照这套方法调通的,现在看到插上NVMe盘直接秒识别、满速跑测试,还挺有成就感的。希望这篇文章能帮你少走点弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询