1#![no_std]
7#![forbid(unsafe_code)]
8
9use hvdef::HV_PAGE_SIZE;
10use memory_range::MemoryRange;
11use thiserror::Error;
12use x86defs::tdx::TDX_SHARED_GPA_BOUNDARY_ADDRESS_BIT;
13use x86defs::tdx::TdCallLeaf;
14use x86defs::tdx::TdCallResult;
15use x86defs::tdx::TdCallResultCode;
16use x86defs::tdx::TdGlaVmAndFlags;
17use x86defs::tdx::TdVmCallR10Result;
18use x86defs::tdx::TdVmCallSubFunction;
19use x86defs::tdx::TdgMemPageAcceptRcx;
20use x86defs::tdx::TdgMemPageAttrGpaMappingReadRcxResult;
21use x86defs::tdx::TdgMemPageAttrWriteR8;
22use x86defs::tdx::TdgMemPageAttrWriteRcx;
23use x86defs::tdx::TdgMemPageGpaAttr;
24use x86defs::tdx::TdgMemPageLevel;
25use x86defs::tdx::TdxExtendedFieldCode;
26use x86defs::tdx::TdxGlaListInfo;
27
28#[derive(Debug)]
32pub struct TdcallInput {
33 pub leaf: TdCallLeaf,
35 pub rcx: u64,
37 pub rdx: u64,
39 pub r8: u64,
41 pub r9: u64,
43 pub r10: u64,
45 pub r11: u64,
47 pub r12: u64,
49 pub r13: u64,
51 pub r14: u64,
53 pub r15: u64,
55}
56
57#[derive(Debug)]
61pub struct TdcallOutput {
62 pub rax: TdCallResult,
64 pub rcx: u64,
66 pub rdx: u64,
68 pub r8: u64,
70 pub r10: u64,
72 pub r11: u64,
74}
75
76pub trait Tdcall {
78 fn tdcall(&mut self, input: TdcallInput) -> TdcallOutput;
80}
81
82pub fn tdcall_rdmsr(
84 call: &mut impl Tdcall,
85 msr_index: u32,
86 msr_value: &mut u64,
87) -> Result<(), TdVmCallR10Result> {
88 let input = TdcallInput {
89 leaf: TdCallLeaf::VP_VMCALL,
90 rcx: 0x1c00, rdx: 0,
92 r8: 0,
93 r9: 0,
94 r10: 0, r11: TdVmCallSubFunction::RdMsr as u64,
96 r12: msr_index as u64,
97 r13: 0,
98 r14: 0,
99 r15: 0,
100 };
101
102 let output = call.tdcall(input);
103
104 assert_eq!(
108 output.rax.code(),
109 TdCallResultCode::SUCCESS,
110 "unexpected nonzero rax {:x} returned by tdcall vmcall",
111 u64::from(output.rax)
112 );
113
114 let result = TdVmCallR10Result(output.r10);
115
116 *msr_value = output.r11;
117
118 #[cfg(feature = "tracing")]
119 tracing::trace!(msr_index, msr_value, output.r10, "tdcall_rdmsr");
120
121 match result {
122 TdVmCallR10Result::SUCCESS => Ok(()),
123 val => Err(val),
124 }
125}
126
127pub fn tdcall_wrmsr(
129 call: &mut impl Tdcall,
130 msr_index: u32,
131 msr_value: u64,
132) -> Result<(), TdVmCallR10Result> {
133 let input = TdcallInput {
134 leaf: TdCallLeaf::VP_VMCALL,
135 rcx: 0x3c00, rdx: 0,
137 r8: 0,
138 r9: 0,
139 r10: 0, r11: TdVmCallSubFunction::WrMsr as u64,
141 r12: msr_index as u64,
142 r13: msr_value,
143 r14: 0,
144 r15: 0,
145 };
146
147 let output = call.tdcall(input);
148
149 assert_eq!(
153 output.rax.code(),
154 TdCallResultCode::SUCCESS,
155 "unexpected nonzero rax {:x} returned by tdcall vmcall",
156 u64::from(output.rax)
157 );
158
159 let result = TdVmCallR10Result(output.r10);
160
161 match result {
162 TdVmCallR10Result::SUCCESS => Ok(()),
163 val => Err(val),
164 }
165}
166
167pub fn tdcall_io_out(
169 call: &mut impl Tdcall,
170 port: u16,
171 value: u32,
172 size: u8,
173) -> Result<(), TdVmCallR10Result> {
174 let input = TdcallInput {
175 leaf: TdCallLeaf::VP_VMCALL,
176 rcx: 0xFF00, rdx: 0,
178 r8: 0,
179 r9: 0,
180 r10: 0, r11: 30,
182 r12: size as u64,
183 r13: 1, r14: port as u64,
185 r15: value as u64,
186 };
187
188 let output = call.tdcall(input);
189
190 assert_eq!(
194 output.rax.code(),
195 TdCallResultCode::SUCCESS,
196 "unexpected nonzero rax {:x} returned by tdcall vmcall",
197 u64::from(output.rax)
198 );
199
200 if output.rax.code() != TdCallResultCode::SUCCESS {
201 panic!(
205 "unexpected nonzero rax {:x} on tdcall_io_out",
206 u64::from(output.rax)
207 );
208 }
209
210 let result = TdVmCallR10Result(output.r10);
211
212 match result {
213 TdVmCallR10Result::SUCCESS => Ok(()),
214 val => Err(val),
215 }
216}
217
218pub fn tdcall_io_in(call: &mut impl Tdcall, port: u16, size: u8) -> Result<u32, TdVmCallR10Result> {
220 let input = TdcallInput {
221 leaf: TdCallLeaf::VP_VMCALL,
222 rcx: 0xFF00, rdx: 0,
224 r8: 0,
225 r9: 0,
226 r10: 0, r11: TdVmCallSubFunction::IoInstr as u64,
228 r12: size as u64,
229 r13: 0, r14: port as u64,
231 r15: 0,
232 };
233
234 let output = call.tdcall(input);
235
236 assert_eq!(
240 output.rax.code(),
241 TdCallResultCode::SUCCESS,
242 "unexpected nonzero rax {:x} returned by tdcall vmcall",
243 u64::from(output.rax)
244 );
245
246 let result = TdVmCallR10Result(output.r10);
247
248 match result {
249 TdVmCallR10Result::SUCCESS => Ok(output.r11 as u32),
250 val => Err(val),
251 }
252}
253
254pub fn tdcall_accept_pages(
256 call: &mut impl Tdcall,
257 gpa_page_number: u64,
258 as_large_page: bool,
259) -> Result<(), TdCallResultCode> {
260 #[cfg(feature = "tracing")]
261 tracing::trace!(gpa_page_number, as_large_page, "tdcall_accept_pages");
262
263 let rcx = TdgMemPageAcceptRcx::new()
264 .with_gpa_page_number(gpa_page_number)
265 .with_level(if as_large_page {
266 TdgMemPageLevel::Size2Mb
267 } else {
268 TdgMemPageLevel::Size4k
269 });
270
271 let input = TdcallInput {
272 leaf: TdCallLeaf::MEM_PAGE_ACCEPT,
273 rcx: rcx.into(),
274 rdx: 0,
275 r8: 0,
276 r9: 0,
277 r10: 0,
278 r11: 0,
279 r12: 0,
280 r13: 0,
281 r14: 0,
282 r15: 0,
283 };
284
285 let output = call.tdcall(input);
286
287 match output.rax.code() {
288 TdCallResultCode::SUCCESS => Ok(()),
289 val => Err(val),
290 }
291}
292
293#[derive(Debug)]
295pub struct TdgPageAttrRdResult {
296 pub mapping: TdgMemPageAttrGpaMappingReadRcxResult,
298 pub attributes: TdgMemPageGpaAttr,
300}
301
302pub fn tdcall_page_attr_rd(
304 call: &mut impl Tdcall,
305 gpa: u64,
306) -> Result<TdgPageAttrRdResult, TdCallResultCode> {
307 #[cfg(feature = "tracing")]
308 tracing::trace!(gpa, "tdcall_page_attr_rd");
309
310 let input = TdcallInput {
311 leaf: TdCallLeaf::MEM_PAGE_ATTR_RD,
312 rcx: gpa,
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(TdgPageAttrRdResult {
328 mapping: TdgMemPageAttrGpaMappingReadRcxResult::from(output.rcx),
329 attributes: TdgMemPageGpaAttr::from(output.rdx),
330 }),
331 val => Err(val),
332 }
333}
334
335pub fn tdcall_page_attr_wr(
337 call: &mut impl Tdcall,
338 mapping: TdgMemPageAttrWriteRcx,
339 attributes: TdgMemPageGpaAttr,
340 mask: TdgMemPageAttrWriteR8,
341) -> Result<(), TdCallResultCode> {
342 #[cfg(feature = "tracing")]
343 tracing::trace!(?mapping, ?attributes, ?mask, "tdcall_page_attr_wr");
344
345 let input = TdcallInput {
346 leaf: TdCallLeaf::MEM_PAGE_ATTR_WR,
347 rcx: mapping.into(),
348 rdx: attributes.into(),
349 r8: mask.into(),
350 r9: 0,
351 r10: 0,
352 r11: 0,
353 r12: 0,
354 r13: 0,
355 r14: 0,
356 r15: 0,
357 };
358
359 let output = call.tdcall(input);
360
361 match output.rax.code() {
364 TdCallResultCode::SUCCESS => Ok(()),
365 val => Err(val),
366 }
367}
368
369fn set_page_attr(
372 call: &mut impl Tdcall,
373 mapping: TdgMemPageAttrWriteRcx,
374 attributes: TdgMemPageGpaAttr,
375 mask: TdgMemPageAttrWriteR8,
376) -> Result<(), TdCallResultCode> {
377 match tdcall_page_attr_wr(call, mapping, attributes, mask) {
378 Ok(()) => {
379 #[cfg(debug_assertions)]
380 {
381 let result =
382 tdcall_page_attr_rd(call, mapping.gpa_page_number() * HV_PAGE_SIZE).unwrap();
383 assert_eq!(u64::from(mapping), result.mapping.into());
384 assert_eq!(attributes.l1(), result.attributes.l1());
385 assert_eq!(
386 attributes.into_bits() & mask.with_reserved(0).into_bits(),
387 result.attributes.into_bits() & mask.with_reserved(0).into_bits()
388 );
389 }
390
391 Ok(())
392 }
393 Err(e) => Err(e),
394 }
395}
396
397#[derive(Debug, Error)]
401pub enum AcceptPagesError {
402 #[error("unknown error: {0:?}")]
404 Unknown(TdCallResultCode),
405 #[error("setting page attributes failed after accepting: {0:?}")]
407 Attributes(TdCallResultCode),
408 #[error("invalid operand: {0:?}")]
410 Invalid(TdCallResultCode),
411 #[error("operand busy: {0:?}")]
413 Busy(TdCallResultCode),
414}
415
416pub enum AcceptPagesAttributes {
418 None,
421 Set {
424 attributes: TdgMemPageGpaAttr,
426 mask: TdgMemPageAttrWriteR8,
428 },
429}
430
431pub fn accept_pages<T: Tdcall>(
433 call: &mut T,
434 range: MemoryRange,
435 attributes: AcceptPagesAttributes,
436) -> Result<(), AcceptPagesError> {
437 #[cfg(feature = "tracing")]
438 tracing::trace!(%range, "accept_pages");
439
440 let set_attributes = |call: &mut T, mapping| -> Result<(), AcceptPagesError> {
441 match attributes {
442 AcceptPagesAttributes::None => Ok(()),
443 AcceptPagesAttributes::Set { attributes, mask } => {
444 set_page_attr(call, mapping, attributes, mask).map_err(AcceptPagesError::Attributes)
445 }
446 }
447 };
448
449 let mut range = range;
450 while !range.is_empty() {
451 if range.start() % x86defs::X64_LARGE_PAGE_SIZE == 0
453 && range.len() >= x86defs::X64_LARGE_PAGE_SIZE
454 {
455 match tdcall_accept_pages(call, range.start_4k_gpn(), true) {
456 Ok(_) => {
457 set_attributes(
458 call,
459 TdgMemPageAttrWriteRcx::new()
460 .with_gpa_page_number(range.start_4k_gpn())
461 .with_level(TdgMemPageLevel::Size2Mb),
462 )?;
463
464 range =
465 MemoryRange::new(range.start() + x86defs::X64_LARGE_PAGE_SIZE..range.end());
466 continue;
467 }
468 Err(e) => match e {
469 TdCallResultCode::OPERAND_BUSY => return Err(AcceptPagesError::Busy(e)),
470 TdCallResultCode::OPERAND_INVALID => return Err(AcceptPagesError::Invalid(e)),
471 TdCallResultCode::PAGE_ALREADY_ACCEPTED => {
472 panic!("page {} already accepted", range.start_4k_gpn());
473 }
474 TdCallResultCode::PAGE_SIZE_MISMATCH => {
475 #[cfg(feature = "tracing")]
476 tracing::trace!("accept pages size mismatch returned");
477 }
478 _ => return Err(AcceptPagesError::Unknown(e)),
479 },
480 }
481 }
482
483 match tdcall_accept_pages(call, range.start_4k_gpn(), false) {
485 Ok(_) => {
486 set_attributes(
487 call,
488 TdgMemPageAttrWriteRcx::new()
489 .with_gpa_page_number(range.start_4k_gpn())
490 .with_level(TdgMemPageLevel::Size4k),
491 )?;
492
493 range = MemoryRange::new(range.start() + HV_PAGE_SIZE..range.end());
494 }
495 Err(e) => match e {
496 TdCallResultCode::OPERAND_BUSY => return Err(AcceptPagesError::Busy(e)),
497 TdCallResultCode::OPERAND_INVALID => return Err(AcceptPagesError::Invalid(e)),
498 TdCallResultCode::PAGE_ALREADY_ACCEPTED => {
499 panic!("page {} already accepted", range.start_4k_gpn());
500 }
501 _ => return Err(AcceptPagesError::Unknown(e)),
502 },
503 }
504 }
505
506 Ok(())
507}
508
509pub fn set_page_attributes(
514 call: &mut impl Tdcall,
515 range: MemoryRange,
516 attributes: TdgMemPageGpaAttr,
517 mask: TdgMemPageAttrWriteR8,
518) -> Result<(), TdCallResultCode> {
519 #[cfg(feature = "tracing")]
520 tracing::trace!(
521 %range,
522 ?attributes,
523 ?mask,
524 "set_page_attributes"
525 );
526
527 let mut range = range;
528 while !range.is_empty() {
529 if range.start() % x86defs::X64_LARGE_PAGE_SIZE == 0
531 && range.len() >= x86defs::X64_LARGE_PAGE_SIZE
532 {
533 let mapping = TdgMemPageAttrWriteRcx::new()
534 .with_gpa_page_number(range.start_4k_gpn())
535 .with_level(TdgMemPageLevel::Size2Mb);
536
537 match set_page_attr(call, mapping, attributes, mask) {
538 Ok(()) => {
539 range =
540 MemoryRange::new(range.start() + x86defs::X64_LARGE_PAGE_SIZE..range.end());
541 continue;
542 }
543 Err(TdCallResultCode::PAGE_SIZE_MISMATCH) => {
544 #[cfg(feature = "tracing")]
545 tracing::trace!("set pages attr size mismatch returned");
546 }
547 Err(e) => return Err(e),
548 }
549 }
550
551 let mapping = TdgMemPageAttrWriteRcx::new()
553 .with_gpa_page_number(range.start_4k_gpn())
554 .with_level(TdgMemPageLevel::Size4k);
555
556 match set_page_attr(call, mapping, attributes, mask) {
557 Ok(()) => range = MemoryRange::new(range.start() + HV_PAGE_SIZE..range.end()),
558 Err(e) => return Err(e),
559 }
560 }
561
562 Ok(())
563}
564
565pub fn tdcall_map_gpa(
577 call: &mut impl Tdcall,
578 range: MemoryRange,
579 host_visible: bool,
580) -> Result<(), TdVmCallR10Result> {
581 let mut gpa = if host_visible {
582 range.start() | TDX_SHARED_GPA_BOUNDARY_ADDRESS_BIT
583 } else {
584 range.start() & !TDX_SHARED_GPA_BOUNDARY_ADDRESS_BIT
585 };
586 let end = gpa + range.len();
587
588 while gpa < end {
589 let input = TdcallInput {
590 leaf: TdCallLeaf::VP_VMCALL,
591 rcx: 0x3c00, rdx: 0,
593 r8: 0,
594 r9: 0,
595 r10: 0, r11: TdVmCallSubFunction::MapGpa as u64,
597 r12: gpa,
598 r13: end - gpa,
599 r14: 0,
600 r15: 0,
601 };
602
603 let output = call.tdcall(input);
604
605 assert_eq!(
609 output.rax.code(),
610 TdCallResultCode::SUCCESS,
611 "unexpected nonzero rax {:x} returned by tdcall vmcall",
612 u64::from(output.rax)
613 );
614
615 let result = TdVmCallR10Result(output.r10);
616
617 match result {
618 TdVmCallR10Result::SUCCESS => gpa = end,
619 TdVmCallR10Result::RETRY => gpa = output.r11,
620 val => return Err(val),
621 }
622 }
623
624 Ok(())
625}
626
627pub fn tdcall_vp_wr(
636 call: &mut impl Tdcall,
637 field_code: TdxExtendedFieldCode,
638 value: u64,
639 mask: u64,
640) -> Result<u64, TdCallResult> {
641 let input = TdcallInput {
642 leaf: TdCallLeaf::VP_WR,
643 rcx: 0,
644 rdx: field_code.into(),
645 r8: value,
646 r9: mask,
647 r10: 0,
648 r11: 0,
649 r12: 0,
650 r13: 0,
651 r14: 0,
652 r15: 0,
653 };
654
655 let output = call.tdcall(input);
656
657 match output.rax.code() {
658 TdCallResultCode::SUCCESS => Ok(output.r8),
659 _ => Err(output.rax),
660 }
661}
662
663pub fn tdcall_vp_rd(
667 call: &mut impl Tdcall,
668 field_code: TdxExtendedFieldCode,
669) -> Result<u64, TdCallResult> {
670 let input = TdcallInput {
671 leaf: TdCallLeaf::VP_RD,
672 rcx: 0,
673 rdx: field_code.into(),
674 r8: 0,
675 r9: 0,
676 r10: 0,
677 r11: 0,
678 r12: 0,
679 r13: 0,
680 r14: 0,
681 r15: 0,
682 };
683
684 let output = call.tdcall(input);
685
686 match output.rax.code() {
687 TdCallResultCode::SUCCESS => Ok(output.r8),
688 _ => Err(output.rax),
689 }
690}
691
692pub fn tdcall_vp_invgla(
694 call: &mut impl Tdcall,
695 gla_flags: TdGlaVmAndFlags,
696 gla_info: TdxGlaListInfo,
697) -> Result<(), TdCallResult> {
698 let input = TdcallInput {
699 leaf: TdCallLeaf::VP_INVGLA,
700 rcx: gla_flags.into(),
701 rdx: gla_info.into(),
702 r8: 0,
703 r9: 0,
704 r10: 0,
705 r11: 0,
706 r12: 0,
707 r13: 0,
708 r14: 0,
709 r15: 0,
710 };
711
712 let output = call.tdcall(input);
713
714 match output.rax.code() {
715 TdCallResultCode::SUCCESS => Ok(()),
716 _ => Err(output.rax),
717 }
718}