Skip to main content

virt_mshv/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Linux /dev/mshv implementation of the virt::generic interfaces.
5
6#![cfg(all(target_os = "linux", guest_is_native))]
7// UNSAFETY: Calling HV APIs and manually managing memory.
8#![expect(unsafe_code)]
9
10#[cfg(guest_arch = "aarch64")]
11mod aarch64;
12#[cfg(guest_arch = "x86_64")]
13mod x86_64;
14
15#[cfg(guest_arch = "aarch64")]
16use aarch64 as arch;
17#[cfg(guest_arch = "x86_64")]
18use x86_64 as arch;
19
20// irqfd is arch-independent (MSI routing + MSHV_IRQFD), wired up on both
21// x86_64 and aarch64.
22pub mod irqfd;
23
24use guestmem::DoorbellRegistration;
25use guestmem::GuestMemory;
26use hv1_emulator::message_queues::MessageQueues;
27use hvdef::HV_PAGE_SHIFT;
28use hvdef::HvDeliverabilityNotificationsRegister;
29use hvdef::HvError;
30use hvdef::HvMessage;
31use hvdef::HvMessageType;
32use hvdef::HvPartitionPropertyCode;
33use hvdef::Vtl;
34use hvdef::hypercall::HV_INTERCEPT_ACCESS_MASK_EXECUTE;
35use hvdef::hypercall::HvRegisterAssoc;
36use inspect::Inspect;
37use inspect::InspectMut;
38use mshv_bindings::MSHV_SET_MEM_BIT_EXECUTABLE;
39use mshv_bindings::MSHV_SET_MEM_BIT_WRITABLE;
40use mshv_bindings::mshv_install_intercept;
41use mshv_bindings::mshv_user_mem_region;
42use mshv_ioctls::Mshv;
43use mshv_ioctls::MshvError;
44use mshv_ioctls::VcpuFd;
45use mshv_ioctls::VmFd;
46use mshv_ioctls::set_bits;
47use pal::unix::pthread::*;
48use pal_event::Event;
49use parking_lot::Mutex;
50use parking_lot::RwLock;
51use std::convert::Infallible;
52use std::future::poll_fn;
53use std::io;
54use std::os::fd::AsFd;
55use std::os::fd::AsRawFd;
56use std::os::fd::IntoRawFd as _;
57use std::sync::Arc;
58use std::sync::Once;
59use std::sync::Weak;
60use std::sync::atomic::AtomicBool;
61use std::sync::atomic::Ordering;
62use std::task::Waker;
63use thiserror::Error;
64use virt::NeedsYield;
65use virt::PartitionAccessState;
66use virt::ProtoPartitionConfig;
67use virt::StopVp;
68use virt::VpHaltReason;
69use virt::VpIndex;
70use virt::io::CpuIo;
71use vmcore::interrupt::Interrupt;
72use vmcore::reference_time::GetReferenceTime;
73use vmcore::reference_time::ReferenceTimeResult;
74use vmcore::synic::GuestEventPort;
75
76/// Extension trait for [`VcpuFd`] to accept hvdef register types directly.
77trait VcpuFdExt {
78    fn get_hvdef_regs(&self, regs: &mut [HvRegisterAssoc]) -> Result<(), KernelError>;
79    fn set_hvdef_regs(&self, regs: &[HvRegisterAssoc]) -> Result<(), KernelError>;
80}
81
82impl VcpuFdExt for VcpuFd {
83    fn get_hvdef_regs(&self, regs: &mut [HvRegisterAssoc]) -> Result<(), KernelError> {
84        use mshv_bindings::hv_register_assoc;
85        const {
86            assert!(size_of::<HvRegisterAssoc>() == size_of::<hv_register_assoc>());
87            assert!(align_of::<HvRegisterAssoc>() >= align_of::<hv_register_assoc>());
88        }
89        // SAFETY: HvRegisterAssoc and hv_register_assoc have the same layout.
90        self.get_reg(unsafe {
91            std::mem::transmute::<&mut [HvRegisterAssoc], &mut [hv_register_assoc]>(regs)
92        })?;
93        Ok(())
94    }
95
96    fn set_hvdef_regs(&self, regs: &[HvRegisterAssoc]) -> Result<(), KernelError> {
97        use mshv_bindings::hv_register_assoc;
98        const {
99            assert!(size_of::<HvRegisterAssoc>() == size_of::<hv_register_assoc>());
100            assert!(align_of::<HvRegisterAssoc>() >= align_of::<hv_register_assoc>());
101        }
102        // SAFETY: HvRegisterAssoc and hv_register_assoc have the same layout.
103        self.set_reg(unsafe {
104            std::mem::transmute::<&[HvRegisterAssoc], &[hv_register_assoc]>(regs)
105        })?;
106        Ok(())
107    }
108}
109
110/// Hypervisor backend for Linux /dev/mshv.
111#[derive(Debug)]
112pub struct LinuxMshv {
113    mshv: Mshv,
114    snp_disable_cpuid_offload: bool,
115}
116
117impl LinuxMshv {
118    /// Creates a new instance of the LinuxMshv hypervisor backend.
119    pub fn new() -> io::Result<Self> {
120        let file = fs_err::File::open("/dev/mshv")?;
121        Ok(Self::from(std::fs::File::from(file)))
122    }
123
124    /// Configures whether MSHV SNP CPUID offloads are disabled.
125    pub fn with_snp_cpuid_offload_disabled(mut self, disabled: bool) -> Self {
126        self.snp_disable_cpuid_offload = disabled;
127        self
128    }
129}
130
131impl From<std::fs::File> for LinuxMshv {
132    fn from(file: std::fs::File) -> Self {
133        LinuxMshv {
134            // SAFETY: We take ownership of the file descriptor and pass it to Mshv.
135            // TODO: fix mshv_bindings to not need this unsafe code.
136            mshv: unsafe { Mshv::new_with_fd_number(file.into_raw_fd()) },
137            snp_disable_cpuid_offload: false,
138        }
139    }
140}
141
142impl<'a> MshvProtoPartition<'a> {
143    /// Performs the post-init partition setup common to both architectures:
144    /// creates VPs, BSP, installs intercepts, sets up the signal handler,
145    /// and checks for unsupported VTL2 configuration.
146    fn new(config: ProtoPartitionConfig<'a>, vmfd: VmFd) -> Result<Self, Error> {
147        if config.processor_topology.vp_count() > u8::MAX as u32 {
148            return Err(ErrorInner::TooManyVps(config.processor_topology.vp_count()).into());
149        }
150
151        let vps = config
152            .processor_topology
153            .vps_arch()
154            .map(|vp| MshvVpInner {
155                vp_info: vp,
156                thread: RwLock::new(None),
157                needs_yield: NeedsYield::new(),
158                message_queues: MessageQueues::new(),
159                message_queues_pending: AtomicBool::new(false),
160                waker: RwLock::new(None),
161            })
162            .collect();
163
164        let bsp = vmfd
165            .create_vcpu(0)
166            .map_err(|e| ErrorInner::CreateVcpu(e.into()))?;
167
168        // Install intercepts required by both architectures.
169        vmfd.install_intercept(mshv_install_intercept {
170            access_type_mask: HV_INTERCEPT_ACCESS_MASK_EXECUTE,
171            intercept_type: hvdef::hypercall::HvInterceptType::HvInterceptTypeHypercall.0,
172            intercept_parameter: Default::default(),
173        })
174        .map_err(|e| ErrorInner::InstallIntercept(e.into()))?;
175
176        vmfd.install_intercept(mshv_install_intercept {
177            access_type_mask: HV_INTERCEPT_ACCESS_MASK_EXECUTE,
178            intercept_type:
179                hvdef::hypercall::HvInterceptType::HvInterceptTypeUnknownSynicConnection.0,
180            intercept_parameter: Default::default(),
181        })
182        .map_err(|e| ErrorInner::InstallIntercept(e.into()))?;
183
184        vmfd.install_intercept(mshv_install_intercept {
185            access_type_mask: HV_INTERCEPT_ACCESS_MASK_EXECUTE,
186            intercept_type:
187                hvdef::hypercall::HvInterceptType::HvInterceptTypeRetargetInterruptWithUnknownDeviceId.0,
188            intercept_parameter: Default::default(),
189        })
190        .map_err(|e| ErrorInner::InstallIntercept(e.into()))?;
191
192        // Set up a signal for forcing vcpufd.run() to exit with EINTR.
193        static SIGNAL_HANDLER_INIT: Once = Once::new();
194        // SAFETY: The signal handler does not perform any actions that are
195        // forbidden for signal handlers to perform, as it performs nothing.
196        SIGNAL_HANDLER_INIT.call_once(|| unsafe {
197            signal_hook::low_level::register(libc::SIGRTMIN(), || {
198                // Signal handler does nothing other than enabling run_fd()
199                // ioctl to return with EINTR, when the associated signal is
200                // sent to run_fd() thread.
201            })
202            .unwrap();
203        });
204
205        if let Some(hv_config) = &config.hv_config {
206            if hv_config.vtl2.is_some() {
207                return Err(ErrorInner::Vtl2NotSupported.into());
208            }
209        }
210
211        Ok(MshvProtoPartition {
212            config,
213            #[cfg(guest_arch = "x86_64")]
214            isolation: arch::MshvProtoPartitionIsolation::None,
215            vmfd,
216            vps,
217            bsp,
218        })
219    }
220}
221
222/// Returns whether MSHV is available on this machine.
223pub fn is_available() -> Result<bool, Error> {
224    match std::fs::metadata("/dev/mshv") {
225        Ok(_) => Ok(true),
226        Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(false),
227        Err(err) => Err(ErrorInner::AvailableCheck(err).into()),
228    }
229}
230
231/// Prototype partition.
232pub struct MshvProtoPartition<'a> {
233    config: ProtoPartitionConfig<'a>,
234    #[cfg(guest_arch = "x86_64")]
235    isolation: arch::MshvProtoPartitionIsolation,
236    vmfd: VmFd,
237    vps: Vec<MshvVpInner>,
238    bsp: VcpuFd,
239}
240
241/// A partition running on the /dev/mshv hypervisor.
242#[derive(Inspect)]
243pub struct MshvPartition {
244    #[inspect(flatten)]
245    inner: Arc<MshvPartitionInner>,
246    #[inspect(skip)]
247    synic_ports: Arc<virt::synic::SynicPorts<MshvPartitionInner>>,
248}
249
250enum MshvIsolationState {
251    None,
252    #[cfg(guest_arch = "x86_64")]
253    Snp(arch::SnpPartitionState),
254}
255
256impl MshvIsolationState {
257    #[cfg(guest_arch = "x86_64")]
258    fn is_isolated(&self) -> bool {
259        !matches!(self, Self::None)
260    }
261
262    #[cfg(guest_arch = "x86_64")]
263    fn initial_vp_state_source(&self) -> virt::InitialVpStateSource {
264        match self {
265            Self::None => virt::InitialVpStateSource::Registers,
266            Self::Snp(_) => virt::InitialVpStateSource::ImportedContext,
267        }
268    }
269
270    #[cfg(guest_arch = "x86_64")]
271    fn snp(&self) -> Option<&arch::SnpPartitionState> {
272        match self {
273            Self::None => None,
274            Self::Snp(snp) => Some(snp),
275        }
276    }
277
278    fn map_user_memory(&self, vmfd: &VmFd, region: mshv_user_mem_region) -> anyhow::Result<bool> {
279        match self {
280            Self::None => {
281                vmfd.map_user_memory(region)?;
282                Ok(true)
283            }
284            #[cfg(guest_arch = "x86_64")]
285            Self::Snp(snp) => match *snp.launch_state.lock() {
286                // Record pre-launch mappings without exposing them to MSHV.
287                // SNP launch flushes them after loader writes are complete.
288                arch::SnpLaunchState::NotStarted => Ok(false),
289                arch::SnpLaunchState::Started => {
290                    anyhow::bail!("cannot add a memory mapping while SNP launch is in progress")
291                }
292                arch::SnpLaunchState::Finished => {
293                    vmfd.map_user_memory(region)?;
294                    Ok(true)
295                }
296                arch::SnpLaunchState::Failed => {
297                    anyhow::bail!("cannot add a memory mapping after SNP launch failed")
298                }
299            },
300        }
301    }
302}
303
304#[derive(Inspect)]
305struct MshvPartitionInner {
306    #[inspect(skip)]
307    vmfd: VmFd,
308    /// The BSP's VcpuFd, retained for partition-level register access
309    /// (VM state get/set). Only used while VPs are stopped.
310    #[inspect(skip)]
311    bsp_vcpufd: VcpuFd,
312    #[inspect(skip)]
313    memory: Mutex<MshvMemoryRangeState>,
314    gm: GuestMemory,
315    mem_layout: vm_topology::memory::MemoryLayout,
316    #[inspect(skip)]
317    vps: Vec<MshvVpInner>,
318    #[cfg(guest_arch = "x86_64")]
319    irq_routes: virt::irqcon::IrqRoutes,
320    #[inspect(skip)]
321    gsi_states: Mutex<Box<[irqfd::GsiState; irqfd::NUM_GSIS]>>,
322    caps: virt::PartitionCapabilities,
323    synic_ports: virt::synic::SynicPortMap,
324    #[cfg(guest_arch = "x86_64")]
325    software_devices: virt::x86::apic_software_device::ApicSoftwareDevices,
326    #[inspect(skip)]
327    isolation: MshvIsolationState,
328    /// Set to `true` when partition time is frozen (e.g. during reset).
329    /// The first VP to enter `run_vp` after a freeze will thaw time.
330    time_frozen: Mutex<bool>,
331    /// aarch64 GIC MSI controller config, used to decode PCIe MSIs into SPI
332    /// assertions via a v2m frame.
333    #[cfg(guest_arch = "aarch64")]
334    #[inspect(skip)]
335    gic_msi: vm_topology::processor::aarch64::GicMsiController,
336}
337
338struct MshvVpInner {
339    vp_info: vm_topology::processor::TargetVpInfo,
340    thread: RwLock<Option<Pthread>>,
341    needs_yield: NeedsYield,
342    message_queues: MessageQueues,
343    /// Set by device threads after enqueuing a message to signal the VP
344    /// thread to flush its message queues.
345    message_queues_pending: AtomicBool,
346    /// Waker for the VP run loop task. Set by the VP thread, used by device
347    /// threads to re-poll the run loop when new messages are enqueued.
348    waker: RwLock<Option<Waker>>,
349}
350
351struct MshvVpInnerCleaner<'a> {
352    vpinner: &'a MshvVpInner,
353}
354
355impl Drop for MshvVpInnerCleaner<'_> {
356    fn drop(&mut self) {
357        self.vpinner.thread.write().take();
358    }
359}
360
361impl GetReferenceTime for MshvPartitionInner {
362    fn now(&self) -> ReferenceTimeResult {
363        // Use the partition property instead of a VP register to avoid
364        // deadlocking when VPs are running.
365        let ref_time = self
366            .vmfd
367            .get_partition_property(HvPartitionPropertyCode::ReferenceTime.0)
368            .unwrap();
369        ReferenceTimeResult {
370            ref_time,
371            system_time: None,
372        }
373    }
374}
375
376impl MshvPartitionInner {
377    fn vp(&self, vp_index: VpIndex) -> &MshvVpInner {
378        &self.vps[vp_index.index() as usize]
379    }
380
381    /// Freezes partition time. Time will remain frozen until [`thaw_time`] is
382    /// called (typically on the first VP run after reset).
383    fn freeze_time(&self) -> Result<(), Error> {
384        let mut frozen = self.time_frozen.lock();
385        if !*frozen {
386            self.vmfd
387                .set_partition_property(HvPartitionPropertyCode::TimeFreeze.0, 1)
388                .map_err(|e| ErrorInner::SetPartitionProperty(e.into()))?;
389            *frozen = true;
390        }
391        Ok(())
392    }
393
394    /// Thaws partition time if it is currently frozen. This is a no-op if
395    /// time is already running.
396    fn thaw_time(&self) -> Result<(), Error> {
397        let mut frozen = self.time_frozen.lock();
398        if *frozen {
399            self.vmfd
400                .set_partition_property(HvPartitionPropertyCode::TimeFreeze.0, 0)
401                .map_err(|e| ErrorInner::SetPartitionProperty(e.into()))?;
402            *frozen = false;
403        }
404        Ok(())
405    }
406
407    fn post_message(&self, vp_index: VpIndex, sint: u8, message: &HvMessage) {
408        let vp = self.vp(vp_index);
409        let wake = vp.message_queues.enqueue_message(sint, message);
410        // Signal the VP thread to flush message queues.
411        if wake && !vp.message_queues_pending.swap(true, Ordering::Release) {
412            if let Some(waker) = &*vp.waker.read() {
413                waker.wake_by_ref();
414            }
415        }
416    }
417
418    /// Posts a message directly to a VP's SynIC sint.
419    ///
420    /// This wraps the HvCallPostMessageDirect hypercall via the raw hvcall
421    /// interface. This is used instead of the `mshv-ioctls` method because
422    /// that method is only available on x86.
423    // TODO: upstream an arch-independent version to mshv-ioctls.
424    fn post_message_direct(&self, vp: u32, sint: u8, message: &HvMessage) -> Result<(), MshvError> {
425        use mshv_bindings::mshv_root_hvcall;
426
427        let post_message = hvdef::hypercall::PostMessageDirect {
428            partition_id: 0,
429            vp_index: vp,
430            vtl: Vtl::Vtl0 as u8,
431            padding0: [0; 3],
432            sint,
433            padding1: [0; 3],
434            message: zerocopy::Unalign::new(*message),
435            padding2: 0,
436        };
437
438        let mut args = mshv_root_hvcall {
439            code: hvdef::HypercallCode::HvCallPostMessageDirect.0,
440            in_sz: size_of::<hvdef::hypercall::PostMessageDirect>() as u16,
441            in_ptr: std::ptr::addr_of!(post_message) as u64,
442            ..Default::default()
443        };
444        self.vmfd.hvcall(&mut args)
445    }
446
447    /// Signals a SynIC event directly on a VP.
448    ///
449    /// This wraps the HvCallSignalEventDirect hypercall via the raw hvcall
450    /// interface. This is used instead of the `mshv-ioctls` method because
451    /// that method is only available on x86.
452    // TODO: upstream an arch-independent version to mshv-ioctls.
453    fn signal_event_direct(&self, vp: u32, sint: u8, flag: u16) -> Result<(), MshvError> {
454        use mshv_bindings::mshv_root_hvcall;
455        use zerocopy::FromZeros;
456
457        let input = hvdef::hypercall::SignalEventDirect {
458            target_partition: 0,
459            target_vp: vp,
460            target_vtl: 0,
461            target_sint: sint,
462            flag_number: flag,
463        };
464        let mut output = hvdef::hypercall::SignalEventDirectOutput::new_zeroed();
465
466        let mut args = mshv_root_hvcall {
467            code: hvdef::HypercallCode::HvCallSignalEventDirect.0,
468            in_sz: size_of::<hvdef::hypercall::SignalEventDirect>() as u16,
469            out_sz: size_of::<hvdef::hypercall::SignalEventDirectOutput>() as u16,
470            in_ptr: std::ptr::addr_of!(input) as u64,
471            out_ptr: std::ptr::addr_of_mut!(output) as u64,
472            ..Default::default()
473        };
474        self.vmfd.hvcall(&mut args)
475    }
476}
477
478/// Binds a virtual processor to the current thread.
479pub struct MshvProcessorBinder {
480    partition: Arc<MshvPartitionInner>,
481    vcpufd: Option<VcpuFd>,
482    vpindex: VpIndex,
483    #[cfg(guest_arch = "x86_64")]
484    snp: Option<arch::SnpVpState>,
485}
486
487/// Wraps a VcpuFd for running a VP. On x86_64, also provides access to the
488/// register page for fast register reads/writes.
489struct MshvVpRunner<'a> {
490    vcpufd: &'a VcpuFd,
491    #[cfg(guest_arch = "x86_64")]
492    reg_page: Option<*mut hvdef::HvX64RegisterPage>,
493    #[cfg(guest_arch = "x86_64")]
494    ghcb_page: Option<*mut x86defs::snp::GhcbPage>,
495}
496
497impl MshvVpRunner<'_> {
498    fn run(&mut self) -> Result<HvMessage, MshvError> {
499        self.vcpufd.run().map(|msg| {
500            // SAFETY: hv_message and HvMessage have the same size
501            // (256 bytes) and compatible layout (header + 240-byte
502            // payload).
503            unsafe { std::mem::transmute::<mshv_bindings::hv_message, HvMessage>(msg) }
504        })
505    }
506
507    #[cfg(guest_arch = "x86_64")]
508    fn reg_page(&mut self) -> &mut hvdef::HvX64RegisterPage {
509        let page = self
510            .reg_page
511            .expect("register page is unavailable for isolated VPs");
512        // SAFETY: VP is stopped (returned from run()), so we have exclusive
513        // access. The pointer is the kernel's VP register-page mapping and
514        // remains valid for the processor borrow.
515        unsafe { &mut *page }
516    }
517
518    #[cfg(guest_arch = "x86_64")]
519    fn ghcb_page(&mut self) -> Option<&mut x86defs::snp::GhcbPage> {
520        // SAFETY: The mapped page is owned by the mutably borrowed processor
521        // binder and remains valid for this processor borrow.
522        self.ghcb_page.map(|page| unsafe { &mut *page })
523    }
524}
525
526/// A bound virtual processor for the /dev/mshv hypervisor.
527#[derive(InspectMut)]
528pub struct MshvProcessor<'a> {
529    #[inspect(skip)]
530    partition: &'a MshvPartitionInner,
531    #[inspect(skip)]
532    inner: &'a MshvVpInner,
533    #[inspect(skip)]
534    vpindex: VpIndex,
535    #[inspect(skip)]
536    runner: MshvVpRunner<'a>,
537    /// The deliverability notification state currently registered with the
538    /// hypervisor.
539    #[inspect(skip)]
540    deliverability_notifications: HvDeliverabilityNotificationsRegister,
541}
542
543impl MshvProcessor<'_> {
544    /// Posts any queued messages for the given sints, and requests
545    /// deliverability notifications for any sints that still have pending
546    /// messages.
547    fn flush_messages(&mut self, deliverable_sints: u16) {
548        let nonempty_sints =
549            self.inner
550                .message_queues
551                .post_pending_messages(deliverable_sints, |sint, message| {
552                    match self
553                        .partition
554                        .post_message_direct(self.vpindex.index(), sint, message)
555                    {
556                        Ok(()) => {
557                            tracing::trace!(sint, "sint message posted successfully");
558                            Ok(())
559                        }
560                        Err(e) => {
561                            tracelimit::warn_ratelimited!(
562                                error = &e as &dyn std::error::Error,
563                                "dropping sint message"
564                            );
565                            Err(HvError::ObjectInUse)
566                        }
567                    }
568                });
569
570        if self.deliverability_notifications.sints() != nonempty_sints {
571            let notifications = self.deliverability_notifications.with_sints(nonempty_sints);
572            tracing::trace!(?notifications, "setting deliverability notifications");
573            self.partition
574                .vmfd
575                .register_deliverabilty_notifications(
576                    self.vpindex.index(),
577                    u64::from(notifications),
578                )
579                .expect("requesting deliverability is not a fallible operation");
580            self.deliverability_notifications = notifications;
581        }
582    }
583
584    /// Handles a synic sint deliverable exit. The deliverable sints bitmap
585    /// is architecture-specific (different message types for x86_64 and
586    /// aarch64), so the caller extracts it and passes it here.
587    fn handle_sint_deliverable(&mut self, deliverable_sints: u16) {
588        // Clear the delivered sints from both the current and next state.
589        self.deliverability_notifications
590            .set_sints(self.deliverability_notifications.sints() & !deliverable_sints);
591
592        self.flush_messages(deliverable_sints);
593    }
594
595    /// Resets the VP's message queue and deliverability notification state.
596    fn reset_synic_state(&mut self) {
597        self.inner.message_queues.clear();
598        self.inner
599            .message_queues_pending
600            .store(false, Ordering::Relaxed);
601        self.deliverability_notifications = HvDeliverabilityNotificationsRegister::new();
602    }
603}
604
605impl virt::Processor for MshvProcessor<'_> {
606    type StateAccess<'a>
607        = &'a mut Self
608    where
609        Self: 'a;
610
611    fn set_debug_state(
612        &mut self,
613        _vtl: Vtl,
614        _state: Option<&virt::x86::DebugState>,
615    ) -> Result<(), <&mut Self as virt::vp::AccessVpState>::Error> {
616        Err(ErrorInner::NotSupported.into())
617    }
618
619    async fn run_vp(
620        &mut self,
621        stop: StopVp<'_>,
622        dev: &impl CpuIo,
623    ) -> Result<Infallible, VpHaltReason> {
624        let vpinner = self.inner;
625        let _cleaner = MshvVpInnerCleaner { vpinner };
626
627        assert!(vpinner.thread.write().replace(Pthread::current()).is_none());
628
629        self.partition
630            .thaw_time()
631            .expect("failed to thaw partition time");
632
633        // Ensure any messages present from a state restore are flushed on
634        // the first loop iteration.
635        if vpinner.message_queues.pending_sints() != 0 {
636            vpinner
637                .message_queues_pending
638                .store(true, Ordering::Relaxed);
639        }
640
641        let mut last_waker: Option<Waker> = None;
642
643        loop {
644            vpinner.needs_yield.maybe_yield().await;
645            stop.check()?;
646
647            // Ensure the waker is set so device threads can wake us.
648            poll_fn(|cx| {
649                if !last_waker.as_ref().is_some_and(|w| cx.waker().will_wake(w)) {
650                    last_waker = Some(cx.waker().clone());
651                    *vpinner.waker.write() = last_waker.clone();
652                }
653                std::task::Poll::Ready(())
654            })
655            .await;
656
657            // Flush any messages enqueued by device threads.
658            if vpinner.message_queues_pending.load(Ordering::Relaxed) {
659                vpinner
660                    .message_queues_pending
661                    .store(false, Ordering::SeqCst);
662                let pending_sints = vpinner.message_queues.pending_sints();
663                if pending_sints != 0 {
664                    self.flush_messages(pending_sints);
665                }
666            }
667
668            match self.runner.run() {
669                Ok(exit) => {
670                    self.handle_exit(&exit, dev).await?;
671                }
672                Err(e) => match e.errno() {
673                    libc::EAGAIN | libc::EINTR => {}
674                    _ => tracing::error!(
675                        error = &e as &dyn std::error::Error,
676                        "vcpufd.run returned error"
677                    ),
678                },
679            }
680        }
681    }
682
683    fn flush_async_requests(&mut self) {}
684
685    fn access_state(&mut self, vtl: Vtl) -> Self::StateAccess<'_> {
686        assert_eq!(vtl, Vtl::Vtl0);
687        self
688    }
689
690    fn reset(&mut self) -> Result<(), impl std::error::Error + Send + Sync + 'static> {
691        use virt::vp::AccessVpState;
692
693        let vp_info = self.inner.vp_info;
694        self.access_state(Vtl::Vtl0)
695            .reset_all(&vp_info)
696            .map_err(|e| ErrorInner::ResetState(Box::new(e)))?;
697
698        self.reset_synic_state();
699
700        Ok::<(), Error>(())
701    }
702}
703
704impl hv1_hypercall::PostMessage for arch::MshvHypercallHandler<'_> {
705    fn post_message(&mut self, connection_id: u32, message: &[u8]) -> hvdef::HvResult<()> {
706        self.partition
707            .synic_ports
708            .handle_post_message(Vtl::Vtl0, connection_id, false, message)
709    }
710}
711
712impl hv1_hypercall::SignalEvent for arch::MshvHypercallHandler<'_> {
713    fn signal_event(&mut self, connection_id: u32, flag: u16) -> hvdef::HvResult<()> {
714        self.partition
715            .synic_ports
716            .handle_signal_event(Vtl::Vtl0, connection_id, flag)
717    }
718}
719
720/// Error type for /dev/mshv operations.
721#[derive(Error, Debug)]
722#[error(transparent)]
723pub struct Error(ErrorInner);
724
725impl<T: Into<ErrorInner>> From<T> for Error {
726    fn from(err: T) -> Self {
727        Error(err.into())
728    }
729}
730
731// TODO: Chunk this up into smaller types.
732#[derive(Error, Debug)]
733enum ErrorInner {
734    #[error("operation not supported")]
735    NotSupported,
736    #[error("create_vm failed")]
737    CreateVMFailed,
738    #[error("failed to initialize VM")]
739    CreateVMInitFailed(#[source] anyhow::Error),
740    #[error("failed to create VCPU")]
741    CreateVcpu(#[source] KernelError),
742    #[cfg(guest_arch = "x86_64")]
743    #[error("VP register page is unavailable")]
744    MissingRegisterPage,
745    #[cfg(guest_arch = "x86_64")]
746    #[error(transparent)]
747    Snp(#[from] arch::SnpError),
748    #[error("vtl2 not supported")]
749    Vtl2NotSupported,
750    #[error("isolation not supported")]
751    IsolationNotSupported,
752    #[cfg(guest_arch = "x86_64")]
753    #[error("invalid MSHV configuration: {0}")]
754    InvalidConfiguration(&'static str),
755    #[cfg(guest_arch = "x86_64")]
756    #[error("SNP IGVM requests unsupported highest VTL {0}")]
757    UnsupportedSnpVtl(u8),
758    #[cfg(guest_arch = "x86_64")]
759    #[error("SNP IGVM requests unsupported shared GPA boundary {0:#x}")]
760    UnsupportedSnpSharedGpaBoundary(u64),
761    #[cfg(guest_arch = "x86_64")]
762    #[error("MSHV does not support SNP IGVM relocation")]
763    SnpIgvmRelocationUnsupported,
764    #[cfg(guest_arch = "x86_64")]
765    #[error("SNP IGVM must contain exactly one BSP VP context")]
766    InvalidSnpIgvmTopology,
767    #[cfg(guest_arch = "x86_64")]
768    #[error("failed to parse the SNP IGVM VMSA")]
769    InvalidSnpIgvmVmsa,
770    #[cfg(guest_arch = "x86_64")]
771    #[error(
772        "unsupported SNP IGVM VMSA state: SEV features {sev_features:#x}, virtual TOM {virtual_tom:#x}"
773    )]
774    UnsupportedSnpIgvmVmsa { sev_features: u64, virtual_tom: u64 },
775    #[cfg(guest_arch = "x86_64")]
776    #[error("SNP IGVM VMSA backing memory is not contiguous")]
777    InvalidSnpVmsaBacking,
778    #[cfg(guest_arch = "x86_64")]
779    #[error("SNP IGVM VMSA GPA {0:#x} is invalid")]
780    InvalidSnpVmsaGpa(u64),
781    #[cfg(guest_arch = "x86_64")]
782    #[error("SNP IGVM VMSA overlaps configured guest RAM")]
783    SnpVmsaOverlapsRam,
784    #[cfg(guest_arch = "x86_64")]
785    #[error("SNP VMSA import does not match the configured IGVM VP context")]
786    InvalidSnpVmsaImport,
787    #[error("failed to stat /dev/mshv")]
788    AvailableCheck(#[source] io::Error),
789    #[cfg(guest_arch = "x86_64")]
790    #[error("failed to get partition property")]
791    GetPartitionProperty(#[source] KernelError),
792    #[error("failed to set partition property")]
793    SetPartitionProperty(#[source] KernelError),
794    #[error("register access error")]
795    Register(#[source] KernelError),
796    #[cfg(guest_arch = "x86_64")]
797    #[error("failed to get VP state {ty}")]
798    GetVpState {
799        #[source]
800        error: KernelError,
801        ty: u8,
802    },
803    #[cfg(guest_arch = "x86_64")]
804    #[error("failed to set VP state {ty}")]
805    SetVpState {
806        #[source]
807        error: KernelError,
808        ty: u8,
809    },
810    #[error("failed to reset state")]
811    ResetState(#[source] Box<virt::state::StateError<Error>>),
812    #[error("install intercept failed")]
813    InstallIntercept(#[source] KernelError),
814    #[cfg(guest_arch = "x86_64")]
815    #[error("failed to register cpuid override")]
816    RegisterCpuid(#[source] KernelError),
817    #[error("too many virtual processors: {0}")]
818    TooManyVps(u32),
819    #[cfg(guest_arch = "x86_64")]
820    #[error("unsupported processor vendor: {0:?}")]
821    UnsupportedProcessorVendor(hvdef::HvProcessorVendor),
822    #[cfg(guest_arch = "x86_64")]
823    #[error("failed to create virtual device")]
824    NewDevice(#[source] virt::x86::apic_software_device::DeviceIdInUse),
825}
826
827/// Equivalent to [`MshvError`] but has a much better error message.
828#[derive(Error, Debug)]
829enum KernelError {
830    #[error("kernel error")]
831    Kernel(#[source] io::Error),
832    #[error("hypercall {code:#x?} error")]
833    Hypercall {
834        code: hvdef::HypercallCode,
835        #[source]
836        error: HvError,
837    },
838}
839
840impl From<MshvError> for KernelError {
841    fn from(err: MshvError) -> Self {
842        match err {
843            MshvError::Errno(e) => KernelError::Kernel(e.into()),
844            MshvError::Hypercall {
845                code,
846                status_raw,
847                status: _,
848            } => KernelError::Hypercall {
849                code: hvdef::HypercallCode(code),
850                error: HvError::from(
851                    std::num::NonZeroU16::new(status_raw)
852                        .expect("not an error, hypercall returned success"),
853                ),
854            },
855        }
856    }
857}
858
859/// Creates a VM with retry on EINTR.
860fn create_vm_with_retry(
861    mshv: &Mshv,
862    args: &mshv_bindings::mshv_create_partition_v2,
863) -> Result<VmFd, Error> {
864    loop {
865        match mshv.create_vm_with_args(args) {
866            Ok(fd) => return Ok(fd),
867            Err(e) => {
868                if e.errno() == libc::EINTR {
869                    continue;
870                } else {
871                    return Err(ErrorInner::CreateVMFailed.into());
872                }
873            }
874        }
875    }
876}
877
878/// Returns the base set of synthetic processor features shared by both
879/// architectures. Each architecture may add extra features before passing
880/// the result to `set_partition_property`.
881fn common_synthetic_features() -> hvdef::HvPartitionSyntheticProcessorFeatures {
882    hvdef::HvPartitionSyntheticProcessorFeatures::new()
883        .with_hypervisor_present(true)
884        .with_hv1(true)
885        .with_access_vp_run_time_reg(true)
886        .with_access_partition_reference_counter(true)
887        .with_access_synic_regs(true)
888        .with_access_synthetic_timer_regs(true)
889        .with_access_intr_ctrl_regs(true)
890        .with_access_hypercall_regs(true)
891        .with_access_vp_index(true)
892        .with_fast_hypercall_output(true)
893        .with_direct_synthetic_timers(true)
894        .with_extended_processor_masks(true)
895        .with_tb_flush_hypercalls(true)
896        .with_synthetic_cluster_ipi(true)
897        .with_notify_long_spin_wait(true)
898        .with_query_numa_distance(true)
899        .with_signal_events(true)
900        .with_retarget_device_interrupt(true)
901}
902
903impl PartitionAccessState for MshvPartition {
904    type StateAccess<'a> = &'a MshvPartition;
905
906    fn access_state(&self, vtl: Vtl) -> Self::StateAccess<'_> {
907        assert_eq!(vtl, Vtl::Vtl0);
908        self
909    }
910}
911
912#[derive(Debug, Default)]
913struct MshvMemoryRangeState {
914    ranges: Vec<Option<MshvMemoryRange>>,
915}
916
917#[derive(Debug, Copy, Clone)]
918struct MshvMemoryRange {
919    region: mshv_user_mem_region,
920    mapped: bool,
921}
922
923impl virt::PartitionMemoryMapper for MshvPartition {
924    fn memory_mapper(&self, vtl: Vtl) -> Arc<dyn virt::PartitionMemoryMap> {
925        assert_eq!(vtl, Vtl::Vtl0);
926        self.inner.clone()
927    }
928
929    fn host_access(&self) -> Option<Arc<dyn virt::PartitionHostAccess>> {
930        #[cfg(guest_arch = "x86_64")]
931        if self.inner.isolation.snp().is_some() {
932            return Some(self.inner.clone());
933        }
934        None
935    }
936}
937
938// TODO: figure out a better abstraction that also works for KVM and WHP.
939impl virt::PartitionHostAccess for MshvPartitionInner {
940    fn acquire_host_access(&self, _addr: u64, _size: u64, _write: bool) -> anyhow::Result<()> {
941        // TODO: The current prototype only provides the acquisition half of
942        // the host-visibility lifecycle. This is sufficient for single-threaded
943        // bring-up with no concurrent page-state changes, but the GPA attribute
944        // intercept revocation path must share serialized state with
945        // acquire_snp_host_access before concurrent use is safe.
946        // TODO: Investigate whether MSHV supports read-only host-access
947        // requests and use `_write` to avoid granting write access for reads.
948        #[cfg(guest_arch = "x86_64")]
949        if self.isolation.snp().is_some() {
950            return arch::acquire_snp_host_access(self, _addr, _size);
951        }
952        anyhow::bail!("acquiring host access is not supported")
953    }
954}
955
956impl virt::PartitionMemoryMap for MshvPartitionInner {
957    unsafe fn map_range(
958        &self,
959        data: *mut u8,
960        size: usize,
961        addr: u64,
962        writable: bool,
963        exec: bool,
964    ) -> anyhow::Result<()> {
965        let mut state = self.memory.lock();
966
967        // Memory slots cannot be resized but can be moved within the guest
968        // address space. Find the existing slot if there is one.
969        let mut slot_to_use = None;
970        for (slot, range) in state.ranges.iter_mut().enumerate() {
971            match range {
972                Some(range) if range.region.userspace_addr == data as u64 => {
973                    slot_to_use = Some(slot);
974                    break;
975                }
976                Some(_) => (),
977                None => slot_to_use = Some(slot),
978            }
979        }
980        if slot_to_use.is_none() {
981            slot_to_use = Some(state.ranges.len());
982            state.ranges.push(None);
983        }
984        let slot_to_use = slot_to_use.unwrap();
985
986        let mut flags = 0;
987        if writable {
988            flags |= set_bits!(u8, MSHV_SET_MEM_BIT_WRITABLE);
989        }
990        if exec {
991            flags |= set_bits!(u8, MSHV_SET_MEM_BIT_EXECUTABLE);
992        }
993        let mem_region = mshv_user_mem_region {
994            size: size as u64,
995            guest_pfn: addr >> HV_PAGE_SHIFT,
996            userspace_addr: data as u64,
997            flags,
998            rsvd: [0; 7],
999        };
1000
1001        let _span = tracing::info_span!(
1002            "mshv map user memory",
1003            guest_pfn = mem_region.guest_pfn,
1004            size = mem_region.size,
1005            writable,
1006            exec,
1007        )
1008        .entered();
1009        let mapped = self.isolation.map_user_memory(&self.vmfd, mem_region)?;
1010        state.ranges[slot_to_use] = Some(MshvMemoryRange {
1011            region: mem_region,
1012            mapped,
1013        });
1014        Ok(())
1015    }
1016
1017    fn unmap_range(&self, addr: u64, size: u64) -> anyhow::Result<()> {
1018        let unmap_start = addr >> HV_PAGE_SHIFT;
1019        let unmap_end = addr
1020            .checked_add(size)
1021            .ok_or_else(|| anyhow::anyhow!("unmap range overflows the guest address space"))?
1022            >> HV_PAGE_SHIFT;
1023        let mut state = self.memory.lock();
1024        for range in state.ranges.iter().flatten() {
1025            let region_start = range.region.guest_pfn;
1026            let region_end = region_start
1027                .checked_add(range.region.size >> HV_PAGE_SHIFT)
1028                .ok_or_else(|| anyhow::anyhow!("tracked memory region overflows GPA space"))?;
1029            anyhow::ensure!(
1030                (unmap_start <= region_start && region_end <= unmap_end)
1031                    || region_end <= unmap_start
1032                    || unmap_end <= region_start,
1033                "unmap range partially overlaps a tracked memory region"
1034            );
1035        }
1036
1037        for entry in &mut state.ranges {
1038            let Some(range) = entry.as_ref() else {
1039                continue;
1040            };
1041            let region = &range.region;
1042            let region_start = region.guest_pfn;
1043            let region_end = region_start
1044                .checked_add(region.size >> HV_PAGE_SHIFT)
1045                .ok_or_else(|| anyhow::anyhow!("tracked memory region overflows GPA space"))?;
1046            if unmap_start <= region_start && region_end <= unmap_end {
1047                // Region is fully contained in the unmap range.
1048                let _span = tracing::info_span!(
1049                    "mshv unmap user memory",
1050                    guest_pfn = region.guest_pfn,
1051                    size = region.size,
1052                )
1053                .entered();
1054                if range.mapped {
1055                    self.vmfd.unmap_user_memory(*region)?;
1056                }
1057                *entry = None;
1058            }
1059        }
1060        Ok(())
1061    }
1062}
1063
1064/// Holds the state needed to deassign an MSHV ioeventfd on drop.
1065///
1066/// The kernel's `mshv_deassign_ioeventfd` matches entries by (eventfd,
1067/// addr, len, datamatch/wildcard), so we must keep all of these alive
1068/// for the deassign ioctl.
1069struct MshvDoorbellEntry {
1070    partition: Weak<MshvPartitionInner>,
1071    event: Event,
1072    guest_address: u64,
1073    datamatch: u64,
1074    len: u32,
1075    flags: u32,
1076}
1077
1078impl MshvDoorbellEntry {
1079    fn new(
1080        partition: &Arc<MshvPartitionInner>,
1081        guest_address: u64,
1082        value: Option<u64>,
1083        length: Option<u32>,
1084        fd: &Event,
1085    ) -> io::Result<MshvDoorbellEntry> {
1086        let flags = if value.is_some() {
1087            1 << mshv_bindings::MSHV_IOEVENTFD_BIT_DATAMATCH
1088        } else {
1089            0
1090        };
1091        let datamatch = value.unwrap_or(0);
1092        let len = length.unwrap_or(0);
1093        let event = fd.clone();
1094
1095        let ioeventfd = mshv_bindings::mshv_user_ioeventfd {
1096            datamatch,
1097            addr: guest_address,
1098            len,
1099            fd: event.as_fd().as_raw_fd(),
1100            flags,
1101            ..Default::default()
1102        };
1103        // SAFETY: `partition.vmfd` is valid because it is owned by
1104        // `MshvPartitionInner`. The `ioeventfd` struct is properly
1105        // initialized on the stack.
1106        let ret = unsafe {
1107            libc::ioctl(
1108                partition.vmfd.as_raw_fd(),
1109                mshv_ioctls::MSHV_IOEVENTFD() as _,
1110                std::ptr::from_ref(&ioeventfd),
1111            )
1112        };
1113        if ret < 0 {
1114            return Err(io::Error::last_os_error());
1115        }
1116
1117        Ok(Self {
1118            partition: Arc::downgrade(partition),
1119            event,
1120            guest_address,
1121            datamatch,
1122            len,
1123            flags,
1124        })
1125    }
1126}
1127
1128impl Drop for MshvDoorbellEntry {
1129    fn drop(&mut self) {
1130        if let Some(partition) = self.partition.upgrade() {
1131            let ioeventfd = mshv_bindings::mshv_user_ioeventfd {
1132                datamatch: self.datamatch,
1133                addr: self.guest_address,
1134                len: self.len,
1135                fd: self.event.as_fd().as_raw_fd(),
1136                flags: self.flags | (1 << mshv_bindings::MSHV_IOEVENTFD_BIT_DEASSIGN),
1137                ..Default::default()
1138            };
1139            // SAFETY: `partition.vmfd` is valid because we successfully
1140            // upgraded the weak reference. The `ioeventfd` struct is
1141            // properly initialized on the stack.
1142            let ret = unsafe {
1143                libc::ioctl(
1144                    partition.vmfd.as_raw_fd(),
1145                    mshv_ioctls::MSHV_IOEVENTFD() as _,
1146                    std::ptr::from_ref(&ioeventfd),
1147                )
1148            };
1149            assert!(
1150                ret >= 0,
1151                "failed to unregister doorbell at {:#x}: {}",
1152                self.guest_address,
1153                io::Error::last_os_error()
1154            );
1155        }
1156    }
1157}
1158
1159impl DoorbellRegistration for MshvPartition {
1160    fn register_doorbell(
1161        &self,
1162        guest_address: u64,
1163        value: Option<u64>,
1164        length: Option<u32>,
1165        fd: &Event,
1166    ) -> io::Result<Box<dyn Send + Sync>> {
1167        Ok(Box::new(MshvDoorbellEntry::new(
1168            &self.inner,
1169            guest_address,
1170            value,
1171            length,
1172            fd,
1173        )?))
1174    }
1175}
1176
1177impl virt::synic::Synic for MshvPartitionInner {
1178    fn port_map(&self) -> &virt::synic::SynicPortMap {
1179        &self.synic_ports
1180    }
1181
1182    fn post_message(&self, _vtl: Vtl, vp: VpIndex, sint: u8, typ: u32, payload: &[u8]) {
1183        self.post_message(vp, sint, &HvMessage::new(HvMessageType(typ), 0, payload));
1184    }
1185
1186    fn new_guest_event_port(
1187        self: Arc<Self>,
1188        _vtl: Vtl,
1189        vp: u32,
1190        sint: u8,
1191        flag: u16,
1192    ) -> Box<dyn GuestEventPort> {
1193        Box::new(MshvGuestEventPort {
1194            partition: Arc::downgrade(&self),
1195            params: Arc::new(Mutex::new(MshvEventPortParams {
1196                vp: VpIndex::new(vp),
1197                sint,
1198                flag,
1199            })),
1200        })
1201    }
1202
1203    fn prefer_os_events(&self) -> bool {
1204        false
1205    }
1206}
1207
1208/// `GuestEventPort` implementation for MSHV partitions.
1209#[derive(Debug, Clone)]
1210struct MshvGuestEventPort {
1211    partition: Weak<MshvPartitionInner>,
1212    params: Arc<Mutex<MshvEventPortParams>>,
1213}
1214
1215#[derive(Debug, Copy, Clone)]
1216struct MshvEventPortParams {
1217    vp: VpIndex,
1218    sint: u8,
1219    flag: u16,
1220}
1221
1222impl GuestEventPort for MshvGuestEventPort {
1223    fn interrupt(&self) -> Interrupt {
1224        let partition = self.partition.clone();
1225        let params = self.params.clone();
1226        Interrupt::from_fn(move || {
1227            let MshvEventPortParams { vp, sint, flag } = *params.lock();
1228            if let Some(partition) = partition.upgrade() {
1229                partition
1230                    .signal_event_direct(vp.index(), sint, flag)
1231                    .unwrap_or_else(|_| {
1232                        panic!(
1233                            "Failed signal synic sint {} on vp {:?} with flag {}",
1234                            sint, vp, flag
1235                        )
1236                    });
1237            }
1238        })
1239    }
1240
1241    fn set_target_vp(&mut self, vp: u32) -> Result<(), vmcore::synic::HypervisorError> {
1242        self.params.lock().vp = VpIndex::new(vp);
1243        Ok(())
1244    }
1245}