一、核心说明
以下时序图基于
https://github.com/rivosinc/salus/blob/main/test-workloads/src/bin/tellus.rs
提供的Tellus测试程序逻辑,聚焦主核心(hart 0) 的核心执行流程,覆盖TVM创建、运行、中断/异常处理、资源回收全生命周期;次级核心(hart 1+)仅简化展示"启动→自旋等待任务"逻辑,不展开细节。
时序图使用Mermaid语法,可直接复制到支持Mermaid的工具(如Mermaid Live Editor、VS Code Mermaid插件)中渲染查看。
二、完整时序图
主核心 次级核心 TVM 阶段1:初始化(基础环境准备) kernel_init入口,初始化控制台/FDT/IMSIC 调用hart_start启动次级核心 secondary_init,自旋等待IPI任务 验证COVE扩展,完成初始化 阶段2:TVM创建(资源配置与最终化) 捐赠页→创建TVM→注册共享内存 添加vCPU0/AIA配置/Guest数据页 TVM_finalize,设置入口+中断预配置 阶段3:TVM运行&异常处理(核心循环) 调用tvm_run运行Guest 触发Trap(ECALL/页故障/中断) 处理Trap,关闭对应中断/映射缺页 循环调用tvm_run继续运行 阶段4:资源回收(校验与系统关机) 向量校验→AIA解绑→销毁TVM 验证共享页→回收所有捐赠页 PMU测试→注销共享内存→系统关机 主核心 次级核心 TVM Tellus测试程序核心时序(RISC-V S-mode TVM测试)
start.S
主核心 SBI 次级核心 主核心(hart0)启动流程 触发_start汇编入口 初始化GP全局指针 清空sstatus/sie,禁用所有中断 循环清零BSS段(未初始化全局变量) 设置SP栈指针指向_stack_end 调用Rust层kernel_init(传入hart_id/fdt_addr) kernel_init完成初始化(控制台/FDT/IMSIC) 理论分支:kernel_init返回后进入WFI循环 次级核心(hart1及以上)启动流程 触发_secondary_start汇编入口 初始化GP全局指针 清空sstatus/sie,禁用所有中断 SP/TP绑定a1(PerCpu结构体地址) 调用Rust层secondary_init secondary_init完成,进入WFI休眠循环 (可选)通过IPI唤醒次级核心执行任务 主核心 SBI 次级核心 Tellus程序核心启动完整时序(RISC-V S-mode)
三、关键节点补充说明
-
时序核心逻辑:
- 初始化阶段:完成硬件配置(IMSIC、CPU、内存)和环境准备;
- TVM创建阶段:核心是"捐赠页→创建TVM→配置资源(内存/中断)→最终化",所有
convert_pages后需调用fence_memory保证内存一致性; - 运行循环:核心是"tvm_run→捕获Trap→处理Trap→循环",覆盖Guest ECALL、缺页、中断三大核心场景;
- 回收阶段:先校验数据(向量寄存器、共享页),再销毁TVM、回收所有捐赠页,最后执行PMU测试并关机。
-
次级核心简化逻辑 :
次级核心启动后仅执行
secondary_init→进入TaskRunner::spin,等待主核心通过IPI发送任务(如fence_memory中的tsm_local_fence),任务执行完成后回到WFI休眠,时序图中未展开此细节(仅标注"自旋等待任务")。 -
中断/异常处理关键:
- 预配置的
stimecmp=0和si_eip[0]pending位会触发首次中断,处理时会关闭对应中断使能; - Guest侧的外部中断通过
SupervisorGuestExternal投递,主核心侧外部中断通过SupervisorExternal投递,两者严格区分。
- 预配置的
四、启动逻辑总结
该时序图核心覆盖:
- 初始化→TVM创建→运行循环→资源回收的全生命周期;
- 关键API调用(cove_host/中断/AIA相关)的执行顺序;
- Trap处理的分支逻辑(ECALL/缺页/中断)。
五、次级核心启动
rust
/// Starts the given cpu executing at `start_addr` with `opaque` in register a1.
///
/// # Safety
///
/// start_addr must point to code that can be safely executed.
/// opaque, if a pointer, must point to data that is safe to access from the newly running context.
pub unsafe fn hart_start(hart_id: u64, start_addr: u64, opaque: u64) -> Result<()> {
// 1. 构造SBI消息结构体:封装hart_id/start_addr/opaque
let msg = SbiMessage::HartState(HartStart {
hart_id, // 要唤醒的次级核心ID(如1/2/3)
start_addr, // 次级核心启动后跳转的地址(_secondary_start的物理地址)
opaque, // 传给次级核心的a1参数(PerCpu结构体地址)
});
// 2. 发送SBI消息并执行ecall调用
// Safety注释:保证start_addr是合法的次级核心启动代码地址
ecall_send::<()>(&msg)?;
Ok(())
}
这段代码的核心是SbiMessage和ecall_send,它们是对底层ecall指令的封装,内部逻辑等价于之前的汇编内联代码:
rust
// (底层ecall_send的简化实现,帮你补全上下文)
fn ecall_send<T>(msg: &SbiMessage) -> Result<T> {
unsafe {
// 按SBI约定,将msg参数映射到a0-a7寄存器
let (a0, a1, a2, a6, a7) = msg.into_registers();
let ret: SbiRet;
// 执行ecall指令,触发SBI调用
asm!(
"ecall",
in("a0") a0,
in("a1") a1,
in("a2") a2,
in("a6") a6,
in("a7") a7,
lateout("a0") ret.error,
lateout("a1") ret.value,
options(nostack),
);
// 处理返回值,转换为Rust的Result类型
if ret.error == 0 {
Ok(unsafe { core::mem::transmute(ret.value) })
} else {
Err(SbiError::from(ret.error))
}
}
}
SbiMessage::HartState:对应SBI的HSM扩展(0x48534D),HartStart对应扩展内的hart_start函数(函数号0);into_registers():将hart_id/start_addr/opaque分别映射到a0/a1/a2,a6=函数号,a7=扩展号,和手动调用的寄存器约定完全一致。