AArch64 EL2 hypervisor for QEMU virt that boots at EL2
1#include "hv/arch.h"
2#include "hv/config.h"
3#include "hv/policy.h"
4#include "hv/string.h"
5#include "hv/timer.h"
6#include "hv/uart.h"
7
8#define KEY(op0, op1, crn, crm, op2) SYSREG_KEY_CONST((op0), (op1), (crn), (crm), (op2))
9#define RO_ID(name, op0, op1, crn, crm, op2, val) \
10 { KEY(op0, op1, crn, crm, op2), name, POLICY_CLASS_ID, POLICY_EMULATE_READ, POLICY_DENY_UNDEF, val, 0, 1, 100 }
11#define SHADOW_RW(name, op0, op1, crn, crm, op2, field, mask) \
12 { KEY(op0, op1, crn, crm, op2), name, POLICY_CLASS_MMU, POLICY_EMULATE_READ, POLICY_WRITE_MASK, 0, mask, 1, 101 }
13#define SHADOW_THREAD(name, op0, op1, crn, crm, op2) \
14 { KEY(op0, op1, crn, crm, op2), name, POLICY_CLASS_THREAD, POLICY_EMULATE_READ, POLICY_EMULATE_WRITE, 0, UINT64_MAX, 1, 102 }
15#define DENY_REG(name, op0, op1, crn, crm, op2, cls) \
16 { KEY(op0, op1, crn, crm, op2), name, cls, POLICY_DENY_UNDEF, POLICY_DENY_UNDEF, 0, 0, 1, 200 }
17
18static const struct policy_entry g_policy[] = {
19 RO_ID("midr_el1", 3, 0, 0, 0, 0, 0x00000000410fd034ull),
20 RO_ID("mpidr_el1", 3, 0, 0, 0, 5, 0x80000000ull),
21 RO_ID("id_aa64pfr0_el1", 3, 0, 0, 4, 0, 0x0000000000000011ull),
22 RO_ID("id_aa64pfr1_el1", 3, 0, 0, 4, 1, 0x0ull),
23 RO_ID("id_aa64isar0_el1", 3, 0, 0, 6, 0, 0x0ull),
24 RO_ID("id_aa64isar1_el1", 3, 0, 0, 6, 1, 0x0ull),
25 RO_ID("id_aa64mmfr0_el1", 3, 0, 0, 7, 0, 0x0000000000001122ull),
26 RO_ID("id_aa64mmfr1_el1", 3, 0, 0, 7, 1, 0x0ull),
27 RO_ID("clidr_el1", 3, 1, 0, 0, 1, 0x0ull),
28 { KEY(3, 2, 0, 0, 0), "csselr_el1", POLICY_CLASS_MISC, POLICY_EMULATE_READ, POLICY_EMULATE_WRITE, 0, 0xfu, 1, 103 },
29 RO_ID("ctr_el0", 3, 3, 0, 0, 1, 0x8444c004ull),
30 RO_ID("dczid_el0", 3, 3, 0, 0, 7, 0x4ull),
31 { KEY(3, 0, 1, 0, 0), "sctlr_el1", POLICY_CLASS_MMU, POLICY_EMULATE_READ, POLICY_WRITE_MASK, 0x30d00800ull, 0x0000000030d0dfffull, 1, 104 },
32 DENY_REG("actlr_el1", 3, 0, 1, 0, 1, POLICY_CLASS_MISC),
33 { KEY(3, 0, 1, 0, 2), "cpacr_el1", POLICY_CLASS_MMU, POLICY_EMULATE_READ, POLICY_WRITE_MASK, 0, 0x00300000ull, 1, 105 },
34 DENY_REG("hcr_el2", 3, 4, 1, 1, 0, POLICY_CLASS_EL2_EL3),
35 DENY_REG("scr_el3", 3, 6, 1, 1, 0, POLICY_CLASS_EL2_EL3),
36 { KEY(3, 0, 2, 0, 0), "ttbr0_el1", POLICY_CLASS_MMU, POLICY_EMULATE_READ, POLICY_EMULATE_WRITE, 0, 0x0000fffffffff000ull, 1, 106 },
37 { KEY(3, 0, 2, 0, 1), "ttbr1_el1", POLICY_CLASS_MMU, POLICY_EMULATE_READ, POLICY_EMULATE_WRITE, 0, 0x0000fffffffff000ull, 1, 107 },
38 { KEY(3, 0, 2, 0, 2), "tcr_el1", POLICY_CLASS_MMU, POLICY_EMULATE_READ, POLICY_WRITE_MASK, 0, 0x00000003ff3fffffull, 1, 108 },
39 DENY_REG("vttbr_el2", 3, 4, 2, 1, 0, POLICY_CLASS_EL2_EL3),
40 DENY_REG("pmcr_el0", 3, 3, 9, 12, 0, POLICY_CLASS_PMU),
41 { KEY(3, 0, 10, 2, 0), "mair_el1", POLICY_CLASS_MMU, POLICY_EMULATE_READ, POLICY_EMULATE_WRITE, 0xffull, UINT64_MAX, 1, 109 },
42 { KEY(3, 0, 12, 0, 0), "vbar_el1", POLICY_CLASS_MMU, POLICY_EMULATE_READ, POLICY_EMULATE_WRITE, 0, 0x0000fffffffff800ull, 1, 110 },
43 DENY_REG("rvbar_el1", 3, 0, 12, 0, 1, POLICY_CLASS_MISC),
44 SHADOW_THREAD("contextidr_el1", 3, 0, 13, 0, 1),
45 SHADOW_THREAD("tpidr_el1", 3, 0, 13, 0, 4),
46 SHADOW_THREAD("tpidrro_el0", 3, 3, 13, 0, 3),
47 { KEY(3, 0, 14, 1, 0), "cntkctl_el1", POLICY_CLASS_TIMER, POLICY_EMULATE_READ, POLICY_WRITE_MASK, 0, 0x3u, 1, 111 },
48 { KEY(3, 3, 14, 3, 0), "cntv_tval_el0", POLICY_CLASS_TIMER, POLICY_EMULATE_READ, POLICY_EMULATE_WRITE, 0, UINT64_MAX, 1, 112 },
49 { KEY(3, 3, 14, 3, 1), "cntv_ctl_el0", POLICY_CLASS_TIMER, POLICY_EMULATE_READ, POLICY_WRITE_MASK, 0, 0x7u, 1, 113 },
50 { KEY(3, 3, 14, 3, 2), "cntv_cval_el0", POLICY_CLASS_TIMER, POLICY_EMULATE_READ, POLICY_EMULATE_WRITE, 0, UINT64_MAX, 1, 114 },
51 { KEY(2, 0, 0, 2, 2), "mdscr_el1", POLICY_CLASS_DEBUG, POLICY_EMULATE_READ, POLICY_WRITE_MASK, 0, 0ull, 1, 115 },
52};
53
54static enum policy_profile g_profile;
55static struct policy_entry g_effective_entry;
56
57void policy_init(void)
58{
59 g_profile = POLICY_PROFILE_STRICT;
60}
61
62const char *policy_profile_name(enum policy_profile profile)
63{
64 switch (profile) {
65 case POLICY_PROFILE_STRICT: return "strict";
66 case POLICY_PROFILE_DIAGNOSTIC: return "diagnostic";
67 case POLICY_PROFILE_LINUX_BOOT: return "linux-boot";
68 case POLICY_PROFILE_DEBUG: return "debug";
69 default: return "unknown";
70 }
71}
72
73bool policy_set_profile(enum policy_profile profile)
74{
75 if (profile > POLICY_PROFILE_DEBUG) {
76 return false;
77 }
78 g_profile = profile;
79 return true;
80}
81
82bool policy_set_profile_name(const char *name)
83{
84 if (name == (const char *)0) {
85 return false;
86 }
87 if (hv_strcmp(name, "strict") == 0) {
88 return policy_set_profile(POLICY_PROFILE_STRICT);
89 }
90 if (hv_strcmp(name, "diagnostic") == 0) {
91 return policy_set_profile(POLICY_PROFILE_DIAGNOSTIC);
92 }
93 if (hv_strcmp(name, "linux-boot") == 0) {
94 return policy_set_profile(POLICY_PROFILE_LINUX_BOOT);
95 }
96 if (hv_strcmp(name, "debug") == 0) {
97 return policy_set_profile(POLICY_PROFILE_DEBUG);
98 }
99 return false;
100}
101
102enum policy_profile policy_get_profile(void)
103{
104 return g_profile;
105}
106
107const struct policy_entry *sysreg_policy_lookup(uint64_t key)
108{
109 for (uint32_t i = 0; i < HV_ARRAY_SIZE(g_policy); i++) {
110 if (g_policy[i].key == key) {
111 return &g_policy[i];
112 }
113 }
114 return (const struct policy_entry *)0;
115}
116
117static uint64_t *shadow_slot(struct vcpu *vcpu, uint64_t key)
118{
119 if (key == KEY(3, 0, 1, 0, 0)) return &vcpu->sysregs.sctlr_el1;
120 if (key == KEY(3, 0, 1, 0, 2)) return &vcpu->sysregs.cpacr_el1;
121 if (key == KEY(3, 0, 2, 0, 0)) return &vcpu->sysregs.ttbr0_el1;
122 if (key == KEY(3, 0, 2, 0, 1)) return &vcpu->sysregs.ttbr1_el1;
123 if (key == KEY(3, 0, 2, 0, 2)) return &vcpu->sysregs.tcr_el1;
124 if (key == KEY(3, 0, 10, 2, 0)) return &vcpu->sysregs.mair_el1;
125 if (key == KEY(3, 0, 12, 0, 0)) return &vcpu->sysregs.vbar_el1;
126 if (key == KEY(3, 0, 13, 0, 1)) return &vcpu->sysregs.contextidr_el1;
127 if (key == KEY(3, 0, 13, 0, 4)) return &vcpu->sysregs.tpidr_el1;
128 if (key == KEY(3, 3, 13, 0, 3)) return &vcpu->sysregs.tpidrro_el0;
129 if (key == KEY(3, 0, 14, 1, 0)) return &vcpu->sysregs.cntkctl_el1;
130 if (key == KEY(3, 3, 14, 3, 0)) return &vcpu->sysregs.cntv_tval_el0;
131 if (key == KEY(3, 3, 14, 3, 1)) return &vcpu->sysregs.cntv_ctl_el0;
132 if (key == KEY(3, 3, 14, 3, 2)) return &vcpu->sysregs.cntv_cval_el0;
133 if (key == KEY(3, 2, 0, 0, 0)) return &vcpu->sysregs.csselr_el1;
134 if (key == KEY(2, 0, 0, 2, 2)) return &vcpu->sysregs.mdscr_el1;
135 return (uint64_t *)0;
136}
137
138static bool is_el1_mmu_key(uint64_t key)
139{
140 return key == KEY(3, 0, 1, 0, 0) ||
141 key == KEY(3, 0, 1, 0, 2) ||
142 key == KEY(3, 0, 2, 0, 0) ||
143 key == KEY(3, 0, 2, 0, 1) ||
144 key == KEY(3, 0, 2, 0, 2) ||
145 key == KEY(3, 0, 10, 2, 0) ||
146 key == KEY(3, 0, 12, 0, 0) ||
147 key == KEY(3, 0, 14, 1, 0);
148}
149
150static const struct policy_entry *effective_entry(const struct policy_entry *entry)
151{
152 if (entry == (const struct policy_entry *)0) {
153 return (const struct policy_entry *)0;
154 }
155
156 g_effective_entry = *entry;
157 if (g_profile == POLICY_PROFILE_DIAGNOSTIC) {
158 if (g_effective_entry.access_class == POLICY_CLASS_PMU ||
159 g_effective_entry.access_class == POLICY_CLASS_MISC) {
160 g_effective_entry.read_policy = POLICY_DENY_SKIP;
161 g_effective_entry.write_policy = POLICY_DENY_SKIP;
162 }
163 } else if (g_profile == POLICY_PROFILE_LINUX_BOOT) {
164 if (g_effective_entry.access_class == POLICY_CLASS_PMU ||
165 g_effective_entry.key == KEY(3, 0, 12, 0, 1)) {
166 g_effective_entry.read_policy = POLICY_DENY_SKIP;
167 g_effective_entry.write_policy = POLICY_DENY_SKIP;
168 }
169 if (g_effective_entry.access_class == POLICY_CLASS_MMU) {
170 g_effective_entry.writable_mask = UINT64_MAX;
171 }
172 if (g_effective_entry.key == KEY(3, 3, 9, 12, 0)) {
173 g_effective_entry.reset_value = 0u;
174 }
175 } else if (g_profile == POLICY_PROFILE_DEBUG) {
176 if (g_effective_entry.access_class == POLICY_CLASS_DEBUG ||
177 g_effective_entry.access_class == POLICY_CLASS_PMU) {
178 g_effective_entry.read_policy = POLICY_EMULATE_READ;
179 g_effective_entry.write_policy = POLICY_WRITE_MASK;
180 g_effective_entry.writable_mask = 0u;
181 }
182 }
183 return &g_effective_entry;
184}
185
186bool policy_apply_sysreg(struct vcpu *vcpu, const struct sysreg_access *access,
187 uint64_t operand, struct policy_decision *decision)
188{
189 if (vcpu == (struct vcpu *)0 || access == (const struct sysreg_access *)0 ||
190 decision == (struct policy_decision *)0) {
191 return false;
192 }
193 hv_memset(decision, 0, sizeof(*decision));
194 const struct policy_entry *entry = effective_entry(sysreg_policy_lookup(access->key));
195 decision->entry = entry;
196 decision->action = POLICY_DENY_SKIP;
197 decision->reason = 250u;
198
199 if (entry == (const struct policy_entry *)0) {
200 return true;
201 }
202
203 decision->action = access->is_read ? entry->read_policy : entry->write_policy;
204 decision->reason = entry->violation_reason;
205
206 uint64_t *slot = shadow_slot(vcpu, access->key);
207 switch (decision->action) {
208 case POLICY_EMULATE_READ:
209 decision->value = slot != (uint64_t *)0 ? *slot : entry->reset_value;
210 return true;
211 case POLICY_EMULATE_WRITE:
212 if (slot == (uint64_t *)0) {
213 decision->action = POLICY_DENY_UNDEF;
214 return true;
215 }
216 *slot = operand & entry->writable_mask;
217 decision->value = *slot;
218 if (g_profile == POLICY_PROFILE_LINUX_BOOT && is_el1_mmu_key(access->key)) {
219 arch_sync_el1_sysregs(vcpu);
220 }
221 timer_vcpu_sync(vcpu);
222 return true;
223 case POLICY_WRITE_MASK:
224 if (slot == (uint64_t *)0) {
225 decision->action = POLICY_DENY_UNDEF;
226 return true;
227 }
228 *slot = (*slot & ~entry->writable_mask) | (operand & entry->writable_mask);
229 decision->value = *slot;
230 if (g_profile == POLICY_PROFILE_LINUX_BOOT && is_el1_mmu_key(access->key)) {
231 arch_sync_el1_sysregs(vcpu);
232 }
233 timer_vcpu_sync(vcpu);
234 return true;
235 case POLICY_DENY_SKIP:
236 case POLICY_DENY_UNDEF:
237 case POLICY_PAUSE:
238 case POLICY_PANIC:
239 case POLICY_ALLOW_NATIVE:
240 default:
241 return true;
242 }
243}
244
245void policy_dump(void)
246{
247 uart_puts("policy profile=");
248 uart_puts(policy_profile_name(g_profile));
249 uart_puts("\n");
250 for (uint32_t i = 0; i < HV_ARRAY_SIZE(g_policy); i++) {
251 const struct policy_entry *entry = effective_entry(&g_policy[i]);
252 uart_puts("policy key=");
253 uart_put_hex64(entry->key);
254 uart_puts(" name=");
255 uart_puts(entry->name);
256 uart_puts(" read=");
257 uart_put_dec64((uint64_t)entry->read_policy);
258 uart_puts(" write=");
259 uart_put_dec64((uint64_t)entry->write_policy);
260 uart_puts(" mask=");
261 uart_put_hex64(entry->writable_mask);
262 uart_puts("\n");
263 }
264}
265
266bool policy_selftest(void)
267{
268 enum policy_profile saved = g_profile;
269 bool profile_ok;
270 for (uint32_t i = 0; i < HV_ARRAY_SIZE(g_policy); i++) {
271 if (sysreg_policy_lookup(g_policy[i].key) != &g_policy[i]) {
272 return false;
273 }
274 for (uint32_t j = i + 1u; j < HV_ARRAY_SIZE(g_policy); j++) {
275 if (g_policy[i].key == g_policy[j].key) {
276 return false;
277 }
278 }
279 }
280 profile_ok = policy_set_profile_name("linux-boot") &&
281 policy_get_profile() == POLICY_PROFILE_LINUX_BOOT &&
282 policy_set_profile(saved);
283 return sysreg_policy_lookup(KEY(3, 4, 1, 1, 0)) != (const struct policy_entry *)0 &&
284 sysreg_policy_lookup(KEY(3, 7, 15, 15, 7)) == (const struct policy_entry *)0 &&
285 profile_ok;
286}