use applevisor_sys as av;
use crate::hvf::Vcpu;
#[derive(Default, Clone)]
pub struct PerVcpuState {
pub gp_regs: Vec<(u32, u64)>, pub simd_regs: Vec<(u32, u128)>, pub sys_regs: Vec<(u32, u64)>, pub icc_regs: Vec<(u32, u64)>, pub redist_regs: Vec<(u32, u64)>, pub vtimer_offset: u64,
pub ich_regs: Vec<(u32, u64)>,
}
impl PerVcpuState {
pub fn pc(&self) -> Option<u64> {
let pc_id = av::hv_reg_t::PC as u32;
self.gp_regs
.iter()
.find(|(id, _)| *id == pc_id)
.map(|(_, v)| *v)
}
}
fn gp_reg_enum() -> Vec<av::hv_reg_t> {
let mut out = Vec::with_capacity(37);
let x0 = av::hv_reg_t::X0 as u32;
for i in 0..=30u32 {
out.push(unsafe { std::mem::transmute::<u32, av::hv_reg_t>(x0 + i) });
}
out.push(av::hv_reg_t::FP);
out.push(av::hv_reg_t::LR);
out.push(av::hv_reg_t::PC);
out.push(av::hv_reg_t::CPSR);
out.push(av::hv_reg_t::FPCR);
out.push(av::hv_reg_t::FPSR);
out
}
fn simd_reg_enum() -> Vec<av::hv_simd_fp_reg_t> {
let q0 = av::hv_simd_fp_reg_t::Q0 as u32;
(0..32u32)
.map(|i| unsafe { std::mem::transmute::<u32, av::hv_simd_fp_reg_t>(q0 + i) })
.collect()
}
fn sys_reg_enum() -> Vec<av::hv_sys_reg_t> {
use av::hv_sys_reg_t::*;
vec![
MPIDR_EL1,
SCTLR_EL1,
CPACR_EL1,
TCR_EL1,
TTBR0_EL1,
TTBR1_EL1,
MAIR_EL1,
AMAIR_EL1,
VBAR_EL1,
CONTEXTIDR_EL1,
TPIDR_EL1,
SPSR_EL1,
ELR_EL1,
SP_EL0,
SP_EL1,
ESR_EL1,
FAR_EL1,
PAR_EL1,
TPIDR_EL0,
TPIDRRO_EL0,
CNTKCTL_EL1,
CSSELR_EL1,
MDSCR_EL1,
APIAKEYLO_EL1,
APIAKEYHI_EL1,
APIBKEYLO_EL1,
APIBKEYHI_EL1,
APDAKEYLO_EL1,
APDAKEYHI_EL1,
APDBKEYLO_EL1,
APDBKEYHI_EL1,
APGAKEYLO_EL1,
APGAKEYHI_EL1,
CNTV_CTL_EL0,
CNTV_CVAL_EL0,
CNTP_CTL_EL0,
CNTP_CVAL_EL0,
]
}
fn icc_reg_enum() -> Vec<av::hv_gic_icc_reg_t> {
use av::hv_gic_icc_reg_t::*;
vec![
PMR_EL1,
BPR0_EL1,
BPR1_EL1,
AP0R0_EL1,
AP1R0_EL1,
RPR_EL1,
CTLR_EL1,
SRE_EL1,
IGRPEN0_EL1,
IGRPEN1_EL1,
]
}
fn ich_reg_enum() -> Vec<av::hv_gic_ich_reg_t> {
use av::hv_gic_ich_reg_t::*;
vec![
AP0R0_EL2, AP1R0_EL2, HCR_EL2, VMCR_EL2, LR0_EL2, LR1_EL2, LR2_EL2, LR3_EL2, LR4_EL2,
LR5_EL2, LR6_EL2, LR7_EL2, LR8_EL2, LR9_EL2, LR10_EL2, LR11_EL2, LR12_EL2, LR13_EL2,
LR14_EL2, LR15_EL2,
]
}
fn redist_reg_offsets() -> Vec<u32> {
let mut v = Vec::with_capacity(11);
v.push(0x10080); v.push(0x10100); v.push(0x10C04); v.push(0x10200); v.push(0x10300); for n in 0..8u32 {
v.push(0x10400 + 4 * n); }
v
}
pub fn capture_vcpu_state(vcpu: &Vcpu) -> crate::hvf::Result<PerVcpuState> {
let mut gp_regs = Vec::with_capacity(37);
for r in gp_reg_enum() {
gp_regs.push((r as u32, vcpu.get_reg(r)?));
}
let mut simd_regs = Vec::with_capacity(32);
for r in simd_reg_enum() {
simd_regs.push((r as u32, vcpu.get_simd_fp_reg(r)?));
}
let mut sys_regs = Vec::new();
for r in sys_reg_enum() {
if let Ok(v) = vcpu.get_sys_reg(r) {
sys_regs.push((r as u32, v));
}
}
let mut icc_regs = Vec::new();
for r in icc_reg_enum() {
if let Ok(v) = vcpu.get_icc_reg(r) {
icc_regs.push((r as u32, v));
}
}
let mut redist_regs = Vec::new();
for off in redist_reg_offsets() {
let reg: av::hv_gic_redistributor_reg_t = unsafe { std::mem::transmute(off) };
if let Ok(v) = vcpu.get_redist_reg(reg) {
redist_regs.push((off, v));
}
}
let mut ich_regs = Vec::new();
for r in ich_reg_enum() {
if let Ok(v) = vcpu.get_ich_reg(r) {
ich_regs.push((r as u32, v));
}
}
let vtimer_offset = vcpu.get_vtimer_offset()?;
Ok(PerVcpuState {
gp_regs,
simd_regs,
sys_regs,
icc_regs,
redist_regs,
vtimer_offset,
ich_regs,
})
}
pub fn restore_vcpu_state(vcpu: &Vcpu, st: &PerVcpuState) -> crate::hvf::Result<()> {
use av::hv_sys_reg_t as S;
let critical = |id: u32| {
let r: S = unsafe { std::mem::transmute(id) };
matches!(
r,
S::SCTLR_EL1 | S::TCR_EL1 | S::TTBR0_EL1 | S::TTBR1_EL1 | S::MAIR_EL1 | S::VBAR_EL1
)
};
for (id, v) in &st.sys_regs {
let r: S = unsafe { std::mem::transmute(*id) };
if let Err(e) = vcpu.set_sys_reg(r, *v) {
if critical(*id) {
return Err(e);
}
}
}
use av::hv_gic_icc_reg_t as I;
let icc_find = |want: I| -> Option<u64> {
st.icc_regs.iter().find_map(|(id, v)| {
let r: I = unsafe { std::mem::transmute(*id) };
(r == want).then_some(*v)
})
};
if let Some(v) = icc_find(I::SRE_EL1) {
let _ = vcpu.set_icc_reg(I::SRE_EL1, v);
}
for (id, v) in &st.icc_regs {
let r: I = unsafe { std::mem::transmute(*id) };
match r {
I::SRE_EL1 | I::IGRPEN0_EL1 | I::IGRPEN1_EL1 => continue,
_ => {
let _ = vcpu.set_icc_reg(r, *v);
}
}
}
if let Some(v) = icc_find(I::IGRPEN0_EL1) {
let _ = vcpu.set_icc_reg(I::IGRPEN0_EL1, v);
}
if let Some(v) = icc_find(I::IGRPEN1_EL1) {
let _ = vcpu.set_icc_reg(I::IGRPEN1_EL1, v);
}
use av::hv_simd_fp_reg_t as Q;
for (id, v) in &st.simd_regs {
let r: Q = unsafe { std::mem::transmute(*id) };
vcpu.set_simd_fp_reg(r, *v)?;
}
let find_off = |off: u32| -> u64 {
st.redist_regs
.iter()
.find_map(|(o, v)| (*o == off).then_some(*v))
.unwrap_or(0)
};
let write_off = |off: u32, val: u64| -> crate::hvf::Result<()> {
let r: av::hv_gic_redistributor_reg_t = unsafe { std::mem::transmute(off) };
vcpu.set_redist_reg(r, val)
};
write_off(0x10080, find_off(0x10080))?; write_off(0x10C04, find_off(0x10C04))?; for n in 0..8u32 {
write_off(0x10400 + 4 * n, find_off(0x10400 + 4 * n))?;
}
write_off(0x10180, 0xFFFF_FFFF)?; write_off(0x10100, find_off(0x10100))?; write_off(0x10280, 0xFFFF_FFFF)?; write_off(0x10200, find_off(0x10200))?; write_off(0x10380, 0xFFFF_FFFF)?; write_off(0x10300, find_off(0x10300))?;
use av::hv_gic_ich_reg_t as H;
for (id, v) in &st.ich_regs {
let r: H = unsafe { std::mem::transmute(*id) };
let _ = vcpu.set_ich_reg(r, *v);
}
let _ = vcpu.set_vtimer_mask(false);
let cntv_ctl = st
.sys_regs
.iter()
.find_map(|(id, v)| {
let r: S = unsafe { std::mem::transmute(*id) };
(r == S::CNTV_CTL_EL0).then_some(*v)
})
.unwrap_or(0);
let enable = cntv_ctl & 1 != 0;
let imask = cntv_ctl & 2 != 0;
if enable && !imask {
vcpu.set_sys_reg(S::CNTV_CVAL_EL0, 0)?;
write_off(0x10200, 1u64 << 27)?;
}
use av::hv_reg_t as R;
for (id, v) in &st.gp_regs {
let r: R = unsafe { std::mem::transmute(*id) };
vcpu.set_reg(r, *v)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::PerVcpuState;
use crate::hypervisor::HypervisorVcpu;
#[test]
fn snapshot_state_seam_round_trips() {
let st = PerVcpuState {
gp_regs: vec![(0, 0x42), (31, 0xdead_beef)],
simd_regs: vec![(0, 0x1122_3344_5566_7788_99aa_bbcc_ddee_ff00u128)],
sys_regs: vec![(7, 0x1234), (9, 0)],
icc_regs: vec![(1, 0x5)],
redist_regs: vec![(0x10080, 0xff)],
vtimer_offset: 0xcafe_f00d,
ich_regs: vec![(2, 0x9)],
};
let mut buf = Vec::new();
<crate::hvf::Vcpu as HypervisorVcpu>::write_snapshot_state(&st, &mut buf).unwrap();
let mut cur = std::io::Cursor::new(&buf);
let back = <crate::hvf::Vcpu as HypervisorVcpu>::read_snapshot_state(&mut cur).unwrap();
assert_eq!(back.gp_regs, st.gp_regs);
assert_eq!(back.simd_regs, st.simd_regs);
assert_eq!(back.sys_regs, st.sys_regs);
assert_eq!(back.icc_regs, st.icc_regs);
assert_eq!(back.redist_regs, st.redist_regs);
assert_eq!(back.vtimer_offset, st.vtimer_offset);
assert_eq!(back.ich_regs, st.ich_regs);
assert_eq!(cur.position() as usize, buf.len(), "consumed exactly");
}
#[test]
fn read_snapshot_state_rejects_huge_count_without_oom() {
let mut bytes = Vec::new();
bytes.extend_from_slice(&0u64.to_le_bytes()); bytes.extend_from_slice(&u32::MAX.to_le_bytes()); let mut cur = std::io::Cursor::new(&bytes[..]);
let r = <crate::hvf::Vcpu as HypervisorVcpu>::read_snapshot_state(&mut cur);
assert!(r.is_err(), "truncated huge-count input must error, not OOM");
}
}