Skip to content

Commit 3b1d915

Browse files
Ricardo KollerMarc Zyngier
authored andcommitted
KVM: selftests: aarch64: Add userfaultfd tests into page_fault_test
Add some userfaultfd tests into page_fault_test. Punch holes into the data and/or page-table memslots, perform some accesses, and check that the faults are taken (or not taken) when expected. Signed-off-by: Ricardo Koller <[email protected]> Signed-off-by: Marc Zyngier <[email protected]> Link: https://lore.kernel.org/r/[email protected]
1 parent 35c5810 commit 3b1d915

File tree

1 file changed

+187
-0
lines changed

1 file changed

+187
-0
lines changed

tools/testing/selftests/kvm/aarch64/page_fault_test.c

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,27 @@ static uint64_t *guest_test_memory = (uint64_t *)TEST_GVA;
3535
#define PREPARE_FN_NR 10
3636
#define CHECK_FN_NR 10
3737

38+
static struct event_cnt {
39+
int uffd_faults;
40+
/* uffd_faults is incremented from multiple threads. */
41+
pthread_mutex_t uffd_faults_mutex;
42+
} events;
43+
3844
struct test_desc {
3945
const char *name;
4046
uint64_t mem_mark_cmd;
4147
/* Skip the test if any prepare function returns false */
4248
bool (*guest_prepare[PREPARE_FN_NR])(void);
4349
void (*guest_test)(void);
4450
void (*guest_test_check[CHECK_FN_NR])(void);
51+
uffd_handler_t uffd_pt_handler;
52+
uffd_handler_t uffd_data_handler;
4553
void (*dabt_handler)(struct ex_regs *regs);
4654
void (*iabt_handler)(struct ex_regs *regs);
4755
uint32_t pt_memslot_flags;
4856
uint32_t data_memslot_flags;
4957
bool skip;
58+
struct event_cnt expected_events;
5059
};
5160

5261
struct test_params {
@@ -263,7 +272,110 @@ static void no_iabt_handler(struct ex_regs *regs)
263272
GUEST_ASSERT_1(false, regs->pc);
264273
}
265274

