继上一篇 COCONUT-SVSM 代码初次阅读,这次来讲讲 COCONUT-SVSM 实现的相关协议,以及是怎么处理这些协议请求的。
阅读该文章之前,需要对 SEV-SNP 以及 SVSM 有一定了解:
COCONUT-SVSM 的开发计划是要支持 AMD 的 SEV-SNP、Intel 的 TDX 以及 Arm 的 CCA。但从目前的计划表来看,Intel 的 TDX 已经提上日程了;但是 Arm CCA 还没看到相关计划。
COCONUT-SVSM 计划实现三种执行模式:
轻量操作系统模式 (Enlight OS Mode),仅提供 Hypervisor 不能提供的设备模拟服务。
Paravisor 模式,接管 VE/VC 异常以及管理私有和共享内存。(应该还在日程中,没实现)
服务虚拟机模式,SVSM 就是操作系统,Hypervisor 只是一个交互通道,向另一个 CVM 提供服务。
1 客户操作系统的启动
继续上一篇,startup_64
执行完成后,会跳转到 svsm_start()
函数,该函数也在 linux/src/svsm.rs
代码中实现。函数有两个参数,一个 KernelLaunchInfo 和 vb_addr。函数执行一下操作:
-
对 GDT 和 IDT 进行初始化。
-
解析 KernelLaunchInfo 参数,对全局变量 LAUNCH 进行初始化。
-
分配安全页面和一些系统寄存器如 cr0 和 cr4 的初始化。
-
初始化终端和相应的环境。
-
从 KernelLaunchInfo 中解析内核可链接执行文件的相关信息。
-
初始化分页,并为每个 CPU 设置页表和平台相关信息。
-
文件系统初始化。
-
设置每个 CPU 起始入口为
svsm_main()
函数,并设置调试接口。
当每个 CPU 启动之后,都会进入到 svsm_main()
函数中,该函数在 linux/src/svsm.rs
代码中实现。函数执行的操作如下:
-
设置 SVSM_PLATFORM 的相关环境。
-
从 LAUNCH 中读取启动相关信息。
-
设置 IGVM。
-
内存映射和内核文件系统的解包。
-
从配置中获取 CPU 的相关信息以及个数,启动全部 CPU。
-
准备固件和 vTPM,然后启动固件。
-
创建和启动内核任务,即
request_processing_main()
函数。这个函数非常的关键。 -
启动和加载用户任务
/init
。 -
进入到
request_loop()
函数中。
2 请求处理函数
request_processing_main()
函数用于处理和分发请求的,在 linux/src/requests.rs
中实现。函数进入后,就是一个死循环。循环中第一个调用的函数就是 wait_for_requests()
,该函数将当前任务加入到等待任务队列 (waitqueue) 中,并进行调度。这就解释了在 中提到的第7步创建和启动内核任务后还能回到当前执行流的原因。
当有请求回到当前内核任务的上下文后,就会执行死循环的内容。首先,从 RAX 寄存器中读取请求的功能对象编号,以及从 VMSA 中读取请求的参数。然后调用 request_loop_once()
函数,处理该请求。
根据 request_loop_once()
处理函数的返回结果,设置 RAX 的值,将请求的相关结果存储到 VMSA 中,回到死循环的开始位置。
3 请求循环函数
request_loop()
函数在 linux/src/requests.rs
代码中实现。其实,这个函数更像是 VMPL 的启动和恢复处理函数。
函数本身也是一个死循环。按照注释的描述,每次循环都是检查是否有客户机是可运行的,如果没有,就暂停并等待可以执行的客户机(该过程由 Hypervisor 进行调度)。如果有可以运行的客户机,则将该客户机的 VMSA 设置成激活状态,并进行 VMPL 切换。
客户机执行时如果需要异常,会先退回到 Hypervisor,由 Hypervisor 交给 VMPL0 处理,即 SVSM。SVSM 恢复了进入 VMPL 时的上下文。读去 GUEST_VMPL 的 CPU 的 RAX 的值,获取请求相关信息。通过 check_requests()
函数检查请求的合法性。如果合法,调用 process_requests()
函数处理。
pub fn wait_for_requests() {
let current_task = current_task();
this_cpu()
.request_waitqueue
.borrow_mut()
.wait_for_event(current_task);
schedule();
}
pub fn process_requests() {
let maybe_task = this_cpu().request_waitqueue.borrow_mut().wakeup();
if let Some(task) = maybe_task {
schedule_task(task);
}
}
process_requests()
函数在 linux/src/cpu/percpu.rs
中实现,代码如上。从 waitqueue 中取出之前放入的内核任务,并调度,从而又回到 提到的 wait_for_requests()
的位置。
4 请求处理
request_loop_once()
函数在 linux/src/requests.rs
中实现。函数先检查了客户机的退出码是否是 VMGEXIT,如果是,在根据请求的协议类型进行分发。COCONUT-SVSM 已经实现了 SVSM 白皮书中提到的三个协议:
-
核心 (core) 协议:交给
core_protocol_request()
函数处理。 -
vTPM 协议:交给
vtpm_protocol_request()
函数处理。 -
APIC 协议:交给
apic_protocol_request()
函数处理。 -
其他都是不支持的协议。
4.1 核心协议
core_protocol_request()
函数在 linux/src/procotol/core.rs
中实现。对核心协议由进行了一次分发:
match request {
SVSM_REQ_CORE_REMAP_CA => core_remap_ca(params),
SVSM_REQ_CORE_PVALIDATE => core_pvalidate(params),
SVSM_REQ_CORE_CREATE_VCPU => core_create_vcpu(params),
SVSM_REQ_CORE_DELETE_VCPU => core_delete_vcpu(params),
SVSM_REQ_CORE_DEPOSIT_MEM => core_deposit_mem(params),
SVSM_REQ_CORE_WITHDRAW_MEM => core_withdraw_mem(params),
SVSM_REQ_CORE_QUERY_PROTOCOL => core_query_protocol(params),
SVSM_REQ_CORE_CONFIGURE_VTOM => core_configure_vtom(params),
_ => Err(SvsmReqError::unsupported_call()),
}
4.2 vTPM 协议
vtpm_protocol_request()
函数在 linux/src/procotol/vtpm.rs
中实现。对 vTPM 协议由进行了一次分发:
match request {
SVSM_VTPM_QUERY => vtpm_query_request(params),
SVSM_VTPM_COMMAND => vtpm_command_request(params),
_ => Err(SvsmReqError::unsupported_call()),
}
4.3 APIC 协议
apic_protocol_request()
函数在 linux/src/procotol/apic.rs
中实现。函数开始会检查是否使用 APIC 的模拟,如果不是,返回错误信息;如果是,对 APIC 协议由进行了一次分发:
match request {
SVSM_REQ_APIC_QUERY_FEATURES => apic_query_features(params),
SVSM_REQ_APIC_CONFIGURE => apic_configure(params),
SVSM_REQ_APIC_READ_REGISTER => apic_read_register(params),
SVSM_REQ_APIC_WRITE_REGISTER => apic_write_register(params),
SVSM_REQ_APIC_CONFIGURE_VECTOR => apic_configure_vector(params),
_ => Err(SvsmReqError::unsupported_call()),
}
各个协议的实现请看后续。