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