PCI Hot plug的规范虽然早在二十世纪末就出现,但并没有规定统一的实现方式。当需要把PCIE卡换到其他槽位,或者替换有故障的卡时,PCIE卡热插拔的特性能够避免服务器宕机,改善用户使用体验。但时至今日,并非所有的服务器都支持PCIE卡热插拔。本人就有幸碰到这种服务器,最近就在为它增加hotplug的支持。

   我的任务是解决上电后PCIE不能LinkRe-train 成功的问题,调试过程中发现一个让人百思不得其解的问题:我的shell脚本运行得跟期望的一样,能够Re-train成功,LinkSpeed是期望的8GT/s;但是实现同样功能的C代码运行完成后,LinkTrain却一直失败。更奇怪的是,用GDB跟进每一步hotplug的操作之后,即便没有做任何修改,Linktrain都能成功,速度达到8GT/s。对比了C代码和脚本,发现C代码里多了很多的对PCIE和错误相关的寄存器的设置,很快屏蔽了这些,发现问题依旧;接着比较两者的不同,发现代码里实现热插拔的步骤和脚本里的有些不一样,把脚本改成和代码一样的顺序后重新测试,仍然没有解决。

   就这样反复尝试对比了好几天,终于发现了一个现象:凡是最后Re-train成功的情况,在上电后到Re-train之前这个阶段,RootportLinkSpeed能够达到8GT/s;凡是失败的情况,在那个时候LinkSpeed只有5GT/s。对比这两个情况下的LinkStatus寄存器,发现成功的时候, Status寄存器中的DataLink Layer Link Active位已经置了起来,而失败的时候那个位对应为0DataLink Layer Link Active顾名思义,这个bit用来表示数据链路层是否已经处于DL_Active状态。而DL_Active一旦置位,则表明系统进入了正常操作模式。显然如果链路都没有处于Active状态,后续的normaloperation都会出现问题,这也就解释了为什么后续的LinkRe-train会失败了。下面的DLL状态图说明了进入DL_Active是后面所有配置访问等其他访问的基础:

wKioL1Xd4l6RZM1-AACQJ4TjaW0199.jpg

         终于有了一些进展,很开心。不过虽然知道了后续Link Re-train失败是由于没有进入DL_Active状态,但是后面的问题也接着来了,怎么样才能保证成功进入DL_Active状态了?为什么我的脚本和GDB下的单步跟踪模式能够让卡进入DL_Active状态,但C代码却不行?

   带着这些疑惑,又折腾了两天,对比单步跟踪和直接运行程序的差异,猜测是因为单步跟踪使得我操作的每一步骤之间多了些延时。难道和时序有关系?带着这个猜测,我又仔细对比了我的脚本和C代码,果然发现我的脚本里在给卡上电之后,有一个sleep(1),而C代码里没这个东西。我马上注释了那一行代码,重新运行脚本,结果居然和C代码运行的结果一模一样:DL_Active没有置起来,上电后Re-trainLink Speed 只有5GT/s而非期望的8GT/s。往C代码里照着加入了那个延时,果然就好了!上电后的延时跟进入DL_Active有什么关系呢?这个是必需的码?毕竟我们的vendor并没有告诉我们要这样做。

PCIE协议规范文档是最好的老师,带着上面的两个问题去仔细读协议,很快在hot plug章节里找到了答案(PCIE3.0 SPEC v1.0:   6.7.3.3节和 6.7.1.8)


    支持hot plugdownstream port 要求响应Data Link Layer State 状态改变事件。当卡hot ×××ertion以及对应的port 上电后,PCIE卡和port会进行link train,当链路建立起来后,portlink状态寄存器中的DL_Active会被硬件置起来。从上电到Data Link Layer State 状态改变事件出现,协议要求硬件必须在1秒内完成;从Data Link layer DA_Active被设置好到软件能进行配置访问,软件至少需要等100毫秒。


    至此,实践上和理论上都已经清晰该如何操作了。当然,为了稳妥起见,可以做进一步的改进:

1.在上电之后,用查询DL_Active被置起来替代固定地等1秒钟;

2.在DL_Active被置起之后,在第一次配置访问之前,等上至少100毫秒。


   通过这次经历,让我学习到了PCIE数据链路层关于DL_Active时序的特点,如果早点了解这些,该少走多少弯路啊!希望读者如果也碰到类似的问题,务必吸取我的教训:)