• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! Hypercalls for x86-64 pKVM.
2 
3 use core::arch::asm;
4 use zerocopy::{FromBytes, Immutable, IntoBytes};
5 
6 /// This CPUID returns the signature and should be used to determine if VM is running under pKVM,
7 /// KVM or not. See the Linux header `arch/x86/include/uapi/asm/kvm_para.h`.
8 const KVM_CPUID_SIGNATURE: u32 = 0x40000000;
9 
10 // See `include/uapi/linux/kvm_para.h`. (These hypercalls numbers can change depending on the
11 // upstream progress.)
12 const KVM_HC_PKVM_OP: u32 = 20;
13 const PKVM_GHC_IOREAD: u32 = KVM_HC_PKVM_OP + 3;
14 const PKVM_GHC_IOWRITE: u32 = KVM_HC_PKVM_OP + 4;
15 
16 /// The maximum number of bytes that can be read or written by a single IO hypercall.
17 const HYP_IO_MAX: usize = 8;
18 
19 /// Gets the signature CPU ID.
cpuid_signature() -> [u8; 4]20 pub fn cpuid_signature() -> [u8; 4] {
21     let signature: u32;
22     unsafe {
23         // The argument for cpuid is passed via rax and in case of KVM_CPUID_SIGNATURE returned via
24         // rbx, rcx and rdx. Ideally using a named argument in inline asm for rbx would be more
25         // straightforward, but when "rbx" is directly used LLVM complains that it is used
26         // internally.
27         //
28         // Therefore use r8 instead and push rbx to the stack before making cpuid call, store
29         // rbx content to r8 as use it as inline asm output and pop the rbx.
30         asm!(
31             "push rbx",
32             "cpuid",
33             "mov r8, rbx",
34             "pop rbx",
35             in("eax") KVM_CPUID_SIGNATURE,
36             out("r8") signature,
37             out("rcx") _,
38             out("rdx") _,
39         );
40     };
41     signature.to_le_bytes()
42 }
43 
44 /// Asks the hypervisor to perform an IO read at the given physical address.
hyp_io_read(address: usize, size: usize) -> u6445 pub fn hyp_io_read(address: usize, size: usize) -> u64 {
46     // Arguments for vmcall are passed via rax, rbx, rcx and rdx. Ideally using a named argument in
47     // the inline asm for rbx would be more straightforward, but when "rbx" is used directly LLVM
48     // complains that it is used internally.
49     //
50     // Therefore use r8 temporary, push rbx to the stack, perform proper call and pop rbx
51     // again
52     let data;
53     unsafe {
54         asm!(
55             "push rbx",
56             "mov rbx, r8",
57             "vmcall",
58             "pop rbx",
59             inout("rax") u64::from(PKVM_GHC_IOREAD) => data,
60             in("r8") address,
61             in("rcx") size,
62         );
63     }
64     data
65 }
66 
67 /// Asks the hypervisor to perform an IO write at the given physical address.
hyp_io_write(address: usize, size: usize, data: u64)68 pub fn hyp_io_write(address: usize, size: usize, data: u64) {
69     unsafe {
70         // Arguments for vmcall are passed via rax, rbx, rcx and rdx. Ideally using a named argument
71         // in the inline asm for rbx would be more straightforward but when "rbx" is used directly
72         // used LLVM complains that it is used internally.
73         //
74         // Therefore use r8 temporary, push rbx to the stack, perform proper call and pop rbx
75         // again
76         asm!(
77             "push rbx",
78             "mov rbx, r8",
79             "vmcall",
80             "pop rbx",
81             in("rax") PKVM_GHC_IOWRITE,
82             in("r8") address,
83             in("rcx") size,
84             in("rdx") data,
85         );
86     }
87 }
88 
89 /// A region of physical address space which may be accessed by IO read and/or write hypercalls.
90 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
91 pub struct HypIoRegion {
92     /// The physical address of the start of the IO region.
93     pub paddr: usize,
94     /// The size of the IO region in bytes.
95     pub size: usize,
96 }
97 
98 impl HypIoRegion {
read<T: FromBytes>(self, offset: usize) -> T99     pub fn read<T: FromBytes>(self, offset: usize) -> T {
100         assert!(offset + size_of::<T>() <= self.size);
101         assert!(size_of::<T>() <= HYP_IO_MAX);
102 
103         let data = hyp_io_read(self.paddr + offset, size_of::<T>());
104         T::read_from_prefix(data.as_bytes()).unwrap().0
105     }
106 
write<T: IntoBytes + Immutable>(self, offset: usize, value: T)107     pub fn write<T: IntoBytes + Immutable>(self, offset: usize, value: T) {
108         assert!(offset + size_of::<T>() <= self.size);
109         assert!(size_of::<T>() <= HYP_IO_MAX);
110 
111         let mut data = 0;
112         data.as_mut_bytes()[..size_of::<T>()].copy_from_slice(value.as_bytes());
113         hyp_io_write(self.paddr + offset, size_of::<T>(), data);
114     }
115 }
116