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}