Skip to main content

hcl/
ioctl.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Interface to `mshv_vtl` driver.
5
6mod deferred;
7pub mod register;
8
9pub mod aarch64;
10pub mod cca;
11pub mod snp;
12pub mod tdx;
13pub mod x64;
14
15use self::deferred::DeferredActionSlots;
16use self::ioctls::*;
17use crate::GuestVtl;
18use crate::ioctl::deferred::DeferredAction;
19use crate::ioctl::register::GetRegError;
20use crate::ioctl::register::SetRegError;
21use crate::mapped_page::MappedPage;
22use crate::protocol;
23use crate::protocol::EnterModes;
24use crate::protocol::HCL_REG_PAGE_OFFSET;
25use crate::protocol::HCL_VMSA_GUEST_VSM_PAGE_OFFSET;
26use crate::protocol::HCL_VMSA_PAGE_OFFSET;
27use crate::protocol::MSHV_APIC_PAGE_OFFSET;
28use crate::protocol::hcl_intr_offload_flags;
29use crate::protocol::hcl_run;
30use bitvec::vec::BitVec;
31use cvm_tracing::CVM_ALLOWED;
32use deferred::RegisteredDeferredActions;
33use deferred::push_deferred_action;
34use deferred::register_deferred_actions;
35use hv1_structs::ProcessorSet;
36use hv1_structs::VtlArray;
37use hvdef::HV_PAGE_SIZE;
38use hvdef::HV_PARTITION_ID_SELF;
39use hvdef::HvAarch64RegisterPage;
40use hvdef::HvError;
41use hvdef::HvMapGpaFlags;
42use hvdef::HvMessage;
43use hvdef::HvRegisterVsmPartitionConfig;
44use hvdef::HvStatus;
45use hvdef::HvX64RegisterPage;
46use hvdef::HypercallCode;
47use hvdef::Vtl;
48use hvdef::hypercall::AssertVirtualInterrupt;
49use hvdef::hypercall::HostVisibilityType;
50use hvdef::hypercall::HvGpaRange;
51use hvdef::hypercall::HvGpaRangeExtended;
52use hvdef::hypercall::HvInputVtl;
53use hvdef::hypercall::HvInterceptParameters;
54use hvdef::hypercall::HvInterceptType;
55use hvdef::hypercall::HypercallOutput;
56use hvdef::hypercall::InitialVpContextX64;
57use hvdef::hypercall::ModifyHostVisibility;
58use memory_range::MemoryRange;
59use pal::unix::pthread::*;
60use parking_lot::Mutex;
61use private::BackingPrivate;
62use sidecar_client::SidecarClient;
63use sidecar_client::SidecarRun;
64use sidecar_client::SidecarVp;
65use std::cell::UnsafeCell;
66use std::fmt::Debug;
67use std::fs::File;
68use std::io;
69use std::os::unix::prelude::*;
70use std::sync::Arc;
71use std::sync::Once;
72use std::sync::atomic::AtomicU8;
73use std::sync::atomic::AtomicU32;
74use std::sync::atomic::Ordering;
75use thiserror::Error;
76use user_driver::DmaClient;
77use user_driver::memory::MemoryBlock;
78use x86defs::snp::SevAvicPage;
79use x86defs::snp::SevVmsa;
80use x86defs::tdx::TdCallResultCode;
81use x86defs::vmx::VmxApicPage;
82use zerocopy::FromBytes;
83use zerocopy::FromZeros;
84use zerocopy::Immutable;
85use zerocopy::IntoBytes;
86use zerocopy::KnownLayout;
87
88// TODO: Chunk this up into smaller per-interface errors.
89/// Error returned by HCL operations.
90#[derive(Error, Debug)]
91#[expect(missing_docs)]
92pub enum Error {
93    #[error("failed to open mshv device")]
94    OpenMshv(#[source] io::Error),
95    #[error("failed to open hvcall device")]
96    OpenHvcall(#[source] io::Error),
97    #[error("failed to open lower VTL memory device")]
98    OpenGpa(#[source] io::Error),
99    #[error("ReturnToLowerVtl")]
100    ReturnToLowerVtl(#[source] nix::Error),
101    #[error("AddVtl0Memory")]
102    AddVtl0Memory(#[source] nix::Error),
103    #[error("hcl_request_interrupt")]
104    RequestInterrupt(#[source] HvError),
105    #[error("failed to signal event")]
106    SignalEvent(#[source] HvError),
107    #[error("failed to mmap the vp context {:?}", .1.map(|vtl| format!("for VTL {:?}", vtl)).unwrap_or("".to_string()))]
108    MmapVp(#[source] io::Error, Option<Vtl>),
109    #[error("failed to set the poll file")]
110    SetPollFile(#[source] nix::Error),
111    #[error("failed to check hcl capabilities {0}")]
112    CheckExtensions(u32, #[source] nix::Error),
113    #[error("failed to mmap the register page")]
114    MmapRegPage(#[source] io::Error),
115    #[error("failed to create vtl")]
116    CreateVTL(#[source] nix::Error),
117    #[error("gpa failed vtl access check")]
118    CheckVtlAccess(#[source] HvError),
119    #[error("sidecar error")]
120    Sidecar(#[source] sidecar_client::SidecarError),
121    #[error(
122        "mismatch between requested isolation type {requested:?} and supported isolation type {supported:?}"
123    )]
124    MismatchedIsolation {
125        supported: IsolationType,
126        requested: IsolationType,
127    },
128    #[error("private page pool allocator missing, required for requested isolation type")]
129    MissingPrivateMemory,
130    #[error("failed to allocate pages for vp")]
131    AllocVp(#[source] anyhow::Error),
132    #[error("failed to map or unmap redirected device interrupt")]
133    MapRedirectedDeviceInterrupt(#[source] nix::Error),
134    #[error("failed to restore partition time")]
135    RestorePartitionTime(#[source] nix::Error),
136    // TODO: added for CCA for now. could separate into own enum
137    #[error("failed to set registers using set_vp_registers hypercall")]
138    SetRegisters(#[source] SetRegError),
139    #[error("Invalid register value")]
140    InvalidRegisterValue,
141}
142
143/// Error for IOCTL errors specifically.
144#[derive(Debug, Error)]
145#[error("hcl request failed")]
146pub struct IoctlError(#[source] pub(crate) nix::Error);
147
148/// Error returned when issuing hypercalls.
149#[derive(Debug, Error)]
150#[expect(missing_docs)]
151pub enum HypercallError {
152    #[error("hypercall failed with {0:?}")]
153    Hypervisor(HvError),
154    #[error("ioctl failed")]
155    Ioctl(#[source] IoctlError),
156}
157
158impl HypercallError {
159    pub(crate) fn check(r: Result<i32, nix::Error>) -> Result<(), Self> {
160        match r {
161            Ok(n) => HvStatus(n.try_into().expect("hypervisor result out of range"))
162                .result()
163                .map_err(Self::Hypervisor),
164            Err(err) => Err(Self::Ioctl(IoctlError(err))),
165        }
166    }
167}
168
169/// Errors when issuing hypercalls via the kernel direct interface.
170#[derive(Error, Debug)]
171#[expect(missing_docs)]
172pub enum HvcallError {
173    #[error(
174        "kernel rejected the hypercall, most likely due to the hypercall code not being allowed via set_allowed_hypercalls"
175    )]
176    HypercallIoctlFailed(#[source] nix::Error),
177    #[error("input parameters are larger than a page")]
178    InputParametersTooLarge,
179    #[error("output parameters are larger than a page")]
180    OutputParametersTooLarge,
181    #[error("output and input list lengths do not match")]
182    InputOutputRepListMismatch,
183}
184
185/// Error applying VTL protections.
186// TODO: move to `underhill_mem`.
187#[derive(Error, Debug)]
188#[expect(missing_docs)]
189pub enum ApplyVtlProtectionsError {
190    #[error("hypervisor failed with {output:?} when protecting pages {range} for vtl {vtl:?}")]
191    Hypervisor {
192        range: MemoryRange,
193        output: HypercallOutput,
194        #[source]
195        hv_error: HvError,
196        vtl: HvInputVtl,
197    },
198    #[error("snp failure to protect pages {range} with {permissions:x?} for vtl {vtl:?}")]
199    Snp {
200        #[source]
201        failed_operation: snp::SnpPageError,
202        range: MemoryRange,
203        permissions: x86defs::snp::SevRmpAdjust,
204        vtl: HvInputVtl,
205    },
206    #[error(
207        "tdcall failed with {error:?} when protecting pages {range} with permissions {permissions:x?} for vtl {vtl:?}"
208    )]
209    Tdx {
210        error: TdCallResultCode,
211        range: MemoryRange,
212        permissions: x86defs::tdx::TdgMemPageGpaAttr,
213        vtl: HvInputVtl,
214    },
215    #[error("cca failed when protecting pages {range} with permissions for vtl {vtl:?}")]
216    Cca { range: MemoryRange, vtl: HvInputVtl },
217    #[error("no valid protections for vtl {0:?}")]
218    InvalidVtl(Vtl),
219}
220
221/// Error setting VSM partition configuration.
222#[derive(Error, Debug)]
223#[expect(missing_docs)]
224pub enum SetVsmPartitionConfigError {
225    #[error("hypervisor failed when configuring vsm partition config {config:?}")]
226    Hypervisor {
227        config: HvRegisterVsmPartitionConfig,
228        #[source]
229        hv_error: HvError,
230    },
231}
232
233/// Error translating a GVA to a GPA.
234#[derive(Error, Debug)]
235#[expect(missing_docs)]
236pub enum TranslateGvaToGpaError {
237    #[error("hypervisor failed when translating gva {gva:#x}")]
238    Hypervisor {
239        gva: u64,
240        #[source]
241        hv_error: HvError,
242    },
243    #[error("sidecar kernel failed when translating gva {gva:#x}")]
244    Sidecar {
245        gva: u64,
246        #[source]
247        error: sidecar_client::SidecarError,
248    },
249}
250
251/// Result from [`Hcl::check_vtl_access`] if vtl permissions were violated
252#[derive(Debug)]
253pub struct CheckVtlAccessResult {
254    /// The intercepting VTL.
255    pub vtl: Vtl,
256    /// The flags that were denied.
257    pub denied_flags: HvMapGpaFlags,
258}
259
260/// Error accepting pages.
261// TODO: move to `underhill_mem`.
262#[derive(Error, Debug)]
263#[expect(missing_docs)]
264pub enum AcceptPagesError {
265    #[error("hypervisor failed to accept pages {range} with {output:?}")]
266    Hypervisor {
267        range: MemoryRange,
268        output: HypercallOutput,
269        #[source]
270        hv_error: HvError,
271    },
272    #[error("snp failure to protect pages {range}")]
273    Snp {
274        #[source]
275        failed_operation: snp::SnpPageError,
276        range: MemoryRange,
277    },
278    #[error("tdcall failure when accepting pages {range}")]
279    Tdx {
280        #[source]
281        error: tdcall::AcceptPagesError,
282        range: MemoryRange,
283    },
284}
285
286// Action translation(to HVCALL) for pin/unpin GPA range.
287#[derive(Debug, Copy, Clone)]
288enum GpaPinUnpinAction {
289    PinGpaRange,
290    UnpinGpaRange,
291}
292
293/// Error pinning a GPA.
294#[derive(Error, Debug)]
295#[error("partial success: {ranges_processed} operations succeeded, but encountered an error")]
296struct PinUnpinError {
297    ranges_processed: usize,
298    #[source]
299    error: HvError,
300}
301
302/// Result of translate gva hypercall from [`Hcl`]
303pub struct TranslateResult {
304    /// The GPA that the GVA translated to.
305    pub gpa_page: u64,
306    /// Whether the page was an overlay page.
307    pub overlay_page: bool, // Note: hardcoded to false on WHP
308}
309
310/// Possible types for rep hypercalls
311enum HvcallRepInput<'a, T> {
312    /// The actual elements to rep over
313    Elements(&'a [T]),
314    /// The elements for the rep are implied and only a count is needed
315    Count(u16),
316}
317
318pub(crate) mod ioctls {
319    use super::cca;
320    use crate::protocol;
321    use hvdef::hypercall::HvRegisterAssoc;
322    use nix::ioctl_none;
323    use nix::ioctl_read;
324    use nix::ioctl_readwrite;
325    use nix::ioctl_write_ptr;
326
327    // The unsafe interface to the `mshv` kernel module comprises
328    // the following IOCTLs.
329    const MSHV_IOCTL: u8 = 0xb8;
330    const MSHV_VTL_RETURN_TO_LOWER_VTL: u16 = 0x27;
331    const MSHV_SET_VP_REGISTERS: u16 = 0x6;
332    const MSHV_GET_VP_REGISTERS: u16 = 0x5;
333    const MSHV_RESTORE_PARTITION_TIME: u16 = 0x13;
334    const MSHV_HVCALL_SETUP: u16 = 0x1E;
335    const MSHV_HVCALL: u16 = 0x1F;
336    const MSHV_VTL_ADD_VTL0_MEMORY: u16 = 0x21;
337    const MSHV_VTL_SET_POLL_FILE: u16 = 0x25;
338    const MSHV_CREATE_VTL: u16 = 0x1D;
339    const MSHV_CHECK_EXTENSION: u16 = 0x00;
340    const MSHV_VTL_PVALIDATE: u16 = 0x28;
341    const MSHV_VTL_RMPADJUST: u16 = 0x29;
342    const MSHV_VTL_TDCALL: u16 = 0x32;
343    const MSHV_VTL_READ_VMX_CR4_FIXED1: u16 = 0x33;
344    const MSHV_VTL_GUEST_VSM_VMSA_PFN: u16 = 0x34;
345    const MSHV_VTL_RMPQUERY: u16 = 0x35;
346    const MSHV_INVLPGB: u16 = 0x36;
347    const MSHV_TLBSYNC: u16 = 0x37;
348    const MSHV_KICKCPUS: u16 = 0x38;
349    const MSHV_MAP_REDIRECTED_DEVICE_INTERRUPT: u16 = 0x39;
350    const MSHV_VTL_SECURE_AVIC_VTL0_PFN: u16 = 0x40;
351    const MSHV_VTL_REALM_CONFIG: u16 = 0x41;
352    const MSHV_VTL_RSI_SYSREG_READ: u16 = 0x42;
353    const MSHV_VTL_RSI_SYSREG_WRITE: u16 = 0x43;
354    const MSHV_VTL_RSI_SET_MEM_PERM: u16 = 0x44;
355    const MSHV_VTL_RSI_GET_IPA_STATE: u16 = 0x45;
356
357    #[repr(C)]
358    #[derive(Copy, Clone)]
359    pub struct mshv_vp_registers {
360        pub count: ::std::os::raw::c_int,
361        pub regs: *mut HvRegisterAssoc,
362    }
363
364    #[repr(C, packed)]
365    #[derive(Copy, Clone)]
366    pub struct mshv_pvalidate {
367        /// Execute the pvalidate instruction on the set of memory pages specified
368        pub start_pfn: ::std::os::raw::c_ulonglong,
369        pub page_count: ::std::os::raw::c_ulonglong,
370        pub validate: ::std::os::raw::c_uchar,
371        pub terminate_on_failure: ::std::os::raw::c_uchar,
372        /// Set to 1 if the page is RAM (from the kernel's perspective), 0 if
373        /// it's device memory.
374        pub ram: u8,
375        pub padding: [::std::os::raw::c_uchar; 1],
376    }
377
378    #[repr(C, packed)]
379    #[derive(Copy, Clone)]
380    pub struct mshv_rmpadjust {
381        /// Execute the rmpadjust instruction on the set of memory pages specified
382        pub start_pfn: ::std::os::raw::c_ulonglong,
383        pub page_count: ::std::os::raw::c_ulonglong,
384        pub value: ::std::os::raw::c_ulonglong,
385        pub terminate_on_failure: ::std::os::raw::c_uchar,
386        /// Set to 1 if the page is RAM (from the kernel's perspective), 0 if
387        /// it's device memory.
388        pub ram: u8,
389        pub padding: [::std::os::raw::c_uchar; 6],
390    }
391
392    #[repr(C, packed)]
393    #[derive(Copy, Clone)]
394    pub struct mshv_rmpquery {
395        /// Execute the rmpquery instruction on the set of memory pages specified
396        pub start_pfn: ::std::os::raw::c_ulonglong,
397        pub page_count: ::std::os::raw::c_ulonglong,
398        pub terminate_on_failure: ::std::os::raw::c_uchar,
399        /// Set to 1 if the page is RAM (from the kernel's perspective), 0 if
400        /// it's device memory.
401        pub ram: u8,
402        pub padding: [::std::os::raw::c_uchar; 6],
403        /// Output array for the flags, must have at least `page_count` entries.
404        pub flags: *mut ::std::os::raw::c_ulonglong,
405        /// Output array for the page sizes, must have at least `page_count` entries.
406        pub page_size: *mut ::std::os::raw::c_ulonglong,
407        /// Output for the amount of pages processed, a scalar.
408        pub pages_processed: *mut ::std::os::raw::c_ulonglong,
409    }
410
411    #[repr(C, packed)]
412    #[derive(Copy, Clone)]
413    pub struct mshv_tdcall {
414        pub rax: u64, // Call code and returned status
415        pub rcx: u64,
416        pub rdx: u64,
417        pub r8: u64,
418        pub r9: u64,
419        pub r10_out: u64, // only supported as output
420        pub r11_out: u64, // only supported as output
421    }
422
423    #[repr(C)]
424    #[derive(Copy, Clone)]
425    pub struct mshv_map_device_int {
426        pub vector: u32,
427        pub apic_id: u32,
428        pub create_mapping: u8,
429        pub padding: [u8; 7],
430    }
431
432    #[repr(C)]
433    #[derive(Copy, Clone)]
434    pub struct mshv_restore_partition_time {
435        pub tsc_sequence: u32,
436        pub reserved: u32,
437        pub reference_time_in_100_ns: u64,
438        pub tsc: u64,
439    }
440
441    ioctl_none!(
442        /// Relinquish the processor to VTL0.
443        hcl_return_to_lower_vtl,
444        MSHV_IOCTL,
445        MSHV_VTL_RETURN_TO_LOWER_VTL
446    );
447
448    ioctl_write_ptr!(
449        /// Set a VTL0 register for the current processor of the current
450        /// partition.
451        /// It is not allowed to set registers for other processors or
452        /// other partitions for the security and coherency reasons.
453        hcl_set_vp_register,
454        MSHV_IOCTL,
455        MSHV_SET_VP_REGISTERS,
456        mshv_vp_registers
457    );
458
459    ioctl_readwrite!(
460        /// Get a VTL0 register for the current processor of the current
461        /// partition.
462        /// It is not allowed to get registers of other processors or
463        /// other partitions for the security and coherency reasons.
464        hcl_get_vp_register,
465        MSHV_IOCTL,
466        MSHV_GET_VP_REGISTERS,
467        mshv_vp_registers
468    );
469
470    ioctl_write_ptr!(
471        /// Adds the VTL0 memory as a ZONE_DEVICE memory (I/O) to support
472        /// DMA from the guest.
473        hcl_add_vtl0_memory,
474        MSHV_IOCTL,
475        MSHV_VTL_ADD_VTL0_MEMORY,
476        protocol::hcl_pfn_range_t
477    );
478
479    ioctl_write_ptr!(
480        /// Sets the file to be polled while running a VP in VTL0. If the file
481        /// becomes readable, then the VP run will be cancelled.
482        hcl_set_poll_file,
483        MSHV_IOCTL,
484        MSHV_VTL_SET_POLL_FILE,
485        protocol::hcl_set_poll_file
486    );
487
488    ioctl_write_ptr!(
489        /// Sets up the hypercall allow map. Allowed once
490        /// per fd.
491        hcl_hvcall_setup,
492        MSHV_IOCTL,
493        MSHV_HVCALL_SETUP,
494        protocol::hcl_hvcall_setup
495    );
496
497    ioctl_readwrite!(
498        /// Performs a hypercall from the user mode.
499        hcl_hvcall,
500        MSHV_IOCTL,
501        MSHV_HVCALL,
502        protocol::hcl_hvcall
503    );
504
505    ioctl_write_ptr!(
506        /// Executes the pvalidate instruction on a page range.
507        hcl_pvalidate_pages,
508        MSHV_IOCTL,
509        MSHV_VTL_PVALIDATE,
510        mshv_pvalidate
511    );
512
513    ioctl_write_ptr!(
514        /// Executes the rmpadjust instruction on a page range.
515        hcl_rmpadjust_pages,
516        MSHV_IOCTL,
517        MSHV_VTL_RMPADJUST,
518        mshv_rmpadjust
519    );
520
521    ioctl_write_ptr!(
522        /// Executes the rmpquery instruction on a page range.
523        hcl_rmpquery_pages,
524        MSHV_IOCTL,
525        MSHV_VTL_RMPQUERY,
526        mshv_rmpquery
527    );
528
529    ioctl_readwrite!(
530        /// Executes a tdcall.
531        hcl_tdcall,
532        MSHV_IOCTL,
533        MSHV_VTL_TDCALL,
534        mshv_tdcall
535    );
536
537    ioctl_read!(
538        hcl_read_vmx_cr4_fixed1,
539        MSHV_IOCTL,
540        MSHV_VTL_READ_VMX_CR4_FIXED1,
541        u64
542    );
543
544    ioctl_readwrite!(
545        hcl_read_guest_vsm_page_pfn,
546        MSHV_IOCTL,
547        MSHV_VTL_GUEST_VSM_VMSA_PFN,
548        u64
549    );
550
551    ioctl_readwrite!(
552        hcl_read_secure_avic_vtl0_pfn,
553        MSHV_IOCTL,
554        MSHV_VTL_SECURE_AVIC_VTL0_PFN,
555        u64
556    );
557
558    pub const HCL_CAP_REGISTER_PAGE: u32 = 1;
559    pub const HCL_CAP_VTL_RETURN_ACTION: u32 = 2;
560    pub const HCL_CAP_DR6_SHARED: u32 = 3;
561    pub const HCL_CAP_LOWER_VTL_TIMER_VIRT: u32 = 4;
562
563    ioctl_write_ptr!(
564        /// Check for the presence of an extension capability.
565        hcl_check_extension,
566        MSHV_IOCTL,
567        MSHV_CHECK_EXTENSION,
568        u32
569    );
570
571    ioctl_read!(mshv_create_vtl, MSHV_IOCTL, MSHV_CREATE_VTL, u8);
572
573    // CCA: Gets the RSI Realm Config value from the kernel
574    ioctl_read!(
575        hcl_realm_config,
576        MSHV_IOCTL,
577        MSHV_VTL_REALM_CONFIG,
578        cca::mshv_realm_config
579    );
580
581    // CCA: Write the value of a system register
582    ioctl_write_ptr!(
583        hcl_rsi_sysreg_write,
584        MSHV_IOCTL,
585        MSHV_VTL_RSI_SYSREG_WRITE,
586        cca::mshv_rsi_sysreg_rw
587    );
588
589    // CCA: Read the value of a system register
590    ioctl_readwrite!(
591        hcl_rsi_sysreg_read,
592        MSHV_IOCTL,
593        MSHV_VTL_RSI_SYSREG_READ,
594        cca::mshv_rsi_sysreg_rw
595    );
596
597    // CCA: Get the RIPAS state of an ipa
598    ioctl_readwrite!(
599        hcl_rsi_ipa_state_read,
600        MSHV_IOCTL,
601        MSHV_VTL_RSI_GET_IPA_STATE,
602        cca::mshv_rsi_get_ipa_state
603    );
604
605    // CCA: Assign the address described by `mshv_rsi_set_mem_perm`
606    // to a plane.
607    // Note: This is a simplification of the memory access configuration.
608    // The kernel driver does some stuff under the hood, making two RSI calls
609    // as part of this ioctl: RSI_MEM_SET_PERM_VALUE and RSI_MEM_SET_PERM_INDEX.
610    // Will need to decide how to design this interface and who maps the
611    // memory of a plane to the RSI calls needed to set it up.
612    ioctl_write_ptr!(
613        hcl_rsi_set_mem_perm,
614        MSHV_IOCTL,
615        MSHV_VTL_RSI_SET_MEM_PERM,
616        cca::mshv_rsi_set_mem_perm
617    );
618
619    #[repr(C)]
620    pub struct mshv_invlpgb {
621        pub rax: u64,
622        pub _pad0: u32,
623        pub edx: u32,
624        pub _pad1: u32,
625        pub ecx: u32,
626    }
627
628    ioctl_write_ptr!(
629        /// Issue an INVLPGB instruction.
630        hcl_invlpgb,
631        MSHV_IOCTL,
632        MSHV_INVLPGB,
633        mshv_invlpgb
634    );
635
636    ioctl_none!(
637        /// Issue a TLBSYNC instruction.
638        hcl_tlbsync,
639        MSHV_IOCTL,
640        MSHV_TLBSYNC
641    );
642
643    ioctl_write_ptr!(
644        /// Kick CPUs.
645        hcl_kickcpus,
646        MSHV_IOCTL,
647        MSHV_KICKCPUS,
648        protocol::hcl_kick_cpus
649    );
650
651    ioctl_readwrite!(
652        /// Map or unmap VTL0 device interrupt in VTL2.
653        hcl_map_redirected_device_interrupt,
654        MSHV_IOCTL,
655        MSHV_MAP_REDIRECTED_DEVICE_INTERRUPT,
656        mshv_map_device_int
657    );
658
659    ioctl_write_ptr!(
660        /// Restore partition time.
661        hcl_restore_partition_time,
662        MSHV_IOCTL,
663        MSHV_RESTORE_PARTITION_TIME,
664        mshv_restore_partition_time
665    );
666}
667
668/// The `/dev/mshv_vtl_low` device for accessing VTL0 memory.
669pub struct MshvVtlLow {
670    file: File,
671}
672
673impl MshvVtlLow {
674    /// Opens the device.
675    pub fn new() -> Result<Self, Error> {
676        let file = fs_err::OpenOptions::new()
677            .read(true)
678            .write(true)
679            .open("/dev/mshv_vtl_low")
680            .map_err(Error::OpenGpa)?;
681
682        Ok(Self { file: file.into() })
683    }
684
685    /// Gets the device file.
686    pub fn get(&self) -> &File {
687        &self.file
688    }
689
690    /// The flag to set in the file offset to map guest memory as shared instead
691    /// of private.
692    pub const SHARED_MEMORY_FLAG: u64 = 1 << 63;
693}
694
695/// An open `/dev/mshv` device file.
696pub struct Mshv {
697    file: File,
698}
699
700impl Mshv {
701    /// Opens the mshv device.
702    pub fn new() -> Result<Self, Error> {
703        let file = fs_err::OpenOptions::new()
704            .read(true)
705            .write(true)
706            .open("/dev/mshv")
707            .map_err(Error::OpenMshv)?;
708
709        Ok(Self { file: file.into() })
710    }
711
712    fn check_extension(&self, cap: u32) -> Result<bool, Error> {
713        // SAFETY: calling IOCTL as documented, with no special requirements.
714        let supported = unsafe {
715            hcl_check_extension(self.file.as_raw_fd(), &cap)
716                .map_err(|e| Error::CheckExtensions(cap, e))?
717        };
718        Ok(supported != 0)
719    }
720
721    /// Opens an mshv_vtl device file.
722    pub fn create_vtl(&self) -> Result<MshvVtl, Error> {
723        let cap = &mut 0_u8;
724        // SAFETY: calling IOCTL as documented, with no special requirements.
725        let supported =
726            unsafe { mshv_create_vtl(self.file.as_raw_fd(), cap).map_err(Error::CreateVTL)? };
727        // SAFETY: calling IOCTL as documented, with no special requirements.
728        let vtl_file = unsafe { File::from_raw_fd(supported) };
729        Ok(MshvVtl { file: vtl_file })
730    }
731}
732
733/// An open mshv_vtl device file.
734#[derive(Debug)]
735pub struct MshvVtl {
736    file: File,
737}
738
739impl MshvVtl {
740    /// Adds the VTL0 memory as a ZONE_DEVICE memory (I/O) to support DMA from the guest.
741    pub fn add_vtl0_memory(&self, mem_range: MemoryRange, shared: bool) -> Result<(), Error> {
742        let flags = if shared {
743            MshvVtlLow::SHARED_MEMORY_FLAG / HV_PAGE_SIZE
744        } else {
745            0
746        };
747        let ram_disposition = protocol::hcl_pfn_range_t {
748            start_pfn: mem_range.start_4k_gpn() | flags,
749            last_pfn: mem_range.end_4k_gpn(),
750        };
751
752        // SAFETY: calling IOCTL as documented, with no special requirements.
753        unsafe {
754            hcl_add_vtl0_memory(self.file.as_raw_fd(), &ram_disposition)
755                .map_err(Error::AddVtl0Memory)?;
756        }
757
758        Ok(())
759    }
760}
761
762/// The `/dev/mshv_hvcall` device for issuing hypercalls directly to the
763/// hypervisor.
764#[derive(Debug)]
765pub struct MshvHvcall(File);
766
767impl MshvHvcall {
768    /// Opens the device.
769    pub fn new() -> Result<Self, Error> {
770        let file = fs_err::OpenOptions::new()
771            .read(true)
772            .write(true)
773            .open("/dev/mshv_hvcall")
774            .map_err(Error::OpenHvcall)?;
775
776        Ok(Self(file.into()))
777    }
778
779    /// Set allowed hypercalls.
780    pub fn set_allowed_hypercalls(&self, codes: &[HypercallCode]) {
781        type ItemType = u64;
782        let item_size_bytes = size_of::<ItemType>();
783        let item_size_bits = item_size_bytes * 8;
784
785        let mut allow_bitmap = Vec::<ItemType>::new();
786        for &code in codes {
787            let map_index = (code.0 as usize) / item_size_bits;
788            if map_index >= allow_bitmap.len() {
789                allow_bitmap.resize(map_index + 1, 0);
790            }
791            allow_bitmap[map_index] |= (1 as ItemType) << (code.0 % item_size_bits as u16);
792        }
793
794        let hvcall_setup = protocol::hcl_hvcall_setup {
795            allow_bitmap_size: (allow_bitmap.len() * item_size_bytes) as u64,
796            allow_bitmap_ptr: allow_bitmap.as_ptr(),
797        };
798
799        // SAFETY: following the IOCTL definition.
800        unsafe {
801            hcl_hvcall_setup(self.0.as_raw_fd(), &hvcall_setup)
802                .expect("Hypercall setup IOCTL must be supported");
803        }
804    }
805
806    /// Accepts VTL 0 pages with no host visibility.
807    ///
808    /// [`HypercallCode::HvCallAcceptGpaPages`] must be allowed.
809    pub fn accept_gpa_pages(
810        &self,
811        range: MemoryRange,
812        memory_type: hvdef::hypercall::AcceptMemoryType,
813    ) -> Result<(), AcceptPagesError> {
814        const MAX_INPUT_ELEMENTS: usize = (HV_PAGE_SIZE as usize
815            - size_of::<hvdef::hypercall::AcceptGpaPages>())
816            / size_of::<u64>();
817
818        let span = tracing::info_span!("accept_pages", CVM_ALLOWED, ?range);
819        let _enter = span.enter();
820
821        let mut current_page = range.start() / HV_PAGE_SIZE;
822        let end = range.end() / HV_PAGE_SIZE;
823
824        while current_page < end {
825            let header = hvdef::hypercall::AcceptGpaPages {
826                partition_id: HV_PARTITION_ID_SELF,
827                page_attributes: hvdef::hypercall::AcceptPagesAttributes::new()
828                    .with_memory_type(memory_type.0)
829                    .with_host_visibility(HostVisibilityType::PRIVATE)
830                    .with_vtl_set(0), // vtl protections cannot be applied for VTL 0 memory
831                vtl_permission_set: hvdef::hypercall::VtlPermissionSet {
832                    vtl_permission_from_1: [0; hvdef::hypercall::HV_VTL_PERMISSION_SET_SIZE],
833                },
834                gpa_page_base: current_page,
835            };
836
837            let remaining_pages = end - current_page;
838            let count = remaining_pages.min(MAX_INPUT_ELEMENTS as u64);
839
840            // SAFETY: The input header and rep slice are the correct types for
841            //         this hypercall. A dummy type of u8 is provided to satisfy
842            //         the compiler for input and output rep type. The given
843            //         input and slices are valid references while this function
844            //         is called.
845            //
846            //         The hypercall output is validated right after the hypercall is issued.
847            let output = unsafe {
848                self.hvcall_rep::<hvdef::hypercall::AcceptGpaPages, u8, u8>(
849                    HypercallCode::HvCallAcceptGpaPages,
850                    &header,
851                    HvcallRepInput::Count(count as u16),
852                    None,
853                )
854                .expect("kernel hypercall submission should always succeed")
855            };
856
857            output
858                .result()
859                .map_err(|err| AcceptPagesError::Hypervisor {
860                    range: MemoryRange::from_4k_gpn_range(current_page..current_page + count),
861                    output,
862                    hv_error: err,
863                })?;
864
865            current_page += count;
866
867            assert_eq!(output.elements_processed() as u64, count);
868        }
869        Ok(())
870    }
871
872    /// Modifies the host visibility of the given pages.
873    ///
874    /// [`HypercallCode::HvCallModifySparseGpaPageHostVisibility`] must be
875    /// allowed.
876    ///
877    /// Returns on error, the hypervisor error and the number of pages
878    /// processed.
879    ///
880    /// VBS FUTURE TODO: For defense in depth it could be useful to prevent usermode from
881    /// changing visibility of a VTL2 kernel page in the kernel.
882    pub fn modify_gpa_visibility(
883        &self,
884        host_visibility: HostVisibilityType,
885        mut gpns: &[u64],
886    ) -> Result<(), (HvError, usize)> {
887        const GPNS_PER_CALL: usize = (HV_PAGE_SIZE as usize
888            - size_of::<hvdef::hypercall::ModifySparsePageVisibility>())
889            / size_of::<u64>();
890
891        while !gpns.is_empty() {
892            let n = gpns.len().min(GPNS_PER_CALL);
893            // SAFETY: The input header and rep slice are the correct types for this hypercall.
894            //         The hypercall output is validated right after the hypercall is issued.
895            let result = unsafe {
896                self.hvcall_rep(
897                    HypercallCode::HvCallModifySparseGpaPageHostVisibility,
898                    &hvdef::hypercall::ModifySparsePageVisibility {
899                        partition_id: HV_PARTITION_ID_SELF,
900                        host_visibility: ModifyHostVisibility::new()
901                            .with_host_visibility(host_visibility),
902                        reserved: 0,
903                    },
904                    HvcallRepInput::Elements(&gpns[..n]),
905                    None::<&mut [u8]>,
906                )
907                .unwrap()
908            };
909
910            match result.result() {
911                Ok(()) => {
912                    assert_eq!({ result.elements_processed() }, n);
913                }
914                Err(HvError::Timeout) => {}
915                Err(e) => return Err((e, result.elements_processed())),
916            }
917            gpns = &gpns[result.elements_processed()..];
918        }
919        Ok(())
920    }
921
922    /// Given a constructed hcl_hvcall protocol object, issues an IOCTL to invoke a hypercall via
923    /// the direct hypercall kernel interface. This function will retry hypercalls if the hypervisor
924    /// times out the hypercall.
925    ///
926    /// Input and output data are referenced as pointers in the call object.
927    ///
928    /// `Ok(HypercallOutput)` is returned if the kernel was successful in issuing the hypercall. A
929    /// caller must check the return value for the result of the hypercall.
930    ///
931    /// Before invoking hypercalls, a list of hypercalls that are allowed
932    /// has to be set with `Hcl::set_allowed_hypercalls`:
933    /// ```ignore
934    /// set_allowed_hypercalls(&[
935    ///     hvdef::HypercallCode::HvCallCheckForIoIntercept,
936    ///     hvdef::HypercallCode::HvCallInstallIntercept,
937    /// ]);
938    /// ```
939    /// # Safety
940    /// This function makes no guarantees that the given input header, input and output types are
941    /// valid for the given hypercall. It is the caller's responsibility to use the correct types
942    /// with the specified hypercall.
943    ///
944    /// The caller must ensure that the input and output data are valid for the lifetime of this
945    /// call.
946    ///
947    /// A caller must check the returned [HypercallOutput] for success or failure from the
948    /// hypervisor.
949    ///
950    /// Hardware isolated VMs cannot trust the output from the hypervisor and so it must be
951    /// validated by the caller if needed.
952    unsafe fn invoke_hvcall_ioctl(
953        &self,
954        mut call_object: protocol::hcl_hvcall,
955    ) -> Result<HypercallOutput, HvcallError> {
956        loop {
957            // SAFETY: following the IOCTL definition. The data referenced in the call
958            // lives as long as `self` does thus the lifetime elision doesn't contradict
959            // the compiler's invariants.
960            //
961            // The hypervisor is trusted to fill out the output page with a valid
962            // representation of an instance the output type, except in the case of hardware
963            // isolated VMs where the caller must validate output as needed.
964            unsafe {
965                hcl_hvcall(self.0.as_raw_fd(), &mut call_object)
966                    .map_err(HvcallError::HypercallIoctlFailed)?;
967            }
968
969            if call_object.status.call_status() == Err(HvError::Timeout).into() {
970                // Any hypercall can timeout, even one that doesn't have reps. Continue processing
971                // from wherever the hypervisor left off.  The rep start index isn't checked for
972                // validity, since it is only being used as an input to the untrusted hypervisor.
973                // This applies to both simple and rep hypercalls.
974                call_object
975                    .control
976                    .set_rep_start(call_object.status.elements_processed());
977            } else {
978                if call_object.control.rep_count() == 0 {
979                    // For non-rep hypercalls, the elements processed field should be 0.
980                    assert_eq!(call_object.status.elements_processed(), 0);
981                } else {
982                    // Hardware isolated VMs cannot trust output from the hypervisor, but check for
983                    // consistency between the number of elements processed and the expected count. A
984                    // violation of this assertion indicates a buggy or malicious hypervisor.
985                    assert!(
986                        (call_object.status.result().is_ok()
987                            && call_object.control.rep_count()
988                                == call_object.status.elements_processed())
989                            || (call_object.status.result().is_err()
990                                && call_object.control.rep_count()
991                                    > call_object.status.elements_processed())
992                    );
993                }
994
995                return Ok(call_object.status);
996            }
997        }
998    }
999
1000    /// Issues a non-rep hypercall to the hypervisor via the direct hypercall kernel interface.
1001    /// This is not intended to be used directly by external callers, rather via write safe hypercall wrappers.
1002    /// This call constructs the appropriate hypercall input control from the described parameters.
1003    ///
1004    /// `Ok(HypercallOutput)` is returned if the kernel was successful in issuing the hypercall. A caller must check the
1005    /// return value for the result of the hypercall.
1006    ///
1007    /// `code` is the hypercall code.
1008    /// `input` is the input type required by the hypercall.
1009    /// `output` is the output type required by the hypercall.
1010    ///
1011    /// Before invoking hypercalls, a list of hypercalls that are allowed
1012    /// has to be set with `Hcl::set_allowed_hypercalls`:
1013    /// ```ignore
1014    /// set_allowed_hypercalls(&[
1015    ///     hvdef::HypercallCode::HvCallCheckForIoIntercept,
1016    ///     hvdef::HypercallCode::HvCallInstallIntercept,
1017    /// ]);
1018    /// ```
1019    /// # Safety
1020    /// This function makes no guarantees that the given input header, input and output types are valid for the
1021    /// given hypercall. It is the caller's responsibility to use the correct types with the specified hypercall.
1022    ///
1023    /// A caller must check the returned [HypercallOutput] for success or failure from the hypervisor.
1024    ///
1025    /// Hardware isolated VMs cannot trust the output from the hypervisor and so it must be validated by the
1026    /// caller if needed.
1027    unsafe fn hvcall<I, O>(
1028        &self,
1029        code: HypercallCode,
1030        input: &I,
1031        output: &mut O,
1032    ) -> Result<HypercallOutput, HvcallError>
1033    where
1034        I: IntoBytes + Sized + Immutable + KnownLayout,
1035        O: IntoBytes + FromBytes + Sized + Immutable + KnownLayout,
1036    {
1037        const fn assert_size<I, O>()
1038        where
1039            I: Sized,
1040            O: Sized,
1041        {
1042            assert!(size_of::<I>() <= HV_PAGE_SIZE as usize);
1043            assert!(size_of::<O>() <= HV_PAGE_SIZE as usize);
1044        }
1045        assert_size::<I, O>();
1046
1047        let control = hvdef::hypercall::Control::new().with_code(code.0);
1048
1049        let call_object = protocol::hcl_hvcall {
1050            control,
1051            input_data: input.as_bytes().as_ptr().cast(),
1052            input_size: size_of::<I>(),
1053            status: FromZeros::new_zeroed(),
1054            output_data: output.as_bytes().as_ptr().cast(),
1055            output_size: size_of::<O>(),
1056        };
1057
1058        // SAFETY: The data referenced in the call lives as long as `self` does.
1059        unsafe { self.invoke_hvcall_ioctl(call_object) }
1060    }
1061
1062    /// Issues a rep hypercall to the hypervisor via the direct hypercall kernel
1063    /// interface. Like the non-rep version, this is not intended to be used
1064    /// externally other than to construct safe wrappers. This call constructs
1065    /// the appropriate hypercall input control from the described parameters.
1066    ///
1067    /// `Ok(HypercallOutput)` is returned if the kernel was successful in
1068    /// issuing the hypercall. A caller must check the return value for the
1069    /// result of the hypercall.
1070    ///
1071    /// `code` is the hypercall code. `input_header` is the hypercall fixed
1072    /// length input header. Variable length headers are not supported.
1073    /// `input_rep` is the list of input elements. The length of the slice is
1074    /// used as the rep count.
1075    ///
1076    /// `output_rep` is the optional output rep list. A caller must check the
1077    /// returned [HypercallOutput] for the number of valid elements in this
1078    /// list.
1079    ///
1080    /// # Safety
1081    /// This function makes no guarantees that the given input header, input rep
1082    /// and output rep types are valid for the given hypercall. It is the
1083    /// caller's responsibility to use the correct types with the specified
1084    /// hypercall.
1085    ///
1086    /// A caller must check the returned [HypercallOutput] for success or
1087    /// failure from the hypervisor and processed rep count.
1088    ///
1089    /// Hardware isolated VMs cannot trust output from the hypervisor. This
1090    /// routine will ensure that the hypervisor either returns success with all
1091    /// elements processed, or returns failure with an incomplete number of
1092    /// elements processed. Actual validation of the output elements is the
1093    /// respsonsibility of the caller.
1094    unsafe fn hvcall_rep<InputHeader, InputRep, O>(
1095        &self,
1096        code: HypercallCode,
1097        input_header: &InputHeader,
1098        input_rep: HvcallRepInput<'_, InputRep>,
1099        output_rep: Option<&mut [O]>,
1100    ) -> Result<HypercallOutput, HvcallError>
1101    where
1102        InputHeader: IntoBytes + Sized + Immutable + KnownLayout,
1103        InputRep: IntoBytes + Sized + Immutable + KnownLayout,
1104        O: IntoBytes + FromBytes + Sized + Immutable + KnownLayout,
1105    {
1106        // Construct input buffer.
1107        let (input, count) = match input_rep {
1108            HvcallRepInput::Elements(e) => {
1109                ([input_header.as_bytes(), e.as_bytes()].concat(), e.len())
1110            }
1111            HvcallRepInput::Count(c) => (input_header.as_bytes().to_vec(), c.into()),
1112        };
1113
1114        if input.len() > HV_PAGE_SIZE as usize {
1115            return Err(HvcallError::InputParametersTooLarge);
1116        }
1117
1118        if let Some(output_rep) = &output_rep {
1119            if output_rep.as_bytes().len() > HV_PAGE_SIZE as usize {
1120                return Err(HvcallError::OutputParametersTooLarge);
1121            }
1122
1123            if count != output_rep.len() {
1124                return Err(HvcallError::InputOutputRepListMismatch);
1125            }
1126        }
1127
1128        let (output_data, output_size) = match output_rep {
1129            Some(output_rep) => (
1130                output_rep.as_bytes().as_ptr().cast(),
1131                output_rep.as_bytes().len(),
1132            ),
1133            None => (std::ptr::null(), 0),
1134        };
1135
1136        let control = hvdef::hypercall::Control::new()
1137            .with_code(code.0)
1138            .with_rep_count(count);
1139
1140        let call_object = protocol::hcl_hvcall {
1141            control,
1142            input_data: input.as_ptr().cast(),
1143            input_size: input.len(),
1144            status: HypercallOutput::new(),
1145            output_data,
1146            output_size,
1147        };
1148
1149        // SAFETY: The data referenced in the call lives as long as `self` does.
1150        unsafe { self.invoke_hvcall_ioctl(call_object) }
1151    }
1152
1153    /// Issues a non-rep hypercall with variable input to the hypervisor via the direct hypercall kernel interface.
1154    /// This is not intended to be used directly by external callers, rather via write safe hypercall wrappers.
1155    /// This call constructs the appropriate hypercall input control from the described parameters.
1156    ///
1157    /// `Ok(HypercallOutput)` is returned if the kernel was successful in issuing the hypercall. A caller must check the
1158    /// return value for the result of the hypercall.
1159    ///
1160    /// `code` is the hypercall code.
1161    /// `input` is the input type required by the hypercall.
1162    /// `output` is the output type required by the hypercall.
1163    /// `variable_input` is the contents of the variable input to the hypercall. The length must be a multiple of 8 bytes.
1164    ///
1165    /// # Safety
1166    /// This function makes no guarantees that the given input header, input and output types are valid for the
1167    /// given hypercall. It is the caller's responsibility to use the correct types with the specified hypercall.
1168    ///
1169    /// A caller must check the returned [HypercallOutput] for success or failure from the hypervisor.
1170    ///
1171    /// Hardware isolated VMs cannot trust the output from the hypervisor and so it must be validated by the
1172    /// caller if needed.
1173    unsafe fn hvcall_var<I, O>(
1174        &self,
1175        code: HypercallCode,
1176        input: &I,
1177        variable_input: &[u8],
1178        output: &mut O,
1179    ) -> Result<HypercallOutput, HvcallError>
1180    where
1181        I: IntoBytes + Sized + Immutable + KnownLayout,
1182        O: IntoBytes + FromBytes + Sized + Immutable + KnownLayout,
1183    {
1184        const fn assert_size<I, O>()
1185        where
1186            I: Sized,
1187            O: Sized,
1188        {
1189            assert!(size_of::<I>() <= HV_PAGE_SIZE as usize);
1190            assert!(size_of::<O>() <= HV_PAGE_SIZE as usize);
1191        }
1192        assert_size::<I, O>();
1193        assert!(variable_input.len().is_multiple_of(8));
1194
1195        let input = [input.as_bytes(), variable_input].concat();
1196        if input.len() > HV_PAGE_SIZE as usize {
1197            return Err(HvcallError::InputParametersTooLarge);
1198        }
1199
1200        let control = hvdef::hypercall::Control::new()
1201            .with_code(code.0)
1202            .with_variable_header_size(variable_input.len() / 8);
1203
1204        let call_object = protocol::hcl_hvcall {
1205            control,
1206            input_data: input.as_bytes().as_ptr().cast(),
1207            input_size: input.len(),
1208            status: FromZeros::new_zeroed(),
1209            output_data: output.as_bytes().as_ptr().cast(),
1210            output_size: size_of::<O>(),
1211        };
1212
1213        // SAFETY: The data referenced in the call lives as long as `self` does.
1214        unsafe { self.invoke_hvcall_ioctl(call_object) }
1215    }
1216
1217    /// Sets the VTL protection mask for the specified memory range.
1218    ///
1219    /// [`HypercallCode::HvCallModifyVtlProtectionMask`] must be allowed.
1220    pub fn modify_vtl_protection_mask(
1221        &self,
1222        range: MemoryRange,
1223        map_flags: HvMapGpaFlags,
1224        target_vtl: HvInputVtl,
1225    ) -> Result<(), ApplyVtlProtectionsError> {
1226        let header = hvdef::hypercall::ModifyVtlProtectionMask {
1227            partition_id: HV_PARTITION_ID_SELF,
1228            map_flags,
1229            target_vtl,
1230            reserved: [0; 3],
1231        };
1232
1233        const MAX_INPUT_ELEMENTS: usize = (HV_PAGE_SIZE as usize
1234            - size_of::<hvdef::hypercall::ModifyVtlProtectionMask>())
1235            / size_of::<u64>();
1236
1237        let span = tracing::info_span!("modify_vtl_protection_mask", CVM_ALLOWED, ?range);
1238        let _enter = span.enter();
1239
1240        let start = range.start() / HV_PAGE_SIZE;
1241        let end = range.end() / HV_PAGE_SIZE;
1242
1243        // Reuse the same vector for every hypercall.
1244        let mut pages = Vec::new();
1245        for current_page in (start..end).step_by(MAX_INPUT_ELEMENTS) {
1246            let remaining_pages = end - current_page;
1247            let count = remaining_pages.min(MAX_INPUT_ELEMENTS as u64);
1248            pages.clear();
1249            pages.extend(current_page..current_page + count);
1250
1251            // SAFETY: The input header and rep slice are the correct types for this hypercall. A dummy type of u8 is
1252            //         provided to satisfy the compiler for output rep type. The given input and slices are valid
1253            //         references while this function is called.
1254            //
1255            //         The hypercall output is validated right after the hypercall is issued.
1256            let output = unsafe {
1257                self.hvcall_rep::<hvdef::hypercall::ModifyVtlProtectionMask, u64, u8>(
1258                    HypercallCode::HvCallModifyVtlProtectionMask,
1259                    &header,
1260                    HvcallRepInput::Elements(pages.as_slice()),
1261                    None,
1262                )
1263                .expect("kernel hypercall submission should always succeed")
1264            };
1265
1266            output.result().map_err(|err| {
1267                let page_range =
1268                    *pages.first().expect("not empty")..*pages.last().expect("not empty") + 1;
1269                ApplyVtlProtectionsError::Hypervisor {
1270                    range: MemoryRange::from_4k_gpn_range(page_range),
1271                    output,
1272                    hv_error: err,
1273                    vtl: target_vtl,
1274                }
1275            })?;
1276
1277            assert_eq!(output.elements_processed() as u64, count);
1278        }
1279
1280        Ok(())
1281    }
1282
1283    /// Invokes the HvCallMemoryMappedIoRead hypercall
1284    pub fn mmio_read(&self, gpa: u64, data: &mut [u8]) -> Result<(), HvError> {
1285        assert!(data.len() <= hvdef::hypercall::HV_HYPERCALL_MMIO_MAX_DATA_LENGTH);
1286
1287        let header = hvdef::hypercall::MemoryMappedIoRead {
1288            gpa,
1289            access_width: data.len() as u32,
1290            reserved_z0: 0,
1291        };
1292
1293        let mut output: hvdef::hypercall::MemoryMappedIoReadOutput = FromZeros::new_zeroed();
1294
1295        // SAFETY: The input header and slice are the correct types for this hypercall.
1296        //         The hypercall output is validated right after the hypercall is issued.
1297        let status = unsafe {
1298            self.hvcall(
1299                HypercallCode::HvCallMemoryMappedIoRead,
1300                &header,
1301                &mut output,
1302            )
1303            .expect("submitting hypercall should not fail")
1304        };
1305
1306        // Only copy the data if the hypercall was successful
1307        if status.result().is_ok() {
1308            data.copy_from_slice(&output.data[..data.len()]);
1309        };
1310
1311        status.result()
1312    }
1313
1314    /// Invokes the HvCallMemoryMappedIoWrite hypercall
1315    pub fn mmio_write(&self, gpa: u64, data: &[u8]) -> Result<(), HvError> {
1316        assert!(data.len() <= hvdef::hypercall::HV_HYPERCALL_MMIO_MAX_DATA_LENGTH);
1317
1318        let mut header = hvdef::hypercall::MemoryMappedIoWrite {
1319            gpa,
1320            access_width: data.len() as u32,
1321            reserved_z0: 0,
1322            data: [0; hvdef::hypercall::HV_HYPERCALL_MMIO_MAX_DATA_LENGTH],
1323        };
1324
1325        header.data[..data.len()].copy_from_slice(data);
1326
1327        // SAFETY: The input header and slice are the correct types for this hypercall.
1328        //         The hypercall output is validated right after the hypercall is issued.
1329        let status = unsafe {
1330            self.hvcall(HypercallCode::HvCallMemoryMappedIoWrite, &header, &mut ())
1331                .expect("submitting hypercall should not fail")
1332        };
1333
1334        status.result()
1335    }
1336
1337    /// Request a VBS VM report from the host VSM.
1338    ///
1339    /// # Arguments
1340    /// - `report_data`: The data to include in the report.
1341    ///
1342    /// Returns a result containing the report or an error.
1343    pub fn vbs_vm_call_report(
1344        &self,
1345        report_data: &[u8],
1346    ) -> Result<[u8; hvdef::hypercall::VBS_VM_MAX_REPORT_SIZE], HvError> {
1347        if report_data.len() > hvdef::hypercall::VBS_VM_REPORT_DATA_SIZE {
1348            return Err(HvError::InvalidParameter);
1349        }
1350
1351        let mut header = hvdef::hypercall::VbsVmCallReport {
1352            report_data: [0; hvdef::hypercall::VBS_VM_REPORT_DATA_SIZE],
1353        };
1354
1355        header.report_data[..report_data.len()].copy_from_slice(report_data);
1356
1357        let mut output: hvdef::hypercall::VbsVmCallReportOutput = FromZeros::new_zeroed();
1358
1359        // SAFETY: The input header and slice are the correct types for this hypercall.
1360        //         The hypercall output is validated right after the hypercall is issued.
1361        let status = unsafe {
1362            self.hvcall(HypercallCode::HvCallVbsVmCallReport, &header, &mut output)
1363                .expect("submitting hypercall should not fail")
1364        };
1365
1366        if status.result().is_ok() {
1367            Ok(output.report)
1368        } else {
1369            Err(status.result().unwrap_err())
1370        }
1371    }
1372}
1373
1374/// The HCL device and collection of fds.
1375#[derive(Debug)]
1376pub struct Hcl {
1377    mshv_hvcall: MshvHvcall,
1378    mshv_vtl: MshvVtl,
1379    vps: Vec<HclVp>,
1380    supports_vtl_ret_action: bool,
1381    supports_register_page: bool,
1382    dr6_shared: bool,
1383    supports_lower_vtl_timer_virt: bool,
1384    isolation: IsolationType,
1385    snp_register_bitmap: [u8; 64],
1386    sidecar: Option<SidecarClient>,
1387}
1388
1389/// The isolation type for a partition.
1390#[derive(Debug, Copy, Clone, PartialEq, Eq)]
1391pub enum IsolationType {
1392    /// No isolation.
1393    None,
1394    /// Hyper-V software isolation.
1395    Vbs,
1396    /// AMD SNP.
1397    Snp,
1398    /// Intel TDX.
1399    Tdx,
1400    /// ARM CCA.
1401    Cca,
1402}
1403
1404impl IsolationType {
1405    /// Returns true if the isolation type is not `None`.
1406    pub fn is_isolated(&self) -> bool {
1407        !matches!(self, Self::None)
1408    }
1409
1410    /// Returns whether the isolation type is hardware-backed.
1411    pub fn is_hardware_isolated(&self) -> bool {
1412        matches!(self, Self::Snp | Self::Tdx | Self::Cca)
1413    }
1414}
1415
1416impl Hcl {
1417    /// Returns true if DR6 is a shared register on this processor.
1418    pub fn dr6_shared(&self) -> bool {
1419        self.dr6_shared
1420    }
1421
1422    /// Returns true if timer virtualization for lower VTL is supported.
1423    pub fn supports_lower_vtl_timer_virt(&self) -> bool {
1424        self.supports_lower_vtl_timer_virt
1425    }
1426}
1427
1428#[derive(Debug)]
1429struct HclVp {
1430    state: Mutex<VpState>,
1431    run: MappedPage<hcl_run>,
1432    backing: BackingState,
1433}
1434
1435#[derive(Debug)]
1436enum BackingState {
1437    MshvAarch64 {
1438        reg_page: Option<MappedPage<HvAarch64RegisterPage>>,
1439    },
1440    MshvX64 {
1441        reg_page: Option<MappedPage<HvX64RegisterPage>>,
1442    },
1443    Snp {
1444        vmsa: VtlArray<MappedPage<SevVmsa>, 2>,
1445        vtl0_apic_page: MappedPage<SevAvicPage>,
1446        /// VTL1 runs with the alternate interrupt injection.
1447        vtl1_apic_page: MemoryBlock,
1448    },
1449    Tdx {
1450        vtl0_apic_page: MappedPage<VmxApicPage>,
1451        vtl1_apic_page: MemoryBlock,
1452    },
1453    Cca {
1454        plane_run: MemoryBlock,
1455    },
1456}
1457
1458#[derive(Debug)]
1459enum VpState {
1460    Running(Pthread),
1461    NotRunning,
1462}
1463
1464impl HclVp {
1465    fn new(
1466        hcl: &Hcl,
1467        vp: u32,
1468        map_reg_page: bool,
1469        isolation_type: IsolationType,
1470        private_dma_client: Option<&Arc<dyn DmaClient>>,
1471    ) -> Result<Self, Error> {
1472        let fd = &hcl.mshv_vtl.file;
1473        let run: MappedPage<hcl_run> =
1474            MappedPage::new(fd, vp as i64).map_err(|e| Error::MmapVp(e, None))?;
1475        // Block proxied interrupts on all vectors by default. The mask will be
1476        // relaxed as the guest runs.
1477        //
1478        // This is only used on CVMs. Skip it otherwise, since run page accesses
1479        // will fault on VPs that are still in the sidecar kernel.
1480        if isolation_type.is_hardware_isolated() {
1481            // SAFETY: The run page is not accessed by any other VPs/kernel at this point
1482            // (`HclVp` creation), so we know we have exclusive access.
1483            unsafe {
1484                (*run.as_ptr()).proxy_irr_blocked.fill(!0);
1485            }
1486        }
1487
1488        let backing = match isolation_type {
1489            IsolationType::None | IsolationType::Vbs if cfg!(guest_arch = "aarch64") => {
1490                BackingState::MshvAarch64 {
1491                    reg_page: if map_reg_page {
1492                        Some(
1493                            MappedPage::new(fd, HCL_REG_PAGE_OFFSET | vp as i64)
1494                                .map_err(Error::MmapRegPage)?,
1495                        )
1496                    } else {
1497                        None
1498                    },
1499                }
1500            }
1501            IsolationType::None | IsolationType::Vbs => BackingState::MshvX64 {
1502                reg_page: if map_reg_page {
1503                    Some(
1504                        MappedPage::new(fd, HCL_REG_PAGE_OFFSET | vp as i64)
1505                            .map_err(Error::MmapRegPage)?,
1506                    )
1507                } else {
1508                    None
1509                },
1510            },
1511            IsolationType::Snp => {
1512                // SAFETY: The run page is not accessed by any other VPs/kernel at this point
1513                // (`HclVp` creation), so we know we have exclusive access.
1514                unsafe {
1515                    let context: &mut protocol::snp_vp_context =
1516                        &mut *(&raw mut (*run.as_ptr()).context).cast();
1517                    context
1518                        .vmsa_tweak_bitmap
1519                        .copy_from_slice(&hcl.snp_register_bitmap);
1520                }
1521                let vmsa_vtl0 = MappedPage::new(fd, HCL_VMSA_PAGE_OFFSET | vp as i64)
1522                    .map_err(|e| Error::MmapVp(e, Some(Vtl::Vtl0)))?;
1523                let vmsa_vtl1 = MappedPage::new(fd, HCL_VMSA_GUEST_VSM_PAGE_OFFSET | vp as i64)
1524                    .map_err(|e| Error::MmapVp(e, Some(Vtl::Vtl1)))?;
1525                BackingState::Snp {
1526                    vmsa: [vmsa_vtl0, vmsa_vtl1].into(),
1527                    vtl0_apic_page: MappedPage::new(fd, MSHV_APIC_PAGE_OFFSET | vp as i64)
1528                        .map_err(|e| Error::MmapVp(e, Some(Vtl::Vtl0)))?,
1529                    vtl1_apic_page: private_dma_client
1530                        .ok_or(Error::MissingPrivateMemory)?
1531                        .allocate_dma_buffer(HV_PAGE_SIZE as usize)
1532                        .map_err(Error::AllocVp)?,
1533                }
1534            }
1535            IsolationType::Tdx => BackingState::Tdx {
1536                vtl0_apic_page: MappedPage::new(fd, MSHV_APIC_PAGE_OFFSET | vp as i64)
1537                    .map_err(|e| Error::MmapVp(e, Some(Vtl::Vtl0)))?,
1538                vtl1_apic_page: private_dma_client
1539                    .ok_or(Error::MissingPrivateMemory)?
1540                    .allocate_dma_buffer(HV_PAGE_SIZE as usize)
1541                    .map_err(Error::AllocVp)?,
1542            },
1543            IsolationType::Cca => BackingState::Cca {
1544                plane_run: private_dma_client
1545                    .ok_or(Error::MissingPrivateMemory)?
1546                    .allocate_dma_buffer(HV_PAGE_SIZE as usize)
1547                    .map_err(Error::AllocVp)?,
1548            },
1549        };
1550
1551        Ok(Self {
1552            state: Mutex::new(VpState::NotRunning),
1553            run,
1554            backing,
1555        })
1556    }
1557}
1558
1559/// Object used to run and to access state for a specific VP.
1560pub struct ProcessorRunner<'a, T: Backing<'a>> {
1561    hcl: &'a Hcl,
1562    vp: &'a HclVp,
1563    sidecar: Option<SidecarVp<'a>>,
1564    deferred_actions: Option<RegisteredDeferredActions<'a>>,
1565    run: &'a UnsafeCell<hcl_run>,
1566    intercept_message: &'a UnsafeCell<HvMessage>,
1567    state: T,
1568}
1569
1570/// An error returned by [`Hcl::runner`].
1571#[derive(Debug, Error)]
1572pub enum NoRunner {
1573    /// The partition is for a different isolation type.
1574    #[error("mismatched isolation type")]
1575    MismatchedIsolation,
1576    /// A sidecar VP was requested, but no sidecar was provided.
1577    #[error("missing sidecar")]
1578    MissingSidecar,
1579    /// The sidecar VP could not be contacted.
1580    #[error("sidecar communication error")]
1581    Sidecar(#[source] sidecar_client::SidecarError),
1582}
1583
1584/// An isolation-type-specific backing for a processor runner.
1585#[expect(private_bounds)]
1586pub trait Backing<'a>: BackingPrivate<'a> {}
1587
1588impl<'a, T: BackingPrivate<'a>> Backing<'a> for T {}
1589
1590mod private {
1591    use super::Hcl;
1592    use super::HclVp;
1593    use super::NoRunner;
1594    use super::ProcessorRunner;
1595    use crate::GuestVtl;
1596    use hvdef::HvRegisterName;
1597    use hvdef::HvRegisterValue;
1598    use sidecar_client::SidecarVp;
1599
1600    pub(super) trait BackingPrivate<'a>: Sized {
1601        fn new(vp: &'a HclVp, sidecar: Option<&SidecarVp<'a>>, hcl: &Hcl)
1602        -> Result<Self, NoRunner>;
1603
1604        fn try_set_reg(
1605            runner: &mut ProcessorRunner<'a, Self>,
1606            vtl: GuestVtl,
1607            name: HvRegisterName,
1608            value: HvRegisterValue,
1609        ) -> bool;
1610
1611        fn must_flush_regs_on(runner: &ProcessorRunner<'a, Self>, name: HvRegisterName) -> bool;
1612
1613        fn try_get_reg(
1614            runner: &ProcessorRunner<'a, Self>,
1615            vtl: GuestVtl,
1616            name: HvRegisterName,
1617        ) -> Option<HvRegisterValue>;
1618
1619        fn flush_register_page(runner: &mut ProcessorRunner<'a, Self>);
1620    }
1621}
1622
1623impl<'a, T: Backing<'a>> Drop for ProcessorRunner<'a, T> {
1624    fn drop(&mut self) {
1625        self.flush_deferred_state();
1626        drop(self.deferred_actions.take());
1627        let old_state = std::mem::replace(&mut *self.vp.state.lock(), VpState::NotRunning);
1628        assert!(matches!(old_state, VpState::Running(thread) if thread == Pthread::current()));
1629    }
1630}
1631
1632impl<'a, T: Backing<'a>> ProcessorRunner<'a, T> {
1633    /// Flushes any deferred state. Must be called if preparing the partition
1634    /// for save/restore (servicing).
1635    pub fn flush_deferred_state(&mut self) {
1636        T::flush_register_page(self);
1637        if let Some(actions) = &mut self.deferred_actions {
1638            actions.flush();
1639        }
1640    }
1641
1642    /// Clears the cancel flag so that the VP can be run again.
1643    pub fn clear_cancel(&mut self) {
1644        if !self.is_sidecar() {
1645            // SAFETY: self.run is mapped, and the cancel field is atomically
1646            // accessed by everyone.
1647            let cancel = unsafe { &*(&raw mut (*self.run.get()).cancel).cast::<AtomicU32>() };
1648            cancel.store(0, Ordering::SeqCst);
1649        }
1650    }
1651
1652    /// Set the halted state of the VP. If `true`, then `run()` will not
1653    /// actually run the VP but will just wait for a cancel request or signal.
1654    pub fn set_halted(&mut self, halted: bool) {
1655        // SAFETY: the `flags` field of the run page will not be concurrently
1656        // updated.
1657        let flags = unsafe { &mut (*self.run.get()).flags };
1658        if halted {
1659            *flags |= protocol::MSHV_VTL_RUN_FLAG_HALTED
1660        } else {
1661            *flags &= !protocol::MSHV_VTL_RUN_FLAG_HALTED
1662        }
1663    }
1664
1665    /// Gets the proxied interrupt request bitmap for VTL 0 from the hypervisor.
1666    pub fn proxy_irr_vtl0(&mut self) -> Option<[u32; 8]> {
1667        // SAFETY: the `scan_proxy_irr` and `proxy_irr` fields of the run page
1668        // are concurrently updated by the kernel on multiple processors. They
1669        // are accessed atomically everywhere.
1670        unsafe {
1671            let scan_proxy_irr = &*((&raw mut (*self.run.get()).scan_proxy_irr).cast::<AtomicU8>());
1672            let proxy_irr = &*((&raw mut (*self.run.get()).proxy_irr).cast::<[AtomicU32; 8]>());
1673            if scan_proxy_irr.load(Ordering::Acquire) == 0 {
1674                return None;
1675            }
1676
1677            scan_proxy_irr.store(0, Ordering::SeqCst);
1678            let mut r = [0; 8];
1679            for (irr, r) in proxy_irr.iter().zip(r.iter_mut()) {
1680                if irr.load(Ordering::Relaxed) != 0 {
1681                    *r = irr.swap(0, Ordering::Relaxed);
1682                }
1683            }
1684            Some(r)
1685        }
1686    }
1687
1688    /// Update the `proxy_irr_blocked` for VTL 0 in the run page
1689    pub fn update_proxy_irr_filter_vtl0(&mut self, irr_filter: &[u32; 8]) {
1690        // SAFETY: `proxy_irr_blocked` is accessed by current VP only, but could
1691        // be concurrently accessed by kernel too, hence accessing as Atomic
1692        let proxy_irr_blocked = unsafe {
1693            &mut *((&raw mut (*self.run.get()).proxy_irr_blocked).cast::<[AtomicU32; 8]>())
1694        };
1695
1696        // `irr_filter` bitmap has bits set for all allowed vectors (i.e. SINT and device interrupts)
1697        // Replace current `proxy_irr_blocked` with the given `irr_filter` bitmap.
1698        // By default block all (i.e. set all), and only allow (unset) given vectors from `irr_filter`.
1699        for (filter, irr) in proxy_irr_blocked.iter_mut().zip(irr_filter.iter()) {
1700            filter.store(!irr, Ordering::Relaxed);
1701            tracing::debug!(irr, "update_proxy_irr_filter");
1702        }
1703    }
1704
1705    /// Gets the proxy_irr_exit bitmask for VTL 0. This mask ensures that
1706    /// the masked interrupts always exit to user-space, and cannot
1707    /// be injected in the kernel. Interrupts matching this condition
1708    /// will be left on the proxy_irr field.
1709    pub fn proxy_irr_exit_mut_vtl0(&mut self) -> &mut [u32; 8] {
1710        // SAFETY: The `proxy_irr_exit` field of the run page will not be concurrently updated.
1711        unsafe { &mut (*self.run.get()).proxy_irr_exit }
1712    }
1713
1714    /// Gets the current offload_flags from the run page.
1715    pub fn offload_flags_mut(&mut self) -> &mut hcl_intr_offload_flags {
1716        // SAFETY: The `offload_flags` field of the run page will not be concurrently updated.
1717        unsafe { &mut (*self.run.get()).offload_flags }
1718    }
1719
1720    /// Runs the VP via the sidecar kernel.
1721    pub fn run_sidecar(&mut self) -> Result<SidecarRun<'_, 'a>, Error> {
1722        self.sidecar.as_mut().unwrap().run().map_err(Error::Sidecar)
1723    }
1724
1725    /// Run the following VP until an exit, error, or interrupt (cancel or
1726    /// signal) occurs.
1727    ///
1728    /// Returns `Ok(true)` if there is an exit to process, `Ok(false)` if there
1729    /// was a signal or cancel request.
1730    pub fn run(&mut self) -> Result<bool, Error> {
1731        assert!(self.sidecar.is_none());
1732        // Apply any deferred actions to the run page.
1733        if let Some(actions) = &mut self.deferred_actions {
1734            debug_assert!(self.hcl.supports_vtl_ret_action);
1735            // SAFETY: there are no concurrent accesses to the deferred action
1736            // slots.
1737            let mut slots = unsafe { DeferredActionSlots::new(self.run) };
1738            actions.move_to_slots(&mut slots);
1739        };
1740
1741        // N.B. cpu_context and exit_context are mutated by this call.
1742        //
1743        // SAFETY: no safety requirements for this ioctl.
1744        let r = unsafe { hcl_return_to_lower_vtl(self.hcl.mshv_vtl.file.as_raw_fd()) };
1745
1746        let has_intercept = match r {
1747            Ok(_) => true,
1748            Err(nix::errno::Errno::EINTR) => false,
1749            Err(err) => return Err(Error::ReturnToLowerVtl(err)),
1750        };
1751        Ok(has_intercept)
1752    }
1753
1754    /// Gets a reference to enter mode value, used by the kernel to specify the
1755    /// mode used when entering a lower VTL.
1756    pub fn enter_mode(&mut self) -> Option<&mut EnterModes> {
1757        if self.sidecar.is_some() {
1758            None
1759        } else {
1760            // SAFETY: self.run is mapped, and the mode field can only be mutated or accessed by
1761            // this object (or the kernel while `run` is called).
1762            Some(unsafe { &mut (*self.run.get()).mode })
1763        }
1764    }
1765
1766    /// Returns a reference to the exit message from the last exit.
1767    pub fn exit_message(&self) -> &HvMessage {
1768        // SAFETY: the exit message will not be concurrently accessed by the
1769        // kernel while this VP is in VTL2.
1770        unsafe { &*self.intercept_message.get() }
1771    }
1772
1773    /// Returns whether this is a sidecar VP.
1774    pub fn is_sidecar(&self) -> bool {
1775        self.sidecar.is_some()
1776    }
1777
1778    /// Sets the VTL that should be returned to when underhill exits
1779    pub fn set_exit_vtl(&mut self, vtl: GuestVtl) {
1780        // SAFETY: self.run is mapped, and the target_vtl field can only be
1781        // mutated or accessed by this object and only before the kernel is
1782        // invoked during `run`
1783        unsafe { (*self.run.get()).target_vtl = vtl.into() }
1784    }
1785}
1786
1787impl Hcl {
1788    /// Returns a new HCL instance.
1789    pub fn new(isolation: IsolationType, sidecar: Option<SidecarClient>) -> Result<Hcl, Error> {
1790        static SIGNAL_HANDLER_INIT: Once = Once::new();
1791        // SAFETY: The signal handler does not perform any actions that are forbidden
1792        // for signal handlers to perform, as it performs nothing.
1793        SIGNAL_HANDLER_INIT.call_once(|| unsafe {
1794            signal_hook::low_level::register(libc::SIGRTMIN(), || {
1795                // Do nothing, the ioctl will now return with EINTR.
1796            })
1797            .unwrap();
1798        });
1799
1800        // Open both mshv fds
1801        let mshv_fd = Mshv::new()?;
1802
1803        // Validate the hypervisor's advertised isolation type matches the
1804        // requested isolation type. In CVM scenarios, this is not trusted, so
1805        // we still need the isolation type from the caller.
1806        //
1807        // FUTURE: the kernel driver should probably tell us this, especially
1808        // since the kernel ABI is different for different isolation types.
1809        let validate_isolation = |supported_isolation| {
1810            if isolation != supported_isolation {
1811                Err(Error::MismatchedIsolation {
1812                    supported: supported_isolation,
1813                    requested: isolation,
1814                })
1815            } else {
1816                Ok(())
1817            }
1818        };
1819
1820        #[cfg(guest_arch = "x86_64")]
1821        {
1822            // xtask-fmt allow-target-arch cpu-intrinsic
1823            #[cfg(target_arch = "x86_64")]
1824            let supported_isolation = {
1825                let result = safe_intrinsics::cpuid(
1826                    hvdef::HV_CPUID_FUNCTION_MS_HV_ISOLATION_CONFIGURATION,
1827                    0,
1828                );
1829                match result.ebx & 0xF {
1830                    0 => IsolationType::None,
1831                    1 => IsolationType::Vbs,
1832                    2 => IsolationType::Snp,
1833                    3 => IsolationType::Tdx,
1834                    ty => panic!("unknown isolation type {ty:#x}"),
1835                }
1836            };
1837            // xtask-fmt allow-target-arch cpu-intrinsic
1838            #[cfg(not(target_arch = "x86_64"))]
1839            let supported_isolation = unreachable!();
1840
1841            validate_isolation(supported_isolation)?;
1842        }
1843
1844        let supports_vtl_ret_action = mshv_fd.check_extension(HCL_CAP_VTL_RETURN_ACTION)?;
1845        let supports_register_page = mshv_fd.check_extension(HCL_CAP_REGISTER_PAGE)?;
1846        let dr6_shared = mshv_fd.check_extension(HCL_CAP_DR6_SHARED)?;
1847        // This capability is TDX-only. On non-TDX guests treat EOPNOTSUPP as
1848        // "not supported" rather than failing; on TDX propagate the error.
1849        let supports_lower_vtl_timer_virt =
1850            match mshv_fd.check_extension(HCL_CAP_LOWER_VTL_TIMER_VIRT) {
1851                Ok(supported) => supported,
1852                Err(Error::CheckExtensions(_, nix::errno::Errno::EOPNOTSUPP))
1853                    if isolation != IsolationType::Tdx =>
1854                {
1855                    false
1856                }
1857                Err(err) => return Err(err),
1858            };
1859
1860        tracing::debug!(
1861            supports_vtl_ret_action,
1862            supports_register_page,
1863            supports_lower_vtl_timer_virt,
1864            "HCL capabilities",
1865        );
1866
1867        let vtl_fd = mshv_fd.create_vtl()?;
1868
1869        #[cfg(guest_arch = "aarch64")]
1870        {
1871            let supported_isolation = match isolation {
1872                IsolationType::Cca => {
1873                    // Realm-visible ID registers can be sanitized, so use the
1874                    // HCL/RMM path to validate that CCA is actually available.
1875                    if vtl_fd.get_realm_config().is_ok() {
1876                        IsolationType::Cca
1877                    } else {
1878                        IsolationType::None
1879                    }
1880                }
1881                _ => IsolationType::None,
1882            };
1883
1884            validate_isolation(supported_isolation)?;
1885        }
1886
1887        #[cfg(not(any(guest_arch = "x86_64", guest_arch = "aarch64")))]
1888        {
1889            let supported_isolation = IsolationType::None;
1890
1891            validate_isolation(supported_isolation)?;
1892        }
1893
1894        // Open the hypercall pseudo-device
1895        let mshv_hvcall = MshvHvcall::new()?;
1896
1897        // Override certain features for hardware isolated VMs.
1898        // TODO: vtl return actions are inhibited for hardware isolated VMs because they currently
1899        // are a pessimization since interrupt handling (and synic handling) are all done from
1900        // within VTL2. Future vtl return actions may be different, requiring granular handling.
1901        let supports_vtl_ret_action = supports_vtl_ret_action && !isolation.is_hardware_isolated();
1902        let supports_register_page = supports_register_page && !isolation.is_hardware_isolated();
1903        let snp_register_bitmap = [0u8; 64];
1904
1905        Ok(Hcl {
1906            mshv_hvcall,
1907            mshv_vtl: vtl_fd,
1908            vps: Vec::new(),
1909            supports_vtl_ret_action,
1910            supports_register_page,
1911            dr6_shared,
1912            supports_lower_vtl_timer_virt,
1913            isolation,
1914            snp_register_bitmap,
1915            sidecar,
1916        })
1917    }
1918
1919    /// Set allowed hypercalls.
1920    pub fn set_allowed_hypercalls(&self, codes: &[HypercallCode]) {
1921        self.mshv_hvcall.set_allowed_hypercalls(codes)
1922    }
1923
1924    /// Initializes SNP register tweak bitmap
1925    pub fn set_snp_register_bitmap(&mut self, register_bitmap: [u8; 64]) {
1926        self.snp_register_bitmap = register_bitmap;
1927    }
1928
1929    /// Adds `vp_count` VPs.
1930    pub fn add_vps(
1931        &mut self,
1932        vp_count: u32,
1933        private_pool: Option<&Arc<dyn DmaClient>>,
1934    ) -> Result<(), Error> {
1935        self.vps = (0..vp_count)
1936            .map(|vp| {
1937                HclVp::new(
1938                    self,
1939                    vp,
1940                    self.supports_register_page,
1941                    self.isolation,
1942                    private_pool,
1943                )
1944            })
1945            .collect::<Result<_, _>>()?;
1946
1947        Ok(())
1948    }
1949
1950    /// Registers with the hypervisor for an intercept.
1951    pub fn register_intercept(
1952        &self,
1953        intercept_type: HvInterceptType,
1954        access_type_mask: u32,
1955        intercept_parameters: HvInterceptParameters,
1956    ) -> Result<(), HvError> {
1957        let intercept_info = hvdef::hypercall::InstallIntercept {
1958            partition_id: HV_PARTITION_ID_SELF,
1959            access_type_mask,
1960            intercept_type,
1961            intercept_parameters,
1962        };
1963
1964        // SAFETY: calling hypercall with appropriate input and output.
1965        unsafe {
1966            self.mshv_hvcall
1967                .hvcall(
1968                    HypercallCode::HvCallInstallIntercept,
1969                    &intercept_info,
1970                    &mut (),
1971                )
1972                .unwrap()
1973                .result()
1974        }
1975    }
1976
1977    /// Returns the base CPU that manages the given sidecar VP.
1978    pub fn sidecar_base_cpu(&self, vp_index: u32) -> Option<u32> {
1979        Some(self.sidecar.as_ref()?.base_cpu(vp_index))
1980    }
1981
1982    /// Returns whether sidecar support is enabled for this partition.
1983    pub fn sidecar_enabled(&self) -> bool {
1984        self.sidecar.is_some()
1985    }
1986
1987    /// Create a VP runner for the given partition.
1988    pub fn runner<'a, T: Backing<'a>>(
1989        &'a self,
1990        vp_index: u32,
1991        use_sidecar: bool,
1992    ) -> Result<ProcessorRunner<'a, T>, NoRunner> {
1993        let vp = &self.vps[vp_index as usize];
1994
1995        let sidecar = if use_sidecar {
1996            Some(
1997                self.sidecar
1998                    .as_ref()
1999                    .ok_or(NoRunner::MissingSidecar)?
2000                    .vp(vp_index),
2001            )
2002        } else {
2003            None
2004        };
2005
2006        let state = T::new(vp, sidecar.as_ref(), self)?;
2007
2008        // Set this thread as the runner.
2009        let VpState::NotRunning =
2010            std::mem::replace(&mut *vp.state.lock(), VpState::Running(Pthread::current()))
2011        else {
2012            panic!("another runner already exists")
2013        };
2014
2015        let actions = if sidecar.is_none() && self.supports_vtl_ret_action {
2016            Some(register_deferred_actions(self))
2017        } else {
2018            None
2019        };
2020
2021        // SAFETY: The run page is guaranteed to be mapped and valid.
2022        // While the exit message might not be filled in yet we're only computing its address.
2023        let intercept_message = unsafe {
2024            &*sidecar.as_ref().map_or(
2025                std::ptr::addr_of!((*vp.run.as_ptr()).exit_message).cast(),
2026                |s| s.intercept_message().cast(),
2027            )
2028        };
2029
2030        Ok(ProcessorRunner {
2031            hcl: self,
2032            vp,
2033            deferred_actions: actions,
2034            run: vp.run.as_ref(),
2035            intercept_message,
2036            state,
2037            sidecar,
2038        })
2039    }
2040
2041    /// Trigger the following interrupt request.
2042    pub fn request_interrupt(
2043        &self,
2044        interrupt_control: hvdef::HvInterruptControl,
2045        destination_address: u64,
2046        requested_vector: u32,
2047        target_vtl: GuestVtl,
2048    ) -> Result<(), Error> {
2049        tracing::trace!(
2050            ?interrupt_control,
2051            destination_address,
2052            requested_vector,
2053            "requesting interrupt"
2054        );
2055
2056        assert!(!self.isolation.is_hardware_isolated());
2057
2058        let request = AssertVirtualInterrupt {
2059            partition_id: HV_PARTITION_ID_SELF,
2060            interrupt_control,
2061            destination_address,
2062            requested_vector,
2063            target_vtl: target_vtl as u8,
2064            rsvd0: 0,
2065            rsvd1: 0,
2066        };
2067
2068        // SAFETY: calling the hypercall with correct input buffer.
2069        let output = unsafe {
2070            self.mshv_hvcall.hvcall(
2071                HypercallCode::HvCallAssertVirtualInterrupt,
2072                &request,
2073                &mut (),
2074            )
2075        }
2076        .unwrap();
2077
2078        output.result().map_err(Error::RequestInterrupt)
2079    }
2080
2081    /// Attempts to signal a given vp/sint/flag combo using HvSignalEventDirect.
2082    ///
2083    /// No result is returned because this request may be deferred until the
2084    /// hypervisor is returning to a lower VTL.
2085    pub fn signal_event_direct(&self, vp: u32, sint: u8, flag: u16) {
2086        tracing::trace!(vp, sint, flag, "signaling event");
2087        push_deferred_action(self, DeferredAction::SignalEvent { vp, sint, flag });
2088    }
2089
2090    fn hvcall_signal_event_direct(&self, vp: u32, sint: u8, flag: u16) -> Result<bool, Error> {
2091        let signal_event_input = hvdef::hypercall::SignalEventDirect {
2092            target_partition: HV_PARTITION_ID_SELF,
2093            target_vp: vp,
2094            target_vtl: Vtl::Vtl0 as u8,
2095            target_sint: sint,
2096            flag_number: flag,
2097        };
2098        let mut signal_event_output = hvdef::hypercall::SignalEventDirectOutput {
2099            newly_signaled: 0,
2100            rsvd: [0; 7],
2101        };
2102
2103        // SAFETY: calling the hypercall with correct input buffer.
2104        let output = unsafe {
2105            self.mshv_hvcall.hvcall(
2106                HypercallCode::HvCallSignalEventDirect,
2107                &signal_event_input,
2108                &mut signal_event_output,
2109            )
2110        }
2111        .unwrap();
2112
2113        output
2114            .result()
2115            .map(|_| signal_event_output.newly_signaled != 0)
2116            .map_err(Error::SignalEvent)
2117    }
2118
2119    /// Attempts to post a given message to a vp/sint combo using HvPostMessageDirect.
2120    pub fn post_message_direct(
2121        &self,
2122        vp: u32,
2123        sint: u8,
2124        message: &HvMessage,
2125    ) -> Result<(), HvError> {
2126        tracing::trace!(vp, sint, "posting message");
2127
2128        let post_message = hvdef::hypercall::PostMessageDirect {
2129            partition_id: HV_PARTITION_ID_SELF,
2130            vp_index: vp,
2131            vtl: Vtl::Vtl0 as u8,
2132            padding0: [0; 3],
2133            sint,
2134            padding1: [0; 3],
2135            message: zerocopy::Unalign::new(*message),
2136            padding2: 0,
2137        };
2138
2139        // SAFETY: calling the hypercall with correct input buffer.
2140        let output = unsafe {
2141            self.mshv_hvcall.hvcall(
2142                HypercallCode::HvCallPostMessageDirect,
2143                &post_message,
2144                &mut (),
2145            )
2146        }
2147        .unwrap();
2148
2149        output.result()
2150    }
2151
2152    /// Sets a file to poll during run. When the file's poll state changes, the
2153    /// run will be automatically cancelled.
2154    pub fn set_poll_file(&self, vp: u32, file: RawFd) -> Result<(), Error> {
2155        // SAFETY: calling the IOCTL as defined. This is safe even if the caller
2156        // does not own `file` since all this does is register the file for
2157        // polling.
2158        unsafe {
2159            hcl_set_poll_file(
2160                self.mshv_vtl.file.as_raw_fd(),
2161                &protocol::hcl_set_poll_file {
2162                    cpu: vp as i32,
2163                    fd: file,
2164                },
2165            )
2166            .map_err(Error::SetPollFile)?;
2167        }
2168        Ok(())
2169    }
2170
2171    fn to_hv_gpa_range_array(gpa_memory_ranges: &[MemoryRange]) -> Vec<HvGpaRange> {
2172        const PAGES_PER_ENTRY: u64 = 2048;
2173        const PAGE_SIZE: u64 = HV_PAGE_SIZE;
2174
2175        // Estimate the total number of pages across all memory ranges
2176        let estimated_size: usize = gpa_memory_ranges
2177            .iter()
2178            .map(|memory_range| {
2179                let total_pages = (memory_range.end() - memory_range.start()).div_ceil(PAGE_SIZE);
2180                total_pages.div_ceil(PAGES_PER_ENTRY)
2181            })
2182            .sum::<u64>() as usize;
2183
2184        // Create a vector with the estimated size
2185        let mut hv_gpa_ranges = Vec::with_capacity(estimated_size);
2186
2187        for memory_range in gpa_memory_ranges {
2188            // Calculate the total number of pages in the memory range
2189            let total_pages = (memory_range.end() - memory_range.start()).div_ceil(PAGE_SIZE);
2190
2191            // Convert start address to page number
2192            let start_page = memory_range.start_4k_gpn();
2193
2194            // Generate the ranges and append them to the vector
2195            hv_gpa_ranges.extend(
2196                (0..total_pages)
2197                    .step_by(PAGES_PER_ENTRY as usize)
2198                    .map(|start| {
2199                        let end = std::cmp::min(total_pages, start + PAGES_PER_ENTRY);
2200                        let pages_in_this_range = end - start;
2201                        let gpa_page_number = start_page + start;
2202
2203                        let extended = HvGpaRangeExtended::new()
2204                            .with_additional_pages(pages_in_this_range - 1)
2205                            .with_large_page(false) // Assuming not a large page
2206                            .with_gpa_page_number(gpa_page_number);
2207
2208                        HvGpaRange(extended.into_bits())
2209                    }),
2210            );
2211        }
2212
2213        hv_gpa_ranges // Return the vector at the end
2214    }
2215
2216    fn pin_unpin_gpa_ranges_internal(
2217        &self,
2218        gpa_ranges: &[HvGpaRange],
2219        action: GpaPinUnpinAction,
2220    ) -> Result<(), PinUnpinError> {
2221        const PIN_REQUEST_HEADER_SIZE: usize =
2222            size_of::<hvdef::hypercall::PinUnpinGpaPageRangesHeader>();
2223        const MAX_INPUT_ELEMENTS: usize =
2224            (HV_PAGE_SIZE as usize - PIN_REQUEST_HEADER_SIZE) / size_of::<u64>();
2225
2226        let header = hvdef::hypercall::PinUnpinGpaPageRangesHeader { reserved: 0 };
2227        let mut ranges_processed = 0;
2228
2229        for chunk in gpa_ranges.chunks(MAX_INPUT_ELEMENTS) {
2230            // SAFETY: This unsafe block is valid because:
2231            // 1. The code and header going to match the expected input for the hypercall.
2232            //
2233            // 2. Hypercall result is checked right after the hypercall is issued.
2234            //
2235            let output = unsafe {
2236                self.mshv_hvcall
2237                    .hvcall_rep(
2238                        match action {
2239                            GpaPinUnpinAction::PinGpaRange => HypercallCode::HvCallPinGpaPageRanges,
2240                            GpaPinUnpinAction::UnpinGpaRange => {
2241                                HypercallCode::HvCallUnpinGpaPageRanges
2242                            }
2243                        },
2244                        &header,
2245                        HvcallRepInput::Elements(chunk),
2246                        None::<&mut [u8]>,
2247                    )
2248                    .expect("submitting pin/unpin hypercall should not fail")
2249            };
2250
2251            ranges_processed += output.elements_processed();
2252
2253            output.result().map_err(|e| PinUnpinError {
2254                ranges_processed,
2255                error: e,
2256            })?;
2257        }
2258
2259        // At end all the ranges should be processed
2260        if ranges_processed == gpa_ranges.len() {
2261            Ok(())
2262        } else {
2263            Err(PinUnpinError {
2264                ranges_processed,
2265                error: HvError::OperationFailed,
2266            })
2267        }
2268    }
2269
2270    fn perform_pin_unpin_gpa_ranges(
2271        &self,
2272        gpa_ranges: &[MemoryRange],
2273        action: GpaPinUnpinAction,
2274        rollback_action: GpaPinUnpinAction,
2275    ) -> Result<(), HvError> {
2276        let hv_gpa_ranges: Vec<HvGpaRange> = Self::to_hv_gpa_range_array(gpa_ranges);
2277
2278        // Attempt to pin/unpin the ranges
2279        match self.pin_unpin_gpa_ranges_internal(&hv_gpa_ranges, action) {
2280            Ok(_) => Ok(()),
2281            Err(PinUnpinError {
2282                error,
2283                ranges_processed,
2284            }) => {
2285                // Unpin the ranges that were successfully pinned
2286                let pinned_ranges = &hv_gpa_ranges[..ranges_processed];
2287                if let Err(rollback_error) =
2288                    self.pin_unpin_gpa_ranges_internal(pinned_ranges, rollback_action)
2289                {
2290                    // Panic if rollback is failing
2291                    panic!(
2292                        "Failed to perform action {:?} on ranges. Error : {:?}. \
2293                        Attempted to rollback {:?} ranges out of {:?}.\n rollback error: {:?}",
2294                        action,
2295                        error,
2296                        ranges_processed,
2297                        gpa_ranges.len(),
2298                        rollback_error
2299                    );
2300                }
2301                // Surface the original error
2302                Err(error)
2303            }
2304        }
2305    }
2306
2307    /// Pins the specified guest physical address ranges in the hypervisor.
2308    /// The memory ranges passed to this function must be VA backed memory.
2309    /// If a partial failure occurs (i.e., some but not all the ranges were successfully pinned),
2310    /// the function will automatically attempt to unpin any successfully pinned ranges.
2311    /// This "rollback" behavior ensures that no partially pinned state remains, which
2312    /// could otherwise lead to inconsistencies.
2313    ///
2314    pub fn pin_gpa_ranges(&self, ranges: &[MemoryRange]) -> Result<(), HvError> {
2315        self.perform_pin_unpin_gpa_ranges(
2316            ranges,
2317            GpaPinUnpinAction::PinGpaRange,
2318            GpaPinUnpinAction::UnpinGpaRange,
2319        )
2320    }
2321
2322    /// Unpins the specified guest physical address ranges in the hypervisor.
2323    /// The memory ranges passed to this function must be VA backed memory.
2324    /// If a partial failure occurs (i.e., some but not all the ranges were successfully unpinned),
2325    /// the function will automatically attempt to pin any successfully unpinned ranges. This "rollback"
2326    /// behavior ensures that no partially unpinned state remains, which could otherwise lead to inconsistencies.
2327    ///
2328    pub fn unpin_gpa_ranges(&self, ranges: &[MemoryRange]) -> Result<(), HvError> {
2329        self.perform_pin_unpin_gpa_ranges(
2330            ranges,
2331            GpaPinUnpinAction::UnpinGpaRange,
2332            GpaPinUnpinAction::PinGpaRange,
2333        )
2334    }
2335
2336    /// Sets the VTL protection mask for the specified memory range.
2337    pub fn modify_vtl_protection_mask(
2338        &self,
2339        range: MemoryRange,
2340        map_flags: HvMapGpaFlags,
2341        target_vtl: HvInputVtl,
2342    ) -> Result<(), ApplyVtlProtectionsError> {
2343        if self.isolation.is_hardware_isolated() {
2344            // TODO SNP TODO TDX - required for vmbus relay monitor page support
2345            todo!();
2346        }
2347
2348        self.mshv_hvcall
2349            .modify_vtl_protection_mask(range, map_flags, target_vtl)
2350    }
2351
2352    /// Checks whether the target vtl has vtl permissions for the given gpa
2353    pub fn check_vtl_access(
2354        &self,
2355        gpa: u64,
2356        target_vtl: GuestVtl,
2357        flags: HvMapGpaFlags,
2358    ) -> Result<Option<CheckVtlAccessResult>, Error> {
2359        assert!(!self.isolation.is_hardware_isolated());
2360
2361        let header = hvdef::hypercall::CheckSparseGpaPageVtlAccess {
2362            partition_id: HV_PARTITION_ID_SELF,
2363            target_vtl: HvInputVtl::from(target_vtl),
2364            desired_access: u32::from(flags) as u8,
2365            reserved0: 0,
2366            reserved1: 0,
2367        };
2368
2369        let mut output = [hvdef::hypercall::CheckSparseGpaPageVtlAccessOutput::new()];
2370
2371        // SAFETY: The input header and rep slice are the correct types for this hypercall.
2372        //         The hypercall output is validated right after the hypercall is issued.
2373        let status = unsafe {
2374            self.mshv_hvcall.hvcall_rep::<hvdef::hypercall::CheckSparseGpaPageVtlAccess, u64, hvdef::hypercall::CheckSparseGpaPageVtlAccessOutput>(
2375                HypercallCode::HvCallCheckSparseGpaPageVtlAccess,
2376                &header,
2377                HvcallRepInput::Elements(&[gpa >> hvdef::HV_PAGE_SHIFT]),
2378                Some(&mut output),
2379            )
2380            .expect("check_vtl_access hypercall should not fail")
2381        };
2382
2383        status.result().map_err(Error::CheckVtlAccess)?;
2384
2385        let access_result = output[0];
2386
2387        if access_result.result_code() as u32
2388            != hvdef::hypercall::CheckGpaPageVtlAccessResultCode::SUCCESS.0
2389        {
2390            return Ok(Some(CheckVtlAccessResult {
2391                vtl: (access_result.intercepting_vtl() as u8)
2392                    .try_into()
2393                    .expect("checking vtl permissions failure should return valid vtl"),
2394                denied_flags: (access_result.denied_access() as u32).into(),
2395            }));
2396        }
2397
2398        assert_eq!(status.elements_processed(), 1);
2399        Ok(None)
2400    }
2401
2402    /// Enables a vtl for the partition
2403    pub fn enable_partition_vtl(
2404        &self,
2405        vtl: GuestVtl,
2406        flags: hvdef::hypercall::EnablePartitionVtlFlags,
2407    ) -> Result<(), HvError> {
2408        use hvdef::hypercall;
2409
2410        let header = hypercall::EnablePartitionVtl {
2411            partition_id: HV_PARTITION_ID_SELF,
2412            target_vtl: vtl.into(),
2413            flags,
2414            reserved_z0: 0,
2415            reserved_z1: 0,
2416        };
2417
2418        // SAFETY: The input header and slice are the correct types for this hypercall.
2419        //         The hypercall output is validated right after the hypercall is issued.
2420        let status = unsafe {
2421            self.mshv_hvcall
2422                .hvcall(HypercallCode::HvCallEnablePartitionVtl, &header, &mut ())
2423                .expect("submitting hypercall should not fail")
2424        };
2425
2426        status.result()
2427    }
2428
2429    /// Enables a vtl on a vp
2430    pub fn enable_vp_vtl(
2431        &self,
2432        vp_index: u32,
2433        vtl: GuestVtl,
2434        hv_vp_context: InitialVpContextX64,
2435    ) -> Result<(), HvError> {
2436        use hvdef::hypercall;
2437
2438        let header = hypercall::EnableVpVtlX64 {
2439            partition_id: HV_PARTITION_ID_SELF,
2440            vp_index,
2441            target_vtl: vtl.into(),
2442            reserved: [0; 3],
2443            vp_vtl_context: hv_vp_context,
2444        };
2445
2446        // SAFETY: The input header and slice are the correct types for this hypercall.
2447        //         The hypercall output is validated right after the hypercall is issued.
2448        let status = unsafe {
2449            self.mshv_hvcall
2450                .hvcall(HypercallCode::HvCallEnableVpVtl, &header, &mut ())
2451                .expect("submitting hypercall should not fail")
2452        };
2453
2454        status.result()
2455    }
2456
2457    /// Gets the PFN for the VTL 1 VMSA
2458    pub fn vtl1_vmsa_pfn(&self, cpu_index: u32) -> u64 {
2459        let mut vp_pfn = cpu_index as u64; // input vp, output pfn
2460
2461        // SAFETY: The ioctl requires no prerequisites other than the VTL 1 VMSA
2462        // should be mapped. This ioctl should never fail as long as the vtl 1
2463        // VMSA was mapped.
2464        unsafe {
2465            hcl_read_guest_vsm_page_pfn(self.mshv_vtl.file.as_raw_fd(), &mut vp_pfn)
2466                .expect("should always succeed");
2467        }
2468
2469        vp_pfn
2470    }
2471
2472    /// Gets the PFN for the VTL 0 secure AVIC.
2473    ///
2474    /// # Panics
2475    ///
2476    /// Panics if the Secure AVIC VTL 0 page was not mapped (i.e., Secure AVIC
2477    /// is not enabled). Callers must ensure Secure AVIC is enabled before
2478    /// calling this method.
2479    pub fn secure_avic_vtl0_pfn(&self, cpu_index: u32) -> u64 {
2480        let mut savic_pfn = cpu_index as u64; // input vp, output pfn
2481
2482        // SAFETY: The ioctl requires no prerequisites other than the Secure AVIC VTL 0
2483        // should be mapped. This ioctl should never fail as long as the VTL 0
2484        // Secure AVIC was mapped.
2485        unsafe {
2486            hcl_read_secure_avic_vtl0_pfn(self.mshv_vtl.file.as_raw_fd(), &mut savic_pfn)
2487                .expect("should always succeed");
2488        }
2489
2490        savic_pfn
2491    }
2492
2493    /// Returns the isolation type for the partition.
2494    pub fn isolation(&self) -> IsolationType {
2495        self.isolation
2496    }
2497
2498    /// Reads MSR_IA32_VMX_CR4_FIXED1 in kernel mode.
2499    pub fn read_vmx_cr4_fixed1(&self) -> u64 {
2500        let mut value = 0;
2501
2502        // SAFETY: The ioctl requires no prerequisites other than a location to
2503        // write the read MSR. This ioctl should never fail.
2504        unsafe {
2505            hcl_read_vmx_cr4_fixed1(self.mshv_vtl.file.as_raw_fd(), &mut value)
2506                .expect("should always succeed");
2507        }
2508
2509        value
2510    }
2511
2512    /// Attempts to opt this TD into hardware-bound seal keys by setting
2513    /// `TD_CTLS.ENABLE_HW_SEAL_KEYS` via `TDG.VM.WR`.
2514    ///
2515    /// Returns `Ok(true)` if the bit is set after the operation (the
2516    /// `TDG.MR.KEY.GET` TDCALL is available), `Ok(false)` if the TDX module
2517    /// does not support sealing, or an error if the write itself was rejected.
2518    ///
2519    /// Only valid on TDX-isolated partitions.
2520    pub fn tdx_enable_hw_seal_keys(&self) -> Result<bool, x86defs::tdx::TdCallResult> {
2521        self.mshv_vtl.tdx_enable_hw_seal_keys()
2522    }
2523
2524    /// Reads the global-scope `TDX_FEATURES0` metadata field via `TDG.SYS.RD`,
2525    /// enumerating optional TDX module features (including hardware-bound
2526    /// sealing support).
2527    ///
2528    /// Returns an error if the module does not support `TDG.SYS.RD` or rejects
2529    /// the field. Only valid on TDX-isolated partitions.
2530    pub fn tdx_read_features0(
2531        &self,
2532    ) -> Result<x86defs::tdx::TdxFeatures0, x86defs::tdx::TdCallResult> {
2533        self.mshv_vtl.tdx_read_features0()
2534    }
2535
2536    /// Invokes the HvCallRetargetDeviceInterrupt hypercall.
2537    /// `target_processors` must be sorted in ascending order.
2538    pub fn retarget_device_interrupt(
2539        &self,
2540        device_id: u64,
2541        entry: hvdef::hypercall::InterruptEntry,
2542        vector: u32,
2543        multicast: bool,
2544        target_processors: ProcessorSet<'_>,
2545        proxy_redirect: bool,
2546    ) -> Result<(), HvError> {
2547        let header = hvdef::hypercall::RetargetDeviceInterrupt {
2548            partition_id: HV_PARTITION_ID_SELF,
2549            device_id,
2550            entry,
2551            rsvd: 0,
2552            target_header: hvdef::hypercall::InterruptTarget {
2553                vector,
2554                flags: hvdef::hypercall::HvInterruptTargetFlags::default()
2555                    .with_multicast(multicast)
2556                    .with_processor_set(true)
2557                    .with_proxy_redirect(proxy_redirect),
2558                // Always use a generic processor set to simplify construction. This hypercall is
2559                // invoked relatively infrequently, the overhead should be acceptable.
2560                mask_or_format: hvdef::hypercall::HV_GENERIC_SET_SPARSE_4K,
2561            },
2562        };
2563        let processor_set = Vec::from_iter(target_processors.as_generic_set());
2564
2565        // SAFETY: The input header and slice are the correct types for this hypercall.
2566        //         The hypercall output is validated right after the hypercall is issued.
2567        let status = unsafe {
2568            self.mshv_hvcall
2569                .hvcall_var(
2570                    HypercallCode::HvCallRetargetDeviceInterrupt,
2571                    &header,
2572                    processor_set.as_bytes(),
2573                    &mut (),
2574                )
2575                .expect("submitting hypercall should not fail")
2576        };
2577
2578        status.result()
2579    }
2580
2581    /// Gets the permissions for a vtl.
2582    /// Currently unused, but available for debugging purposes
2583    #[cfg(debug_assertions)]
2584    pub fn rmp_query(&self, gpa: u64, vtl: GuestVtl) -> x86defs::snp::SevRmpAdjust {
2585        use x86defs::snp::SevRmpAdjust;
2586
2587        let page_count = 1u64;
2588        let flags = [u64::from(SevRmpAdjust::new().with_target_vmpl(match vtl {
2589            GuestVtl::Vtl0 => 2,
2590            GuestVtl::Vtl1 => 1,
2591        }))];
2592        let page_size = [0u64];
2593        let pages_processed = 0;
2594
2595        debug_assert!(flags.len() == page_count as usize);
2596        debug_assert!(page_size.len() == page_count as usize);
2597
2598        let query = mshv_rmpquery {
2599            start_pfn: gpa / HV_PAGE_SIZE,
2600            page_count,
2601            terminate_on_failure: 0,
2602            ram: 0,
2603            padding: Default::default(),
2604            flags: flags.as_ptr().cast_mut(),
2605            page_size: page_size.as_ptr().cast_mut(),
2606            pages_processed: core::ptr::from_ref(&pages_processed).cast_mut(),
2607        };
2608
2609        // SAFETY: the input query is the correct type for this ioctl
2610        unsafe {
2611            hcl_rmpquery_pages(self.mshv_vtl.file.as_raw_fd(), &query)
2612                .expect("should always succeed");
2613        }
2614        debug_assert!(pages_processed <= page_count);
2615
2616        SevRmpAdjust::from(flags[0])
2617    }
2618
2619    /// Issues an INVLPGB instruction.
2620    pub fn invlpgb(&self, rax: u64, edx: u32, ecx: u32) {
2621        let data = mshv_invlpgb {
2622            rax,
2623            edx,
2624            ecx,
2625            _pad0: 0,
2626            _pad1: 0,
2627        };
2628        // SAFETY: ioctl has no prerequisites.
2629        unsafe {
2630            hcl_invlpgb(self.mshv_vtl.file.as_raw_fd(), &data).expect("should always succeed");
2631        }
2632    }
2633
2634    /// Issues a TLBSYNC instruction.
2635    pub fn tlbsync(&self) {
2636        // SAFETY: ioctl has no prerequisites.
2637        unsafe {
2638            hcl_tlbsync(self.mshv_vtl.file.as_raw_fd()).expect("should always succeed");
2639        }
2640    }
2641
2642    /// Causes the specified CPUs to be woken out of a lower VTL.
2643    pub fn kick_cpus(
2644        &self,
2645        cpus: impl IntoIterator<Item = u32>,
2646        cancel_run: bool,
2647        wait_for_other_cpus: bool,
2648    ) {
2649        let mut cpu_bitmap: BitVec<u8> = BitVec::from_vec(vec![0; self.vps.len().div_ceil(8)]);
2650        for cpu in cpus {
2651            cpu_bitmap.set(cpu as usize, true);
2652        }
2653
2654        let data = protocol::hcl_kick_cpus {
2655            len: cpu_bitmap.len() as u64,
2656            cpu_mask: cpu_bitmap.as_bitptr().pointer(),
2657            flags: protocol::hcl_kick_cpus_flags::new()
2658                .with_cancel_run(cancel_run)
2659                .with_wait_for_other_cpus(wait_for_other_cpus),
2660        };
2661
2662        // SAFETY: ioctl has no prerequisites.
2663        unsafe {
2664            hcl_kickcpus(self.mshv_vtl.file.as_raw_fd(), &data).expect("should always succeed");
2665        }
2666    }
2667
2668    /// Map or unmap guest device interrupt vector in VTL2 kernel
2669    pub fn map_redirected_device_interrupt(
2670        &self,
2671        vector: u32,
2672        apic_id: u32,
2673        create_mapping: bool,
2674    ) -> Result<u32, Error> {
2675        let mut param = mshv_map_device_int {
2676            vector,
2677            apic_id,
2678            create_mapping: create_mapping.into(),
2679            padding: [0; 7],
2680        };
2681
2682        // SAFETY: following the IOCTL definition.
2683        unsafe {
2684            hcl_map_redirected_device_interrupt(self.mshv_vtl.file.as_raw_fd(), &mut param)
2685                .map_err(Error::MapRedirectedDeviceInterrupt)?;
2686        }
2687
2688        Ok(param.vector)
2689    }
2690
2691    /// Restore partition time. This is typically called after resume from
2692    /// hibernate to synchronize the TSC with the value at hibernate time.
2693    pub fn restore_partition_time(
2694        &self,
2695        tsc_sequence: u32,
2696        reference_time_in_100_ns: u64,
2697        tsc: u64,
2698    ) -> Result<(), Error> {
2699        let partition_time = mshv_restore_partition_time {
2700            tsc_sequence,
2701            reserved: 0,
2702            reference_time_in_100_ns,
2703            tsc,
2704        };
2705
2706        // SAFETY: ioctl has no prerequisites.
2707        unsafe {
2708            hcl_restore_partition_time(self.mshv_vtl.file.as_raw_fd(), &partition_time)
2709                .map_err(Error::RestorePartitionTime)?;
2710        }
2711
2712        Ok(())
2713    }
2714}