Skip to main content

virt/
generic.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4mod partition_memory_map;
5
6pub use partition_memory_map::PartitionMemoryMap;
7pub use vm_topology::processor::VpIndex;
8
9use crate::CpuidLeaf;
10use crate::PartitionCapabilities;
11use crate::io::CpuIo;
12use crate::irqcon::ControlGic;
13use crate::irqcon::IoApicRouting;
14use crate::irqcon::MsiRequest;
15use crate::irqfd::IrqFd;
16use crate::x86::DebugState;
17use crate::x86::HardwareBreakpoint;
18use guestmem::DoorbellRegistration;
19use guestmem::GuestMemory;
20use guestmem::GuestMemoryBackingError;
21use hvdef::Vtl;
22use inspect::Inspect;
23use inspect::InspectMut;
24use memory_range::MemoryRange;
25use pci_core::msi::SignalMsi;
26use std::cell::Cell;
27use std::convert::Infallible;
28use std::fmt::Debug;
29use std::future::Future;
30use std::future::poll_fn;
31use std::pin::pin;
32use std::sync::Arc;
33use std::sync::atomic::AtomicBool;
34use std::sync::atomic::Ordering;
35use std::task::Poll;
36use std::task::Waker;
37use vm_topology::memory::MemoryLayout;
38use vm_topology::processor::ProcessorTopology;
39use vmcore::reference_time::ReferenceTimeSource;
40use vmcore::vmtime::VmTimeSource;
41use vmcore::vpci_msi::MapVpciInterrupt;
42use vmcore::vpci_msi::MsiAddressData;
43use vmcore::vpci_msi::RegisterInterruptError;
44use vmcore::vpci_msi::VpciInterruptParameters;
45
46/// Platform capabilities detected from the hypervisor before partition
47/// creation. On x86 there are currently no pre-partition queries.
48#[cfg(guest_arch = "x86_64")]
49#[derive(Debug, Clone, Default)]
50pub struct PlatformInfo {}
51
52/// Platform capabilities detected from the hypervisor before partition
53/// creation.
54#[cfg(guest_arch = "aarch64")]
55#[derive(Debug, Clone)]
56pub struct PlatformInfo {
57    /// The platform PMU GSIV (GIC INTID), if available.
58    pub platform_gsiv: Option<u32>,
59    /// Whether the hypervisor supports GICv3. When `false`, only
60    /// GICv2 is available (e.g., Raspberry Pi 5 with GIC-400).
61    pub supports_gic_v3: bool,
62    /// Whether the hypervisor supports an in-kernel GICv3 ITS for
63    /// MSI delivery via LPIs. When `true`, the topology can include
64    /// a `GicItsInfo` and the backend will create/manage the ITS device.
65    pub supports_its: bool,
66    /// How the physical SMMU implementation selects the IOVA range reserved
67    /// for device-assignment MSI writes.
68    pub device_assignment_msi_iova: DeviceAssignmentMsiIova,
69}
70
71/// Selection policy for the device-assignment MSI IOVA reservation.
72#[cfg(guest_arch = "aarch64")]
73#[derive(Debug, Clone, Copy)]
74pub enum DeviceAssignmentMsiIova {
75    /// Device assignment does not expose an MSI IOVA reservation contract.
76    Unsupported,
77    /// The physical SMMU driver requires this exact range.
78    Fixed(MemoryRange),
79    /// The VMM selects the range and passes its base to the physical SMMU
80    /// implementation during partition creation.
81    Configurable,
82}
83
84/// A hypervisor backend capable of creating partitions.
85///
86/// # Recognized features
87///
88/// The `recognizes_*` methods report whether the backend acts on an optional
89/// partition request rather than silently ignoring it: it either honors the
90/// request or fails partition creation with a specific error. They let the code
91/// assembling a [`ProtoPartitionConfig`] reject a request up front when the
92/// backend has no concept of it, instead of the request being quietly dropped.
93/// Recognition is *not* a promise that the request succeeds — the backend may
94/// still reject it in combination with another feature, or fail later during
95/// partition creation. Each method defaults to `false`, so a new optional
96/// feature is unrecognized everywhere until a backend overrides its method.
97pub trait Hypervisor: 'static {
98    /// The prototype partition type.
99    type ProtoPartition<'a>: ProtoPartition<Partition = Self::Partition>;
100    /// The partition type.
101    type Partition;
102    /// The error type when creating the partition.
103    type Error: std::error::Error + Send + Sync + 'static;
104
105    /// Returns platform capabilities detected from the hypervisor.
106    ///
107    /// This is called before partition creation to query platform-specific
108    /// information needed for topology construction and firmware table
109    /// generation.
110    fn platform_info(&self) -> PlatformInfo;
111
112    /// Whether the backend recognizes a request to expose hardware
113    /// virtualization (VMX/SVM) to the guest so it can run its own hypervisor.
114    /// See the [`Hypervisor`] trait docs on recognized features.
115    fn recognizes_nested_virt(&self) -> bool {
116        false
117    }
118
119    /// Returns a new prototype partition from the given configuration.
120    fn new_partition<'a>(
121        &'a mut self,
122        config: ProtoPartitionConfig<'a>,
123    ) -> Result<Self::ProtoPartition<'a>, Self::Error>;
124}
125
126/// Isolation type for a partition.
127#[derive(Eq, PartialEq, Debug, Copy, Clone, Inspect)]
128pub enum IsolationType {
129    /// No isolation.
130    None,
131    /// Hypervisor based isolation.
132    Vbs,
133    /// Secure nested paging (AMD SEV-SNP) - hardware based isolation.
134    Snp,
135    /// Trust domain extensions (Intel TDX) - hardware based isolation.
136    Tdx,
137    /// Confidential Compute Architecture (ARM CCA) - hardware based isolation.
138    Cca,
139}
140
141impl IsolationType {
142    /// Returns true if the isolation type is not `None`.
143    pub fn is_isolated(&self) -> bool {
144        !matches!(self, Self::None)
145    }
146
147    /// Returns whether the isolation type is hardware-backed.
148    pub fn is_hardware_isolated(&self) -> bool {
149        matches!(self, Self::Snp | Self::Tdx | Self::Cca)
150    }
151}
152
153/// An unexpected isolation type was provided.
154#[derive(Debug)]
155pub struct UnexpectedIsolationType;
156
157impl IsolationType {
158    pub const fn from_hv(
159        value: hvdef::HvPartitionIsolationType,
160    ) -> Result<Self, UnexpectedIsolationType> {
161        match value {
162            hvdef::HvPartitionIsolationType::NONE => Ok(IsolationType::None),
163            hvdef::HvPartitionIsolationType::VBS => Ok(IsolationType::Vbs),
164            hvdef::HvPartitionIsolationType::SNP => Ok(IsolationType::Snp),
165            hvdef::HvPartitionIsolationType::TDX => Ok(IsolationType::Tdx),
166            hvdef::HvPartitionIsolationType::CCA => Ok(IsolationType::Cca),
167            _ => Err(UnexpectedIsolationType),
168        }
169    }
170
171    pub const fn to_hv(self) -> hvdef::HvPartitionIsolationType {
172        match self {
173            IsolationType::None => hvdef::HvPartitionIsolationType::NONE,
174            IsolationType::Vbs => hvdef::HvPartitionIsolationType::VBS,
175            IsolationType::Snp => hvdef::HvPartitionIsolationType::SNP,
176            IsolationType::Tdx => hvdef::HvPartitionIsolationType::TDX,
177            IsolationType::Cca => hvdef::HvPartitionIsolationType::CCA,
178        }
179    }
180}
181
182/// Page visibility types for isolated partitions.
183#[derive(Eq, PartialEq, Debug, Copy, Clone, Inspect)]
184pub enum PageVisibility {
185    /// The guest has exclusive access to the page, and no access from the host.
186    Exclusive,
187    /// The page has shared access with the guest and host.
188    Shared,
189}
190
191/// Initial page import type for isolated partitions.
192#[derive(Eq, PartialEq, Debug, Copy, Clone, Inspect)]
193pub enum InitialPageImportType {
194    /// A measured page with exclusive guest access.
195    Normal,
196    /// An unmeasured page with exclusive guest access.
197    NormalUnmeasured,
198    /// A page shared between the guest and host.
199    Shared,
200    /// A virtual processor context page.
201    VpContext,
202    /// An SNP secrets page.
203    Secrets,
204    /// An SNP CPUID page.
205    Cpuid,
206    /// An SNP CPUID extended state page.
207    CpuidExtendedState,
208}
209
210impl InitialPageImportType {
211    /// Returns the visibility implied by this import type.
212    pub fn page_visibility(self) -> PageVisibility {
213        match self {
214            Self::Shared => PageVisibility::Shared,
215            Self::Normal
216            | Self::NormalUnmeasured
217            | Self::VpContext
218            | Self::Secrets
219            | Self::Cpuid
220            | Self::CpuidExtendedState => PageVisibility::Exclusive,
221        }
222    }
223}
224
225/// Initial page import metadata for isolated partitions.
226#[derive(Eq, PartialEq, Debug, Clone)]
227pub struct InitialPageImport {
228    /// The guest physical range being imported.
229    pub range: MemoryRange,
230    /// The hypervisor-facing import type for this range.
231    pub import_type: InitialPageImportType,
232    /// Loader-provided debug tag identifying the source of this range.
233    pub tag: &'static str,
234}
235
236/// An opaque SNP virtual processor context.
237#[derive(Eq, PartialEq, Debug, Clone)]
238pub struct SnpVpContext {
239    /// The guest physical address associated with the context.
240    pub gpa: u64,
241    /// The virtual processor described by the context.
242    pub vp_index: VpIndex,
243    /// The complete 4-KiB VMSA page.
244    pub page: Box<[u8; 4096]>,
245}
246
247/// SNP ID block and authentication data supplied by an IGVM file.
248#[derive(Eq, PartialEq, Debug, Clone)]
249pub struct SnpIdBlock {
250    /// Whether the author key is enabled.
251    pub author_key_enabled: u8,
252    /// The launch digest supplied by the IGVM file.
253    pub launch_digest: [u8; 48],
254    /// The guest family identifier.
255    pub family_id: [u8; 16],
256    /// The guest image identifier.
257    pub image_id: [u8; 16],
258    /// The ID-block format version.
259    pub version: u32,
260    /// The guest security version number.
261    pub guest_svn: u32,
262    /// The ID-key algorithm.
263    pub id_key_algorithm: u32,
264    /// The author-key algorithm.
265    pub author_key_algorithm: u32,
266    /// The ID-block signature.
267    pub id_key_signature: x86defs::snp::SnpIdBlockSignature,
268    /// The ID public key.
269    pub id_public_key: x86defs::snp::SnpIdBlockPublicKey,
270    /// The author-key signature.
271    pub author_key_signature: x86defs::snp::SnpIdBlockSignature,
272    /// The author public key.
273    pub author_public_key: x86defs::snp::SnpIdBlockPublicKey,
274}
275
276/// Backend-neutral SNP launch configuration extracted from an IGVM file.
277#[derive(Eq, PartialEq, Debug, Clone)]
278pub struct SnpConfig {
279    /// The SNP guest policy.
280    pub policy: u64,
281    /// The highest VTL requested by the selected IGVM platform.
282    pub highest_vtl: u8,
283    /// The shared GPA boundary requested by the selected IGVM platform.
284    pub shared_gpa_boundary: u64,
285    /// Whether the IGVM contains relocation metadata.
286    pub has_relocation: bool,
287    /// Opaque virtual processor contexts in file order.
288    pub vp_contexts: Vec<SnpVpContext>,
289    /// Optional ID block and authentication data.
290    pub id_block: Option<SnpIdBlock>,
291}
292
293/// Isolation configuration needed before a backend creates a partition.
294#[derive(Eq, PartialEq, Debug, Clone)]
295pub enum ProtoPartitionIsolation {
296    /// No isolation.
297    None,
298    /// Hypervisor-based isolation.
299    Vbs,
300    /// AMD SEV-SNP, optionally with launch configuration from an IGVM file.
301    Snp(Option<Box<SnpConfig>>),
302    /// Intel Trust Domain Extensions.
303    Tdx,
304    /// Arm Confidential Compute Architecture.
305    Cca,
306}
307
308impl ProtoPartitionIsolation {
309    /// Returns the simple isolation classification.
310    pub fn isolation_type(&self) -> IsolationType {
311        match self {
312            Self::None => IsolationType::None,
313            Self::Vbs => IsolationType::Vbs,
314            Self::Snp(_) => IsolationType::Snp,
315            Self::Tdx => IsolationType::Tdx,
316            Self::Cca => IsolationType::Cca,
317        }
318    }
319
320    /// Returns whether the partition is isolated.
321    pub fn is_isolated(&self) -> bool {
322        self.isolation_type().is_isolated()
323    }
324}
325
326impl From<IsolationType> for ProtoPartitionIsolation {
327    fn from(value: IsolationType) -> Self {
328        match value {
329            IsolationType::None => Self::None,
330            IsolationType::Vbs => Self::Vbs,
331            IsolationType::Snp => Self::Snp(None),
332            IsolationType::Tdx => Self::Tdx,
333            IsolationType::Cca => Self::Cca,
334        }
335    }
336}
337
338/// Prototype partition creation configuration.
339pub struct ProtoPartitionConfig<'a> {
340    /// The set of VPs to create.
341    pub processor_topology: &'a ProcessorTopology,
342    /// Microsoft hypervisor guest interface configuration.
343    pub hv_config: Option<HvConfig>,
344    /// VM time access.
345    pub vmtime: &'a VmTimeSource,
346    /// Isolation type and optional backend configuration for this partition.
347    pub isolation: ProtoPartitionIsolation,
348    /// Expose hardware virtualization (VMX/SVM) to the guest so that it can run
349    /// its own hypervisor.
350    ///
351    /// The code assembling this config must only set this when the chosen
352    /// backend recognizes it via [`Hypervisor::recognizes_nested_virt`]; a
353    /// backend that receives an unrecognized request may silently ignore it.
354    pub nested_virt: bool,
355    /// Device-assignment MSI IOVA reservation selected for this partition.
356    #[cfg(guest_arch = "aarch64")]
357    pub device_assignment_msi_iova_range: Option<MemoryRange>,
358}
359
360/// Partition creation configuration.
361pub struct PartitionConfig<'a> {
362    /// The guest memory layout.
363    pub mem_layout: &'a MemoryLayout,
364    /// Guest memory access.
365    pub guest_memory: &'a GuestMemory,
366    /// Cpuid leaves to add to the default CPUID results.
367    pub cpuid: &'a [CpuidLeaf],
368    /// The offset of the VTL0 alias map. This maps VTL0's view of memory into
369    /// VTL2 at the specified offset (which must be a power of 2).
370    pub vtl0_alias_map: Option<u64>,
371    /// An optional resolver used to prepare guest-memory backing on demand when
372    /// the partition delivers memory-access faults back to the VMM.
373    ///
374    /// This is set only when the backend reports
375    /// [`ProtoPartition::supports_memory_fault_resolution`]. The backend calls
376    /// it from its memory-fault handler to commit lazily-backed pages and to
377    /// learn the (possibly widened) GPA range to map; the backend retains the
378    /// final per-page safety decision over the returned range.
379    pub fault_resolver: Option<Arc<dyn ResolveMemoryFault>>,
380}
381
382/// Prepares guest-memory backing to resolve a memory-access fault, and reports
383/// the GPA range the partition should map in response.
384///
385/// This is implemented by the memory backing and called by hypervisor backends
386/// (e.g. WHP) that forward guest memory-access faults to the VMM. It lets the
387/// backing commit lazily-backed pages and opportunistically widen the mapped
388/// range to a large page (soft large pages), while the backend keeps the final
389/// per-page safety decision over the returned range.
390pub trait ResolveMemoryFault: Send + Sync {
391    /// Prepares backing for the faulting range `fault` and returns the GPA range
392    /// the partition should map.
393    ///
394    /// The caller passes the range it needs backed (expressed in whatever page
395    /// granularity the backend uses), so this layer never needs to know the
396    /// guest page size. The returned range is always a superset of `fault`,
397    /// clamped to a single uniform RAM region. It is widened (e.g. to 2 MB) only
398    /// on the first fault of a large-page-eligible region that fully contains
399    /// `fault`; otherwise `fault` is returned unchanged. Subsequent faults of an
400    /// already-attempted region are not widened.
401    fn resolve(
402        &self,
403        fault: MemoryRange,
404        write: bool,
405    ) -> Result<MemoryRange, GuestMemoryBackingError>;
406}
407
408/// Trait for a prototype partition, one that is partially created but still
409/// needs final configuration.
410///
411/// This is separate from the partition so that it can be queried to determine
412/// the final partition configuration.
413pub trait ProtoPartition {
414    /// The partition type.
415    type Partition: Partition;
416    /// The VP binder type.
417    type ProcessorBinder: 'static + BindProcessor + Send;
418    /// The error type when creating the partition.
419    type Error: std::error::Error + Send + Sync + 'static;
420
421    /// The maximum physical address width that processors and devices for this
422    /// partition can access.
423    ///
424    /// This may be smaller than what is reported to the guest via architectural
425    /// interfaces by default, and it may be larger or smaller than what the VMM
426    /// ultimately chooses to report to the guest.
427    fn max_physical_address_size(&self) -> u8;
428
429    /// Whether the partition delivers guest-memory-access faults back to the
430    /// VMM and resolves them through a [`ResolveMemoryFault`] supplied in
431    /// [`PartitionConfig::fault_resolver`].
432    ///
433    /// Defaults to `false`. A backend that forwards memory faults to the VMM
434    /// (e.g. WHP) overrides this to `true`. The code assembling
435    /// [`PartitionConfig`] uses it to decide whether to supply a resolver, and
436    /// the memory backing uses it to select a lazy commit strategy.
437    fn supports_memory_fault_resolution(&self) -> bool {
438        false
439    }
440
441    /// Constructs the full partition.
442    fn build(
443        self,
444        config: PartitionConfig<'_>,
445    ) -> Result<(Self::Partition, Vec<Self::ProcessorBinder>), Self::Error>;
446}
447
448/// Trait used to bind a processor to the current thread.
449pub trait BindProcessor {
450    /// The processor object.
451    type Processor<'a>: Processor
452    where
453        Self: 'a;
454
455    /// A binding error.
456    type Error: std::error::Error + Send + Sync + 'static;
457
458    /// Binds the processor to the current thread.
459    fn bind(&mut self) -> Result<Self::Processor<'_>, Self::Error>;
460}
461
462/// Policy for the partition when mapping VTL0 memory late.
463#[derive(Eq, PartialEq, Debug, Copy, Clone)]
464pub enum LateMapVtl0MemoryPolicy {
465    /// Halt execution of the VP if VTL0 memory is accessed.
466    Halt,
467    /// Log the error but emulate the access with the instruction emulator.
468    Log,
469    /// Inject an exception into the guest.
470    InjectException,
471}
472
473/// Which ranges VTL2 is allowed to access before VTL0 ram is mapped.
474#[derive(Debug, Clone)]
475pub enum LateMapVtl0AllowedRanges {
476    /// Ask the memory layout what the vtl2_ram ranges are.
477    MemoryLayout,
478    /// These specific ranges are allowed.
479    Ranges(Vec<MemoryRange>),
480}
481
482/// Config used to determine late mapping VTL0 memory.
483#[derive(Debug, Clone)]
484pub struct LateMapVtl0MemoryConfig {
485    /// What ranges VTL2 are allowed to access before VTL0 memory is mapped.
486    /// Generally this consists of the ranges representing VTL2 ram.
487    pub allowed_ranges: LateMapVtl0AllowedRanges,
488    /// The policy for the partition mapping VTL0 memory late.
489    pub policy: LateMapVtl0MemoryPolicy,
490}
491
492/// VTL2 configuration.
493#[derive(Debug)]
494pub struct Vtl2Config {
495    /// If set, map VTL0 memory late after VTL2 has started. The current
496    /// heuristic is to defer mapping VTL0 memory until the first
497    /// [`hvdef::HypercallCode::HvCallModifyVtlProtectionMask`] hypercall is
498    /// made.
499    ///
500    /// Accesses before memory is mapped is determined by the specified config.
501    pub late_map_vtl0_memory: Option<LateMapVtl0MemoryConfig>,
502}
503
504/// Hypervisor configuration.
505#[derive(Debug)]
506pub struct HvConfig {
507    /// Allow device assignment on the partition.
508    pub allow_device_assignment: bool,
509    /// Enable VTL2 support if set. Additional options are described by
510    /// [Vtl2Config].
511    pub vtl2: Option<Vtl2Config>,
512}
513
514/// Methods for manipulating a VM partition.
515pub trait Partition: 'static + Hv1 + Inspect + Send + Sync {
516    /// Returns a trait object for initial page imports during the initial start
517    /// flow.
518    fn supports_initial_page_acceptance(
519        &self,
520    ) -> Option<&dyn AcceptInitialPages<Error = <Self as Hv1>::Error>> {
521        None
522    }
523
524    /// Returns a trait object to reset the partition, if supported.
525    fn supports_reset(&self) -> Option<&dyn ResetPartition<Error = <Self as Hv1>::Error>>;
526
527    /// Returns a trait object to reset VTL state, if supported.
528    fn supports_vtl_scrub(&self) -> Option<&dyn ScrubVtl<Error = <Self as Hv1>::Error>> {
529        None
530    }
531
532    /// Returns an interface for registering MMIO doorbells for this partition.
533    ///
534    /// Not all partitions support this.
535    fn doorbell_registration(
536        self: &Arc<Self>,
537        minimum_vtl: Vtl,
538    ) -> Option<Arc<dyn DoorbellRegistration>> {
539        let _ = minimum_vtl;
540        None
541    }
542
543    /// Requests an MSI for the specified VTL.
544    ///
545    /// On x86, the MSI format is the architectural APIC format.
546    ///
547    /// On ARM64, the MSI format is currently not defined, since we only support
548    /// Hyper-V-style VMs (which use synthetic MSIs via VPCI). In the future, we
549    /// may want to support either or both SPI- and ITS+LPI-based MSIs.
550    fn request_msi(&self, vtl: Vtl, request: MsiRequest);
551
552    /// Returns an MSI interrupt target for this partition, which can be used to
553    /// create MSI interrupts.
554    ///
555    /// Not all partitions support this.
556    fn as_signal_msi(&self, vtl: Vtl) -> Option<Arc<dyn SignalMsi>> {
557        let _ = vtl;
558        None
559    }
560
561    /// Returns an irqfd routing interface for this partition.
562    ///
563    /// irqfd allows the kernel to inject MSIs directly into the guest when an
564    /// eventfd is signaled, without a userspace transition. This is used for
565    /// device passthrough with VFIO.
566    ///
567    /// Not all partitions support this.
568    fn irqfd(&self) -> Option<Arc<dyn IrqFd>> {
569        None
570    }
571
572    /// Get the partition capabilities for this partition.
573    fn caps(&self) -> &PartitionCapabilities;
574
575    /// Forces the run_vp call to yield to the scheduler (i.e. return
576    /// Poll::Pending).
577    fn request_yield(&self, vp_index: VpIndex);
578}
579
580/// X86-specific partition methods.
581pub trait X86Partition: Partition {
582    /// Gets the IO-APIC routing control for VTL0.
583    fn ioapic_routing(&self) -> Arc<dyn IoApicRouting>;
584
585    /// Pulses the specified APIC's local interrupt line (0 or 1).
586    fn pulse_lint(&self, vp_index: VpIndex, vtl: Vtl, lint: u8);
587}
588
589/// ARM64-specific partition methods.
590pub trait Aarch64Partition: Partition {
591    /// Returns an interface for accessing the GIC interrupt controller for `vtl`.
592    fn control_gic(&self, vtl: Vtl) -> Arc<dyn ControlGic>;
593}
594
595/// Extension trait for accepting initial pages.
596pub trait AcceptInitialPages {
597    type Error: std::error::Error;
598
599    /// Accepts initial pages on behalf of the guest.
600    ///
601    /// This can only be used during the load path during partition start to
602    /// accept pages on behalf of the guest that were set as part of the load
603    /// process. The host virtstack cannot accept pages on behalf of the guest
604    /// once it has started running.
605    fn accept_initial_pages(&self, pages: &[InitialPageImport]) -> Result<(), Self::Error>;
606}
607
608/// Extension trait for resetting the partition.
609pub trait ResetPartition {
610    type Error: std::error::Error;
611
612    /// Resets the partition, restoring all partition state to the initial
613    /// state.
614    ///
615    /// The caller must ensure that no VPs are running when this is called.
616    ///
617    /// This resets partition-level (VM-wide) state. After this completes,
618    /// the caller dispatches [`Processor::reset`] to each VP's thread to
619    /// reset per-VP state (registers, APIC, synic message queues, etc.).
620    ///
621    /// If this fails, the partition is in a bad state and cannot be resumed
622    /// until a subsequent reset call succeeds.
623    fn reset(&self) -> Result<(), Self::Error>;
624}
625
626/// Extension trait for scrubbing higher VTL state while leaving lower VTLs
627/// untouched.
628pub trait ScrubVtl {
629    type Error: std::error::Error;
630
631    /// Scrubs partition and VP state for `vtl`. This is useful for servicing
632    /// and restarting a higher VTL without touching the lower VTL.
633    ///
634    /// The caller must ensure that no VPs are running when this is called.
635    ///
636    /// This scrubs partition-level state. After this completes, the caller
637    /// dispatches [`Processor::scrub`] to each VP's thread to scrub per-VP
638    /// state for the specified VTL.
639    ///
640    /// Note that this does not reset page protections. This is necessary
641    /// because there may be devices assigned to lower VTLs, and they should not
642    /// be able to DMA to higher VTL memory during servicing.
643    fn scrub(&self, vtl: Vtl) -> Result<(), Self::Error>;
644}
645
646/// Provides access to partition state for save, restore, and reset.
647///
648/// This is not part of [`Partition`] because some scenarios do not require such
649/// access.
650pub trait PartitionAccessState {
651    type StateAccess<'a>: crate::vm::AccessVmState
652    where
653        Self: 'a;
654
655    /// Returns an object to access VM state for the specified VTL.
656    fn access_state(&self, vtl: Vtl) -> Self::StateAccess<'_>;
657}
658
659/// Change memory protections for lower VTLs. This can be used to share memory
660/// with a lower VTL or make memory accesses trigger an intercept. This is
661/// intended for dynamic state as initial memory protections are applied at VM
662/// start.
663pub trait VtlMemoryProtection {
664    /// Sets lower VTL permissions on a physical page.
665    ///
666    /// TODO: To remain generic may want to replace hvdef::HvMapGpaFlags with
667    ///       something else.
668    fn modify_vtl_page_setting(&self, pfn: u64, flags: hvdef::HvMapGpaFlags) -> anyhow::Result<()>;
669}
670
671pub trait Processor: InspectMut {
672    type StateAccess<'a>: crate::vp::AccessVpState
673    where
674        Self: 'a;
675
676    /// Sets the debug state: conditions under which the VP should exit for
677    /// debugging the guest. This including single stepping and hardware
678    /// breakpoints.
679    ///
680    /// TODO: generalize for non-x86 architectures.
681    fn set_debug_state(
682        &mut self,
683        vtl: Vtl,
684        state: Option<&DebugState>,
685    ) -> Result<(), <Self::StateAccess<'_> as crate::vp::AccessVpState>::Error>;
686
687    /// Runs the VP.
688    ///
689    /// Although this is an async function, it may block synchronously until
690    /// [`Partition::request_yield`] is called for this VP. Then its future must
691    /// return [`Poll::Pending`] at least once.
692    ///
693    /// Returns when an error occurs, the VP halts, or the VP is requested to
694    /// stop via `stop`.
695    #[expect(async_fn_in_trait)] // don't need or want Send bound
696    async fn run_vp(
697        &mut self,
698        stop: StopVp<'_>,
699        dev: &impl CpuIo,
700    ) -> Result<Infallible, VpHaltReason>;
701
702    /// Without running the VP, flushes any asynchronous requests from other
703    /// processors or objects that might affect this state, so that the object
704    /// can be saved/restored correctly.
705    fn flush_async_requests(&mut self);
706
707    /// Returns whether the specified VTL can be inspected on this processor.
708    ///
709    /// VTL0 is always inspectable.
710    fn vtl_inspectable(&self, vtl: Vtl) -> bool {
711        vtl == Vtl::Vtl0
712    }
713
714    /// Resets per-VP state after a partition-level reset.
715    ///
716    /// Called on each VP's thread while VPs are stopped, after
717    /// [`ResetPartition::reset`] has completed.
718    ///
719    /// The default implementation panics. Backends that support
720    /// [`ResetPartition`] must override this.
721    #[expect(unreachable_code)]
722    fn reset(&mut self) -> Result<(), impl std::error::Error + Send + Sync + 'static> {
723        Ok::<(), Infallible>(unimplemented!(
724            "Processor::reset not implemented for this backend"
725        ))
726    }
727
728    /// Scrubs per-VP state for a specific VTL.
729    ///
730    /// Called on each VP's thread while VPs are stopped, after
731    /// [`ScrubVtl::scrub`] has completed.
732    ///
733    /// The default implementation panics. Backends that support
734    /// [`ScrubVtl`] must override this.
735    #[expect(unreachable_code)]
736    fn scrub(&mut self, _vtl: Vtl) -> Result<(), impl std::error::Error + Send + Sync + 'static> {
737        Ok::<(), Infallible>(unimplemented!(
738            "Processor::scrub not implemented for this backend"
739        ))
740    }
741
742    fn access_state(&mut self, vtl: Vtl) -> Self::StateAccess<'_>;
743}
744
745/// A source for [`StopVp`].
746pub struct StopVpSource {
747    stop: Cell<bool>,
748    waker: Cell<Option<Waker>>,
749}
750
751impl StopVpSource {
752    /// Creates a new source.
753    pub fn new() -> Self {
754        Self {
755            stop: Cell::new(false),
756            waker: Cell::new(None),
757        }
758    }
759
760    /// Returns an object to wait for stops.
761    pub fn checker(&self) -> StopVp<'_> {
762        StopVp { source: self }
763    }
764
765    /// Initiates a VP stop.
766    ///
767    /// After this, calls to [`StopVp::check`] or [`StopVp::until_stop`] will
768    /// fail.
769    pub fn stop(&self) {
770        self.stop.set(true);
771        if let Some(waker) = self.waker.take() {
772            waker.wake();
773        }
774    }
775
776    /// Returns whether [`Self::stop`] has been called.
777    pub fn is_stopping(&self) -> bool {
778        self.stop.get()
779    }
780}
781
782/// Object to check for VP stop requests.
783pub struct StopVp<'a> {
784    source: &'a StopVpSource,
785}
786
787/// An error result that the VP stopped due to request.
788#[derive(Debug)]
789pub struct VpStopped(());
790
791impl StopVp<'_> {
792    /// Returns `Err(VpStopped(_))` if the VP should stop.
793    pub fn check(&self) -> Result<(), VpStopped> {
794        if self.source.stop.get() {
795            Err(VpStopped(()))
796        } else {
797            Ok(())
798        }
799    }
800
801    /// Runs `fut` until it completes or the VP should stop.
802    pub async fn until_stop<Fut: Future>(&mut self, fut: Fut) -> Result<Fut::Output, VpStopped> {
803        let mut fut = pin!(fut);
804        poll_fn(|cx| match fut.as_mut().poll(cx) {
805            Poll::Ready(r) => Poll::Ready(Ok(r)),
806            Poll::Pending => {
807                self.check()?;
808                self.source.waker.set(Some(cx.waker().clone()));
809                Poll::Pending
810            }
811        })
812        .await
813    }
814}
815
816/// An object that can be polled to see if a yield has been requested.
817#[derive(Debug)]
818pub struct NeedsYield {
819    yield_requested: AtomicBool,
820}
821
822impl NeedsYield {
823    /// Creates a new object.
824    pub fn new() -> Self {
825        Self {
826            yield_requested: false.into(),
827        }
828    }
829
830    /// Requests a yield.
831    ///
832    /// Returns whether a signal is necessary to ensure that the task yields
833    /// soon.
834    pub fn request_yield(&self) -> bool {
835        !self.yield_requested.swap(true, Ordering::Release)
836    }
837
838    /// Yields execution to the executor if `request_yield` has been called
839    /// since the last call to `maybe_yield`.
840    pub async fn maybe_yield(&self) {
841        poll_fn(|cx| {
842            if self.yield_requested.load(Ordering::Acquire) {
843                // Wake this task again to ensure it runs again.
844                cx.waker().wake_by_ref();
845                self.yield_requested.store(false, Ordering::Relaxed);
846                Poll::Pending
847            } else {
848                Poll::Ready(())
849            }
850        })
851        .await
852    }
853}
854
855/// The reason that [`Processor::run_vp`] returned.
856#[derive(Debug)]
857pub enum VpHaltReason {
858    /// The processor was requested to stop.
859    Stop(VpStopped),
860    /// The processor task should be restarted, possibly on a different thread.
861    Cancel,
862    /// The processor initiated a power off.
863    PowerOff,
864    /// The processor initiated a reboot.
865    Reset,
866    /// The processor initiated a hibernation.
867    Hibernate,
868    /// The processor triple faulted.
869    TripleFault {
870        /// The faulting VTL.
871        // FUTURE: move VTL state into `AccessVpState``.
872        vtl: Vtl,
873    },
874    /// Debugger single step.
875    SingleStep,
876    /// Debugger hardware breakpoint.
877    HwBreak(HardwareBreakpoint),
878}
879
880impl From<VpStopped> for VpHaltReason {
881    fn from(stop: VpStopped) -> Self {
882        Self::Stop(stop)
883    }
884}
885
886pub trait PartitionMemoryMapper {
887    /// Returns a memory mapper for the partition backing `vtl`.
888    fn memory_mapper(&self, vtl: Vtl) -> Arc<dyn PartitionMemoryMap>;
889}
890
891pub trait Hv1 {
892    type Error: std::error::Error + Send + Sync + 'static;
893    type Device: MapVpciInterrupt + SignalMsi;
894
895    fn reference_time_source(&self) -> Option<ReferenceTimeSource>;
896
897    fn new_virtual_device(
898        &self,
899    ) -> Option<&dyn DeviceBuilder<Device = Self::Device, Error = Self::Error>>;
900
901    /// Returns the partition's synic port access, or an error if the
902    /// backend cannot support synic in its current configuration.
903    fn synic(&self) -> anyhow::Result<Arc<dyn vmcore::synic::SynicPortAccess>>;
904}
905
906pub trait DeviceBuilder: Hv1 {
907    fn build(&self, vtl: Vtl, device_id: u64) -> Result<Self::Device, Self::Error>;
908}
909
910pub enum UnimplementedDevice {}
911
912impl MapVpciInterrupt for UnimplementedDevice {
913    async fn register_interrupt(
914        &self,
915        _vector_count: u32,
916        _params: &VpciInterruptParameters<'_>,
917    ) -> Result<MsiAddressData, RegisterInterruptError> {
918        match *self {}
919    }
920
921    async fn unregister_interrupt(&self, _address: u64, _data: u32) {
922        match *self {}
923    }
924}
925
926impl SignalMsi for UnimplementedDevice {
927    fn signal_msi(&self, _devid: Option<u32>, _address: u64, _data: u32) {
928        match *self {}
929    }
930}
931
932/// MNF support routines for the emulator
933pub trait EmulatorMonitorSupport {
934    /// Check if the specified write is inside the monitor page, and signal the associated
935    /// connection ID if it is.
936    #[must_use]
937    fn check_write(&self, gpa: u64, bytes: &[u8]) -> bool;
938
939    /// Check if the specified read is inside the monitor page, and fill the provided buffer
940    /// if it is.
941    #[must_use]
942    fn check_read(&self, gpa: u64, bytes: &mut [u8]) -> bool;
943}