虚拟 Oxide 实验室:模拟完整 Oxide 机架
Virtual Oxide Labs: Emulating a Full Oxide Rack

原始链接: https://oxide.computer/blog/voxel-labs

sp-emu 是一个开源工具,可在 illumos/Linux(x86/ARM)上仿真 Cortex-M7 和 Cortex-M33 设备,用更高保真度的仿真取代 SpSim 桩。你可以将 Hubris 固件烧录到虚拟设备,并通过 MGS 和 IPCC 经套接字通信。最难的部分是时间:由于 Hubris 使用 1 ms SysTick,而仿真比硅片慢,时间被建模为指令。`maybe_tick` 每条指令递减一次 SysTick 计数器,并且仅在安全时触发 SysTick 异常。外设访问耗费一个指令 tick。`exception_entry` 建模 Cortex-M 异常进入。仿真器假定 Hubris 的内核不可抢占:SVCall、PendSV、SysTick 和 IRQ 共享优先级,故障优先级更高。`maybe_interrupt` 根据 PRIMASK/FAULTMASK/BASEPRI 投递挂起的 IRQ,并处理 PendSV 上下文切换。其主循环单步执行一条 Thumb-2 指令,更新 SysTick,投递中断,并定期为 MGS 数据包泵送 Ethernet/UART。

Hacker News 新 | 过去 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 Virtual Oxide Labs:模拟完整的 Oxide 机架 ( oxide.computer ) 24 分 由 tosh 3 小时前 | 隐藏 | 过去 | 收藏 | 1 条评论 帮助 swherdman 36 分钟前 [–] 如果你只想尝试模拟虚拟化栈,我大约 6-9 个月前构建了这个 https://github.com/swherdman/whoah-cli 如果你更喜欢手动构建,步骤如下: https://swherdman.com/blog/We-Have-Oxide-at-Home-P1/ https://swherdman.com/blog/We-Have-Oxide-at-Home-P2/ 回复 考虑申请 YC 2027 年冬季批次! 申请 开放至 11 月 2 日。 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系 搜索:
相关文章

原文

This is the bit I mentioned a moment ago. A big part of what makes our racks unique is the embedded RoT and SP in every sled. We’ve long had an SpSim construct that served as a stub for the service processor and would return the right info at the right times. But that’s simulation, not emulation. It’s always going to be happy path, and that’s just not enough fidelity for real testing cases.

As such, we built actual emulated Cortex-M7 and Cortex-M33 devices for illumos and Linux (x86 and ARM). The repo for this tool (sp-emu) is open source. Each of the two devices runs in a single thread, and (mostly) faithfully emulates the three main requirements: instructions, time, and interrupts.

From there, you can flash our Hubris firmware directly to these emulated devices, and then communicate by way of an ordinary socket. The Voxel lab can speak with these virtual devices via the Management Gateway Service (MGS) and Inter-Processor Communication Channel (IPCC). You can learn everything you want and more about these concepts in the Management Gateway Service repo and Hubris docs.

There really is no "run real microcontrollers as virtual devices with this one neat trick!" I can think of. But the real challenge of sp-emu’s job is time. Remember all of the code we’re running there is meant for silicon. Hubris schedules off a 1ms SysTick and the emulator doesn’t use wall time; as such, we had to invent a clock. That said, emulated hardware is also slower than the real thing. So there’s…​ well, less time per time. So instead of trying to get all temporal about it, time became instructions. For example, I give you maybe_tick:

if self.systick == 0 {
self.systick = self.syst_rvr;
}
self.systick -= 1;
if self.systick == 0 {
self.systick = self.syst_rvr;
self.tick_events = self.tick_events.wrapping_add(1);
if self.syst_csr & 2 != 0
&& self.mode == Mode::Thread
&& !self.primask
{
self.exception_entry(15, bus); // SysTick exception
}
}

In this case, we count instructions against the kernel’s own reload value and fire exceptions anywhere the single-priority design makes it safe to take it — in thread mode, interrupts unmasked. By the same token, peripheral access costs one tick of our clock (an instruction), whereas on silicon it might stall for dozens of cycles on the bus.

You may have noticed self.exception_entry which is defined elsewhere in the code:

fn exception_entry(&mut self, vecnum: u32, bus: &mut Bus)

In short, this function models everything the hardware does on a Cortex-M core taking an exception, all the way between "exception becomes active" to the first handler instruction. vecnum here is an ARMv7-M exception number (also the vector table index for that exception’s handler). bus is the memory system which is used to actually push the frame and read the table.

Hubris has a non-preemptible kernel, though it schedules tasks preemptively. SVCall, PendSV, SysTick and all IRQs run at the same priority level, while faults keep a higher priority. As such, the emulator doesn’t have to nest handlers. It processes pending IRQs based on the PRIMASK, FAULTMASK, and BASEPRI settings. If one of them pended PendSV, that will perform the deferred context switch for the kernel. For more details on how it works internally, you can check this lovely bit of documentation.

if let Some(irq) = bus.next_irq() {
if self.basepri == 0 || (bus.irq_prio(irq) as u32) < self.basepri {
bus.clear_pending(irq);
self.exception_entry(16 + irq as u32, bus);
return;
}
}
if bus.take_pendsv() {
self.exception_entry(14, bus);
}

There’s a lot more we can talk about here, but to sum it all up you can look at this pseudo-ish loop:

loop {
cpu.step(&mut bus, host.as_mut())?; // one Thumb-2 instruction
cpu.maybe_tick(&mut bus); // SysTick: time is instructions
cpu.maybe_interrupt(&mut bus); // NVIC and PendSV delivery
if cpu.cycles % 4096 == 0 {
bus.pump_eth(host.as_mut()); // MGS packets in and out
bus.pump_uart(host.as_mut());
}
}

Note: yes I’m aware I said there’s no cycles then have a field named cycles. That’s the "fake it till you make it" part of emulation.

That’s the overall heartbeat of sp-emu, and it runs ad infinitum. Everything else exists to make those calls seem real.

联系我们 contact @ memedata.com