1#include <asm.h>
2#include <atomic.h>
3#include <cpuid_defs.h>
4#include <math/bit.h>
5#include <string.h>
6#include <virt/kvm.h>
7
8enum kvm_state {
9 KVM_STATE_UNKNOWN = 0,
10 KVM_STATE_ABSENT,
11 KVM_STATE_VMCALL, /* Intel */
12 KVM_STATE_VMMCALL, /* AMD, Hygon */
13};
14
15/* Races are fine since everyone would report the same thing */
16static atomic_uint32_t kvm_state = KVM_STATE_UNKNOWN;
17static atomic_uint32_t kvm_features;
18
19static enum kvm_state kvm_probe(uint32_t *features) {
20 uint32_t a, b, c, d;
21
22 cpuid_count(CPUID_LEAF_FEATURES, subleaf: 0, eax: &a, ebx: &b, ecx: &c, edx: &d);
23 if (!(c & CPUID_ECX_HYPERVISOR))
24 return KVM_STATE_ABSENT;
25
26 uint32_t base = CPUID_HV_LEAF_BASE;
27 for (; base < CPUID_HV_LEAF_LIMIT; base += CPUID_HV_LEAF_STRIDE) {
28 uint8_t sig[CPUID_HV_SIGNATURE_LEN];
29 cpuid_count(leaf: base, subleaf: 0, eax: &a, ebx: &b, ecx: &c, edx: &d);
30 memcpy(sig + 0, &b, 4);
31 memcpy(sig + 4, &c, 4);
32 memcpy(sig + 8, &d, 4);
33
34 /* Old KVM reports a max leaf of 0, meaning base + 1 */
35 if (!memcmp(sig, KVM_CPUID_SIGNATURE, sizeof(sig)) &&
36 (!a || a >= base + KVM_CPUID_FEATURES_OFF))
37 break;
38 }
39
40 if (base >= CPUID_HV_LEAF_LIMIT)
41 return KVM_STATE_ABSENT;
42
43 cpuid_count(leaf: base + KVM_CPUID_FEATURES_OFF, subleaf: 0, eax: &a, ebx: &b, ecx: &c, edx: &d);
44 *features = a;
45
46 cpuid_count(CPUID_LEAF_VENDOR, subleaf: 0, eax: &a, ebx: &b, ecx: &c, edx: &d);
47 bool amd = b == CPUID_VENDOR_EBX_AMD || b == CPUID_VENDOR_EBX_HYGON;
48 return amd ? KVM_STATE_VMMCALL : KVM_STATE_VMCALL;
49}
50
51static enum kvm_state kvm_state_get(void) {
52 enum kvm_state s = atomic_load_acq(&kvm_state);
53 if (s != KVM_STATE_UNKNOWN)
54 return s;
55
56 uint32_t features = 0;
57 s = kvm_probe(features: &features);
58
59 atomic_store_rlx(&kvm_features, features);
60 atomic_store_release(&kvm_state, s);
61 return s;
62}
63
64bool kvm_detect(void) {
65 return kvm_state_get() != KVM_STATE_ABSENT;
66}
67
68bool kvm_has_feature(uint32_t feature) {
69 if (!kvm_detect())
70 return false;
71
72 return atomic_load_rlx(&kvm_features) & BIT(feature);
73}
74
75static enum err kvm_err(int64_t ret) {
76 switch (-ret) {
77 case KVM_ENOSYS:
78 case KVM_EOPNOTSUPP: return ERR_NOT_IMPL;
79 case KVM_EFAULT: return ERR_FAULT;
80 case KVM_EINVAL: return ERR_INVAL;
81 case KVM_E2BIG: return ERR_OVERFLOW;
82 case KVM_EPERM: return ERR_PERM;
83 default: return ERR_UNKNOWN;
84 }
85}
86
87enum err kvm_hypercall4(uint64_t nr, uint64_t a0, uint64_t a1, uint64_t a2,
88 uint64_t a3, uint64_t *out) {
89 int64_t ret;
90
91 switch (kvm_state_get()) {
92 case KVM_STATE_VMMCALL:
93 asm volatile("vmmcall"
94 : "=a"(ret)
95 : "a"(nr), "b"(a0), "c"(a1), "d"(a2), "S"(a3)
96 : "memory");
97 break;
98
99 case KVM_STATE_VMCALL:
100 asm volatile("vmcall"
101 : "=a"(ret)
102 : "a"(nr), "b"(a0), "c"(a1), "d"(a2), "S"(a3)
103 : "memory");
104 break;
105
106 default: return ERR_NO_DEV;
107 }
108
109 if (ret < 0)
110 return kvm_err(ret);
111
112 if (out)
113 *out = (uint64_t) ret;
114
115 return ERR_OK;
116}
117