1#![no_std]
7#![forbid(unsafe_code)]
8
9use hvdef::HV_PAGE_SIZE;
10use hvdef::hypercall::HypercallOutput;
11use memory_range::MemoryRange;
12use thiserror::Error;
13use x86defs::tdx::TDX_SHARED_GPA_BOUNDARY_ADDRESS_BIT;
14use x86defs::tdx::TdCallLeaf;
15use x86defs::tdx::TdCallResult;
16use x86defs::tdx::TdCallResultCode;
17use x86defs::tdx::TdGlaVmAndFlags;
18use x86defs::tdx::TdReport;
19use x86defs::tdx::TdVmCallR10Result;
20use x86defs::tdx::TdVmCallSubFunction;
21use x86defs::tdx::TdgMemPageAcceptRcx;
22use x86defs::tdx::TdgMemPageAttrGpaMappingReadRcxResult;
23use x86defs::tdx::TdgMemPageAttrWriteR8;
24use x86defs::tdx::TdgMemPageAttrWriteRcx;
25use x86defs::tdx::TdgMemPageGpaAttr;
26use x86defs::tdx::TdgMemPageLevel;
27use x86defs::tdx::TdxExtendedFieldCode;
28use x86defs::tdx::TdxGlaListInfo;
29
30#[derive(Debug)]
34pub struct TdcallInput {
35 pub leaf: TdCallLeaf,
37 pub rcx: u64,
39 pub rdx: u64,
41 pub r8: u64,
43 pub r9: u64,
45 pub r10: u64,
47 pub r11: u64,
49 pub r12: u64,
51 pub r13: u64,
53 pub r14: u64,
55 pub r15: u64,
57}
58
59#[derive(Debug)]
63pub struct TdcallOutput {
64 pub rax: TdCallResult,
66 pub rcx: u64,
68 pub rdx: u64,
70 pub r8: u64,
72 pub r10: u64,
74 pub r11: u64,
76}
77
78pub trait Tdcall {
80 fn tdcall(&mut self, input: TdcallInput) -> TdcallOutput;
82}
83
84pub fn tdcall_hypercall(
86 call: &mut impl Tdcall,
87 control: hvdef::hypercall::Control,
88 input_gpa: u64,
89 output_gpa: u64,
90) -> HypercallOutput {
91 let input = TdcallInput {
92 leaf: TdCallLeaf::VP_VMCALL,
93 rcx: 0x0d04, rdx: input_gpa,
95 r8: output_gpa,
96 r9: 0,
97 r10: u64::from(control), r11: 0,
99 r12: 0,
100 r13: 0,
101 r14: 0,
102 r15: 0,
103 };
104
105 let output = call.tdcall(input);
106
107 if output.rax.code() != TdCallResultCode::SUCCESS {
108 panic!(
112 "unexpected nonzero rax {:x} on tdcall_hypercall",
113 u64::from(output.rax)
114 );
115 }
116
117 HypercallOutput::from(output.r11)
119}
120
121pub fn tdcall_rdmsr(
123 call: &mut impl Tdcall,
124 msr_index: u32,
125 msr_value: &mut u64,
126) -> Result<(), TdVmCallR10Result> {
127 let input = TdcallInput {
128 leaf: TdCallLeaf::VP_VMCALL,
129 rcx: 0x1c00, rdx: 0,
131 r8: 0,
132 r9: 0,
133 r10: 0, r11: TdVmCallSubFunction::RdMsr as u64,
135 r12: msr_index as u64,
136 r13: 0,
137 r14: 0,
138 r15: 0,
139 };
140
141 let output = call.tdcall(input);
142
143 assert_eq!(
147 output.rax.code(),
148 TdCallResultCode::SUCCESS,
149 "unexpected nonzero rax {:x} returned by tdcall vmcall",
150 u64::from(output.rax)
151 );
152
153 let result = TdVmCallR10Result(output.r10);
154
155 *msr_value = output.r11;
156
157 #[cfg(feature = "tracing")]
158 tracing::trace!(msr_index, msr_value, output.r10, "tdcall_rdmsr");
159
160 match result {
161 TdVmCallR10Result::SUCCESS => Ok(()),
162 val => Err(val),
163 }
164}
165
166pub fn tdcall_wrmsr(
168 call: &mut impl Tdcall,
169 msr_index: u32,
170 msr_value: u64,
171) -> Result<(), TdVmCallR10Result> {
172 let input = TdcallInput {
173 leaf: TdCallLeaf::VP_VMCALL,
174 rcx: 0x3c00, rdx: 0,
176 r8: 0,
177 r9: 0,
178 r10: 0, r11: TdVmCallSubFunction::WrMsr as u64,
180 r12: msr_index as u64,
181 r13: msr_value,
182 r14: 0,
183 r15: 0,
184 };
185
186 let output = call.tdcall(input);
187
188 assert_eq!(
192 output.rax.code(),
193 TdCallResultCode::SUCCESS,
194 "unexpected nonzero rax {:x} returned by tdcall vmcall",
195 u64::from(output.rax)
196 );
197
198 let result = TdVmCallR10Result(output.r10);
199
200 match result {
201 TdVmCallR10Result::SUCCESS => Ok(()),
202 val => Err(val),
203 }
204}
205
206pub fn tdcall_io_out(
208 call: &mut impl Tdcall,
209 port: u16,
210 value: u32,
211 size: u8,
212) -> Result<(), TdVmCallR10Result> {
213 let input = TdcallInput {
214 leaf: TdCallLeaf::VP_VMCALL,
215 rcx: 0xFF00, rdx: 0,
217 r8: 0,
218 r9: 0,
219 r10: 0, r11: 30,
221 r12: size as u64,
222 r13: 1, r14: port as u64,
224 r15: value as u64,
225 };
226
227 let output = call.tdcall(input);
228
229 assert_eq!(
233 output.rax.code(),
234 TdCallResultCode::SUCCESS,
235 "unexpected nonzero rax {:x} returned by tdcall vmcall",
236 u64::from(output.rax)
237 );
238
239 if output.rax.code() != TdCallResultCode::SUCCESS {
240 panic!(
244 "unexpected nonzero rax {:x} on tdcall_io_out",
245 u64::from(output.rax)
246 );
247 }
248
249 let result = TdVmCallR10Result(output.r10);
250
251 match result {
252 TdVmCallR10Result::SUCCESS => Ok(()),
253 val => Err(val),
254 }
255}
256
257pub fn tdcall_io_in(call: &mut impl Tdcall, port: u16, size: u8) -> Result<u32, TdVmCallR10Result> {
259 let input = TdcallInput {
260 leaf: TdCallLeaf::VP_VMCALL,
261 rcx: 0xFF00, rdx: 0,
263 r8: 0,
264 r9: 0,
265 r10: 0, r11: TdVmCallSubFunction::IoInstr as u64,
267 r12: size as u64,
268 r13: 0, r14: port as u64,
270 r15: 0,
271 };
272
273 let output = call.tdcall(input);
274
275 assert_eq!(
279 output.rax.code(),
280 TdCallResultCode::SUCCESS,
281 "unexpected nonzero rax {:x} returned by tdcall vmcall",
282 u64::from(output.rax)
283 );
284
285 let result = TdVmCallR10Result(output.r10);
286
287 match result {
288 TdVmCallR10Result::SUCCESS => Ok(output.r11 as u32),
289 val => Err(val),
290 }
291}
292
293pub fn tdcall_accept_pages(
295 call: &mut impl Tdcall,
296 gpa_page_number: u64,
297 as_large_page: bool,
298) -> Result<(), TdCallResultCode> {
299 #[cfg(feature = "tracing")]
300 tracing::trace!(gpa_page_number, as_large_page, "tdcall_accept_pages");
301
302 let rcx = TdgMemPageAcceptRcx::new()
303 .with_gpa_page_number(gpa_page_number)
304 .with_level(if as_large_page {
305 TdgMemPageLevel::Size2Mb
306 } else {
307 TdgMemPageLevel::Size4k
308 });
309
310 let input = TdcallInput {
311 leaf: TdCallLeaf::MEM_PAGE_ACCEPT,
312 rcx: rcx.into(),
313 rdx: 0,
314 r8: 0,
315 r9: 0,
316 r10: 0,
317 r11: 0,
318 r12: 0,
319 r13: 0,
320 r14: 0,
321 r15: 0,
322 };
323
324 let output = call.tdcall(input);
325
326 match output.rax.code() {
327 TdCallResultCode::SUCCESS => Ok(()),
328 val => Err(val),
329 }
330}
331
332#[derive(Debug)]
334pub struct TdgPageAttrRdResult {
335 pub mapping: TdgMemPageAttrGpaMappingReadRcxResult,
337 pub attributes: TdgMemPageGpaAttr,
339}
340
341pub fn tdcall_page_attr_rd(
343 call: &mut impl Tdcall,
344 gpa: u64,
345) -> Result<TdgPageAttrRdResult, TdCallResultCode> {
346 #[cfg(feature = "tracing")]
347 tracing::trace!(gpa, "tdcall_page_attr_rd");
348
349 let input = TdcallInput {
350 leaf: TdCallLeaf::MEM_PAGE_ATTR_RD,
351 rcx: gpa,
352 rdx: 0,
353 r8: 0,
354 r9: 0,
355 r10: 0,
356 r11: 0,
357 r12: 0,
358 r13: 0,
359 r14: 0,
360 r15: 0,
361 };
362
363 let output = call.tdcall(input);
364
365 match output.rax.code() {
366 TdCallResultCode::SUCCESS => Ok(TdgPageAttrRdResult {
367 mapping: TdgMemPageAttrGpaMappingReadRcxResult::from(output.rcx),
368 attributes: TdgMemPageGpaAttr::from(output.rdx),
369 }),
370 val => Err(val),
371 }
372}
373
374pub fn tdcall_page_attr_wr(
376 call: &mut impl Tdcall,
377 mapping: TdgMemPageAttrWriteRcx,
378 attributes: TdgMemPageGpaAttr,
379 mask: TdgMemPageAttrWriteR8,
380) -> Result<(), TdCallResultCode> {
381 #[cfg(feature = "tracing")]
382 tracing::trace!(?mapping, ?attributes, ?mask, "tdcall_page_attr_wr");
383
384 let input = TdcallInput {
385 leaf: TdCallLeaf::MEM_PAGE_ATTR_WR,
386 rcx: mapping.into(),
387 rdx: attributes.into(),
388 r8: mask.into(),
389 r9: 0,
390 r10: 0,
391 r11: 0,
392 r12: 0,
393 r13: 0,
394 r14: 0,
395 r15: 0,
396 };
397
398 let output = call.tdcall(input);
399
400 match output.rax.code() {
403 TdCallResultCode::SUCCESS => Ok(()),
404 val => Err(val),
405 }
406}
407
408fn set_page_attr(
411 call: &mut impl Tdcall,
412 mapping: TdgMemPageAttrWriteRcx,
413 attributes: TdgMemPageGpaAttr,
414 mask: TdgMemPageAttrWriteR8,
415) -> Result<(), TdCallResultCode> {
416 match tdcall_page_attr_wr(call, mapping, attributes, mask) {
417 Ok(()) => {
418 #[cfg(debug_assertions)]
419 {
420 let result =
421 tdcall_page_attr_rd(call, mapping.gpa_page_number() * HV_PAGE_SIZE).unwrap();
422 assert_eq!(u64::from(mapping), result.mapping.into());
423 assert_eq!(attributes.l1(), result.attributes.l1());
424 assert_eq!(
425 attributes.into_bits() & mask.with_reserved(0).into_bits(),
426 result.attributes.into_bits() & mask.with_reserved(0).into_bits()
427 );
428 }
429
430 Ok(())
431 }
432 Err(e) => Err(e),
433 }
434}
435
436#[derive(Debug, Error)]
440pub enum AcceptPagesError {
441 #[error("unknown error: {0:?}")]
443 Unknown(TdCallResultCode),
444 #[error("setting page attributes failed after accepting: {0:?}")]
446 Attributes(TdCallResultCode),
447 #[error("invalid operand: {0:?}")]
449 Invalid(TdCallResultCode),
450 #[error("operand busy: {0:?}")]
452 Busy(TdCallResultCode),
453}
454
455pub enum AcceptPagesAttributes {
457 None,
460 Set {
463 attributes: TdgMemPageGpaAttr,
465 mask: TdgMemPageAttrWriteR8,
467 },
468}
469
470pub fn accept_pages<T: Tdcall>(
472 call: &mut T,
473 range: MemoryRange,
474 attributes: AcceptPagesAttributes,
475) -> Result<(), AcceptPagesError> {
476 #[cfg(feature = "tracing")]
477 tracing::trace!(%range, "accept_pages");
478
479 let set_attributes = |call: &mut T, mapping| -> Result<(), AcceptPagesError> {
480 match attributes {
481 AcceptPagesAttributes::None => Ok(()),
482 AcceptPagesAttributes::Set { attributes, mask } => {
483 set_page_attr(call, mapping, attributes, mask).map_err(AcceptPagesError::Attributes)
484 }
485 }
486 };
487
488 let mut range = range;
489 while !range.is_empty() {
490 if range.start() % x86defs::X64_LARGE_PAGE_SIZE == 0
492 && range.len() >= x86defs::X64_LARGE_PAGE_SIZE
493 {
494 match tdcall_accept_pages(call, range.start_4k_gpn(), true) {
495 Ok(_) => {
496 set_attributes(
497 call,
498 TdgMemPageAttrWriteRcx::new()
499 .with_gpa_page_number(range.start_4k_gpn())
500 .with_level(TdgMemPageLevel::Size2Mb),
501 )?;
502
503 range =
504 MemoryRange::new(range.start() + x86defs::X64_LARGE_PAGE_SIZE..range.end());
505 continue;
506 }
507 Err(e) => match e {
508 TdCallResultCode::OPERAND_BUSY => return Err(AcceptPagesError::Busy(e)),
509 TdCallResultCode::OPERAND_INVALID => return Err(AcceptPagesError::Invalid(e)),
510 TdCallResultCode::PAGE_ALREADY_ACCEPTED => {
511 panic!("page {} already accepted", range.start_4k_gpn());
512 }
513 TdCallResultCode::PAGE_SIZE_MISMATCH => {
514 #[cfg(feature = "tracing")]
515 tracing::trace!("accept pages size mismatch returned");
516 }
517 _ => return Err(AcceptPagesError::Unknown(e)),
518 },
519 }
520 }
521
522 match tdcall_accept_pages(call, range.start_4k_gpn(), false) {
524 Ok(_) => {
525 set_attributes(
526 call,
527 TdgMemPageAttrWriteRcx::new()
528 .with_gpa_page_number(range.start_4k_gpn())
529 .with_level(TdgMemPageLevel::Size4k),
530 )?;
531
532 range = MemoryRange::new(range.start() + HV_PAGE_SIZE..range.end());
533 }
534 Err(e) => match e {
535 TdCallResultCode::OPERAND_BUSY => return Err(AcceptPagesError::Busy(e)),
536 TdCallResultCode::OPERAND_INVALID => return Err(AcceptPagesError::Invalid(e)),
537 TdCallResultCode::PAGE_ALREADY_ACCEPTED => {
538 panic!("page {} already accepted", range.start_4k_gpn());
539 }
540 _ => return Err(AcceptPagesError::Unknown(e)),
541 },
542 }
543 }
544
545 Ok(())
546}
547
548pub fn set_page_attributes(
553 call: &mut impl Tdcall,
554 range: MemoryRange,
555 attributes: TdgMemPageGpaAttr,
556 mask: TdgMemPageAttrWriteR8,
557) -> Result<(), TdCallResultCode> {
558 #[cfg(feature = "tracing")]
559 tracing::trace!(
560 %range,
561 ?attributes,
562 ?mask,
563 "set_page_attributes"
564 );
565
566 let mut range = range;
567 while !range.is_empty() {
568 if range.start() % x86defs::X64_LARGE_PAGE_SIZE == 0
570 && range.len() >= x86defs::X64_LARGE_PAGE_SIZE
571 {
572 let mapping = TdgMemPageAttrWriteRcx::new()
573 .with_gpa_page_number(range.start_4k_gpn())
574 .with_level(TdgMemPageLevel::Size2Mb);
575
576 match set_page_attr(call, mapping, attributes, mask) {
577 Ok(()) => {
578 range =
579 MemoryRange::new(range.start() + x86defs::X64_LARGE_PAGE_SIZE..range.end());
580 continue;
581 }
582 Err(TdCallResultCode::PAGE_SIZE_MISMATCH) => {
583 #[cfg(feature = "tracing")]
584 tracing::trace!("set pages attr size mismatch returned");
585 }
586 Err(e) => return Err(e),
587 }
588 }
589
590 let mapping = TdgMemPageAttrWriteRcx::new()
592 .with_gpa_page_number(range.start_4k_gpn())
593 .with_level(TdgMemPageLevel::Size4k);
594
595 match set_page_attr(call, mapping, attributes, mask) {
596 Ok(()) => range = MemoryRange::new(range.start() + HV_PAGE_SIZE..range.end()),
597 Err(e) => return Err(e),
598 }
599 }
600
601 Ok(())
602}
603
604pub fn tdcall_map_gpa(
616 call: &mut impl Tdcall,
617 range: MemoryRange,
618 host_visible: bool,
619) -> Result<(), TdVmCallR10Result> {
620 let mut gpa = if host_visible {
621 range.start() | TDX_SHARED_GPA_BOUNDARY_ADDRESS_BIT
622 } else {
623 range.start() & !TDX_SHARED_GPA_BOUNDARY_ADDRESS_BIT
624 };
625 let end = gpa + range.len();
626
627 while gpa < end {
628 let input = TdcallInput {
629 leaf: TdCallLeaf::VP_VMCALL,
630 rcx: 0x3c00, rdx: 0,
632 r8: 0,
633 r9: 0,
634 r10: 0, r11: TdVmCallSubFunction::MapGpa as u64,
636 r12: gpa,
637 r13: end - gpa,
638 r14: 0,
639 r15: 0,
640 };
641
642 let output = call.tdcall(input);
643
644 assert_eq!(
648 output.rax.code(),
649 TdCallResultCode::SUCCESS,
650 "unexpected nonzero rax {:x} returned by tdcall vmcall",
651 u64::from(output.rax)
652 );
653
654 let result = TdVmCallR10Result(output.r10);
655
656 match result {
657 TdVmCallR10Result::SUCCESS => gpa = end,
658 TdVmCallR10Result::RETRY => gpa = output.r11,
659 val => return Err(val),
660 }
661 }
662
663 Ok(())
664}
665
666pub fn tdcall_vp_wr(
675 call: &mut impl Tdcall,
676 field_code: TdxExtendedFieldCode,
677 value: u64,
678 mask: u64,
679) -> Result<u64, TdCallResult> {
680 let input = TdcallInput {
681 leaf: TdCallLeaf::VP_WR,
682 rcx: 0,
683 rdx: field_code.into(),
684 r8: value,
685 r9: mask,
686 r10: 0,
687 r11: 0,
688 r12: 0,
689 r13: 0,
690 r14: 0,
691 r15: 0,
692 };
693
694 let output = call.tdcall(input);
695
696 match output.rax.code() {
697 TdCallResultCode::SUCCESS => Ok(output.r8),
698 _ => Err(output.rax),
699 }
700}
701
702pub fn tdcall_vp_rd(
706 call: &mut impl Tdcall,
707 field_code: TdxExtendedFieldCode,
708) -> Result<u64, TdCallResult> {
709 let input = TdcallInput {
710 leaf: TdCallLeaf::VP_RD,
711 rcx: 0,
712 rdx: field_code.into(),
713 r8: 0,
714 r9: 0,
715 r10: 0,
716 r11: 0,
717 r12: 0,
718 r13: 0,
719 r14: 0,
720 r15: 0,
721 };
722
723 let output = call.tdcall(input);
724
725 match output.rax.code() {
726 TdCallResultCode::SUCCESS => Ok(output.r8),
727 _ => Err(output.rax),
728 }
729}
730
731pub fn tdcall_vp_invgla(
733 call: &mut impl Tdcall,
734 gla_flags: TdGlaVmAndFlags,
735 gla_info: TdxGlaListInfo,
736) -> Result<(), TdCallResult> {
737 let input = TdcallInput {
738 leaf: TdCallLeaf::VP_INVGLA,
739 rcx: gla_flags.into(),
740 rdx: gla_info.into(),
741 r8: 0,
742 r9: 0,
743 r10: 0,
744 r11: 0,
745 r12: 0,
746 r13: 0,
747 r14: 0,
748 r15: 0,
749 };
750
751 let output = call.tdcall(input);
752
753 match output.rax.code() {
754 TdCallResultCode::SUCCESS => Ok(()),
755 _ => Err(output.rax),
756 }
757}
758
759#[repr(C, align(64))]
760struct AddlData {
761 pub report_data: [u8; 64],
763}
764
765pub fn tdcall_mr_report(call: &mut impl Tdcall, report: &mut TdReport) -> Result<(), TdCallResult> {
767 let addl_data = AddlData {
768 report_data: [0; 64],
769 };
770
771 let input = TdcallInput {
772 leaf: TdCallLeaf::MR_REPORT,
773 rcx: core::ptr::from_mut::<TdReport>(report) as u64,
774 rdx: 0,
775 r8: 0,
776 r9: 0,
777 r10: 0,
778 r11: 0,
779 r12: addl_data.report_data.as_ptr() as u64,
780 r13: 0,
781 r14: 0,
782 r15: 0,
783 };
784
785 let output = call.tdcall(input);
786
787 match output.rax.code() {
788 TdCallResultCode::SUCCESS => Ok(()),
789 _ => Err(output.rax),
790 }
791}