1#![cfg(all(guest_is_native, target_os = "linux"))]
9
10mod devmsr;
11
12cfg_if::cfg_if!(
13 if #[cfg(guest_arch = "x86_64")] {
14 mod cvm_cpuid;
15
16 pub use crate::processor::mshv::x64::HypervisorBackedX86 as HypervisorBacked;
17 pub use processor::snp::SnpBacked;
18 pub use processor::tdx::TdxBacked;
19
20 use bitvec::prelude::BitArray;
21 use bitvec::prelude::Lsb0;
22 use crate::processor::mshv::x64::HypervisorBackedX86Shared as HypervisorBackedShared;
23 use devmsr::MsrDevice;
24 use processor::LapicState;
25 use processor::snp::SnpBackedShared;
26 use processor::tdx::TdxBackedShared;
27 use std::arch::x86_64::CpuidResult;
28 use virt::CpuidLeaf;
29 use virt::X86Partition;
30 use virt::state::StateElement;
31 use virt::vp::MpState;
32 use virt_support_apic::LocalApicSet;
33
34 type IrrBitmap = BitArray<[u32; 8], Lsb0>;
37 } else if #[cfg(guest_arch = "aarch64")] {
38 pub use crate::processor::mshv::arm64::HypervisorBackedArm64 as HypervisorBacked;
39 pub use processor::cca::CcaBacked;
40
41 use aarch64defs::Vendor;
42 use crate::processor::mshv::arm64::HypervisorBackedArm64Shared as HypervisorBackedShared;
43 use processor::cca::CcaBackedShared;
44 use safe_intrinsics::read_cntfrq_el0;
45 }
46);
47
48mod processor;
49use hv1_emulator::hv::ProcessorVtlHv;
50pub use processor::Backing;
51pub use processor::UhProcessor;
52
53use crate::processor::HardwareIsolatedBacking;
54use anyhow::Context as AnyhowContext;
55use bitfield_struct::bitfield;
56use bitvec::boxed::BitBox;
57use bitvec::vec::BitVec;
58use cvm_tracing::CVM_ALLOWED;
59use guestmem::GuestMemory;
60use guestmem::GuestMemoryBackingError;
61use hcl::GuestVtl;
62use hcl::ioctl::Hcl;
63use hcl::ioctl::SetVsmPartitionConfigError;
64use hv1_emulator::hv::GlobalHv;
65use hv1_emulator::message_queues::MessageQueues;
66use hv1_emulator::synic::GlobalSynic;
67use hv1_emulator::synic::SintProxied;
68use hv1_structs::VtlArray;
69use hvdef::GuestCrashCtl;
70use hvdef::HV_PAGE_SHIFT;
71use hvdef::HV_PAGE_SIZE;
72use hvdef::HV_PAGE_SIZE_USIZE;
73use hvdef::HvError;
74use hvdef::HvMapGpaFlags;
75use hvdef::HvPartitionPrivilege;
76use hvdef::HvRegisterName;
77use hvdef::HvRegisterVsmPartitionConfig;
78use hvdef::HvRegisterVsmPartitionStatus;
79use hvdef::Vtl;
80use hvdef::hypercall::HV_INTERCEPT_ACCESS_MASK_EXECUTE;
81use hvdef::hypercall::HV_INTERCEPT_ACCESS_MASK_NONE;
82use hvdef::hypercall::HV_INTERCEPT_ACCESS_MASK_READ_WRITE;
83use hvdef::hypercall::HV_INTERCEPT_ACCESS_MASK_WRITE;
84use hvdef::hypercall::HostVisibilityType;
85use hvdef::hypercall::HvGuestOsId;
86use hvdef::hypercall::HvInputVtl;
87use hvdef::hypercall::HvInterceptParameters;
88use hvdef::hypercall::HvInterceptType;
89use inspect::Inspect;
90use inspect::InspectMut;
91use memory_range::MemoryRange;
92use pal::unix::affinity;
93use pal::unix::affinity::CpuSet;
94use pal_async::driver::Driver;
95use pal_async::driver::SpawnDriver;
96use pal_uring::IdleControl;
97use parking_lot::Mutex;
98use parking_lot::RwLock;
99use processor::BackingSharedParams;
100use processor::SidecarExitReason;
101use sidecar_client::NewSidecarClientError;
102use std::collections::HashMap;
103use std::ops::RangeInclusive;
104use std::os::fd::AsRawFd;
105use std::sync::Arc;
106use std::sync::Weak;
107use std::sync::atomic::AtomicBool;
108use std::sync::atomic::AtomicU8;
109use std::sync::atomic::AtomicU32;
110use std::sync::atomic::AtomicU64;
111use std::sync::atomic::Ordering;
112use std::task::Waker;
113use thiserror::Error;
114use user_driver::DmaClient;
115use virt::IsolationType;
116use virt::PartitionCapabilities;
117use virt::VpIndex;
118use virt::irqcon::IoApicRouting;
119use virt::irqcon::MsiRequest;
120use virt::x86::apic_software_device::ApicSoftwareDevices;
121use vm_topology::memory::MemoryLayout;
122use vm_topology::processor::ProcessorTopology;
123use vm_topology::processor::TargetVpInfo;
124use vmcore::monitor::MonitorPage;
125use vmcore::reference_time::GetReferenceTime;
126use vmcore::reference_time::ReferenceTimeResult;
127use vmcore::reference_time::ReferenceTimeSource;
128use vmcore::vmtime::VmTimeSource;
129use x86defs::snp::REG_TWEAK_BITMAP_OFFSET;
130use x86defs::snp::REG_TWEAK_BITMAP_SIZE;
131use x86defs::tdx::TdCallResult;
132use zerocopy::FromBytes;
133use zerocopy::FromZeros;
134use zerocopy::Immutable;
135use zerocopy::IntoBytes;
136use zerocopy::KnownLayout;
137
138#[derive(Error, Debug)]
140#[expect(missing_docs)]
141pub enum Error {
142 #[error("hcl error")]
143 Hcl(#[source] hcl::ioctl::Error),
144 #[error("failed to open sidecar client")]
145 Sidecar(#[source] NewSidecarClientError),
146 #[error("failed to install {0:?} intercept: {1:?}")]
147 InstallIntercept(HvInterceptType, HvError),
148 #[error("failed to query hypervisor register {0:#x?}")]
149 Register(HvRegisterName, #[source] HvError),
150 #[error("failed to set vsm partition config register")]
151 VsmPartitionConfig(#[source] SetVsmPartitionConfigError),
152 #[error("failed to create virtual device")]
153 NewDevice(#[source] virt::x86::apic_software_device::DeviceIdInUse),
154 #[error("failed to create cpuid tables for cvm")]
155 #[cfg(guest_arch = "x86_64")]
156 CvmCpuid(#[source] cvm_cpuid::CpuidResultsError),
157 #[error("failed to update hypercall msr")]
158 UpdateHypercallMsr,
159 #[error("failed to update reference tsc msr")]
160 UpdateReferenceTsc,
161 #[error("failed to map overlay page")]
162 MapOverlay(#[source] std::io::Error),
163 #[error("failed to allocate shared visibility pages for overlay")]
164 AllocateSharedVisOverlay(#[source] anyhow::Error),
165 #[error("failed to open msr device")]
166 OpenMsr(#[source] std::io::Error),
167 #[error("cpuid did not contain valid TSC frequency information")]
168 BadCpuidTsc,
169 #[error("failed to read tsc frequency")]
170 ReadTscFrequency(#[source] std::io::Error),
171 #[error(
172 "tsc frequency mismatch between hypervisor ({hv}) and hardware {hw}, exceeds allowed error {allowed_error}"
173 )]
174 TscFrequencyMismatch {
175 hv: u64,
176 hw: u64,
177 allowed_error: u64,
178 },
179 #[error("failed to set vsm partition config: {0:?}")]
180 FailedToSetL2Ctls(TdCallResult),
181 #[error("debugging is configured but the binary does not have the gdb feature")]
182 InvalidDebugConfiguration,
183 #[error("failed to allocate TLB flush page")]
184 AllocateTlbFlushPage(#[source] anyhow::Error),
185 #[error("host does not support required cpu capabilities")]
186 Capabilities(virt::PartitionCapabilitiesError),
187 #[error("failed to get register")]
188 GetReg(#[source] hcl::ioctl::register::GetRegError),
189 #[error("failed to set register")]
190 SetReg(#[source] hcl::ioctl::register::SetRegError),
191}
192
193#[derive(Error, Debug)]
195#[expect(missing_docs)]
196pub enum RevokeGuestVsmError {
197 #[error("failed to set vsm config")]
198 SetGuestVsmConfig(#[source] hcl::ioctl::register::SetRegError),
199 #[error("VTL 1 is already enabled")]
200 Vtl1AlreadyEnabled,
201}
202
203#[derive(Inspect)]
205pub struct UhPartition {
206 #[inspect(flatten)]
207 inner: Arc<UhPartitionInner>,
208 #[inspect(skip)]
210 interrupt_targets: VtlArray<Arc<UhInterruptTarget>, 2>,
211 #[inspect(skip)]
212 synic_ports: Arc<virt::synic::SynicPorts<UhPartitionInner>>,
213}
214
215#[derive(Inspect)]
217#[inspect(extra = "UhPartitionInner::inspect_extra")]
218struct UhPartitionInner {
219 #[inspect(skip)]
220 hcl: Hcl,
221 #[inspect(skip)] vps: Vec<UhVpInner>,
223 irq_routes: virt::irqcon::IrqRoutes,
224 caps: PartitionCapabilities,
225 #[inspect(skip)] enter_modes: Mutex<EnterModes>,
227 #[inspect(skip)]
228 enter_modes_atomic: AtomicU8,
229 #[cfg(guest_arch = "x86_64")]
230 cpuid: virt::CpuidLeafSet,
231 lower_vtl_memory_layout: MemoryLayout,
232 gm: VtlArray<GuestMemory, 2>,
233 vtl0_kernel_exec_gm: GuestMemory,
234 vtl0_user_exec_gm: GuestMemory,
235 #[cfg_attr(guest_arch = "aarch64", expect(dead_code))]
236 #[inspect(skip)]
237 crash_notification_send: mesh::Sender<VtlCrash>,
238 monitor_page: MonitorPage,
239 #[inspect(skip)]
240 allocated_monitor_page: Mutex<Option<user_driver::memory::MemoryBlock>>,
241 software_devices: Option<ApicSoftwareDevices>,
242 #[inspect(skip)]
243 vmtime: VmTimeSource,
244 isolation: IsolationType,
245 #[inspect(with = "inspect::AtomicMut")]
246 no_sidecar_hotplug: AtomicBool,
247 use_mmio_hypercalls: bool,
248 backing_shared: BackingShared,
249 intercept_debug_exceptions: bool,
250 #[cfg(guest_arch = "x86_64")]
251 #[inspect(hex, with = "|x| inspect::iter_by_index(x.read().into_inner())")]
253 device_vector_table: RwLock<IrrBitmap>,
254 vmbus_relay: bool,
255 synic_ports: virt::synic::SynicPortMap,
256}
257
258#[derive(Inspect)]
259#[inspect(untagged)]
260enum BackingShared {
261 Hypervisor(#[inspect(flatten)] HypervisorBackedShared),
262 #[cfg(guest_arch = "x86_64")]
263 Snp(#[inspect(flatten)] SnpBackedShared),
264 #[cfg(guest_arch = "x86_64")]
265 Tdx(#[inspect(flatten)] TdxBackedShared),
266 #[cfg(guest_arch = "aarch64")]
267 Cca(#[inspect(flatten)] Box<CcaBackedShared>),
268}
269
270impl BackingShared {
271 fn new(
272 isolation: IsolationType,
273 partition_params: &UhPartitionNewParams<'_>,
274 backing_shared_params: BackingSharedParams<'_>,
275 ) -> Result<BackingShared, Error> {
276 Ok(match isolation {
277 IsolationType::None | IsolationType::Vbs => {
278 assert!(backing_shared_params.cvm_state.is_none());
279 BackingShared::Hypervisor(HypervisorBackedShared::new(
280 partition_params,
281 backing_shared_params,
282 )?)
283 }
284 #[cfg(guest_arch = "x86_64")]
285 IsolationType::Snp => BackingShared::Snp(SnpBackedShared::new(
286 partition_params,
287 backing_shared_params,
288 )?),
289 #[cfg(guest_arch = "x86_64")]
290 IsolationType::Tdx => BackingShared::Tdx(TdxBackedShared::new(
291 partition_params,
292 backing_shared_params,
293 )?),
294 #[cfg(guest_arch = "aarch64")]
295 IsolationType::Cca => {
296 BackingShared::Cca(Box::new(CcaBackedShared::new(backing_shared_params)?))
297 }
298 _ => unreachable!(),
299 })
300 }
301
302 fn cvm_state(&self) -> Option<&UhCvmPartitionState> {
303 match self {
304 BackingShared::Hypervisor(_) => None,
305 #[cfg(guest_arch = "x86_64")]
306 BackingShared::Snp(SnpBackedShared { cvm, .. })
307 | BackingShared::Tdx(TdxBackedShared { cvm, .. }) => Some(cvm),
308 #[cfg(guest_arch = "aarch64")]
309 BackingShared::Cca(s) => Some(&s.cvm),
310 }
311 }
312
313 fn untrusted_synic(&self) -> Option<&GlobalSynic> {
314 match self {
315 BackingShared::Hypervisor(_) => None,
316 #[cfg(guest_arch = "x86_64")]
317 BackingShared::Snp(_) => None,
318 #[cfg(guest_arch = "x86_64")]
319 BackingShared::Tdx(s) => s.untrusted_synic.as_ref(),
320 #[cfg(guest_arch = "aarch64")]
321 BackingShared::Cca(_) => None,
322 }
323 }
324}
325
326#[derive(InspectMut, Copy, Clone)]
332struct EnterModes {
333 #[inspect(mut)]
335 first: EnterMode,
336 #[inspect(mut)]
338 second: EnterMode,
339}
340
341impl Default for EnterModes {
342 fn default() -> Self {
343 Self {
344 first: EnterMode::Fast,
345 second: EnterMode::IdleToVtl0,
346 }
347 }
348}
349
350impl From<EnterModes> for hcl::protocol::EnterModes {
351 fn from(value: EnterModes) -> Self {
352 Self::new()
353 .with_first(value.first.into())
354 .with_second(value.second.into())
355 }
356}
357
358#[derive(InspectMut, Copy, Clone)]
364enum EnterMode {
365 Fast,
368 PlayIdle,
373 IdleToVtl0,
377}
378
379impl From<EnterMode> for hcl::protocol::EnterMode {
380 fn from(value: EnterMode) -> Self {
381 match value {
382 EnterMode::Fast => Self::FAST,
383 EnterMode::PlayIdle => Self::PLAY_IDLE,
384 EnterMode::IdleToVtl0 => Self::IDLE_TO_VTL0,
385 }
386 }
387}
388
389#[cfg(guest_arch = "x86_64")]
390#[derive(Inspect)]
391struct GuestVsmVpState {
392 #[inspect(with = "|x| x.as_ref().map(inspect::AsDebug)")]
395 vtl0_exit_pending_event: Option<hvdef::HvX64PendingExceptionEvent>,
396 reg_intercept: SecureRegisterInterceptState,
397 vp_mbec_enabled: bool,
399}
400
401#[cfg(guest_arch = "x86_64")]
402impl GuestVsmVpState {
403 fn new() -> Self {
404 GuestVsmVpState {
405 vtl0_exit_pending_event: None,
406 reg_intercept: Default::default(),
407 vp_mbec_enabled: false,
408 }
409 }
410}
411
412#[derive(Inspect)]
413struct UhCvmVpState {
415 #[inspect(debug)]
417 direct_overlay_handle: user_driver::memory::MemoryBlock,
418 exit_vtl: GuestVtl,
420 hv: VtlArray<ProcessorVtlHv, 2>,
422 #[inspect(safe)]
424 #[cfg(guest_arch = "x86_64")]
425 lapics: VtlArray<LapicState, 2>,
426 #[cfg(guest_arch = "x86_64")]
428 vtl1: Option<GuestVsmVpState>,
429}
430
431impl UhCvmVpState {
432 pub(crate) fn new(
434 cvm_partition: &UhCvmPartitionState,
435 #[cfg_attr(guest_arch = "aarch64", expect(unused_variables))] inner: &UhPartitionInner,
436 vp_info: &TargetVpInfo,
437 overlay_pages_required: usize,
438 ) -> Result<Self, Error> {
439 let direct_overlay_handle = cvm_partition
440 .shared_dma_client
441 .allocate_dma_buffer(overlay_pages_required * HV_PAGE_SIZE as usize)
442 .map_err(Error::AllocateSharedVisOverlay)?;
443
444 #[cfg(guest_arch = "x86_64")]
445 let lapics = VtlArray::from_fn(|vtl| {
446 let apic_base = virt::vp::Apic::at_reset(&inner.caps, vp_info).apic_base;
447 let apic_set = &cvm_partition.lapic[vtl];
448
449 let mut lapic = apic_set.add_apic(vp_info, vtl == Vtl::Vtl1);
452 lapic.set_apic_base(apic_base).unwrap();
454 let activity = if vtl == Vtl::Vtl0 && !vp_info.base.is_bsp() {
456 MpState::WaitForSipi
457 } else {
458 MpState::Running
459 };
460 LapicState::new(lapic, activity)
461 });
462
463 let hv = VtlArray::from_fn(|vtl| cvm_partition.hv.add_vp(vp_info.base.vp_index, vtl));
464
465 Ok(Self {
466 direct_overlay_handle,
467 exit_vtl: GuestVtl::Vtl0,
468 hv,
469 #[cfg(guest_arch = "x86_64")]
470 lapics,
471 #[cfg(guest_arch = "x86_64")]
472 vtl1: None,
473 })
474 }
475}
476
477#[derive(Inspect, Default)]
478#[inspect(hex)]
479pub struct SecureRegisterInterceptState {
481 #[inspect(with = "|&x| u64::from(x)")]
482 intercept_control: hvdef::HvRegisterCrInterceptControl,
483 cr0_mask: u64,
484 cr4_mask: u64,
485 ia32_misc_enable_mask: u64,
488}
489
490#[derive(Clone, Inspect)]
493struct ProxyRedirectVectorInfo {
494 device_id: u64,
496 original_vector: u32,
498}
499
500#[derive(Inspect)]
501struct UhCvmPartitionState {
503 #[cfg(guest_arch = "x86_64")]
504 vps_per_socket: u32,
505 #[inspect(
507 with = "|arr| inspect::iter_by_index(arr.iter()).map_value(|bb| inspect::iter_by_index(bb.iter().map(|v| *v)))"
508 )]
509 tlb_locked_vps: VtlArray<BitBox<AtomicU64>, 2>,
510 #[inspect(with = "inspect::iter_by_index")]
511 vps: Vec<UhCvmVpInner>,
512 shared_memory: GuestMemory,
513 #[inspect(skip)]
514 isolated_memory_protector: Arc<dyn ProtectIsolatedMemory>,
515 #[cfg(guest_arch = "x86_64")]
516 lapic: VtlArray<LocalApicSet, 2>,
518 hv: GlobalHv<2>,
520 guest_vsm: RwLock<GuestVsmState<CvmVtl1State>>,
522 access_vsm_privilege: bool,
524 shared_dma_client: Arc<dyn DmaClient>,
526 private_dma_client: Arc<dyn DmaClient>,
528 hide_isolation: bool,
529 proxy_interrupt_redirect: bool,
530}
531
532#[cfg_attr(guest_arch = "aarch64", expect(dead_code))]
533impl UhCvmPartitionState {
534 fn vp_inner(&self, vp_index: u32) -> &UhCvmVpInner {
535 &self.vps[vp_index as usize]
536 }
537
538 fn is_lower_vtl_startup_denied(&self) -> bool {
539 matches!(
540 *self.guest_vsm.read(),
541 GuestVsmState::Enabled {
542 vtl1: CvmVtl1State {
543 deny_lower_vtl_startup: true,
544 ..
545 }
546 }
547 )
548 }
549
550 fn access_vsm_privilege(&self) -> bool {
554 self.access_vsm_privilege
555 }
556}
557
558#[derive(Inspect)]
559struct UhCvmVpInner {
561 tlb_lock_info: VtlArray<TlbLockInfo, 2>,
563 vtl1_enable_called: Mutex<bool>,
565 started: AtomicBool,
567 #[inspect(with = "|arr| inspect::iter_by_index(arr.iter().map(|v| v.lock().is_some()))")]
569 hv_start_enable_vtl_vp: VtlArray<Mutex<Option<Box<VpStartEnableVtl>>>, 2>,
570 #[inspect(with = "|x| inspect::adhoc(|req| inspect::iter_by_key(&*x.lock()).inspect(req))")]
572 proxy_redirect_interrupts: Mutex<HashMap<u32, ProxyRedirectVectorInfo>>,
573}
574
575#[cfg_attr(guest_arch = "aarch64", expect(dead_code))]
577#[derive(Inspect)]
578#[inspect(tag = "guest_vsm_state")]
579enum GuestVsmState<T: Inspect> {
581 NotPlatformSupported,
586 NotGuestEnabled,
588 Enabled {
592 #[inspect(flatten)]
593 vtl1: T,
594 },
595}
596
597impl<T: Inspect> GuestVsmState<T> {
598 pub fn from_availability(guest_vsm_available: bool) -> Self {
599 if guest_vsm_available {
600 GuestVsmState::NotGuestEnabled
601 } else {
602 GuestVsmState::NotPlatformSupported
603 }
604 }
605}
606
607#[derive(Inspect)]
608struct CvmVtl1State {
609 enabled_on_any_vp: bool,
611 zero_memory_on_reset: bool,
613 deny_lower_vtl_startup: bool,
615 pub mbec_enabled: bool,
617 pub shadow_supervisor_stack_enabled: bool,
619 #[inspect(with = "|bb| inspect::iter_by_index(bb.iter().map(|v| *v))")]
620 io_read_intercepts: BitBox<u64>,
621 #[inspect(with = "|bb| inspect::iter_by_index(bb.iter().map(|v| *v))")]
622 io_write_intercepts: BitBox<u64>,
623}
624
625#[cfg_attr(guest_arch = "aarch64", expect(dead_code))]
626impl CvmVtl1State {
627 fn new(mbec_enabled: bool) -> Self {
628 Self {
629 enabled_on_any_vp: false,
630 zero_memory_on_reset: false,
631 deny_lower_vtl_startup: false,
632 mbec_enabled,
633 shadow_supervisor_stack_enabled: false,
634 io_read_intercepts: BitVec::repeat(false, u16::MAX as usize + 1).into_boxed_bitslice(),
635 io_write_intercepts: BitVec::repeat(false, u16::MAX as usize + 1).into_boxed_bitslice(),
636 }
637 }
638}
639
640#[cfg_attr(guest_arch = "aarch64", expect(dead_code))]
641struct TscReferenceTimeSource {
642 tsc_scale: u64,
643}
644
645impl TscReferenceTimeSource {
646 fn new(tsc_frequency: u64) -> Self {
647 TscReferenceTimeSource {
648 tsc_scale: (((10_000_000_u128) << 64) / tsc_frequency as u128) as u64,
649 }
650 }
651}
652
653impl GetReferenceTime for TscReferenceTimeSource {
655 fn now(&self) -> ReferenceTimeResult {
656 #[cfg(guest_arch = "x86_64")]
657 {
658 let tsc = safe_intrinsics::rdtsc();
659 let ref_time = ((self.tsc_scale as u128 * tsc as u128) >> 64) as u64;
660 ReferenceTimeResult {
661 ref_time,
662 system_time: None,
663 }
664 }
665
666 #[cfg(guest_arch = "aarch64")]
667 {
668 todo!("AARCH64_TODO");
669 }
670 }
671}
672
673impl virt::irqcon::ControlGic for UhPartitionInner {
674 fn set_spi_irq(&self, irq_id: u32, high: bool) {
675 if let Err(err) = self.hcl.request_interrupt(
676 hvdef::HvInterruptControl::new()
677 .with_arm64_asserted(high)
678 .with_interrupt_type(hvdef::HvInterruptType::HvArm64InterruptTypeFixed),
679 0,
680 irq_id,
681 GuestVtl::Vtl0,
682 ) {
683 tracelimit::warn_ratelimited!(
684 error = &err as &dyn std::error::Error,
685 irq = irq_id,
686 asserted = high,
687 "failed to request spi"
688 );
689 }
690 }
691}
692
693impl virt::Aarch64Partition for UhPartition {
694 fn control_gic(&self, vtl: Vtl) -> Arc<dyn virt::irqcon::ControlGic> {
695 debug_assert!(vtl == Vtl::Vtl0);
696 self.inner.clone()
697 }
698}
699
700pub struct UhProcessorBox {
706 partition: Arc<UhPartitionInner>,
707 vp_info: TargetVpInfo,
708}
709
710impl UhProcessorBox {
711 pub fn vp_index(&self) -> VpIndex {
713 self.vp_info.base.vp_index
714 }
715
716 pub fn sidecar_enabled(&self) -> bool {
718 self.partition.hcl.sidecar_enabled()
719 }
720
721 pub fn sidecar_base_cpu(&self) -> Option<u32> {
724 self.partition
725 .hcl
726 .sidecar_base_cpu(self.vp_info.base.vp_index.index())
727 }
728
729 pub fn bind_processor<'a, T: Backing>(
736 &'a mut self,
737 driver: &impl Driver,
738 control: Option<&'a mut IdleControl>,
739 ) -> Result<UhProcessor<'a, T>, Error> {
740 if let Some(control) = &control {
741 let vp_index = self.vp_info.base.vp_index;
742
743 let mut current = Default::default();
744 affinity::get_current_thread_affinity(&mut current).unwrap();
745 assert_eq!(¤t, CpuSet::new().set(vp_index.index()));
746
747 self.partition
748 .hcl
749 .set_poll_file(
750 self.partition.vp(vp_index).unwrap().cpu_index,
751 control.ring_fd().as_raw_fd(),
752 )
753 .map_err(Error::Hcl)?;
754 }
755
756 UhProcessor::new(driver, &self.partition, self.vp_info, control)
757 }
758
759 pub fn set_sidecar_exit_due_to_task(&self, task: Arc<str>) {
764 self.partition
765 .vp(self.vp_info.base.vp_index)
766 .unwrap()
767 .set_sidecar_exit_reason(SidecarExitReason::TaskRequest(task))
768 }
769}
770
771#[derive(Debug, Inspect)]
772struct UhVpInner {
773 wake_reasons: AtomicU64,
775 #[inspect(skip)]
776 waker: RwLock<Option<Waker>>,
777 message_queues: VtlArray<MessageQueues, 2>,
778 #[inspect(skip)]
779 vp_info: TargetVpInfo,
780 cpu_index: u32,
783 sidecar_exit_reason: Mutex<Option<SidecarExitReason>>,
784}
785
786impl UhVpInner {
787 pub fn vp_index(&self) -> VpIndex {
788 self.vp_info.base.vp_index
789 }
790}
791
792#[cfg_attr(not(guest_arch = "x86_64"), expect(dead_code))]
793#[derive(Debug, Inspect)]
794enum InitialVpContextOperation {
796 StartVp,
798 EnableVpVtl,
800}
801
802#[cfg_attr(not(guest_arch = "x86_64"), expect(dead_code))]
803#[derive(Debug, Inspect)]
804struct VpStartEnableVtl {
806 operation: InitialVpContextOperation,
809 #[inspect(skip)]
810 context: hvdef::hypercall::InitialVpContextX64,
811}
812
813#[derive(Debug, Inspect)]
814struct TlbLockInfo {
815 #[inspect(with = "|bb| inspect::iter_by_index(bb.iter().map(|v| *v))")]
817 blocked_vps: BitBox<AtomicU64>,
818 #[inspect(with = "|bb| inspect::iter_by_index(bb.iter().map(|v| *v))")]
821 blocking_vps: BitBox<AtomicU64>,
822 blocking_vp_count: AtomicU32,
826 sleeping: AtomicBool,
828}
829
830impl TlbLockInfo {
831 fn new(vp_count: usize) -> Self {
832 Self {
833 blocked_vps: BitVec::repeat(false, vp_count).into_boxed_bitslice(),
834 blocking_vps: BitVec::repeat(false, vp_count).into_boxed_bitslice(),
835 blocking_vp_count: AtomicU32::new(0),
836 sleeping: false.into(),
837 }
838 }
839}
840
841#[bitfield(u32)]
842#[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
843struct WakeReason {
844 extint: bool,
845 message_queues: bool,
846 hv_start_enable_vtl_vp: bool,
847 intcon: bool,
848 update_proxy_irr_filter: bool,
849 #[bits(27)]
850 _reserved: u32,
851}
852
853impl WakeReason {
854 #[cfg(guest_arch = "x86_64")]
856 const EXTINT: Self = Self::new().with_extint(true);
857 const MESSAGE_QUEUES: Self = Self::new().with_message_queues(true);
858 #[cfg(guest_arch = "x86_64")]
859 const HV_START_ENABLE_VP_VTL: Self = Self::new().with_hv_start_enable_vtl_vp(true); #[cfg(guest_arch = "x86_64")]
861 const INTCON: Self = Self::new().with_intcon(true);
862 #[cfg(guest_arch = "x86_64")]
863 const UPDATE_PROXY_IRR_FILTER: Self = Self::new().with_update_proxy_irr_filter(true);
864}
865
866#[bitfield(u32)]
867#[derive(IntoBytes, Immutable, KnownLayout, FromBytes)]
868struct ExitActivity {
869 pending_event: bool,
870 #[bits(31)]
871 _reserved: u32,
872}
873
874impl UhPartition {
876 pub fn revoke_guest_vsm(&self) -> Result<(), RevokeGuestVsmError> {
878 fn revoke<T: Inspect>(vsm_state: &mut GuestVsmState<T>) -> Result<(), RevokeGuestVsmError> {
879 if matches!(vsm_state, GuestVsmState::Enabled { .. }) {
880 return Err(RevokeGuestVsmError::Vtl1AlreadyEnabled);
881 }
882 *vsm_state = GuestVsmState::NotPlatformSupported;
883 Ok(())
884 }
885
886 match &self.inner.backing_shared {
887 BackingShared::Hypervisor(s) => {
888 revoke(&mut *s.guest_vsm.write())?;
889 self.inner
890 .hcl
891 .set_guest_vsm_partition_config(false)
892 .map_err(RevokeGuestVsmError::SetGuestVsmConfig)?;
893 }
894 #[cfg(guest_arch = "x86_64")]
895 BackingShared::Snp(SnpBackedShared { cvm, .. })
896 | BackingShared::Tdx(TdxBackedShared { cvm, .. }) => {
897 revoke(&mut *cvm.guest_vsm.write())?;
898 }
899 #[cfg(guest_arch = "aarch64")]
900 BackingShared::Cca(s) => {
901 revoke(&mut *s.cvm.guest_vsm.write())?;
902 }
903 };
904
905 Ok(())
906 }
907
908 pub fn reference_time(&self) -> u64 {
910 if let Some(hv) = self.inner.hv() {
911 hv.ref_time_source().now().ref_time
912 } else {
913 self.inner
914 .hcl
915 .reference_time()
916 .expect("should not fail to get the reference time")
917 }
918 }
919}
920
921impl virt::Partition for UhPartition {
922 fn initial_vp_state_source(&self) -> virt::InitialVpStateSource {
923 virt::InitialVpStateSource::Registers
924 }
925
926 fn supports_reset(&self) -> Option<&dyn virt::ResetPartition<Error = Self::Error>> {
927 None
928 }
929
930 fn caps(&self) -> &PartitionCapabilities {
931 &self.inner.caps
932 }
933
934 fn request_msi(&self, vtl: Vtl, request: MsiRequest) {
935 self.inner
936 .request_msi(vtl.try_into().expect("higher vtl not configured"), request)
937 }
938
939 fn request_yield(&self, _vp_index: VpIndex) {
940 unimplemented!()
941 }
942}
943
944#[cfg(guest_arch = "x86_64")]
945impl X86Partition for UhPartition {
946 fn ioapic_routing(&self) -> Arc<dyn IoApicRouting> {
947 self.inner.clone()
948 }
949
950 fn pulse_lint(&self, vp_index: VpIndex, vtl: Vtl, lint: u8) {
951 let vtl = GuestVtl::try_from(vtl).expect("higher vtl not configured");
952 if let Some(apic) = &self.inner.lapic(vtl) {
953 apic.lint(vp_index, lint.into(), |vp_index| {
954 self.inner
955 .vp(vp_index)
956 .unwrap()
957 .wake(vtl, WakeReason::INTCON);
958 });
959 } else if lint == 0 {
960 self.inner
961 .vp(vp_index)
962 .unwrap()
963 .wake(vtl, WakeReason::EXTINT);
964 } else {
965 unimplemented!()
966 }
967 }
968}
969
970impl UhPartitionInner {
971 fn vp(&self, index: VpIndex) -> Option<&'_ UhVpInner> {
972 self.vps.get(index.index() as usize)
973 }
974
975 #[cfg(guest_arch = "x86_64")]
976 fn lapic(&self, vtl: GuestVtl) -> Option<&LocalApicSet> {
977 self.backing_shared.cvm_state().map(|x| &x.lapic[vtl])
978 }
979
980 fn hv(&self) -> Option<&GlobalHv<2>> {
981 self.backing_shared.cvm_state().map(|x| &x.hv)
982 }
983
984 #[cfg(guest_arch = "x86_64")]
986 fn request_proxy_irr_filter_update(
987 &self,
988 vtl: GuestVtl,
989 device_vector: u8,
990 req_vp_index: VpIndex,
991 ) {
992 tracing::debug!(
993 ?vtl,
994 device_vector,
995 req_vp_index = req_vp_index.index(),
996 "request_proxy_irr_filter_update"
997 );
998
999 {
1001 let mut device_vector_table = self.device_vector_table.write();
1002 device_vector_table.set(device_vector as usize, true);
1003 }
1004
1005 for vp in self.vps.iter() {
1007 if vp.vp_index() != req_vp_index {
1008 vp.wake(vtl, WakeReason::UPDATE_PROXY_IRR_FILTER);
1009 }
1010 }
1011 }
1012
1013 #[cfg(guest_arch = "x86_64")]
1015 fn fill_device_vectors(&self, _vtl: GuestVtl, irr_vectors: &mut IrrBitmap) {
1016 let device_vector_table = self.device_vector_table.read();
1017 for idx in device_vector_table.iter_ones() {
1018 irr_vectors.set(idx, true);
1019 }
1020 }
1021
1022 fn inspect_extra(&self, resp: &mut inspect::Response<'_>) {
1023 let mut wake_vps = false;
1024 resp.field_mut(
1025 "enter_modes",
1026 &mut inspect::adhoc_mut(|req| {
1027 let update = req.is_update();
1028 {
1029 let mut modes = self.enter_modes.lock();
1030 modes.inspect_mut(req);
1031 if update {
1032 self.enter_modes_atomic.store(
1033 hcl::protocol::EnterModes::from(*modes).into(),
1034 Ordering::Relaxed,
1035 );
1036 wake_vps = true;
1037 }
1038 }
1039 }),
1040 );
1041
1042 if wake_vps {
1044 for vp in self.vps.iter() {
1045 vp.wake_vtl2();
1046 }
1047 }
1048 }
1049
1050 #[cfg_attr(guest_arch = "aarch64", expect(dead_code))]
1052 fn vsm_status(
1053 &self,
1054 ) -> Result<HvRegisterVsmPartitionStatus, hcl::ioctl::register::GetRegError> {
1055 self.hcl.get_vsm_partition_status()
1057 }
1058}
1059
1060impl virt::synic::Synic for UhPartitionInner {
1061 fn port_map(&self) -> &virt::synic::SynicPortMap {
1062 &self.synic_ports
1063 }
1064
1065 fn post_message(&self, vtl: Vtl, vp_index: VpIndex, sint: u8, typ: u32, payload: &[u8]) {
1066 let vtl = GuestVtl::try_from(vtl).expect("higher vtl not configured");
1067 let Some(vp) = self.vp(vp_index) else {
1068 tracelimit::warn_ratelimited!(
1069 CVM_ALLOWED,
1070 vp = vp_index.index(),
1071 "invalid vp target for post_message"
1072 );
1073 return;
1074 };
1075
1076 vp.post_message(
1077 vtl,
1078 sint,
1079 &hvdef::HvMessage::new(hvdef::HvMessageType(typ), 0, payload),
1080 );
1081 }
1082
1083 fn new_guest_event_port(
1084 self: Arc<Self>,
1085 vtl: Vtl,
1086 vp: u32,
1087 sint: u8,
1088 flag: u16,
1089 ) -> Box<dyn vmcore::synic::GuestEventPort> {
1090 let vtl = GuestVtl::try_from(vtl).expect("higher vtl not configured");
1091 Box::new(UhEventPort {
1092 partition: Arc::downgrade(&self),
1093 params: Arc::new(Mutex::new(UhEventPortParams {
1094 vp: VpIndex::new(vp),
1095 sint,
1096 flag,
1097 vtl,
1098 })),
1099 })
1100 }
1101
1102 fn prefer_os_events(&self) -> bool {
1103 false
1104 }
1105
1106 fn monitor_support(&self) -> Option<&dyn virt::synic::SynicMonitor> {
1107 Some(self)
1108 }
1109}
1110
1111impl virt::synic::SynicMonitor for UhPartitionInner {
1112 fn set_monitor_page(&self, vtl: Vtl, gpa: Option<u64>) -> anyhow::Result<()> {
1113 let mut allocated_block = self.allocated_monitor_page.lock();
1115 let old_gpa = self.monitor_page.set_gpa(gpa);
1116
1117 let allocated_page = allocated_block.take();
1119 if let Some(old_gpa) = old_gpa {
1120 let allocated_gpa = allocated_page
1121 .as_ref()
1122 .map(|b| b.pfns()[0] << HV_PAGE_SHIFT);
1123
1124 let result = if allocated_gpa == Some(old_gpa) {
1127 let vtl = GuestVtl::try_from(vtl).unwrap();
1128 self.unregister_cvm_dma_overlay_page(vtl, old_gpa >> HV_PAGE_SHIFT)
1129 } else {
1130 self.hcl
1131 .modify_vtl_protection_mask(
1132 MemoryRange::new(old_gpa..old_gpa + HV_PAGE_SIZE),
1133 hvdef::HV_MAP_GPA_PERMISSIONS_ALL,
1134 HvInputVtl::CURRENT_VTL,
1135 )
1136 .map_err(|err| anyhow::anyhow!(err))
1137 };
1138
1139 result
1140 .context("failed to unregister old monitor page")
1141 .inspect_err(|_| {
1142 self.monitor_page.set_gpa(None);
1144 })?;
1145
1146 tracing::debug!(old_gpa, "unregistered monitor page");
1147 }
1148
1149 if let Some(gpa) = gpa {
1150 self.hcl
1153 .modify_vtl_protection_mask(
1154 MemoryRange::new(gpa..gpa + HV_PAGE_SIZE),
1155 HvMapGpaFlags::new().with_readable(true),
1156 HvInputVtl::CURRENT_VTL,
1157 )
1158 .context("failed to register monitor page")
1159 .inspect_err(|_| {
1160 self.monitor_page.set_gpa(None);
1162 })?;
1163
1164 tracing::debug!(gpa, "registered monitor page");
1165 }
1166
1167 Ok(())
1168 }
1169
1170 fn register_monitor(
1171 &self,
1172 monitor_id: vmcore::monitor::MonitorId,
1173 connection_id: u32,
1174 ) -> Box<dyn Sync + Send> {
1175 self.monitor_page
1176 .register_monitor(monitor_id, connection_id)
1177 }
1178
1179 fn allocate_monitor_page(&self, vtl: Vtl) -> anyhow::Result<Option<u64>> {
1180 let vtl = GuestVtl::try_from(vtl).unwrap();
1181
1182 let Some(state) = self.backing_shared.cvm_state() else {
1184 return Ok(None);
1185 };
1186
1187 let mut allocated_block = self.allocated_monitor_page.lock();
1188 if let Some(block) = allocated_block.as_ref() {
1189 let gpa = block.pfns()[0] << HV_PAGE_SHIFT;
1191 assert_eq!(self.monitor_page.gpa(), Some(gpa));
1192 return Ok(Some(gpa));
1193 }
1194
1195 let block = state
1196 .private_dma_client
1197 .allocate_dma_buffer(HV_PAGE_SIZE_USIZE)
1198 .context("failed to allocate monitor page")?;
1199
1200 let gpn = block.pfns()[0];
1201 *allocated_block = Some(block);
1202 let gpa = gpn << HV_PAGE_SHIFT;
1203 let old_gpa = self.monitor_page.set_gpa(Some(gpa));
1204 if let Some(old_gpa) = old_gpa {
1205 self.hcl
1208 .modify_vtl_protection_mask(
1209 MemoryRange::new(old_gpa..old_gpa + HV_PAGE_SIZE),
1210 hvdef::HV_MAP_GPA_PERMISSIONS_ALL,
1211 HvInputVtl::CURRENT_VTL,
1212 )
1213 .context("failed to unregister old monitor page")
1214 .inspect_err(|_| {
1215 self.monitor_page.set_gpa(None);
1217 })?;
1218
1219 tracing::debug!(old_gpa, "unregistered monitor page");
1220 }
1221
1222 self.register_cvm_dma_overlay_page(vtl, gpn, HvMapGpaFlags::new().with_readable(true))
1225 .context("failed to unregister monitor page")
1226 .inspect_err(|_| {
1227 self.monitor_page.set_gpa(None);
1229 })?;
1230
1231 tracing::debug!(gpa, "registered allocated monitor page");
1232
1233 Ok(Some(gpa))
1234 }
1235}
1236
1237impl UhPartitionInner {
1238 #[cfg(guest_arch = "x86_64")]
1239 pub(crate) fn synic_interrupt(
1240 &self,
1241 vp_index: VpIndex,
1242 vtl: GuestVtl,
1243 ) -> impl '_ + hv1_emulator::RequestInterrupt {
1244 move |vector, auto_eoi| {
1247 self.lapic(vtl).unwrap().synic_interrupt(
1248 vp_index,
1249 vector as u8,
1250 auto_eoi,
1251 |vp_index| self.vp(vp_index).unwrap().wake(vtl, WakeReason::INTCON),
1252 );
1253 }
1254 }
1255
1256 #[cfg(guest_arch = "aarch64")]
1257 fn synic_interrupt(
1258 &self,
1259 _vp_index: VpIndex,
1260 _vtl: GuestVtl,
1261 ) -> impl '_ + hv1_emulator::RequestInterrupt {
1262 move |_, _| {}
1263 }
1264}
1265
1266#[derive(Debug)]
1267struct UhEventPort {
1268 partition: Weak<UhPartitionInner>,
1269 params: Arc<Mutex<UhEventPortParams>>,
1270}
1271
1272#[derive(Debug, Copy, Clone)]
1273struct UhEventPortParams {
1274 vp: VpIndex,
1275 sint: u8,
1276 flag: u16,
1277 vtl: GuestVtl,
1278}
1279
1280impl vmcore::synic::GuestEventPort for UhEventPort {
1281 fn interrupt(&self) -> vmcore::interrupt::Interrupt {
1282 let partition = self.partition.clone();
1283 let params = self.params.clone();
1284 vmcore::interrupt::Interrupt::from_fn(move || {
1285 let UhEventPortParams {
1286 vp,
1287 sint,
1288 flag,
1289 vtl,
1290 } = *params.lock();
1291 let Some(partition) = partition.upgrade() else {
1292 return;
1293 };
1294 tracing::trace!(vp = vp.index(), sint, flag, "signal_event");
1295 if let Some(hv) = partition.hv() {
1296 match hv.synic[vtl].signal_event(
1297 vp,
1298 sint,
1299 flag,
1300 &mut partition.synic_interrupt(vp, vtl),
1301 ) {
1302 Ok(_) => {}
1303 Err(SintProxied) => {
1304 tracing::trace!(
1305 vp = vp.index(),
1306 sint,
1307 flag,
1308 "forwarding event to untrusted synic"
1309 );
1310 if let Some(synic) = partition.backing_shared.untrusted_synic() {
1311 synic
1312 .signal_event(
1313 vp,
1314 sint,
1315 flag,
1316 &mut partition.synic_interrupt(vp, vtl),
1317 )
1318 .ok();
1319 } else {
1320 partition.hcl.signal_event_direct(vp.index(), sint, flag)
1321 }
1322 }
1323 }
1324 } else {
1325 partition.hcl.signal_event_direct(vp.index(), sint, flag);
1326 }
1327 })
1328 }
1329
1330 fn set_target_vp(&mut self, vp: u32) -> Result<(), vmcore::synic::HypervisorError> {
1331 self.params.lock().vp = VpIndex::new(vp);
1332 Ok(())
1333 }
1334}
1335
1336impl virt::Hv1 for UhPartition {
1337 type Error = Error;
1338 type Device = virt::x86::apic_software_device::ApicSoftwareDevice;
1339
1340 fn reference_time_source(&self) -> Option<ReferenceTimeSource> {
1341 Some(if let Some(hv) = self.inner.hv() {
1342 hv.ref_time_source().clone()
1343 } else {
1344 ReferenceTimeSource::from(self.inner.clone() as Arc<_>)
1345 })
1346 }
1347
1348 fn new_virtual_device(
1349 &self,
1350 ) -> Option<&dyn virt::DeviceBuilder<Device = Self::Device, Error = Self::Error>> {
1351 self.inner.software_devices.is_some().then_some(self)
1352 }
1353
1354 fn synic(&self) -> anyhow::Result<Arc<dyn vmcore::synic::SynicPortAccess>> {
1355 Ok(self.synic_ports.clone())
1356 }
1357}
1358
1359impl GetReferenceTime for UhPartitionInner {
1360 fn now(&self) -> ReferenceTimeResult {
1361 ReferenceTimeResult {
1362 ref_time: self.hcl.reference_time().unwrap(),
1363 system_time: None,
1364 }
1365 }
1366}
1367
1368impl virt::DeviceBuilder for UhPartition {
1369 fn build(&self, vtl: Vtl, device_id: u64) -> Result<Self::Device, Self::Error> {
1370 let vtl = GuestVtl::try_from(vtl).expect("higher vtl not configured");
1371 let device = self
1372 .inner
1373 .software_devices
1374 .as_ref()
1375 .expect("checked in new_virtual_device")
1376 .new_device(self.interrupt_targets[vtl].clone(), device_id)
1377 .map_err(Error::NewDevice)?;
1378
1379 Ok(device)
1380 }
1381}
1382
1383struct UhInterruptTarget {
1384 partition: Arc<UhPartitionInner>,
1385 vtl: GuestVtl,
1386}
1387
1388impl pci_core::msi::SignalMsi for UhInterruptTarget {
1389 fn signal_msi(&self, _devid: Option<u32>, address: u64, data: u32) {
1390 self.partition
1391 .request_msi(self.vtl, MsiRequest { address, data });
1392 }
1393}
1394
1395impl UhPartitionInner {
1396 #[cfg(guest_arch = "x86_64")]
1397 fn request_msi(&self, vtl: GuestVtl, request: MsiRequest) {
1398 if let Some(lapic) = self.lapic(vtl) {
1399 tracing::trace!(?request, "interrupt");
1400 lapic.request_interrupt(request.address, request.data, |vp_index| {
1401 self.vp(vp_index).unwrap().wake(vtl, WakeReason::INTCON)
1402 });
1403 } else {
1404 let (address, data) = request.as_x86();
1405 if let Err(err) = self.hcl.request_interrupt(
1406 request.hv_x86_interrupt_control(),
1407 address.virt_destination().into(),
1408 data.vector().into(),
1409 vtl,
1410 ) {
1411 tracelimit::warn_ratelimited!(
1412 CVM_ALLOWED,
1413 error = &err as &dyn std::error::Error,
1414 address = request.address,
1415 data = request.data,
1416 "failed to request msi"
1417 );
1418 }
1419 }
1420 }
1421
1422 #[cfg(guest_arch = "aarch64")]
1423 fn request_msi(&self, vtl: GuestVtl, request: MsiRequest) {
1424 match self.isolation {
1425 IsolationType::Cca => {
1426 tracelimit::warn_ratelimited!(
1427 ?vtl,
1428 ?request,
1429 "ignoring MSI request for CCA on aarch64: MSI routing is not implemented"
1430 );
1431 }
1432 _ => unimplemented!("MSI routing is not implemented on aarch64"),
1433 }
1434 }
1435}
1436
1437impl IoApicRouting for UhPartitionInner {
1438 fn set_irq_route(&self, irq: u8, request: Option<MsiRequest>) {
1439 self.irq_routes.set_irq_route(irq, request)
1440 }
1441
1442 fn assert_irq(&self, irq: u8) {
1444 self.irq_routes
1445 .assert_irq(irq, |request| self.request_msi(GuestVtl::Vtl0, request))
1446 }
1447}
1448
1449#[cfg(target_arch = "x86_64")]
1451fn is_restore_partition_time_available() -> bool {
1452 let result =
1453 safe_intrinsics::cpuid(hvdef::HV_CPUID_FUNCTION_MS_HV_ENLIGHTENMENT_INFORMATION, 0);
1454 let enlightenment_info = hvdef::HvEnlightenmentInformation::from(
1455 result.eax as u128
1456 | (result.ebx as u128) << 32
1457 | (result.ecx as u128) << 64
1458 | (result.edx as u128) << 96,
1459 );
1460 enlightenment_info.restore_time_on_resume()
1461}
1462#[cfg(not(target_arch = "x86_64"))]
1464fn is_restore_partition_time_available() -> bool {
1465 false
1467}
1468
1469fn set_vtl2_vsm_partition_config(hcl: &Hcl) -> Result<(), Error> {
1472 if hcl.isolation() == hcl::ioctl::IsolationType::Cca {
1474 tracing::warn!("cca: set_vtl2_vsm_partition_config: do nothing now");
1475 return Ok(());
1476 }
1477 let caps = hcl.get_vsm_capabilities().map_err(Error::GetReg)?;
1479 let hardware_isolated = hcl.isolation().is_hardware_isolated();
1480 let isolated = hcl.isolation().is_isolated();
1481 let config = HvRegisterVsmPartitionConfig::new()
1482 .with_default_vtl_protection_mask(0xF)
1483 .with_enable_vtl_protection(!hardware_isolated)
1484 .with_zero_memory_on_reset(!hardware_isolated)
1485 .with_intercept_cpuid_unimplemented(!hardware_isolated)
1486 .with_intercept_page(caps.intercept_page_available())
1487 .with_intercept_unrecoverable_exception(true)
1488 .with_intercept_not_present(caps.intercept_not_present_available() && !isolated)
1489 .with_intercept_acceptance(isolated)
1490 .with_intercept_enable_vtl_protection(isolated && !hardware_isolated)
1491 .with_intercept_system_reset(caps.intercept_system_reset_available())
1492 .with_intercept_restore_partition_time(is_restore_partition_time_available());
1493
1494 hcl.set_vtl2_vsm_partition_config(config)
1495 .map_err(Error::SetReg)
1496}
1497
1498pub struct UhPartitionNewParams<'a> {
1502 pub isolation: IsolationType,
1504 pub hide_isolation: bool,
1507 pub lower_vtl_memory_layout: &'a MemoryLayout,
1509 pub topology: &'a ProcessorTopology,
1511 pub cvm_cpuid_info: Option<&'a [u8]>,
1514 pub snp_secrets: Option<&'a [u8]>,
1516 pub vtom: Option<u64>,
1520 pub handle_synic: bool,
1524 pub no_sidecar_hotplug: bool,
1527 pub use_mmio_hypercalls: bool,
1529 pub intercept_debug_exceptions: bool,
1531 pub disable_proxy_redirect: bool,
1533 pub disable_lower_vtl_timer_virt: bool,
1535}
1536
1537pub struct UhLateParams<'a> {
1539 pub gm: VtlArray<GuestMemory, 2>,
1541 pub vtl0_kernel_exec_gm: GuestMemory,
1543 pub vtl0_user_exec_gm: GuestMemory,
1545 #[cfg(guest_arch = "x86_64")]
1547 pub cpuid: Vec<CpuidLeaf>,
1548 pub crash_notification_send: mesh::Sender<VtlCrash>,
1551 pub vmtime: &'a VmTimeSource,
1553 pub cvm_params: Option<CvmLateParams>,
1555 pub vmbus_relay: bool,
1557}
1558
1559pub struct CvmLateParams {
1561 pub shared_gm: GuestMemory,
1563 pub isolated_memory_protector: Arc<dyn ProtectIsolatedMemory>,
1565 pub shared_dma_client: Arc<dyn DmaClient>,
1567 pub private_dma_client: Arc<dyn DmaClient>,
1569}
1570
1571#[derive(Debug, Copy, Clone, PartialEq, Eq)]
1573pub enum GpnSource {
1574 GuestMemory,
1576 Dma,
1578}
1579
1580pub trait ProtectIsolatedMemory: Send + Sync {
1582 fn change_host_visibility(
1584 &self,
1585 vtl: GuestVtl,
1586 shared: bool,
1587 gpns: &[u64],
1588 tlb_access: &mut dyn TlbFlushLockAccess,
1589 ) -> Result<(), (HvError, usize)>;
1590
1591 fn query_host_visibility(
1593 &self,
1594 gpns: &[u64],
1595 host_visibility: &mut [HostVisibilityType],
1596 ) -> Result<(), (HvError, usize)>;
1597
1598 fn default_vtl0_protections(&self) -> HvMapGpaFlags;
1600
1601 fn change_default_vtl_protections(
1605 &self,
1606 target_vtl: GuestVtl,
1607 protections: HvMapGpaFlags,
1608 tlb_access: &mut dyn TlbFlushLockAccess,
1609 ) -> Result<(), HvError>;
1610
1611 fn change_vtl_protections(
1613 &self,
1614 target_vtl: GuestVtl,
1615 gpns: &[u64],
1616 protections: HvMapGpaFlags,
1617 tlb_access: &mut dyn TlbFlushLockAccess,
1618 ) -> Result<(), (HvError, usize)>;
1619
1620 fn register_overlay_page(
1623 &self,
1624 vtl: GuestVtl,
1625 gpn: u64,
1626 gpn_source: GpnSource,
1627 check_perms: HvMapGpaFlags,
1628 new_perms: Option<HvMapGpaFlags>,
1629 tlb_access: &mut dyn TlbFlushLockAccess,
1630 ) -> Result<(), HvError>;
1631
1632 fn unregister_overlay_page(
1635 &self,
1636 vtl: GuestVtl,
1637 gpn: u64,
1638 tlb_access: &mut dyn TlbFlushLockAccess,
1639 ) -> Result<(), HvError>;
1640
1641 fn is_overlay_page(&self, vtl: GuestVtl, gpn: u64) -> bool;
1643
1644 fn lock_gpns(&self, vtl: GuestVtl, gpns: &[u64]) -> Result<(), GuestMemoryBackingError>;
1646
1647 fn unlock_gpns(&self, vtl: GuestVtl, gpns: &[u64]);
1653
1654 fn set_vtl1_protections_enabled(&self);
1657
1658 fn vtl1_protections_enabled(&self) -> bool;
1661}
1662
1663pub trait TlbFlushLockAccess {
1665 fn flush(&mut self, vtl: GuestVtl);
1667
1668 fn flush_entire(&mut self);
1670
1671 fn set_wait_for_tlb_locks(&mut self, vtl: GuestVtl);
1673}
1674
1675pub struct UhProtoPartition<'a> {
1678 params: UhPartitionNewParams<'a>,
1679 hcl: Hcl,
1680 guest_vsm_available: bool,
1681 create_partition_available: bool,
1682 tdx_hw_seal_keys_enabled: bool,
1683 #[cfg(guest_arch = "x86_64")]
1684 cpuid: virt::CpuidLeafSet,
1685}
1686
1687impl<'a> UhProtoPartition<'a> {
1688 pub fn new<T: SpawnDriver>(
1693 params: &UhPartitionNewParams<'a>,
1694 driver: impl FnMut(u32) -> T,
1695 ) -> Result<Self, Error> {
1696 let hcl_isolation = match params.isolation {
1697 IsolationType::None => hcl::ioctl::IsolationType::None,
1698 IsolationType::Vbs => hcl::ioctl::IsolationType::Vbs,
1699 IsolationType::Snp => hcl::ioctl::IsolationType::Snp,
1700 IsolationType::Tdx => hcl::ioctl::IsolationType::Tdx,
1701 IsolationType::Cca => hcl::ioctl::IsolationType::Cca,
1702 };
1703
1704 let sidecar = sidecar_client::SidecarClient::new(driver).map_err(Error::Sidecar)?;
1706
1707 let hcl = Hcl::new(hcl_isolation, sidecar).map_err(Error::Hcl)?;
1708
1709 let mut allowed_hypercalls = vec![
1711 hvdef::HypercallCode::HvCallGetVpRegisters,
1712 hvdef::HypercallCode::HvCallSetVpRegisters,
1713 hvdef::HypercallCode::HvCallInstallIntercept,
1714 hvdef::HypercallCode::HvCallTranslateVirtualAddress,
1715 hvdef::HypercallCode::HvCallPostMessageDirect,
1716 hvdef::HypercallCode::HvCallSignalEventDirect,
1717 hvdef::HypercallCode::HvCallModifyVtlProtectionMask,
1718 hvdef::HypercallCode::HvCallTranslateVirtualAddressEx,
1719 hvdef::HypercallCode::HvCallCheckSparseGpaPageVtlAccess,
1720 hvdef::HypercallCode::HvCallAssertVirtualInterrupt,
1721 hvdef::HypercallCode::HvCallGetVpIndexFromApicId,
1722 hvdef::HypercallCode::HvCallAcceptGpaPages,
1723 hvdef::HypercallCode::HvCallModifySparseGpaPageHostVisibility,
1724 ];
1725
1726 if params.isolation.is_hardware_isolated() {
1727 allowed_hypercalls.extend(vec![
1728 hvdef::HypercallCode::HvCallEnablePartitionVtl,
1729 hvdef::HypercallCode::HvCallRetargetDeviceInterrupt,
1730 hvdef::HypercallCode::HvCallEnableVpVtl,
1731 ]);
1732 }
1733
1734 if params.use_mmio_hypercalls {
1735 allowed_hypercalls.extend(vec![
1736 hvdef::HypercallCode::HvCallMemoryMappedIoRead,
1737 hvdef::HypercallCode::HvCallMemoryMappedIoWrite,
1738 ]);
1739 }
1740
1741 hcl.set_allowed_hypercalls(allowed_hypercalls.as_slice());
1742
1743 set_vtl2_vsm_partition_config(&hcl)?;
1744
1745 let tdx_hw_seal_keys_enabled = if params.isolation == IsolationType::Tdx {
1758 match hcl.tdx_read_features0() {
1766 Ok(features0) => {
1767 tracing::info!(
1768 CVM_ALLOWED,
1769 sealing = features0.sealing(),
1770 td_signing_and_svn = features0.td_signing_and_svn(),
1771 sealkey_128 = features0.sealkey_128(),
1772 "TDX_FEATURES0 sealing capability"
1773 );
1774 }
1775 Err(err) => {
1776 tracing::warn!(
1777 CVM_ALLOWED,
1778 error = u64::from(err),
1779 "failed to read TDX_FEATURES0 (TDG.SYS.RD unsupported?)"
1780 );
1781 }
1782 }
1783
1784 match hcl.tdx_enable_hw_seal_keys() {
1785 Ok(true) => {
1786 tracing::info!(CVM_ALLOWED, "TDX hardware-bound seal keys enabled");
1787 true
1788 }
1789 Ok(false) => {
1790 tracing::info!(
1791 CVM_ALLOWED,
1792 "TDX hardware-bound seal keys not supported by the TDX module"
1793 );
1794 false
1795 }
1796 Err(err) => {
1797 tracing::warn!(
1798 CVM_ALLOWED,
1799 error = u64::from(err),
1800 "failed to enable TDX hardware-bound seal keys"
1801 );
1802 false
1803 }
1804 }
1805 } else {
1806 false
1807 };
1808
1809 let privs = hcl
1810 .get_privileges_and_features_info()
1811 .map_err(Error::GetReg)?;
1812 let guest_vsm_available = Self::check_guest_vsm_support(privs, &hcl)?;
1813
1814 #[cfg(guest_arch = "x86_64")]
1815 let cpuid = match params.isolation {
1816 IsolationType::Snp => {
1817 let secure_avic = x86defs::snp::SevStatusMsr::from(
1818 MsrDevice::new(0)
1819 .expect("open msr")
1820 .read_msr(x86defs::X86X_AMD_MSR_SEV)
1821 .expect("read msr"),
1822 )
1823 .secure_avic();
1824 cvm_cpuid::CpuidResultsIsolationType::Snp {
1825 cpuid_pages: params.cvm_cpuid_info.unwrap(),
1826 vtom: params.vtom.unwrap(),
1827 access_vsm: guest_vsm_available,
1828 secure_avic,
1829 }
1830 .build()
1831 .map_err(Error::CvmCpuid)?
1832 }
1833
1834 IsolationType::Tdx => cvm_cpuid::CpuidResultsIsolationType::Tdx {
1835 topology: params.topology,
1836 vtom: params.vtom.unwrap(),
1837 access_vsm: guest_vsm_available,
1838 }
1839 .build()
1840 .map_err(Error::CvmCpuid)?,
1841 IsolationType::Vbs | IsolationType::None => virt::CpuidLeafSet::new(Vec::new()),
1842 IsolationType::Cca => unreachable!(),
1845 };
1846
1847 let vtom = match params.isolation {
1848 #[cfg(guest_arch = "aarch64")]
1849 IsolationType::Cca if params.vtom.is_none() => {
1850 let realm_config = hcl.get_realm_config().map_err(Error::Hcl)?;
1852 Some(1_u64 << (realm_config.ipa_width() - 1))
1853 }
1854 _ => params.vtom,
1855 };
1856
1857 Ok(UhProtoPartition {
1858 hcl,
1859 params: UhPartitionNewParams { vtom, ..*params },
1860 guest_vsm_available,
1861 create_partition_available: privs.create_partitions(),
1862 tdx_hw_seal_keys_enabled,
1863 #[cfg(guest_arch = "x86_64")]
1864 cpuid,
1865 })
1866 }
1867
1868 pub fn guest_vsm_available(&self) -> bool {
1870 self.guest_vsm_available
1871 }
1872
1873 pub fn create_partition_available(&self) -> bool {
1876 self.create_partition_available
1877 }
1878
1879 pub fn tdx_hw_seal_keys_enabled(&self) -> bool {
1883 self.tdx_hw_seal_keys_enabled
1884 }
1885
1886 pub async fn build(
1888 self,
1889 late_params: UhLateParams<'_>,
1890 ) -> Result<(UhPartition, Vec<UhProcessorBox>), Error> {
1891 let Self {
1892 mut hcl,
1893 params,
1894 guest_vsm_available,
1895 create_partition_available: _,
1896 tdx_hw_seal_keys_enabled: _,
1897 #[cfg(guest_arch = "x86_64")]
1898 cpuid,
1899 } = self;
1900 let isolation = params.isolation;
1901 let is_hardware_isolated = isolation.is_hardware_isolated();
1902
1903 if params.intercept_debug_exceptions {
1909 if !cfg!(feature = "gdb") {
1910 return Err(Error::InvalidDebugConfiguration);
1911 }
1912
1913 cfg_if::cfg_if! {
1914 if #[cfg(guest_arch = "x86_64")] {
1915 if isolation != IsolationType::Tdx {
1916 let debug_exception_vector = 0x1;
1917 hcl.register_intercept(
1918 HvInterceptType::HvInterceptTypeException,
1919 HV_INTERCEPT_ACCESS_MASK_EXECUTE,
1920 HvInterceptParameters::new_exception(debug_exception_vector),
1921 )
1922 .map_err(|err| Error::InstallIntercept(HvInterceptType::HvInterceptTypeException, err))?;
1923 }
1924 } else {
1925 return Err(Error::InvalidDebugConfiguration);
1926 }
1927 }
1928 }
1929
1930 if !is_hardware_isolated {
1931 if cfg!(guest_arch = "x86_64") {
1932 hcl.register_intercept(
1933 HvInterceptType::HvInterceptTypeX64Msr,
1934 HV_INTERCEPT_ACCESS_MASK_READ_WRITE,
1935 HvInterceptParameters::new_zeroed(),
1936 )
1937 .map_err(|err| {
1938 Error::InstallIntercept(HvInterceptType::HvInterceptTypeX64Msr, err)
1939 })?;
1940
1941 hcl.register_intercept(
1942 HvInterceptType::HvInterceptTypeX64ApicEoi,
1943 HV_INTERCEPT_ACCESS_MASK_WRITE,
1944 HvInterceptParameters::new_zeroed(),
1945 )
1946 .map_err(|err| {
1947 Error::InstallIntercept(HvInterceptType::HvInterceptTypeX64ApicEoi, err)
1948 })?;
1949 } else {
1950 if false {
1951 todo!("AARCH64_TODO");
1952 }
1953 }
1954 }
1955
1956 if isolation == IsolationType::Snp {
1957 hcl.register_intercept(
1959 HvInterceptType::HvInterceptTypeException,
1960 HV_INTERCEPT_ACCESS_MASK_EXECUTE,
1961 HvInterceptParameters::new_exception(
1962 x86defs::Exception::SEV_VMM_COMMUNICATION.0 as u16,
1963 ),
1964 )
1965 .map_err(|err| {
1966 Error::InstallIntercept(HvInterceptType::HvInterceptTypeException, err)
1967 })?;
1968
1969 let mut bitmap = [0u8; 64];
1971 if let Some(secrets) = params.snp_secrets {
1972 bitmap.copy_from_slice(
1973 &secrets
1974 [REG_TWEAK_BITMAP_OFFSET..REG_TWEAK_BITMAP_OFFSET + REG_TWEAK_BITMAP_SIZE],
1975 );
1976 }
1977 hcl.set_snp_register_bitmap(bitmap);
1978 }
1979
1980 hcl.add_vps(
1982 params.topology.vp_count(),
1983 late_params
1984 .cvm_params
1985 .as_ref()
1986 .map(|x| &x.private_dma_client),
1987 )
1988 .map_err(Error::Hcl)?;
1989
1990 let vps: Vec<_> = params
1991 .topology
1992 .vps_arch()
1993 .map(|vp_info| {
1994 let cpu_index = vp_info.base.vp_index.index();
1997 UhVpInner::new(cpu_index, vp_info)
1998 })
1999 .collect();
2000
2001 #[cfg(guest_arch = "x86_64")]
2003 let software_devices = {
2004 let res = if !is_hardware_isolated {
2005 hcl.register_intercept(
2006 HvInterceptType::HvInterceptTypeRetargetInterruptWithUnknownDeviceId,
2007 HV_INTERCEPT_ACCESS_MASK_EXECUTE,
2008 HvInterceptParameters::new_zeroed(),
2009 )
2010 } else {
2011 Ok(())
2012 };
2013 match res {
2014 Ok(()) => Some(ApicSoftwareDevices::new(
2015 params.topology.vps_arch().map(|vp| vp.apic_id).collect(),
2016 )),
2017 Err(HvError::InvalidParameter | HvError::AccessDenied) => None,
2018 Err(err) => {
2019 return Err(Error::InstallIntercept(
2020 HvInterceptType::HvInterceptTypeRetargetInterruptWithUnknownDeviceId,
2021 err,
2022 ));
2023 }
2024 }
2025 };
2026
2027 #[cfg(guest_arch = "aarch64")]
2029 let software_devices = None;
2030
2031 #[cfg(guest_arch = "aarch64")]
2032 let caps = virt::aarch64::Aarch64PartitionCapabilities {
2033 isolation: params.isolation,
2034 vendor: Vendor::ARM,
2035 supports_aarch32_el0: false,
2036 };
2037
2038 #[cfg(guest_arch = "x86_64")]
2039 let cpuid = UhPartition::construct_cpuid_results(
2040 cpuid,
2041 &late_params.cpuid,
2042 params.topology,
2043 isolation,
2044 params.hide_isolation,
2045 );
2046
2047 #[cfg(guest_arch = "x86_64")]
2048 let caps = UhPartition::construct_capabilities(
2049 params.topology,
2050 &cpuid,
2051 isolation,
2052 params.hide_isolation,
2053 )
2054 .map_err(Error::Capabilities)?;
2055
2056 if params.handle_synic && !matches!(isolation, IsolationType::Tdx) {
2057 hcl.register_intercept(
2065 HvInterceptType::HvInterceptTypeUnknownSynicConnection,
2066 HV_INTERCEPT_ACCESS_MASK_EXECUTE,
2067 HvInterceptParameters::new_zeroed(),
2068 )
2069 .expect("registering synic intercept cannot fail");
2070 }
2071
2072 let cvm_state = if is_hardware_isolated {
2073 let vsm_caps = hcl.get_vsm_capabilities().map_err(Error::GetReg)?;
2074 let proxy_interrupt_redirect_available =
2075 vsm_caps.proxy_interrupt_redirect_available() && !params.disable_proxy_redirect;
2076
2077 Some(Self::construct_cvm_state(
2078 ¶ms,
2079 late_params.cvm_params.unwrap(),
2080 &caps,
2081 guest_vsm_available,
2082 proxy_interrupt_redirect_available,
2083 )?)
2084 } else {
2085 None
2086 };
2087
2088 let lower_vtl_timer_virt_available =
2089 hcl.supports_lower_vtl_timer_virt() && !params.disable_lower_vtl_timer_virt;
2090
2091 let backing_shared = BackingShared::new(
2092 isolation,
2093 ¶ms,
2094 BackingSharedParams {
2095 cvm_state,
2096 #[cfg(guest_arch = "x86_64")]
2097 cpuid: &cpuid,
2098 hcl: &hcl,
2099 guest_vsm_available,
2100 lower_vtl_timer_virt_available,
2101 },
2102 )?;
2103
2104 let enter_modes = EnterModes::default();
2105
2106 let partition = Arc::new(UhPartitionInner {
2107 hcl,
2108 vps,
2109 irq_routes: Default::default(),
2110 caps,
2111 enter_modes: Mutex::new(enter_modes),
2112 enter_modes_atomic: u8::from(hcl::protocol::EnterModes::from(enter_modes)).into(),
2113 gm: late_params.gm,
2114 vtl0_kernel_exec_gm: late_params.vtl0_kernel_exec_gm,
2115 vtl0_user_exec_gm: late_params.vtl0_user_exec_gm,
2116 #[cfg(guest_arch = "x86_64")]
2117 cpuid,
2118 crash_notification_send: late_params.crash_notification_send,
2119 monitor_page: MonitorPage::new(),
2120 allocated_monitor_page: Mutex::new(None),
2121 software_devices,
2122 lower_vtl_memory_layout: params.lower_vtl_memory_layout.clone(),
2123 vmtime: late_params.vmtime.clone(),
2124 isolation,
2125 no_sidecar_hotplug: params.no_sidecar_hotplug.into(),
2126 use_mmio_hypercalls: params.use_mmio_hypercalls,
2127 backing_shared,
2128 #[cfg(guest_arch = "x86_64")]
2129 device_vector_table: RwLock::new(IrrBitmap::new(Default::default())),
2130 intercept_debug_exceptions: params.intercept_debug_exceptions,
2131 vmbus_relay: late_params.vmbus_relay,
2132 synic_ports: Default::default(),
2133 });
2134
2135 if cfg!(guest_arch = "x86_64") {
2136 partition.manage_io_port_intercept_region(0, !0, true);
2138 }
2139
2140 let vps = params
2141 .topology
2142 .vps_arch()
2143 .map(|vp_info| UhProcessorBox {
2144 partition: partition.clone(),
2145 vp_info,
2146 })
2147 .collect();
2148
2149 Ok((
2150 UhPartition {
2151 synic_ports: Arc::new(virt::synic::SynicPorts::new(partition.clone())),
2152 inner: partition.clone(),
2153 interrupt_targets: VtlArray::from_fn(|vtl| {
2154 Arc::new(UhInterruptTarget {
2155 partition: partition.clone(),
2156 vtl: vtl.try_into().unwrap(),
2157 })
2158 }),
2159 },
2160 vps,
2161 ))
2162 }
2163}
2164
2165impl UhPartition {
2166 pub fn vtl0_guest_os_id(&self) -> Result<HvGuestOsId, hcl::ioctl::register::GetRegError> {
2168 let id = if let Some(hv) = self.inner.hv() {
2172 hv.guest_os_id(Vtl::Vtl0)
2173 } else {
2174 self.inner.hcl.get_guest_os_id(GuestVtl::Vtl0)?
2176 };
2177 Ok(id)
2178 }
2179
2180 pub fn register_host_io_port_fast_path(
2185 &self,
2186 range: RangeInclusive<u16>,
2187 ) -> HostIoPortFastPathHandle {
2188 assert!(!self.inner.isolation.is_hardware_isolated());
2192
2193 self.inner
2194 .manage_io_port_intercept_region(*range.start(), *range.end(), false);
2195 HostIoPortFastPathHandle {
2196 inner: Arc::downgrade(&self.inner),
2197 begin: *range.start(),
2198 end: *range.end(),
2199 }
2200 }
2201
2202 #[cfg(guest_arch = "x86_64")]
2204 pub fn assert_debug_interrupt(&self, vtl: Vtl) {
2205 const LINT_INDEX_1: u8 = 1;
2206 if self.inner.isolation == IsolationType::Snp {
2211 let vnmi = match &self.inner.backing_shared {
2212 BackingShared::Snp(snp) => snp.vnmi,
2213 _ => false,
2214 };
2215 if !vnmi {
2216 tracing::error!("debug interrupt is not supported on SNP without virtual NMI");
2217 return;
2218 }
2219 }
2220 let bsp_index = VpIndex::new(0);
2221 self.pulse_lint(bsp_index, vtl, LINT_INDEX_1);
2222 }
2223
2224 #[cfg(guest_arch = "aarch64")]
2226 pub fn assert_debug_interrupt(&self, _vtl: Vtl) {
2227 tracing::error!("debug interrupts are not supported on aarch64");
2228 }
2229
2230 pub fn set_pm_timer_assist(
2232 &self,
2233 port: Option<u16>,
2234 ) -> Result<(), hcl::ioctl::register::SetRegError> {
2235 self.inner.hcl.set_pm_timer_assist(port)
2236 }
2237}
2238
2239impl UhPartitionInner {
2240 fn register_cvm_dma_overlay_page(
2242 &self,
2243 vtl: GuestVtl,
2244 gpn: u64,
2245 new_perms: HvMapGpaFlags,
2246 ) -> anyhow::Result<()> {
2247 match &self.backing_shared {
2249 #[cfg(guest_arch = "x86_64")]
2250 BackingShared::Snp(snp_backed_shared) => snp_backed_shared
2251 .cvm
2252 .isolated_memory_protector
2253 .register_overlay_page(
2254 vtl,
2255 gpn,
2256 GpnSource::Dma,
2258 HvMapGpaFlags::new(),
2259 Some(new_perms),
2260 &mut SnpBacked::tlb_flush_lock_access(None, self, snp_backed_shared),
2261 )
2262 .map_err(|e| anyhow::anyhow!(e)),
2263 #[cfg(guest_arch = "x86_64")]
2264 BackingShared::Tdx(tdx_backed_shared) => tdx_backed_shared
2265 .cvm
2266 .isolated_memory_protector
2267 .register_overlay_page(
2268 vtl,
2269 gpn,
2270 GpnSource::Dma,
2271 HvMapGpaFlags::new(),
2272 Some(new_perms),
2273 &mut TdxBacked::tlb_flush_lock_access(None, self, tdx_backed_shared),
2274 )
2275 .map_err(|e| anyhow::anyhow!(e)),
2276 #[cfg(guest_arch = "aarch64")]
2277 BackingShared::Cca(cca_backed_shared) => cca_backed_shared
2278 .cvm
2279 .isolated_memory_protector
2280 .register_overlay_page(
2281 vtl,
2282 gpn,
2283 GpnSource::Dma,
2284 HvMapGpaFlags::new(),
2285 Some(new_perms),
2286 &mut CcaBacked::tlb_flush_lock_access(None, self, cca_backed_shared),
2287 )
2288 .map_err(|e| anyhow::anyhow!(e)),
2289 BackingShared::Hypervisor(_) => {
2290 let _ = (vtl, gpn, new_perms);
2291 unreachable!()
2292 }
2293 }
2294 }
2295
2296 fn unregister_cvm_dma_overlay_page(&self, vtl: GuestVtl, gpn: u64) -> anyhow::Result<()> {
2298 match &self.backing_shared {
2300 #[cfg(guest_arch = "x86_64")]
2301 BackingShared::Snp(snp_backed_shared) => snp_backed_shared
2302 .cvm
2303 .isolated_memory_protector
2304 .unregister_overlay_page(
2305 vtl,
2306 gpn,
2307 &mut SnpBacked::tlb_flush_lock_access(None, self, snp_backed_shared),
2308 )
2309 .map_err(|e| anyhow::anyhow!(e)),
2310 #[cfg(guest_arch = "x86_64")]
2311 BackingShared::Tdx(tdx_backed_shared) => tdx_backed_shared
2312 .cvm
2313 .isolated_memory_protector
2314 .unregister_overlay_page(
2315 vtl,
2316 gpn,
2317 &mut TdxBacked::tlb_flush_lock_access(None, self, tdx_backed_shared),
2318 )
2319 .map_err(|e| anyhow::anyhow!(e)),
2320 BackingShared::Hypervisor(_) => {
2321 let _ = (vtl, gpn);
2322 unreachable!()
2323 }
2324 #[cfg(guest_arch = "aarch64")]
2325 BackingShared::Cca(cca_backed_shared) => cca_backed_shared
2326 .cvm
2327 .isolated_memory_protector
2328 .unregister_overlay_page(
2329 vtl,
2330 gpn,
2331 &mut CcaBacked::tlb_flush_lock_access(None, self, cca_backed_shared),
2332 )
2333 .map_err(|e| anyhow::anyhow!(e)),
2334 }
2335 }
2336}
2337
2338impl UhProtoPartition<'_> {
2339 fn check_guest_vsm_support(privs: HvPartitionPrivilege, hcl: &Hcl) -> Result<bool, Error> {
2342 if !privs.access_vsm() {
2343 return Ok(false);
2344 }
2345
2346 let guest_vsm_config = hcl
2347 .get_guest_vsm_partition_config()
2348 .map_err(Error::GetReg)?;
2349 Ok(guest_vsm_config.maximum_vtl() >= u8::from(GuestVtl::Vtl1))
2350 }
2351
2352 fn construct_cvm_state(
2354 params: &UhPartitionNewParams<'_>,
2355 late_params: CvmLateParams,
2356 caps: &PartitionCapabilities,
2357 guest_vsm_available: bool,
2358 proxy_interrupt_redirect_available: bool,
2359 ) -> Result<UhCvmPartitionState, Error> {
2360 use vmcore::reference_time::ReferenceTimeSource;
2361
2362 let vp_count = params.topology.vp_count() as usize;
2363 let vps = (0..vp_count)
2364 .map(|vp_index| UhCvmVpInner {
2365 tlb_lock_info: VtlArray::from_fn(|_| TlbLockInfo::new(vp_count)),
2366 vtl1_enable_called: Mutex::new(false),
2367 started: AtomicBool::new(vp_index == 0),
2368 hv_start_enable_vtl_vp: VtlArray::from_fn(|_| Mutex::new(None)),
2369 proxy_redirect_interrupts: Mutex::new(HashMap::new()),
2370 })
2371 .collect();
2372 let tlb_locked_vps =
2373 VtlArray::from_fn(|_| BitVec::repeat(false, vp_count).into_boxed_bitslice());
2374
2375 #[cfg(guest_arch = "x86_64")]
2376 let lapic = VtlArray::from_fn(|_| {
2377 LocalApicSet::builder()
2378 .x2apic_capable(caps.x2apic)
2379 .hyperv_enlightenments(true)
2380 .build()
2381 });
2382
2383 let tsc_frequency = get_tsc_frequency(params.isolation)?;
2384 let ref_time = ReferenceTimeSource::new(TscReferenceTimeSource::new(tsc_frequency));
2385
2386 let hv = GlobalHv::new(hv1_emulator::hv::GlobalHvParams {
2393 max_vp_count: params.topology.vp_count(),
2394 vendor: caps.vendor,
2395 tsc_frequency,
2396 ref_time,
2397 is_ref_time_backed_by_tsc: true,
2398 });
2399
2400 Ok(UhCvmPartitionState {
2401 #[cfg(guest_arch = "x86_64")]
2402 vps_per_socket: params.topology.reserved_vps_per_socket(),
2403 tlb_locked_vps,
2404 vps,
2405 shared_memory: late_params.shared_gm,
2406 isolated_memory_protector: late_params.isolated_memory_protector,
2407 #[cfg(guest_arch = "x86_64")]
2408 lapic,
2409 hv,
2410 guest_vsm: RwLock::new(GuestVsmState::from_availability(guest_vsm_available)),
2411 access_vsm_privilege: guest_vsm_available,
2412 shared_dma_client: late_params.shared_dma_client,
2413 private_dma_client: late_params.private_dma_client,
2414 hide_isolation: params.hide_isolation,
2415 proxy_interrupt_redirect: proxy_interrupt_redirect_available,
2416 })
2417 }
2418
2419 pub fn get_vtom(&self) -> Option<u64> {
2421 self.params.vtom
2422 }
2423}
2424
2425impl UhPartition {
2426 #[cfg(guest_arch = "x86_64")]
2427 fn construct_cpuid_results(
2429 cpuid: virt::CpuidLeafSet,
2430 initial_cpuid: &[CpuidLeaf],
2431 topology: &ProcessorTopology<vm_topology::processor::x86::X86Topology>,
2432 isolation: IsolationType,
2433 hide_isolation: bool,
2434 ) -> virt::CpuidLeafSet {
2435 let mut cpuid = cpuid.into_leaves();
2436 if isolation.is_hardware_isolated() {
2437 let x2apic = match topology.apic_mode() {
2439 vm_topology::processor::x86::ApicMode::XApic => false,
2440 vm_topology::processor::x86::ApicMode::X2ApicSupported => true,
2441 vm_topology::processor::x86::ApicMode::X2ApicEnabled => true,
2442 };
2443 let ecx = x86defs::cpuid::VersionAndFeaturesEcx::new().with_x2_apic(x2apic);
2444 let ecx_mask = x86defs::cpuid::VersionAndFeaturesEcx::new().with_x2_apic(true);
2445 cpuid.push(
2446 CpuidLeaf::new(
2447 x86defs::cpuid::CpuidFunction::VersionAndFeatures.0,
2448 [0, 0, ecx.into(), 0],
2449 )
2450 .masked([0, 0, ecx_mask.into(), 0]),
2451 );
2452
2453 let hv_version = safe_intrinsics::cpuid(hvdef::HV_CPUID_FUNCTION_MS_HV_VERSION, 0);
2457
2458 hv1_emulator::cpuid::process_hv_cpuid_leaves(
2460 &mut cpuid,
2461 hide_isolation,
2462 [
2463 hv_version.eax,
2464 hv_version.ebx,
2465 hv_version.ecx,
2466 hv_version.edx,
2467 ],
2468 );
2469 }
2470 cpuid.extend(initial_cpuid);
2471 virt::CpuidLeafSet::new(cpuid)
2472 }
2473
2474 #[cfg(guest_arch = "x86_64")]
2475 fn construct_capabilities(
2477 topology: &ProcessorTopology,
2478 cpuid: &virt::CpuidLeafSet,
2479 isolation: IsolationType,
2480 hide_isolation: bool,
2481 ) -> Result<virt::x86::X86PartitionCapabilities, virt::x86::X86PartitionCapabilitiesError> {
2482 let mut native_cpuid_fn;
2483 let mut cvm_cpuid_fn;
2484
2485 let cpuid_fn: &mut dyn FnMut(u32, u32) -> [u32; 4] = if isolation.is_hardware_isolated() {
2488 cvm_cpuid_fn = move |leaf, sub_leaf| cpuid.result(leaf, sub_leaf, &[0, 0, 0, 0]);
2490 &mut cvm_cpuid_fn
2491 } else {
2492 native_cpuid_fn = |leaf, sub_leaf| {
2494 let CpuidResult { eax, ebx, ecx, edx } = safe_intrinsics::cpuid(leaf, sub_leaf);
2495 cpuid.result(leaf, sub_leaf, &[eax, ebx, ecx, edx])
2496 };
2497 &mut native_cpuid_fn
2498 };
2499
2500 let mut caps = virt::x86::X86PartitionCapabilities::from_cpuid(topology, cpuid_fn)?;
2502 match isolation {
2503 IsolationType::Tdx => {
2504 assert_eq!(caps.vtom.is_some(), !hide_isolation);
2505 caps.nxe_forced_on = true;
2507 }
2508 IsolationType::Snp => {
2509 assert_eq!(caps.vtom.is_some(), !hide_isolation);
2510 }
2511 _ => {
2512 assert!(caps.vtom.is_none());
2513 }
2514 }
2515
2516 Ok(caps)
2517 }
2518}
2519
2520fn get_tsc_frequency(isolation: IsolationType) -> Result<u64, Error> {
2522 #[cfg(guest_arch = "x86_64")]
2525 let hv_frequency = {
2526 let msr = MsrDevice::new(0).map_err(Error::OpenMsr)?;
2527 msr.read_msr(hvdef::HV_X64_MSR_TSC_FREQUENCY)
2528 .map_err(Error::ReadTscFrequency)?
2529 };
2530 #[cfg(guest_arch = "aarch64")]
2531 let hv_frequency = read_cntfrq_el0();
2532
2533 let hw_info: Option<(u64, u64)> = match isolation {
2536 #[cfg(guest_arch = "x86_64")]
2537 IsolationType::Tdx => {
2538 let max_function =
2540 safe_intrinsics::cpuid(x86defs::cpuid::CpuidFunction::VendorAndMaxFunction.0, 0)
2541 .eax;
2542
2543 if max_function < x86defs::cpuid::CpuidFunction::CoreCrystalClockInformation.0 {
2544 return Err(Error::BadCpuidTsc);
2545 }
2546 let result = safe_intrinsics::cpuid(
2547 x86defs::cpuid::CpuidFunction::CoreCrystalClockInformation.0,
2548 0,
2549 );
2550 let ratio_denom = result.eax;
2551 let ratio_num = result.ebx;
2552 let clock = result.ecx;
2553 if ratio_num == 0 || ratio_denom == 0 || clock == 0 {
2554 return Err(Error::BadCpuidTsc);
2555 }
2556 let allowed_error = 12_500_000;
2559 Some((
2560 clock as u64 * ratio_num as u64 / ratio_denom as u64,
2561 allowed_error,
2562 ))
2563 }
2564 #[cfg(not(guest_arch = "x86_64"))]
2565 IsolationType::Tdx => None,
2566 IsolationType::Snp => {
2567 None
2569 }
2570 IsolationType::Vbs | IsolationType::None => None,
2571 IsolationType::Cca => {
2572 None
2574 }
2575 };
2576
2577 if let Some((hw_frequency, allowed_error)) = hw_info {
2578 let delta = hw_frequency.abs_diff(hv_frequency);
2581 if delta > allowed_error {
2582 return Err(Error::TscFrequencyMismatch {
2583 hv: hv_frequency,
2584 hw: hw_frequency,
2585 allowed_error,
2586 });
2587 }
2588 }
2589
2590 Ok(hv_frequency)
2591}
2592
2593impl UhPartitionInner {
2594 fn manage_io_port_intercept_region(&self, begin: u16, end: u16, active: bool) {
2595 if self.isolation.is_hardware_isolated() {
2596 return;
2597 }
2598
2599 static SKIP_RANGE: AtomicBool = AtomicBool::new(false);
2600
2601 let access_type_mask = if active {
2602 HV_INTERCEPT_ACCESS_MASK_READ_WRITE
2603 } else {
2604 HV_INTERCEPT_ACCESS_MASK_NONE
2605 };
2606
2607 if !SKIP_RANGE.load(Ordering::Relaxed) {
2609 match self.hcl.register_intercept(
2610 HvInterceptType::HvInterceptTypeX64IoPortRange,
2611 access_type_mask,
2612 HvInterceptParameters::new_io_port_range(begin..=end),
2613 ) {
2614 Ok(()) => return,
2615 Err(HvError::InvalidParameter) => {
2616 SKIP_RANGE.store(true, Ordering::Relaxed);
2619 tracing::warn!(
2620 CVM_ALLOWED,
2621 "old hypervisor build; using slow path for intercept ranges"
2622 );
2623 }
2624 Err(err) => {
2625 panic!("io port range registration failure: {err:?}");
2626 }
2627 }
2628 }
2629
2630 for port in begin..=end {
2632 self.hcl
2633 .register_intercept(
2634 HvInterceptType::HvInterceptTypeX64IoPort,
2635 access_type_mask,
2636 HvInterceptParameters::new_io_port(port),
2637 )
2638 .expect("registering io intercept cannot fail");
2639 }
2640 }
2641
2642 fn is_gpa_lower_vtl_ram(&self, gpa: u64) -> bool {
2643 self.lower_vtl_memory_layout
2647 .ram()
2648 .iter()
2649 .any(|m| m.range.contains_addr(gpa))
2650 }
2651
2652 fn is_gpa_mapped(&self, gpa: u64, write: bool) -> bool {
2653 if self.is_gpa_lower_vtl_ram(gpa) {
2657 !write || self.monitor_page.gpa() != Some(gpa & !(HV_PAGE_SIZE - 1))
2659 } else {
2660 false
2661 }
2662 }
2663}
2664
2665#[must_use]
2670pub struct HostIoPortFastPathHandle {
2671 inner: Weak<UhPartitionInner>,
2672 begin: u16,
2673 end: u16,
2674}
2675
2676impl Drop for HostIoPortFastPathHandle {
2677 fn drop(&mut self) {
2678 if let Some(inner) = self.inner.upgrade() {
2679 inner.manage_io_port_intercept_region(self.begin, self.end, true);
2680 }
2681 }
2682}
2683
2684#[derive(Copy, Clone, Debug)]
2689pub struct VtlCrash {
2690 pub vp_index: VpIndex,
2692 pub last_vtl: GuestVtl,
2694 pub control: GuestCrashCtl,
2696 pub parameters: [u64; 5],
2698}
2699
2700#[cfg_attr(guest_arch = "aarch64", expect(dead_code))]
2703fn validate_vtl_gpa_flags(
2704 flags: HvMapGpaFlags,
2705 mbec_enabled: bool,
2706 shadow_supervisor_stack_enabled: bool,
2707) -> bool {
2708 if flags.adjustable() {
2710 return false;
2711 }
2712
2713 if flags.kernel_executable() != flags.user_executable() {
2715 if (flags.kernel_executable() && !flags.user_executable()) || !mbec_enabled {
2716 return false;
2717 }
2718 }
2719
2720 if flags.writable()
2722 || flags.kernel_executable()
2723 || flags.user_executable()
2724 || flags.supervisor_shadow_stack()
2725 || flags.paging_writability()
2726 || flags.verify_paging_writability()
2727 {
2728 if !flags.readable() {
2729 return false;
2730 }
2731 }
2732
2733 if flags.supervisor_shadow_stack()
2736 && ((!flags.kernel_executable() && !flags.user_executable())
2737 || shadow_supervisor_stack_enabled)
2738 {
2739 return false;
2740 }
2741
2742 true
2743}