xref: /linux/tools/testing/selftests/kvm/arm64/vgic_its_save.c (revision d5a821f66047c2effd06147b84775f9b2ce0f4aa)
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