USB4 和 Thunderbolt

USB4 是基于 Thunderbolt 3 协议的公开规范,在寄存器级别等方面存在一些差异。连接管理器是运行在主路由器(主机控制器)上的实体,负责枚举路由器和建立通道。连接管理器可以实现在固件或软件中。通常,个人电脑随附用于 Thunderbolt 3 和早期支持 USB4 系统的固件连接管理器。另一方面,苹果系统使用软件连接管理器,后来的 USB4 兼容设备也效仿了这一点。

Linux Thunderbolt 驱动程序两者皆支持,并能在运行时检测要使用哪种连接管理器实现。为了安全起见,Linux 中的软件连接管理器还会通告 user 安全级别,这意味着默认情况下禁用 PCIe 通道。以下文档适用于这两种实现,但软件连接管理器仅支持 user 安全级别,并且预期需要配合基于 IOMMU 的 DMA 保护使用。

安全级别以及如何使用它们

此处呈现的接口并非面向最终用户。相反,应该有一个用户空间工具来处理所有底层细节,维护已授权设备的数据库,并提示用户进行新连接。

有关 Thunderbolt 设备 sysfs 接口的更多详细信息,请参阅 ABI file testing/sysfs-bus-thunderbolt

那些只想连接任何设备而不进行任何手动操作的用户可以将以下行添加到 /etc/udev/rules.d/99-local.rules

ACTION=="add", SUBSYSTEM=="thunderbolt", ATTR{authorized}=="0", ATTR{authorized}="1"

这将在所有设备出现时自动对其进行授权。但是请记住,这会绕过安全级别,并使系统容易受到 DMA 攻击。

从 Intel Falcon Ridge Thunderbolt 控制器开始,有 4 个可用的安全级别。Intel Titan Ridge 增加了一个安全级别(usbonly)。出现这些级别的原因是连接的设备可以作为 DMA 主设备,从而在 CPU 和操作系统不知情的情况下读取主机内存的内容。有通过设置 IOMMU 来防止这种情况的方法,但由于各种原因,它并不总是可用的。

某些 USB4 系统具有禁用 PCIe 通道的 BIOS 设置。这被视为另一个安全级别(nopcie)。

安全级别如下

所有设备都由固件自动连接。不需要用户批准。在 BIOS 设置中,这通常被称为 传统模式 (Legacy mode)

user

系统会询问用户是否允许连接该设备。然后,用户可以根据通过 /sys/bus/thunderbolt/devices 获取的设备识别信息做出决定。在 BIOS 设置中,这通常被称为 唯一 ID (Unique ID)

secure

系统会询问用户是否允许连接该设备。除了 UUID 之外,还会向设备(如果它支持安全连接)发送一个质询,该质询应该与基于写入 key sysfs 属性的随机密钥所预期的质询相匹配。在 BIOS 设置中,这通常被称为 单次保存密钥 (One time saved key)

dponly

固件会自动为 DisplayPort 和 USB 创建通道。不进行 PCIe 通道建立。在 BIOS 设置中,这通常被称为 仅 Display Port (Display Port Only)

usbonly

固件会自动为扩展坞中的 USB 控制器和 DisplayPort 创建通道。扩展坞下游的所有 PCIe 链路都会被移除。

nopcie

从 BIOS 禁用/禁止 PCIe 通道。在某些 USB4 系统中可用。

可以从 /sys/bus/thunderbolt/devices/domainX/security 读取当前安全级别,其中 domainX 是主机控制器管理的 Thunderbolt 域。通常每个 Thunderbolt 主机控制器有一个域。

如果安全级别读取为 usersecure,则连接的设备必须在创建 PCIe 通道(例如 PCIe 设备出现)之前由用户授权。

每个插入的 Thunderbolt 设备都会出现在 sysfs 的 /sys/bus/thunderbolt/devices 下。设备目录包含可用于识别特定设备的信息,包括其名称和 UUID。

当安全级别为 usersecure 时授权设备

当设备插入时,它将按如下方式出现在 sysfs 中

/sys/bus/thunderbolt/devices/0-1/authorized   - 0
/sys/bus/thunderbolt/devices/0-1/device       - 0x8004
/sys/bus/thunderbolt/devices/0-1/device_name  - Thunderbolt to FireWire Adapter
/sys/bus/thunderbolt/devices/0-1/vendor       - 0x1
/sys/bus/thunderbolt/devices/0-1/vendor_name  - Apple, Inc.
/sys/bus/thunderbolt/devices/0-1/unique_id    - e0376f00-0300-0100-ffff-ffffffffffff

