1#![no_std]
7#![forbid(unsafe_code)]
8
9use hvdef::HV_PAGE_SIZE;
10use memory_range::MemoryRange;
11use tdx_guest_device::protocol::TdReport;
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::TdVmCallR10Result;
19use x86defs::tdx::TdVmCallSubFunction;
20use x86defs::tdx::TdgMemPageAcceptRcx;
21use x86defs::tdx::TdgMemPageAttrGpaMappingReadRcxResult;
22use x86defs::tdx::TdgMemPageAttrWriteR8;
23use x86defs::tdx::TdgMemPageAttrWriteRcx;
24use x86defs::tdx::TdgMemPageGpaAttr;
25use x86defs::tdx::TdgMemPageLevel;
26use x86defs::tdx::TdxExtendedFieldCode;
27use x86defs::tdx::TdxGlaListInfo;
28
29#[derive(Debug)]
33pub struct TdcallInput {
34 pub leaf: TdCallLeaf,
36 pub rcx: u64,
38 pub rdx: u64,
40 pub r8: u64,
42 pub r9: u64,
44 pub r10: u64,
46 pub r11: u64,
48 pub r12: u64,
50 pub r13: u64,
52 pub r14: u64,
54 pub r15: u64,
56}
57
58#[derive(Debug)]
62pub struct TdcallOutput {
63 pub rax: TdCallResult,
65 pub rcx: u64,
67 pub rdx: u64,
69 pub r8: u64,
71 pub r10: u64,
73 pub r11: u64,
75}
76
77pub trait Tdcall {
79 fn tdcall(&mut self, input: TdcallInput) -> TdcallOutput;
81}
82
83pub fn tdcall_hypercall(
85 call: &mut impl Tdcall,
86 control: hvdef::hypercall::Control,
87 input_gpa: u64,
88 output_gpa: u64,
89) -> Result<(), TdVmCallR10Result> {
90 let input = TdcallInput {
91 leaf: TdCallLeaf::VP_VMCALL,
92 rcx: 0x0d04, rdx: input_gpa,
94 r8: output_gpa,
95 r9: 0,
96 r10: u64::from(control), r11: 0,
98 r12: 0,
99 r13: 0,
100 r14: 0,
101 r15: 0,
102 };
103
104 let output = call.tdcall(input);
105
106 if output.rax.code() != TdCallResultCode::SUCCESS {
107 panic!(
111 "unexpected nonzero rax {:x} on tdcall_hypercall",
112 u64::from(output.rax)
113 );
114 }
115
116 let result = TdVmCallR10Result(output.r11);
118
119 match result {
120 TdVmCallR10Result::SUCCESS => Ok(()),
121 val => Err(val),
122 }
123}
124
125pub fn tdcall_rdmsr(
127 call: &mut impl Tdcall,
128 msr_index: u32,
129 msr_value: &mut u64,
130) -> Result<(), TdVmCallR10Result> {
131 let input = TdcallInput {
132 leaf: TdCallLeaf::VP_VMCALL,
133 rcx: 0x1c00, rdx: 0,
135 r8: 0,
136 r9: 0,
137 r10: 0, r11: TdVmCallSubFunction::RdMsr as u64,
139 r12: msr_index as u64,
140 r13: 0,
141 r14: 0,
142 r15: 0,
143 };
144
145 let output = call.tdcall(input);
146
147 assert_eq!(
151 output.rax.code(),
152 TdCallResultCode::SUCCESS,
153 "unexpected nonzero rax {:x} returned by tdcall vmcall",
154 u64::from(output.rax)
155 );
156
157 let result = TdVmCallR10Result(output.r10);
158
159 *msr_value = output.r11;
160
161 #[cfg(feature = "tracing")]
162 tracing::trace!(msr_index, msr_value, output.r10, "tdcall_rdmsr");
163
164 match result {
165 TdVmCallR10Result::SUCCESS => Ok(()),
166 val => Err(val),
167 }
168}
169
170pub fn tdcall_wrmsr(
172 call: &mut impl Tdcall,
173 msr_index: u32,
174 msr_value: u64,
175) -> Result<(), TdVmCallR10Result> {
176 let input = TdcallInput {
177 leaf: TdCallLeaf::VP_VMCALL,
178 rcx: 0x3c00, rdx: 0,
180 r8: 0,
181 r9: 0,
182 r10: 0, r11: TdVmCallSubFunction::WrMsr as u64,
184 r12: msr_index as u64,
185 r13: msr_value,
186 r14: 0,
187 r15: 0,
188 };
189
190 let output = call.tdcall(input);
191
192 assert_eq!(
196 output.rax.code(),
197 TdCallResultCode::SUCCESS,
198 "unexpected nonzero rax {:x} returned by tdcall vmcall",
199 u64::from(output.rax)
200 );
201
202 let result = TdVmCallR10Result(output.r10);
203
204 match result {
205 TdVmCallR10Result::SUCCESS => Ok(()),
206 val => Err(val),
207 }
208}
209
210pub fn tdcall_io_out(
212 call: &mut impl Tdcall,
213 port: u16,
214 value: u32,
215 size: u8,
216) -> Result<(), TdVmCallR10Result> {
217 let input = TdcallInput {
218 leaf: TdCallLeaf::VP_VMCALL,
219 rcx: 0xFF00, rdx: 0,
221 r8: 0,
222 r9: 0,
223 r10: 0, r11: 30,
225 r12: size as u64,
226 r13: 1, r14: port as u64,
228 r15: value as u64,
229 };
230
231 let output = call.tdcall(input);
232
233 assert_eq!(
237 output.rax.code(),
238 TdCallResultCode::SUCCESS,
239 "unexpected nonzero rax {:x} returned by tdcall vmcall",
240 u64::from(output.rax)
241 );
242
243 if output.rax.code() != TdCallResultCode::SUCCESS {
244 panic!(
248 "unexpected nonzero rax {:x} on tdcall_io_out",
249 u64::from(output.rax)
250 );
251 }
252
253 let result = TdVmCallR10Result(output.r10);
254
255 match result {
256 TdVmCallR10Result::SUCCESS => Ok(()),
257 val => Err(val),
258 }
259}
260
261pub fn tdcall_io_in(call: &mut impl Tdcall, port: u16, size: u8) -> Result<u32, TdVmCallR10Result> {
263 let input = TdcallInput {
264 leaf: TdCallLeaf::VP_VMCALL,
265 rcx: 0xFF00, rdx: 0,
267 r8: 0,
268 r9: 0,
269 r10: 0, r11: TdVmCallSubFunction::IoInstr as u64,
271 r12: size as u64,
272 r13: 0, r14: port as u64,
274 r15: 0,
275 };
276
277 let output = call.tdcall(input);
278
279 assert_eq!(
283 output.rax.code(),
284 TdCallResultCode::SUCCESS,
285 "unexpected nonzero rax {:x} returned by tdcall vmcall",
286 u64::from(output.rax)
287 );
288
289 let result = TdVmCallR10Result(output.r10);
290
291 match result {
292 TdVmCallR10Result::SUCCESS => Ok(output.r11 as u32),
293 val => Err(val),
294 }
295}
296
297pub fn tdcall_accept_pages(
299 call: &mut impl Tdcall,
300 gpa_page_number: u64,
301 as_large_page: bool,
302) -> Result<(), TdCallResultCode> {
303 #[cfg(feature = "tracing")]
304 tracing::trace!(gpa_page_number, as_large_page, "tdcall_accept_pages");
305
306 let rcx = TdgMemPageAcceptRcx::new()
307 .with_gpa_page_number(gpa_page_number)
308 .with_level(if as_large_page {
309 TdgMemPageLevel::Size2Mb
310 } else {
311 TdgMemPageLevel::Size4k
312 });
313
314 let input = TdcallInput {
315 leaf: TdCallLeaf::MEM_PAGE_ACCEPT,
316 rcx: rcx.into(),
317 rdx: 0,
318 r8: 0,
319 r9: 0,
320 r10: 0,
321 r11: 0,
322 r12: 0,
323 r13: 0,
324 r14: 0,
325 r15: 0,
326 };
327
328 let output = call.tdcall(input);
329
330 match output.rax.code() {
331 TdCallResultCode::SUCCESS => Ok(()),
332 val => Err(val),
333 }
334}
335
336#[derive(Debug)]
338pub struct TdgPageAttrRdResult {
339 pub mapping: TdgMemPageAttrGpaMappingReadRcxResult,
341 pub attributes: TdgMemPageGpaAttr,
343}
344
345pub fn tdcall_page_attr_rd(
347 call: &mut impl Tdcall,
348 gpa: u64,
349) -> Result<TdgPageAttrRdResult, TdCallResultCode> {
350 #[cfg(feature = "tracing")]
351 tracing::trace!(gpa, "tdcall_page_attr_rd");
352
353 let input = TdcallInput {
354 leaf: TdCallLeaf::MEM_PAGE_ATTR_RD,
355 rcx: gpa,
356 rdx: 0,
357 r8: 0,
358 r9: 0,
359 r10: 0,
360 r11: 0,
361 r12: 0,
362 r13: 0,
363 r14: 0,
364 r15: 0,
365 };
366
367 let output = call.tdcall(input);
368
369 match output.rax.code() {
370 TdCallResultCode::SUCCESS => Ok(TdgPageAttrRdResult {
371 mapping: TdgMemPageAttrGpaMappingReadRcxResult::from(output.rcx),
372 attributes: TdgMemPageGpaAttr::from(output.rdx),
373 }),
374 val => Err(val),
375 }
376}
377
378pub fn tdcall_page_attr_wr(
380 call: &mut impl Tdcall,
381 mapping: TdgMemPageAttrWriteRcx,
382 attributes: TdgMemPageGpaAttr,
383 mask: TdgMemPageAttrWriteR8,
384) -> Result<(), TdCallResultCode> {
385 #[cfg(feature = "tracing")]
386 tracing::trace!(?mapping, ?attributes, ?mask, "tdcall_page_attr_wr");
387
388 let input = TdcallInput {
389 leaf: TdCallLeaf::MEM_PAGE_ATTR_WR,
390 rcx: mapping.into(),
391 rdx: attributes.into(),
392 r8: mask.into(),
393 r9: 0,
394 r10: 0,
395 r11: 0,
396 r12: 0,
397 r13: 0,
398 r14: 0,
399 r15: 0,
400 };
401
402 let output = call.tdcall(input);
403
404 match output.rax.code() {
407 TdCallResultCode::SUCCESS => Ok(()),
408 val => Err(val),
409 }
410}
411
412fn set_page_attr(
415 call: &mut impl Tdcall,
416 mapping: TdgMemPageAttrWriteRcx,
417 attributes: TdgMemPageGpaAttr,
418 mask: TdgMemPageAttrWriteR8,
419) -> Result<(), TdCallResultCode> {
420 match tdcall_page_attr_wr(call, mapping, attributes, mask) {
421 Ok(()) => {
422 #[cfg(debug_assertions)]
423 {
424 let result =
425 tdcall_page_attr_rd(call, mapping.gpa_page_number() * HV_PAGE_SIZE).unwrap();
426 assert_eq!(u64::from(mapping), result.mapping.into());
427 assert_eq!(attributes.l1(), result.attributes.l1());
428 assert_eq!(
429 attributes.into_bits() & mask.with_reserved(0).into_bits(),
430 result.attributes.into_bits() & mask.with_reserved(0).into_bits()
431 );
432 }
433
434 Ok(())
435 }
436 Err(e) => Err(e),
437 }
438}
439
440#[derive(Debug, Error)]
444pub enum AcceptPagesError {
445 #[error("unknown error: {0:?}")]
447 Unknown(TdCallResultCode),
448 #[error("setting page attributes failed after accepting: {0:?}")]
450 Attributes(TdCallResultCode),
451 #[error("invalid operand: {0:?}")]
453 Invalid(TdCallResultCode),
454 #[error("operand busy: {0:?}")]
456 Busy(TdCallResultCode),
457}
458
459pub enum AcceptPagesAttributes {
461 None,
464 Set {
467 attributes: TdgMemPageGpaAttr,
469 mask: TdgMemPageAttrWriteR8,
471 },
472}
473
474pub fn accept_pages<T: Tdcall>(
476 call: &mut T,
477 range: MemoryRange,
478 attributes: AcceptPagesAttributes,
479) -> Result<(), AcceptPagesError> {
480 #[cfg(feature = "tracing")]
481 tracing::trace!(%range, "accept_pages");
482
483 let set_attributes = |call: &mut T, mapping| -> Result<(), AcceptPagesError> {
484 match attributes {
485 AcceptPagesAttributes::None => Ok(()),
486 AcceptPagesAttributes::Set { attributes, mask } => {
487 set_page_attr(call, mapping, attributes, mask).map_err(AcceptPagesError::Attributes)
488 }
489 }
490 };
491
492 let mut range = range;
493 while !range.is_empty() {
494 if range.start() % x86defs::X64_LARGE_PAGE_SIZE == 0
496 && range.len() >= x86defs::X64_LARGE_PAGE_SIZE
497 {
498 match tdcall_accept_pages(call, range.start_4k_gpn(), true) {
499 Ok(_) => {
500 set_attributes(
501 call,
502 TdgMemPageAttrWriteRcx::new()
503 .with_gpa_page_number(range.start_4k_gpn())
504 .with_level(TdgMemPageLevel::Size2Mb),
505 )?;
506
507 range =
508 MemoryRange::new(range.start() + x86defs::X64_LARGE_PAGE_SIZE..range.end());
509 continue;
510 }
511 Err(e) => match e {
512 TdCallResultCode::OPERAND_BUSY => return Err(AcceptPagesError::Busy(e)),
513 TdCallResultCode::OPERAND_INVALID => return Err(AcceptPagesError::Invalid(e)),
514 TdCallResultCode::PAGE_ALREADY_ACCEPTED => {
515 panic!("page {} already accepted", range.start_4k_gpn());
516 }
517 TdCallResultCode::PAGE_SIZE_MISMATCH => {
518 #[cfg(feature = "tracing")]
519 tracing::trace!("accept pages size mismatch returned");
520 }
521 _ => return Err(AcceptPagesError::Unknown(e)),
522 },
523 }
524 }
525
526 match tdcall_accept_pages(call, range.start_4k_gpn(), false) {
528 Ok(_) => {
529 set_attributes(
530 call,
531 TdgMemPageAttrWriteRcx::new()
532 .with_gpa_page_number(range.start_4k_gpn())
533 .with_level(TdgMemPageLevel::Size4k),
534 )?;
535
536 range = MemoryRange::new(range.start() + HV_PAGE_SIZE..range.end());
537 }
538 Err(e) => match e {
539 TdCallResultCode::OPERAND_BUSY => return Err(AcceptPagesError::Busy(e)),
540 TdCallResultCode::OPERAND_INVALID => return Err(AcceptPagesError::Invalid(e)),
541 TdCallResultCode::PAGE_ALREADY_ACCEPTED => {
542 panic!("page {} already accepted", range.start_4k_gpn());
543 }
544 _ => return Err(AcceptPagesError::Unknown(e)),
545 },
546 }
547 }
548
549 Ok(())
550}
551
552pub fn set_page_attributes(
557 call: &mut impl Tdcall,
558 range: MemoryRange,
559 attributes: TdgMemPageGpaAttr,
560 mask: TdgMemPageAttrWriteR8,
561) -> Result<(), TdCallResultCode> {
562 #[cfg(feature = "tracing")]
563 tracing::trace!(
564 %range,
565 ?attributes,
566 ?mask,
567 "set_page_attributes"
568 );
569
570 let mut range = range;
571 while !range.is_empty() {
572 if range.start() % x86defs::X64_LARGE_PAGE_SIZE == 0
574 && range.len() >= x86defs::X64_LARGE_PAGE_SIZE
575 {
576 let mapping = TdgMemPageAttrWriteRcx::new()
577 .with_gpa_page_number(range.start_4k_gpn())
578 .with_level(TdgMemPageLevel::Size2Mb);
579
580 match set_page_attr(call, mapping, attributes, mask) {
581 Ok(()) => {
582 range =
583 MemoryRange::new(range.start() + x86defs::X64_LARGE_PAGE_SIZE..range.end());
584 continue;
585 }
586 Err(TdCallResultCode::PAGE_SIZE_MISMATCH) => {
587 #[cfg(feature = "tracing")]
588 tracing::trace!("set pages attr size mismatch returned");
589 }
590 Err(e) => return Err(e),
591 }
592 }
593
594 let mapping = TdgMemPageAttrWriteRcx::new()
596 .with_gpa_page_number(range.start_4k_gpn())
597 .with_level(TdgMemPageLevel::Size4k);
598
599 match set_page_attr(call, mapping, attributes, mask) {
600 Ok(()) => range = MemoryRange::new(range.start() + HV_PAGE_SIZE..range.end()),
601 Err(e) => return Err(e),
602 }
603 }
604
605 Ok(())
606}
607
608pub fn tdcall_map_gpa(
620 call: &mut impl Tdcall,
621 range: MemoryRange,
622 host_visible: bool,
623) -> Result<(), TdVmCallR10Result> {
624 let mut gpa = if host_visible {
625 range.start() | TDX_SHARED_GPA_BOUNDARY_ADDRESS_BIT
626 } else {
627 range.start() & !TDX_SHARED_GPA_BOUNDARY_ADDRESS_BIT
628 };
629 let end = gpa + range.len();
630
631 while gpa < end {
632 let input = TdcallInput {
633 leaf: TdCallLeaf::VP_VMCALL,
634 rcx: 0x3c00, rdx: 0,
636 r8: 0,
637 r9: 0,
638 r10: 0, r11: TdVmCallSubFunction::MapGpa as u64,
640 r12: gpa,
641 r13: end - gpa,
642 r14: 0,
643 r15: 0,
644 };
645
646 let output = call.tdcall(input);
647
648 assert_eq!(
652 output.rax.code(),
653 TdCallResultCode::SUCCESS,
654 "unexpected nonzero rax {:x} returned by tdcall vmcall",
655 u64::from(output.rax)
656 );
657
658 let result = TdVmCallR10Result(output.r10);
659
660 match result {
661 TdVmCallR10Result::SUCCESS => gpa = end,
662 TdVmCallR10Result::RETRY => gpa = output.r11,
663 val => return Err(val),
664 }
665 }
666
667 Ok(())
668}
669
670pub fn tdcall_vp_wr(
679 call: &mut impl Tdcall,
680 field_code: TdxExtendedFieldCode,
681 value: u64,
682 mask: u64,
683) -> Result<u64, TdCallResult> {
684 let input = TdcallInput {
685 leaf: TdCallLeaf::VP_WR,
686 rcx: 0,
687 rdx: field_code.into(),
688 r8: value,
689 r9: mask,
690 r10: 0,
691 r11: 0,
692 r12: 0,
693 r13: 0,
694 r14: 0,
695 r15: 0,
696 };
697
698 let output = call.tdcall(input);
699
700 match output.rax.code() {
701 TdCallResultCode::SUCCESS => Ok(output.r8),
702 _ => Err(output.rax),
703 }
704}
705
706pub fn tdcall_vp_rd(
710 call: &mut impl Tdcall,
711 field_code: TdxExtendedFieldCode,
712) -> Result<u64, TdCallResult> {
713 let input = TdcallInput {
714 leaf: TdCallLeaf::VP_RD,
715 rcx: 0,
716 rdx: field_code.into(),
717 r8: 0,
718 r9: 0,
719 r10: 0,
720 r11: 0,
721 r12: 0,
722 r13: 0,
723 r14: 0,
724 r15: 0,
725 };
726
727 let output = call.tdcall(input);
728
729 match output.rax.code() {
730 TdCallResultCode::SUCCESS => Ok(output.r8),
731 _ => Err(output.rax),
732 }
733}
734
735pub fn tdcall_vp_invgla(
737 call: &mut impl Tdcall,
738 gla_flags: TdGlaVmAndFlags,
739 gla_info: TdxGlaListInfo,
740) -> Result<(), TdCallResult> {
741 let input = TdcallInput {
742 leaf: TdCallLeaf::VP_INVGLA,
743 rcx: gla_flags.into(),
744 rdx: gla_info.into(),
745 r8: 0,
746 r9: 0,
747 r10: 0,
748 r11: 0,
749 r12: 0,
750 r13: 0,
751 r14: 0,
752 r15: 0,
753 };
754
755 let output = call.tdcall(input);
756
757 match output.rax.code() {
758 TdCallResultCode::SUCCESS => Ok(()),
759 _ => Err(output.rax),
760 }
761}
762
763#[repr(C, align(64))]
764struct AddlData {
765 pub report_data: [u8; 64],
767}
768
769pub fn tdcall_mr_report(call: &mut impl Tdcall, report: &mut TdReport) -> Result<(), TdCallResult> {
771 let addl_data = AddlData {
772 report_data: [0; 64],
773 };
774
775 let input = TdcallInput {
776 leaf: TdCallLeaf::MR_REPORT,
777 rcx: core::ptr::from_mut::<TdReport>(report) as u64,
778 rdx: 0,
779 r8: 0,
780 r9: 0,
781 r10: 0,
782 r11: 0,
783 r12: addl_data.report_data.as_ptr() as u64,
784 r13: 0,
785 r14: 0,
786 r15: 0,
787 };
788
789 let output = call.tdcall(input);
790
791 match output.rax.code() {
792 TdCallResultCode::SUCCESS => Ok(()),
793 _ => Err(output.rax),
794 }
795}