3. USB4链路训练状态监控

USB4链路训练状态监控 学习卡片

任务目标

在 Linux 上通过 sysfs 与 debugfs 读取 USB4/Thunderbolt 链路训练状态,解析出当前链路速率(Gen)与链路宽度(lane 数),并给出可复现的读取与解析脚本。

环境与依赖

  • 内核已启用 CONFIG_USB4 与 CONFIG_USB4_DEBUGFS(debugfs 节点依赖后者)。
  • 系统已挂载 debugfs:mount -t debugfs none /sys/kernel/debug。
  • Python 3(仅用标准库)。

数据来源

USB4 设备在 sysfs 下暴露为 /sys/bus/thunderbolt/devices/<domain>/<port>,其中 link_speed 与 link_width 为链路训练完成后的协商结果;debugfs 的 /sys/kernel/debug/thunderbolt/<domain>/<port>/regs 提供寄存器原始值,用于交叉验证。

sysfs 设备节点 debugfs regs 读取 link_speed link_width 解析 Gen / Lane 输出结果

实现代码

#!/usr/bin/env python3
# usb4_link_status.py —— 读取并解析 USB4 链路训练状态
import os
import re
import sys

SYSFS_ROOT = "/sys/bus/thunderbolt/devices"
DEBUGFS_ROOT = "/sys/kernel/debug/thunderbolt"

SPEED_MAP = {
    "1.25": "Gen1 (1.25 GT/s)",
    "2.5":  "Gen2 (2.5 GT/s)",
    "5.0":  "Gen3 (5.0 GT/s)",
    "10.0": "Gen4 (10.0 GT/s)",
    "20.0": "Gen5 (20.0 GT/s)",
}

def read_file(path):
    try:
        with open(path, "r") as f:
            return f.read().strip()
    except (OSError, IOError):
        return None

def list_ports():
    """枚举 domain/port 组合。"""
    ports = []
    if not os.path.isdir(SYSFS_ROOT):
        return ports
    for domain in sorted(os.listdir(SYSFS_ROOT)):
        dpath = os.path.join(SYSFS_ROOT, domain)
        if not os.path.isdir(dpath):
            continue
        for port in sorted(os.listdir(dpath)):
            ppath = os.path.join(dpath, port)
            if os.path.isdir(ppath) and os.path.exists(os.path.join(ppath, "link_speed")):
                ports.append((domain, port, ppath))
    return ports

def parse_speed(raw):
    """sysfs link_speed 形如 '20.0' 或 '20.0 Gb/s'。"""
    if raw is None:
        return None, "未知"
    m = re.search(r"(\d+(?:\.\d+)?)", raw)
    if not m:
        return None, "未知"
    val = m.group(1)
    return val, SPEED_MAP.get(val, f"{val} GT/s")

def parse_width(raw):
    """sysfs link_width 形如 '2' 或 '2 lanes'。"""
    if raw is None:
        return None
    m = re.search(r"(\d+)", raw)
    return int(m.group(1)) if m else None

def read_debugfs_regs(domain, port):
    """读取 debugfs 寄存器原始值,用于交叉验证。"""
    path = os.path.join(DEBUGFS_ROOT, domain, port, "regs")
    return read_file(path)

def main():
    ports = list_ports()
    if not ports:
        print("未发现 USB4/Thunderbolt 端口,请确认内核与硬件支持。")
        return 1

    for domain, port, ppath in ports:
        raw_speed = read_file(os.path.join(ppath, "link_speed"))
        raw_width = read_file(os.path.join(ppath, "link_width"))
        speed_val, speed_desc = parse_speed(raw_speed)
        width = parse_width(raw_width)

        print(f"[{domain}/{port}]")
        print(f"  link_speed raw : {raw_speed}")
        print(f"  link_width raw : {raw_width}")
        print(f"  解析速率       : {speed_desc}")
        print(f"  解析宽度       : {width} lane(s)" if width else "  解析宽度       : 未知")

        regs = read_debugfs_regs(domain, port)
        if regs:
            # 仅展示前几行,避免刷屏
            head = "\n".join(regs.splitlines()[:4])
            print(f"  debugfs regs   :\n{head}")
        else:
            print("  debugfs regs   : 不可用(检查 CONFIG_USB4_DEBUGFS 与挂载)")
        print()
    return 0

if __name__ == "__main__":
    sys.exit(main())

运行与验证

chmod +x usb4_link_status.py
sudo ./usb4_link_status.py

判定通过条件:脚本对每个已训练完成的端口打印出 link_speed 与 link_width 的原始值,并给出对应的 Gen 速率与 lane 数;若 debugfs 可用,同时打印寄存器前几行。若某端口未连接设备,link_speed 可能为空或为 0,此时解析结果为“未知”,属正常现象。

常见问题

  • 无输出:确认 /sys/bus/thunderbolt/devices 存在,且内核已加载 thunderbolt 模块。
  • debugfs 不可用:检查 CONFIG_USB4_DEBUGFS 是否编译进内核,并确认已挂载 debugfs。
  • 速率与预期不符:链路训练可能降速降宽,属正常协商结果,可结合 debugfs 寄存器进一步排查。