authorized 属性读取为 0,这意味着尚未创建 PCIe 通道。用户只需输入以下内容即可授权设备

# echo 1 > /sys/bus/thunderbolt/devices/0-1/authorized

这将创建 PCIe 通道,设备现在已连接。

如果设备支持安全连接,并且域安全级别设置为 secure,它将具有一个额外的属性 key,该属性可以包含一个用于授权并在未来连接中质询设备的随机 32 字节值

/sys/bus/thunderbolt/devices/0-3/authorized   - 0
/sys/bus/thunderbolt/devices/0-3/device       - 0x305
/sys/bus/thunderbolt/devices/0-3/device_name  - AKiTiO Thunder3 PCIe Box
/sys/bus/thunderbolt/devices/0-3/key          -
/sys/bus/thunderbolt/devices/0-3/vendor       - 0x41
/sys/bus/thunderbolt/devices/0-3/vendor_name  - inXtron
/sys/bus/thunderbolt/devices/0-3/unique_id    - dc010000-0000-8508-a22d-32ca6421cb16

注意,默认情况下密钥为空。

如果用户不想使用安全连接,他们只需向 authorized 属性 echo 1,PCIe 通道就会以与 user 安全级别相同的方式创建。

如果用户想要使用安全连接,则在第一次插入设备时,需要创建一个密钥并发送给设备

# key=$(openssl rand -hex 32)
# echo $key > /sys/bus/thunderbolt/devices/0-3/key
# echo 1 > /sys/bus/thunderbolt/devices/0-3/authorized

现在设备已连接(创建了 PCIe 通道),此外,密钥也存储在设备的 NVM 上。

下次插入设备时,用户可以使用相同的密钥验证(质询)设备

# echo $key > /sys/bus/thunderbolt/devices/0-3/key
# echo 2 > /sys/bus/thunderbolt/devices/0-3/authorized

如果设备返回的质询与我们基于密钥所期望的相匹配,则设备连接并创建 PCIe 通道。但是,如果质询失败,则不会创建通道,并向用户返回错误。

如果用户仍然想要连接设备,他们可以选择在没有密钥的情况下批准设备,或者写入新密钥并向 authorized 文件写入 1,以便将新密钥存储在设备的 NVM 上。

取消设备授权

可以通过向设备的 authorized 属性写入 0 来取消设备授权。这需要连接管理器实现的的支持,并且可以通过读取域的 deauthorization 属性来检查。如果读取为 1,则支持此功能。

当取消设备授权时,从父设备的 PCIe 下游(或根)端口到设备的 PCIe 上游端口的 PCIe 通道将被拆除。这本质上与 PCIe 热移除相同,并且在再次授权设备之前,相关的 PCIe 拓扑将无法再访问。如果涉及 NVMe 或类似的存储,如果该存储上的文件系统未正确关闭,则存在数据丢失的风险。请注意!

利用 IOMMU 的 DMA 保护

2018 年及以后带有 Thunderbolt 端口的近代系统可能原生支持 IOMMU。这意味着 Thunderbolt 安全性由 IOMMU 处理,因此连接的设备无法访问驱动程序为它们分配的区域之外的内存区域。当 Linux 在此类系统上运行时,如果用户尚未启用 IOMMU,它会自动启用 IOMMU。可以通过从 /sys/bus/thunderbolt/devices/domainX/iommu_dma_protection 属性读取 1 来识别这些系统。

在这种情况下,驱动程序不会做任何特殊的事情,但由于 DMA 保护由 IOMMU 处理,安全级别(如果已设置)是多余的。因此,一些系统出厂时将安全级别设置为 none。其他系统将安全级别设置为 user,以便支持降级到旧版操作系统,因此希望在启用 IOMMU DMA 保护时自动授权设备的用户可以使用以下 udev 规则

ACTION=="add", SUBSYSTEM=="thunderbolt", ATTRS{iommu_dma_protection}=="1", ATTR{authorized}=="0", ATTR{authorized}="1"

升级 Thunderbolt 设备、主机或重定时器 (retimer) 上的 NVM

由于大多数功能都是由运行在主机控制器或设备上的固件处理的,因此将固件升级到已修复其中可能漏洞的最新版本非常重要。通常,OEM 会在其支持网站上提供此固件。

