Skip to main content

virt_mshv_vtl/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Implementation of the Underhill hypervisor backend, which uses
5//! `/dev/mshv_vtl` to interact with the Microsoft hypervisor while running in
6//! VTL2.
7
8#![cfg(all(guest_is_native, target_os = "linux"))]
9
10mod devmsr;
11
12cfg_if::cfg_if!(
13    if #[cfg(guest_arch = "x86_64")] {
14        mod cvm_cpuid;
15
16        pub use crate::processor::mshv::x64::HypervisorBackedX86 as HypervisorBacked;
17        pub use processor::snp::SnpBacked;
18        pub use processor::tdx::TdxBacked;
19
20        use bitvec::prelude::BitArray;
21        use bitvec::prelude::Lsb0;
22        use crate::processor::mshv::x64::HypervisorBackedX86Shared as HypervisorBackedShared;
23        use devmsr::MsrDevice;
24        use processor::LapicState;
25        use processor::snp::SnpBackedShared;
26        use processor::tdx::TdxBackedShared;
27        use std::arch::x86_64::CpuidResult;
28        use virt::CpuidLeaf;
29        use virt::X86Partition;
30        use virt::state::StateElement;
31        use virt::vp::MpState;
32        use virt_support_apic::LocalApicSet;
33
34        /// Bitarray type for representing IRR bits in a x86-64 APIC
35        /// Each bit represent the 256 possible vectors.
36        type IrrBitmap = BitArray<[u32; 8], Lsb0>;
37    } else if #[cfg(guest_arch = "aarch64")] {
38        pub use crate::processor::mshv::arm64::HypervisorBackedArm64 as HypervisorBacked;
39        pub use processor::cca::CcaBacked;
40
41        use aarch64defs::Vendor;
42        use crate::processor::mshv::arm64::HypervisorBackedArm64Shared as HypervisorBackedShared;
43        use processor::cca::CcaBackedShared;
44        use safe_intrinsics::read_cntfrq_el0;
45    }
46);
47
48mod processor;
49use hv1_emulator::hv::ProcessorVtlHv;
50pub use processor::Backing;
51pub use processor::UhProcessor;
52
53use crate::processor::HardwareIsolatedBacking;
54use anyhow::Context as AnyhowContext;
55use bitfield_struct::bitfield;
56use bitvec::boxed::BitBox;
57use bitvec::vec::BitVec;
58use cvm_tracing::CVM_ALLOWED;
59use guestmem::GuestMemory;
60use guestmem::GuestMemoryBackingError;
61use hcl::GuestVtl;
62use hcl::ioctl::Hcl;
63use hcl::ioctl::SetVsmPartitionConfigError;
64use hv1_emulator::hv::GlobalHv;
65use hv1_emulator::message_queues::MessageQueues;
66use hv1_emulator::synic::GlobalSynic;
67use hv1_emulator::synic::SintProxied;
68use hv1_structs::VtlArray;
69use hvdef::GuestCrashCtl;
70use hvdef::HV_PAGE_SHIFT;
71use hvdef::HV_PAGE_SIZE;
72use hvdef::HV_PAGE_SIZE_USIZE;
73use hvdef::HvError;
74use hvdef::HvMapGpaFlags;
75use hvdef::HvPartitionPrivilege;
76use hvdef::HvRegisterName;
77use hvdef::HvRegisterVsmPartitionConfig;
78use hvdef::HvRegisterVsmPartitionStatus;
79use hvdef::Vtl;
80use hvdef::hypercall::HV_INTERCEPT_ACCESS_MASK_EXECUTE;
81use hvdef::hypercall::HV_INTERCEPT_ACCESS_MASK_NONE;
82use hvdef::hypercall::HV_INTERCEPT_ACCESS_MASK_READ_WRITE;
83use hvdef::hypercall::HV_INTERCEPT_ACCESS_MASK_WRITE;
84use hvdef::hypercall::HostVisibilityType;
85use hvdef::hypercall::HvGuestOsId;
86use hvdef::hypercall::HvInputVtl;
87use hvdef::hypercall::HvInterceptParameters;
88use hvdef::hypercall::HvInterceptType;
89use inspect::Inspect;
90use inspect::InspectMut;
91use memory_range::MemoryRange;
92use pal::unix::affinity;
93use pal::unix::affinity::CpuSet;
94use pal_async::driver::Driver;
95use pal_async::driver::SpawnDriver;
96use pal_uring::IdleControl;
97use parking_lot::Mutex;
98use parking_lot::RwLock;
99use processor::BackingSharedParams;
100use processor::SidecarExitReason;
101use sidecar_client::NewSidecarClientError;
102use std::collections::HashMap;
103use std::ops::RangeInclusive;
104use std::os::fd::AsRawFd;
105use std::sync::Arc;
106use std::sync::Weak;
107use std::sync::atomic::AtomicBool;
108use std::sync::atomic::AtomicU8;
109use std::sync::atomic::AtomicU32;
110use std::sync::atomic::AtomicU64;
111use std::sync::atomic::Ordering;
112use std::task::Waker;
113use thiserror::Error;
114use user_driver::DmaClient;
115use virt::IsolationType;
116use virt::PartitionCapabilities;
117use virt::VpIndex;
118use virt::irqcon::IoApicRouting;
119use virt::irqcon::MsiRequest;
120use virt::x86::apic_software_device::ApicSoftwareDevices;
121use vm_topology::memory::MemoryLayout;
122use vm_topology::processor::ProcessorTopology;
123use vm_topology::processor::TargetVpInfo;
124use vmcore::monitor::MonitorPage;
125use vmcore::reference_time::GetReferenceTime;
126use vmcore::reference_time::ReferenceTimeResult;
127use vmcore::reference_time::ReferenceTimeSource;
128use vmcore::vmtime::VmTimeSource;
129use x86defs::snp::REG_TWEAK_BITMAP_OFFSET;
130use x86defs::snp::REG_TWEAK_BITMAP_SIZE;
131use x86defs::tdx::TdCallResult;
132use zerocopy::FromBytes;
133use zerocopy::FromZeros;
134use zerocopy::Immutable;
135use zerocopy::IntoBytes;
136use zerocopy::KnownLayout;
137
138/// General error returned by operations.
139#[derive(Error, Debug)]
140#[expect(missing_docs)]
141pub enum Error {
142    #[error("hcl error")]
143    Hcl(#[source] hcl::ioctl::Error),
144    #[error("failed to open sidecar client")]
145    Sidecar(#[source] NewSidecarClientError),
146    #[error("failed to install {0:?} intercept: {1:?}")]
147    InstallIntercept(HvInterceptType, HvError),
148    #[error("failed to query hypervisor register {0:#x?}")]
149    Register(HvRegisterName, #[source] HvError),
150    #[error("failed to set vsm partition config register")]
151    VsmPartitionConfig(#[source] SetVsmPartitionConfigError),
152    #[error("failed to create virtual device")]
153    NewDevice(#[source] virt::x86::apic_software_device::DeviceIdInUse),
154    #[error("failed to create cpuid tables for cvm")]
155    #[cfg(guest_arch = "x86_64")]
156    CvmCpuid(#[source] cvm_cpuid::CpuidResultsError),
157    #[error("failed to update hypercall msr")]
158    UpdateHypercallMsr,
159    #[error("failed to update reference tsc msr")]
160    UpdateReferenceTsc,
161    #[error("failed to map overlay page")]
162    MapOverlay(#[source] std::io::Error),
163    #[error("failed to allocate shared visibility pages for overlay")]
164    AllocateSharedVisOverlay(#[source] anyhow::Error),
165    #[error("failed to open msr device")]
166    OpenMsr(#[source] std::io::Error),
167    #[error("cpuid did not contain valid TSC frequency information")]
168    BadCpuidTsc,
169    #[error("failed to read tsc frequency")]
170    ReadTscFrequency(#[source] std::io::Error),
171    #[error(
172        "tsc frequency mismatch between hypervisor ({hv}) and hardware {hw}, exceeds allowed error {allowed_error}"
173    )]
174    TscFrequencyMismatch {
175        hv: u64,
176        hw: u64,
177        allowed_error: u64,
178    },
179    #[error("failed to set vsm partition config: {0:?}")]
180    FailedToSetL2Ctls(TdCallResult),
181    #[error("debugging is configured but the binary does not have the gdb feature")]
182    InvalidDebugConfiguration,
183    #[error("failed to allocate TLB flush page")]
184    AllocateTlbFlushPage(#[source] anyhow::Error),
185    #[error("host does not support required cpu capabilities")]
186    Capabilities(virt::PartitionCapabilitiesError),
187    #[error("failed to get register")]
188    GetReg(#[source] hcl::ioctl::register::GetRegError),
189    #[error("failed to set register")]
190    SetReg(#[source] hcl::ioctl::register::SetRegError),
191}
192
193/// Error revoking guest VSM.
194#[derive(Error, Debug)]
195#[expect(missing_docs)]
196pub enum RevokeGuestVsmError {
197    #[error("failed to set vsm config")]
198    SetGuestVsmConfig(#[source] hcl::ioctl::register::SetRegError),
199    #[error("VTL 1 is already enabled")]
200    Vtl1AlreadyEnabled,
201}
202
203/// Underhill partition.
204#[derive(Inspect)]
205pub struct UhPartition {
206    #[inspect(flatten)]
207    inner: Arc<UhPartitionInner>,
208    // TODO: remove this extra indirection by refactoring some traits.
209    #[inspect(skip)]
210    interrupt_targets: VtlArray<Arc<UhInterruptTarget>, 2>,
211    #[inspect(skip)]
212    synic_ports: Arc<virt::synic::SynicPorts<UhPartitionInner>>,
213}
214
215/// Underhill partition.
216#[derive(Inspect)]
217#[inspect(extra = "UhPartitionInner::inspect_extra")]
218struct UhPartitionInner {
219    #[inspect(skip)]
220    hcl: Hcl,
221    #[inspect(skip)] // inspected separately
222    vps: Vec<UhVpInner>,
223    irq_routes: virt::irqcon::IrqRoutes,
224    caps: PartitionCapabilities,
225    #[inspect(skip)] // handled in `inspect_extra`
226    enter_modes: Mutex<EnterModes>,
227    #[inspect(skip)]
228    enter_modes_atomic: AtomicU8,
229    #[cfg(guest_arch = "x86_64")]
230    cpuid: virt::CpuidLeafSet,
231    lower_vtl_memory_layout: MemoryLayout,
232    gm: VtlArray<GuestMemory, 2>,
233    vtl0_kernel_exec_gm: GuestMemory,
234    vtl0_user_exec_gm: GuestMemory,
235    #[cfg_attr(guest_arch = "aarch64", expect(dead_code))]
236    #[inspect(skip)]
237    crash_notification_send: mesh::Sender<VtlCrash>,
238    monitor_page: MonitorPage,
239    #[inspect(skip)]
240    allocated_monitor_page: Mutex<Option<user_driver::memory::MemoryBlock>>,
241    software_devices: Option<ApicSoftwareDevices>,
242    #[inspect(skip)]
243    vmtime: VmTimeSource,
244    isolation: IsolationType,
245    #[inspect(with = "inspect::AtomicMut")]
246    no_sidecar_hotplug: AtomicBool,
247    use_mmio_hypercalls: bool,
248    backing_shared: BackingShared,
249    intercept_debug_exceptions: bool,
250    #[cfg(guest_arch = "x86_64")]
251    // N.B For now, only one device vector table i.e. for VTL0 only
252    #[inspect(hex, with = "|x| inspect::iter_by_index(x.read().into_inner())")]
253    device_vector_table: RwLock<IrrBitmap>,
254    vmbus_relay: bool,
255    synic_ports: virt::synic::SynicPortMap,
256}
257
258#[derive(Inspect)]
259#[inspect(untagged)]
260enum BackingShared {
261    Hypervisor(#[inspect(flatten)] HypervisorBackedShared),
262    #[cfg(guest_arch = "x86_64")]
263    Snp(#[inspect(flatten)] SnpBackedShared),
264    #[cfg(guest_arch = "x86_64")]
265    Tdx(#[inspect(flatten)] TdxBackedShared),
266    #[cfg(guest_arch = "aarch64")]
267    Cca(#[inspect(flatten)] Box<CcaBackedShared>),
268}
269
270impl BackingShared {
271    fn new(
272        isolation: IsolationType,
273        partition_params: &UhPartitionNewParams<'_>,
274        backing_shared_params: BackingSharedParams<'_>,
275    ) -> Result<BackingShared, Error> {
276        Ok(match isolation {
277            IsolationType::None | IsolationType::Vbs => {
278                assert!(backing_shared_params.cvm_state.is_none());
279                BackingShared::Hypervisor(HypervisorBackedShared::new(
280                    partition_params,
281                    backing_shared_params,
282                )?)
283            }
284            #[cfg(guest_arch = "x86_64")]
285            IsolationType::Snp => BackingShared::Snp(SnpBackedShared::new(
286                partition_params,
287                backing_shared_params,
288            )?),
289            #[cfg(guest_arch = "x86_64")]
290            IsolationType::Tdx => BackingShared::Tdx(TdxBackedShared::new(
291                partition_params,
292                backing_shared_params,
293            )?),
294            #[cfg(guest_arch = "aarch64")]
295            IsolationType::Cca => {
296                BackingShared::Cca(Box::new(CcaBackedShared::new(backing_shared_params)?))
297            }
298            _ => unreachable!(),
299        })
300    }
301
302    fn cvm_state(&self) -> Option<&UhCvmPartitionState> {
303        match self {
304            BackingShared::Hypervisor(_) => None,
305            #[cfg(guest_arch = "x86_64")]
306            BackingShared::Snp(SnpBackedShared { cvm, .. })
307            | BackingShared::Tdx(TdxBackedShared { cvm, .. }) => Some(cvm),
308            #[cfg(guest_arch = "aarch64")]
309            BackingShared::Cca(s) => Some(&s.cvm),
310        }
311    }
312
313    fn untrusted_synic(&self) -> Option<&GlobalSynic> {
314        match self {
315            BackingShared::Hypervisor(_) => None,
316            #[cfg(guest_arch = "x86_64")]
317            BackingShared::Snp(_) => None,
318            #[cfg(guest_arch = "x86_64")]
319            BackingShared::Tdx(s) => s.untrusted_synic.as_ref(),
320            #[cfg(guest_arch = "aarch64")]
321            BackingShared::Cca(_) => None,
322        }
323    }
324}
325
326/// Lower-VTL entry policies supplied to the `mshv_vtl` run ioctl.
327///
328/// The driver snapshots both policies when the ioctl begins. It starts with
329/// [`Self::first`] and switches to [`Self::second`] after an interrupt-only
330/// lower-VTL exit that does not need to return to user mode.
331#[derive(InspectMut, Copy, Clone)]
332struct EnterModes {
333    /// Policy for the first lower-VTL entry after invoking the run ioctl.
334    #[inspect(mut)]
335    first: EnterMode,
336    /// Policy for subsequent in-kernel reentries after an interrupt-only exit.
337    #[inspect(mut)]
338    second: EnterMode,
339}
340
341impl Default for EnterModes {
342    fn default() -> Self {
343        Self {
344            first: EnterMode::Fast,
345            second: EnterMode::IdleToVtl0,
346        }
347    }
348}
349
350impl From<EnterModes> for hcl::protocol::EnterModes {
351    fn from(value: EnterModes) -> Self {
352        Self::new()
353            .with_first(value.first.into())
354            .with_second(value.second.into())
355    }
356}
357
358/// Controls how the `mshv_vtl` driver enters a lower VTL.
359///
360/// The kernel applies the selected policy both to the initial entry and, when
361/// configured as the reentry policy, after an interrupt returns control to
362/// VTL2 without requiring an exit to user mode.
363#[derive(InspectMut, Copy, Clone)]
364enum EnterMode {
365    /// Enters the lower VTL directly from the VP thread without stopping the
366    /// VTL2 scheduler tick.
367    Fast,
368    /// Marks the VP thread idle, stops the VTL2 scheduler tick, and enters the
369    /// lower VTL directly from that thread. This avoids a scheduler handoff,
370    /// but an in-kernel reentry does not give other runnable VTL2 tasks an
371    /// opportunity to run first.
372    PlayIdle,
373    /// Parks the VP thread and delegates lower-VTL entry to the VTL2 idle
374    /// thread. This lets runnable VTL2 work run before reentry, but delays the
375    /// lower VTL until the CPU reaches the idle loop.
376    IdleToVtl0,
377}
378
379impl From<EnterMode> for hcl::protocol::EnterMode {
380    fn from(value: EnterMode) -> Self {
381        match value {
382            EnterMode::Fast => Self::FAST,
383            EnterMode::PlayIdle => Self::PLAY_IDLE,
384            EnterMode::IdleToVtl0 => Self::IDLE_TO_VTL0,
385        }
386    }
387}
388
389#[cfg(guest_arch = "x86_64")]
390#[derive(Inspect)]
391struct GuestVsmVpState {
392    /// The pending event that VTL 1 wants to inject into VTL 0. Injected on
393    /// next exit to VTL 0.
394    #[inspect(with = "|x| x.as_ref().map(inspect::AsDebug)")]
395    vtl0_exit_pending_event: Option<hvdef::HvX64PendingExceptionEvent>,
396    reg_intercept: SecureRegisterInterceptState,
397    /// Whether Mode-Based Execution Control is enabled on this VP.
398    vp_mbec_enabled: bool,
399}
400
401#[cfg(guest_arch = "x86_64")]
402impl GuestVsmVpState {
403    fn new() -> Self {
404        GuestVsmVpState {
405            vtl0_exit_pending_event: None,
406            reg_intercept: Default::default(),
407            vp_mbec_enabled: false,
408        }
409    }
410}
411
412#[derive(Inspect)]
413/// VP state for CVMs.
414struct UhCvmVpState {
415    // Allocation handle for direct overlays
416    #[inspect(debug)]
417    direct_overlay_handle: user_driver::memory::MemoryBlock,
418    /// Used in VTL 2 exit code to determine which VTL to exit to.
419    exit_vtl: GuestVtl,
420    /// Hypervisor enlightenment emulator state.
421    hv: VtlArray<ProcessorVtlHv, 2>,
422    /// LAPIC state.
423    #[inspect(safe)]
424    #[cfg(guest_arch = "x86_64")]
425    lapics: VtlArray<LapicState, 2>,
426    /// Guest VSM state for this vp. Some when VTL 1 is enabled.
427    #[cfg(guest_arch = "x86_64")]
428    vtl1: Option<GuestVsmVpState>,
429}
430
431impl UhCvmVpState {
432    /// Creates a new CVM VP state.
433    pub(crate) fn new(
434        cvm_partition: &UhCvmPartitionState,
435        #[cfg_attr(guest_arch = "aarch64", expect(unused_variables))] inner: &UhPartitionInner,
436        vp_info: &TargetVpInfo,
437        overlay_pages_required: usize,
438    ) -> Result<Self, Error> {
439        let direct_overlay_handle = cvm_partition
440            .shared_dma_client
441            .allocate_dma_buffer(overlay_pages_required * HV_PAGE_SIZE as usize)
442            .map_err(Error::AllocateSharedVisOverlay)?;
443
444        #[cfg(guest_arch = "x86_64")]
445        let lapics = VtlArray::from_fn(|vtl| {
446            let apic_base = virt::vp::Apic::at_reset(&inner.caps, vp_info).apic_base;
447            let apic_set = &cvm_partition.lapic[vtl];
448
449            // The APIC is software-enabled after reset for secure VTLs, to
450            // maintain compatibility with released versions of secure kernel
451            let mut lapic = apic_set.add_apic(vp_info, vtl == Vtl::Vtl1);
452            // Initialize APIC base to match the reset VM state.
453            lapic.set_apic_base(apic_base).unwrap();
454            // Only the VTL 0 non-BSP LAPICs should be in the WaitForSipi state.
455            let activity = if vtl == Vtl::Vtl0 && !vp_info.base.is_bsp() {
456                MpState::WaitForSipi
457            } else {
458                MpState::Running
459            };
460            LapicState::new(lapic, activity)
461        });
462
463        let hv = VtlArray::from_fn(|vtl| cvm_partition.hv.add_vp(vp_info.base.vp_index, vtl));
464
465        Ok(Self {
466            direct_overlay_handle,
467            exit_vtl: GuestVtl::Vtl0,
468            hv,
469            #[cfg(guest_arch = "x86_64")]
470            lapics,
471            #[cfg(guest_arch = "x86_64")]
472            vtl1: None,
473        })
474    }
475}
476
477#[derive(Inspect, Default)]
478#[inspect(hex)]
479/// Configuration of VTL 1 registration for intercepts on certain registers
480pub struct SecureRegisterInterceptState {
481    #[inspect(with = "|&x| u64::from(x)")]
482    intercept_control: hvdef::HvRegisterCrInterceptControl,
483    cr0_mask: u64,
484    cr4_mask: u64,
485    // Writes to X86X_IA32_MSR_MISC_ENABLE are dropped, so this is only used so
486    // that get_vp_register returns the correct value from a set_vp_register
487    ia32_misc_enable_mask: u64,
488}
489
490/// Information about a redirected interrupt for a specific vector.
491/// Stored per-processor, indexed by the redirected vector number in VTL2.
492#[derive(Clone, Inspect)]
493struct ProxyRedirectVectorInfo {
494    /// Device ID that owns this interrupt
495    device_id: u64,
496    /// Original interrupt vector from the device
497    original_vector: u32,
498}
499
500#[derive(Inspect)]
501/// Partition-wide state for CVMs.
502struct UhCvmPartitionState {
503    #[cfg(guest_arch = "x86_64")]
504    vps_per_socket: u32,
505    /// VPs that have locked their TLB.
506    #[inspect(
507        with = "|arr| inspect::iter_by_index(arr.iter()).map_value(|bb| inspect::iter_by_index(bb.iter().map(|v| *v)))"
508    )]
509    tlb_locked_vps: VtlArray<BitBox<AtomicU64>, 2>,
510    #[inspect(with = "inspect::iter_by_index")]
511    vps: Vec<UhCvmVpInner>,
512    shared_memory: GuestMemory,
513    #[inspect(skip)]
514    isolated_memory_protector: Arc<dyn ProtectIsolatedMemory>,
515    #[cfg(guest_arch = "x86_64")]
516    /// The emulated local APIC set.
517    lapic: VtlArray<LocalApicSet, 2>,
518    /// The emulated hypervisor state.
519    hv: GlobalHv<2>,
520    /// Guest VSM state.
521    guest_vsm: RwLock<GuestVsmState<CvmVtl1State>>,
522    /// Whether the partition has the access vsm privilege.
523    access_vsm_privilege: bool,
524    /// Dma client for shared visibility pages.
525    shared_dma_client: Arc<dyn DmaClient>,
526    /// Dma client for private visibility pages.
527    private_dma_client: Arc<dyn DmaClient>,
528    hide_isolation: bool,
529    proxy_interrupt_redirect: bool,
530}
531
532#[cfg_attr(guest_arch = "aarch64", expect(dead_code))]
533impl UhCvmPartitionState {
534    fn vp_inner(&self, vp_index: u32) -> &UhCvmVpInner {
535        &self.vps[vp_index as usize]
536    }
537
538    fn is_lower_vtl_startup_denied(&self) -> bool {
539        matches!(
540            *self.guest_vsm.read(),
541            GuestVsmState::Enabled {
542                vtl1: CvmVtl1State {
543                    deny_lower_vtl_startup: true,
544                    ..
545                }
546            }
547        )
548    }
549
550    /// The access vsm privilege at the time of partition creation. Per VSM
551    /// spec, it is not updated if VTL 1 is later revoked. Used for validating
552    /// register access that depends only on the privilege availability.
553    fn access_vsm_privilege(&self) -> bool {
554        self.access_vsm_privilege
555    }
556}
557
558#[derive(Inspect)]
559/// Per-vp state for CVMs.
560struct UhCvmVpInner {
561    /// The current status of TLB locks
562    tlb_lock_info: VtlArray<TlbLockInfo, 2>,
563    /// Whether EnableVpVtl for VTL 1 has been called on this VP.
564    vtl1_enable_called: Mutex<bool>,
565    /// Whether the VP has been started via the StartVp hypercall.
566    started: AtomicBool,
567    /// Start context for StartVp and EnableVpVtl calls.
568    #[inspect(with = "|arr| inspect::iter_by_index(arr.iter().map(|v| v.lock().is_some()))")]
569    hv_start_enable_vtl_vp: VtlArray<Mutex<Option<Box<VpStartEnableVtl>>>, 2>,
570    /// Tracking of proxy redirect interrupts mapped on this VP.
571    #[inspect(with = "|x| inspect::adhoc(|req| inspect::iter_by_key(&*x.lock()).inspect(req))")]
572    proxy_redirect_interrupts: Mutex<HashMap<u32, ProxyRedirectVectorInfo>>,
573}
574
575// TODO Guest VSM: cleanup these states for better clarity
576#[cfg_attr(guest_arch = "aarch64", expect(dead_code))]
577#[derive(Inspect)]
578#[inspect(tag = "guest_vsm_state")]
579/// Partition-wide state for guest vsm.
580enum GuestVsmState<T: Inspect> {
581    /// Whether VTL 1 is available. If the platform does not support VTL 1, or
582    /// VTL 1 was revoked, then the partition will be in this state. Note: some
583    /// vsm-related functionality may be available even if the state is
584    /// NotPlatformSupported.
585    NotPlatformSupported,
586    /// OpenHCL has not yet handled the guest calling EnablePartitionVtl.
587    NotGuestEnabled,
588    /// Note: this state is only used for CVMs. For non-CVMs, this is not an
589    /// accurate reflection of whether VTL 1 is enabled since the hypercall
590    /// goes to the hypervisor.
591    Enabled {
592        #[inspect(flatten)]
593        vtl1: T,
594    },
595}
596
597impl<T: Inspect> GuestVsmState<T> {
598    pub fn from_availability(guest_vsm_available: bool) -> Self {
599        if guest_vsm_available {
600            GuestVsmState::NotGuestEnabled
601        } else {
602            GuestVsmState::NotPlatformSupported
603        }
604    }
605}
606
607#[derive(Inspect)]
608struct CvmVtl1State {
609    /// Whether VTL 1 has been enabled on any vp
610    enabled_on_any_vp: bool,
611    /// Whether guest memory should be zeroed before it resets.
612    zero_memory_on_reset: bool,
613    /// Whether a vp can be started or reset by a lower vtl.
614    deny_lower_vtl_startup: bool,
615    /// Whether Mode-Based Execution Control should be enforced on lower VTLs.
616    pub mbec_enabled: bool,
617    /// Whether shadow supervisor stack is enabled.
618    pub shadow_supervisor_stack_enabled: bool,
619    #[inspect(with = "|bb| inspect::iter_by_index(bb.iter().map(|v| *v))")]
620    io_read_intercepts: BitBox<u64>,
621    #[inspect(with = "|bb| inspect::iter_by_index(bb.iter().map(|v| *v))")]
622    io_write_intercepts: BitBox<u64>,
623}
624
625#[cfg_attr(guest_arch = "aarch64", expect(dead_code))]
626impl CvmVtl1State {
627    fn new(mbec_enabled: bool) -> Self {
628        Self {
629            enabled_on_any_vp: false,
630            zero_memory_on_reset: false,
631            deny_lower_vtl_startup: false,
632            mbec_enabled,
633            shadow_supervisor_stack_enabled: false,
634            io_read_intercepts: BitVec::repeat(false, u16::MAX as usize + 1).into_boxed_bitslice(),
635            io_write_intercepts: BitVec::repeat(false, u16::MAX as usize + 1).into_boxed_bitslice(),
636        }
637    }
638}
639
640#[cfg_attr(guest_arch = "aarch64", expect(dead_code))]
641struct TscReferenceTimeSource {
642    tsc_scale: u64,
643}
644
645impl TscReferenceTimeSource {
646    fn new(tsc_frequency: u64) -> Self {
647        TscReferenceTimeSource {
648            tsc_scale: (((10_000_000_u128) << 64) / tsc_frequency as u128) as u64,
649        }
650    }
651}
652
653/// A time implementation based on TSC.
654impl GetReferenceTime for TscReferenceTimeSource {
655    fn now(&self) -> ReferenceTimeResult {
656        #[cfg(guest_arch = "x86_64")]
657        {
658            let tsc = safe_intrinsics::rdtsc();
659            let ref_time = ((self.tsc_scale as u128 * tsc as u128) >> 64) as u64;
660            ReferenceTimeResult {
661                ref_time,
662                system_time: None,
663            }
664        }
665
666        #[cfg(guest_arch = "aarch64")]
667        {
668            todo!("AARCH64_TODO");
669        }
670    }
671}
672
673impl virt::irqcon::ControlGic for UhPartitionInner {
674    fn set_spi_irq(&self, irq_id: u32, high: bool) {
675        if let Err(err) = self.hcl.request_interrupt(
676            hvdef::HvInterruptControl::new()
677                .with_arm64_asserted(high)
678                .with_interrupt_type(hvdef::HvInterruptType::HvArm64InterruptTypeFixed),
679            0,
680            irq_id,
681            GuestVtl::Vtl0,
682        ) {
683            tracelimit::warn_ratelimited!(
684                error = &err as &dyn std::error::Error,
685                irq = irq_id,
686                asserted = high,
687                "failed to request spi"
688            );
689        }
690    }
691}
692
693impl virt::Aarch64Partition for UhPartition {
694    fn control_gic(&self, vtl: Vtl) -> Arc<dyn virt::irqcon::ControlGic> {
695        debug_assert!(vtl == Vtl::Vtl0);
696        self.inner.clone()
697    }
698}
699
700/// A wrapper around [`UhProcessor`] that is [`Send`].
701///
702/// This is used to instantiate the processor object on the correct thread,
703/// since all lower VTL processor state accesses must occur from the same
704/// processor at VTL2.
705pub struct UhProcessorBox {
706    partition: Arc<UhPartitionInner>,
707    vp_info: TargetVpInfo,
708}
709
710impl UhProcessorBox {
711    /// Returns the VP index.
712    pub fn vp_index(&self) -> VpIndex {
713        self.vp_info.base.vp_index
714    }
715
716    /// Returns whether sidecar support is enabled.
717    pub fn sidecar_enabled(&self) -> bool {
718        self.partition.hcl.sidecar_enabled()
719    }
720
721    /// Returns the base CPU that manages this processor, when it is a sidecar
722    /// VP.
723    pub fn sidecar_base_cpu(&self) -> Option<u32> {
724        self.partition
725            .hcl
726            .sidecar_base_cpu(self.vp_info.base.vp_index.index())
727    }
728
729    /// Returns the processor object, bound to this thread.
730    ///
731    /// If `control` is provided, then this must be called on the VP's
732    /// associated thread pool thread, and it will dispatch the VP directly.
733    /// Otherwise, the processor will control the processor via the sidecar
734    /// kernel.
735    pub fn bind_processor<'a, T: Backing>(
736        &'a mut self,
737        driver: &impl Driver,
738        control: Option<&'a mut IdleControl>,
739    ) -> Result<UhProcessor<'a, T>, Error> {
740        if let Some(control) = &control {
741            let vp_index = self.vp_info.base.vp_index;
742
743            let mut current = Default::default();
744            affinity::get_current_thread_affinity(&mut current).unwrap();
745            assert_eq!(&current, CpuSet::new().set(vp_index.index()));
746
747            self.partition
748                .hcl
749                .set_poll_file(
750                    self.partition.vp(vp_index).unwrap().cpu_index,
751                    control.ring_fd().as_raw_fd(),
752                )
753                .map_err(Error::Hcl)?;
754        }
755
756        UhProcessor::new(driver, &self.partition, self.vp_info, control)
757    }
758
759    /// Sets the sidecar remove reason for the processor to be due to a task
760    /// running with the given name.
761    ///
762    /// This is useful for diagnostics.
763    pub fn set_sidecar_exit_due_to_task(&self, task: Arc<str>) {
764        self.partition
765            .vp(self.vp_info.base.vp_index)
766            .unwrap()
767            .set_sidecar_exit_reason(SidecarExitReason::TaskRequest(task))
768    }
769}
770
771#[derive(Debug, Inspect)]
772struct UhVpInner {
773    /// 32 bits per VTL: top bits are VTL 1, bottom bits are VTL 0.
774    wake_reasons: AtomicU64,
775    #[inspect(skip)]
776    waker: RwLock<Option<Waker>>,
777    message_queues: VtlArray<MessageQueues, 2>,
778    #[inspect(skip)]
779    vp_info: TargetVpInfo,
780    /// The Linux kernel's CPU index for this VP. This should be used instead of VpIndex
781    /// when interacting with non-MSHV kernel interfaces.
782    cpu_index: u32,
783    sidecar_exit_reason: Mutex<Option<SidecarExitReason>>,
784}
785
786impl UhVpInner {
787    pub fn vp_index(&self) -> VpIndex {
788        self.vp_info.base.vp_index
789    }
790}
791
792#[cfg_attr(not(guest_arch = "x86_64"), expect(dead_code))]
793#[derive(Debug, Inspect)]
794/// Which operation is setting the initial vp context
795enum InitialVpContextOperation {
796    /// The VP is being started via the StartVp hypercall.
797    StartVp,
798    /// The VP is being started via the EnableVpVtl hypercall.
799    EnableVpVtl,
800}
801
802#[cfg_attr(not(guest_arch = "x86_64"), expect(dead_code))]
803#[derive(Debug, Inspect)]
804/// State for handling StartVp/EnableVpVtl hypercalls.
805struct VpStartEnableVtl {
806    /// Which operation, startvp or enablevpvtl, is setting the initial vp
807    /// context
808    operation: InitialVpContextOperation,
809    #[inspect(skip)]
810    context: hvdef::hypercall::InitialVpContextX64,
811}
812
813#[derive(Debug, Inspect)]
814struct TlbLockInfo {
815    /// The set of VPs that are waiting for this VP to release the TLB lock.
816    #[inspect(with = "|bb| inspect::iter_by_index(bb.iter().map(|v| *v))")]
817    blocked_vps: BitBox<AtomicU64>,
818    /// The set of VPs that are holding the TLB lock and preventing this VP
819    /// from proceeding.
820    #[inspect(with = "|bb| inspect::iter_by_index(bb.iter().map(|v| *v))")]
821    blocking_vps: BitBox<AtomicU64>,
822    /// The count of blocking VPs. This should always be equivalent to
823    /// `blocking_vps.count_ones()`, however it is accessible in a single
824    /// atomic operation while counting is not.
825    blocking_vp_count: AtomicU32,
826    /// Whether the VP is sleeping due to a TLB lock.
827    sleeping: AtomicBool,
828}
829
830impl TlbLockInfo {
831    fn new(vp_count: usize) -> Self {
832        Self {
833            blocked_vps: BitVec::repeat(false, vp_count).into_boxed_bitslice(),
834            blocking_vps: BitVec::repeat(false, vp_count).into_boxed_bitslice(),
835            blocking_vp_count: AtomicU32::new(0),
836            sleeping: false.into(),
837        }
838    }
839}
840
841#[bitfield(u32)]
842#[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
843struct WakeReason {
844    extint: bool,
845    message_queues: bool,
846    hv_start_enable_vtl_vp: bool,
847    intcon: bool,
848    update_proxy_irr_filter: bool,
849    #[bits(27)]
850    _reserved: u32,
851}
852
853impl WakeReason {
854    // Convenient constants.
855    #[cfg(guest_arch = "x86_64")]
856    const EXTINT: Self = Self::new().with_extint(true);
857    const MESSAGE_QUEUES: Self = Self::new().with_message_queues(true);
858    #[cfg(guest_arch = "x86_64")]
859    const HV_START_ENABLE_VP_VTL: Self = Self::new().with_hv_start_enable_vtl_vp(true); // StartVp/EnableVpVtl handling
860    #[cfg(guest_arch = "x86_64")]
861    const INTCON: Self = Self::new().with_intcon(true);
862    #[cfg(guest_arch = "x86_64")]
863    const UPDATE_PROXY_IRR_FILTER: Self = Self::new().with_update_proxy_irr_filter(true);
864}
865
866#[bitfield(u32)]
867#[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
868struct ExitActivity {
869    pending_event: bool,
870    #[bits(31)]
871    _reserved: u32,
872}
873
874/// Immutable access to useful bits of Partition state.
875impl UhPartition {
876    /// Revokes guest VSM.
877    pub fn revoke_guest_vsm(&self) -> Result<(), RevokeGuestVsmError> {
878        fn revoke<T: Inspect>(vsm_state: &mut GuestVsmState<T>) -> Result<(), RevokeGuestVsmError> {
879            if matches!(vsm_state, GuestVsmState::Enabled { .. }) {
880                return Err(RevokeGuestVsmError::Vtl1AlreadyEnabled);
881            }
882            *vsm_state = GuestVsmState::NotPlatformSupported;
883            Ok(())
884        }
885
886        match &self.inner.backing_shared {
887            BackingShared::Hypervisor(s) => {
888                revoke(&mut *s.guest_vsm.write())?;
889                self.inner
890                    .hcl
891                    .set_guest_vsm_partition_config(false)
892                    .map_err(RevokeGuestVsmError::SetGuestVsmConfig)?;
893            }
894            #[cfg(guest_arch = "x86_64")]
895            BackingShared::Snp(SnpBackedShared { cvm, .. })
896            | BackingShared::Tdx(TdxBackedShared { cvm, .. }) => {
897                revoke(&mut *cvm.guest_vsm.write())?;
898            }
899            #[cfg(guest_arch = "aarch64")]
900            BackingShared::Cca(s) => {
901                revoke(&mut *s.cvm.guest_vsm.write())?;
902            }
903        };
904
905        Ok(())
906    }
907
908    /// Returns the current hypervisor reference time, in 100ns units.
909    pub fn reference_time(&self) -> u64 {
910        if let Some(hv) = self.inner.hv() {
911            hv.ref_time_source().now().ref_time
912        } else {
913            self.inner
914                .hcl
915                .reference_time()
916                .expect("should not fail to get the reference time")
917        }
918    }
919}
920
921impl virt::Partition for UhPartition {
922    fn initial_vp_state_source(&self) -> virt::InitialVpStateSource {
923        virt::InitialVpStateSource::Registers
924    }
925
926    fn supports_reset(&self) -> Option<&dyn virt::ResetPartition<Error = Self::Error>> {
927        None
928    }
929
930    fn caps(&self) -> &PartitionCapabilities {
931        &self.inner.caps
932    }
933
934    fn request_msi(&self, vtl: Vtl, request: MsiRequest) {
935        self.inner
936            .request_msi(vtl.try_into().expect("higher vtl not configured"), request)
937    }
938
939    fn request_yield(&self, _vp_index: VpIndex) {
940        unimplemented!()
941    }
942}
943
944#[cfg(guest_arch = "x86_64")]
945impl X86Partition for UhPartition {
946    fn ioapic_routing(&self) -> Arc<dyn IoApicRouting> {
947        self.inner.clone()
948    }
949
950    fn pulse_lint(&self, vp_index: VpIndex, vtl: Vtl, lint: u8) {
951        let vtl = GuestVtl::try_from(vtl).expect("higher vtl not configured");
952        if let Some(apic) = &self.inner.lapic(vtl) {
953            apic.lint(vp_index, lint.into(), |vp_index| {
954                self.inner
955                    .vp(vp_index)
956                    .unwrap()
957                    .wake(vtl, WakeReason::INTCON);
958            });
959        } else if lint == 0 {
960            self.inner
961                .vp(vp_index)
962                .unwrap()
963                .wake(vtl, WakeReason::EXTINT);
964        } else {
965            unimplemented!()
966        }
967    }
968}
969
970impl UhPartitionInner {
971    fn vp(&self, index: VpIndex) -> Option<&'_ UhVpInner> {
972        self.vps.get(index.index() as usize)
973    }
974
975    #[cfg(guest_arch = "x86_64")]
976    fn lapic(&self, vtl: GuestVtl) -> Option<&LocalApicSet> {
977        self.backing_shared.cvm_state().map(|x| &x.lapic[vtl])
978    }
979
980    fn hv(&self) -> Option<&GlobalHv<2>> {
981        self.backing_shared.cvm_state().map(|x| &x.hv)
982    }
983
984    /// For requester VP to issue `proxy_irr_blocked` update to other VPs
985    #[cfg(guest_arch = "x86_64")]
986    fn request_proxy_irr_filter_update(
987        &self,
988        vtl: GuestVtl,
989        device_vector: u8,
990        req_vp_index: VpIndex,
991    ) {
992        tracing::debug!(
993            ?vtl,
994            device_vector,
995            req_vp_index = req_vp_index.index(),
996            "request_proxy_irr_filter_update"
997        );
998
999        // Add given vector to partition global device vector table (VTL0 only for now)
1000        {
1001            let mut device_vector_table = self.device_vector_table.write();
1002            device_vector_table.set(device_vector as usize, true);
1003        }
1004
1005        // Wake all other VPs for their `proxy_irr_blocked` filter update
1006        for vp in self.vps.iter() {
1007            if vp.vp_index() != req_vp_index {
1008                vp.wake(vtl, WakeReason::UPDATE_PROXY_IRR_FILTER);
1009            }
1010        }
1011    }
1012
1013    /// Get current partition global device irr vectors (VTL0 for now)
1014    #[cfg(guest_arch = "x86_64")]
1015    fn fill_device_vectors(&self, _vtl: GuestVtl, irr_vectors: &mut IrrBitmap) {
1016        let device_vector_table = self.device_vector_table.read();
1017        for idx in device_vector_table.iter_ones() {
1018            irr_vectors.set(idx, true);
1019        }
1020    }
1021
1022    fn inspect_extra(&self, resp: &mut inspect::Response<'_>) {
1023        let mut wake_vps = false;
1024        resp.field_mut(
1025            "enter_modes",
1026            &mut inspect::adhoc_mut(|req| {
1027                let update = req.is_update();
1028                {
1029                    let mut modes = self.enter_modes.lock();
1030                    modes.inspect_mut(req);
1031                    if update {
1032                        self.enter_modes_atomic.store(
1033                            hcl::protocol::EnterModes::from(*modes).into(),
1034                            Ordering::Relaxed,
1035                        );
1036                        wake_vps = true;
1037                    }
1038                }
1039            }),
1040        );
1041
1042        // Wake VPs to propagate updates.
1043        if wake_vps {
1044            for vp in self.vps.iter() {
1045                vp.wake_vtl2();
1046            }
1047        }
1048    }
1049
1050    // TODO VBS GUEST VSM: enable for aarch64
1051    #[cfg_attr(guest_arch = "aarch64", expect(dead_code))]
1052    fn vsm_status(
1053        &self,
1054    ) -> Result<HvRegisterVsmPartitionStatus, hcl::ioctl::register::GetRegError> {
1055        // TODO: It might be possible to cache VsmPartitionStatus.
1056        self.hcl.get_vsm_partition_status()
1057    }
1058}
1059
1060impl virt::synic::Synic for UhPartitionInner {
1061    fn port_map(&self) -> &virt::synic::SynicPortMap {
1062        &self.synic_ports
1063    }
1064
1065    fn post_message(&self, vtl: Vtl, vp_index: VpIndex, sint: u8, typ: u32, payload: &[u8]) {
1066        let vtl = GuestVtl::try_from(vtl).expect("higher vtl not configured");
1067        let Some(vp) = self.vp(vp_index) else {
1068            tracelimit::warn_ratelimited!(
1069                CVM_ALLOWED,
1070                vp = vp_index.index(),
1071                "invalid vp target for post_message"
1072            );
1073            return;
1074        };
1075
1076        vp.post_message(
1077            vtl,
1078            sint,
1079            &hvdef::HvMessage::new(hvdef::HvMessageType(typ), 0, payload),
1080        );
1081    }
1082
1083    fn new_guest_event_port(
1084        self: Arc<Self>,
1085        vtl: Vtl,
1086        vp: u32,
1087        sint: u8,
1088        flag: u16,
1089    ) -> Box<dyn vmcore::synic::GuestEventPort> {
1090        let vtl = GuestVtl::try_from(vtl).expect("higher vtl not configured");
1091        Box::new(UhEventPort {
1092            partition: Arc::downgrade(&self),
1093            params: Arc::new(Mutex::new(UhEventPortParams {
1094                vp: VpIndex::new(vp),
1095                sint,
1096                flag,
1097                vtl,
1098            })),
1099        })
1100    }
1101
1102    fn prefer_os_events(&self) -> bool {
1103        false
1104    }
1105
1106    fn monitor_support(&self) -> Option<&dyn virt::synic::SynicMonitor> {
1107        Some(self)
1108    }
1109}
1110
1111impl virt::synic::SynicMonitor for UhPartitionInner {
1112    fn set_monitor_page(&self, vtl: Vtl, gpa: Option<u64>) -> anyhow::Result<()> {
1113        // Keep this locked the whole function to avoid racing with allocate_monitor_page.
1114        let mut allocated_block = self.allocated_monitor_page.lock();
1115        let old_gpa = self.monitor_page.set_gpa(gpa);
1116
1117        // Take ownership of any allocated monitor page so it will be freed on function exit.
1118        let allocated_page = allocated_block.take();
1119        if let Some(old_gpa) = old_gpa {
1120            let allocated_gpa = allocated_page
1121                .as_ref()
1122                .map(|b| b.pfns()[0] << HV_PAGE_SHIFT);
1123
1124            // Revert the old page's permissions, using the appropriate method depending on
1125            // whether it was allocated or guest-supplied.
1126            let result = if allocated_gpa == Some(old_gpa) {
1127                let vtl = GuestVtl::try_from(vtl).unwrap();
1128                self.unregister_cvm_dma_overlay_page(vtl, old_gpa >> HV_PAGE_SHIFT)
1129            } else {
1130                self.hcl
1131                    .modify_vtl_protection_mask(
1132                        MemoryRange::new(old_gpa..old_gpa + HV_PAGE_SIZE),
1133                        hvdef::HV_MAP_GPA_PERMISSIONS_ALL,
1134                        HvInputVtl::CURRENT_VTL,
1135                    )
1136                    .map_err(|err| anyhow::anyhow!(err))
1137            };
1138
1139            result
1140                .context("failed to unregister old monitor page")
1141                .inspect_err(|_| {
1142                    // Leave the page unset if returning a failure.
1143                    self.monitor_page.set_gpa(None);
1144                })?;
1145
1146            tracing::debug!(old_gpa, "unregistered monitor page");
1147        }
1148
1149        if let Some(gpa) = gpa {
1150            // Disallow VTL0 from writing to the page, so we'll get an intercept. Note that read
1151            // permissions must be enabled or this doesn't work correctly.
1152            self.hcl
1153                .modify_vtl_protection_mask(
1154                    MemoryRange::new(gpa..gpa + HV_PAGE_SIZE),
1155                    HvMapGpaFlags::new().with_readable(true),
1156                    HvInputVtl::CURRENT_VTL,
1157                )
1158                .context("failed to register monitor page")
1159                .inspect_err(|_| {
1160                    // Leave the page unset if returning a failure.
1161                    self.monitor_page.set_gpa(None);
1162                })?;
1163
1164            tracing::debug!(gpa, "registered monitor page");
1165        }
1166
1167        Ok(())
1168    }
1169
1170    fn register_monitor(
1171        &self,
1172        monitor_id: vmcore::monitor::MonitorId,
1173        connection_id: u32,
1174    ) -> Box<dyn Sync + Send> {
1175        self.monitor_page
1176            .register_monitor(monitor_id, connection_id)
1177    }
1178
1179    fn allocate_monitor_page(&self, vtl: Vtl) -> anyhow::Result<Option<u64>> {
1180        let vtl = GuestVtl::try_from(vtl).unwrap();
1181
1182        // Allocating a monitor page is only supported for CVMs.
1183        let Some(state) = self.backing_shared.cvm_state() else {
1184            return Ok(None);
1185        };
1186
1187        let mut allocated_block = self.allocated_monitor_page.lock();
1188        if let Some(block) = allocated_block.as_ref() {
1189            // An allocated monitor page is already in use; no need to change it.
1190            let gpa = block.pfns()[0] << HV_PAGE_SHIFT;
1191            assert_eq!(self.monitor_page.gpa(), Some(gpa));
1192            return Ok(Some(gpa));
1193        }
1194
1195        let block = state
1196            .private_dma_client
1197            .allocate_dma_buffer(HV_PAGE_SIZE_USIZE)
1198            .context("failed to allocate monitor page")?;
1199
1200        let gpn = block.pfns()[0];
1201        *allocated_block = Some(block);
1202        let gpa = gpn << HV_PAGE_SHIFT;
1203        let old_gpa = self.monitor_page.set_gpa(Some(gpa));
1204        if let Some(old_gpa) = old_gpa {
1205            // The old GPA is guaranteed not to be allocated, since that was checked above, so
1206            // revert its permissions using the method for guest-supplied memory.
1207            self.hcl
1208                .modify_vtl_protection_mask(
1209                    MemoryRange::new(old_gpa..old_gpa + HV_PAGE_SIZE),
1210                    hvdef::HV_MAP_GPA_PERMISSIONS_ALL,
1211                    HvInputVtl::CURRENT_VTL,
1212                )
1213                .context("failed to unregister old monitor page")
1214                .inspect_err(|_| {
1215                    // Leave the page unset if returning a failure.
1216                    self.monitor_page.set_gpa(None);
1217                })?;
1218
1219            tracing::debug!(old_gpa, "unregistered monitor page");
1220        }
1221
1222        // Disallow VTL0 from writing to the page, so we'll get an intercept. Note that read
1223        // permissions must be enabled or this doesn't work correctly.
1224        self.register_cvm_dma_overlay_page(vtl, gpn, HvMapGpaFlags::new().with_readable(true))
1225            .context("failed to unregister monitor page")
1226            .inspect_err(|_| {
1227                // Leave the page unset if returning a failure.
1228                self.monitor_page.set_gpa(None);
1229            })?;
1230
1231        tracing::debug!(gpa, "registered allocated monitor page");
1232
1233        Ok(Some(gpa))
1234    }
1235}
1236
1237impl UhPartitionInner {
1238    #[cfg(guest_arch = "x86_64")]
1239    pub(crate) fn synic_interrupt(
1240        &self,
1241        vp_index: VpIndex,
1242        vtl: GuestVtl,
1243    ) -> impl '_ + hv1_emulator::RequestInterrupt {
1244        // TODO CVM: optimize for SNP with secure avic to avoid internal wake
1245        // and for TDX to avoid trip to user mode
1246        move |vector, auto_eoi| {
1247            self.lapic(vtl).unwrap().synic_interrupt(
1248                vp_index,
1249                vector as u8,
1250                auto_eoi,
1251                |vp_index| self.vp(vp_index).unwrap().wake(vtl, WakeReason::INTCON),
1252            );
1253        }
1254    }
1255
1256    #[cfg(guest_arch = "aarch64")]
1257    fn synic_interrupt(
1258        &self,
1259        _vp_index: VpIndex,
1260        _vtl: GuestVtl,
1261    ) -> impl '_ + hv1_emulator::RequestInterrupt {
1262        move |_, _| {}
1263    }
1264}
1265
1266#[derive(Debug)]
1267struct UhEventPort {
1268    partition: Weak<UhPartitionInner>,
1269    params: Arc<Mutex<UhEventPortParams>>,
1270}
1271
1272#[derive(Debug, Copy, Clone)]
1273struct UhEventPortParams {
1274    vp: VpIndex,
1275    sint: u8,
1276    flag: u16,
1277    vtl: GuestVtl,
1278}
1279
1280impl vmcore::synic::GuestEventPort for UhEventPort {
1281    fn interrupt(&self) -> vmcore::interrupt::Interrupt {
1282        let partition = self.partition.clone();
1283        let params = self.params.clone();
1284        vmcore::interrupt::Interrupt::from_fn(move || {
1285            let UhEventPortParams {
1286                vp,
1287                sint,
1288                flag,
1289                vtl,
1290            } = *params.lock();
1291            let Some(partition) = partition.upgrade() else {
1292                return;
1293            };
1294            tracing::trace!(vp = vp.index(), sint, flag, "signal_event");
1295            if let Some(hv) = partition.hv() {
1296                match hv.synic[vtl].signal_event(
1297                    vp,
1298                    sint,
1299                    flag,
1300                    &mut partition.synic_interrupt(vp, vtl),
1301                ) {
1302                    Ok(_) => {}
1303                    Err(SintProxied) => {
1304                        tracing::trace!(
1305                            vp = vp.index(),
1306                            sint,
1307                            flag,
1308                            "forwarding event to untrusted synic"
1309                        );
1310                        if let Some(synic) = partition.backing_shared.untrusted_synic() {
1311                            synic
1312                                .signal_event(
1313                                    vp,
1314                                    sint,
1315                                    flag,
1316                                    &mut partition.synic_interrupt(vp, vtl),
1317                                )
1318                                .ok();
1319                        } else {
1320                            partition.hcl.signal_event_direct(vp.index(), sint, flag)
1321                        }
1322                    }
1323                }
1324            } else {
1325                partition.hcl.signal_event_direct(vp.index(), sint, flag);
1326            }
1327        })
1328    }
1329
1330    fn set_target_vp(&mut self, vp: u32) -> Result<(), vmcore::synic::HypervisorError> {
1331        self.params.lock().vp = VpIndex::new(vp);
1332        Ok(())
1333    }
1334}
1335
1336impl virt::Hv1 for UhPartition {
1337    type Error = Error;
1338    type Device = virt::x86::apic_software_device::ApicSoftwareDevice;
1339
1340    fn reference_time_source(&self) -> Option<ReferenceTimeSource> {
1341        Some(if let Some(hv) = self.inner.hv() {
1342            hv.ref_time_source().clone()
1343        } else {
1344            ReferenceTimeSource::from(self.inner.clone() as Arc<_>)
1345        })
1346    }
1347
1348    fn new_virtual_device(
1349        &self,
1350    ) -> Option<&dyn virt::DeviceBuilder<Device = Self::Device, Error = Self::Error>> {
1351        self.inner.software_devices.is_some().then_some(self)
1352    }
1353
1354    fn synic(&self) -> anyhow::Result<Arc<dyn vmcore::synic::SynicPortAccess>> {
1355        Ok(self.synic_ports.clone())
1356    }
1357}
1358
1359impl GetReferenceTime for UhPartitionInner {
1360    fn now(&self) -> ReferenceTimeResult {
1361        ReferenceTimeResult {
1362            ref_time: self.hcl.reference_time().unwrap(),
1363            system_time: None,
1364        }
1365    }
1366}
1367
1368impl virt::DeviceBuilder for UhPartition {
1369    fn build(&self, vtl: Vtl, device_id: u64) -> Result<Self::Device, Self::Error> {
1370        let vtl = GuestVtl::try_from(vtl).expect("higher vtl not configured");
1371        let device = self
1372            .inner
1373            .software_devices
1374            .as_ref()
1375            .expect("checked in new_virtual_device")
1376            .new_device(self.interrupt_targets[vtl].clone(), device_id)
1377            .map_err(Error::NewDevice)?;
1378
1379        Ok(device)
1380    }
1381}
1382
1383struct UhInterruptTarget {
1384    partition: Arc<UhPartitionInner>,
1385    vtl: GuestVtl,
1386}
1387
1388impl pci_core::msi::SignalMsi for UhInterruptTarget {
1389    fn signal_msi(&self, _devid: Option<u32>, address: u64, data: u32) {
1390        self.partition
1391            .request_msi(self.vtl, MsiRequest { address, data });
1392    }
1393}
1394
1395impl UhPartitionInner {
1396    #[cfg(guest_arch = "x86_64")]
1397    fn request_msi(&self, vtl: GuestVtl, request: MsiRequest) {
1398        if let Some(lapic) = self.lapic(vtl) {
1399            tracing::trace!(?request, "interrupt");
1400            lapic.request_interrupt(request.address, request.data, |vp_index| {
1401                self.vp(vp_index).unwrap().wake(vtl, WakeReason::INTCON)
1402            });
1403        } else {
1404            let (address, data) = request.as_x86();
1405            if let Err(err) = self.hcl.request_interrupt(
1406                request.hv_x86_interrupt_control(),
1407                address.virt_destination().into(),
1408                data.vector().into(),
1409                vtl,
1410            ) {
1411                tracelimit::warn_ratelimited!(
1412                    CVM_ALLOWED,
1413                    error = &err as &dyn std::error::Error,
1414                    address = request.address,
1415                    data = request.data,
1416                    "failed to request msi"
1417                );
1418            }
1419        }
1420    }
1421
1422    #[cfg(guest_arch = "aarch64")]
1423    fn request_msi(&self, vtl: GuestVtl, request: MsiRequest) {
1424        match self.isolation {
1425            IsolationType::Cca => {
1426                tracelimit::warn_ratelimited!(
1427                    ?vtl,
1428                    ?request,
1429                    "ignoring MSI request for CCA on aarch64: MSI routing is not implemented"
1430                );
1431            }
1432            _ => unimplemented!("MSI routing is not implemented on aarch64"),
1433        }
1434    }
1435}
1436
1437impl IoApicRouting for UhPartitionInner {
1438    fn set_irq_route(&self, irq: u8, request: Option<MsiRequest>) {
1439        self.irq_routes.set_irq_route(irq, request)
1440    }
1441
1442    // The IO-APIC is always hooked up to VTL0.
1443    fn assert_irq(&self, irq: u8) {
1444        self.irq_routes
1445            .assert_irq(irq, |request| self.request_msi(GuestVtl::Vtl0, request))
1446    }
1447}
1448
1449// xtask-fmt allow-target-arch cpu-intrinsic
1450#[cfg(target_arch = "x86_64")]
1451fn is_restore_partition_time_available() -> bool {
1452    let result =
1453        safe_intrinsics::cpuid(hvdef::HV_CPUID_FUNCTION_MS_HV_ENLIGHTENMENT_INFORMATION, 0);
1454    let enlightenment_info = hvdef::HvEnlightenmentInformation::from(
1455        result.eax as u128
1456            | (result.ebx as u128) << 32
1457            | (result.ecx as u128) << 64
1458            | (result.edx as u128) << 96,
1459    );
1460    enlightenment_info.restore_time_on_resume()
1461}
1462// xtask-fmt allow-target-arch cpu-intrinsic
1463#[cfg(not(target_arch = "x86_64"))]
1464fn is_restore_partition_time_available() -> bool {
1465    // Only available on x86_64 Hyper-V hypervisor.
1466    false
1467}
1468
1469/// Configure the [`hvdef::HvRegisterVsmPartitionConfig`] register with the
1470/// values used by underhill.
1471fn set_vtl2_vsm_partition_config(hcl: &Hcl) -> Result<(), Error> {
1472    // TODO CCA: handle setting vtl2 vsm partition configuration
1473    if hcl.isolation() == hcl::ioctl::IsolationType::Cca {
1474        tracing::warn!("cca: set_vtl2_vsm_partition_config: do nothing now");
1475        return Ok(());
1476    }
1477    // Read available capabilities to determine what to enable.
1478    let caps = hcl.get_vsm_capabilities().map_err(Error::GetReg)?;
1479    let hardware_isolated = hcl.isolation().is_hardware_isolated();
1480    let isolated = hcl.isolation().is_isolated();
1481    let config = HvRegisterVsmPartitionConfig::new()
1482        .with_default_vtl_protection_mask(0xF)
1483        .with_enable_vtl_protection(!hardware_isolated)
1484        .with_zero_memory_on_reset(!hardware_isolated)
1485        .with_intercept_cpuid_unimplemented(!hardware_isolated)
1486        .with_intercept_page(caps.intercept_page_available())
1487        .with_intercept_unrecoverable_exception(true)
1488        .with_intercept_not_present(caps.intercept_not_present_available() && !isolated)
1489        .with_intercept_acceptance(isolated)
1490        .with_intercept_enable_vtl_protection(isolated && !hardware_isolated)
1491        .with_intercept_system_reset(caps.intercept_system_reset_available())
1492        .with_intercept_restore_partition_time(is_restore_partition_time_available());
1493
1494    hcl.set_vtl2_vsm_partition_config(config)
1495        .map_err(Error::SetReg)
1496}
1497
1498/// Configuration parameters supplied to [`UhProtoPartition::new`].
1499///
1500/// These do not include runtime resources.
1501pub struct UhPartitionNewParams<'a> {
1502    /// The isolation type for the partition.
1503    pub isolation: IsolationType,
1504    /// Hide isolation from the guest. The guest will run as if it is not
1505    /// isolated.
1506    pub hide_isolation: bool,
1507    /// The memory layout for lower VTLs.
1508    pub lower_vtl_memory_layout: &'a MemoryLayout,
1509    /// The guest processor topology.
1510    pub topology: &'a ProcessorTopology,
1511    /// The unparsed CVM cpuid info.
1512    // TODO: move parsing up a layer.
1513    pub cvm_cpuid_info: Option<&'a [u8]>,
1514    /// The unparsed CVM secrets page.
1515    pub snp_secrets: Option<&'a [u8]>,
1516    /// The virtual top of memory for hardware-isolated VMs.
1517    ///
1518    /// Must be a power of two.
1519    pub vtom: Option<u64>,
1520    /// Handle synic messages and events.
1521    ///
1522    /// On TDX, this prevents the hypervisor from getting vmtdcall exits.
1523    pub handle_synic: bool,
1524    /// Do not hotplug sidecar VPs on their first exit. Just continue running
1525    /// the VP remotely.
1526    pub no_sidecar_hotplug: bool,
1527    /// Use MMIO access hypercalls.
1528    pub use_mmio_hypercalls: bool,
1529    /// Intercept guest debug exceptions to support gdbstub.
1530    pub intercept_debug_exceptions: bool,
1531    /// Disable proxy interrupt redirection.
1532    pub disable_proxy_redirect: bool,
1533    /// Disable lower VTL timer virtualization.
1534    pub disable_lower_vtl_timer_virt: bool,
1535}
1536
1537/// Parameters to [`UhProtoPartition::build`].
1538pub struct UhLateParams<'a> {
1539    /// Guest memory for lower VTLs.
1540    pub gm: VtlArray<GuestMemory, 2>,
1541    /// Guest memory for VTL 0 kernel execute access.
1542    pub vtl0_kernel_exec_gm: GuestMemory,
1543    /// Guest memory for VTL 0 user execute access.
1544    pub vtl0_user_exec_gm: GuestMemory,
1545    /// The CPUID leaves to expose to the guest.
1546    #[cfg(guest_arch = "x86_64")]
1547    pub cpuid: Vec<CpuidLeaf>,
1548    /// The mesh sender to use for crash notifications.
1549    // FUTURE: remove mesh dependency from this layer.
1550    pub crash_notification_send: mesh::Sender<VtlCrash>,
1551    /// The VM time source.
1552    pub vmtime: &'a VmTimeSource,
1553    /// Parameters for CVMs only.
1554    pub cvm_params: Option<CvmLateParams>,
1555    /// vmbus_relay is enabled and active for partition
1556    pub vmbus_relay: bool,
1557}
1558
1559/// CVM-only parameters to [`UhProtoPartition::build`].
1560pub struct CvmLateParams {
1561    /// Guest memory for untrusted devices, like overlay pages.
1562    pub shared_gm: GuestMemory,
1563    /// An object to call to change host visibility on guest memory.
1564    pub isolated_memory_protector: Arc<dyn ProtectIsolatedMemory>,
1565    /// Dma client for shared visibility pages.
1566    pub shared_dma_client: Arc<dyn DmaClient>,
1567    /// Allocator for private visibility pages.
1568    pub private_dma_client: Arc<dyn DmaClient>,
1569}
1570
1571/// Represents a GPN that is either in guest memory or was allocated by dma_client.
1572#[derive(Debug, Copy, Clone, PartialEq, Eq)]
1573pub enum GpnSource {
1574    /// The GPN is in regular guest RAM.
1575    GuestMemory,
1576    /// The GPN was allocated by dma_client and is not in guest RAM.
1577    Dma,
1578}
1579
1580/// Trait for CVM-related protections on guest memory.
1581pub trait ProtectIsolatedMemory: Send + Sync {
1582    /// Changes host visibility on guest memory.
1583    fn change_host_visibility(
1584        &self,
1585        vtl: GuestVtl,
1586        shared: bool,
1587        gpns: &[u64],
1588        tlb_access: &mut dyn TlbFlushLockAccess,
1589    ) -> Result<(), (HvError, usize)>;
1590
1591    /// Queries host visibility on guest memory.
1592    fn query_host_visibility(
1593        &self,
1594        gpns: &[u64],
1595        host_visibility: &mut [HostVisibilityType],
1596    ) -> Result<(), (HvError, usize)>;
1597
1598    /// Gets the default protections/permissions for VTL 0.
1599    fn default_vtl0_protections(&self) -> HvMapGpaFlags;
1600
1601    /// Changes the default protections/permissions for a VTL. For VBS-isolated
1602    /// VMs, the protections apply to all vtls lower than the specified one. For
1603    /// hardware-isolated VMs, they apply just to the given vtl.
1604    fn change_default_vtl_protections(
1605        &self,
1606        target_vtl: GuestVtl,
1607        protections: HvMapGpaFlags,
1608        tlb_access: &mut dyn TlbFlushLockAccess,
1609    ) -> Result<(), HvError>;
1610
1611    /// Changes the vtl protections on a range of guest memory.
1612    fn change_vtl_protections(
1613        &self,
1614        target_vtl: GuestVtl,
1615        gpns: &[u64],
1616        protections: HvMapGpaFlags,
1617        tlb_access: &mut dyn TlbFlushLockAccess,
1618    ) -> Result<(), (HvError, usize)>;
1619
1620    /// Registers a page as an overlay page by first validating it has the
1621    /// required permissions, optionally modifying them, then locking them.
1622    fn register_overlay_page(
1623        &self,
1624        vtl: GuestVtl,
1625        gpn: u64,
1626        gpn_source: GpnSource,
1627        check_perms: HvMapGpaFlags,
1628        new_perms: Option<HvMapGpaFlags>,
1629        tlb_access: &mut dyn TlbFlushLockAccess,
1630    ) -> Result<(), HvError>;
1631
1632    /// Unregisters an overlay page, removing its permission lock and restoring
1633    /// the previous permissions.
1634    fn unregister_overlay_page(
1635        &self,
1636        vtl: GuestVtl,
1637        gpn: u64,
1638        tlb_access: &mut dyn TlbFlushLockAccess,
1639    ) -> Result<(), HvError>;
1640
1641    /// Checks whether a page is currently registered as an overlay page.
1642    fn is_overlay_page(&self, vtl: GuestVtl, gpn: u64) -> bool;
1643
1644    /// Locks the permissions and mappings for a set of guest pages.
1645    fn lock_gpns(&self, vtl: GuestVtl, gpns: &[u64]) -> Result<(), GuestMemoryBackingError>;
1646
1647    /// Unlocks the permissions and mappings for a set of guest pages.
1648    ///
1649    /// Panics if asked to unlock a page that was not previously locked. The
1650    /// caller must ensure that the given slice has the same ordering as the
1651    /// one passed to `lock_gpns`.
1652    fn unlock_gpns(&self, vtl: GuestVtl, gpns: &[u64]);
1653
1654    /// Alerts the memory protector that vtl 1 is ready to set vtl protections
1655    /// on lower-vtl memory, and that these protections should be enforced.
1656    fn set_vtl1_protections_enabled(&self);
1657
1658    /// Whether VTL 1 is prepared to modify vtl protections on lower-vtl memory,
1659    /// and therefore whether these protections should be enforced.
1660    fn vtl1_protections_enabled(&self) -> bool;
1661}
1662
1663/// Trait for access to TLB flush and lock machinery.
1664pub trait TlbFlushLockAccess {
1665    /// Flush the entire TLB for all VPs for the given VTL.
1666    fn flush(&mut self, vtl: GuestVtl);
1667
1668    /// Flush the entire TLB for all VPs for all VTLs.
1669    fn flush_entire(&mut self);
1670
1671    /// Causes the specified VTL on the current VP to wait on all TLB locks.
1672    fn set_wait_for_tlb_locks(&mut self, vtl: GuestVtl);
1673}
1674
1675/// A partially built partition. Used to allow querying partition capabilities
1676/// before fully instantiating the partition.
1677pub struct UhProtoPartition<'a> {
1678    params: UhPartitionNewParams<'a>,
1679    hcl: Hcl,
1680    guest_vsm_available: bool,
1681    create_partition_available: bool,
1682    tdx_hw_seal_keys_enabled: bool,
1683    #[cfg(guest_arch = "x86_64")]
1684    cpuid: virt::CpuidLeafSet,
1685}
1686
1687impl<'a> UhProtoPartition<'a> {
1688    /// Creates a new prototype partition.
1689    ///
1690    /// `driver(cpu)` returns the driver to use for polling the sidecar device
1691    /// whose base CPU is `cpu`.
1692    pub fn new<T: SpawnDriver>(
1693        params: &UhPartitionNewParams<'a>,
1694        driver: impl FnMut(u32) -> T,
1695    ) -> Result<Self, Error> {
1696        let hcl_isolation = match params.isolation {
1697            IsolationType::None => hcl::ioctl::IsolationType::None,
1698            IsolationType::Vbs => hcl::ioctl::IsolationType::Vbs,
1699            IsolationType::Snp => hcl::ioctl::IsolationType::Snp,
1700            IsolationType::Tdx => hcl::ioctl::IsolationType::Tdx,
1701            IsolationType::Cca => hcl::ioctl::IsolationType::Cca,
1702        };
1703
1704        // Try to open the sidecar device, if it is present.
1705        let sidecar = sidecar_client::SidecarClient::new(driver).map_err(Error::Sidecar)?;
1706
1707        let hcl = Hcl::new(hcl_isolation, sidecar).map_err(Error::Hcl)?;
1708
1709        // Set the hypercalls that this process will use.
1710        let mut allowed_hypercalls = vec![
1711            hvdef::HypercallCode::HvCallGetVpRegisters,
1712            hvdef::HypercallCode::HvCallSetVpRegisters,
1713            hvdef::HypercallCode::HvCallInstallIntercept,
1714            hvdef::HypercallCode::HvCallTranslateVirtualAddress,
1715            hvdef::HypercallCode::HvCallPostMessageDirect,
1716            hvdef::HypercallCode::HvCallSignalEventDirect,
1717            hvdef::HypercallCode::HvCallModifyVtlProtectionMask,
1718            hvdef::HypercallCode::HvCallTranslateVirtualAddressEx,
1719            hvdef::HypercallCode::HvCallCheckSparseGpaPageVtlAccess,
1720            hvdef::HypercallCode::HvCallAssertVirtualInterrupt,
1721            hvdef::HypercallCode::HvCallGetVpIndexFromApicId,
1722            hvdef::HypercallCode::HvCallAcceptGpaPages,
1723            hvdef::HypercallCode::HvCallModifySparseGpaPageHostVisibility,
1724        ];
1725
1726        if params.isolation.is_hardware_isolated() {
1727            allowed_hypercalls.extend(vec![
1728                hvdef::HypercallCode::HvCallEnablePartitionVtl,
1729                hvdef::HypercallCode::HvCallRetargetDeviceInterrupt,
1730                hvdef::HypercallCode::HvCallEnableVpVtl,
1731            ]);
1732        }
1733
1734        if params.use_mmio_hypercalls {
1735            allowed_hypercalls.extend(vec![
1736                hvdef::HypercallCode::HvCallMemoryMappedIoRead,
1737                hvdef::HypercallCode::HvCallMemoryMappedIoWrite,
1738            ]);
1739        }
1740
1741        hcl.set_allowed_hypercalls(allowed_hypercalls.as_slice());
1742
1743        set_vtl2_vsm_partition_config(&hcl)?;
1744
1745        // For TDX, opt this TD into hardware-bound seal keys as early as
1746        // possible (before attestation runs `TDG.MR.KEY.GET` to seal/unseal the
1747        // VMGS DEK). `TD_CTLS.ENABLE_HW_SEAL_KEYS` is a prerequisite for
1748        // `TDG.MR.KEY.GET`, so it must be set before
1749        // `initialize_platform_security`, which runs before the partition
1750        // backing (and individual VPs) are created. This is best-effort: it is
1751        // a no-op on TDX modules that do not implement sealing, in which case
1752        // attestation falls back to other key-protection schemes.
1753        //
1754        // The result is recorded so that the `tee_call` used by attestation can
1755        // accurately report whether `TDG.MR.KEY.GET` is available this boot,
1756        // instead of optimistically assuming support.
1757        let tdx_hw_seal_keys_enabled = if params.isolation == IsolationType::Tdx {
1758            // Read `TDX_FEATURES0` purely for diagnostics: it surfaces whether
1759            // the module advertises sealing support so a field failure can be
1760            // triaged as "module doesn't implement sealing" vs. "enable didn't
1761            // stick". It does NOT gate enablement below; the authoritative check
1762            // is the read-back verify inside `tdx_enable_hw_seal_keys`, since
1763            // `ENABLE_HW_SEAL_KEYS` can work even when the feature bit is clear.
1764            // Best-effort: older modules may not support `TDG.SYS.RD`.
1765            match hcl.tdx_read_features0() {
1766                Ok(features0) => {
1767                    tracing::info!(
1768                        CVM_ALLOWED,
1769                        sealing = features0.sealing(),
1770                        td_signing_and_svn = features0.td_signing_and_svn(),
1771                        sealkey_128 = features0.sealkey_128(),
1772                        "TDX_FEATURES0 sealing capability"
1773                    );
1774                }
1775                Err(err) => {
1776                    tracing::warn!(
1777                        CVM_ALLOWED,
1778                        error = u64::from(err),
1779                        "failed to read TDX_FEATURES0 (TDG.SYS.RD unsupported?)"
1780                    );
1781                }
1782            }
1783
1784            match hcl.tdx_enable_hw_seal_keys() {
1785                Ok(true) => {
1786                    tracing::info!(CVM_ALLOWED, "TDX hardware-bound seal keys enabled");
1787                    true
1788                }
1789                Ok(false) => {
1790                    tracing::info!(
1791                        CVM_ALLOWED,
1792                        "TDX hardware-bound seal keys not supported by the TDX module"
1793                    );
1794                    false
1795                }
1796                Err(err) => {
1797                    tracing::warn!(
1798                        CVM_ALLOWED,
1799                        error = u64::from(err),
1800                        "failed to enable TDX hardware-bound seal keys"
1801                    );
1802                    false
1803                }
1804            }
1805        } else {
1806            false
1807        };
1808
1809        let privs = hcl
1810            .get_privileges_and_features_info()
1811            .map_err(Error::GetReg)?;
1812        let guest_vsm_available = Self::check_guest_vsm_support(privs, &hcl)?;
1813
1814        #[cfg(guest_arch = "x86_64")]
1815        let cpuid = match params.isolation {
1816            IsolationType::Snp => {
1817                let secure_avic = x86defs::snp::SevStatusMsr::from(
1818                    MsrDevice::new(0)
1819                        .expect("open msr")
1820                        .read_msr(x86defs::X86X_AMD_MSR_SEV)
1821                        .expect("read msr"),
1822                )
1823                .secure_avic();
1824                cvm_cpuid::CpuidResultsIsolationType::Snp {
1825                    cpuid_pages: params.cvm_cpuid_info.unwrap(),
1826                    vtom: params.vtom.unwrap(),
1827                    access_vsm: guest_vsm_available,
1828                    secure_avic,
1829                }
1830                .build()
1831                .map_err(Error::CvmCpuid)?
1832            }
1833
1834            IsolationType::Tdx => cvm_cpuid::CpuidResultsIsolationType::Tdx {
1835                topology: params.topology,
1836                vtom: params.vtom.unwrap(),
1837                access_vsm: guest_vsm_available,
1838            }
1839            .build()
1840            .map_err(Error::CvmCpuid)?,
1841            IsolationType::Vbs | IsolationType::None => virt::CpuidLeafSet::new(Vec::new()),
1842            // Eliminate 'non-exhaustive patterns' compilation warning, we shouldn't reach here for
1843            // any arm64 types.
1844            IsolationType::Cca => unreachable!(),
1845        };
1846
1847        let vtom = match params.isolation {
1848            #[cfg(guest_arch = "aarch64")]
1849            IsolationType::Cca if params.vtom.is_none() => {
1850                // Query vtom from realm config.
1851                let realm_config = hcl.get_realm_config().map_err(Error::Hcl)?;
1852                Some(1_u64 << (realm_config.ipa_width() - 1))
1853            }
1854            _ => params.vtom,
1855        };
1856
1857        Ok(UhProtoPartition {
1858            hcl,
1859            params: UhPartitionNewParams { vtom, ..*params },
1860            guest_vsm_available,
1861            create_partition_available: privs.create_partitions(),
1862            tdx_hw_seal_keys_enabled,
1863            #[cfg(guest_arch = "x86_64")]
1864            cpuid,
1865        })
1866    }
1867
1868    /// Returns whether VSM support will be available to the guest.
1869    pub fn guest_vsm_available(&self) -> bool {
1870        self.guest_vsm_available
1871    }
1872
1873    /// Returns whether this partition has the create partitions hypercall
1874    /// available.
1875    pub fn create_partition_available(&self) -> bool {
1876        self.create_partition_available
1877    }
1878
1879    /// Returns whether TDX hardware-bound seal keys were successfully enabled
1880    /// for this TD (always `false` for non-TDX isolation). When `true`, the
1881    /// `TDG.MR.KEY.GET` TDCALL is available for deriving hardware-bound keys.
1882    pub fn tdx_hw_seal_keys_enabled(&self) -> bool {
1883        self.tdx_hw_seal_keys_enabled
1884    }
1885
1886    /// Returns a new Underhill partition.
1887    pub async fn build(
1888        self,
1889        late_params: UhLateParams<'_>,
1890    ) -> Result<(UhPartition, Vec<UhProcessorBox>), Error> {
1891        let Self {
1892            mut hcl,
1893            params,
1894            guest_vsm_available,
1895            create_partition_available: _,
1896            tdx_hw_seal_keys_enabled: _,
1897            #[cfg(guest_arch = "x86_64")]
1898            cpuid,
1899        } = self;
1900        let isolation = params.isolation;
1901        let is_hardware_isolated = isolation.is_hardware_isolated();
1902
1903        // Intercept Debug Exceptions
1904        // On TDX because all OpenHCL TDs today have the debug policy bit set,
1905        // OpenHCL registers for the intercepts itself.
1906        // However, on non-TDX platforms hypervisor installs the
1907        // intercept on behalf of the guest.
1908        if params.intercept_debug_exceptions {
1909            if !cfg!(feature = "gdb") {
1910                return Err(Error::InvalidDebugConfiguration);
1911            }
1912
1913            cfg_if::cfg_if! {
1914                if #[cfg(guest_arch = "x86_64")] {
1915                    if isolation != IsolationType::Tdx {
1916                        let debug_exception_vector = 0x1;
1917                        hcl.register_intercept(
1918                            HvInterceptType::HvInterceptTypeException,
1919                            HV_INTERCEPT_ACCESS_MASK_EXECUTE,
1920                            HvInterceptParameters::new_exception(debug_exception_vector),
1921                        )
1922                        .map_err(|err| Error::InstallIntercept(HvInterceptType::HvInterceptTypeException, err))?;
1923                    }
1924                } else {
1925                    return Err(Error::InvalidDebugConfiguration);
1926                }
1927            }
1928        }
1929
1930        if !is_hardware_isolated {
1931            if cfg!(guest_arch = "x86_64") {
1932                hcl.register_intercept(
1933                    HvInterceptType::HvInterceptTypeX64Msr,
1934                    HV_INTERCEPT_ACCESS_MASK_READ_WRITE,
1935                    HvInterceptParameters::new_zeroed(),
1936                )
1937                .map_err(|err| {
1938                    Error::InstallIntercept(HvInterceptType::HvInterceptTypeX64Msr, err)
1939                })?;
1940
1941                hcl.register_intercept(
1942                    HvInterceptType::HvInterceptTypeX64ApicEoi,
1943                    HV_INTERCEPT_ACCESS_MASK_WRITE,
1944                    HvInterceptParameters::new_zeroed(),
1945                )
1946                .map_err(|err| {
1947                    Error::InstallIntercept(HvInterceptType::HvInterceptTypeX64ApicEoi, err)
1948                })?;
1949            } else {
1950                if false {
1951                    todo!("AARCH64_TODO");
1952                }
1953            }
1954        }
1955
1956        if isolation == IsolationType::Snp {
1957            // SNP VMs register for the #VC exception to support reflect-VC.
1958            hcl.register_intercept(
1959                HvInterceptType::HvInterceptTypeException,
1960                HV_INTERCEPT_ACCESS_MASK_EXECUTE,
1961                HvInterceptParameters::new_exception(
1962                    x86defs::Exception::SEV_VMM_COMMUNICATION.0 as u16,
1963                ),
1964            )
1965            .map_err(|err| {
1966                Error::InstallIntercept(HvInterceptType::HvInterceptTypeException, err)
1967            })?;
1968
1969            // Get the register tweak bitmap from secrets page.
1970            let mut bitmap = [0u8; 64];
1971            if let Some(secrets) = params.snp_secrets {
1972                bitmap.copy_from_slice(
1973                    &secrets
1974                        [REG_TWEAK_BITMAP_OFFSET..REG_TWEAK_BITMAP_OFFSET + REG_TWEAK_BITMAP_SIZE],
1975                );
1976            }
1977            hcl.set_snp_register_bitmap(bitmap);
1978        }
1979
1980        // Do per-VP HCL initialization.
1981        hcl.add_vps(
1982            params.topology.vp_count(),
1983            late_params
1984                .cvm_params
1985                .as_ref()
1986                .map(|x| &x.private_dma_client),
1987        )
1988        .map_err(Error::Hcl)?;
1989
1990        let vps: Vec<_> = params
1991            .topology
1992            .vps_arch()
1993            .map(|vp_info| {
1994                // TODO: determine CPU index, which in theory could be different
1995                // from the VP index, though this hasn't happened yet.
1996                let cpu_index = vp_info.base.vp_index.index();
1997                UhVpInner::new(cpu_index, vp_info)
1998            })
1999            .collect();
2000
2001        // Enable support for VPCI devices if the hypervisor supports it.
2002        #[cfg(guest_arch = "x86_64")]
2003        let software_devices = {
2004            let res = if !is_hardware_isolated {
2005                hcl.register_intercept(
2006                    HvInterceptType::HvInterceptTypeRetargetInterruptWithUnknownDeviceId,
2007                    HV_INTERCEPT_ACCESS_MASK_EXECUTE,
2008                    HvInterceptParameters::new_zeroed(),
2009                )
2010            } else {
2011                Ok(())
2012            };
2013            match res {
2014                Ok(()) => Some(ApicSoftwareDevices::new(
2015                    params.topology.vps_arch().map(|vp| vp.apic_id).collect(),
2016                )),
2017                Err(HvError::InvalidParameter | HvError::AccessDenied) => None,
2018                Err(err) => {
2019                    return Err(Error::InstallIntercept(
2020                        HvInterceptType::HvInterceptTypeRetargetInterruptWithUnknownDeviceId,
2021                        err,
2022                    ));
2023                }
2024            }
2025        };
2026
2027        // TODO: CCA: will probably need to add some support here like above
2028        #[cfg(guest_arch = "aarch64")]
2029        let software_devices = None;
2030
2031        #[cfg(guest_arch = "aarch64")]
2032        let caps = virt::aarch64::Aarch64PartitionCapabilities {
2033            isolation: params.isolation,
2034            vendor: Vendor::ARM,
2035            supports_aarch32_el0: false,
2036        };
2037
2038        #[cfg(guest_arch = "x86_64")]
2039        let cpuid = UhPartition::construct_cpuid_results(
2040            cpuid,
2041            &late_params.cpuid,
2042            params.topology,
2043            isolation,
2044            params.hide_isolation,
2045        );
2046
2047        #[cfg(guest_arch = "x86_64")]
2048        let caps = UhPartition::construct_capabilities(
2049            params.topology,
2050            &cpuid,
2051            isolation,
2052            params.hide_isolation,
2053        )
2054        .map_err(Error::Capabilities)?;
2055
2056        if params.handle_synic && !matches!(isolation, IsolationType::Tdx) {
2057            // The hypervisor will manage the untrusted SINTs (or the whole
2058            // synic for non-hardware-isolated VMs), but some event ports
2059            // and message ports are implemented here. Register an intercept
2060            // to handle HvSignalEvent and HvPostMessage hypercalls when the
2061            // hypervisor doesn't recognize the connection ID.
2062            //
2063            // TDX manages this locally instead of through the hypervisor.
2064            hcl.register_intercept(
2065                HvInterceptType::HvInterceptTypeUnknownSynicConnection,
2066                HV_INTERCEPT_ACCESS_MASK_EXECUTE,
2067                HvInterceptParameters::new_zeroed(),
2068            )
2069            .expect("registering synic intercept cannot fail");
2070        }
2071
2072        let cvm_state = if is_hardware_isolated {
2073            let vsm_caps = hcl.get_vsm_capabilities().map_err(Error::GetReg)?;
2074            let proxy_interrupt_redirect_available =
2075                vsm_caps.proxy_interrupt_redirect_available() && !params.disable_proxy_redirect;
2076
2077            Some(Self::construct_cvm_state(
2078                &params,
2079                late_params.cvm_params.unwrap(),
2080                &caps,
2081                guest_vsm_available,
2082                proxy_interrupt_redirect_available,
2083            )?)
2084        } else {
2085            None
2086        };
2087
2088        let lower_vtl_timer_virt_available =
2089            hcl.supports_lower_vtl_timer_virt() && !params.disable_lower_vtl_timer_virt;
2090
2091        let backing_shared = BackingShared::new(
2092            isolation,
2093            &params,
2094            BackingSharedParams {
2095                cvm_state,
2096                #[cfg(guest_arch = "x86_64")]
2097                cpuid: &cpuid,
2098                hcl: &hcl,
2099                guest_vsm_available,
2100                lower_vtl_timer_virt_available,
2101            },
2102        )?;
2103
2104        let enter_modes = EnterModes::default();
2105
2106        let partition = Arc::new(UhPartitionInner {
2107            hcl,
2108            vps,
2109            irq_routes: Default::default(),
2110            caps,
2111            enter_modes: Mutex::new(enter_modes),
2112            enter_modes_atomic: u8::from(hcl::protocol::EnterModes::from(enter_modes)).into(),
2113            gm: late_params.gm,
2114            vtl0_kernel_exec_gm: late_params.vtl0_kernel_exec_gm,
2115            vtl0_user_exec_gm: late_params.vtl0_user_exec_gm,
2116            #[cfg(guest_arch = "x86_64")]
2117            cpuid,
2118            crash_notification_send: late_params.crash_notification_send,
2119            monitor_page: MonitorPage::new(),
2120            allocated_monitor_page: Mutex::new(None),
2121            software_devices,
2122            lower_vtl_memory_layout: params.lower_vtl_memory_layout.clone(),
2123            vmtime: late_params.vmtime.clone(),
2124            isolation,
2125            no_sidecar_hotplug: params.no_sidecar_hotplug.into(),
2126            use_mmio_hypercalls: params.use_mmio_hypercalls,
2127            backing_shared,
2128            #[cfg(guest_arch = "x86_64")]
2129            device_vector_table: RwLock::new(IrrBitmap::new(Default::default())),
2130            intercept_debug_exceptions: params.intercept_debug_exceptions,
2131            vmbus_relay: late_params.vmbus_relay,
2132            synic_ports: Default::default(),
2133        });
2134
2135        if cfg!(guest_arch = "x86_64") {
2136            // Intercept all IOs unless opted out.
2137            partition.manage_io_port_intercept_region(0, !0, true);
2138        }
2139
2140        let vps = params
2141            .topology
2142            .vps_arch()
2143            .map(|vp_info| UhProcessorBox {
2144                partition: partition.clone(),
2145                vp_info,
2146            })
2147            .collect();
2148
2149        Ok((
2150            UhPartition {
2151                synic_ports: Arc::new(virt::synic::SynicPorts::new(partition.clone())),
2152                inner: partition.clone(),
2153                interrupt_targets: VtlArray::from_fn(|vtl| {
2154                    Arc::new(UhInterruptTarget {
2155                        partition: partition.clone(),
2156                        vtl: vtl.try_into().unwrap(),
2157                    })
2158                }),
2159            },
2160            vps,
2161        ))
2162    }
2163}
2164
2165impl UhPartition {
2166    /// Gets the guest OS ID for VTL0.
2167    pub fn vtl0_guest_os_id(&self) -> Result<HvGuestOsId, hcl::ioctl::register::GetRegError> {
2168        // If Underhill is emulating the hypervisor interfaces, get this value
2169        // from the emulator. This happens when running under hardware isolation
2170        // or when configured for testing.
2171        let id = if let Some(hv) = self.inner.hv() {
2172            hv.guest_os_id(Vtl::Vtl0)
2173        } else {
2174            // Ask the hypervisor for this value.
2175            self.inner.hcl.get_guest_os_id(GuestVtl::Vtl0)?
2176        };
2177        Ok(id)
2178    }
2179
2180    /// Configures guest accesses to IO ports in `range` to go directly to the
2181    /// host.
2182    ///
2183    /// When the return value is dropped, the ports will be unregistered.
2184    pub fn register_host_io_port_fast_path(
2185        &self,
2186        range: RangeInclusive<u16>,
2187    ) -> HostIoPortFastPathHandle {
2188        // There is no way to provide a fast path for some hardware isolated
2189        // VM architectures. The devices that do use this facility are not
2190        // enabled on hardware isolated VMs.
2191        assert!(!self.inner.isolation.is_hardware_isolated());
2192
2193        self.inner
2194            .manage_io_port_intercept_region(*range.start(), *range.end(), false);
2195        HostIoPortFastPathHandle {
2196            inner: Arc::downgrade(&self.inner),
2197            begin: *range.start(),
2198            end: *range.end(),
2199        }
2200    }
2201
2202    /// Trigger the LINT1 interrupt vector on the LAPIC of the BSP.
2203    #[cfg(guest_arch = "x86_64")]
2204    pub fn assert_debug_interrupt(&self, vtl: Vtl) {
2205        const LINT_INDEX_1: u8 = 1;
2206        // For SNP CVMs, only deliver the debug NMI when the host CPU
2207        // supports virtual NMI (CPUID Fn8000_000A_EDX[V_NMI]). Without
2208        // V_NMI, injecting multiple NMIs can corrupt the NMI stack in the
2209        // guest.
2210        if self.inner.isolation == IsolationType::Snp {
2211            let vnmi = match &self.inner.backing_shared {
2212                BackingShared::Snp(snp) => snp.vnmi,
2213                _ => false,
2214            };
2215            if !vnmi {
2216                tracing::error!("debug interrupt is not supported on SNP without virtual NMI");
2217                return;
2218            }
2219        }
2220        let bsp_index = VpIndex::new(0);
2221        self.pulse_lint(bsp_index, vtl, LINT_INDEX_1);
2222    }
2223
2224    /// Debug interrupts are not supported on aarch64.
2225    #[cfg(guest_arch = "aarch64")]
2226    pub fn assert_debug_interrupt(&self, _vtl: Vtl) {
2227        tracing::error!("debug interrupts are not supported on aarch64");
2228    }
2229
2230    /// Enables or disables the PM timer assist.
2231    pub fn set_pm_timer_assist(
2232        &self,
2233        port: Option<u16>,
2234    ) -> Result<(), hcl::ioctl::register::SetRegError> {
2235        self.inner.hcl.set_pm_timer_assist(port)
2236    }
2237}
2238
2239impl UhPartitionInner {
2240    /// Sets guest memory protections for a monitor page.
2241    fn register_cvm_dma_overlay_page(
2242        &self,
2243        vtl: GuestVtl,
2244        gpn: u64,
2245        new_perms: HvMapGpaFlags,
2246    ) -> anyhow::Result<()> {
2247        // How the monitor page is protected depends on the isolation type of the VM.
2248        match &self.backing_shared {
2249            #[cfg(guest_arch = "x86_64")]
2250            BackingShared::Snp(snp_backed_shared) => snp_backed_shared
2251                .cvm
2252                .isolated_memory_protector
2253                .register_overlay_page(
2254                    vtl,
2255                    gpn,
2256                    // On a CVM, the monitor page is always DMA-allocated.
2257                    GpnSource::Dma,
2258                    HvMapGpaFlags::new(),
2259                    Some(new_perms),
2260                    &mut SnpBacked::tlb_flush_lock_access(None, self, snp_backed_shared),
2261                )
2262                .map_err(|e| anyhow::anyhow!(e)),
2263            #[cfg(guest_arch = "x86_64")]
2264            BackingShared::Tdx(tdx_backed_shared) => tdx_backed_shared
2265                .cvm
2266                .isolated_memory_protector
2267                .register_overlay_page(
2268                    vtl,
2269                    gpn,
2270                    GpnSource::Dma,
2271                    HvMapGpaFlags::new(),
2272                    Some(new_perms),
2273                    &mut TdxBacked::tlb_flush_lock_access(None, self, tdx_backed_shared),
2274                )
2275                .map_err(|e| anyhow::anyhow!(e)),
2276            #[cfg(guest_arch = "aarch64")]
2277            BackingShared::Cca(cca_backed_shared) => cca_backed_shared
2278                .cvm
2279                .isolated_memory_protector
2280                .register_overlay_page(
2281                    vtl,
2282                    gpn,
2283                    GpnSource::Dma,
2284                    HvMapGpaFlags::new(),
2285                    Some(new_perms),
2286                    &mut CcaBacked::tlb_flush_lock_access(None, self, cca_backed_shared),
2287                )
2288                .map_err(|e| anyhow::anyhow!(e)),
2289            BackingShared::Hypervisor(_) => {
2290                let _ = (vtl, gpn, new_perms);
2291                unreachable!()
2292            }
2293        }
2294    }
2295
2296    /// Reverts guest memory protections for a monitor page.
2297    fn unregister_cvm_dma_overlay_page(&self, vtl: GuestVtl, gpn: u64) -> anyhow::Result<()> {
2298        // How the monitor page is protected depends on the isolation type of the VM.
2299        match &self.backing_shared {
2300            #[cfg(guest_arch = "x86_64")]
2301            BackingShared::Snp(snp_backed_shared) => snp_backed_shared
2302                .cvm
2303                .isolated_memory_protector
2304                .unregister_overlay_page(
2305                    vtl,
2306                    gpn,
2307                    &mut SnpBacked::tlb_flush_lock_access(None, self, snp_backed_shared),
2308                )
2309                .map_err(|e| anyhow::anyhow!(e)),
2310            #[cfg(guest_arch = "x86_64")]
2311            BackingShared::Tdx(tdx_backed_shared) => tdx_backed_shared
2312                .cvm
2313                .isolated_memory_protector
2314                .unregister_overlay_page(
2315                    vtl,
2316                    gpn,
2317                    &mut TdxBacked::tlb_flush_lock_access(None, self, tdx_backed_shared),
2318                )
2319                .map_err(|e| anyhow::anyhow!(e)),
2320            BackingShared::Hypervisor(_) => {
2321                let _ = (vtl, gpn);
2322                unreachable!()
2323            }
2324            #[cfg(guest_arch = "aarch64")]
2325            BackingShared::Cca(cca_backed_shared) => cca_backed_shared
2326                .cvm
2327                .isolated_memory_protector
2328                .unregister_overlay_page(
2329                    vtl,
2330                    gpn,
2331                    &mut CcaBacked::tlb_flush_lock_access(None, self, cca_backed_shared),
2332                )
2333                .map_err(|e| anyhow::anyhow!(e)),
2334        }
2335    }
2336}
2337
2338impl UhProtoPartition<'_> {
2339    /// Whether Guest VSM is available to the guest. If so, for hardware CVMs,
2340    /// it is safe to expose Guest VSM support via cpuid.
2341    fn check_guest_vsm_support(privs: HvPartitionPrivilege, hcl: &Hcl) -> Result<bool, Error> {
2342        if !privs.access_vsm() {
2343            return Ok(false);
2344        }
2345
2346        let guest_vsm_config = hcl
2347            .get_guest_vsm_partition_config()
2348            .map_err(Error::GetReg)?;
2349        Ok(guest_vsm_config.maximum_vtl() >= u8::from(GuestVtl::Vtl1))
2350    }
2351
2352    /// Constructs partition-wide CVM state.
2353    fn construct_cvm_state(
2354        params: &UhPartitionNewParams<'_>,
2355        late_params: CvmLateParams,
2356        caps: &PartitionCapabilities,
2357        guest_vsm_available: bool,
2358        proxy_interrupt_redirect_available: bool,
2359    ) -> Result<UhCvmPartitionState, Error> {
2360        use vmcore::reference_time::ReferenceTimeSource;
2361
2362        let vp_count = params.topology.vp_count() as usize;
2363        let vps = (0..vp_count)
2364            .map(|vp_index| UhCvmVpInner {
2365                tlb_lock_info: VtlArray::from_fn(|_| TlbLockInfo::new(vp_count)),
2366                vtl1_enable_called: Mutex::new(false),
2367                started: AtomicBool::new(vp_index == 0),
2368                hv_start_enable_vtl_vp: VtlArray::from_fn(|_| Mutex::new(None)),
2369                proxy_redirect_interrupts: Mutex::new(HashMap::new()),
2370            })
2371            .collect();
2372        let tlb_locked_vps =
2373            VtlArray::from_fn(|_| BitVec::repeat(false, vp_count).into_boxed_bitslice());
2374
2375        #[cfg(guest_arch = "x86_64")]
2376        let lapic = VtlArray::from_fn(|_| {
2377            LocalApicSet::builder()
2378                .x2apic_capable(caps.x2apic)
2379                .hyperv_enlightenments(true)
2380                .build()
2381        });
2382
2383        let tsc_frequency = get_tsc_frequency(params.isolation)?;
2384        let ref_time = ReferenceTimeSource::new(TscReferenceTimeSource::new(tsc_frequency));
2385
2386        // If we're emulating the APIC, then we also must emulate the hypervisor
2387        // enlightenments, since the hypervisor can't support enlightenments
2388        // without also providing an APIC.
2389        //
2390        // Additionally, TDX provides hardware APIC emulation but we still need
2391        // to emulate the hypervisor enlightenments.
2392        let hv = GlobalHv::new(hv1_emulator::hv::GlobalHvParams {
2393            max_vp_count: params.topology.vp_count(),
2394            vendor: caps.vendor,
2395            tsc_frequency,
2396            ref_time,
2397            is_ref_time_backed_by_tsc: true,
2398        });
2399
2400        Ok(UhCvmPartitionState {
2401            #[cfg(guest_arch = "x86_64")]
2402            vps_per_socket: params.topology.reserved_vps_per_socket(),
2403            tlb_locked_vps,
2404            vps,
2405            shared_memory: late_params.shared_gm,
2406            isolated_memory_protector: late_params.isolated_memory_protector,
2407            #[cfg(guest_arch = "x86_64")]
2408            lapic,
2409            hv,
2410            guest_vsm: RwLock::new(GuestVsmState::from_availability(guest_vsm_available)),
2411            access_vsm_privilege: guest_vsm_available,
2412            shared_dma_client: late_params.shared_dma_client,
2413            private_dma_client: late_params.private_dma_client,
2414            hide_isolation: params.hide_isolation,
2415            proxy_interrupt_redirect: proxy_interrupt_redirect_available,
2416        })
2417    }
2418
2419    /// vtom getter
2420    pub fn get_vtom(&self) -> Option<u64> {
2421        self.params.vtom
2422    }
2423}
2424
2425impl UhPartition {
2426    #[cfg(guest_arch = "x86_64")]
2427    /// Constructs the set of cpuid results to show to the guest
2428    fn construct_cpuid_results(
2429        cpuid: virt::CpuidLeafSet,
2430        initial_cpuid: &[CpuidLeaf],
2431        topology: &ProcessorTopology<vm_topology::processor::x86::X86Topology>,
2432        isolation: IsolationType,
2433        hide_isolation: bool,
2434    ) -> virt::CpuidLeafSet {
2435        let mut cpuid = cpuid.into_leaves();
2436        if isolation.is_hardware_isolated() {
2437            // Update the x2apic leaf based on the topology.
2438            let x2apic = match topology.apic_mode() {
2439                vm_topology::processor::x86::ApicMode::XApic => false,
2440                vm_topology::processor::x86::ApicMode::X2ApicSupported => true,
2441                vm_topology::processor::x86::ApicMode::X2ApicEnabled => true,
2442            };
2443            let ecx = x86defs::cpuid::VersionAndFeaturesEcx::new().with_x2_apic(x2apic);
2444            let ecx_mask = x86defs::cpuid::VersionAndFeaturesEcx::new().with_x2_apic(true);
2445            cpuid.push(
2446                CpuidLeaf::new(
2447                    x86defs::cpuid::CpuidFunction::VersionAndFeatures.0,
2448                    [0, 0, ecx.into(), 0],
2449                )
2450                .masked([0, 0, ecx_mask.into(), 0]),
2451            );
2452
2453            // Get the hypervisor version from the host. This is just for
2454            // reporting purposes, so it is safe even if the hypervisor is not
2455            // trusted.
2456            let hv_version = safe_intrinsics::cpuid(hvdef::HV_CPUID_FUNCTION_MS_HV_VERSION, 0);
2457
2458            // Perform final processing steps for synthetic leaves.
2459            hv1_emulator::cpuid::process_hv_cpuid_leaves(
2460                &mut cpuid,
2461                hide_isolation,
2462                [
2463                    hv_version.eax,
2464                    hv_version.ebx,
2465                    hv_version.ecx,
2466                    hv_version.edx,
2467                ],
2468            );
2469        }
2470        cpuid.extend(initial_cpuid);
2471        virt::CpuidLeafSet::new(cpuid)
2472    }
2473
2474    #[cfg(guest_arch = "x86_64")]
2475    /// Computes the partition capabilities
2476    fn construct_capabilities(
2477        topology: &ProcessorTopology,
2478        cpuid: &virt::CpuidLeafSet,
2479        isolation: IsolationType,
2480        hide_isolation: bool,
2481    ) -> Result<virt::x86::X86PartitionCapabilities, virt::x86::X86PartitionCapabilitiesError> {
2482        let mut native_cpuid_fn;
2483        let mut cvm_cpuid_fn;
2484
2485        // Determine the method to get cpuid results for the guest when
2486        // computing partition capabilities.
2487        let cpuid_fn: &mut dyn FnMut(u32, u32) -> [u32; 4] = if isolation.is_hardware_isolated() {
2488            // Use the filtered CPUID to determine capabilities.
2489            cvm_cpuid_fn = move |leaf, sub_leaf| cpuid.result(leaf, sub_leaf, &[0, 0, 0, 0]);
2490            &mut cvm_cpuid_fn
2491        } else {
2492            // Just use the native cpuid.
2493            native_cpuid_fn = |leaf, sub_leaf| {
2494                let CpuidResult { eax, ebx, ecx, edx } = safe_intrinsics::cpuid(leaf, sub_leaf);
2495                cpuid.result(leaf, sub_leaf, &[eax, ebx, ecx, edx])
2496            };
2497            &mut native_cpuid_fn
2498        };
2499
2500        // Compute and validate capabilities.
2501        let mut caps = virt::x86::X86PartitionCapabilities::from_cpuid(topology, cpuid_fn)?;
2502        match isolation {
2503            IsolationType::Tdx => {
2504                assert_eq!(caps.vtom.is_some(), !hide_isolation);
2505                // TDX 1.5 requires EFER.NXE to be set to 1, so set it at RESET/INIT.
2506                caps.nxe_forced_on = true;
2507            }
2508            IsolationType::Snp => {
2509                assert_eq!(caps.vtom.is_some(), !hide_isolation);
2510            }
2511            _ => {
2512                assert!(caps.vtom.is_none());
2513            }
2514        }
2515
2516        Ok(caps)
2517    }
2518}
2519
2520/// Gets the TSC frequency for the current platform.
2521fn get_tsc_frequency(isolation: IsolationType) -> Result<u64, Error> {
2522    // Always get the frequency from the hypervisor. It's believed that, as long
2523    // as the hypervisor is behaving, it will provide the most precise and accurate frequency.
2524    #[cfg(guest_arch = "x86_64")]
2525    let hv_frequency = {
2526        let msr = MsrDevice::new(0).map_err(Error::OpenMsr)?;
2527        msr.read_msr(hvdef::HV_X64_MSR_TSC_FREQUENCY)
2528            .map_err(Error::ReadTscFrequency)?
2529    };
2530    #[cfg(guest_arch = "aarch64")]
2531    let hv_frequency = read_cntfrq_el0();
2532
2533    // Get the hardware-advertised frequency and validate that the
2534    // hypervisor frequency is not too far off.
2535    let hw_info: Option<(u64, u64)> = match isolation {
2536        #[cfg(guest_arch = "x86_64")]
2537        IsolationType::Tdx => {
2538            // TDX provides the TSC frequency via cpuid.
2539            let max_function =
2540                safe_intrinsics::cpuid(x86defs::cpuid::CpuidFunction::VendorAndMaxFunction.0, 0)
2541                    .eax;
2542
2543            if max_function < x86defs::cpuid::CpuidFunction::CoreCrystalClockInformation.0 {
2544                return Err(Error::BadCpuidTsc);
2545            }
2546            let result = safe_intrinsics::cpuid(
2547                x86defs::cpuid::CpuidFunction::CoreCrystalClockInformation.0,
2548                0,
2549            );
2550            let ratio_denom = result.eax;
2551            let ratio_num = result.ebx;
2552            let clock = result.ecx;
2553            if ratio_num == 0 || ratio_denom == 0 || clock == 0 {
2554                return Err(Error::BadCpuidTsc);
2555            }
2556            // TDX TSC is configurable in units of 25MHz, so allow up to 12.5MHz
2557            // error.
2558            let allowed_error = 12_500_000;
2559            Some((
2560                clock as u64 * ratio_num as u64 / ratio_denom as u64,
2561                allowed_error,
2562            ))
2563        }
2564        #[cfg(not(guest_arch = "x86_64"))]
2565        IsolationType::Tdx => None,
2566        IsolationType::Snp => {
2567            // SNP currently does not provide the frequency.
2568            None
2569        }
2570        IsolationType::Vbs | IsolationType::None => None,
2571        IsolationType::Cca => {
2572            // CCA currently does not provide the frequency.
2573            None
2574        }
2575    };
2576
2577    if let Some((hw_frequency, allowed_error)) = hw_info {
2578        // Don't allow the frequencies to be different by more than the hardware
2579        // precision.
2580        let delta = hw_frequency.abs_diff(hv_frequency);
2581        if delta > allowed_error {
2582            return Err(Error::TscFrequencyMismatch {
2583                hv: hv_frequency,
2584                hw: hw_frequency,
2585                allowed_error,
2586            });
2587        }
2588    }
2589
2590    Ok(hv_frequency)
2591}
2592
2593impl UhPartitionInner {
2594    fn manage_io_port_intercept_region(&self, begin: u16, end: u16, active: bool) {
2595        if self.isolation.is_hardware_isolated() {
2596            return;
2597        }
2598
2599        static SKIP_RANGE: AtomicBool = AtomicBool::new(false);
2600
2601        let access_type_mask = if active {
2602            HV_INTERCEPT_ACCESS_MASK_READ_WRITE
2603        } else {
2604            HV_INTERCEPT_ACCESS_MASK_NONE
2605        };
2606
2607        // Try to register the whole range at once.
2608        if !SKIP_RANGE.load(Ordering::Relaxed) {
2609            match self.hcl.register_intercept(
2610                HvInterceptType::HvInterceptTypeX64IoPortRange,
2611                access_type_mask,
2612                HvInterceptParameters::new_io_port_range(begin..=end),
2613            ) {
2614                Ok(()) => return,
2615                Err(HvError::InvalidParameter) => {
2616                    // Probably a build that doesn't support range wrapping yet.
2617                    // Don't try again.
2618                    SKIP_RANGE.store(true, Ordering::Relaxed);
2619                    tracing::warn!(
2620                        CVM_ALLOWED,
2621                        "old hypervisor build; using slow path for intercept ranges"
2622                    );
2623                }
2624                Err(err) => {
2625                    panic!("io port range registration failure: {err:?}");
2626                }
2627            }
2628        }
2629
2630        // Fall back to registering one port at a time.
2631        for port in begin..=end {
2632            self.hcl
2633                .register_intercept(
2634                    HvInterceptType::HvInterceptTypeX64IoPort,
2635                    access_type_mask,
2636                    HvInterceptParameters::new_io_port(port),
2637                )
2638                .expect("registering io intercept cannot fail");
2639        }
2640    }
2641
2642    fn is_gpa_lower_vtl_ram(&self, gpa: u64) -> bool {
2643        // TODO: this probably should reflect changes to the memory map via PAM
2644        // registers. Right now this isn't an issue because the relevant region,
2645        // VGA, is handled on the host.
2646        self.lower_vtl_memory_layout
2647            .ram()
2648            .iter()
2649            .any(|m| m.range.contains_addr(gpa))
2650    }
2651
2652    fn is_gpa_mapped(&self, gpa: u64, write: bool) -> bool {
2653        // TODO: this probably should reflect changes to the memory map via PAM
2654        // registers. Right now this isn't an issue because the relevant region,
2655        // VGA, is handled on the host.
2656        if self.is_gpa_lower_vtl_ram(gpa) {
2657            // The monitor page is protected against lower VTL writes.
2658            !write || self.monitor_page.gpa() != Some(gpa & !(HV_PAGE_SIZE - 1))
2659        } else {
2660            false
2661        }
2662    }
2663}
2664
2665/// Handle returned by [`UhPartition::register_host_io_port_fast_path`].
2666///
2667/// When dropped, unregisters the IO ports so that they are no longer forwarded
2668/// to the host.
2669#[must_use]
2670pub struct HostIoPortFastPathHandle {
2671    inner: Weak<UhPartitionInner>,
2672    begin: u16,
2673    end: u16,
2674}
2675
2676impl Drop for HostIoPortFastPathHandle {
2677    fn drop(&mut self) {
2678        if let Some(inner) = self.inner.upgrade() {
2679            inner.manage_io_port_intercept_region(self.begin, self.end, true);
2680        }
2681    }
2682}
2683
2684/// The application level VTL crash data not suited for putting
2685/// on the wire.
2686///
2687/// FUTURE: move/remove this to standardize across virt backends.
2688#[derive(Copy, Clone, Debug)]
2689pub struct VtlCrash {
2690    /// The VP that crashed.
2691    pub vp_index: VpIndex,
2692    /// The VTL that crashed.
2693    pub last_vtl: GuestVtl,
2694    /// The crash control information.
2695    pub control: GuestCrashCtl,
2696    /// The crash parameters.
2697    pub parameters: [u64; 5],
2698}
2699
2700/// Validate that flags is a valid setting for VTL memory protection when
2701/// applied to VTL 1.
2702#[cfg_attr(guest_arch = "aarch64", expect(dead_code))]
2703fn validate_vtl_gpa_flags(
2704    flags: HvMapGpaFlags,
2705    mbec_enabled: bool,
2706    shadow_supervisor_stack_enabled: bool,
2707) -> bool {
2708    // Adjust is not allowed for VTL1.
2709    if flags.adjustable() {
2710        return false;
2711    }
2712
2713    // KX must equal UX unless MBEC is enabled. KX && !UX is invalid.
2714    if flags.kernel_executable() != flags.user_executable() {
2715        if (flags.kernel_executable() && !flags.user_executable()) || !mbec_enabled {
2716            return false;
2717        }
2718    }
2719
2720    // Read must be specified if anything else is specified.
2721    if flags.writable()
2722        || flags.kernel_executable()
2723        || flags.user_executable()
2724        || flags.supervisor_shadow_stack()
2725        || flags.paging_writability()
2726        || flags.verify_paging_writability()
2727    {
2728        if !flags.readable() {
2729            return false;
2730        }
2731    }
2732
2733    // Supervisor shadow stack protection is invalid if shadow stacks are disabled
2734    // or if execute is not specified.
2735    if flags.supervisor_shadow_stack()
2736        && ((!flags.kernel_executable() && !flags.user_executable())
2737            || shadow_supervisor_stack_enabled)
2738    {
2739        return false;
2740    }
2741
2742    true
2743}