Skip to content

Commit 14c47b7

Browse files
Andrew Jonesbonzini
authored andcommitted
kvm: selftests: introduce ucall
Rework the guest exit to userspace code to generalize the concept into what it is, a "hypercall to userspace", and provide two implementations of it: the PortIO version currently used, but only useable by x86, and an MMIO version that other architectures (except s390) can use. Signed-off-by: Andrew Jones <drjones@redhat.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
1 parent 6c93026 commit 14c47b7

File tree

10 files changed

+222
-85
lines changed

10 files changed

+222
-85
lines changed

tools/testing/selftests/kvm/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ all:
33
top_srcdir = ../../../../
44
UNAME_M := $(shell uname -m)
55

6-
LIBKVM = lib/assert.c lib/elf.c lib/io.c lib/kvm_util.c lib/sparsebit.c
6+
LIBKVM = lib/assert.c lib/elf.c lib/io.c lib/kvm_util.c lib/ucall.c lib/sparsebit.c
77
LIBKVM_x86_64 = lib/x86.c lib/vmx.c
88

99
TEST_GEN_PROGS_x86_64 = platform_info_test

tools/testing/selftests/kvm/cr4_cpuid_sync_test.c

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ int main(int argc, char *argv[])
6767
struct kvm_vm *vm;
6868
struct kvm_sregs sregs;
6969
struct kvm_cpuid_entry2 *entry;
70+
struct ucall uc;
7071
int rc;
7172

7273
entry = kvm_get_supported_cpuid_entry(1);
@@ -87,21 +88,20 @@ int main(int argc, char *argv[])
8788
rc = _vcpu_run(vm, VCPU_ID);
8889

8990
if (run->exit_reason == KVM_EXIT_IO) {
90-
switch (run->io.port) {
91-
case GUEST_PORT_SYNC:
91+
switch (get_ucall(vm, VCPU_ID, &uc)) {
92+
case UCALL_SYNC:
9293
/* emulate hypervisor clearing CR4.OSXSAVE */
9394
vcpu_sregs_get(vm, VCPU_ID, &sregs);
9495
sregs.cr4 &= ~X86_CR4_OSXSAVE;
9596
vcpu_sregs_set(vm, VCPU_ID, &sregs);
9697
break;
97-
case GUEST_PORT_ABORT:
98+
case UCALL_ABORT:
9899
TEST_ASSERT(false, "Guest CR4 bit (OSXSAVE) unsynchronized with CPUID bit.");
99100
break;
100-
case GUEST_PORT_DONE:
101+
case UCALL_DONE:
101102
goto done;
102103
default:
103-
TEST_ASSERT(false, "Unknown port 0x%x.",
104-
run->io.port);
104+
TEST_ASSERT(false, "Unknown ucall 0x%x.", uc.cmd);
105105
}
106106
}
107107
}

tools/testing/selftests/kvm/dirty_log_test.c

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ void *vcpu_worker(void *data)
110110
uint64_t loops, *guest_array, pages_count = 0;
111111
struct kvm_vm *vm = data;
112112
struct kvm_run *run;
113-
struct guest_args args;
113+
struct ucall uc;
114114

115115
run = vcpu_state(vm, VCPU_ID);
116116

