From 3505e0e2aeab130e5175d113f6270d76de3b97aa Mon Sep 17 00:00:00 2001 From: Changyuan Lyu Date: Fri, 16 Jan 2026 16:16:30 -0800 Subject: [PATCH 1/3] Revert "refactor(board): introduce board state Fatal" This reverts commit dcd524fd51e255fc349617297a79707aa5b96f4f. --- alioth/src/board/board.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/alioth/src/board/board.rs b/alioth/src/board/board.rs index cdd3451d..99c399ec 100644 --- a/alioth/src/board/board.rs +++ b/alioth/src/board/board.rs @@ -169,12 +169,12 @@ pub enum BoardState { Running, Shutdown, RebootPending, - Fatal, } #[derive(Debug)] struct MpSync { state: BoardState, + fatal: bool, count: u16, } @@ -251,6 +251,7 @@ where mp_sync: Mutex::new(MpSync { state: BoardState::Created, count: 0, + fatal: false, }), cond_var: Condvar::new(), } @@ -365,7 +366,7 @@ where fn sync_vcpus(&self, vcpus: &VcpuGuard) -> Result<()> { let mut mp_sync = self.mp_sync.lock(); - if mp_sync.state == BoardState::Fatal { + if mp_sync.fatal { return error::PeerFailure.fail(); } @@ -377,7 +378,7 @@ where self.cond_var.wait(&mut mp_sync) } - if mp_sync.state == BoardState::Fatal { + if mp_sync.fatal { return error::PeerFailure.fail(); } @@ -491,7 +492,7 @@ where log::warn!("VCPU-{index} reported error, unblocking other VCPUs..."); let mut mp_sync = self.mp_sync.lock(); - mp_sync.state = BoardState::Fatal; + mp_sync.fatal = true; if mp_sync.count > 0 { self.cond_var.notify_all(); } From 5f1fe7e1a5c8225a6ff23538f7a9d8623ecbb8e1 Mon Sep 17 00:00:00 2001 From: Changyuan Lyu Date: Fri, 16 Jan 2026 15:45:29 -0800 Subject: [PATCH 2/3] fix(hvf): interrupt VCPU on stop request instead of shutdown Modify `Vm::stop_vcpu()` to send a generic interrupt rather than forcing an immediate shutdown. This allows the VMM to decide the appropriate action when a VCPU is stopped (e.g., due to a fatal error on a peer VCPU), rather than being forced into a shutdown. Signed-off-by: Changyuan Lyu --- alioth/src/hv/hvf/vcpu/vcpu.rs | 33 +++++++++++++-------------------- alioth/src/hv/hvf/vm.rs | 4 ++-- 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/alioth/src/hv/hvf/vcpu/vcpu.rs b/alioth/src/hv/hvf/vcpu/vcpu.rs index 3f119fcd..d4f19bde 100644 --- a/alioth/src/hv/hvf/vcpu/vcpu.rs +++ b/alioth/src/hv/hvf/vcpu/vcpu.rs @@ -24,7 +24,7 @@ use std::sync::{Arc, mpsc}; use parking_lot::Mutex; use snafu::ResultExt; -use crate::arch::reg::{MpidrEl1, Reg, SReg}; +use crate::arch::reg::{MpidrEl1, Pstate, Reg, SReg}; use crate::hv::hvf::check_ret; use crate::hv::hvf::vm::{HvfVm, VcpuEvent}; use crate::hv::{Result, Vcpu, VmEntry, VmExit, error}; @@ -51,14 +51,10 @@ pub struct HvfVcpu { } impl HvfVcpu { - fn handle_event(&mut self, event: &VcpuEvent) -> Result<()> { - match event { - VcpuEvent::PowerOn { pc, context } => { - self.set_regs(&[(Reg::Pc, *pc), (Reg::X0, *context), (Reg::Pstate, 5)])?; - self.power_on = true; - } - VcpuEvent::PowerOff => self.power_on = false, - } + fn power_on(&mut self, pc: u64, context: u64) -> Result<()> { + let pstate = (Pstate::EL_H | Pstate::EL_BIT2).bits() as u64; + self.set_regs(&[(Reg::Pc, pc), (Reg::X0, context), (Reg::Pstate, pstate)])?; + self.power_on = true; Ok(()) } @@ -160,33 +156,30 @@ impl Vcpu for HvfVcpu { VmEntry::None => {} VmEntry::Mmio { data } => self.entry_mmio(data)?, VmEntry::Shutdown => return Ok(VmExit::Shutdown), - _ => unimplemented!("{entry:?}"), + VmEntry::Reboot => return Ok(VmExit::Reboot), } + if !self.power_on { let Ok(event) = self.receiver.recv() else { return Err(ErrorKind::BrokenPipe.into()).context(error::RunVcpu); }; - self.handle_event(&event)?; - if !self.power_on { - return Ok(VmExit::Shutdown); + match event { + VcpuEvent::Interrupt => return Ok(VmExit::Interrupted), + VcpuEvent::PowerOn { pc, context } => { + self.power_on(pc, context)?; + } } } loop { let ret = unsafe { hv_vcpu_run(self.vcpu_id) }; check_ret(ret).context(error::RunVcpu)?; - while let Ok(event) = self.receiver.try_recv() { - self.handle_event(&event)?; - if !self.power_on { - return Ok(VmExit::Shutdown); - } - } - let exit = unsafe { &*self.exit }; match exit.reason { HvExitReason::EXCEPTION => { self.handle_exception(&exit.exception)?; } + HvExitReason::CANCEL => break Ok(VmExit::Interrupted), _ => { break error::VmExit { msg: format!("{exit:x?}"), diff --git a/alioth/src/hv/hvf/vm.rs b/alioth/src/hv/hvf/vm.rs index 843c68c3..9c1f005a 100644 --- a/alioth/src/hv/hvf/vm.rs +++ b/alioth/src/hv/hvf/vm.rs @@ -251,7 +251,7 @@ impl Its for HvfIts { #[derive(Debug)] pub enum VcpuEvent { PowerOn { pc: u64, context: u64 }, - PowerOff, + Interrupt, } #[derive(Debug)] @@ -322,7 +322,7 @@ impl Vm for HvfVm { return Err(ErrorKind::NotFound.into()).context(error::StopVcpu); }; - if vcpu.sender.send(VcpuEvent::PowerOff).is_err() { + if vcpu.sender.send(VcpuEvent::Interrupt).is_err() { return Err(ErrorKind::BrokenPipe.into()).context(error::StopVcpu); }; let ret = unsafe { hv_vcpus_exit(&vcpu.vcpu_id, 1) }; From 4026a3154ce8024d543974d3503bd43c7e8787d5 Mon Sep 17 00:00:00 2001 From: Changyuan Lyu Date: Fri, 16 Jan 2026 15:54:19 -0800 Subject: [PATCH 3/3] feat(psci): implement CPU_OFF and AFFINITY_INFO Add support for the `CPU_OFF` and `AFFINITY_INFO` PSCI calls. This functionality is a prerequisite for `kexec` support in guests. Signed-off-by: Changyuan Lyu --- alioth/src/arch/aarch64/psci.rs | 24 ++++++++++++++++ alioth/src/hv/hv.rs | 22 ++++++++++++++- alioth/src/hv/hvf/vcpu/vcpu.rs | 30 +++++++++++--------- alioth/src/hv/hvf/vcpu/vmexit.rs | 47 +++++++++++++++++++++++++++++--- alioth/src/hv/hvf/vm.rs | 6 ++-- 5 files changed, 107 insertions(+), 22 deletions(-) diff --git a/alioth/src/arch/aarch64/psci.rs b/alioth/src/arch/aarch64/psci.rs index bb14478e..4b7b5064 100644 --- a/alioth/src/arch/aarch64/psci.rs +++ b/alioth/src/arch/aarch64/psci.rs @@ -58,6 +58,21 @@ c_enum! { pub const PSCI_VERSION_1_1: u32 = (1 << 16) | 1; +c_enum! { + pub struct PsciErr(i64); + { + NOT_SUPPORTED = -1; + INVALID_PARAMETERS = -2; + DENIED = -3; + ALREADY_ON = -4; + ON_PENDING = -5; + INTERNAL_FAILURE = -6; + NOT_PRESENT = -7; + DISABLED = -8; + INVALID_ADDRESS = -9; + } +} + c_enum! { /// https://developer.arm.com/documentation/den0022/latest/ pub struct PsciMigrateInfo(u32); @@ -67,3 +82,12 @@ c_enum! { NOT_REQUIRED = 2; } } + +c_enum! { + pub struct PsciAffinityInfo(u64); + { + ON_PENDING = 2; + OFF = 1; + ON = 0; + } +} diff --git a/alioth/src/hv/hv.rs b/alioth/src/hv/hv.rs index de5720f8..104ea1ff 100644 --- a/alioth/src/hv/hv.rs +++ b/alioth/src/hv/hv.rs @@ -30,7 +30,7 @@ use std::thread::JoinHandle; use serde::Deserialize; use serde_aco::Help; -use snafu::Snafu; +use snafu::{AsErrorSource, Snafu}; #[cfg(target_arch = "x86_64")] use crate::arch::cpuid::CpuidIn; @@ -100,11 +100,31 @@ pub enum Error { StopVcpu { error: std::io::Error }, #[snafu(display("Failed to handle VM exit: {msg}"))] VmExit { msg: String }, + #[snafu(display("Broken channel"))] + BrokenChannel, #[cfg(target_os = "linux")] #[snafu(display("KVM internal error"), context(false))] KvmErr { source: Box }, } +impl From for Error { + fn from(error: std::sync::mpsc::RecvError) -> Self { + let source = error.as_error_source(); + Error::BrokenChannel { + _location: snafu::GenerateImplicitData::generate_with_source(source), + } + } +} + +impl From> for Error { + fn from(error: std::sync::mpsc::SendError) -> Self { + let source = error.as_error_source(); + Error::BrokenChannel { + _location: snafu::GenerateImplicitData::generate_with_source(source), + } + } +} + pub type Result = std::result::Result; #[derive(Debug, Deserialize, Clone, Help)] diff --git a/alioth/src/hv/hvf/vcpu/vcpu.rs b/alioth/src/hv/hvf/vcpu/vcpu.rs index d4f19bde..2c36bf7b 100644 --- a/alioth/src/hv/hvf/vcpu/vcpu.rs +++ b/alioth/src/hv/hvf/vcpu/vcpu.rs @@ -16,8 +16,8 @@ mod vmentry; mod vmexit; use std::collections::HashMap; -use std::io::ErrorKind; use std::ptr::null_mut; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::{Receiver, Sender}; use std::sync::{Arc, mpsc}; @@ -36,6 +36,7 @@ use crate::sys::hvf::{ #[derive(Debug)] pub struct VcpuHandle { pub vcpu_id: u64, + pub power_on: Arc, pub sender: Sender, } @@ -45,16 +46,17 @@ pub struct HvfVcpu { vcpu_id: u64, vmexit: Option, exit_reg: Option, - vcpus: Arc>>, + vcpus: Arc>>>, receiver: Receiver, - power_on: bool, + power_on: Arc, } impl HvfVcpu { fn power_on(&mut self, pc: u64, context: u64) -> Result<()> { let pstate = (Pstate::EL_H | Pstate::EL_BIT2).bits() as u64; self.set_regs(&[(Reg::Pc, pc), (Reg::X0, context), (Reg::Pstate, pstate)])?; - self.power_on = true; + self.set_sregs(&[(SReg::SCTLR_EL1, 0x0)])?; + self.power_on.store(true, Ordering::Relaxed); Ok(()) } @@ -70,7 +72,12 @@ impl HvfVcpu { let (sender, receiver) = mpsc::channel(); - let handle = VcpuHandle { vcpu_id, sender }; + let power_on = Arc::new(AtomicBool::new(false)); + let handle = Arc::new(VcpuHandle { + vcpu_id, + sender, + power_on: power_on.clone(), + }); vm.vcpus.lock().insert(mpidr, handle); @@ -81,7 +88,7 @@ impl HvfVcpu { exit_reg: None, vcpus: vm.vcpus.clone(), receiver, - power_on: false, + power_on, }) } } @@ -143,8 +150,8 @@ impl Reg { impl Vcpu for HvfVcpu { fn reset(&mut self, is_bsp: bool) -> Result<()> { - self.power_on = is_bsp; - self.set_sregs(&[(SReg::SCTLR_EL1, 0)]) + self.power_on.store(is_bsp, Ordering::Relaxed); + self.set_sregs(&[(SReg::SCTLR_EL1, 0x0)]) } fn dump(&self) -> Result<()> { @@ -159,11 +166,8 @@ impl Vcpu for HvfVcpu { VmEntry::Reboot => return Ok(VmExit::Reboot), } - if !self.power_on { - let Ok(event) = self.receiver.recv() else { - return Err(ErrorKind::BrokenPipe.into()).context(error::RunVcpu); - }; - match event { + if !self.power_on.load(Ordering::Relaxed) { + match self.receiver.recv()? { VcpuEvent::Interrupt => return Ok(VmExit::Interrupted), VcpuEvent::PowerOn { pc, context } => { self.power_on(pc, context)?; diff --git a/alioth/src/hv/hvf/vcpu/vmexit.rs b/alioth/src/hv/hvf/vcpu/vmexit.rs index c892fb5c..3e39490a 100644 --- a/alioth/src/hv/hvf/vcpu/vmexit.rs +++ b/alioth/src/hv/hvf/vcpu/vmexit.rs @@ -12,9 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::sync::atomic::Ordering; + use snafu::ResultExt; -use crate::arch::psci::{PSCI_VERSION_1_1, PsciFunc, PsciMigrateInfo}; +use crate::arch::psci::{PSCI_VERSION_1_1, PsciAffinityInfo, PsciErr, PsciFunc, PsciMigrateInfo}; use crate::arch::reg::{EsrEl2DataAbort, EsrEl2Ec, EsrEl2SysReg, MpidrEl1, Reg, SReg, encode}; use crate::hv::hvf::check_ret; use crate::hv::hvf::vcpu::HvfVcpu; @@ -80,6 +82,9 @@ impl HvfVcpu { | PsciFunc::SYSTEM_OFF2_64 | PsciFunc::CPU_ON_32 | PsciFunc::CPU_ON_64 + | PsciFunc::CPU_OFF + | PsciFunc::AFFINITY_INFO_32 + | PsciFunc::AFFINITY_INFO_64 | PsciFunc::SYSTEM_RESET | PsciFunc::SYSTEM_RESET2_32 | PsciFunc::SYSTEM_RESET2_64 => 0, @@ -95,9 +100,7 @@ impl HvfVcpu { let pc = self.get_reg(Reg::X2)?; let context = self.get_reg(Reg::X3)?; if let Some(vcpu) = self.vcpus.lock().get(&MpidrEl1(mpidr)) { - vcpu.sender - .send(VcpuEvent::PowerOn { pc, context }) - .unwrap(); + vcpu.sender.send(VcpuEvent::PowerOn { pc, context })?; 0 } else { log::error!("Failed to find CPU with mpidr {mpidr:#x}"); @@ -108,6 +111,8 @@ impl HvfVcpu { self.vmexit = Some(VmExit::Reboot); return Ok(()); } + PsciFunc::AFFINITY_INFO_32 | PsciFunc::AFFINITY_INFO_64 => self.psci_affinity_info()?, + PsciFunc::CPU_OFF => self.psci_cpu_off()?, f => { return error::VmExit { msg: format!("HVC: {f:x?}"), @@ -146,4 +151,38 @@ impl HvfVcpu { } .fail() } + + fn psci_affinity_info(&mut self) -> Result { + let lowest_affinity_level = self.get_reg(Reg::X2)?; + if lowest_affinity_level != 0 { + // PSCI 1.0 and later no longer requires AFFINITY_INFO to + // support affinity levels greater than 0. + return Ok(PsciErr::INVALID_PARAMETERS.raw() as u64); + } + let target_affinity = self.get_reg(Reg::X1)?; + let vcpus = self.vcpus.lock(); + let Some(vcpu) = vcpus.get(&MpidrEl1(target_affinity)) else { + return Ok(PsciErr::INVALID_PARAMETERS.raw() as u64); + }; + let info = if vcpu.power_on.load(Ordering::Relaxed) { + PsciAffinityInfo::ON + } else { + PsciAffinityInfo::OFF + }; + Ok(info.raw()) + } + + fn psci_cpu_off(&mut self) -> Result { + self.power_on.store(false, Ordering::Relaxed); + match self.receiver.recv()? { + VcpuEvent::PowerOn { pc, context } => { + self.power_on(pc, context)?; + Ok(context) + } + VcpuEvent::Interrupt => { + self.vmexit = Some(VmExit::Interrupted); + Ok(0) + } + } + } } diff --git a/alioth/src/hv/hvf/vm.rs b/alioth/src/hv/hvf/vm.rs index 9c1f005a..9700fac2 100644 --- a/alioth/src/hv/hvf/vm.rs +++ b/alioth/src/hv/hvf/vm.rs @@ -257,7 +257,7 @@ pub enum VcpuEvent { #[derive(Debug)] pub struct HvfVm { gic_config: Mutex<(OsObject, bool)>, - pub vcpus: Arc>>, + pub vcpus: Arc>>>, } impl HvfVm { @@ -322,9 +322,7 @@ impl Vm for HvfVm { return Err(ErrorKind::NotFound.into()).context(error::StopVcpu); }; - if vcpu.sender.send(VcpuEvent::Interrupt).is_err() { - return Err(ErrorKind::BrokenPipe.into()).context(error::StopVcpu); - }; + vcpu.sender.send(VcpuEvent::Interrupt)?; let ret = unsafe { hv_vcpus_exit(&vcpu.vcpu_id, 1) }; check_ret(ret).context(error::StopVcpu) }