275+
static struct uffd_args {
276+
char *copy;
277+
void *hva;
278+
uint64_t paging_size;
279+
} pt_args, data_args;
280+
266281
/* Returns true to continue the test, and false if it should be skipped. */
282+
static int uffd_generic_handler(int uffd_mode, int uffd, struct uffd_msg *msg,
283+
struct uffd_args *args, bool expect_write)
284+
{
285+
uint64_t addr = msg->arg.pagefault.address;
286+
uint64_t flags = msg->arg.pagefault.flags;
287+
struct uffdio_copy copy;
288+
int ret;
289+
290+
TEST_ASSERT(uffd_mode == UFFDIO_REGISTER_MODE_MISSING,
291+
"The only expected UFFD mode is MISSING");
292+
ASSERT_EQ(!!(flags & UFFD_PAGEFAULT_FLAG_WRITE), expect_write);
293+
ASSERT_EQ(addr, (uint64_t)args->hva);
294+
295+
pr_debug("uffd fault: addr=%p write=%d\n",
296+
(void *)addr, !!(flags & UFFD_PAGEFAULT_FLAG_WRITE));
297+
298+
copy.src = (uint64_t)args->copy;
299+
copy.dst = addr;
300+
copy.len = args->paging_size;
301+
copy.mode = 0;
302+
303+
ret = ioctl(uffd, UFFDIO_COPY, &copy);
304+
if (ret == -1) {
305+
pr_info("Failed UFFDIO_COPY in 0x%lx with errno: %d\n",
306+
addr, errno);
307+
return ret;
308+
}
309+
310+
pthread_mutex_lock(&events.uffd_faults_mutex);
311+
events.uffd_faults += 1;
312+
pthread_mutex_unlock(&events.uffd_faults_mutex);
313+
return 0;
314+
}
315+
316+
static int uffd_pt_write_handler(int mode, int uffd, struct uffd_msg *msg)
317+
{
318+
return uffd_generic_handler(mode, uffd, msg, &pt_args, true);
319+
}
320+
321+
static int uffd_data_write_handler(int mode, int uffd, struct uffd_msg *msg)
322+
{
323+
return uffd_generic_handler(mode, uffd, msg, &data_args, true);
324+
}
325+
326+
static int uffd_data_read_handler(int mode, int uffd, struct uffd_msg *msg)
327+
{
328+
return uffd_generic_handler(mode, uffd, msg, &data_args, false);
329+
}
330+
331+
static void setup_uffd_args(struct userspace_mem_region *region,
332+
struct uffd_args *args)
333+
{
334+
args->hva = (void *)region->region.userspace_addr;
335+
args->paging_size = region->region.memory_size;
336+
337+
args->copy = malloc(args->paging_size);
338+
TEST_ASSERT(args->copy, "Failed to allocate data copy.");
339+
memcpy(args->copy, args->hva, args->paging_size);
340+
}
341+
342+
static void setup_uffd(struct kvm_vm *vm, struct test_params *p,
343+
struct uffd_desc **pt_uffd, struct uffd_desc **data_uffd)
344+
{
345+
struct test_desc *test = p->test_desc;
346+
int uffd_mode = UFFDIO_REGISTER_MODE_MISSING;
347+
348+
setup_uffd_args(vm_get_mem_region(vm, MEM_REGION_PT), &pt_args);
349+
setup_uffd_args(vm_get_mem_region(vm, MEM_REGION_TEST_DATA), &data_args);
350+
351+
*pt_uffd = NULL;
352+
if (test->uffd_pt_handler)
353+
*pt_uffd = uffd_setup_demand_paging(uffd_mode, 0,
354+
pt_args.hva,
355+
pt_args.paging_size,
356+
test->uffd_pt_handler);
357+
358+
*data_uffd = NULL;
359+
if (test->uffd_data_handler)
360+
*data_uffd = uffd_setup_demand_paging(uffd_mode, 0,
361+
data_args.hva,
362+
data_args.paging_size,
363+
test->uffd_data_handler);
364+
}
365+
366+
static void free_uffd(struct test_desc *test, struct uffd_desc *pt_uffd,
367+
struct uffd_desc *data_uffd)
368+
{
369+
if (test->uffd_pt_handler)
370+
uffd_stop_demand_paging(pt_uffd);
371+
if (test->uffd_data_handler)
372+
uffd_stop_demand_paging(data_uffd);
373+
374+
free(pt_args.copy);
375+
free(data_args.copy);
376+
}
377+
378+
/* Returns false if the test should be skipped. */
267379
static bool punch_hole_in_backing_store(struct kvm_vm *vm,
268380
struct userspace_mem_region *region)
269381
{
@@ -404,6 +516,11 @@ static void setup_memslots(struct kvm_vm *vm, struct test_params *p)
404516
vm->memslots[MEM_REGION_TEST_DATA] = TEST_DATA_MEMSLOT;
405517
}
406518

519+
static void check_event_counts(struct test_desc *test)
520+
{
521+
ASSERT_EQ(test->expected_events.uffd_faults, events.uffd_faults);
522+
}
523+
407524
static void print_test_banner(enum vm_guest_mode mode, struct test_params *p)
408525
{
409526
struct test_desc *test = p->test_desc;
@@ -414,6 +531,11 @@ static void print_test_banner(enum vm_guest_mode mode, struct test_params *p)
414531
vm_mem_backing_src_alias(p->src_type)->name);
415532
}
416533

534+
static void reset_event_counts(void)
535+
{
536+
memset(&events, 0, sizeof(events));
537+
}
538+
417539
/*
418540
* This function either succeeds, skips the test (after setting test->skip), or
419541
* fails with a TEST_FAIL that aborts all tests.
@@ -453,6 +575,7 @@ static void run_test(enum vm_guest_mode mode, void *arg)
453575
struct test_desc *test = p->test_desc;
454576
struct kvm_vm *vm;
455577
struct kvm_vcpu *vcpu;
578+
struct uffd_desc *pt_uffd, *data_uffd;
456579

457580
print_test_banner(mode, p);
458581

@@ -465,14 +588,31 @@ static void run_test(enum vm_guest_mode mode, void *arg)
465588

466589
ucall_init(vm, NULL);
467590

591+
reset_event_counts();
592+
593+
/*
594+
* Set some code in the data memslot for the guest to execute (only
595+
* applicable to the EXEC tests). This has to be done before
596+
* setup_uffd() as that function copies the memslot data for the uffd
597+
* handler.
598+
*/
468599
load_exec_code_for_test(vm);
600+
setup_uffd(vm, p, &pt_uffd, &data_uffd);
469601
setup_abort_handlers(vm, vcpu, test);
470602
vcpu_args_set(vcpu, 1, test);
471603

472604
vcpu_run_loop(vm, vcpu, test);
473605