目前,推荐的更新固件方法是通过 “fwupd” 工具。它默认使用 LVFS(Linux 厂商固件服务)门户从硬件厂商获取最新固件,并在发现兼容时更新连接的设备。有关详情,请参考:https://github.com/fwupd/fwupd

在升级设备、主机或重定时器上的固件之前,请确保这是一次合适的升级。否则可能会使设备处于无法再通过常规方式正常使用的状态,甚至需要特殊工具才能恢复!

不支持在 Apple Mac 上升级主机 NVM。

Fwupd 默认已安装。如果您的系统上没有它,只需使用您的发行版包管理器来获取它。

要通过 fwupd 查看可能的更新,您需要插入一个 Thunderbolt 设备,以便主机控制器出现。连接哪个设备并不重要(除非您正在升级设备上的 NVM —— 此时您需要连接该特定设备)。

请注意,您的系统可能提供了一种 OEM 特定的方法来为控制器加电(“force power”),在这种情况下无需插入 Thunderbolt 设备。

使用 fwupd 更新固件非常简单 - 请参考 fwupd GitHub 上的官方说明文档。

如果固件镜像写入成功,设备会短暂消失。一旦它重新出现,驱动程序会注意到它并启动完整的电源循环。过了一会儿,设备再次出现,此时它应该完全正常工作。

所关注的设备应在 fwupd 界面中的“Current version”和“Update State: Success”下显示新版本。

手动升级固件

如果可能,请使用 fwupd 更新固件。但是,如果您的设备 OEM 尚未将固件上传到 LVFS,但可以从他们的网站下载,您可以使用以下方法直接升级固件。

手动固件更新可以使用 ‘dd’ 工具完成。要使用此方法更新固件,您需要将其写入主机或设备的 NVM 的非活动部分。以下是如何更新 Intel NUC6i7KYK(骷髅峡谷)Thunderbolt 控制器 NVM 的示例

# dd if=KYK_TBT_FW_0018.bin of=/sys/bus/thunderbolt/devices/0-0/nvm_non_active0/nvmem

操作完成后,我们可以按如下方式触发 NVM 认证和升级过程

# echo 1 > /sys/bus/thunderbolt/devices/0-0/nvm_authenticate

如果没有返回错误,设备的表现应如上一节所述。

我们可以通过运行以下命令来验证新的 NVM 固件是否处于活动状态

# cat /sys/bus/thunderbolt/devices/0-0/nvm_authenticate
0x0
# cat /sys/bus/thunderbolt/devices/0-0/nvm_version
18.0

如果 nvm_authenticate 包含除 0x0 之外的内容,则是来自上一个认证周期的错误代码,这意味着 NVM 镜像的认证失败。

请注意,NVMem 设备 nvm_activeNnvm_non_activeN 的名称取决于它们在 NVMem 子系统中注册的顺序。名称中的 N 是由 NVMem 子系统添加的标识符。

在未连接电缆时升级板载重定时器 NVM

如果平台支持,即使 USB4 端口上没有连接任何设备,也可能升级重定时器 NVM 固件。在这种情况下,usb4_portX 设备具有两个特殊属性:offlinerescan。升级固件的方法是首先将 USB4 端口置于离线模式

# echo 1 > /sys/bus/thunderbolt/devices/0-0/usb4_port1/offline

此步骤确保端口不响应任何热插拔事件,并确保重定时器已上电。下一步是扫描重定时器

# echo 1 > /sys/bus/thunderbolt/devices/0-0/usb4_port1/rescan

这会枚举并添加板载重定时器。现在,可以像连接电缆时一样升级重定时器 NVM(参见上一节)。但是,由于我们处于离线模式,重定时器不会断开连接,因此在向 nvm_authenticate 写入 1 后,应该等待 5 秒或更长时间,然后再重新运行 rescan

# echo 1 > /sys/bus/thunderbolt/devices/0-0/usb4_port1/rescan

此时,如果一切顺利,可以将端口恢复到正常工作状态

# echo 0 > /sys/bus/thunderbolt/devices/0-0/usb4_port1/offline

当主机控制器处于安全模式时升级 NVM

如果现有的 NVM 未通过正确认证(或丢失),主机控制器将进入安全模式,这意味着唯一可用的功能是烧录新的 NVM 镜像。在此模式下,读取 nvm_version 会失败并返回 ENODATA,并且设备识别信息会丢失。

要从该模式恢复,需要以与前一章节相同的方式将有效的 NVM 镜像烧录到主机控制器。

通道事件

