pub mod mshv;
mod nice;
mod vp_state;
cfg_if::cfg_if! {
if #[cfg(guest_arch = "x86_64")] {
mod hardware_cvm;
pub mod snp;
pub mod tdx;
use crate::TlbFlushLockAccess;
use crate::VtlCrash;
use bitvec::prelude::BitArray;
use bitvec::prelude::Lsb0;
use hvdef::HvX64RegisterName;
use virt::vp::MpState;
use virt::x86::MsrError;
use virt_support_apic::LocalApic;
use virt_support_x86emu::translate::TranslationRegisters;
use virt::vp::AccessVpState;
} else if #[cfg(guest_arch = "aarch64")] {
use hv1_hypercall::Arm64RegisterState;
use hvdef::HvArm64RegisterName;
} else {
compile_error!("unsupported guest architecture");
}
}
use super::Error;
use super::UhPartitionInner;
use super::UhVpInner;
use crate::GuestVtl;
use crate::WakeReason;
use hcl::ioctl::ProcessorRunner;
use hv1_emulator::message_queues::MessageQueues;
use hv1_hypercall::HvRepResult;
use hv1_structs::ProcessorSet;
use hv1_structs::VtlArray;
use hvdef::HvError;
use hvdef::HvMessage;
use hvdef::HvSynicSint;
use hvdef::NUM_SINTS;
use hvdef::Vtl;
use inspect::Inspect;
use inspect::InspectMut;
use pal::unix::affinity;
use pal::unix::affinity::CpuSet;
use pal_async::driver::Driver;
use pal_async::driver::PollImpl;
use pal_async::timer::PollTimer;
use pal_uring::IdleControl;
use private::BackingPrivate;
use std::convert::Infallible;
use std::future::poll_fn;
use std::marker::PhantomData;
use std::sync::Arc;
use std::sync::atomic::Ordering;
use std::task::Poll;
use std::time::Duration;
use virt::Processor;
use virt::StopVp;
use virt::VpHaltReason;
use virt::VpIndex;
use virt::io::CpuIo;
use vm_topology::processor::TargetVpInfo;
use vmcore::vmtime::VmTimeAccess;
#[derive(InspectMut)]
#[inspect(extra = "UhProcessor::inspect_extra", bound = "T: Backing")]
pub struct UhProcessor<'a, T: Backing> {
_not_send: PhantomData<*mut ()>,
#[inspect(flatten)]
inner: &'a UhVpInner,
#[inspect(skip)]
partition: &'a UhPartitionInner,
#[inspect(skip)]
idle_control: Option<&'a mut IdleControl>,
#[inspect(skip)]
kernel_returns: u64,
#[inspect(with = "|x| inspect::iter_by_index(x).map_value(inspect::AsHex)")]
crash_reg: [u64; hvdef::HV_X64_GUEST_CRASH_PARAMETER_MSRS],
#[inspect(with = "|x| inspect::AsHex(u64::from(*x))")]
crash_control: hvdef::GuestCrashCtl,
vmtime: VmTimeAccess,
#[inspect(skip)]
timer: PollImpl<dyn PollTimer>,
#[inspect(mut)]
force_exit_sidecar: bool,
vtls_tlb_locked: VtlsTlbLocked,
#[inspect(skip)]
shared: &'a T::Shared,
#[inspect(skip)]
runner: ProcessorRunner<'a, T::HclBacking<'a>>,
#[inspect(mut)]
backing: T,
}
#[derive(Inspect)]
struct VtlsTlbLocked {
vtl1: VtlArray<bool, 1>,
vtl2: VtlArray<bool, 2>,
}
impl VtlsTlbLocked {
fn get(&self, requesting_vtl: Vtl, target_vtl: GuestVtl) -> bool {
match requesting_vtl {
Vtl::Vtl0 => unreachable!(),
Vtl::Vtl1 => self.vtl1[target_vtl],
Vtl::Vtl2 => self.vtl2[target_vtl],
}
}
fn set(&mut self, requesting_vtl: Vtl, target_vtl: GuestVtl, value: bool) {
match requesting_vtl {
Vtl::Vtl0 => unreachable!(),
Vtl::Vtl1 => self.vtl1[target_vtl] = value,
Vtl::Vtl2 => self.vtl2[target_vtl] = value,
}
}
fn fill(&mut self, requesting_vtl: Vtl, value: bool) {
match requesting_vtl {
Vtl::Vtl0 => unreachable!(),
Vtl::Vtl1 => self.vtl1.fill(value),
Vtl::Vtl2 => self.vtl2.fill(value),
}
}
}
#[cfg(guest_arch = "x86_64")]
#[derive(Inspect)]
pub(crate) struct LapicState {
lapic: LocalApic,
activity: MpState,
nmi_pending: bool,
}
#[cfg(guest_arch = "x86_64")]
impl LapicState {
pub fn new(lapic: LocalApic, activity: MpState) -> Self {
Self {
lapic,
activity,
nmi_pending: false,
}
}
}
struct BackingParams<'a, 'b, T: Backing> {
partition: &'a UhPartitionInner,
vp_info: &'a TargetVpInfo,
runner: &'a mut ProcessorRunner<'b, T::HclBacking<'b>>,
}
mod private {
use super::BackingParams;
use super::UhRunVpError;
use super::vp_state;
use crate::BackingShared;
use crate::Error;
use crate::GuestVtl;
use crate::processor::UhProcessor;
use hv1_emulator::hv::ProcessorVtlHv;
use hv1_emulator::synic::ProcessorSynic;
use inspect::InspectMut;
use std::future::Future;
use virt::StopVp;
use virt::VpHaltReason;
use virt::io::CpuIo;
use virt::vp::AccessVpState;
#[expect(private_interfaces)]
pub trait BackingPrivate: 'static + Sized + InspectMut + Sized {
type HclBacking<'b>: hcl::ioctl::Backing<'b>;
type EmulationCache;
type Shared;
fn shared(shared: &BackingShared) -> &Self::Shared;
fn new(params: BackingParams<'_, '_, Self>, shared: &Self::Shared) -> Result<Self, Error>;
type StateAccess<'p, 'a>: AccessVpState<Error = vp_state::Error>
where
Self: 'a + 'p,
'p: 'a;
fn init(this: &mut UhProcessor<'_, Self>);
fn access_vp_state<'a, 'p>(
this: &'a mut UhProcessor<'p, Self>,
vtl: GuestVtl,
) -> Self::StateAccess<'p, 'a>;
fn run_vp(
this: &mut UhProcessor<'_, Self>,
dev: &impl CpuIo,
stop: &mut StopVp<'_>,
) -> impl Future<Output = Result<(), VpHaltReason<UhRunVpError>>>;
fn poll_apic(
this: &mut UhProcessor<'_, Self>,
vtl: GuestVtl,
scan_irr: bool,
) -> Result<(), UhRunVpError>;
fn request_extint_readiness(this: &mut UhProcessor<'_, Self>);
fn request_untrusted_sint_readiness(this: &mut UhProcessor<'_, Self>, sints: u16);
fn handle_cross_vtl_interrupts(
this: &mut UhProcessor<'_, Self>,
dev: &impl CpuIo,
) -> Result<bool, UhRunVpError>;
fn handle_vp_start_enable_vtl_wake(
_this: &mut UhProcessor<'_, Self>,
_vtl: GuestVtl,
) -> Result<(), UhRunVpError>;
fn inspect_extra(_this: &mut UhProcessor<'_, Self>, _resp: &mut inspect::Response<'_>) {}
fn hv(&self, vtl: GuestVtl) -> Option<&ProcessorVtlHv>;
fn hv_mut(&mut self, vtl: GuestVtl) -> Option<&mut ProcessorVtlHv>;
fn untrusted_synic(&self) -> Option<&ProcessorSynic>;
fn untrusted_synic_mut(&mut self) -> Option<&mut ProcessorSynic>;
fn vtl1_inspectable(this: &UhProcessor<'_, Self>) -> bool;
}
}
pub trait Backing: BackingPrivate {}
impl<T: BackingPrivate> Backing for T {}
pub(crate) struct BackingSharedParams {
pub cvm_state: Option<crate::UhCvmPartitionState>,
pub guest_vsm_available: bool,
}
#[cfg(guest_arch = "x86_64")]
trait HardwareIsolatedBacking: Backing {
fn cvm_state(&self) -> &crate::UhCvmVpState;
fn cvm_state_mut(&mut self) -> &mut crate::UhCvmVpState;
fn cvm_partition_state(shared: &Self::Shared) -> &crate::UhCvmPartitionState;
fn tlb_flush_lock_access<'a>(
vp_index: VpIndex,
partition: &'a UhPartitionInner,
shared: &'a Self::Shared,
) -> impl TlbFlushLockAccess + 'a;
fn switch_vtl(this: &mut UhProcessor<'_, Self>, source_vtl: GuestVtl, target_vtl: GuestVtl);
fn translation_registers(
&self,
this: &UhProcessor<'_, Self>,
vtl: GuestVtl,
) -> TranslationRegisters;
}
#[cfg_attr(guest_arch = "aarch64", expect(dead_code))]
#[derive(Inspect, Debug)]
#[inspect(tag = "reason")]
pub(crate) enum SidecarExitReason {
#[inspect(transparent)]
Exit(SidecarRemoveExit),
#[inspect(transparent)]
TaskRequest(Arc<str>),
ManualRequest,
}
#[cfg_attr(guest_arch = "aarch64", expect(dead_code))]
#[derive(Inspect, Debug)]
#[inspect(tag = "exit")]
pub(crate) enum SidecarRemoveExit {
Msr {
#[inspect(hex)]
msr: u32,
value: Option<u64>,
},
Io {
#[inspect(hex)]
port: u16,
write: bool,
},
Mmio {
#[inspect(hex)]
gpa: u64,
write: bool,
},
Hypercall {
#[inspect(debug)]
code: hvdef::HypercallCode,
},
Cpuid {
#[inspect(hex)]
leaf: u32,
#[inspect(hex)]
subleaf: u32,
},
Hypervisor {
#[inspect(debug)]
message: hvdef::HvMessageType,
},
}
impl UhVpInner {
pub fn new(cpu_index: u32, vp_info: TargetVpInfo) -> Self {
Self {
wake_reasons: Default::default(),
message_queues: VtlArray::from_fn(|_| MessageQueues::new()),
waker: Default::default(),
cpu_index,
vp_info,
sidecar_exit_reason: Default::default(),
}
}
pub fn post_message(&self, vtl: GuestVtl, sint: u8, message: &HvMessage) {
if self.message_queues[vtl].enqueue_message(sint, message) {
self.wake(vtl, WakeReason::MESSAGE_QUEUES);
}
}
pub fn wake(&self, vtl: GuestVtl, reason: WakeReason) {
let reason = u64::from(reason.0) << (vtl as u8 * 32);
if self.wake_reasons.fetch_or(reason, Ordering::Release) & reason == 0 {
if let Some(waker) = &*self.waker.read() {
waker.wake_by_ref();
}
}
}
pub fn wake_vtl2(&self) {
if let Some(waker) = &*self.waker.read() {
waker.wake_by_ref();
}
}
pub fn set_sidecar_exit_reason(&self, reason: SidecarExitReason) {
self.sidecar_exit_reason.lock().get_or_insert_with(|| {
tracing::info!(?reason, "sidecar exit");
reason
});
}
}
#[derive(Debug, Error)]
pub enum UhRunVpError {
#[error("failed to run")]
Run(#[source] hcl::ioctl::Error),
#[error("sidecar run error")]
Sidecar(#[source] sidecar_client::SidecarError),
#[error("failed to access state for emulation")]
EmulationState(#[source] hcl::ioctl::Error),
#[error("failed to translate GVA")]
TranslateGva(#[source] hcl::ioctl::Error),
#[error("failed VTL access check")]
VtlAccess(#[source] hcl::ioctl::Error),
#[error("failed to advance rip")]
AdvanceRip(#[source] hcl::ioctl::Error),
#[error("failed to set pending event")]
Event(#[source] hcl::ioctl::Error),
#[error("guest accessed unaccepted gpa {0}")]
UnacceptedMemoryAccess(u64),
#[error("state access error")]
State(#[source] vp_state::Error),
#[error("invalid vmcb")]
InvalidVmcb,
#[error("unknown exit {0:#x?}")]
UnknownVmxExit(x86defs::vmx::VmxExit),
#[error("bad guest state on VP.ENTER")]
VmxBadGuestState,
#[error("failed to access VP assist page")]
VpAssistPage(#[source] guestmem::GuestMemoryError),
#[error("failed to read hypercall parameters")]
HypercallParameters(#[source] guestmem::GuestMemoryError),
#[error("failed to write hypercall result")]
HypercallResult(#[source] guestmem::GuestMemoryError),
#[error("unexpected debug exception with dr6 value {0:#x}")]
UnexpectedDebugException(u64),
#[error("invalid intercepted vtl {0:?}")]
InvalidInterceptedVtl(u8),
}
#[derive(Debug, Error)]
pub enum ProcessorError {
#[error("hcl error")]
Ioctl(#[from] hcl::ioctl::Error),
#[error("state access error")]
State(#[from] vp_state::Error),
#[error("operation not supported")]
NotSupported,
}
fn duration_from_100ns(n: u64) -> Duration {
const NUM_100NS_IN_SEC: u64 = 10 * 1000 * 1000;
Duration::new(n / NUM_100NS_IN_SEC, (n % NUM_100NS_IN_SEC) as u32 * 100)
}
impl<T: Backing> UhProcessor<'_, T> {
fn inspect_extra(&mut self, resp: &mut inspect::Response<'_>) {
resp.child("stats", |req| {
if let Ok(stats) = hcl::stats::vp_stats() {
let stats = &stats[self.vp_index().index() as usize];
req.respond()
.counter("vtl_transitions", stats.vtl_transitions)
.counter(
"spurious_exits",
stats.vtl_transitions.saturating_sub(self.kernel_returns),
);
}
})
.field(
"last_enter_modes",
self.runner
.enter_mode()
.map(|&mut v| inspect::AsHex(u8::from(v))),
)
.field("sidecar", self.runner.is_sidecar())
.field(
"sidecar_base_cpu",
self.partition.hcl.sidecar_base_cpu(self.vp_index().index()),
);
T::inspect_extra(self, resp);
}
fn update_synic(&mut self, vtl: GuestVtl, untrusted_synic: bool) {
loop {
let hv = self.backing.hv_mut(vtl).unwrap();
let ref_time_now = hv.ref_time_now();
let synic = if untrusted_synic {
debug_assert_eq!(vtl, GuestVtl::Vtl0);
self.backing.untrusted_synic_mut().unwrap()
} else {
&mut hv.synic
};
let (ready_sints, next_ref_time) = synic.scan(
ref_time_now,
&self.partition.gm[vtl],
&mut self
.partition
.synic_interrupt(self.inner.vp_info.base.vp_index, vtl),
);
if let Some(next_ref_time) = next_ref_time {
let ref_diff = next_ref_time.saturating_sub(ref_time_now);
let timeout = self
.vmtime
.now()
.wrapping_add(duration_from_100ns(ref_diff));
self.vmtime.set_timeout_if_before(timeout);
}
if ready_sints == 0 {
break;
}
self.deliver_synic_messages(vtl, ready_sints);
}
}
#[cfg(guest_arch = "x86_64")]
fn handle_debug_exception(&mut self, vtl: GuestVtl) -> Result<(), VpHaltReason<UhRunVpError>> {
if vtl == GuestVtl::Vtl0 {
let debug_regs: virt::x86::vp::DebugRegisters = self
.access_state(Vtl::Vtl0)
.debug_regs()
.expect("register query should not fail");
let dr = [
debug_regs.dr0,
debug_regs.dr1,
debug_regs.dr2,
debug_regs.dr3,
];
if debug_regs.dr6 & x86defs::DR6_SINGLE_STEP != 0 {
return Err(VpHaltReason::SingleStep);
}
const BREAKPOINT_INDEX_OFFSET: usize = 4;
let i = debug_regs.dr6.trailing_zeros() as usize;
if i >= BREAKPOINT_INDEX_OFFSET {
return Err(VpHaltReason::InvalidVmState(
UhRunVpError::UnexpectedDebugException(debug_regs.dr6),
));
}
let bp = virt::x86::HardwareBreakpoint::from_dr7(debug_regs.dr7, dr[i], i);
return Err(VpHaltReason::HwBreak(bp));
}
panic!("unexpected debug exception in VTL {:?}", vtl);
}
}
impl<'p, T: Backing> Processor for UhProcessor<'p, T> {
type Error = ProcessorError;
type RunVpError = UhRunVpError;
type StateAccess<'a>
= T::StateAccess<'p, 'a>
where
Self: 'a;
#[cfg(guest_arch = "aarch64")]
fn set_debug_state(
&mut self,
_vtl: Vtl,
_state: Option<&virt::x86::DebugState>,
) -> Result<(), Self::Error> {
Err(ProcessorError::NotSupported)
}
#[cfg(guest_arch = "x86_64")]
fn set_debug_state(
&mut self,
vtl: Vtl,
state: Option<&virt::x86::DebugState>,
) -> Result<(), Self::Error> {
if vtl == Vtl::Vtl0 {
let mut db: [u64; 4] = [0; 4];
let mut rflags =
x86defs::RFlags::from(self.access_state(Vtl::Vtl0).registers().unwrap().rflags);
let mut dr7: u64 = 0;
if let Some(state) = state {
rflags.set_trap(state.single_step);
for (i, bp) in state.breakpoints.iter().enumerate() {
if let Some(bp) = bp {
db[i] = bp.address;
dr7 |= bp.dr7_bits(i);
}
}
}
let debug_registers = virt::x86::vp::DebugRegisters {
dr0: db[0],
dr1: db[1],
dr2: db[2],
dr3: db[3],
dr6: 0,
dr7,
};
let mut access_state = self.access_state(vtl);
access_state.set_debug_regs(&debug_registers)?;
let registers = {
let mut registers = access_state.registers().unwrap();
registers.rflags = rflags.into();
registers
};
access_state.set_registers(®isters)?;
return Ok(());
}
panic!("unexpected set debug state in VTL {:?}", vtl);
}
async fn run_vp(
&mut self,
mut stop: StopVp<'_>,
dev: &impl CpuIo,
) -> Result<Infallible, VpHaltReason<UhRunVpError>> {
if self.runner.is_sidecar() {
if self.force_exit_sidecar {
self.inner
.set_sidecar_exit_reason(SidecarExitReason::ManualRequest);
return Err(VpHaltReason::Cancel);
}
} else {
let mut current = Default::default();
affinity::get_current_thread_affinity(&mut current).unwrap();
assert_eq!(¤t, CpuSet::new().set(self.inner.cpu_index));
nice::nice(1);
}
let mut last_waker = None;
let vtl0_wakes = WakeReason::new()
.with_message_queues(true)
.with_intcon(true);
let vtl1_wakes = WakeReason::new().with_message_queues(true);
self.inner.wake_reasons.fetch_or(
((vtl1_wakes.0 as u64) << 32) | (vtl0_wakes.0 as u64),
Ordering::Relaxed,
);
let mut first_scan_irr = true;
loop {
poll_fn(|cx| {
loop {
stop.check()?;
self.runner.clear_cancel();
self.vmtime.cancel_timeout();
if !last_waker
.as_ref()
.is_some_and(|waker| cx.waker().will_wake(waker))
{
last_waker = Some(cx.waker().clone());
self.inner.waker.write().clone_from(&last_waker);
}
let scan_irr = if self.inner.wake_reasons.load(Ordering::Relaxed) != 0 {
self.handle_wake().map_err(VpHaltReason::Hypervisor)?
} else {
[false, false].into()
};
if self.backing.untrusted_synic().is_some() {
self.update_synic(GuestVtl::Vtl0, true);
}
for vtl in [GuestVtl::Vtl1, GuestVtl::Vtl0] {
if self.backing.hv(vtl).is_some() {
self.update_synic(vtl, false);
}
T::poll_apic(self, vtl, scan_irr[vtl] || first_scan_irr)
.map_err(VpHaltReason::Hypervisor)?;
}
first_scan_irr = false;
if T::handle_cross_vtl_interrupts(self, dev)
.map_err(VpHaltReason::InvalidVmState)?
{
continue;
}
if let Some(timeout) = self.vmtime.get_timeout() {
let deadline = self.vmtime.host_time(timeout);
if self.timer.poll_timer(cx, deadline).is_ready() {
continue;
}
}
return <Result<_, VpHaltReason<_>>>::Ok(()).into();
}
})
.await?;
if let Some(idle_control) = &mut self.idle_control {
if !idle_control.pre_block() {
yield_now().await;
continue;
}
}
if let Some(mode) = self.runner.enter_mode() {
*mode = self
.partition
.enter_modes_atomic
.load(Ordering::Relaxed)
.into();
}
T::run_vp(self, dev, &mut stop).await?;
self.kernel_returns += 1;
}
}
fn flush_async_requests(&mut self) -> Result<(), Self::RunVpError> {
if self.inner.wake_reasons.load(Ordering::Relaxed) != 0 {
let scan_irr = self.handle_wake()?;
for vtl in [GuestVtl::Vtl1, GuestVtl::Vtl0] {
if scan_irr[vtl] {
T::poll_apic(self, vtl, true)?;
}
}
}
self.runner.flush_deferred_state();
Ok(())
}
fn access_state(&mut self, vtl: Vtl) -> Self::StateAccess<'_> {
T::access_vp_state(self, vtl.try_into().unwrap())
}
fn vtl_inspectable(&self, vtl: Vtl) -> bool {
match vtl {
Vtl::Vtl0 => true,
Vtl::Vtl1 => T::vtl1_inspectable(self),
Vtl::Vtl2 => false,
}
}
}
impl<'a, T: Backing> UhProcessor<'a, T> {
pub(super) fn new(
driver: &impl Driver,
partition: &'a UhPartitionInner,
vp_info: TargetVpInfo,
idle_control: Option<&'a mut IdleControl>,
) -> Result<Self, Error> {
let inner = partition.vp(vp_info.base.vp_index).unwrap();
let mut runner = partition
.hcl
.runner(inner.vp_index().index(), idle_control.is_none())
.unwrap();
let backing_shared = T::shared(&partition.backing_shared);
let backing = T::new(
BackingParams {
partition,
vp_info: &vp_info,
runner: &mut runner,
},
backing_shared,
)?;
let mut vp = Self {
partition,
inner,
runner,
idle_control,
kernel_returns: 0,
crash_reg: [0; hvdef::HV_X64_GUEST_CRASH_PARAMETER_MSRS],
crash_control: hvdef::GuestCrashCtl::new()
.with_crash_notify(true)
.with_crash_message(true),
_not_send: PhantomData,
backing,
shared: backing_shared,
vmtime: partition
.vmtime
.access(format!("vp-{}", vp_info.base.vp_index.index())),
timer: driver.new_dyn_timer(),
force_exit_sidecar: false,
vtls_tlb_locked: VtlsTlbLocked {
vtl1: VtlArray::new(false),
vtl2: VtlArray::new(false),
},
};
T::init(&mut vp);
Ok(vp)
}
fn handle_wake(&mut self) -> Result<VtlArray<bool, 2>, UhRunVpError> {
let wake_reasons_raw = self.inner.wake_reasons.swap(0, Ordering::SeqCst);
let wake_reasons_vtl: [WakeReason; 2] = zerocopy::transmute!(wake_reasons_raw);
for (vtl, wake_reasons) in [
(GuestVtl::Vtl1, wake_reasons_vtl[1]),
(GuestVtl::Vtl0, wake_reasons_vtl[0]),
] {
if wake_reasons.message_queues() {
let pending_sints = self.inner.message_queues[vtl].pending_sints();
if pending_sints != 0 {
let pending_sints = self.inner.message_queues[vtl].pending_sints();
let mut masked_sints = 0;
for sint in 0..NUM_SINTS as u8 {
if pending_sints & (1 << sint) == 0 {
continue;
}
let sint_msr = if let Some(hv) = self.backing.hv(vtl).as_ref() {
hv.synic.sint(sint)
} else {
#[cfg(guest_arch = "x86_64")]
let sint_reg =
HvX64RegisterName(HvX64RegisterName::Sint0.0 + sint as u32);
#[cfg(guest_arch = "aarch64")]
let sint_reg =
HvArm64RegisterName(HvArm64RegisterName::Sint0.0 + sint as u32);
self.runner.get_vp_register(vtl, sint_reg).unwrap().as_u64()
};
masked_sints |= (HvSynicSint::from(sint_msr).masked() as u16) << sint;
}
self.inner.message_queues[vtl].post_pending_messages(masked_sints, |_, _| {
Err(HvError::InvalidSynicState)
});
self.request_sint_notifications(vtl, pending_sints & !masked_sints);
}
}
if wake_reasons.extint() {
T::request_extint_readiness(self);
}
#[cfg(guest_arch = "x86_64")]
if wake_reasons.hv_start_enable_vtl_vp() {
T::handle_vp_start_enable_vtl_wake(self, vtl)?;
}
#[cfg(guest_arch = "x86_64")]
if wake_reasons.update_proxy_irr_filter() {
debug_assert!(self.partition.isolation.is_hardware_isolated());
self.update_proxy_irr_filter(vtl);
}
}
Ok(wake_reasons_vtl.map(|w| w.intcon()).into())
}
fn request_sint_notifications(&mut self, vtl: GuestVtl, sints: u16) {
if sints == 0 {
return;
}
let untrusted_sints = if let Some(hv) = self.backing.hv_mut(vtl).as_mut() {
let proxied_sints = hv.synic.proxied_sints();
hv.synic.request_sint_readiness(sints & !proxied_sints);
proxied_sints
} else {
!0
};
if sints & untrusted_sints != 0 {
assert_eq!(vtl, GuestVtl::Vtl0);
T::request_untrusted_sint_readiness(self, sints & untrusted_sints);
}
}
fn vp_index(&self) -> VpIndex {
self.inner.vp_index()
}
#[cfg(guest_arch = "x86_64")]
fn write_msr(&mut self, msr: u32, value: u64, vtl: GuestVtl) -> Result<(), MsrError> {
match msr {
hvdef::HV_X64_MSR_GUEST_CRASH_CTL => {
self.crash_control = hvdef::GuestCrashCtl::from(value);
let crash = VtlCrash {
vp_index: self.vp_index(),
last_vtl: vtl,
control: self.crash_control,
parameters: self.crash_reg,
};
tracelimit::info_ratelimited!(?crash, "Guest has reported system crash");
self.partition.crash_notification_send.send(crash);
}
hvdef::HV_X64_MSR_GUEST_CRASH_P0
| hvdef::HV_X64_MSR_GUEST_CRASH_P1
| hvdef::HV_X64_MSR_GUEST_CRASH_P2
| hvdef::HV_X64_MSR_GUEST_CRASH_P3
| hvdef::HV_X64_MSR_GUEST_CRASH_P4 => {
self.crash_reg[(msr - hvdef::HV_X64_MSR_GUEST_CRASH_P0) as usize] = value;
}
_ => return Err(MsrError::Unknown),
}
Ok(())
}
#[cfg(guest_arch = "x86_64")]
fn read_msr(&mut self, msr: u32, vtl: GuestVtl) -> Result<u64, MsrError> {
if msr & 0xf0000000 == 0x40000000 {
if let Some(hv) = self.backing.hv(vtl).as_ref() {
let r = hv.msr_read(msr);
if !matches!(r, Err(MsrError::Unknown)) {
return r;
}
}
}
let v = match msr {
hvdef::HV_X64_MSR_GUEST_CRASH_CTL => self.crash_control.into(),
hvdef::HV_X64_MSR_GUEST_CRASH_P0 => self.crash_reg[0],
hvdef::HV_X64_MSR_GUEST_CRASH_P1 => self.crash_reg[1],
hvdef::HV_X64_MSR_GUEST_CRASH_P2 => self.crash_reg[2],
hvdef::HV_X64_MSR_GUEST_CRASH_P3 => self.crash_reg[3],
hvdef::HV_X64_MSR_GUEST_CRASH_P4 => self.crash_reg[4],
_ => return Err(MsrError::Unknown),
};
Ok(v)
}
#[cfg(guest_arch = "x86_64")]
async fn emulate<D: CpuIo>(
&mut self,
devices: &D,
interruption_pending: bool,
vtl: GuestVtl,
cache: T::EmulationCache,
) -> Result<(), VpHaltReason<UhRunVpError>>
where
for<'b> UhEmulationState<'b, 'a, D, T>:
virt_support_x86emu::emulate::EmulatorSupport<Error = UhRunVpError>,
{
let guest_memory = &self.partition.gm[vtl];
let mut emulation_state = UhEmulationState {
vp: &mut *self,
interruption_pending,
devices,
vtl,
cache,
};
virt_support_x86emu::emulate::emulate(&mut emulation_state, guest_memory, devices).await
}
#[cfg(guest_arch = "aarch64")]
async fn emulate<D: CpuIo>(
&mut self,
devices: &D,
intercept_state: &aarch64emu::InterceptState,
vtl: GuestVtl,
cache: T::EmulationCache,
) -> Result<(), VpHaltReason<UhRunVpError>>
where
for<'b> UhEmulationState<'b, 'a, D, T>:
virt_support_aarch64emu::emulate::EmulatorSupport<Error = UhRunVpError>,
{
let guest_memory = &self.partition.gm[vtl];
virt_support_aarch64emu::emulate::emulate(
&mut UhEmulationState {
vp: &mut *self,
interruption_pending: intercept_state.interruption_pending,
devices,
vtl,
cache,
},
intercept_state,
guest_memory,
devices,
)
.await
}
fn deliver_synic_messages(&mut self, vtl: GuestVtl, sints: u16) {
let proxied_sints = self
.backing
.hv(vtl)
.as_ref()
.map_or(!0, |hv| hv.synic.proxied_sints());
let pending_sints =
self.inner.message_queues[vtl].post_pending_messages(sints, |sint, message| {
if proxied_sints & (1 << sint) != 0 {
if let Some(synic) = self.backing.untrusted_synic_mut().as_mut() {
synic.post_message(
&self.partition.gm[vtl],
sint,
message,
&mut self
.partition
.synic_interrupt(self.inner.vp_info.base.vp_index, vtl),
)
} else {
self.partition.hcl.post_message_direct(
self.inner.vp_info.base.vp_index.index(),
sint,
message,
)
}
} else {
self.backing
.hv_mut(vtl)
.as_mut()
.unwrap()
.synic
.post_message(
&self.partition.gm[vtl],
sint,
message,
&mut self
.partition
.synic_interrupt(self.inner.vp_info.base.vp_index, vtl),
)
}
});
self.request_sint_notifications(vtl, pending_sints);
}
#[cfg(guest_arch = "x86_64")]
fn update_proxy_irr_filter(&mut self, vtl: GuestVtl) {
assert_eq!(vtl, GuestVtl::Vtl0);
let mut irr_bits: BitArray<[u32; 8], Lsb0> = BitArray::new(Default::default());
if let Some(hv) = self.backing.hv(vtl).as_ref() {
for sint in 0..NUM_SINTS as u8 {
let sint_msr = hv.synic.sint(sint);
let hv_sint = HvSynicSint::from(sint_msr);
if hv_sint.proxy() && !hv_sint.masked() {
irr_bits.set(hv_sint.vector() as usize, true);
}
}
}
self.partition.fill_device_vectors(vtl, &mut irr_bits);
self.runner
.update_proxy_irr_filter_vtl0(&irr_bits.into_inner());
}
}
fn signal_mnf(dev: &impl CpuIo, connection_id: u32) {
if let Err(err) = dev.signal_synic_event(Vtl::Vtl0, connection_id, 0) {
tracelimit::warn_ratelimited!(
error = &err as &dyn std::error::Error,
connection_id,
"failed to signal mnf"
);
}
}
async fn yield_now() {
let mut yielded = false;
poll_fn(|cx| {
if !yielded {
cx.waker().wake_by_ref();
yielded = true;
Poll::Pending
} else {
Poll::Ready(())
}
})
.await;
}
struct UhEmulationState<'a, 'b, T: CpuIo, U: Backing> {
vp: &'a mut UhProcessor<'b, U>,
interruption_pending: bool,
devices: &'a T,
vtl: GuestVtl,
cache: U::EmulationCache,
}
struct UhHypercallHandler<'a, 'b, T, B: Backing> {
vp: &'a mut UhProcessor<'b, B>,
bus: &'a T,
trusted: bool,
intercepted_vtl: GuestVtl,
}
impl<T, B: Backing> UhHypercallHandler<'_, '_, T, B> {
fn target_vtl_no_higher(&self, target_vtl: Vtl) -> Result<GuestVtl, HvError> {
if Vtl::from(self.intercepted_vtl) < target_vtl {
return Err(HvError::AccessDenied);
}
Ok(target_vtl.try_into().unwrap())
}
}
impl<T, B: Backing> hv1_hypercall::GetVpIndexFromApicId for UhHypercallHandler<'_, '_, T, B> {
fn get_vp_index_from_apic_id(
&mut self,
partition_id: u64,
target_vtl: Vtl,
apic_ids: &[u32],
vp_indices: &mut [u32],
) -> HvRepResult {
tracing::debug!(partition_id, ?target_vtl, "HvGetVpIndexFromApicId");
if partition_id != hvdef::HV_PARTITION_ID_SELF {
return Err((HvError::InvalidPartitionId, 0));
}
let _target_vtl = self.target_vtl_no_higher(target_vtl).map_err(|e| (e, 0))?;
#[cfg(guest_arch = "aarch64")]
if true {
let _ = apic_ids;
let _ = vp_indices;
todo!("AARCH64_TODO");
}
#[cfg(guest_arch = "x86_64")]
for (i, (&apic_id, vp_index)) in apic_ids.iter().zip(vp_indices).enumerate() {
*vp_index = self
.vp
.partition
.vps
.iter()
.find(|vp| vp.vp_info.apic_id == apic_id)
.ok_or((HvError::InvalidParameter, i))?
.vp_info
.base
.vp_index
.index()
}
Ok(())
}
}
#[cfg(guest_arch = "aarch64")]
impl<T: CpuIo, B: Backing> Arm64RegisterState for UhHypercallHandler<'_, '_, T, B> {
fn pc(&mut self) -> u64 {
self.vp
.runner
.get_vp_register(self.intercepted_vtl, HvArm64RegisterName::XPc)
.expect("get vp register cannot fail")
.as_u64()
}
fn set_pc(&mut self, pc: u64) {
self.vp
.runner
.set_vp_register(self.intercepted_vtl, HvArm64RegisterName::XPc, pc.into())
.expect("set vp register cannot fail");
}
fn x(&mut self, n: u8) -> u64 {
self.vp
.runner
.get_vp_register(
self.intercepted_vtl,
HvArm64RegisterName(HvArm64RegisterName::X0.0 + n as u32),
)
.expect("get vp register cannot fail")
.as_u64()
}
fn set_x(&mut self, n: u8, v: u64) {
self.vp
.runner
.set_vp_register(
self.intercepted_vtl,
HvArm64RegisterName(HvArm64RegisterName::X0.0 + n as u32),
v.into(),
)
.expect("set vp register cannot fail")
}
}
impl<T: CpuIo, B: Backing> hv1_hypercall::PostMessage for UhHypercallHandler<'_, '_, T, B> {
fn post_message(&mut self, connection_id: u32, message: &[u8]) -> hvdef::HvResult<()> {
tracing::trace!(
connection_id,
self.trusted,
"handling post message intercept"
);
self.bus.post_synic_message(
self.intercepted_vtl.into(),
connection_id,
self.trusted,
message,
)
}
}
impl<T: CpuIo, B: Backing> hv1_hypercall::SignalEvent for UhHypercallHandler<'_, '_, T, B> {
fn signal_event(&mut self, connection_id: u32, flag: u16) -> hvdef::HvResult<()> {
tracing::trace!(connection_id, "handling signal event intercept");
self.bus
.signal_synic_event(self.intercepted_vtl.into(), connection_id, flag)
}
}
impl<T: CpuIo, B: Backing> UhHypercallHandler<'_, '_, T, B> {
fn retarget_virtual_interrupt(
&mut self,
device_id: u64,
address: u64,
data: u32,
vector: u32,
multicast: bool,
target_processors: ProcessorSet<'_>,
) -> hvdef::HvResult<()> {
let target_processors = Vec::from_iter(target_processors);
let vpci_params = vmcore::vpci_msi::VpciInterruptParameters {
vector,
multicast,
target_processors: &target_processors,
};
self.vp
.partition
.software_devices
.as_ref()
.expect("should exist if this intercept is registered or this is a CVM")
.retarget_interrupt(device_id, address, data, &vpci_params)
}
}
impl<T, B: Backing> hv1_hypercall::ExtendedQueryCapabilities for UhHypercallHandler<'_, '_, T, B> {
fn query_extended_capabilities(&mut self) -> hvdef::HvResult<u64> {
Err(HvError::InvalidHypercallCode)
}
}