474606
ucall_uninit(vm);
475607
kvm_vm_free(vm);
608+
free_uffd(test, pt_uffd, data_uffd);
609+
610+
/*
611+
* Make sure we check the events after the uffd threads have exited,
612+
* which means they updated their respective event counters.
613+
*/
614+
if (!test->skip)
615+
check_event_counts(test);
476616
}
477617

478618
static void help(char *name)
@@ -488,6 +628,7 @@ static void help(char *name)
488628
#define SNAME(s) #s
489629
#define SCAT2(a, b) SNAME(a ## _ ## b)
490630
#define SCAT3(a, b, c) SCAT2(a, SCAT2(b, c))
631+
#define SCAT4(a, b, c, d) SCAT2(a, SCAT3(b, c, d))
491632

492633
#define _CHECK(_test) _CHECK_##_test
493634
#define _PREPARE(_test) _PREPARE_##_test
@@ -515,6 +656,21 @@ static void help(char *name)
515656
.mem_mark_cmd = _mark_cmd, \
516657
.guest_test = _access, \
517658
.guest_test_check = { _CHECK(_with_af) }, \
659+
.expected_events = { 0 }, \
660+
}
661+
662+
#define TEST_UFFD(_access, _with_af, _mark_cmd, \
663+
_uffd_data_handler, _uffd_pt_handler, _uffd_faults) \
664+
{ \
665+
.name = SCAT4(uffd, _access, _with_af, #_mark_cmd), \
666+
.guest_prepare = { _PREPARE(_with_af), \
667+
_PREPARE(_access) }, \
668+
.guest_test = _access, \
669+
.mem_mark_cmd = _mark_cmd, \
670+
.guest_test_check = { _CHECK(_with_af) }, \
671+
.uffd_data_handler = _uffd_data_handler, \
672+
.uffd_pt_handler = _uffd_pt_handler, \
673+
.expected_events = { .uffd_faults = _uffd_faults, }, \
518674
}
519675

520676
static struct test_desc tests[] = {
@@ -545,6 +701,37 @@ static struct test_desc tests[] = {
545701
TEST_ACCESS(guest_at, no_af, CMD_HOLE_DATA),
546702
TEST_ACCESS(guest_dc_zva, no_af, CMD_HOLE_DATA),
547703

704+
/*
705+
* Punch holes in the data and PT backing stores and mark them for
706+
* userfaultfd handling. This should result in 2 faults: the access
707+
* on the data backing store, and its respective S1 page table walk
708+
* (S1PTW).
709+
*/
710+
TEST_UFFD(guest_read64, with_af, CMD_HOLE_DATA | CMD_HOLE_PT,
711+
uffd_data_read_handler, uffd_pt_write_handler, 2),
712+
/* no_af should also lead to a PT write. */
713+
TEST_UFFD(guest_read64, no_af, CMD_HOLE_DATA | CMD_HOLE_PT,
714+
uffd_data_read_handler, uffd_pt_write_handler, 2),
715+
/* Note how that cas invokes the read handler. */
716+
TEST_UFFD(guest_cas, with_af, CMD_HOLE_DATA | CMD_HOLE_PT,
717+
uffd_data_read_handler, uffd_pt_write_handler, 2),
718+
/*
719+
* Can't test guest_at with_af as it's IMPDEF whether the AF is set.
720+
* The S1PTW fault should still be marked as a write.
721+
*/
722+
TEST_UFFD(guest_at, no_af, CMD_HOLE_DATA | CMD_HOLE_PT,
723+
uffd_data_read_handler, uffd_pt_write_handler, 1),
724+
TEST_UFFD(guest_ld_preidx, with_af, CMD_HOLE_DATA | CMD_HOLE_PT,
725+
uffd_data_read_handler, uffd_pt_write_handler, 2),
726+
TEST_UFFD(guest_write64, with_af, CMD_HOLE_DATA | CMD_HOLE_PT,
727+
uffd_data_write_handler, uffd_pt_write_handler, 2),
728+
TEST_UFFD(guest_dc_zva, with_af, CMD_HOLE_DATA | CMD_HOLE_PT,
729+
uffd_data_write_handler, uffd_pt_write_handler, 2),
730+
TEST_UFFD(guest_st_preidx, with_af, CMD_HOLE_DATA | CMD_HOLE_PT,
731+
uffd_data_write_handler, uffd_pt_write_handler, 2),
732+
TEST_UFFD(guest_exec, with_af, CMD_HOLE_DATA | CMD_HOLE_PT,
733+
uffd_data_read_handler, uffd_pt_write_handler, 2),
734+
548735
{ 0 }
549736
};
550737

0 commit comments

Comments
 (0)