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
57static bool policy_action_valid(enum policy_action action)
58{
59 return action == POLICY_ALLOW_NATIVE ||
60 action == POLICY_EMULATE_READ ||
61 action == POLICY_EMULATE_WRITE ||
62 action == POLICY_WRITE_MASK ||
63 action == POLICY_DENY_SKIP ||
64 action == POLICY_DENY_UNDEF ||
65 action == POLICY_PAUSE ||
66 action == POLICY_PANIC;
67}
68
69static bool policy_class_valid(enum policy_access_class access_class)
70{
71 return access_class == POLICY_CLASS_ID ||
72 access_class == POLICY_CLASS_MMU ||
73 access_class == POLICY_CLASS_TIMER ||
74 access_class == POLICY_CLASS_THREAD ||
75 access_class == POLICY_CLASS_PMU ||
76 access_class == POLICY_CLASS_DEBUG ||
77 access_class == POLICY_CLASS_EL2_EL3 ||
78 access_class == POLICY_CLASS_MISC;
79}
80
81static bool policy_lattice_valid(const struct policy_entry *entry)
82{
83 if (entry == (const struct policy_entry *)0 || entry->name == (const char *)0) {
84 return false;
85 }
86 if (!policy_class_valid(entry->access_class) ||
87 !policy_action_valid(entry->read_policy) ||
88 !policy_action_valid(entry->write_policy)) {
89 return false;
90 }
91 if (entry->access_class == POLICY_CLASS_EL2_EL3 &&
92 (entry->read_policy != POLICY_DENY_UNDEF ||
93 entry->write_policy != POLICY_DENY_UNDEF)) {
94 return false;
95 }
96 if (entry->write_policy == POLICY_WRITE_MASK && entry->writable_mask == 0u &&
97 entry->access_class != POLICY_CLASS_DEBUG) {
98 return false;
99 }
100 return true;
101}
102
103static uint64_t policy_mask_value(const struct policy_entry *entry, uint64_t current, uint64_t operand)
104{
105 uint64_t preserve = current & ~entry->writable_mask;
106 if (preserve == 0u) {
107 preserve = entry->reset_value & ~entry->writable_mask;
108 }
109 return preserve | (operand & entry->writable_mask);
110}
111
112void policy_init(void)
113{
114 g_profile = POLICY_PROFILE_STRICT;
115}
116
117const char *policy_profile_name(enum policy_profile profile)
118{
119 switch (profile) {
120 case POLICY_PROFILE_STRICT: return "strict";
121 case POLICY_PROFILE_DIAGNOSTIC: return "diagnostic";
122 case POLICY_PROFILE_LINUX_BOOT: return "linux-boot";
123 case POLICY_PROFILE_DEBUG: return "debug";
124 default: return "unknown";
125 }
126}
127
128bool policy_set_profile(enum policy_profile profile)
129{
130 if (profile > POLICY_PROFILE_DEBUG) {
131 return false;
132 }
133 g_profile = profile;
134 return true;
135}
136
137bool policy_set_profile_name(const char *name)
138{
139 if (name == (const char *)0) {
140 return false;
141 }
142 if (hv_strcmp(name, "strict") == 0) {
143 return policy_set_profile(POLICY_PROFILE_STRICT);
144 }
145 if (hv_strcmp(name, "diagnostic") == 0) {
146 return policy_set_profile(POLICY_PROFILE_DIAGNOSTIC);
147 }
148 if (hv_strcmp(name, "linux-boot") == 0) {
149 return policy_set_profile(POLICY_PROFILE_LINUX_BOOT);
150 }
151 if (hv_strcmp(name, "debug") == 0) {
152 return policy_set_profile(POLICY_PROFILE_DEBUG);
153 }
154 return false;
155}
156
157enum policy_profile policy_get_profile(void)
158{
159 return g_profile;
160}
161
162const struct policy_entry *sysreg_policy_lookup(uint64_t key)
163{
164 for (uint32_t i = 0; i < HV_ARRAY_SIZE(g_policy); i++) {
165 if (g_policy[i].key == key) {
166 return &g_policy[i];
167 }
168 }
169 return (const struct policy_entry *)0;
170}
171
172static uint64_t *shadow_slot(struct vcpu *vcpu, uint64_t key)
173{
174 if (key == KEY(3, 0, 1, 0, 0)) return &vcpu->sysregs.sctlr_el1;
175 if (key == KEY(3, 0, 1, 0, 2)) return &vcpu->sysregs.cpacr_el1;
176 if (key == KEY(3, 0, 2, 0, 0)) return &vcpu->sysregs.ttbr0_el1;
177 if (key == KEY(3, 0, 2, 0, 1)) return &vcpu->sysregs.ttbr1_el1;
178 if (key == KEY(3, 0, 2, 0, 2)) return &vcpu->sysregs.tcr_el1;
179 if (key == KEY(3, 0, 10, 2, 0)) return &vcpu->sysregs.mair_el1;
180 if (key == KEY(3, 0, 12, 0, 0)) return &vcpu->sysregs.vbar_el1;
181 if (key == KEY(3, 0, 13, 0, 1)) return &vcpu->sysregs.contextidr_el1;
182 if (key == KEY(3, 0, 13, 0, 4)) return &vcpu->sysregs.tpidr_el1;
183 if (key == KEY(3, 3, 13, 0, 3)) return &vcpu->sysregs.tpidrro_el0;
184 if (key == KEY(3, 0, 14, 1, 0)) return &vcpu->sysregs.cntkctl_el1;
185 if (key == KEY(3, 3, 14, 3, 0)) return &vcpu->sysregs.cntv_tval_el0;
186 if (key == KEY(3, 3, 14, 3, 1)) return &vcpu->sysregs.cntv_ctl_el0;
187 if (key == KEY(3, 3, 14, 3, 2)) return &vcpu->sysregs.cntv_cval_el0;
188 if (key == KEY(3, 2, 0, 0, 0)) return &vcpu->sysregs.csselr_el1;
189 if (key == KEY(2, 0, 0, 2, 2)) return &vcpu->sysregs.mdscr_el1;
190 return (uint64_t *)0;
191}
192
193static bool is_el1_mmu_key(uint64_t key)
194{
195 return key == KEY(3, 0, 1, 0, 0) ||
196 key == KEY(3, 0, 1, 0, 2) ||
197 key == KEY(3, 0, 2, 0, 0) ||
198 key == KEY(3, 0, 2, 0, 1) ||
199 key == KEY(3, 0, 2, 0, 2) ||
200 key == KEY(3, 0, 10, 2, 0) ||
201 key == KEY(3, 0, 12, 0, 0) ||
202 key == KEY(3, 0, 14, 1, 0);
203}
204
205static const struct policy_entry *effective_entry(const struct policy_entry *entry)
206{
207 if (entry == (const struct policy_entry *)0) {
208 return (const struct policy_entry *)0;
209 }
210
211 g_effective_entry = *entry;
212 if (g_profile == POLICY_PROFILE_DIAGNOSTIC) {
213 if (g_effective_entry.access_class == POLICY_CLASS_PMU ||
214 g_effective_entry.access_class == POLICY_CLASS_MISC) {
215 g_effective_entry.read_policy = POLICY_DENY_SKIP;
216 g_effective_entry.write_policy = POLICY_DENY_SKIP;
217 }
218 } else if (g_profile == POLICY_PROFILE_LINUX_BOOT) {
219 if (g_effective_entry.access_class == POLICY_CLASS_PMU ||
220 g_effective_entry.key == KEY(3, 0, 12, 0, 1)) {
221 g_effective_entry.read_policy = POLICY_DENY_SKIP;
222 g_effective_entry.write_policy = POLICY_DENY_SKIP;
223 }
224 if (g_effective_entry.access_class == POLICY_CLASS_MMU) {
225 g_effective_entry.writable_mask = UINT64_MAX;
226 }
227 if (g_effective_entry.key == KEY(3, 3, 9, 12, 0)) {
228 g_effective_entry.reset_value = 0u;
229 }
230 } else if (g_profile == POLICY_PROFILE_DEBUG) {
231 if (g_effective_entry.access_class == POLICY_CLASS_DEBUG ||
232 g_effective_entry.access_class == POLICY_CLASS_PMU) {
233 g_effective_entry.read_policy = POLICY_EMULATE_READ;
234 g_effective_entry.write_policy = POLICY_WRITE_MASK;
235 g_effective_entry.writable_mask = 0u;
236 }
237 }
238 return &g_effective_entry;
239}
240
241bool policy_apply_sysreg(struct vcpu *vcpu, const struct sysreg_access *access,
242 uint64_t operand, struct policy_decision *decision)
243{
244 if (vcpu == (struct vcpu *)0 || access == (const struct sysreg_access *)0 ||
245 decision == (struct policy_decision *)0) {
246 return false;
247 }
248 hv_memset(decision, 0, sizeof(*decision));
249 const struct policy_entry *entry = effective_entry(sysreg_policy_lookup(access->key));
250 decision->entry = entry;
251 decision->action = POLICY_DENY_SKIP;
252 decision->reason = 250u;
253
254 if (entry == (const struct policy_entry *)0) {
255 return true;
256 }
257
258 decision->action = access->is_read ? entry->read_policy : entry->write_policy;
259 decision->reason = entry->violation_reason;
260
261 uint64_t *slot = shadow_slot(vcpu, access->key);
262 switch (decision->action) {
263 case POLICY_EMULATE_READ:
264 decision->value = slot != (uint64_t *)0 ? *slot : entry->reset_value;
265 return true;
266 case POLICY_EMULATE_WRITE:
267 if (slot == (uint64_t *)0) {
268 decision->action = POLICY_DENY_UNDEF;
269 return true;
270 }
271 *slot = policy_mask_value(entry, *slot, operand);
272 decision->value = *slot;
273 if (g_profile == POLICY_PROFILE_LINUX_BOOT && is_el1_mmu_key(access->key)) {
274 arch_sync_el1_sysregs(vcpu);
275 }
276 timer_vcpu_sync(vcpu);
277 return true;
278 case POLICY_WRITE_MASK:
279 if (slot == (uint64_t *)0) {
280 decision->action = POLICY_DENY_UNDEF;
281 return true;
282 }
283 *slot = policy_mask_value(entry, *slot, operand);
284 decision->value = *slot;
285 if (g_profile == POLICY_PROFILE_LINUX_BOOT && is_el1_mmu_key(access->key)) {
286 arch_sync_el1_sysregs(vcpu);
287 }
288 timer_vcpu_sync(vcpu);
289 return true;
290 case POLICY_DENY_SKIP:
291 case POLICY_DENY_UNDEF:
292 case POLICY_PAUSE:
293 case POLICY_PANIC:
294 case POLICY_ALLOW_NATIVE:
295 default:
296 return true;
297 }
298}
299
300void policy_dump(void)
301{
302 uart_puts("policy profile=");
303 uart_puts(policy_profile_name(g_profile));
304 uart_puts("\n");
305 for (uint32_t i = 0; i < HV_ARRAY_SIZE(g_policy); i++) {
306 const struct policy_entry *entry = effective_entry(&g_policy[i]);
307 uart_puts("policy key=");
308 uart_put_hex64(entry->key);
309 uart_puts(" name=");
310 uart_puts(entry->name);
311 uart_puts(" read=");
312 uart_put_dec64((uint64_t)entry->read_policy);
313 uart_puts(" write=");
314 uart_put_dec64((uint64_t)entry->write_policy);
315 uart_puts(" mask=");
316 uart_put_hex64(entry->writable_mask);
317 uart_puts("\n");
318 }
319}
320
321bool policy_selftest(void)
322{
323 enum policy_profile saved = g_profile;
324 bool profile_ok;
325 for (uint32_t i = 0; i < HV_ARRAY_SIZE(g_policy); i++) {
326 if (!policy_lattice_valid(&g_policy[i])) {
327 return false;
328 }
329 if (sysreg_policy_lookup(g_policy[i].key) != &g_policy[i]) {
330 return false;
331 }
332 for (uint32_t j = i + 1u; j < HV_ARRAY_SIZE(g_policy); j++) {
333 if (g_policy[i].key == g_policy[j].key) {
334 return false;
335 }
336 }
337 }
338 profile_ok = policy_set_profile_name("linux-boot") &&
339 policy_get_profile() == POLICY_PROFILE_LINUX_BOOT &&
340 policy_set_profile(saved);
341 return sysreg_policy_lookup(KEY(3, 4, 1, 1, 0)) != (const struct policy_entry *)0 &&
342 sysreg_policy_lookup(KEY(3, 7, 15, 15, 7)) == (const struct policy_entry *)0 &&
343 profile_ok;
344}