@@ -124,9 +124,8 @@ void *vcpu_worker(void *data)
124124
while (!READ_ONCE(host_quit)) {
125125
/* Let the guest to dirty these random pages */
126126
ret = _vcpu_run(vm, VCPU_ID);
127-
guest_args_read(vm, VCPU_ID, &args);
128127
if (run->exit_reason == KVM_EXIT_IO &&
129-
args.port == GUEST_PORT_SYNC) {
128+
get_ucall(vm, VCPU_ID, &uc) == UCALL_SYNC) {
130129
pages_count += TEST_PAGES_PER_LOOP;
131130
generate_random_array(guest_array, TEST_PAGES_PER_LOOP);
132131
} else {

tools/testing/selftests/kvm/include/kvm_util.h

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -152,43 +152,49 @@ allocate_kvm_dirty_log(struct kvm_userspace_memory_region *region);
152152

153153
int vm_create_device(struct kvm_vm *vm, struct kvm_create_device *cd);
154154

155-
#define GUEST_PORT_SYNC 0x1000
156-
#define GUEST_PORT_ABORT 0x1001
157-
#define GUEST_PORT_DONE 0x1002
158-
159-
static inline void __exit_to_l0(uint16_t port, uint64_t arg0, uint64_t arg1)
160-
{
161-
__asm__ __volatile__("in %[port], %%al"
162-
:
163-
: [port]"d"(port), "D"(arg0), "S"(arg1)
164-
: "rax");
165-
}
166-
167-
/*
168-
* Allows to pass three arguments to the host: port is 16bit wide,
169-
* arg0 & arg1 are 64bit wide
170-
*/
171-
#define GUEST_SYNC_ARGS(_port, _arg0, _arg1) \
172-
__exit_to_l0(_port, (uint64_t) (_arg0), (uint64_t) (_arg1))
173-
174-
#define GUEST_ASSERT(_condition) do { \
175-
if (!(_condition)) \
176-
GUEST_SYNC_ARGS(GUEST_PORT_ABORT, \
177-
"Failed guest assert: " \
178-
#_condition, __LINE__); \
179-
} while (0)
180-
181-
#define GUEST_SYNC(stage) GUEST_SYNC_ARGS(GUEST_PORT_SYNC, "hello", stage)
155+
#define sync_global_to_guest(vm, g) ({ \
156+
typeof(g) *_p = addr_gva2hva(vm, (vm_vaddr_t)&(g)); \
157+
memcpy(_p, &(g), sizeof(g)); \
158+
})
159+
160+
#define sync_global_from_guest(vm, g) ({ \
161+
typeof(g) *_p = addr_gva2hva(vm, (vm_vaddr_t)&(g)); \
162+
memcpy(&(g), _p, sizeof(g)); \
163+
})
164+
165+
/* ucall implementation types */
166+
typedef enum {
167+
UCALL_PIO,
168+
UCALL_MMIO,
169+
} ucall_type_t;
170+
171+
/* Common ucalls */
172+
enum {
173+
UCALL_NONE,
174+
UCALL_SYNC,
175+
UCALL_ABORT,
176+
UCALL_DONE,
177+
};
182178

183-
#define GUEST_DONE() GUEST_SYNC_ARGS(GUEST_PORT_DONE, 0, 0)
179+
#define UCALL_MAX_ARGS 6
184180

185-
struct guest_args {
186-
uint64_t arg0;
187-
uint64_t arg1;
188-
uint16_t port;
189-
} __attribute__ ((packed));
181+
struct ucall {
182+
uint64_t cmd;
183+
uint64_t args[UCALL_MAX_ARGS];
184+
};
190185

191-
void guest_args_read(struct kvm_vm *vm, uint32_t vcpu_id,
192-
struct guest_args *args);
186+
void ucall_init(struct kvm_vm *vm, ucall_type_t type, void *arg);
187+
void ucall_uninit(struct kvm_vm *vm);
188+
void ucall(uint64_t cmd, int nargs, ...);
189+
uint64_t get_ucall(struct kvm_vm *vm, uint32_t vcpu_id, struct ucall *uc);
190+
191+
#define GUEST_SYNC(stage) ucall(UCALL_SYNC, 2, "hello", stage)
192+
#define GUEST_DONE() ucall(UCALL_DONE, 0)
193+
#define GUEST_ASSERT(_condition) do { \
194+
if (!(_condition)) \
195+
ucall(UCALL_ABORT, 2, \
196+
"Failed guest assert: " \
197+
#_condition, __LINE__); \
198+
} while (0)
193199

