1#[cfg(target_arch = "x86_64")]
7use crate::arch::TdxHypercallPage;
8#[cfg(target_arch = "x86_64")]
9use crate::arch::tdx::invoke_tdcall_hypercall;
10use crate::single_threaded::SingleThreaded;
11use arrayvec::ArrayVec;
12use core::cell::RefCell;
13use core::cell::UnsafeCell;
14use core::mem::size_of;
15use hvdef::HV_PAGE_SIZE;
16use hvdef::Vtl;
17use hvdef::hypercall::HvInputVtl;
18#[cfg(target_arch = "x86_64")]
19use hvdef::hypercall::StartVirtualProcessorX64;
20use memory_range::MemoryRange;
21use minimal_rt::arch::hypercall::invoke_hypercall;
22use zerocopy::FromBytes;
23use zerocopy::IntoBytes;
24
25#[repr(C, align(4096))]
27struct HvcallPage {
28 buffer: [u8; HV_PAGE_SIZE as usize],
29}
30
31impl HvcallPage {
32 pub const fn new() -> Self {
33 HvcallPage {
34 buffer: [0; HV_PAGE_SIZE as usize],
35 }
36 }
37
38 fn address(&self) -> u64 {
40 let addr = self.buffer.as_ptr() as u64;
41
42 assert!(addr.is_multiple_of(HV_PAGE_SIZE));
44
45 addr
46 }
47}
48
49static HVCALL_INPUT: SingleThreaded<UnsafeCell<HvcallPage>> =
51 SingleThreaded(UnsafeCell::new(HvcallPage::new()));
52
53static HVCALL_OUTPUT: SingleThreaded<UnsafeCell<HvcallPage>> =
55 SingleThreaded(UnsafeCell::new(HvcallPage::new()));
56
57static HVCALL: SingleThreaded<RefCell<HvCall>> = SingleThreaded(RefCell::new(HvCall {
58 initialized: false,
59 vtl: Vtl::Vtl0,
60 #[cfg(target_arch = "x86_64")]
65 tdx_io_page: None,
66}));
67
68pub struct HvCall {
73 initialized: bool,
74 vtl: Vtl,
75 #[cfg(target_arch = "x86_64")]
76 tdx_io_page: Option<TdxHypercallPage>,
77}
78
79#[track_caller]
83pub fn hvcall() -> core::cell::RefMut<'static, HvCall> {
84 HVCALL.borrow_mut()
85}
86
87impl HvCall {
88 fn input_page() -> &'static mut HvcallPage {
89 unsafe { &mut *HVCALL_INPUT.get() }
91 }
92
93 fn output_page() -> &'static mut HvcallPage {
94 unsafe { &mut *HVCALL_OUTPUT.get() }
96 }
97
98 #[cfg(target_arch = "x86_64")]
101 pub fn hypercall_page(&mut self) -> u64 {
102 self.init_if_needed();
103 core::ptr::addr_of!(minimal_rt::arch::hypercall::HYPERCALL_PAGE) as u64
104 }
105
106 #[cfg(target_arch = "x86_64")]
107 fn init_if_needed(&mut self) {
108 if !self.initialized {
109 self.initialize();
110 }
111 }
112
113 pub fn initialize(&mut self) {
115 assert!(!self.initialized);
116 let guest_os_id = hvdef::hypercall::HvGuestOsMicrosoft::new().with_os_id(1);
118
119 crate::arch::hypercall::initialize(guest_os_id);
120 self.initialized = true;
121
122 self.vtl = self
123 .get_register(hvdef::HvAllArchRegisterName::VsmVpStatus.into())
124 .map_or(Vtl::Vtl0, |status| {
125 hvdef::HvRegisterVsmVpStatus::from(status.as_u64())
126 .active_vtl()
127 .try_into()
128 .unwrap()
129 });
130 }
131
132 pub fn uninitialize(&mut self) {
134 if self.initialized {
135 self.initialized = false;
136
137 cfg_select! {
143 target_arch = "x86_64" => {
144 if self.tdx_io_page.is_some() {
145 self.uninitialize_tdx()
146 } else {
147 crate::arch::hypercall::uninitialize();
148 }
149 }
150 _ => {
151 crate::arch::hypercall::uninitialize();
152 }
153 }
154 }
155 }
156
157 pub fn vtl(&mut self) -> Vtl {
159 assert!(self.initialized);
160 self.vtl
161 }
162
163 fn dispatch_hvcall(
166 &mut self,
167 code: hvdef::HypercallCode,
168 rep_count: Option<usize>,
169 ) -> hvdef::hypercall::HypercallOutput {
170 assert!(self.initialized);
171
172 let control = hvdef::hypercall::Control::new()
173 .with_code(code.0)
174 .with_rep_count(rep_count.unwrap_or_default());
175
176 #[cfg(target_arch = "x86_64")]
177 if let Some(tdx_io_page) = &self.tdx_io_page {
178 return invoke_tdcall_hypercall(control, tdx_io_page);
179 }
180 unsafe {
182 invoke_hypercall(
183 control,
184 Self::input_page().address(),
185 Self::output_page().address(),
186 )
187 }
188 }
189
190 pub fn set_register(
192 &mut self,
193 name: hvdef::HvRegisterName,
194 value: hvdef::HvRegisterValue,
195 ) -> Result<(), hvdef::HvError> {
196 const HEADER_SIZE: usize = size_of::<hvdef::hypercall::GetSetVpRegisters>();
197
198 let header = hvdef::hypercall::GetSetVpRegisters {
199 partition_id: hvdef::HV_PARTITION_ID_SELF,
200 vp_index: hvdef::HV_VP_INDEX_SELF,
201 target_vtl: HvInputVtl::CURRENT_VTL,
202 rsvd: [0; 3],
203 };
204
205 header
207 .write_to_prefix(Self::input_page().buffer.as_mut_slice())
208 .unwrap();
209
210 let reg = hvdef::hypercall::HvRegisterAssoc {
211 name,
212 pad: Default::default(),
213 value,
214 };
215
216 reg.write_to_prefix(&mut Self::input_page().buffer[HEADER_SIZE..])
218 .unwrap();
219
220 let output = self.dispatch_hvcall(hvdef::HypercallCode::HvCallSetVpRegisters, Some(1));
221
222 output.result()
223 }
224
225 pub fn get_register(
227 &mut self,
228 name: hvdef::HvRegisterName,
229 ) -> Result<hvdef::HvRegisterValue, hvdef::HvError> {
230 const HEADER_SIZE: usize = size_of::<hvdef::hypercall::GetSetVpRegisters>();
231
232 let header = hvdef::hypercall::GetSetVpRegisters {
233 partition_id: hvdef::HV_PARTITION_ID_SELF,
234 vp_index: hvdef::HV_VP_INDEX_SELF,
235 target_vtl: HvInputVtl::CURRENT_VTL,
236 rsvd: [0; 3],
237 };
238
239 header
241 .write_to_prefix(Self::input_page().buffer.as_mut_slice())
242 .unwrap();
243 name.write_to_prefix(&mut Self::input_page().buffer[HEADER_SIZE..])
245 .unwrap();
246
247 let output = self.dispatch_hvcall(hvdef::HypercallCode::HvCallGetVpRegisters, Some(1));
248 output.result()?;
249 let value = hvdef::HvRegisterValue::read_from_prefix(&Self::output_page().buffer)
250 .unwrap()
251 .0; Ok(value)
253 }
254
255 #[cfg_attr(target_arch = "aarch64", expect(dead_code))]
257 pub fn apply_vtl2_protections(&mut self, range: MemoryRange) -> Result<(), hvdef::HvError> {
258 const HEADER_SIZE: usize = size_of::<hvdef::hypercall::ModifyVtlProtectionMask>();
259 const MAX_INPUT_ELEMENTS: usize = (HV_PAGE_SIZE as usize - HEADER_SIZE) / size_of::<u64>();
260
261 let header = hvdef::hypercall::ModifyVtlProtectionMask {
262 partition_id: hvdef::HV_PARTITION_ID_SELF,
263 map_flags: hvdef::HV_MAP_GPA_PERMISSIONS_NONE,
264 target_vtl: HvInputVtl::CURRENT_VTL,
265 reserved: [0; 3],
266 };
267
268 let mut current_page = range.start_4k_gpn();
269 while current_page < range.end_4k_gpn() {
270 let remaining_pages = range.end_4k_gpn() - current_page;
271 let count = remaining_pages.min(MAX_INPUT_ELEMENTS as u64) as usize;
272
273 header
275 .write_to_prefix(Self::input_page().buffer.as_mut_slice())
276 .unwrap();
277
278 let mut input_offset = HEADER_SIZE;
279 for i in 0..count {
280 let page_num = current_page + i as u64;
281 page_num
283 .write_to_prefix(&mut Self::input_page().buffer[input_offset..])
284 .unwrap();
285 input_offset += size_of::<u64>();
286 }
287
288 let output = self.dispatch_hvcall(
289 hvdef::HypercallCode::HvCallModifyVtlProtectionMask,
290 Some(count),
291 );
292
293 output.result()?;
294
295 current_page += count as u64;
296 }
297
298 Ok(())
299 }
300
301 #[cfg(target_arch = "aarch64")]
303 pub fn enable_vp_vtl(&mut self, vp_index: u32) -> Result<(), hvdef::HvError> {
304 let header = hvdef::hypercall::EnableVpVtlArm64 {
305 partition_id: hvdef::HV_PARTITION_ID_SELF,
306 vp_index,
307 target_vtl: Vtl::Vtl2.into(),
310 reserved: [0; 3],
311 vp_vtl_context: zerocopy::FromZeros::new_zeroed(),
312 };
313
314 header
316 .write_to_prefix(Self::input_page().buffer.as_mut_slice())
317 .unwrap();
318
319 let output = self.dispatch_hvcall(hvdef::HypercallCode::HvCallEnableVpVtl, None);
320 match output.result() {
321 Ok(()) | Err(hvdef::HvError::VtlAlreadyEnabled) => Ok(()),
322 err => err,
323 }
324 }
325
326 #[cfg_attr(target_arch = "aarch64", expect(dead_code))]
329 pub fn accept_vtl2_pages(
330 &mut self,
331 range: MemoryRange,
332 memory_type: hvdef::hypercall::AcceptMemoryType,
333 ) -> Result<(), hvdef::HvError> {
334 const HEADER_SIZE: usize = size_of::<hvdef::hypercall::AcceptGpaPages>();
335 const MAX_INPUT_ELEMENTS: usize = (HV_PAGE_SIZE as usize - HEADER_SIZE) / size_of::<u64>();
336
337 let mut current_page = range.start_4k_gpn();
338 while current_page < range.end_4k_gpn() {
339 let header = hvdef::hypercall::AcceptGpaPages {
340 partition_id: hvdef::HV_PARTITION_ID_SELF,
341 page_attributes: hvdef::hypercall::AcceptPagesAttributes::new()
342 .with_memory_type(memory_type.0)
343 .with_host_visibility(hvdef::hypercall::HostVisibilityType::PRIVATE) .with_vtl_set(1 << 2), vtl_permission_set: hvdef::hypercall::VtlPermissionSet {
346 vtl_permission_from_1: [0; hvdef::hypercall::HV_VTL_PERMISSION_SET_SIZE],
347 },
348 gpa_page_base: current_page,
349 };
350
351 let remaining_pages = range.end_4k_gpn() - current_page;
352 let count = remaining_pages.min(MAX_INPUT_ELEMENTS as u64) as usize;
353
354 header
356 .write_to_prefix(Self::input_page().buffer.as_mut_slice())
357 .unwrap();
358
359 let output =
360 self.dispatch_hvcall(hvdef::HypercallCode::HvCallAcceptGpaPages, Some(count));
361
362 output.result()?;
363
364 current_page += count as u64;
365 }
366
367 Ok(())
368 }
369
370 pub fn get_vp_index_from_hw_id<const N: usize>(
377 &mut self,
378 hw_ids: &[HwId],
379 output: &mut ArrayVec<u32, N>,
380 ) -> Result<(), hvdef::HvError> {
381 let header = hvdef::hypercall::GetVpIndexFromApicId {
382 partition_id: hvdef::HV_PARTITION_ID_SELF,
383 target_vtl: 0,
384 reserved: [0; 7],
385 };
386
387 const HEADER_SIZE: usize = size_of::<hvdef::hypercall::GetVpIndexFromApicId>();
389 const MAX_PER_CALL: usize = (HV_PAGE_SIZE as usize - HEADER_SIZE) / size_of::<HwId>();
390
391 for hw_ids in hw_ids.chunks(MAX_PER_CALL) {
392 header
394 .write_to_prefix(Self::input_page().buffer.as_mut_slice())
395 .unwrap();
396 hw_ids
399 .write_to_prefix(&mut Self::input_page().buffer[HEADER_SIZE..])
400 .unwrap();
401
402 let r = self.dispatch_hvcall(
405 hvdef::HypercallCode::HvCallGetVpIndexFromApicId,
406 Some(hw_ids.len()),
407 );
408
409 let n = r.elements_processed();
410 output.extend(
411 <[u32]>::ref_from_bytes(&Self::output_page().buffer[..n * 4])
412 .unwrap()
413 .iter()
414 .copied(),
415 );
416 r.result()?;
417 assert_eq!(n, hw_ids.len());
418 }
419
420 Ok(())
421 }
422}
423
424#[cfg(target_arch = "x86_64")]
426impl HvCall {
427 pub fn initialize_tdx(&mut self, tdx_io_page: TdxHypercallPage) {
429 assert!(!self.initialized);
430 self.initialized = true;
431 self.vtl = Vtl::Vtl2;
432 self.tdx_io_page = Some(tdx_io_page);
433
434 let guest_os_id = hvdef::hypercall::HvGuestOsMicrosoft::new().with_os_id(1);
436
437 crate::arch::tdx::initialize_hypercalls(
438 guest_os_id.into(),
439 self.tdx_io_page.as_ref().unwrap(),
440 );
441 }
442
443 pub fn uninitialize_tdx(&mut self) {
445 crate::arch::tdx::uninitialize_hypercalls(
446 self.tdx_io_page
447 .take()
448 .expect("an initialized instance of HvCall on TDX must have an io page"),
449 );
450 }
451
452 pub fn tdx_enable_vp_vtl2(&mut self, vp_index: u32) -> Result<(), hvdef::HvError> {
454 let header = hvdef::hypercall::EnableVpVtlX64 {
455 partition_id: hvdef::HV_PARTITION_ID_SELF,
456 vp_index,
457 target_vtl: Vtl::Vtl2.into(),
460 reserved: [0; 3],
461 vp_vtl_context: zerocopy::FromZeros::new_zeroed(),
463 };
464 assert!(self.initialized);
465
466 let input_page_addr = self
467 .tdx_io_page
468 .as_ref()
469 .expect("an initialized instance of HvCall on TDX must have an io page")
470 .input();
471 let input_page = unsafe { &mut *(input_page_addr as *mut [u8; 4096]) };
475 header
476 .write_to_prefix(input_page)
477 .expect("unable to write to hypercall page");
478
479 let output = self.dispatch_hvcall(hvdef::HypercallCode::HvCallEnableVpVtl, None);
480 match output.result() {
481 Ok(()) | Err(hvdef::HvError::VtlAlreadyEnabled) => Ok(()),
482 err => err,
483 }
484 }
485
486 pub fn tdx_start_vp(&mut self, vp_index: u32) -> Result<(), hvdef::HvError> {
488 let header = StartVirtualProcessorX64 {
489 partition_id: hvdef::HV_PARTITION_ID_SELF,
490 vp_index,
491 target_vtl: Vtl::Vtl2.into(),
492 rsvd0: 0,
493 rsvd1: 0,
494 vp_context: zerocopy::FromZeros::new_zeroed(),
496 };
497 assert!(self.initialized);
498
499 let input_page_addr = self
500 .tdx_io_page
501 .as_ref()
502 .expect("an initialized instance of HvCall on TDX must have an io page")
503 .input();
504 let input_page = unsafe { &mut *(input_page_addr as *mut [u8; 4096]) };
507 header
508 .write_to_prefix(input_page)
509 .expect("unable to write to hypercall page");
510
511 let output = self.dispatch_hvcall(hvdef::HypercallCode::HvCallStartVirtualProcessor, None);
512 match output.result() {
513 Ok(()) | Err(hvdef::HvError::VtlAlreadyEnabled) => Ok(()),
514 err => err,
515 }
516 }
517}
518
519#[cfg(target_arch = "x86_64")]
522pub type HwId = u32;
523
524#[cfg(target_arch = "aarch64")]
527pub type HwId = u64;