@@ -52,6 +52,8 @@ uint64_t pte_gpa;
enum { PT, TEST, NR_MEMSLOTS};
struct memslot_desc {
+ size_t paging_size;
+ char *data_copy;
void *hva;
uint64_t gpa;
uint64_t size;
@@ -73,6 +75,9 @@ struct memslot_desc {
static struct event_cnt {
int aborts;
int fail_vcpu_runs;
+ int uffd_faults;
+ /* uffd_faults is incremented from multiple threads. */
+ pthread_mutex_t uffd_faults_mutex;
} events;
struct test_desc {
@@ -82,6 +87,8 @@ struct test_desc {
bool (*guest_prepare[PREPARE_FN_NR])(void);
void (*guest_test)(void);
void (*guest_test_check[CHECK_FN_NR])(void);
+ int (*uffd_pt_handler)(int mode, int uffd, struct uffd_msg *msg);
+ int (*uffd_test_handler)(int mode, int uffd, struct uffd_msg *msg);
void (*dabt_handler)(struct ex_regs *regs);
void (*iabt_handler)(struct ex_regs *regs);
uint32_t pt_memslot_flags;
@@ -306,6 +313,7 @@ static void guest_code(struct test_desc *test)
if (!guest_prepare(test))
GUEST_SYNC(CMD_SKIP_TEST);
+ flush_tlb_page(TEST_GVA);
GUEST_SYNC(test->mem_mark_cmd);
test->guest_test();
@@ -323,6 +331,56 @@ static void no_iabt_handler(struct ex_regs *regs)
GUEST_ASSERT_1(false, regs->pc);
}
+static int uffd_generic_handler(int uffd_mode, int uffd,
+ struct uffd_msg *msg, struct memslot_desc *memslot,
+ bool expect_write)
+{
+ uint64_t addr = msg->arg.pagefault.address;
+ uint64_t flags = msg->arg.pagefault.flags;
+ struct uffdio_copy copy;
+ int ret;
+
+ TEST_ASSERT(uffd_mode == UFFDIO_REGISTER_MODE_MISSING,
+ "The only expected UFFD mode is MISSING");
+ ASSERT_EQ(!!(flags & UFFD_PAGEFAULT_FLAG_WRITE), expect_write);
+ ASSERT_EQ(addr, (uint64_t)memslot->hva);
+
+ pr_debug("uffd fault: addr=%p write=%d\n",
+ (void *)addr, !!(flags & UFFD_PAGEFAULT_FLAG_WRITE));
+
+ copy.src = (uint64_t)memslot->data_copy;
+ copy.dst = addr;
+ copy.len = memslot->paging_size;
+ copy.mode = 0;
+
+ ret = ioctl(uffd, UFFDIO_COPY, ©);
+ if (ret == -1) {
+ pr_info("Failed UFFDIO_COPY in 0x%lx with errno: %d\n",
+ addr, errno);
+ return ret;
+ }
+
+ pthread_mutex_lock(&events.uffd_faults_mutex);
+ events.uffd_faults += 1;
+ pthread_mutex_unlock(&events.uffd_faults_mutex);
+ return 0;
+}
+
+static int uffd_pt_write_handler(int mode, int uffd, struct uffd_msg *msg)
+{
+ return uffd_generic_handler(mode, uffd, msg, &memslot[PT], true);
+}
+
+static int uffd_test_write_handler(int mode, int uffd, struct uffd_msg *msg)
+{
+ return uffd_generic_handler(mode, uffd, msg, &memslot[TEST], true);
+}
+
+static int uffd_test_read_handler(int mode, int uffd, struct uffd_msg *msg)
+{
+ return uffd_generic_handler(mode, uffd, msg, &memslot[TEST], false);
+}
+
static void punch_hole_in_memslot(struct kvm_vm *vm,
struct memslot_desc *memslot)
{
@@ -332,11 +390,11 @@ static void punch_hole_in_memslot(struct kvm_vm *vm,
fd = vm_mem_region_get_src_fd(vm, memslot->idx);
if (fd != -1) {
ret = fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
- 0, memslot->size);
+ 0, memslot->paging_size);
TEST_ASSERT(ret == 0, "fallocate failed, errno: %d\n", errno);
} else {
hva = addr_gpa2hva(vm, memslot->gpa);
- ret = madvise(hva, memslot->size, MADV_DONTNEED);
+ ret = madvise(hva, memslot->paging_size, MADV_DONTNEED);
TEST_ASSERT(ret == 0, "madvise failed, errno: %d\n", errno);
}
}
@@ -484,9 +542,60 @@ static void setup_memslots(struct kvm_vm *vm, enum vm_guest_mode mode,
virt_pg_map(vm, TEST_PTE_GVA, pte_gpa);
}
+static void setup_uffd(enum vm_guest_mode mode, struct test_params *p,
+ struct uffd_desc **uffd)
+{
+ struct test_desc *test = p->test_desc;
+ uint64_t large_page_size = get_backing_src_pagesz(p->src_type);
+ int i;
+
+ /*
+ * When creating the map, we might not only have created a pte page,
+ * but also an intermediate level (pte_gpa != gpa[PT]). So, we
+ * might need to demand page both.
+ */
+ memslot[PT].paging_size = align_up(pte_gpa - memslot[PT].gpa,
+ large_page_size) + large_page_size;
+ memslot[TEST].paging_size = large_page_size;
+
+ for (i = 0; i < NR_MEMSLOTS; i++) {
+ memslot[i].data_copy = malloc(memslot[i].paging_size);
+ TEST_ASSERT(memslot[i].data_copy, "Failed malloc.");
+ memcpy(memslot[i].data_copy, memslot[i].hva,
+ memslot[i].paging_size);
+ }
+
+ uffd[PT] = NULL;
+ if (test->uffd_pt_handler)
+ uffd[PT] = uffd_setup_demand_paging(
+ UFFDIO_REGISTER_MODE_MISSING, 0,
+ memslot[PT].hva, memslot[PT].paging_size,
+ test->uffd_pt_handler);
+
+ uffd[TEST] = NULL;
+ if (test->uffd_test_handler)
+ uffd[TEST] = uffd_setup_demand_paging(
+ UFFDIO_REGISTER_MODE_MISSING, 0,
+ memslot[TEST].hva, memslot[TEST].paging_size,
+ test->uffd_test_handler);
+}
+
static void check_event_counts(struct test_desc *test)
{
ASSERT_EQ(test->expected_events.aborts, events.aborts);
+ ASSERT_EQ(test->expected_events.uffd_faults, events.uffd_faults);
+}
+
+static void free_uffd(struct test_desc *test, struct uffd_desc **uffd)
+{
+ int i;
+
+ if (test->uffd_pt_handler)
+ uffd_stop_demand_paging(uffd[PT]);
+ if (test->uffd_test_handler)
+ uffd_stop_demand_paging(uffd[TEST]);
+ for (i = 0; i < NR_MEMSLOTS; i++)
+ free(memslot[i].data_copy);
}
static void print_test_banner(enum vm_guest_mode mode, struct test_params *p)
@@ -543,6 +652,7 @@ static void run_test(enum vm_guest_mode mode, void *arg)
struct test_params *p = (struct test_params *)arg;
struct test_desc *test = p->test_desc;
struct kvm_vm *vm;
+ struct uffd_desc *uffd[NR_MEMSLOTS];
bool skip_test = false;
print_test_banner(mode, p);
@@ -554,7 +664,14 @@ static void run_test(enum vm_guest_mode mode, void *arg)
reset_event_counts();
setup_memslots(vm, mode, p);
+ /*
+ * Set some code at memslot[TEST].hva for the guest to execute (only
+ * applicable to the EXEC tests). This has to be done before
+ * setup_uffd() as that function copies the memslot data for the uffd
+ * handler.
+ */
load_exec_code_for_test();
+ setup_uffd(mode, p, uffd);
setup_abort_handlers(vm, test);
vcpu_args_set(vm, 0, 1, test);
@@ -565,7 +682,12 @@ static void run_test(enum vm_guest_mode mode, void *arg)
sync_stats_from_guest(vm);
ucall_uninit(vm);
kvm_vm_free(vm);
+ free_uffd(test, uffd);
+ /*
+ * Make sure this is called after the uffd threads have exited (and
+ * updated their respective event counters).
+ */
if (!skip_test)
check_event_counts(test);
}
@@ -616,6 +738,7 @@ int main(int argc, char *argv[])
#define SNAME(s) #s
#define SCAT2(a, b) SNAME(a ## _ ## b)
#define SCAT3(a, b, c) SCAT2(a, SCAT2(b, c))
+#define SCAT4(a, b, c, d) SCAT2(a, SCAT3(b, c, d))
#define _CHECK(_test) _CHECK_##_test
#define _PREPARE(_test) _PREPARE_##_test
@@ -646,6 +769,20 @@ int main(int argc, char *argv[])
.expected_events = { 0 }, \
}
+#define TEST_UFFD(_access, _with_af, _mark_cmd, \
+ _uffd_test_handler, _uffd_pt_handler, _uffd_faults) \
+{ \
+ .name = SCAT4(uffd, _access, _with_af, #_mark_cmd), \
+ .guest_prepare = { _PREPARE(_with_af), \
+ _PREPARE(_access) }, \
+ .guest_test = _access, \
+ .mem_mark_cmd = _mark_cmd, \
+ .guest_test_check = { _CHECK(_with_af) }, \
+ .uffd_test_handler = _uffd_test_handler, \
+ .uffd_pt_handler = _uffd_pt_handler, \
+ .expected_events = { .uffd_faults = _uffd_faults, }, \
+}
+
static struct test_desc tests[] = {
/* Check that HW is setting the Access Flag (AF) (sanity checks). */
TEST_ACCESS(guest_read64, with_af, CMD_NONE),
@@ -668,6 +805,36 @@ static struct test_desc tests[] = {
TEST_ACCESS(guest_at, no_af, CMD_HOLE_TEST),
TEST_ACCESS(guest_dc_zva, no_af, CMD_HOLE_TEST),
+ /*
+ * Punch holes in the test and PT memslots and mark them for
+ * userfaultfd handling. This should result in 2 faults: the test
+ * access and its respective S1 page table walk (S1PTW).
+ */
+ TEST_UFFD(guest_read64, with_af, CMD_HOLE_TEST | CMD_HOLE_PT,
+ uffd_test_read_handler, uffd_pt_write_handler, 2),
+ /* no_af should also lead to a PT write. */
+ TEST_UFFD(guest_read64, no_af, CMD_HOLE_TEST | CMD_HOLE_PT,
+ uffd_test_read_handler, uffd_pt_write_handler, 2),
+ /* Note how that cas invokes the read handler. */
+ TEST_UFFD(guest_cas, with_af, CMD_HOLE_TEST | CMD_HOLE_PT,
+ uffd_test_read_handler, uffd_pt_write_handler, 2),
+ /*
+ * Can't test guest_at with_af as it's IMPDEF whether the AF is set.
+ * The S1PTW fault should still be marked as a write.
+ */
+ TEST_UFFD(guest_at, no_af, CMD_HOLE_TEST | CMD_HOLE_PT,
+ uffd_test_read_handler, uffd_pt_write_handler, 1),
+ TEST_UFFD(guest_ld_preidx, with_af, CMD_HOLE_TEST | CMD_HOLE_PT,
+ uffd_test_read_handler, uffd_pt_write_handler, 2),
+ TEST_UFFD(guest_write64, with_af, CMD_HOLE_TEST | CMD_HOLE_PT,
+ uffd_test_write_handler, uffd_pt_write_handler, 2),
+ TEST_UFFD(guest_dc_zva, with_af, CMD_HOLE_TEST | CMD_HOLE_PT,
+ uffd_test_write_handler, uffd_pt_write_handler, 2),
+ TEST_UFFD(guest_st_preidx, with_af, CMD_HOLE_TEST | CMD_HOLE_PT,
+ uffd_test_write_handler, uffd_pt_write_handler, 2),
+ TEST_UFFD(guest_exec, with_af, CMD_HOLE_TEST | CMD_HOLE_PT,
+ uffd_test_read_handler, uffd_pt_write_handler, 2),
+
{ 0 },
};
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 <ricarkol@google.com> --- .../selftests/kvm/aarch64/page_fault_test.c | 171 +++++++++++++++++- 1 file changed, 169 insertions(+), 2 deletions(-)