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}