4. L0s深入:进入条件、退出延迟、LTSSM中的对应状态

说实话,刚开始接触ASPM时,我一直以为L0s就是个“省电模式”而已——让链路歇着就完了。但实际调过几个项目后才发现,L0s的脾气远比你想象的要大。你配置对了,它能帮你省下可观的电量;配置错了,链路上能给你整出一堆莫名其妙的超时和错误。

我个人习惯把L0s看作一种“浅休眠”状态。不像L1那样能睡到天荒地老,L0s更像是在等红绿灯时的那几秒闭眼。它能快速恢复,代价就是省电幅度有限。这一节,咱们就把它里里外外捋一遍。

4.1 L0s到底是个什么状态?

L0s是LTSSM(链路训练状态机)中的一个子状态,属于Active State Power Management的一环。它的特点是:

  • 逐方向独立进入:每个方向(发送/接收)可以单独进入L0s,另一方向可能还在L0。
  • 不保留TLP:一旦进入L0s,发送缓冲区的内容会被清空,不保存事务包。
  • 退出极快:理论退出延迟为数ns到数百ns,远快于L1(几μs)。

你想想看,为什么PCIe要设计这么个“半吊子”低功耗状态?原因很简单:很多时候链路只是偶尔空闲几μs,如果能在这几μs里省电还保证快速响应,那整体功耗就能掉下来。L0s就是干这个的。

状态 功耗节省程度 退出延迟 双向独立 保留TLP
L0 无 — — 是
L0s 中 ns级 是 否
L1 高 μs级 否(双向同步) 否

4.2 L0s进入条件

进入L0s的条件并不复杂,但在项目里调错的地方恰恰是这些“看起来简单”的东西。

  1. 链路必须处于L0状态——这不用多说,得先醒着才能睡。
  2. 发送端TX的Physical Layer空闲超过一定时间(由软件配置):
    • 具体时间由L0s Entry Timer决定,单位为1个UI(Ultra Interval)。
    • 典型的默认值是7个UI,但你可以通过寄存器调节。
    • 我个人建议:对于延迟敏感的链路(比如NVMe盘),把这个timer设大一点,避免频繁进出L0s。
  3. 接收端RX没有数据正在接收——如果对面正在发东西,你还想睡?门都没有。
  4. 链路两侧都必须使能L0s:
    • 通过Device Control Register (DCR)中的Enable L0s位控制。
    • 如果对端不使能,那本端强行进入L0s也没用,链路会直接报错。
  5. 没有Pending的数据包或重放缓冲区中的包——缓冲区里有货,你就别想进L0s。
关键参数: L0s Entry Timer(寄存器偏移0x50,bit[7:0])。单位是1个UI,不同Gen下UI不同:
  • Gen1: 1UI ≈ 1ns
  • Gen2: 1UI ≈ 0.5ns
  • Gen3: 1UI ≈ 0.25ns
  • Gen4: 1UI ≈ 0.125ns
一个小技巧: 我习惯在BIOS的ASPM配置表中把L0s Entry Timer设到16~32 UI。这样既不会让链路频繁进出,又能在较长时间空闲时省电。特别是一些芯片组在低负载时,默认的7 UI太容易触发L0s了。

4.3 L0s退出过程与延迟

L0s的退出可比进入要敏感得多。为什么?因为退出延迟直接决定了系统响应时间。你要是退出太慢,应用层那边可能已经等到超时了。

退出触发条件:TX一旦有数据要发送,必须立即退出L0s。这个“立即”是硬件层面的动作——不需要软件干预,完全由Physical Layer自己裁决。

退出延迟的组成:

  1. Electrical Idle Exit Time(电气空闲退出时间):
    • 从L0s退出命令发出到链路重新电气激活的时间。
    • PCIe Base Spec规定:L0s退出延迟 ≤ 4个UI + 几十ns。
    • 实际上,Gen3/Gen4的电气空闲检测更复杂,有时会多出额外的训练序列。
  2. Bit Lock + Block Alignment Time(位锁定与块对齐时间):
    • 退出L0s后,接收端需要重新建立位同步和块对齐。
    • Gen1/Gen2: 约需4个符号(Symbol)时间。
    • Gen3/Gen4: 需要额外的8b/10b或128b/130b块同步序列。
  3. Fast Training Sequence (FTS) 开销:
    • L0s退出时需要发送FTS(最多4个FTS,由链路宽度决定)。
    • 每增加1条Lane,FTS需要额外重复一次。
