Skip to main content

virt_mshv/x86_64/
mod.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! x86_64-specific implementation of the mshv hypervisor backend.
5
6mod vm_state;
7mod vp_state;
8
9use crate::Error;
10use crate::ErrorInner;
11use crate::LinuxMshv;
12use crate::MshvIsolationState;
13use crate::MshvPartition;
14use crate::MshvPartitionInner;
15use crate::MshvProcessor;
16use crate::MshvProcessorBinder;
17use crate::MshvProtoPartition;
18use crate::MshvVpRunner;
19use crate::VcpuFdExt;
20use crate::common_synthetic_features;
21use crate::create_vm_with_retry;
22
23use guestmem::DoorbellRegistration;
24use guestmem::GuestMemory;
25use headervec::HeaderVec;
26use hv1_hypercall::X64RegisterIo;
27use hvdef::HvDeliverabilityNotificationsRegister;
28use hvdef::HvMessage;
29use hvdef::HvMessageType;
30use hvdef::HvPartitionPropertyCode;
31use hvdef::HvProcessorVendor;
32use hvdef::HvX64RegisterName;
33use hvdef::HvX64RegisterPage;
34use hvdef::Vtl;
35use hvdef::hypercall::HvRegisterAssoc;
36use memory_range::MemoryRange;
37use mshv_ioctls::InterruptRequest;
38use mshv_ioctls::VcpuFd;
39use pal::unix::pthread::Pthread;
40use parking_lot::Mutex;
41use pci_core::msi::SignalMsi;
42use std::os::fd::AsRawFd;
43use std::sync::Arc;
44use virt::Hv1;
45use virt::PartitionAccessState;
46use virt::PartitionConfig;
47use virt::ProtoPartition;
48use virt::ProtoPartitionConfig;
49use virt::VpHaltReason;
50use virt::VpIndex;
51use virt::io::CpuIo;
52use virt::irqcon::MsiRequest;
53use virt::state::StateElement as _;
54use virt::x86::apic_software_device::ApicSoftwareDevice;
55use virt::x86::apic_software_device::ApicSoftwareDevices;
56use virt_support_x86emu::emulate::EmuTranslateError;
57use virt_support_x86emu::emulate::EmuTranslateResult;
58use virt_support_x86emu::emulate::EmulatorSupport;
59use virt_support_x86emu::emulate::TranslateGvaSupport;
60use virt_support_x86emu::emulate::TranslateMode;
61use virt_support_x86emu::emulate::emulate_translate_gva;
62use virt_support_x86emu::translate::TranslationRegisters;
63use vmcore::reference_time::ReferenceTimeSource;
64use x86defs::RFlags;
65use x86defs::SegmentRegister;
66use zerocopy::FromBytes;
67use zerocopy::FromZeros;
68use zerocopy::IntoBytes;
69
70mod snp;
71
72pub(crate) use snp::MshvSnpConfig;
73pub(super) use snp::SnpError;
74pub(crate) use snp::SnpLaunchState;
75pub(crate) use snp::SnpPartitionState;
76pub(crate) use snp::SnpVpState;
77pub(crate) use snp::acquire_snp_host_access;
78use snp::prepare_snp_config;
79use snp::snp_cpuid_overrides;
80use snp::snp_hv_cpuid_overrides;
81use snp::snp_start_vp_vmsa_gpa;
82
83pub(crate) enum MshvProtoPartitionIsolation {
84    None,
85    Snp {
86        config: Option<Box<MshvSnpConfig>>,
87        disable_cpuid_offload: bool,
88    },
89}
90
91impl virt::Hypervisor for LinuxMshv {
92    type ProtoPartition<'a> = MshvProtoPartition<'a>;
93    type Partition = MshvPartition;
94    type Error = Error;
95
96    fn platform_info(&self) -> virt::PlatformInfo {
97        virt::PlatformInfo {}
98    }
99
100    fn new_partition<'a>(
101        &mut self,
102        config: ProtoPartitionConfig<'a>,
103    ) -> Result<MshvProtoPartition<'a>, Self::Error> {
104        // An IGVM supplies SNP state that MSHV needs before partition build.
105        let igvm_snp_config = match &config.isolation {
106            virt::ProtoPartitionIsolation::None => None,
107            virt::ProtoPartitionIsolation::Snp(snp_config) => snp_config.as_deref(),
108            _ => return Err(ErrorInner::IsolationNotSupported.into()),
109        };
110        let isolation = config.isolation.isolation_type();
111        validate_snp_cpuid_offload_config(isolation, self.snp_disable_cpuid_offload)?;
112        let snp = isolation == virt::IsolationType::Snp;
113        let x2apic = matches!(
114            config.processor_topology.apic_mode(),
115            vm_topology::processor::x86::ApicMode::X2ApicSupported
116                | vm_topology::processor::x86::ApicMode::X2ApicEnabled
117        );
118        let create_args =
119            partition_create_args(snp, x2apic, config.processor_topology.smt_enabled());
120
121        let vmfd = create_vm_with_retry(&self.mshv, &create_args)?;
122
123        // Set synthetic processor features before initialization when the
124        // guest interface is configured. SNP partitions require the smaller
125        // early-property feature set accepted by the hypervisor.
126        if config.hv_config.is_some() || snp {
127            let synthetic_features = if snp {
128                snp_synthetic_features()
129            } else {
130                common_synthetic_features()
131                    .with_access_partition_reference_tsc(true)
132                    .with_access_guest_idle_reg(true)
133                    .with_access_frequency_regs(true)
134                    .with_enable_extended_gva_ranges_for_flush_virtual_address_list(true)
135            };
136
137            vmfd.set_partition_property(
138                HvPartitionPropertyCode::SyntheticProcFeatures.0,
139                u64::from(synthetic_features),
140            )
141            .map_err(|e| ErrorInner::SetPartitionProperty(e.into()))?;
142        }
143
144        vmfd.initialize()
145            .map_err(|e| ErrorInner::CreateVMInitFailed(e.into()))?;
146
147        if snp {
148            let snp_policy = igvm_snp_config.as_ref().map_or_else(
149                || {
150                    let policy = mshv_bindings::snp::get_default_snp_guest_policy();
151                    // SAFETY: This generated C union contains a valid u64 view.
152                    unsafe { policy.as_uint64 }
153                },
154                |config| config.policy,
155            );
156            let vmgexit_offloads = snp_vmgexit_offloads(self.snp_disable_cpuid_offload);
157            // SAFETY: This generated C union contains a valid u64 view.
158            let vmgexit_offloads = unsafe { vmgexit_offloads.as_uint64 };
159
160            for (code, value) in [
161                (HvPartitionPropertyCode::IsolationPolicy, snp_policy),
162                (
163                    HvPartitionPropertyCode::SevVmgexitOffloads,
164                    vmgexit_offloads,
165                ),
166                (
167                    HvPartitionPropertyCode::UnimplementedMsrAction,
168                    mshv_bindings::hv_unimplemented_msr_action_HV_UNIMPLEMENTED_MSR_ACTION_IGNORE_WRITE_READ_ZERO
169                        as u64,
170                ),
171                (HvPartitionPropertyCode::TimeFreeze, 1),
172            ] {
173                vmfd.set_partition_property(code.0, value)
174                    .map_err(|e| ErrorInner::SetPartitionProperty(e.into()))?;
175            }
176        }
177
178        // Tell the hypervisor how many VPs are in each socket.
179        vmfd.set_partition_property(
180            HvPartitionPropertyCode::ProcessorsPerSocket.0,
181            config.processor_topology.reserved_vps_per_socket() as u64,
182        )
183        .map_err(|e| ErrorInner::SetPartitionProperty(e.into()))?;
184
185        let snp_config = if let Some(snp_config) = igvm_snp_config {
186            let physical_address_width =
187                vmfd.get_partition_property(HvPartitionPropertyCode::PhysicalAddressWidth.0)
188                    .map_err(|e| ErrorInner::GetPartitionProperty(e.into()))? as u8;
189            Some(Box::new(prepare_snp_config(
190                snp_config,
191                physical_address_width,
192            )?))
193        } else {
194            None
195        };
196
197        let isolation = if snp {
198            MshvProtoPartitionIsolation::Snp {
199                config: snp_config,
200                disable_cpuid_offload: self.snp_disable_cpuid_offload,
201            }
202        } else {
203            MshvProtoPartitionIsolation::None
204        };
205        let mut proto = MshvProtoPartition::new(config, vmfd)?;
206        proto.isolation = isolation;
207        Ok(proto)
208    }
209}
210
211fn partition_create_args(
212    snp: bool,
213    x2apic: bool,
214    smt: bool,
215) -> mshv_bindings::mshv_create_partition_v2 {
216    let mut pt_flags =
217        1 << mshv_bindings::MSHV_PT_BIT_LAPIC | 1 << mshv_bindings::MSHV_PT_BIT_GPA_SUPER_PAGES;
218
219    if snp || x2apic {
220        pt_flags |= 1 << mshv_bindings::MSHV_PT_BIT_X2APIC;
221    }
222    if smt {
223        pt_flags |= 1 << mshv_bindings::MSHV_PT_BIT_SMT_ENABLED_GUEST;
224    }
225
226    mshv_bindings::mshv_create_partition_v2 {
227        pt_flags: pt_flags | 1 << mshv_bindings::MSHV_PT_BIT_CPU_AND_XSAVE_FEATURES,
228        pt_isolation: if snp {
229            mshv_bindings::MSHV_PT_ISOLATION_SNP as u64
230        } else {
231            mshv_bindings::MSHV_PT_ISOLATION_NONE as u64
232        },
233        pt_num_cpu_fbanks: mshv_bindings::MSHV_NUM_CPU_FEATURES_BANKS as u16,
234        pt_cpu_fbanks: [
235            !u64::from(supported_processor_features()),
236            !u64::from(supported_processor_features1()),
237        ],
238        pt_disabled_xsave: !u64::from(supported_xsave_features()),
239        ..Default::default()
240    }
241}
242
243fn snp_synthetic_features() -> hvdef::HvPartitionSyntheticProcessorFeatures {
244    hvdef::HvPartitionSyntheticProcessorFeatures::new()
245        .with_hypervisor_present(true)
246        .with_hv1(true)
247        .with_access_partition_reference_counter(true)
248        .with_access_synic_regs(true)
249        .with_access_synthetic_timer_regs(true)
250        // HACK: Do not advertise AccessPartitionReferenceTsc to the ACI SNP
251        // kernel yet. Physical Hyper-V and Cloud Hypervisor both advertise
252        // it, relying on the hypervisor/MSHV rather than userspace to service
253        // HV_X64_MSR_REFERENCE_TSC and its page. OpenVMM launch-imports GPA
254        // 0x3402000 normally; the ACI kernel then shares it as its retained
255        // Hyper-V `tsc_pg`. MSHV accepts the initial import but later reports
256        // that GPA as unaccepted when device I/O begins. Keeping ACI on the
257        // reference counter MSR makes virtio-blk write/readback work.
258        // Re-enable this after tracing and fixing MSHV's SNP PSC/host-access
259        // handling for the shared reference-TSC page.
260        .with_access_frequency_regs(true)
261        .with_access_intr_ctrl_regs(true)
262        .with_access_vp_index(true)
263        .with_access_hypercall_regs(true)
264        .with_access_guest_idle_reg(true)
265        .with_tb_flush_hypercalls(true)
266        .with_synthetic_cluster_ipi(true)
267        .with_direct_synthetic_timers(true)
268}
269
270fn snp_vmgexit_offloads(disable_cpuid: bool) -> mshv_bindings::hv_sev_vmgexit_offload {
271    let mut offloads = mshv_bindings::snp::get_default_vmgexit_offload_features();
272    if disable_cpuid {
273        // SAFETY: The generated accessors correspond to fields in the active
274        // bitfield member of this C union.
275        unsafe {
276            offloads.__bindgen_anon_1.set_nae_cpuid(0);
277            offloads.__bindgen_anon_1.set_msr_cpuid(0);
278        }
279    }
280    offloads
281}
282
283fn hv1_reference_tsc_page_supported(
284    hv1_enabled: bool,
285    isolation: virt::IsolationType,
286    advertised: bool,
287) -> bool {
288    // TODO: Reinvestigate advertising the reference TSC page for MSHV SNP
289    // when a newer guest kernel with fixed shared reference-TSC-page handling
290    // is available.
291    hv1_enabled && isolation != virt::IsolationType::Snp && advertised
292}
293
294fn validate_snp_cpuid_offload_config(
295    isolation: virt::IsolationType,
296    disabled: bool,
297) -> Result<(), Error> {
298    if disabled && isolation != virt::IsolationType::Snp {
299        return Err(ErrorInner::InvalidConfiguration(
300            "snp_disable_cpuid_offload requires SNP isolation",
301        )
302        .into());
303    }
304    Ok(())
305}
306
307impl MshvProtoPartition<'_> {
308    /// Build partition capabilities from partition properties instead of
309    /// CPUID.
310    fn caps_from_properties(&self) -> Result<virt::x86::X86PartitionCapabilities, Error> {
311        use virt::x86::X86PartitionCapabilities;
312        use virt::x86::XsaveCapabilities;
313        use x86defs::cpuid::Vendor;
314        use x86defs::xsave::XSAVE_VARIABLE_OFFSET;
315
316        let vendor_id = self
317            .vmfd
318            .get_partition_property(HvPartitionPropertyCode::ProcessorVendor.0)
319            .map_err(|e| ErrorInner::GetPartitionProperty(e.into()))?;
320
321        let vendor = match HvProcessorVendor(vendor_id as u32) {
322            HvProcessorVendor::AMD => Vendor::AMD,
323            HvProcessorVendor::INTEL => Vendor::INTEL,
324            HvProcessorVendor::HYGON => Vendor::HYGON,
325            v => return Err(ErrorInner::UnsupportedProcessorVendor(v).into()),
326        };
327
328        let xsave_states = self
329            .vmfd
330            .get_partition_property(HvPartitionPropertyCode::XsaveStates.0)
331            .map_err(|e| ErrorInner::GetPartitionProperty(e.into()))?;
332
333        let max_xsave_data_size = self
334            .vmfd
335            .get_partition_property(HvPartitionPropertyCode::MaxXsaveDataSize.0)
336            .map_err(|e| ErrorInner::GetPartitionProperty(e.into()))?;
337
338        let reset_rdx = if self.config.isolation.isolation_type() == virt::IsolationType::Snp {
339            0
340        } else {
341            let mut assoc = [HvRegisterAssoc::from((HvX64RegisterName::Rdx, 0u64))];
342            self.bsp
343                .get_hvdef_regs(&mut assoc)
344                .map_err(ErrorInner::Register)?;
345            assoc[0].value.as_u64()
346        };
347
348        let x2apic = matches!(
349            self.config.processor_topology.apic_mode(),
350            vm_topology::processor::x86::ApicMode::X2ApicSupported
351                | vm_topology::processor::x86::ApicMode::X2ApicEnabled
352        );
353        let x2apic_enabled = matches!(
354            self.config.processor_topology.apic_mode(),
355            vm_topology::processor::x86::ApicMode::X2ApicEnabled
356        );
357
358        let hv1 = self.config.hv_config.is_some();
359        Ok(X86PartitionCapabilities {
360            vendor,
361            hv1,
362            hv1_reference_tsc_page: hv1_reference_tsc_page_supported(
363                hv1,
364                self.config.isolation.isolation_type(),
365                true,
366            ),
367            xsave: XsaveCapabilities {
368                features: xsave_states,
369                supervisor_features: 0,
370                standard_len: XSAVE_VARIABLE_OFFSET as u32,
371                compact_len: max_xsave_data_size as u32,
372                feature_info: [Default::default(); 63],
373            },
374            x2apic,
375            x2apic_enabled,
376            reset_rdx,
377            cet: false,
378            cet_ss: false,
379            sgx: false,
380            tsc_aux: false,
381            vtom: None,
382            physical_address_width: self
383                .vmfd
384                .get_partition_property(HvPartitionPropertyCode::PhysicalAddressWidth.0)
385                .map_err(|e| ErrorInner::GetPartitionProperty(e.into()))?
386                as u8,
387            snp_c_bit: None,
388            can_freeze_time: false,
389            xsaves_state_bv_broken: false,
390            dr6_tsx_broken: false,
391            nxe_forced_on: false,
392            nested_virt: false,
393        })
394    }
395
396    fn max_physical_address_size(&self) -> u8 {
397        self.vmfd
398            .get_partition_property(HvPartitionPropertyCode::PhysicalAddressWidth.0)
399            .expect("failed to get physical address width") as u8
400    }
401}
402
403impl ProtoPartition for MshvProtoPartition<'_> {
404    type Partition = MshvPartition;
405    type ProcessorBinder = MshvProcessorBinder;
406    type Error = Error;
407
408    fn max_physical_address_size(&self) -> u8 {
409        self.max_physical_address_size()
410    }
411
412    fn build(
413        self,
414        config: PartitionConfig<'_>,
415    ) -> Result<(Self::Partition, Vec<Self::ProcessorBinder>), Self::Error> {
416        let snp_config = match &self.isolation {
417            MshvProtoPartitionIsolation::None => None,
418            MshvProtoPartitionIsolation::Snp { config, .. } => config.as_deref(),
419        };
420        if let Some(snp_config) = snp_config {
421            // The IGVM VMSA has separate stable userspace backing. It cannot
422            // overlap the normal RAM mapping because that would register two
423            // different host mappings for the same GPA with MSHV.
424            let vmsa_range =
425                MemoryRange::new(snp_config.vmsa_gpa..snp_config.vmsa_gpa + hvdef::HV_PAGE_SIZE);
426            if config
427                .mem_layout
428                .ram()
429                .iter()
430                .any(|range| range.range.overlaps(&vmsa_range))
431            {
432                return Err(ErrorInner::SnpVmsaOverlapsRam.into());
433            }
434        }
435        let mut cpuid = config.cpuid.to_vec();
436        if matches!(&self.isolation, MshvProtoPartitionIsolation::Snp { .. }) {
437            let expose_hypervisor = self.config.hv_config.is_some();
438            cpuid.extend(snp_cpuid_overrides(expose_hypervisor));
439            if expose_hypervisor {
440                let native_max_leaf = self
441                    .bsp
442                    .get_cpuid_values(hvdef::HV_CPUID_FUNCTION_HV_VENDOR_AND_MAX_FUNCTION, 0, 0, 0)
443                    .map(|values| values[0])
444                    .unwrap_or(hvdef::HV_CPUID_FUNCTION_MS_HV_ISOLATION_CONFIGURATION);
445                cpuid.extend(snp_hv_cpuid_overrides(native_max_leaf));
446            }
447        }
448        let cpuid = virt::CpuidLeafSet::new(cpuid);
449
450        // Apply CPUID overrides partition-wide.
451        for leaf in cpuid.leaves().iter() {
452            let input = hvdef::hypercall::RegisterInterceptResultCpuid {
453                partition_id: 0,
454                vp_index: hvdef::HV_ANY_VP,
455                intercept_type: hvdef::hypercall::HvInterceptType::HvInterceptTypeX64Cpuid,
456                parameters: hvdef::hypercall::HvRegisterX64CpuidResultParameters {
457                    input: hvdef::hypercall::HvRegisterX64CpuidResultParametersInput {
458                        eax: leaf.function,
459                        ecx: leaf.index.unwrap_or(0),
460                        subleaf_specific: u8::from(leaf.index.is_some()),
461                        always_override: 1,
462                        padding: 0,
463                    },
464                    result: hvdef::hypercall::HvRegisterX64CpuidResultParametersOutput {
465                        eax: leaf.result[0],
466                        eax_mask: leaf.mask[0],
467                        ebx: leaf.result[1],
468                        ebx_mask: leaf.mask[1],
469                        ecx: leaf.result[2],
470                        ecx_mask: leaf.mask[2],
471                        edx: leaf.result[3],
472                        edx_mask: leaf.mask[3],
473                    },
474                },
475                _reserved: 0,
476            };
477            let mut args = mshv_bindings::mshv_root_hvcall {
478                code: hvdef::HypercallCode::HvCallRegisterInterceptResult.0,
479                in_sz: size_of_val(&input) as u16,
480                in_ptr: std::ptr::addr_of!(input) as u64,
481                ..Default::default()
482            };
483            self.vmfd
484                .hvcall(&mut args)
485                .map_err(|e| ErrorInner::RegisterCpuid(e.into()))?;
486        }
487
488        let caps = {
489            let mut cpuid_error = None;
490            let cpuid_caps = virt::PartitionCapabilities::from_cpuid(
491                self.config.processor_topology,
492                &mut |function, index| {
493                    self.bsp
494                        .get_cpuid_values(function, index, 0, 0)
495                        .unwrap_or_else(|err| {
496                            cpuid_error.get_or_insert(err);
497                            [0; 4]
498                        })
499                },
500            );
501            let mut caps = match (cpuid_caps, cpuid_error) {
502                (Ok(caps), None) => caps,
503                (result, error) => {
504                    tracing::warn!(
505                        error = error.as_ref().map(|err| err as &dyn std::error::Error),
506                        capabilities_error = result
507                            .err()
508                            .as_ref()
509                            .map(|err| err as &dyn std::error::Error),
510                        "failed to query CPUID capabilities, falling back to partition properties; some features may be unavailable"
511                    );
512                    self.caps_from_properties()?
513                }
514            };
515            caps.hv1 = self.config.hv_config.is_some();
516            caps.hv1_reference_tsc_page = hv1_reference_tsc_page_supported(
517                caps.hv1,
518                self.config.isolation.isolation_type(),
519                caps.hv1_reference_tsc_page,
520            );
521            caps.xsaves_state_bv_broken = true;
522            caps.can_freeze_time = true;
523            caps
524        };
525
526        let apic_id_map = self
527            .config
528            .processor_topology
529            .vps_arch()
530            .map(|vp| vp.apic_id)
531            .collect();
532
533        let isolation = match self.isolation {
534            MshvProtoPartitionIsolation::None => MshvIsolationState::None,
535            MshvProtoPartitionIsolation::Snp {
536                config,
537                disable_cpuid_offload,
538            } => MshvIsolationState::Snp(SnpPartitionState::with_config(
539                disable_cpuid_offload,
540                config,
541            )),
542        };
543        let time_frozen = isolation.is_isolated();
544        let inner = Arc::new(MshvPartitionInner {
545            vmfd: self.vmfd,
546            bsp_vcpufd: self.bsp,
547            memory: Default::default(),
548            gm: config.guest_memory.clone(),
549            mem_layout: config.mem_layout.clone(),
550            vps: self.vps,
551            irq_routes: Default::default(),
552            gsi_states: Mutex::new(Box::new(
553                [crate::irqfd::GsiState::Unallocated; crate::irqfd::NUM_GSIS],
554            )),
555            caps,
556            synic_ports: Default::default(),
557            software_devices: ApicSoftwareDevices::new(apic_id_map),
558            isolation,
559            // SNP partition creation set TimeFreeze=1 before this object was built.
560            time_frozen: Mutex::new(time_frozen),
561        });
562        inner.add_snp_vmsa_mapping()?;
563
564        let partition = MshvPartition {
565            synic_ports: Arc::new(virt::synic::SynicPorts::new(inner.clone())),
566            inner,
567        };
568
569        let vps = self
570            .config
571            .processor_topology
572            .vps()
573            .map(|vp| MshvProcessorBinder {
574                partition: partition.inner.clone(),
575                vpindex: vp.vp_index,
576                vcpufd: None,
577                snp: None,
578            })
579            .collect();
580
581        Ok((partition, vps))
582    }
583}
584
585impl virt::Partition for MshvPartition {
586    fn initial_vp_state_source(&self) -> virt::InitialVpStateSource {
587        self.inner.isolation.initial_vp_state_source()
588    }
589
590    fn supports_initial_page_acceptance(
591        &self,
592    ) -> Option<&dyn virt::AcceptInitialPages<Error = Error>> {
593        self.inner.isolation.snp().is_some().then_some(self)
594    }
595
596    fn supports_reset(&self) -> Option<&dyn virt::ResetPartition<Error = Error>> {
597        // TODO: Support resetting SNP launch state and rebuilding the protected
598        // guest before advertising reset support.
599        self.inner.isolation.snp().is_none().then_some(self)
600    }
601
602    fn doorbell_registration(
603        self: &Arc<Self>,
604        _minimum_vtl: Vtl,
605    ) -> Option<Arc<dyn DoorbellRegistration>> {
606        Some(self.clone())
607    }
608
609    fn caps(&self) -> &virt::PartitionCapabilities {
610        &self.inner.caps
611    }
612
613    fn request_msi(&self, _vtl: Vtl, request: MsiRequest) {
614        self.inner.request_msi(request)
615    }
616
617    fn as_signal_msi(&self, _vtl: Vtl) -> Option<Arc<dyn SignalMsi>> {
618        Some(self.inner.clone())
619    }
620
621    fn irqfd(&self) -> Option<Arc<dyn virt::irqfd::IrqFd>> {
622        Some(Arc::new(crate::irqfd::MshvIrqFd::new(self.inner.clone())))
623    }
624
625    fn request_yield(&self, vp_index: VpIndex) {
626        let vp = self.inner.vp(vp_index);
627        if vp.needs_yield.request_yield() {
628            let thread = vp.thread.read();
629            if let Some(thread) = *thread {
630                if thread != Pthread::current() {
631                    thread
632                        .signal(libc::SIGRTMIN())
633                        .expect("thread cancel signal failed");
634                }
635            }
636        }
637    }
638}
639
640impl virt::X86Partition for MshvPartition {
641    fn ioapic_routing(&self) -> Arc<dyn virt::irqcon::IoApicRouting> {
642        self.inner.clone()
643    }
644
645    fn pulse_lint(&self, vp_index: VpIndex, vtl: Vtl, lint: u8) {
646        // TODO: Implement LINT injection for non-isolated MSHV partitions.
647        //
648        // The legacy PIC/PIT are temporarily attached for direct-boot TSC
649        // calibration, but MSHV isolated VPs cannot receive PIC ExtINT through
650        // LINT0. The guest must route runtime interrupts through the IOAPIC/MSI
651        // path; PIC-dependent isolated guests are not supported.
652        tracelimit::warn_ratelimited!(?vp_index, ?vtl, lint, "ignored lint pulse");
653    }
654}
655
656impl virt::ResetPartition for MshvPartition {
657    type Error = Error;
658
659    fn reset(&self) -> Result<(), Error> {
660        use virt::x86::vm::AccessVmState;
661
662        for irq in 0..virt::irqcon::IRQ_LINES as u8 {
663            self.inner.irq_routes.set_irq_route(irq, None);
664        }
665
666        self.inner.freeze_time()?;
667
668        let bsp_vp_info = &self.inner.vps[0].vp_info;
669        self.access_state(Vtl::Vtl0)
670            .reset_all(bsp_vp_info)
671            .map_err(|e| ErrorInner::ResetState(Box::new(e)))?;
672
673        Ok(())
674    }
675}
676
677impl Hv1 for MshvPartition {
678    type Error = Error;
679    type Device = ApicSoftwareDevice;
680
681    fn reference_time_source(&self) -> Option<ReferenceTimeSource> {
682        Some(ReferenceTimeSource::from(self.inner.clone() as Arc<_>))
683    }
684
685    fn new_virtual_device(
686        &self,
687    ) -> Option<&dyn virt::DeviceBuilder<Device = Self::Device, Error = Self::Error>> {
688        Some(self)
689    }
690
691    fn synic(&self) -> anyhow::Result<Arc<dyn vmcore::synic::SynicPortAccess>> {
692        Ok(self.synic_ports.clone())
693    }
694}
695
696impl virt::DeviceBuilder for MshvPartition {
697    fn build(&self, _vtl: Vtl, device_id: u64) -> Result<Self::Device, Self::Error> {
698        Ok(self
699            .inner
700            .software_devices
701            .new_device(self.inner.clone(), device_id)
702            .map_err(ErrorInner::NewDevice)?)
703    }
704}
705
706impl MshvPartitionInner {
707    fn request_msi(&self, request: MsiRequest) {
708        let (address, data) = request.as_x86();
709        let control = request.hv_x86_interrupt_control();
710        let mshv_req = InterruptRequest {
711            interrupt_type: control.interrupt_type().0,
712            apic_id: address.virt_destination().into(),
713            vector: data.vector().into(),
714            level_triggered: control.x86_level_triggered(),
715            logical_destination_mode: control.x86_logical_destination_mode(),
716            long_mode: false,
717        };
718
719        if let Err(err) = self.vmfd.request_virtual_interrupt(&mshv_req) {
720            tracelimit::warn_ratelimited!(
721                address = request.address,
722                data = request.data,
723                error = &err as &dyn std::error::Error,
724                "failed to request msi"
725            );
726        }
727    }
728}
729
730impl SignalMsi for MshvPartitionInner {
731    fn signal_msi(&self, _devid: Option<u32>, address: u64, data: u32) {
732        self.request_msi(MsiRequest { address, data });
733    }
734}
735
736impl virt::irqcon::IoApicRouting for MshvPartitionInner {
737    fn set_irq_route(&self, irq: u8, request: Option<MsiRequest>) {
738        self.irq_routes.set_irq_route(irq, request)
739    }
740
741    fn assert_irq(&self, irq: u8) {
742        self.irq_routes
743            .assert_irq(irq, |request| self.request_msi(request))
744    }
745}
746
747// ---------------------------------------------------------------------------
748// Processor binding and run loop
749// ---------------------------------------------------------------------------
750
751impl virt::BindProcessor for MshvProcessorBinder {
752    type Processor<'a>
753        = MshvProcessor<'a>
754    where
755        Self: 'a;
756    type Error = Error;
757
758    fn bind(&mut self) -> Result<Self::Processor<'_>, Self::Error> {
759        let inner = &self.partition.vps[self.vpindex.index() as usize];
760
761        let vcpufd = if self.vpindex.is_bsp() {
762            &self.partition.bsp_vcpufd
763        } else {
764            if self.vcpufd.is_none() {
765                let vcpufd = self
766                    .partition
767                    .vmfd
768                    .create_vcpu(u8::try_from(self.vpindex.index()).expect("validated above"))
769                    .map_err(|e| ErrorInner::CreateVcpu(e.into()))?;
770                self.vcpufd = Some(vcpufd);
771            }
772            self.vcpufd.as_ref().unwrap()
773        };
774
775        let reg_page_ptr = if self.partition.isolation.snp().is_some() {
776            None
777        } else {
778            Some(
779                vcpufd
780                    .get_vp_reg_page()
781                    .ok_or(ErrorInner::MissingRegisterPage)?
782                    .0
783                    .cast::<HvX64RegisterPage>(),
784            )
785        };
786
787        let runner = MshvVpRunner {
788            vcpufd,
789            reg_page: reg_page_ptr,
790            ghcb_page: if self.partition.isolation.snp().is_some() {
791                if self.snp.is_none() {
792                    self.snp = Some(SnpVpState::new(vcpufd)?);
793                }
794                self.snp.as_mut().map(SnpVpState::page_ptr)
795            } else {
796                None
797            },
798        };
799
800        let this = MshvProcessor {
801            partition: &self.partition,
802            inner,
803            vpindex: self.vpindex,
804            runner,
805            deliverability_notifications: HvDeliverabilityNotificationsRegister::new(),
806        };
807
808        if this.partition.isolation.snp().is_none() {
809            // Set the APIC state.
810            let apic_base =
811                virt::vp::Apic::at_reset(&this.partition.caps, &this.inner.vp_info).apic_base;
812
813            let regs = &[
814                HvRegisterAssoc::from((
815                    HvX64RegisterName::InitialApicId,
816                    u64::from(inner.vp_info.apic_id),
817                )),
818                HvRegisterAssoc::from((HvX64RegisterName::ApicBase, apic_base)),
819                HvRegisterAssoc::from((
820                    HvX64RegisterName::ApicId,
821                    u64::from(inner.vp_info.apic_id),
822                )),
823            ];
824
825            let reg_count = if this.partition.caps.x2apic { 2 } else { 3 };
826
827            vcpufd
828                .set_hvdef_regs(&regs[..reg_count])
829                .map_err(ErrorInner::Register)?;
830        }
831
832        Ok(this)
833    }
834}
835
836impl MshvProcessor<'_> {
837    async fn emulate(
838        &mut self,
839        message: &HvMessage,
840        devices: &impl CpuIo,
841        interruption_pending: bool,
842    ) -> Result<(), VpHaltReason> {
843        let emu_mem = virt_support_x86emu::emulate::EmulatorMemoryAccess {
844            gm: &self.partition.gm,
845            kx_gm: &self.partition.gm,
846            ux_gm: &self.partition.gm,
847        };
848
849        let mut support = MshvEmulationState {
850            partition: self.partition,
851            vcpufd: self.runner.vcpufd,
852            reg_page: self.runner.reg_page(),
853            vp_index: self.vpindex,
854            message,
855            interruption_pending,
856        };
857        virt_support_x86emu::emulate::emulate(&mut support, &emu_mem, devices).await
858    }
859
860    pub(crate) async fn handle_exit(
861        &mut self,
862        exit: &HvMessage,
863        dev: &impl CpuIo,
864    ) -> Result<(), VpHaltReason> {
865        if self.partition.isolation.snp().is_some() {
866            return self.handle_snp_exit(exit, dev).await;
867        }
868
869        match exit.header.typ {
870            HvMessageType::HvMessageTypeUnrecoverableException => {
871                return Err(VpHaltReason::TripleFault { vtl: Vtl::Vtl0 });
872            }
873            HvMessageType::HvMessageTypeX64IoPortIntercept => {
874                self.handle_io_port_intercept(exit, dev).await?;
875            }
876            HvMessageType::HvMessageTypeUnmappedGpa | HvMessageType::HvMessageTypeGpaIntercept => {
877                self.handle_mmio_intercept(exit, dev).await?;
878            }
879            HvMessageType::HvMessageTypeSynicSintDeliverable => {
880                tracing::trace!("SYNIC_SINT_DELIVERABLE");
881                let info = exit.as_message::<hvdef::HvX64SynicSintDeliverableMessage>();
882                self.handle_sint_deliverable(info.deliverable_sints);
883            }
884            HvMessageType::HvMessageTypeHypercallIntercept => {
885                tracing::trace!("HYPERCALL_INTERCEPT");
886                self.handle_hypercall_intercept(exit)?;
887            }
888            HvMessageType::HvMessageTypeX64ApicEoi => {
889                let msg = exit.as_message::<hvdef::HvX64ApicEoiMessage>();
890                dev.handle_eoi(msg.interrupt_vector);
891            }
892            exit_type => {
893                panic!("Unhandled vcpu exit code {exit_type:?}");
894            }
895        }
896        Ok(())
897    }
898
899    async fn handle_io_port_intercept(
900        &mut self,
901        message: &HvMessage,
902        devices: &impl CpuIo,
903    ) -> Result<(), VpHaltReason> {
904        let info = message.as_message::<hvdef::HvX64IoPortInterceptMessage>();
905        let access_info = info.access_info;
906
907        if access_info.string_op() || access_info.rep_prefix() {
908            let interruption_pending = info.header.execution_state.interruption_pending();
909            self.emulate(message, devices, interruption_pending).await?
910        } else {
911            let mut ret_rax = info.rax;
912            virt_support_x86emu::emulate::emulate_io(
913                self.vpindex,
914                info.header.intercept_access_type == hvdef::HvInterceptAccessType::WRITE,
915                info.port_number,
916                &mut ret_rax,
917                access_info.access_size(),
918                devices,
919            )
920            .await;
921
922            let insn_len = info.header.instruction_len() as u64;
923
924            let rp = self.runner.reg_page();
925            rp.gp_registers[x86emu::Gp::RAX as usize] = ret_rax;
926            rp.rip = info.header.rip + insn_len;
927            rp.dirty.set_general_purpose(true);
928            rp.dirty.set_instruction_pointer(true);
929        }
930
931        Ok(())
932    }
933
934    async fn handle_mmio_intercept(
935        &mut self,
936        message: &HvMessage,
937        devices: &impl CpuIo,
938    ) -> Result<(), VpHaltReason> {
939        let info = message.as_message::<hvdef::HvX64MemoryInterceptMessage>();
940        let interruption_pending = info.header.execution_state.interruption_pending();
941        self.emulate(message, devices, interruption_pending).await
942    }
943
944    fn handle_hypercall_intercept(&mut self, message: &HvMessage) -> Result<(), VpHaltReason> {
945        let info = message.as_message::<hvdef::HvX64HypercallInterceptMessage>();
946        let is_64bit =
947            info.header.execution_state.cr0_pe() && info.header.execution_state.efer_lma();
948        let mut handler = MshvHypercallHandler {
949            partition: self.partition,
950            reg_page: self.runner.reg_page(),
951            caller_vp: self.vpindex,
952            isolated: false,
953            modified_gp: 0,
954            modified_xmm: 0,
955        };
956
957        MshvHypercallHandler::DISPATCHER.dispatch(
958            &self.partition.gm,
959            X64RegisterIo::new(&mut handler, is_64bit, true),
960        );
961        Ok(())
962    }
963}
964
965// ---------------------------------------------------------------------------
966// x86 emulation support
967// ---------------------------------------------------------------------------
968
969struct MshvEmulationState<'a> {
970    partition: &'a MshvPartitionInner,
971    vcpufd: &'a VcpuFd,
972    reg_page: &'a mut HvX64RegisterPage,
973    vp_index: VpIndex,
974    message: &'a HvMessage,
975    interruption_pending: bool,
976}
977
978impl EmulatorSupport for MshvEmulationState<'_> {
979    fn vp_index(&self) -> VpIndex {
980        self.vp_index
981    }
982
983    fn vendor(&self) -> x86defs::cpuid::Vendor {
984        self.partition.caps.vendor
985    }
986
987    fn gp(&mut self, reg: x86emu::Gp) -> u64 {
988        self.reg_page.gp_registers[reg as usize]
989    }
990
991    fn set_gp(&mut self, reg: x86emu::Gp, v: u64) {
992        self.reg_page.gp_registers[reg as usize] = v;
993        self.reg_page.dirty.set_general_purpose(true);
994    }
995
996    fn rip(&mut self) -> u64 {
997        self.reg_page.rip
998    }
999
1000    fn set_rip(&mut self, v: u64) {
1001        self.reg_page.rip = v;
1002        self.reg_page.dirty.set_instruction_pointer(true);
1003    }
1004
1005    fn segment(&mut self, reg: x86emu::Segment) -> SegmentRegister {
1006        virt::x86::SegmentRegister::from(self.reg_page.segment[reg as usize]).into()
1007    }
1008
1009    fn efer(&mut self) -> u64 {
1010        self.reg_page.efer
1011    }
1012
1013    fn cr0(&mut self) -> u64 {
1014        self.reg_page.cr0
1015    }
1016
1017    fn rflags(&mut self) -> RFlags {
1018        RFlags::from(self.reg_page.rflags)
1019    }
1020
1021    fn set_rflags(&mut self, v: RFlags) {
1022        self.reg_page.rflags = v.into();
1023        self.reg_page.dirty.set_flags(true);
1024    }
1025
1026    fn xmm(&mut self, reg: usize) -> u128 {
1027        assert!(reg < 16);
1028        if reg < 6 {
1029            self.reg_page.xmm[reg]
1030        } else {
1031            let name = HvX64RegisterName(HvX64RegisterName::Xmm0.0 + reg as u32);
1032            let mut assoc = [HvRegisterAssoc::from((name, 0u128))];
1033            let _ = self.vcpufd.get_hvdef_regs(&mut assoc);
1034            assoc[0].value.as_u128()
1035        }
1036    }
1037
1038    fn set_xmm(&mut self, reg: usize, value: u128) {
1039        assert!(reg < 16);
1040        if reg < 6 {
1041            self.reg_page.xmm[reg] = value;
1042            self.reg_page.dirty.set_xmm(true);
1043        } else {
1044            let name = HvX64RegisterName(HvX64RegisterName::Xmm0.0 + reg as u32);
1045            let assoc = [HvRegisterAssoc::from((name, value))];
1046            self.vcpufd.set_hvdef_regs(&assoc).unwrap();
1047        }
1048    }
1049
1050    fn flush(&mut self) {}
1051
1052    fn instruction_bytes(&self) -> &[u8] {
1053        match self.message.header.typ {
1054            HvMessageType::HvMessageTypeGpaIntercept
1055            | HvMessageType::HvMessageTypeUnmappedGpa
1056            | HvMessageType::HvMessageTypeUnacceptedGpa => {
1057                let info = self
1058                    .message
1059                    .as_message::<hvdef::HvX64MemoryInterceptMessage>();
1060                &info.instruction_bytes[..info.instruction_byte_count as usize]
1061            }
1062            HvMessageType::HvMessageTypeX64IoPortIntercept => {
1063                let info = self
1064                    .message
1065                    .as_message::<hvdef::HvX64IoPortInterceptMessage>();
1066                &info.instruction_bytes[..info.instruction_byte_count as usize]
1067            }
1068            _ => unreachable!(),
1069        }
1070    }
1071
1072    fn physical_address(&self) -> Option<u64> {
1073        match self.message.header.typ {
1074            HvMessageType::HvMessageTypeGpaIntercept
1075            | HvMessageType::HvMessageTypeUnmappedGpa
1076            | HvMessageType::HvMessageTypeUnacceptedGpa => {
1077                let info = self
1078                    .message
1079                    .as_message::<hvdef::HvX64MemoryInterceptMessage>();
1080                Some(info.guest_physical_address)
1081            }
1082            _ => None,
1083        }
1084    }
1085
1086    fn initial_gva_translation(
1087        &mut self,
1088    ) -> Option<virt_support_x86emu::emulate::InitialTranslation> {
1089        match self.message.header.typ {
1090            HvMessageType::HvMessageTypeGpaIntercept
1091            | HvMessageType::HvMessageTypeUnmappedGpa
1092            | HvMessageType::HvMessageTypeUnacceptedGpa => {}
1093            _ => return None,
1094        }
1095
1096        let message = self
1097            .message
1098            .as_message::<hvdef::HvX64MemoryInterceptMessage>();
1099
1100        if !message.memory_access_info.gva_gpa_valid() {
1101            return None;
1102        }
1103
1104        if let Ok(translate_mode) = TranslateMode::try_from(message.header.intercept_access_type) {
1105            Some(virt_support_x86emu::emulate::InitialTranslation {
1106                gva: message.guest_virtual_address,
1107                gpa: message.guest_physical_address,
1108                translate_mode,
1109            })
1110        } else {
1111            None
1112        }
1113    }
1114
1115    fn interruption_pending(&self) -> bool {
1116        self.interruption_pending
1117    }
1118
1119    fn check_vtl_access(
1120        &mut self,
1121        _gpa: u64,
1122        _mode: TranslateMode,
1123    ) -> Result<(), virt_support_x86emu::emulate::EmuCheckVtlAccessError> {
1124        Ok(())
1125    }
1126
1127    fn translate_gva(
1128        &mut self,
1129        gva: u64,
1130        mode: TranslateMode,
1131    ) -> Result<EmuTranslateResult, EmuTranslateError> {
1132        emulate_translate_gva(self, gva, mode)
1133    }
1134
1135    fn inject_pending_event(&mut self, event_info: hvdef::HvX64PendingEvent) {
1136        self.vcpufd
1137            .set_hvdef_regs(&[
1138                HvRegisterAssoc::from((
1139                    HvX64RegisterName::PendingEvent0,
1140                    u128::from(event_info.reg_0),
1141                )),
1142                HvRegisterAssoc::from((
1143                    HvX64RegisterName::PendingEvent1,
1144                    u128::from(event_info.reg_1),
1145                )),
1146            ])
1147            .unwrap();
1148    }
1149
1150    fn is_gpa_mapped(&self, gpa: u64, _write: bool) -> bool {
1151        self.partition
1152            .mem_layout
1153            .ram()
1154            .iter()
1155            .any(|r| r.range.contains_addr(gpa))
1156    }
1157
1158    fn lapic_base_address(&self) -> Option<u64> {
1159        None
1160    }
1161
1162    fn lapic_read(&mut self, _address: u64, _data: &mut [u8]) {
1163        unreachable!()
1164    }
1165
1166    fn lapic_write(&mut self, _address: u64, _data: &[u8]) {
1167        unreachable!()
1168    }
1169}
1170
1171impl TranslateGvaSupport for MshvEmulationState<'_> {
1172    fn guest_memory(&self) -> &GuestMemory {
1173        &self.partition.gm
1174    }
1175
1176    fn acquire_tlb_lock(&mut self) {}
1177
1178    fn registers(&mut self) -> TranslationRegisters {
1179        TranslationRegisters {
1180            cr0: self.reg_page.cr0,
1181            cr4: self.reg_page.cr4,
1182            efer: self.reg_page.efer,
1183            cr3: self.reg_page.cr3,
1184            rflags: self.reg_page.rflags,
1185            ss: virt::x86::SegmentRegister::from(
1186                self.reg_page.segment[x86emu::Segment::SS as usize],
1187            )
1188            .into(),
1189            encryption_mode: virt_support_x86emu::translate::EncryptionMode::None,
1190        }
1191    }
1192}
1193
1194// ---------------------------------------------------------------------------
1195// Hypercall handler
1196// ---------------------------------------------------------------------------
1197
1198impl hv1_hypercall::X64RegisterState for MshvHypercallHandler<'_> {
1199    fn rip(&mut self) -> u64 {
1200        self.reg_page.rip
1201    }
1202
1203    fn set_rip(&mut self, rip: u64) {
1204        self.reg_page.rip = rip;
1205        self.reg_page.dirty.set_instruction_pointer(true);
1206    }
1207
1208    fn gp(&mut self, n: hv1_hypercall::X64HypercallRegister) -> u64 {
1209        self.reg_page.gp_registers[n as usize]
1210    }
1211
1212    fn set_gp(&mut self, n: hv1_hypercall::X64HypercallRegister, value: u64) {
1213        let index = n as usize;
1214        self.reg_page.gp_registers[index] = value;
1215        if self.isolated {
1216            self.modified_gp |= 1 << index;
1217        } else {
1218            self.reg_page.dirty.set_general_purpose(true);
1219        }
1220    }
1221
1222    fn xmm(&mut self, n: usize) -> u128 {
1223        self.reg_page.xmm[n]
1224    }
1225
1226    fn set_xmm(&mut self, n: usize, value: u128) {
1227        self.reg_page.xmm[n] = value;
1228        if self.isolated {
1229            self.modified_xmm |= 1 << n;
1230        } else {
1231            self.reg_page.dirty.set_xmm(true);
1232        }
1233    }
1234}
1235
1236pub(crate) struct MshvHypercallHandler<'a> {
1237    pub(crate) partition: &'a MshvPartitionInner,
1238    pub(crate) reg_page: &'a mut HvX64RegisterPage,
1239    pub(crate) caller_vp: VpIndex,
1240    isolated: bool,
1241    modified_gp: u16,
1242    modified_xmm: u8,
1243}
1244
1245impl MshvHypercallHandler<'_> {
1246    const DISPATCHER: hv1_hypercall::Dispatcher<Self> = hv1_hypercall::dispatcher!(
1247        Self,
1248        [
1249            hv1_hypercall::HvPostMessage,
1250            hv1_hypercall::HvSignalEvent,
1251            hv1_hypercall::HvRetargetDeviceInterrupt,
1252            hv1_hypercall::HvX64StartVirtualProcessor,
1253        ],
1254    );
1255}
1256
1257impl hv1_hypercall::StartVirtualProcessor<hvdef::hypercall::InitialVpContextX64>
1258    for MshvHypercallHandler<'_>
1259{
1260    fn start_virtual_processor(
1261        &mut self,
1262        partition_id: u64,
1263        target_vp: u32,
1264        target_vtl: Vtl,
1265        vp_context: &hvdef::hypercall::InitialVpContextX64,
1266    ) -> hvdef::HvResult<()> {
1267        if self.partition.isolation.snp().is_none() || partition_id != hvdef::HV_PARTITION_ID_SELF {
1268            return Err(hvdef::HvError::InvalidPartitionId);
1269        }
1270        if target_vtl != Vtl::Vtl0 {
1271            return Err(hvdef::HvError::InvalidParameter);
1272        }
1273
1274        let target_vp = VpIndex::new(target_vp);
1275        if target_vp.is_bsp()
1276            || target_vp == self.caller_vp
1277            || target_vp.index() as usize >= self.partition.vps.len()
1278        {
1279            return Err(hvdef::HvError::InvalidVpIndex);
1280        }
1281
1282        let vmsa_gpa = snp_start_vp_vmsa_gpa(vp_context).ok_or(hvdef::HvError::InvalidParameter)?;
1283        let vmsa_end = vmsa_gpa
1284            .checked_add(hvdef::HV_PAGE_SIZE)
1285            .ok_or(hvdef::HvError::InvalidParameter)?;
1286        if !self
1287            .partition
1288            .mem_layout
1289            .ram()
1290            .iter()
1291            .any(|range| range.range.contains(&MemoryRange::new(vmsa_gpa..vmsa_end)))
1292        {
1293            return Err(hvdef::HvError::InvalidParameter);
1294        }
1295
1296        let request = mshv_bindings::mshv_sev_snp_ap_create {
1297            vp_id: u64::from(target_vp.index()),
1298            vmsa_gpa,
1299        };
1300        self.partition
1301            .vmfd
1302            .sev_snp_ap_create(&request)
1303            .map_err(|err| {
1304                tracelimit::error_ratelimited!(
1305                    error = &err as &dyn std::error::Error,
1306                    target_vp = target_vp.index(),
1307                    vmsa_gpa,
1308                    "failed to handle SNP StartVirtualProcessor"
1309                );
1310                hvdef::HvError::InvalidVpState
1311            })
1312    }
1313}
1314
1315impl hv1_hypercall::RetargetDeviceInterrupt for MshvHypercallHandler<'_> {
1316    fn retarget_interrupt(
1317        &mut self,
1318        device_id: u64,
1319        address: u64,
1320        data: u32,
1321        params: hv1_hypercall::HvInterruptParameters<'_>,
1322    ) -> hvdef::HvResult<()> {
1323        let target_processors = Vec::from_iter(params.target_processors);
1324        let vpci_params = vmcore::vpci_msi::VpciInterruptParameters {
1325            vector: params.vector,
1326            multicast: params.multicast,
1327            target_processors: &target_processors,
1328        };
1329
1330        self.partition
1331            .software_devices
1332            .retarget_interrupt(device_id, address, data, &vpci_params)
1333    }
1334}
1335
1336// ---------------------------------------------------------------------------
1337// CPU feature lists
1338// ---------------------------------------------------------------------------
1339
1340/// Processor features (bank 0) that we support exposing to guests.
1341fn supported_processor_features() -> hvdef::HvX64PartitionProcessorFeatures {
1342    hvdef::HvX64PartitionProcessorFeatures::new()
1343        .with_sse3_support(true)
1344        .with_lahf_sahf_support(true)
1345        .with_ssse3_support(true)
1346        .with_sse4_1_support(true)
1347        .with_sse4_2_support(true)
1348        .with_sse4a_support(true)
1349        .with_xop_support(true)
1350        .with_pop_cnt_support(true)
1351        .with_cmpxchg16b_support(true)
1352        .with_altmovcr8_support(true)
1353        .with_lzcnt_support(true)
1354        .with_mis_align_sse_support(true)
1355        .with_mmx_ext_support(true)
1356        .with_amd3d_now_support(true)
1357        .with_extended_amd3d_now_support(true)
1358        .with_page_1gb_support(true)
1359        .with_aes_support(true)
1360        .with_pclmulqdq_support(true)
1361        .with_pcid_support(true)
1362        .with_fma4_support(true)
1363        .with_f16c_support(true)
1364        .with_rd_rand_support(true)
1365        .with_rd_wr_fs_gs_support(true)
1366        .with_smep_support(true)
1367        .with_enhanced_fast_string_support(true)
1368        .with_bmi1_support(true)
1369        .with_bmi2_support(true)
1370        .with_movbe_support(true)
1371        .with_npiep1_support(true)
1372        .with_dep_x87_fpu_save_support(true)
1373        .with_rd_seed_support(true)
1374        .with_adx_support(true)
1375        .with_intel_prefetch_support(true)
1376        .with_smap_support(true)
1377        .with_hle_support(true)
1378        .with_rtm_support(true)
1379        .with_rdtscp_support(true)
1380        .with_clflushopt_support(true)
1381        .with_clwb_support(true)
1382        .with_sha_support(true)
1383        .with_x87_pointers_saved_support(true)
1384        .with_invpcid_support(true)
1385        .with_ibrs_support(true)
1386        .with_stibp_support(true)
1387        .with_ibpb_support(true)
1388        .with_unrestricted_guest_support(true)
1389        .with_mdd_support(true)
1390        .with_fast_short_rep_mov_support(true)
1391        .with_rdcl_no_support(true)
1392        .with_ibrs_all_support(true)
1393        .with_ssb_no_support(true)
1394        .with_rsb_a_no_support(true)
1395        .with_rd_pid_support(true)
1396        .with_umip_support(true)
1397        .with_mbs_no_support(true)
1398        .with_mb_clear_support(true)
1399        .with_taa_no_support(true)
1400        .with_tsx_ctrl_support(true)
1401}
1402
1403/// Processor features (bank 1) that we support exposing to guests.
1404fn supported_processor_features1() -> hvdef::HvX64PartitionProcessorFeatures1 {
1405    hvdef::HvX64PartitionProcessorFeatures1::new()
1406        .with_a_count_m_count_support(true)
1407        .with_tsc_invariant_support(true)
1408        .with_cl_zero_support(true)
1409        .with_rdpru_support(true)
1410        .with_la57_support(true)
1411        .with_mbec_support(true)
1412        .with_nested_virt_support(true)
1413        .with_psfd_support(true)
1414        .with_cet_ss_support(true)
1415        .with_cet_ibt_support(true)
1416        .with_vmx_exception_inject_support(true)
1417        .with_umwait_tpause_support(true)
1418        .with_movdiri_support(true)
1419        .with_movdir64b_support(true)
1420        .with_cldemote_support(true)
1421        .with_serialize_support(true)
1422        .with_tsc_deadline_tmr_support(true)
1423        .with_tsc_adjust_support(true)
1424        .with_fz_l_rep_movsb(true)
1425        .with_fs_rep_stosb(true)
1426        .with_fs_rep_cmpsb(true)
1427        .with_tsx_ld_trk_support(true)
1428        .with_vmx_ins_outs_exit_info_support(true)
1429        .with_sbdr_ssdp_no_support(true)
1430        .with_fbsdp_no_support(true)
1431        .with_psdp_no_support(true)
1432        .with_fb_clear_support(true)
1433        .with_btc_no_support(true)
1434        .with_ibpb_rsb_flush_support(true)
1435        .with_stibp_always_on_support(true)
1436        .with_perf_global_ctrl_support(true)
1437        .with_npt_execute_only_support(true)
1438        .with_npt_ad_flags_support(true)
1439        .with_npt_1gb_page_support(true)
1440        .with_cmpccxadd_support(true)
1441        .with_prefetch_i_support(true)
1442        .with_sha512_support(true)
1443        .with_rfds_no_support(true)
1444        .with_rfds_clear_support(true)
1445        .with_sm3_support(true)
1446        .with_sm4_support(true)
1447}
1448
1449/// XSAVE features that we support exposing to guests.
1450fn supported_xsave_features() -> hvdef::HvX64PartitionProcessorXsaveFeatures {
1451    hvdef::HvX64PartitionProcessorXsaveFeatures::new()
1452        .with_xsave_support(true)
1453        .with_xsaveopt_support(true)
1454        .with_avx_support(true)
1455        .with_avx2_support(true)
1456        .with_fma_support(true)
1457        .with_mpx_support(true)
1458        .with_avx512_support(true)
1459        .with_avx512_dq_support(true)
1460        .with_avx512_cd_support(true)
1461        .with_avx512_bw_support(true)
1462        .with_avx512_vl_support(true)
1463        .with_xsave_comp_support(true)
1464        .with_xsave_supervisor_support(true)
1465        .with_xcr1_support(true)
1466        .with_avx512_bitalg_support(true)
1467        .with_avx512_ifma_support(true)
1468        .with_avx512_vbmi_support(true)
1469        .with_avx512_vbmi2_support(true)
1470        .with_avx512_vnni_support(true)
1471        .with_gfni_support(true)
1472        .with_vaes_support(true)
1473        .with_avx512_vpopcntdq_support(true)
1474        .with_vpclmulqdq_support(true)
1475        .with_avx512_bf16_support(true)
1476        .with_avx512_vp2_intersect_support(true)
1477        .with_avx512_fp16_support(true)
1478        .with_xfd_support(true)
1479        .with_amx_tile_support(true)
1480        .with_amx_bf16_support(true)
1481        .with_amx_int8_support(true)
1482        .with_avx_vnni_support(true)
1483        .with_avx_ifma_support(true)
1484        .with_avx_ne_convert_support(true)
1485        .with_avx_vnni_int8_support(true)
1486        .with_avx_vnni_int16_support(true)
1487        .with_avx10_1_256_support(true)
1488        .with_avx10_1_512_support(true)
1489        .with_amx_fp16_support(true)
1490}
1491
1492#[cfg(test)]
1493mod tests {
1494    use super::*;
1495    use test_with_tracing::test;
1496
1497    #[test]
1498    fn can_disable_snp_cpuid_offloads() {
1499        let enabled = snp_vmgexit_offloads(false);
1500        let disabled = snp_vmgexit_offloads(true);
1501        assert!(SnpPartitionState::new(false).cpuid_offloads_enabled);
1502        assert!(!SnpPartitionState::new(true).cpuid_offloads_enabled);
1503
1504        // SAFETY: These generated C unions contain the bitfield member used by
1505        // get_default_vmgexit_offload_features() and snp_vmgexit_offloads().
1506        unsafe {
1507            assert_eq!(enabled.__bindgen_anon_1.nae_cpuid(), 1);
1508            assert_eq!(enabled.__bindgen_anon_1.msr_cpuid(), 1);
1509            assert_eq!(disabled.__bindgen_anon_1.nae_cpuid(), 0);
1510            assert_eq!(disabled.__bindgen_anon_1.msr_cpuid(), 0);
1511            assert_eq!(
1512                disabled.__bindgen_anon_1.nae_rdmsr(),
1513                enabled.__bindgen_anon_1.nae_rdmsr()
1514            );
1515        }
1516    }
1517
1518    #[test]
1519    fn disabling_snp_cpuid_offloads_requires_snp_isolation() {
1520        validate_snp_cpuid_offload_config(virt::IsolationType::Snp, true).unwrap();
1521        validate_snp_cpuid_offload_config(virt::IsolationType::None, false).unwrap();
1522        assert_eq!(
1523            validate_snp_cpuid_offload_config(virt::IsolationType::None, true)
1524                .unwrap_err()
1525                .to_string(),
1526            "invalid MSHV configuration: snp_disable_cpuid_offload requires SNP isolation"
1527        );
1528    }
1529
1530    #[test]
1531    fn snp_does_not_report_reference_tsc_page_support() {
1532        assert!(!hv1_reference_tsc_page_supported(
1533            true,
1534            virt::IsolationType::Snp,
1535            true
1536        ));
1537        assert!(hv1_reference_tsc_page_supported(
1538            true,
1539            virt::IsolationType::None,
1540            true
1541        ));
1542        assert!(!hv1_reference_tsc_page_supported(
1543            false,
1544            virt::IsolationType::None,
1545            true
1546        ));
1547        assert!(!hv1_reference_tsc_page_supported(
1548            true,
1549            virt::IsolationType::None,
1550            false
1551        ));
1552    }
1553
1554    #[test]
1555    fn snp_partition_creation_uses_isolation_flags() {
1556        let args = partition_create_args(true, false, false);
1557        let pt_isolation = args.pt_isolation;
1558        let pt_num_cpu_fbanks = args.pt_num_cpu_fbanks;
1559        let pt_cpu_fbanks = args.pt_cpu_fbanks;
1560        let pt_disabled_xsave = args.pt_disabled_xsave;
1561
1562        assert_eq!(pt_isolation, mshv_bindings::MSHV_PT_ISOLATION_SNP as u64);
1563        assert_ne!(args.pt_flags & 1 << mshv_bindings::MSHV_PT_BIT_LAPIC, 0);
1564        assert_ne!(
1565            args.pt_flags & 1 << mshv_bindings::MSHV_PT_BIT_GPA_SUPER_PAGES,
1566            0
1567        );
1568        assert_ne!(args.pt_flags & 1 << mshv_bindings::MSHV_PT_BIT_X2APIC, 0);
1569        assert_ne!(
1570            args.pt_flags & 1 << mshv_bindings::MSHV_PT_BIT_CPU_AND_XSAVE_FEATURES,
1571            0
1572        );
1573        assert_eq!(
1574            pt_num_cpu_fbanks,
1575            mshv_bindings::MSHV_NUM_CPU_FEATURES_BANKS as u16
1576        );
1577        assert_eq!(
1578            pt_cpu_fbanks,
1579            [
1580                !u64::from(supported_processor_features()),
1581                !u64::from(supported_processor_features1()),
1582            ]
1583        );
1584        assert_eq!(pt_disabled_xsave, !u64::from(supported_xsave_features()));
1585    }
1586
1587    #[test]
1588    fn ordinary_partition_creation_keeps_feature_banks() {
1589        let args = partition_create_args(false, false, true);
1590        let pt_isolation = args.pt_isolation;
1591        let pt_num_cpu_fbanks = args.pt_num_cpu_fbanks;
1592
1593        assert_eq!(pt_isolation, mshv_bindings::MSHV_PT_ISOLATION_NONE as u64);
1594        assert_ne!(
1595            args.pt_flags & 1 << mshv_bindings::MSHV_PT_BIT_CPU_AND_XSAVE_FEATURES,
1596            0
1597        );
1598        assert_ne!(
1599            args.pt_flags & 1 << mshv_bindings::MSHV_PT_BIT_SMT_ENABLED_GUEST,
1600            0
1601        );
1602        assert_eq!(args.pt_flags & 1 << mshv_bindings::MSHV_PT_BIT_X2APIC, 0);
1603        assert_ne!(
1604            args.pt_flags & 1 << mshv_bindings::MSHV_PT_BIT_GPA_SUPER_PAGES,
1605            0
1606        );
1607        assert_eq!(
1608            pt_num_cpu_fbanks,
1609            mshv_bindings::MSHV_NUM_CPU_FEATURES_BANKS as u16
1610        );
1611    }
1612}