3. TPM硬件与接口

TPM 并不是一个纯软件抽象,它首先是一颗真实存在的安全芯片或固件模块。Linux 要使用 TPM,必须先在物理层或总线层“找到它”,再通过内核驱动把它抽象成 /dev/tpm0 与 /dev/tpmrm0 这样的字符设备。因此,理解 TPM 的物理接口、枚举路径以及固件描述方式,是驱动移植与系统集成的基础。

一、核心原理:TPM 的物理接口形态

TPM 在物理连接上主要有离散芯片与固件 TPM(fTPM)两种形态。离散芯片常见接口包括 LPC、SPI、I2C,部分服务器平台还会使用 I2C 或 SPI 的 TPM 2.0 模块。不同接口决定了内核中不同的总线驱动与枚举方式。

  • LPC:传统 PC/服务器平台常见,TPM 挂在 LPC 总线的固定 I/O 窗口,通常由 ACPI 的 TPM2 表或 PNP0C31 设备描述,驱动通过固定端口访问。
  • SPI:嵌入式与部分 PC 平台常用,TPM 作为 SPI 从设备,依赖片选、时钟、MOSI/MISO 四线通信,枚举依赖 ACPI 或设备树中的 SPI 节点。
  • I2C:多见于 ARM 嵌入式与部分服务器,TPM 作为 I2C 从设备,通过 7 位地址寻址,枚举依赖设备树或 ACPI 的 I2C 描述。
  • fTPM:由 CPU 或安全协处理器固件实现,通常通过 TEE 或 MMIO 寄存器访问,Linux 侧可能表现为 tpm_ftpm 或 tpm_crb 设备。

无论哪种接口,Linux 内核都通过 tpm_tis、tpm_tis_spi、tpm_tis_i2c、tpm_crb 等驱动完成寄存器级访问,再向上提供统一的 TPM 字符设备接口。

二、设备枚举与固件描述方式

TPM 设备枚举的关键在于“固件如何告诉内核这里有一颗 TPM”。在 x86/ACPI 平台上,主要依赖 ACPI 表;在 ARM/嵌入式平台上,主要依赖设备树(Device Tree)。两者描述的信息包括:总线类型、片选/地址、中断、寄存器访问方式、是否 CRB 或 FIFO 接口等。

ACPI 侧常见描述包括:

  • TPM2 表:描述 TPM 2.0 的物理存在、控制区域、启动状态等。
  • PNP0C31:传统 TPM 设备 ID,常见于 LPC TPM。
  • MSFT0101:Windows 兼容的 TPM 2.0 设备 ID,Linux 也支持。
  • _CRS、_DSM:描述资源与功能,如 FIFO/CRB 选择、中断号等。

设备树侧则通过节点属性描述,例如:

tpm@0 {
    compatible = "infineon,slb9670", "tcg,tpm_tis-spi";
    reg = <0>;
    spi-max-frequency = <10000000>;
    interrupt-parent = <&gpio1>;
    interrupts = <12 IRQ_TYPE_LEVEL_LOW>;
};

其中 compatible 是驱动匹配的核心,reg 表示片选或 I2C 地址,spi-max-frequency 决定 SPI 时钟上限,interrupts 描述中断引脚。内核根据这些信息实例化 SPI/I2C 设备,再绑定到 tpm_tis_spi 或 tpm_tis_i2c 驱动。

TPM 物理接口与枚举路径 固件描述层 ACPI 表 TPM2 / PNP0C31 MSFT0101 / _CRS 设备树 DT compatible / reg interrupts / 频率 fTPM / CRB MMIO / TEE tpm_crb 总线与接口层 LPC 固定 I/O 窗口 SPI CS / CLK / MOSI / MISO I2C 7 位从地址 CRB / MMIO 寄存器窗口 内核驱动与用户接口 tpm_tis / tpm_tis_spi / tpm_tis_i2c 寄存器读写与命令传输 tpm_crb CRB 接口与 MMIO 映射 TPM 字符设备 /dev/tpm0 /dev/tpmrm0 用户空间工具 tpm2-tools / tss2 固件描述 → 总线枚举 → 内核驱动绑定 → 字符设备与用户空间

三、方法流程:从固件到 /dev/tpm0

以 SPI TPM 为例,典型枚举流程如下:

  1. 固件(ACPI 或 DT)描述 SPI 控制器下的 TPM 子节点,给出片选、频率、中断和 compatible。
  2. 内核 SPI 子系统根据 reg 实例化 SPI 设备,并匹配 tpm_tis_spi 驱动。
  3. 驱动初始化 TIS 寄存器,读取 TPM_DID_VID 与 TPM_RID 确认芯片存在。
  4. 注册 TPM 字符设备,创建 /dev/tpm0 与 /dev/tpmrm0。
  5. 用户空间通过 tpm2-tools 或 tss2 发起命令,驱动将 TPM 命令封装为 SPI 传输。

在 ACPI 平台上,可以用以下命令确认 TPM 是否被正确枚举:

# 查看 ACPI 中的 TPM 设备
dmesg | grep -i tpm
# 典型输出
tpm_tis_spi spi0.0: 2.0 TPM (device-id 0x1B, rev-id 16)
tpm tpm0: A TPM error (256) occurred attempting the self test

# 查看字符设备
ls -l /dev/tpm*
crw------- 1 root root 10, 224 Jan  1 00:00 /dev/tpm0
crw------- 1 root root 10, 225 Jan  1 00:00 /dev/tpmrm0

四、案例与场景:设备树中的 SPI TPM

在 ARM 嵌入式板卡上,TPM 通常挂在 SPI1 控制器下,片选 0,中断接 GPIO。设备树片段如下:

&spi1 {
    status = "okay";
    tpm: tpm@0 {
        compatible = "infineon,slb9670", "tcg,tpm_tis-spi";
        reg = <0>;
        spi-max-frequency = <10000000>;
        interrupt-parent = <&gpio1>;
        interrupts = <12 IRQ_TYPE_LEVEL_LOW>;
    };
};

如果 compatible 写错,内核不会绑定 tpm_tis_spi,/dev/tpm0 不会出现。此时应检查 dmesg 中是否有 SPI 设备注册但无 TPM 驱动匹配的记录,并核对 compatible 是否与驱动中的 of_device_id 一致。

五、常见误区与复盘

误区一:只改设备树,忽略内核配置。 即使设备树正确,若内核未开启 CONFIG_TCG_TPM、CONFIG_TCG_TIS_SPI,驱动也不会编译进内核。

误区二:把 I2C 地址当成 SPI 片选。 I2C TPM 的 reg 是从地址,SPI TPM 的 reg 是片选号,两者含义不同。

误区三:忽略中断配置。 部分 TPM 驱动依赖中断完成命令,若中断号错误,可能出现超时或命令失败。

接口类型 典型描述方式 内核驱动 关键参数
LPC ACPI TPM2 / PNP0C31 tpm_tis I/O 端口、中断
SPI ACPI 或设备树 SPI 节点 tpm_tis_spi 片选、频率、中断
I2C ACPI 或设备树 I2C 节点 tpm_tis_i2c 从地址、频率、中断
fTPM / CRB ACPI TPM2 / MMIO tpm_crb 控制区域、启动状态

调试 TPM 枚举问题时,建议按“固件描述 → 总线注册 → 驱动匹配 → 字符设备”四层逐级排查。先用 dmesg | grep -i tpm 看驱动是否加载,再用 ls /dev/tpm* 确认设备节点,最后用 tpm2_getcap 验证命令通道是否可用。