Gen 典型退出延迟 主要影响因素
Gen1 100~200ns 电气空闲检测 + FTS
Gen2 70~150ns 电气空闲检测 + FTS
Gen3 200~400ns 电气空闲检测 + 块对齐 + EQ
Gen4 300~600ns 电气空闲检测 + 块对齐 + EQ
我曾经踩过的坑: 在一个Gen4的NVMe SSD项目里,我把L0s Entry Timer设得太小(7UI),结果链路频繁进出L0s。每次退出都要走一遍FTS和块对齐,导致写性能从2800MB/s掉到2200MB/s。后来我把Entry Timer改成32UI,吞吐率就回来了。记住:L0s不是无代价的省电,退出延迟是真实存在的!

4.4 LTSSM中的对应状态

LTSSM是PCIe协议的“交通警察”,它控制着链路所有状态的转换。L0s不是孤立的,它跟Recovery、L0、L1之间都有明确的跳转路径。

我画了一张图,把相关的状态流转关系标出来了,你一看就懂:

L0s 退出延迟与 LTSSM 状态转换 LTSSM中L0s相关状态转换图 L0 (活动状态) L0s Entry (发起进入) L0s (低功耗等待) L0s Exit (退出进行中) Recovery (恢复) Idle超7~32UI Entry完成 TX有数据待发送 Exit完成 + FTS通过 Exit失败 / 电气空闲错误 链路重新训练完成 注意:L0s进入和退出都是逐方向独立的 FTS: Fast Training Sequence, 最多4个

结合这张图,咱们再捋一下LTSSM的行为:

  • L0 → L0s Entry:当TX空闲时间大于设定的Entry Timer,硬件自动发起进入L0s的请求。
  • L0s Entry → L0s:成功进入低功耗等待状态。此时发送端停止驱动,链路呈现Electrical Idle。
  • L0s → L0s Exit:一旦TX有新数据要发出,立即退出。这个过程不可被软件打断,完全由硬件完成。
  • L0s Exit → L0:退出后如果链路状态正常,直接回到L0继续收发数据。
  • L0s Exit → Recovery:如果退出过程中电气空闲检测失败、或者对端未准备好,LTSSM会跳转到Recovery进行链路恢复。
LTSSM中L0s相关状态一览表:
状态名称 性质 典型停留时间 主要动作
L0 活动状态 不定 正常发送接收TLP、DLLP
L0s Entry 过渡状态 几个UI 发送Electrical Idle Ordered Set (EIOS)
L0s 低功耗状态 几μs到几ms 电气空闲,不驱动差分对
L0s Exit 过渡状态 几十ns到几百ns 发送FTS、进行位同步和块对齐
Recovery 错误恢复状态 几μs 重新训练链路、重新均衡

4.5 一些实战中需要注意的点

关于L0s Entry Timer的选择

我在多个项目中试过不同的值,最终得到一条经验:

  • 对于延迟敏感的应用(如NVMe、GPU),建议设到16~32 UI,减少无谓的进出。
  • 对于功耗敏感但带宽要求不高的应用(如某些IoT Card),可以用7~8 UI。
  • 千万不要全部用芯片默认值。默认值通常是厂商提供的保守值,不一定适合你的设计。

关于逐方向独立带来的问题

嗯,这里要特别注意:L0s是逐方向独立的。也就是说,读方向可能进入了L0s,写方向还在L0。这会导致一种情况——你收到一个读请求,但对方发方向的TX还在L0s,你需要等它醒来才能响应。

我记得有一个NVMe RAID卡的调试场景:两个NVMe盘共用一条PCIe链路,一个盘在做大量读操作,另一个盘空闲进入了L0s。结果空闲盘的读方向上突然收到请求,需要先退出L0s再响应,引入了额外的访问延迟。

解决思路: 如果你的链路是双向对称高负载,可以考虑在驱动层限制L0s的使能;或者把Entry Timer设大,让链路在短时间内不进入L0s。对于非对称负载,可以只在一个方向上使能L0s,另一个方向保持L0。

L0s与L1的联动

有些芯片组会把L0s作为进入L1的“前置条件”,也就是必须先进入L0s才能进入L1。这样做的好处是确保链路在进入更深休眠前已经处于稳定状态。但这也会让L1的进入时间变长。

我曾经在一个Gen3的存储服务器主板上遇到过L0s和L1冲突的问题:L0s Entry Timer太短,导致链路频繁进出L0s,结果G1根本进不去,功耗下不来。后来我把Entry Timer调整到24UI,同时把L1的进入条件改为“L0s持续100μs以上”,问题就解决了。

4.6 小结

L0s是整个ASPM体系中最容易被低估的一环。它看似简单——让链路歇几微秒再起来——但实际上,Entry Timer的配置、退出延迟的掌控、以及LTSSM中状态机的流转,都会直接影响系统的实际表现。

我个人建议你在设计初期就把L0s的行为纳入链路仿真环境中测试,而不是等到硬件投片后再去调。否则,像上面提到的NVMe性能下降、L1进入失败这些坑,你大概率也会踩一遍。