194200
#endif /* SELFTEST_KVM_UTIL_H */

tools/testing/selftests/kvm/lib/kvm_util.c

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ struct kvm_vm *vm_create(enum vm_guest_mode mode, uint64_t phy_pages, int perm)
133133
case VM_MODE_FLAT48PG:
134134
vm->page_size = 0x1000;
135135
vm->page_shift = 12;
136+
vm->va_bits = 48;
136137

137138
/* Limit to 48-bit canonical virtual addresses. */
138139
vm->vpages_valid = sparsebit_alloc();
@@ -1669,17 +1670,3 @@ void *addr_gva2hva(struct kvm_vm *vm, vm_vaddr_t gva)
16691670
{
16701671
return addr_gpa2hva(vm, addr_gva2gpa(vm, gva));
16711672
}
1672-
1673-
void guest_args_read(struct kvm_vm *vm, uint32_t vcpu_id,
1674-
struct guest_args *args)
1675-
{
1676-
struct kvm_run *run = vcpu_state(vm, vcpu_id);
1677-
struct kvm_regs regs;
1678-
1679-
memset(&regs, 0, sizeof(regs));
1680-
vcpu_regs_get(vm, vcpu_id, &regs);
1681-
1682-
args->port = run->io.port;
1683-
args->arg0 = regs.rdi;
1684-
args->arg1 = regs.rsi;
1685-
}

