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 驱动。
三、方法流程:从固件到 /dev/tpm0
以 SPI TPM 为例,典型枚举流程如下:
- 固件(ACPI 或 DT)描述 SPI 控制器下的 TPM 子节点,给出片选、频率、中断和 compatible。
- 内核 SPI 子系统根据
reg实例化 SPI 设备,并匹配tpm_tis_spi驱动。 - 驱动初始化 TIS 寄存器,读取
TPM_DID_VID与TPM_RID确认芯片存在。 - 注册 TPM 字符设备,创建
/dev/tpm0与/dev/tpmrm0。 - 用户空间通过
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 验证命令通道是否可用。