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