1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * vgic_its_save - KVM_DEV_ARM_ITS_SAVE_TABLES against tables a guest broke.
4 *
5 * Both cases are reachable by a guest on its own, and neither may fail a save
6 * that userspace has to be able to issue:
7 *
8 * - Changing GITS_BASER<coll> drops the collections it described, so the save
9 * writes nothing but the terminating invalid entry.
10 * - A device the device table can no longer address is skipped, and the saved
11 * DTE chain skips it too rather than pointing at an entry never written.
12 *
13 * Both cases then reset and restore, which is what the save exists for.
14 *
15 * Copyright (c) 2026 Google LLC
16 * Author: Fuad Tabba <fuad.tabba@linux.dev>
17 */
18
19 #include <endian.h>
20 #include <linux/align.h>
21 #include <linux/bitfield.h>
22 #include <linux/sizes.h>
23
24 #include "kvm_util.h"
25 #include "gic.h"
26 #include "gic_v3.h"
27 #include "gic_v3_its.h"
28 #include "processor.h"
29 #include "ucall.h"
30 #include "vgic.h"
31
32 #define TEST_MEMSLOT_INDEX 1
33
34 /* All three ITS table entry sizes are 8 bytes in ABI 0. */
35 #define ESZ 8
36 #define ENTRIES_PER_PAGE (SZ_64K / ESZ)
37
38 /* CTE and DTE layout, mirroring KVM's KVM_ITS_* in arch/arm64/kvm/vgic/vgic.h */
39 #define CTE_VALID_MASK BIT_ULL(63)
40 #define DTE_VALID_MASK BIT_ULL(63)
41 #define DTE_NEXT_SHIFT 49
42 #define DTE_NEXT_MASK GENMASK_ULL(62, 49)
43
44 /* L1 entry of an indirect table: valid bit plus a 64K aligned L2 address. */
45 #define L1E_VALID_MASK BIT_ULL(63)
46 #define L1E_ADDR_MASK GENMASK_ULL(51, 16)
47
48 #define GITS_BASER_PAGES_MASK GENMASK_ULL(7, 0)
49
50 #define POISON 0xdeadbeefdeadbeefULL
51
52 /* The collection table starts at two pages and is shrunk to one. */
53 #define COLL_TBL_PAGES 2
54 #define COLL_TBL_SZ (COLL_TBL_PAGES * SZ_64K)
55
56 /* One more collection than the shrunken table can hold. */
57 #define NR_COLLECTIONS (ENTRIES_PER_PAGE + 1)
58
59 /* Two devices, one per L2 block of the indirect device table. */
60 #define DEVICE_A_ID 0
61 #define DEVICE_B_ID ENTRIES_PER_PAGE
62
63 /*
64 * its_send_mapd_cmd() encodes ilog2(itt_size) - 1 as num_eventid_bits, and
65 * vgic_its_restore_itt() scans BIT_ULL(num_eventid_bits) * ESZ, so the size
66 * handed to MAPD has to match the ITT allocated for it.
67 */
68 #define ITT_EVENTID_BITS 13
69 #define ITT_MAPD_SIZE BIT_ULL(ITT_EVENTID_BITS + 1)
70 #define ITT_SZ (BIT_ULL(ITT_EVENTID_BITS) * ESZ)
71
72 static struct kvm_vm *vm;
73 static struct kvm_vcpu *vcpu;
74 static int its_fd;
75 static gpa_t gpa_base;
76
77 static struct test_data {
78 gpa_t device_table;
79 gpa_t collection_table;
80 gpa_t cmdq_base;
81 void *cmdq_base_va;
82
83 gpa_t lpi_prop_table;
84 gpa_t lpi_pend_table;
85
86 void *device_l1_va;
87 gpa_t device_l2[2];
88 gpa_t itt_tables;
89 } test_data;
90
its_baser_offset(unsigned int type)91 static unsigned long its_baser_offset(unsigned int type)
92 {
93 int i;
94
95 for (i = 0; i < GITS_BASER_NR_REGS; i++) {
96 unsigned long offset = GITS_BASER + (i * sizeof(u64));
97 u64 baser = readq_relaxed(GITS_BASE_GVA + offset);
98
99 if (GITS_BASER_TYPE(baser) == type)
100 return offset;
101 }
102
103 GUEST_FAIL("Couldn't find an ITS BASER of type %u", type);
104 return -1;
105 }
106
its_set_enable(bool enable)107 static void its_set_enable(bool enable)
108 {
109 u32 ctlr = readl_relaxed(GITS_BASE_GVA + GITS_CTLR);
110
111 if (enable)
112 ctlr |= GITS_CTLR_ENABLE;
113 else
114 ctlr &= ~GITS_CTLR_ENABLE;
115
116 writel_relaxed(ctlr, GITS_BASE_GVA + GITS_CTLR);
117 }
118
119 /*
120 * Shrink the collection table to a single page, leaving VALID set. BASER
121 * writes are ignored while the ITS is enabled.
122 */
guest_shrink_coll_table(void)123 static void guest_shrink_coll_table(void)
124 {
125 unsigned long offset = its_baser_offset(GITS_BASER_TYPE_COLLECTION);
126 u64 baser;
127
128 its_set_enable(false);
129
130 baser = readq_relaxed(GITS_BASE_GVA + offset);
131 baser &= ~GITS_BASER_PAGES_MASK;
132 writeq_relaxed(baser, GITS_BASE_GVA + offset);
133 }
134
guest_baser_change(void)135 static void guest_baser_change(void)
136 {
137 u32 coll_id;
138
139 gic_init(GIC_V3, 1);
140 gic_rdist_enable_lpis(test_data.lpi_prop_table, SZ_64K,
141 test_data.lpi_pend_table);
142
143 its_init(test_data.collection_table, COLL_TBL_SZ,
144 test_data.device_table, SZ_64K,
145 test_data.cmdq_base, SZ_64K);
146
147 for (coll_id = 0; coll_id < NR_COLLECTIONS; coll_id++)
148 its_send_mapc_cmd(test_data.cmdq_base_va, 0, coll_id, true);
149
150 guest_shrink_coll_table();
151
152 GUEST_DONE();
153 }
154
155 /* Turn the already installed device table into an indirect one. */
guest_make_device_table_indirect(void)156 static void guest_make_device_table_indirect(void)
157 {
158 unsigned long offset = its_baser_offset(GITS_BASER_TYPE_DEVICE);
159 u64 baser;
160
161 its_set_enable(false);
162
163 baser = readq_relaxed(GITS_BASE_GVA + offset);
164 writeq_relaxed(baser | GITS_BASER_INDIRECT, GITS_BASE_GVA + offset);
165
166 its_set_enable(true);
167 }
168
guest_unreachable_device(void)169 static void guest_unreachable_device(void)
170 {
171 u64 *l1;
172
173 gic_init(GIC_V3, 1);
174 gic_rdist_enable_lpis(test_data.lpi_prop_table, SZ_64K,
175 test_data.lpi_pend_table);
176
177 its_init(test_data.collection_table, SZ_64K,
178 test_data.device_table, SZ_64K,
179 test_data.cmdq_base, SZ_64K);
180
181 guest_make_device_table_indirect();
182
183 /* Both L2 blocks present, so both MAPDs are in range. */
184 l1 = test_data.device_l1_va;
185 l1[0] = L1E_VALID_MASK | (test_data.device_l2[0] & L1E_ADDR_MASK);
186 l1[1] = L1E_VALID_MASK | (test_data.device_l2[1] & L1E_ADDR_MASK);
187
188 its_send_mapd_cmd(test_data.cmdq_base_va, DEVICE_A_ID,
189 test_data.itt_tables, ITT_MAPD_SIZE, true);
190 its_send_mapd_cmd(test_data.cmdq_base_va, DEVICE_B_ID,
191 test_data.itt_tables + ITT_SZ, ITT_MAPD_SIZE, true);
192
193 /*
194 * Drop the block holding device B. No ITS command and no GITS_BASER
195 * write is involved, so nothing tells KVM the device is now
196 * unreachable.
197 */
198 l1[1] = 0;
199
200 GUEST_DONE();
201 }
202
run_guest(void)203 static void run_guest(void)
204 {
205 struct ucall uc;
206
207 vcpu_run(vcpu);
208 switch (get_ucall(vcpu, &uc)) {
209 case UCALL_DONE:
210 break;
211 case UCALL_ABORT:
212 REPORT_GUEST_ASSERT(uc);
213 break;
214 default:
215 TEST_FAIL("Unexpected ucall: %lu", uc.cmd);
216 }
217 }
218
save_tables(void)219 static int save_tables(void)
220 {
221 return __kvm_device_attr_set(its_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
222 KVM_DEV_ARM_ITS_SAVE_TABLES, NULL);
223 }
224
its_reg_get(unsigned long offset)225 static u64 its_reg_get(unsigned long offset)
226 {
227 u64 val;
228
229 kvm_device_attr_get(its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS, offset,
230 &val);
231 return val;
232 }
233
its_reg_set(unsigned long offset,u64 val)234 static void its_reg_set(unsigned long offset, u64 val)
235 {
236 kvm_device_attr_set(its_fd, KVM_DEV_ARM_VGIC_GRP_ITS_REGS, offset,
237 &val);
238 }
239
240 /*
241 * What a migration target does with the saved tables, in the order
242 * Documentation/virt/kvm/devices/arm-vgic-its.rst gives: the GITS_ registers
243 * first, then the tables. The reset in between clears GITS_BASER<n>.Valid,
244 * which is why the registers have to be written back before the restore.
245 */
reset_and_restore_tables(void)246 static void reset_and_restore_tables(void)
247 {
248 u64 baser[GITS_BASER_NR_REGS];
249 int ret, i;
250
251 for (i = 0; i < GITS_BASER_NR_REGS; i++)
252 baser[i] = its_reg_get(GITS_BASER + (i * sizeof(u64)));
253
254 ret = __kvm_device_attr_set(its_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
255 KVM_DEV_ARM_ITS_CTRL_RESET, NULL);
256 TEST_ASSERT(!ret, "Expected the reset to succeed, got ret %d errno %d",
257 ret, errno);
258
259 for (i = 0; i < GITS_BASER_NR_REGS; i++)
260 its_reg_set(GITS_BASER + (i * sizeof(u64)), baser[i]);
261
262 ret = __kvm_device_attr_set(its_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
263 KVM_DEV_ARM_ITS_RESTORE_TABLES, NULL);
264 TEST_ASSERT(!ret, "Expected the restore to succeed, got ret %d errno %d",
265 ret, errno);
266 }
267
poison_range(gpa_t base,size_t size)268 static void poison_range(gpa_t base, size_t size)
269 {
270 u64 *entry = addr_gpa2hva(vm, base);
271 size_t i;
272
273 for (i = 0; i < size / ESZ; i++)
274 entry[i] = POISON;
275 }
276
setup_memslot(size_t sz)277 static void setup_memslot(size_t sz)
278 {
279 size_t pages = sz / vm->page_size;
280
281 gpa_base = ((vm_compute_max_gfn(vm) + 1) * vm->page_size) - sz;
282 vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, gpa_base,
283 TEST_MEMSLOT_INDEX, pages, 0);
284 }
285
alloc_64k(size_t nr)286 static gpa_t alloc_64k(size_t nr)
287 {
288 size_t pages_per_64k = vm_calc_num_guest_pages(vm->mode, SZ_64K);
289 gpa_t gpa = vm_phy_pages_alloc(vm, nr * pages_per_64k, gpa_base,
290 TEST_MEMSLOT_INDEX);
291
292 TEST_ASSERT(IS_ALIGNED(gpa, SZ_64K),
293 "Allocation at 0x%lx is not 64K aligned, GITS_BASER cannot address it",
294 gpa);
295 return gpa;
296 }
297
map_to_guest(gpa_t gpa,size_t nr)298 static void map_to_guest(gpa_t gpa, size_t nr)
299 {
300 size_t pages_per_64k = vm_calc_num_guest_pages(vm->mode, SZ_64K);
301
302 virt_map(vm, gpa, gpa, nr * pages_per_64k);
303 }
304
setup_common(void)305 static void setup_common(void)
306 {
307 test_data.cmdq_base = alloc_64k(1);
308 map_to_guest(test_data.cmdq_base, 1);
309 test_data.cmdq_base_va = (void *)test_data.cmdq_base;
310
311 test_data.lpi_prop_table = alloc_64k(1);
312 test_data.lpi_pend_table = alloc_64k(1);
313 }
314
teardown(void)315 static void teardown(void)
316 {
317 close(its_fd);
318 kvm_vm_free(vm);
319 memset(&test_data, 0, sizeof(test_data));
320 }
321
322 /*
323 * A GITS_BASER<coll> write that changes the table drops the collections it
324 * described. The save then has an empty list, so it writes the terminating
325 * invalid entry and nothing else.
326 */
test_baser_change_drops_collections(void)327 static void test_baser_change_drops_collections(void)
328 {
329 u64 *cte;
330 int ret, i;
331
332 pr_info("Testing that a GITS_BASER change drops the collections\n");
333
334 vm = vm_create_with_one_vcpu(&vcpu, guest_baser_change);
335 setup_memslot((4 + COLL_TBL_PAGES) * SZ_64K);
336 its_fd = vgic_its_setup(vm);
337
338 test_data.device_table = alloc_64k(1);
339 test_data.collection_table = alloc_64k(COLL_TBL_PAGES);
340 setup_common();
341
342 sync_global_to_guest(vm, test_data);
343 run_guest();
344
345 /* Anything KVM writes is then the only thing that changed. */
346 poison_range(test_data.collection_table, COLL_TBL_SZ);
347
348 ret = save_tables();
349 TEST_ASSERT(!ret, "Expected the save to succeed, got %d errno %d",
350 ret, errno);
351
352 cte = addr_gpa2hva(vm, test_data.collection_table);
353
354 /*
355 * Finding the terminator at the head of the table is also what proves
356 * the reads below landed in the saved table rather than elsewhere.
357 */
358 TEST_ASSERT(le64toh(cte[0]) == 0,
359 "CTE 0: expected the terminating invalid entry, got 0x%llx",
360 (unsigned long long)le64toh(cte[0]));
361
362 for (i = 1; i < COLL_TBL_SZ / ESZ; i++)
363 TEST_ASSERT(cte[i] == POISON,
364 "CTE %d: expected it untouched, got 0x%llx",
365 i, (unsigned long long)cte[i]);
366
367 reset_and_restore_tables();
368
369 teardown();
370 }
371
372 /*
373 * A device whose L2 block the guest dropped is skipped by the save, and the
374 * DTE chain skips it too: left alone, the surviving device would point at an
375 * entry the save never wrote.
376 */
test_unreachable_device_skipped(void)377 static void test_unreachable_device_skipped(void)
378 {
379 u64 dte;
380 int ret;
381
382 pr_info("Testing that an unreachable device is skipped by the save\n");
383
384 vm = vm_create_with_one_vcpu(&vcpu, guest_unreachable_device);
385 setup_memslot(9 * SZ_64K);
386 its_fd = vgic_its_setup(vm);
387
388 test_data.device_table = alloc_64k(1);
389 test_data.collection_table = alloc_64k(1);
390 test_data.device_l2[0] = alloc_64k(1);
391 test_data.device_l2[1] = alloc_64k(1);
392 test_data.itt_tables = alloc_64k(2);
393 setup_common();
394
395 map_to_guest(test_data.device_table, 1);
396 test_data.device_l1_va = (void *)test_data.device_table;
397
398 sync_global_to_guest(vm, test_data);
399 run_guest();
400
401 poison_range(test_data.device_l2[0], SZ_64K);
402 poison_range(test_data.device_l2[1], SZ_64K);
403
404 ret = save_tables();
405 TEST_ASSERT(!ret, "Expected the save to succeed, got %d errno %d",
406 ret, errno);
407
408 dte = le64toh(*(u64 *)addr_gpa2hva(vm, test_data.device_l2[0]));
409
410 /* Device A is still reachable, so it is saved. */
411 TEST_ASSERT(dte & DTE_VALID_MASK,
412 "Device A: expected a valid DTE, got 0x%llx",
413 (unsigned long long)dte);
414
415 /*
416 * Device B is the only device after it and was skipped, so nothing
417 * follows A in the saved chain.
418 */
419 TEST_ASSERT(FIELD_GET(DTE_NEXT_MASK, dte) == 0,
420 "Device A: expected no next device, got offset %llu",
421 (unsigned long long)FIELD_GET(DTE_NEXT_MASK, dte));
422
423 /* And nothing was written into the block the guest dropped. */
424 TEST_ASSERT(*(u64 *)addr_gpa2hva(vm, test_data.device_l2[1]) == POISON,
425 "Device B: expected its entry untouched");
426
427 reset_and_restore_tables();
428
429 teardown();
430 }
431
main(void)432 int main(void)
433 {
434 TEST_REQUIRE(kvm_supports_vgic_v3());
435
436 test_baser_change_drops_collections();
437 test_unreachable_device_skipped();
438
439 pr_info("All ok!\n");
440 return 0;
441 }
442