tools/testing/selftests/kvm/lib/kvm_util_internal.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ struct kvm_vm {
4747
int fd;
4848
unsigned int page_size;
4949
unsigned int page_shift;
50+
unsigned int va_bits;
5051
uint64_t max_gfn;
5152
struct vcpu *vcpu_head;
5253
struct userspace_mem_region *userspace_mem_region_head;
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* ucall support. A ucall is a "hypercall to userspace".
4+
*
5+
* Copyright (C) 2018, Red Hat, Inc.
6+
*/
7+
#include "kvm_util.h"
8+
#include "kvm_util_internal.h"
9+
10+
#define UCALL_PIO_PORT ((uint16_t)0x1000)
11+
12+
static ucall_type_t ucall_type;
13+
static vm_vaddr_t *ucall_exit_mmio_addr;
14+
15+
static bool ucall_mmio_init(struct kvm_vm *vm, vm_paddr_t gpa)
16+
{
17+
if (kvm_userspace_memory_region_find(vm, gpa, gpa + 1))
18+
return false;
19+
20+
virt_pg_map(vm, gpa, gpa, 0);
21+
22+
ucall_exit_mmio_addr = (vm_vaddr_t *)gpa;
23+
sync_global_to_guest(vm, ucall_exit_mmio_addr);
24+
25+
return true;
26+
}
27+
28+
void ucall_init(struct kvm_vm *vm, ucall_type_t type, void *arg)
29+
{
30+
ucall_type = type;
31+
sync_global_to_guest(vm, ucall_type);
32+
33+
if (type == UCALL_PIO)
34+
return;
35+
36+
if (type == UCALL_MMIO) {
37+
vm_paddr_t gpa, start, end, step;
38+
bool ret;
39+
40+
if (arg) {
41+
gpa = (vm_paddr_t)arg;
42+
ret = ucall_mmio_init(vm, gpa);
43+
TEST_ASSERT(ret, "Can't set ucall mmio address to %lx", gpa);
44+
return;
45+
}
46+
47+
/*
48+
* Find an address within the allowed virtual address space,
49+
* that does _not_ have a KVM memory region associated with it.
50+
* Identity mapping an address like this allows the guest to
51+
* access it, but as KVM doesn't know what to do with it, it
52+
* will assume it's something userspace handles and exit with
53+
* KVM_EXIT_MMIO. Well, at least that's how it works for AArch64.
54+
* Here we start with a guess that the addresses around two
55+
* thirds of the VA space are unmapped and then work both down
56+
* and up from there in 1/6 VA space sized steps.
57+
*/
58+
start = 1ul << (vm->va_bits * 2 / 3);
59+
end = 1ul << vm->va_bits;
60+
step = 1ul << (vm->va_bits / 6);
61+
for (gpa = start; gpa >= 0; gpa -= step) {
62+
if (ucall_mmio_init(vm, gpa & ~(vm->page_size - 1)))
63+
return;
64+
}
65+
for (gpa = start + step; gpa < end; gpa += step) {
66+
if (ucall_mmio_init(vm, gpa & ~(vm->page_size - 1)))
67+
return;
68+
}
69+
TEST_ASSERT(false, "Can't find a ucall mmio address");
70+
}
71+
}
72+
73+
void ucall_uninit(struct kvm_vm *vm)
74+
{
75+
ucall_type = 0;
76+
sync_global_to_guest(vm, ucall_type);
77+
ucall_exit_mmio_addr = 0;
78+
sync_global_to_guest(vm, ucall_exit_mmio_addr);
79+
}
80+
81+
static void ucall_pio_exit(struct ucall *uc)
82+
{
83+
#ifdef __x86_64__
84+
asm volatile("in %[port], %%al"
85+
: : [port] "d" (UCALL_PIO_PORT), "D" (uc) : "rax");
86+
#endif
87+
}
88+
89+
static void ucall_mmio_exit(struct ucall *uc)
90+
{
91+
*ucall_exit_mmio_addr = (vm_vaddr_t)uc;
92+
}
93+
94+
void ucall(uint64_t cmd, int nargs, ...)
95+
{
96+
struct ucall uc = {
97+
.cmd = cmd,
98+
};
99+
va_list va;
100+
int i;
101+
102+
nargs = nargs <= UCALL_MAX_ARGS ? nargs : UCALL_MAX_ARGS;
103+
104+
va_start(va, nargs);
105+
for (i = 0; i < nargs; ++i)
106+
uc.args[i] = va_arg(va, uint64_t);
107+
va_end(va);
108+
109+
switch (ucall_type) {
110+
case UCALL_PIO:
111+
ucall_pio_exit(&uc);
112+
break;
113+
case UCALL_MMIO:
114+
ucall_mmio_exit(&uc);
115+
break;
116+
};
117+
}
118+
119+
uint64_t get_ucall(struct kvm_vm *vm, uint32_t vcpu_id, struct ucall *uc)
120+
{
121+
struct kvm_run *run = vcpu_state(vm, vcpu_id);
122+
123+
memset(uc, 0, sizeof(*uc));
124+
125+
#ifdef __x86_64__
126+
if (ucall_type == UCALL_PIO && run->exit_reason == KVM_EXIT_IO &&
127+
run->io.port == UCALL_PIO_PORT) {
128+
struct kvm_regs regs;
129+
vcpu_regs_get(vm, vcpu_id, &regs);
130+
memcpy(uc, addr_gva2hva(vm, (vm_vaddr_t)regs.rdi), sizeof(*uc));
131+
return uc->cmd;
132+
}
133+
#endif
134+
if (ucall_type == UCALL_MMIO && run->exit_reason == KVM_EXIT_MMIO &&
135+
run->mmio.phys_addr == (uint64_t)ucall_exit_mmio_addr) {
136+
vm_vaddr_t gva;
137+
TEST_ASSERT(run->mmio.is_write && run->mmio.len == 8,
138+
"Unexpected ucall exit mmio address access");
139+
gva = *(vm_vaddr_t *)run->mmio.data;
140+
memcpy(uc, addr_gva2hva(vm, gva), sizeof(*uc));
141+
}
142+
143+
return uc->cmd;
144+
}

tools/testing/selftests/kvm/platform_info_test.c

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,19 +48,19 @@ static void set_msr_platform_info_enabled(struct kvm_vm *vm, bool enable)
4848
static void test_msr_platform_info_enabled(struct kvm_vm *vm)
4949
{
5050
struct kvm_run *run = vcpu_state(vm, VCPU_ID);
51-
struct guest_args args;
51+
struct ucall uc;
5252

5353
set_msr_platform_info_enabled(vm, true);
5454
vcpu_run(vm, VCPU_ID);
5555
TEST_ASSERT(run->exit_reason == KVM_EXIT_IO,
5656
"Exit_reason other than KVM_EXIT_IO: %u (%s),\n",
5757
run->exit_reason,
5858
exit_reason_str(run->exit_reason));
59-
guest_args_read(vm, VCPU_ID, &args);
60-
TEST_ASSERT(args.port == GUEST_PORT_SYNC,
61-
"Received IO from port other than PORT_HOST_SYNC: %u\n",
62-
run->io.port);
63-
TEST_ASSERT((args.arg1 & MSR_PLATFORM_INFO_MAX_TURBO_RATIO) ==
59+
get_ucall(vm, VCPU_ID, &uc);
60+
TEST_ASSERT(uc.cmd == UCALL_SYNC,
61+
"Received ucall other than UCALL_SYNC: %u\n",
62+
ucall);
63+
TEST_ASSERT((uc.args[1] & MSR_PLATFORM_INFO_MAX_TURBO_RATIO) ==
6464
MSR_PLATFORM_INFO_MAX_TURBO_RATIO,
6565
"Expected MSR_PLATFORM_INFO to have max turbo ratio mask: %i.",
6666
MSR_PLATFORM_INFO_MAX_TURBO_RATIO);

tools/testing/selftests/kvm/state_test.c

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ int main(int argc, char *argv[])
127127
struct kvm_vm *vm;
128128
struct kvm_run *run;
129129
struct kvm_x86_state *state;
130+
struct ucall uc;
130131
int stage;
131132

132133
struct kvm_cpuid_entry2 *entry = kvm_get_supported_cpuid_entry(1);
@@ -155,23 +156,23 @@ int main(int argc, char *argv[])
155156

156157
memset(&regs1, 0, sizeof(regs1));
157158
vcpu_regs_get(vm, VCPU_ID, &regs1);
158-
switch (run->io.port) {
159-
case GUEST_PORT_ABORT:
160-
TEST_ASSERT(false, "%s at %s:%d", (const char *) regs1.rdi,
161-
__FILE__, regs1.rsi);
159+
switch (get_ucall(vm, VCPU_ID, &uc)) {
160+
case UCALL_ABORT:
161+
TEST_ASSERT(false, "%s at %s:%d", (const char *)uc.args[0],
162+
__FILE__, uc.args[1]);
162163
/* NOT REACHED */
163-
case GUEST_PORT_SYNC:
164+
case UCALL_SYNC:
164165
break;
165-
case GUEST_PORT_DONE:
166+
case UCALL_DONE:
166167
goto done;
167168
default:
168-
TEST_ASSERT(false, "Unknown port 0x%x.", run->io.port);
169+
TEST_ASSERT(false, "Unknown ucall 0x%x.", uc.cmd);
169170
}
170171

171-
/* PORT_SYNC is handled here. */
172-
TEST_ASSERT(!strcmp((const char *)regs1.rdi, "hello") &&
173-
regs1.rsi == stage, "Unexpected register values vmexit #%lx, got %lx",
174-
stage, (ulong) regs1.rsi);
172+
/* UCALL_SYNC is handled here. */
173+
TEST_ASSERT(!strcmp((const char *)uc.args[0], "hello") &&
174+
uc.args[1] == stage, "Unexpected register values vmexit #%lx, got %lx",
175+
stage, (ulong)uc.args[1]);
175176

176177
state = vcpu_save_state(vm, VCPU_ID);
177178
kvm_vm_release(vm);

0 commit comments

Comments
 (0)