thunderbolt_domain 中发生通道更改时,驱动程序会向用户空间发送 KOBJ_CHANGE 事件。通知带有以下环境变量

TUNNEL_EVENT=<EVENT>
TUNNEL_DETAILS=0:12 <-> 1:20 (USB3)

<EVENT> 的可能值有

activated

通道已激活(创建)。

changed

此通道发生了更改。例如,带宽分配发生了更改。

deactivated

通道已被拆除。

low bandwidth

通道未获得最佳带宽。

insufficient bandwidth

没有足够的带宽来满足当前的通道需求。

仅当通道已知时才提供 TUNNEL_DETAILS。例如,在使用固件连接管理器的情况下,此项缺失或不提供完整的通道信息。在使用软件连接管理器的情况下,这包括完整的通道详情。当前格式与驱动程序在记录日志时使用的格式相匹配。这可能会随着时间而改变。

通过 Thunderbolt 电缆进行网络连接

Thunderbolt 技术允许通过 Thunderbolt 电缆连接的两个主机之间进行软件通信。

可以在 Thunderbolt 链路上通道化传输任何类型的流量,但目前我们仅支持 Apple ThunderboltIP 协议。

如果另一台主机运行 Windows 或 macOS,您唯一需要做的就是在两台主机之间连接一根 Thunderbolt 电缆;thunderbolt-net 驱动程序将自动加载。如果另一台主机也是 Linux,您应该在一台主机上手动加载 thunderbolt-net(哪一台都可以)

# modprobe thunderbolt-net

这会自动触发另一台主机上的模块加载。如果驱动程序已内置到内核镜像中,则无需执行任何操作。

驱动程序将为每个 Thunderbolt 端口创建一个虚拟以太网接口,其命名类似 thunderbolt0 等。从这一点开始,您可以使用诸如 ip 之类的标准用户空间工具来配置接口,或者让您的图形界面自动处理它。

直接通过 Thunderbolt 电缆流式传输数据

除了 Thunderbolt 网络(又称 USB4NET)之外,Linux 还支持直接通过电缆流式传输数据(又称 USB4STREAM)。这可以通过 thunderbolt-stream 驱动程序实现。

thunderbolt-net 类似,您首先在一端加载驱动程序

host1 # modprobe thunderbolt-stream

然后通过 ConfigFS 进行配置

host1 # cd /sys/kernel/config/thunderbolt/stream
host1 # mkdir -p 0-1.0/data
host1 # cd 0-1.0
host1 # echo -1 > data/in_hopid
host1 # echo -1 > data/out_hopid

此信息通过 XDomain 属性自动通告给另一端,因此如果您连接了电缆,另一端会知道有一个名为 data 的流可用,并且可以为您自动配置它

host2 # cd /sys/kernel/config/thunderbolt/stream
host2 # mkdir -p 0-3.0/data

这里我们使用了自动配置,但您也可以手动配置它。在这种情况下,您需要相应地填充 in_hopidout_hopid。如果将它们设置为 -1,则会使用下一个可用的 HopID,这通常正是我们想要的。

配置完成后,您可以在两侧使用 /dev/tbstreamX 来传输数据

host2 # cat /dev/tbstream0
host1 # dmesg > /dev/tbstream0

完成流传输后,您可以将它们移除

host2 # cd /sys/kernel/config/thunderbolt/stream
host2 # rmdir -p 0-1.0/data
host1 # cd /sys/kernel/config/thunderbolt/stream
host1 # rmdir -p 0-3.0/data

由于流本质上是文件,因此您可以使用以某种形式支持 read(2)write(2) 的任何现有应用程序。

可以拥有多个流,并且可以同时使用流和 thunderbolt-net。例如,我们可以创建两个名为 controldata 的流,像这样

host1 # cd /sys/kernel/config/thunderbolt/stream
host1 # mkdir 0-1.0
host1 # cd 0-1.0
host1 # mkdir control
host1 # mkdir data

然后,您拥有用于 control/dev/tbstream0 以及用于 data/dev/tbstream1。在使用它们之前,您需要按照上面单流情况所示对它们进行配置。

强制加电

许多 OEM 都包含一种方法,即使未连接任何设备,也可以用于将 Thunderbolt 控制器的电源强制置于“开启”状态。如果您的机器支持这一点,它将通过 WMI 总线公开为一个名为“force_power”的 sysfs 属性,详见 ABI file testing/sysfs-platform-intel-wmi-thunderbolt

注意:目前无法查询平台的强制加电状态。