Linux CoCo 测量寄存器(Measurement Registers)API:用 tsm-mr 为 TVM 度量设施构建 sysfs 接口
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
Linux 内核在机密计算(Confidential Computing,CoCo)Guest 侧提供了通用的测量寄存器(MR)框架,用于把 TVM(Trusted Virtual Machine)硬件/固件支持的度量能力统一暴露为 sysfs 二进制属性。本文基于内核文档 Measurement Registers 及其引用的两份源码,完整解析该 API 的数据模型、回调约定、sysfs 属性生成规则以及底层读/写路径的缓存同步机制,帮助读者掌握 CC Guest 驱动接入度量寄存器设施所需的内核能力。
文档定位:CoCo 文档树中的 MR 规范
Documentation/driver-api/coco/目录下当前只收录了这一篇测量寄存器文档(见 index.rst)。文档本身非常简短,其全部内容通过kernel-doc指令直接从两份源码文件中提取:
- include/linux/tsm-mr.h:以
:internal:方式引入,即只渲染结构体/宏/函数原型等内部 API; - drivers/virt/coco/guest/tsm-mr.c:以
:export:方式引入,即只渲染导出的EXPORT_SYMBOL_GPL函数。
也就是说,阅读这两份源文件中的 kernel-doc 注释,就等价于阅读了该文档的全部正文。这个组织方式也提示了该 API 的设计取向:接口契约写在头文件,通用实现放在 Guest 侧公共代码中,架构相关部分由各自的 CC Guest 驱动提供。
数据模型:struct tsm_measurement_register 描述一个测量寄存器
框架的核心抽象是单个测量寄存器的描述结构体,定义于 include/linux/tsm-mr.h:
struct tsm_measurement_register { const char *mr_name; void *mr_value; u32 mr_size; u32 mr_flags; enum hash_algo mr_hash; };各字段语义如下:
| 字段 | 含义 |
|---|---|
mr_name | MR 的名称。与mr_value一样,必须在该结构体不再被使用之前保持有效(生命周期约束)。 |
mr_value | 指向当前 MR 值缓冲区的指针,即内核侧的 MR 缓存。 |
mr_size | MR 的字节大小,通常就是mr_hash对应摘要的长度(digest size)。 |
mr_flags | 一组标志位的按位或,详见下文标志表。 |
mr_hash | 可选的哈希算法标识,取值来自include/uapi/linux/hash_info.h中定义的enum hash_algo。 |
头文件注释明确指出,CC Guest 驱动需要把若干struct tsm_measurement_register组织成数组,包裹进struct tsm_measurements,以此向框架描述底层 CC 硬件支持的度量设施。
标志位(Flags)
标志位定义在 include/linux/tsm-mr.h:
#define TSM_MR_F_NOHASH 1 #define TSM_MR_F_WRITABLE 2 #define TSM_MR_F_READABLE 4 #define TSM_MR_F_LIVE 8 #define TSM_MR_F_RTMR (TSM_MR_F_LIVE | TSM_MR_F_WRITABLE)| 标志 | 值 | 语义 |
|---|---|---|
TSM_MR_F_NOHASH | 1 | 该 MR不关联哈希算法,此时mr_hash字段被忽略。 |
TSM_MR_F_WRITABLE | 2 | 对应的 sysfs 属性可写。语义上通常是“extend”(把输入数据扩展进 MR),但具体语义可能因架构和 MR 不同而变化。 |
TSM_MR_F_READABLE | 4 | 对应的 sysfs 属性可读。 |
TSM_MR_F_LIVE | 8 | 该 MR 的当前值可能与最后一次写入的值不同(例如被固件/其他 CPU 持续扩展),因此必须回读底层 CC 硬件/固件刷新。 |
TSM_MR_F_RTMR | LIVE \| WRITABLE | 组合标志:既是 LIVE 又可写,即一个实时度量寄存器(RTMR,运行时度量寄存器)的常用特征组合。 |
TSM_MR_() 初始化宏
对于最常见的“只读 + 带哈希”的 MR,头文件提供了一个初始化宏(include/linux/tsm-mr.h):
#define TSM_MR_(mr, hash) \ .mr_name = #mr, .mr_size = hash##_DIGEST_SIZE, \ .mr_hash = HASH_ALGO_##hash, .mr_flags = TSM_MR_F_READABLE它做了三件事:字符串化寄存器名mr;用hash##_DIGEST_SIZE展开得到摘要长度作为mr_size(例如TSM_MR_(rtmr, sha384)会得到mr_size = sha384_DIGEST_SIZE);把mr_hash设为HASH_ALGO_##hash,并默认只置TSM_MR_F_READABLE。带 LIVE/可写/NOHASH 的寄存器则需要在该宏基础上自行补全相应标志。
驱动注册入口:struct tsm_measurements 与两个回调
架构侧驱动与框架的契约是struct tsm_measurements,定义于 include/linux/tsm-mr.h:
struct tsm_measurements { const struct tsm_measurement_register *mrs; size_t nr_mrs; int (*refresh)(const struct tsm_measurements *tm); int (*write)(const struct tsm_measurements *tm, const struct tsm_measurement_register *mr, const u8 *data); };| 成员 | 说明 |
|---|---|
mrs | MR 定义数组。 |
nr_mrs | 数组元素个数。 |
refresh | 从 TVM 硬件/固件批量加载/同步所有 MR到内核缓存的回调。当某个带TSM_MR_F_LIVE的 MR 被读取且缓存过期时,框架会调用它;回调实现必须重新加载所有TSM_MR_F_LIVE的 MR。其唯一参数@tm是指回struct tsm_measurements自身的指针。 |
write | 写入指定 MR 的回调,除@tm外还接收两个参数:@mr(指向正在写入的 MR,即tm->mrs中的元素)和@data(待写入的字节流,长度为mr->mr_size)。典型语义是把输入缓冲区扩展(extend)进该 MR。 |
两个回调的返回值约定一致:成功返回 0,失败返回适当的负错误码。这一约定直接决定了上层 sysfs 读/写路径的错误传播行为(见下文实现分析)。
API 函数:创建与释放属性组
框架对外的导出接口只有两个函数(原型见 include/linux/tsm-mr.h,实现见 drivers/virt/coco/guest/tsm-mr.c):
const struct attribute_group * tsm_mr_create_attribute_group(const struct tsm_measurements *tm); void tsm_mr_free_attribute_group(const struct attribute_group *attr_grp);tsm_mr_create_attribute_group() 的行为与校验规则
实现位于 drivers/virt/coco/guest/tsm-mr.c#L140-L235,kernel-doc 说明其运行于进程上下文、可能因内存分配而睡眠。源码中可以看到它在创建属性前做了一组严格的定义校验,全部不满足则返回-EINVAL:
tm或tm->mrs为空 —— 直接-EINVAL;- 存在带
TSM_MR_F_LIVE的 MR 但tm->refresh为 NULL ——-EINVAL(LIVE MR 没有刷新来源,读到的值不可信); - 存在带
TSM_MR_F_WRITABLE的 MR 但tm->write为 NULL ——-EINVAL; - 任一 MR 缺少
mr_name——-EINVAL; - 未设
TSM_MR_F_NOHASH的 MR,其mr_hash必须小于HASH_ALGO__LAST——-EINVAL; - 生成后的 sysfs 属性名(见下条命名规则)出现重复 ——
-EINVAL。
此外,若内存分配失败则返回-ENOMEM。
sysfs 属性命名与权限规则
源码揭示了属性在 sysfs 中的落盘形态:
- 命名规则:带哈希的 MR 属性名为
MRNAME:HASH(如rtmr:sha384),其中 HASH 部分取自hash_algo_name[]表;带TSM_MR_F_NOHASH的 MR 直接用mr_name作为属性名(见 drivers/virt/coco/guest/tsm-mr.c#L189-L203)。 - 属性类型:全部是
bin_attribute(二进制属性),bap->size设为该 MR 的mr_size,因此用户态读取一次得到的就是完整的 digest。 - 权限位:
TSM_MR_F_READABLE叠加0444并挂接tm_digest_read;TSM_MR_F_WRITABLE叠加0200并挂接tm_digest_write。一个既不可读也不可写的 MR 不会产生任何访问入口。 - 属性组名:固定为
"measurements"(ctx->agrp.name),CC Guest 驱动拿到返回的attribute_group后把它并入自己的 kobject 属性组即可。
一个值得注意的实现细节:属性指针数组与所有 MR 名串被合并进单次连续分配(属性数组之后紧跟字符串区),目的是让释放时只需kfree两次,无需逐个释放 MR 名串。
调用方生命周期约束与释放函数
kernel-doc 明确要求:在调用tsm_mr_free_attribute_group()之前不得释放tm,因为生成的属性引用了tm及其成员。释放函数实现于 drivers/virt/coco/guest/tsm-mr.c#L244-L251,它对IS_ERR_OR_NULL(attr_grp)做了防御,因此创建失败返回的错误指针也可以安全地传入释放。
底层实现:tm_context 与缓存同步机制
tm_digest_read()/tm_digest_write()背后的全部状态集中在内部结构体struct tm_context(drivers/virt/coco/guest/tsm-mr.c#L37-L43):
struct tm_context { struct rw_semaphore rwsem; /* 保护 MR 缓存的并发访问 */ struct attribute_group agrp; const struct tsm_measurements *tm; bool in_sync; /* MR 缓存是否最新 */ struct bin_attribute mrs[]; /* 每个 MR 一个 bin_attribute */ };其中in_sync是理解整个读路径的钥匙。源码注释解释了两层设计取舍:
- 为什么需要缓存:
tm->refresh()可能很昂贵(需要向 TVM 硬件/固件发起批量读取),因此tm_digest_read()缓存 MR 值,仅在必要时调用tm->refresh()。只有 LIVE MR 可能触发 refresh;非 LIVE MR 被假定一直保留着上次tm->write()写入的值。 - 为什么是全局标志而非每 MR 标志:多数(甚至所有)架构支持一次性读出全部 MR,因此用一个全局
in_sync比逐 MR 跟踪更简单。tm_digest_write()成功后会把in_sync置为false,表示“自上次 refresh 之后发生过写入,缓存已过期”。
读路径:tm_digest_read() 的锁升级与双重检查
tm_digest_read()(drivers/virt/coco/guest/tsm-mr.c#L45-L89)的流程是:
- 先
down_read_interruptible()拿读锁; - 若目标 MR 是 LIVE 且
!in_sync:释放读锁,down_write_killable()升为写锁,再次检查in_sync(双重检查,避免两个读者先后升级锁导致重复 refresh),调用ctx->tm->refresh(ctx->tm),把in_sync置为!rc(refresh 失败也视为“已尝试同步”,不无限重试),并通过trace_tsm_mr_refresh()打点,最后downgrade_write()降级回读锁; memcpy出mr->mr_value + off共count字节,打trace_tsm_mr_read()点,放锁返回。
写路径:tm_digest_write() 拒绝部分写
tm_digest_write()(drivers/virt/coco/guest/tsm-mr.c#L91-L120)的关键约束是不支持部分写:
/* partial writes are not supported */ if (off != 0 || count != attr->size) return -EINVAL;即用户态必须从偏移 0 开始、一次写入恰好mr_size字节(整个 digest 长度)。拿到写锁后调用tm->write();成功后把in_sync置false并打trace_tsm_mr_write()点;返回rc ?: count,保证成功时返回写入字节数。
可观测性与构建配置
tsm_mr 追踪事件
框架自带一组 tracepoint,定义在 include/trace/events/tsm_mr.h,与读/写/刷新路径一一对应:
| 事件 | 触发点 | 输出内容 |
|---|---|---|
tsm_mr_read | 每次成功读取 MR | MR 名、哈希名(NOHASH 时显示data)、MR 值 hex 串,格式[name] hash:<hex> |
tsm_mr_refresh | LIVE MR 触发刷新后 | MR 名、succeeded/failed及错误码 |
tsm_mr_write | 每次成功写入 MR | MR 名、哈希名、写入数据 hex 串 |
因此调试 MR 行为时无需改动驱动代码,启用tsm_mr:*类追踪即可观察读取、扩展与刷新的完整时序与结果。
编译开关
该框架由独立 Kconfig 选项CONFIG_TSM_MEASUREMENTS控制,构建规则见 drivers/virt/coco/guest/Makefile:
obj-$(CONFIG_TSM_MEASUREMENTS) += tsm-mr.o从源码结构看,tsm-mr.o位于drivers/virt/coco/guest/公共 Guest 侧目录(同目录还有report.c即 TSM 报告框架),而drivers/virt/coco/下按架构并列了sev-guest、tdx-guest、pkvm-guest、arm-cca-guest等各自独立的选择项,可以推断各架构 CC Guest 驱动在选中自身选项的同时依赖本框架提供 MR 的 sysfs 抽象。
面向 CC Guest 驱动开发者的接入要点
综合头文件契约与通用实现,一个 CC Guest 驱动接入 MR 设施需要做(按顺序):
- 定义
struct tsm_measurement_register数组:为每个 MR 指定mr_name、digest 缓存指针与大小、标志位(只读 MR 可直接用TSM_MR_()宏;RTMR 用TSM_MR_F_RTMR;无哈希的裸数据寄存器加TSM_MR_F_NOHASH); - 实现
refresh()回调:一次性从 TVM 硬件/固件回填所有LIVE MR 的mr_value缓存,成功返回 0; - 如有可写 MR,实现
write()回调:按架构语义(通常是 extend)把data(长度mr_size)扩展进指定 MR,成功返回 0; - 把以上打包进
struct tsm_measurements,调用tsm_mr_create_attribute_group()得到名为measurements的属性组,并入驱动的 kobject; - 卸载/退出时调用
tsm_mr_free_attribute_group(),且在此之前不得释放tm及 MR 名串、digest 缓存(mr_name与mr_value的“保持有效”约束); - 注意失败路径:创建函数可能返回
-EINVAL(定义非法,如 LIVE 无 refresh、WRITABLE 无 write、重名等)或-ENOMEM,驱动需对错误指针做处理,且释放函数可安全接收错误指针。
这套 API 的分工非常清晰:头文件定义“描述什么”(寄存器、哈希、标志、回调契约),tsm-mr.c负责“怎么呈现”(sysfs 属性生成、缓存与锁、部分写拒绝、追踪点),架构驱动只负责“值从哪里来、写到哪里去”。对维护者而言,理解in_sync全局同步标志和“LIVE 才触发 refresh”这条规则,是掌握整个测量寄存器子系统的关键。
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考