1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * steal/stolen time test |
4 | * |
5 | * Copyright (C) 2020, Red Hat, Inc. |
6 | */ |
7 | #define _GNU_SOURCE |
8 | #include <stdio.h> |
9 | #include <time.h> |
10 | #include <sched.h> |
11 | #include <pthread.h> |
12 | #include <linux/kernel.h> |
13 | #include <asm/kvm.h> |
14 | #ifndef __riscv |
15 | #include <asm/kvm_para.h> |
16 | #endif |
17 | |
18 | #include "test_util.h" |
19 | #include "kvm_util.h" |
20 | #include "processor.h" |
21 | |
22 | #define NR_VCPUS 4 |
23 | #define ST_GPA_BASE (1 << 30) |
24 | |
25 | static void *st_gva[NR_VCPUS]; |
26 | static uint64_t guest_stolen_time[NR_VCPUS]; |
27 | |
28 | #if defined(__x86_64__) |
29 | |
30 | /* steal_time must have 64-byte alignment */ |
31 | #define STEAL_TIME_SIZE ((sizeof(struct kvm_steal_time) + 63) & ~63) |
32 | |
33 | static void check_status(struct kvm_steal_time *st) |
34 | { |
35 | GUEST_ASSERT(!(READ_ONCE(st->version) & 1)); |
36 | GUEST_ASSERT_EQ(READ_ONCE(st->flags), 0); |
37 | GUEST_ASSERT_EQ(READ_ONCE(st->preempted), 0); |
38 | } |
39 | |
40 | static void guest_code(int cpu) |
41 | { |
42 | struct kvm_steal_time *st = st_gva[cpu]; |
43 | uint32_t version; |
44 | |
45 | GUEST_ASSERT_EQ(rdmsr(MSR_KVM_STEAL_TIME), ((uint64_t)st_gva[cpu] | KVM_MSR_ENABLED)); |
46 | |
47 | memset(st, 0, sizeof(*st)); |
48 | GUEST_SYNC(0); |
49 | |
50 | check_status(st); |
51 | WRITE_ONCE(guest_stolen_time[cpu], st->steal); |
52 | version = READ_ONCE(st->version); |
53 | check_status(st); |
54 | GUEST_SYNC(1); |
55 | |
56 | check_status(st); |
57 | GUEST_ASSERT(version < READ_ONCE(st->version)); |
58 | WRITE_ONCE(guest_stolen_time[cpu], st->steal); |
59 | check_status(st); |
60 | GUEST_DONE(); |
61 | } |
62 | |
63 | static bool is_steal_time_supported(struct kvm_vcpu *vcpu) |
64 | { |
65 | return kvm_cpu_has(X86_FEATURE_KVM_STEAL_TIME); |
66 | } |
67 | |
68 | static void steal_time_init(struct kvm_vcpu *vcpu, uint32_t i) |
69 | { |
70 | int ret; |
71 | |
72 | /* ST_GPA_BASE is identity mapped */ |
73 | st_gva[i] = (void *)(ST_GPA_BASE + i * STEAL_TIME_SIZE); |
74 | sync_global_to_guest(vcpu->vm, st_gva[i]); |
75 | |
76 | ret = _vcpu_set_msr(vcpu, MSR_KVM_STEAL_TIME, |
77 | (ulong)st_gva[i] | KVM_STEAL_RESERVED_MASK); |
78 | TEST_ASSERT(ret == 0, "Bad GPA didn't fail" ); |
79 | |
80 | vcpu_set_msr(vcpu, MSR_KVM_STEAL_TIME, (ulong)st_gva[i] | KVM_MSR_ENABLED); |
81 | } |
82 | |
83 | static void steal_time_dump(struct kvm_vm *vm, uint32_t vcpu_idx) |
84 | { |
85 | struct kvm_steal_time *st = addr_gva2hva(vm, (ulong)st_gva[vcpu_idx]); |
86 | int i; |
87 | |
88 | pr_info("VCPU%d:\n" , vcpu_idx); |
89 | pr_info(" steal: %lld\n" , st->steal); |
90 | pr_info(" version: %d\n" , st->version); |
91 | pr_info(" flags: %d\n" , st->flags); |
92 | pr_info(" preempted: %d\n" , st->preempted); |
93 | pr_info(" u8_pad: " ); |
94 | for (i = 0; i < 3; ++i) |
95 | pr_info("%d" , st->u8_pad[i]); |
96 | pr_info("\n pad: " ); |
97 | for (i = 0; i < 11; ++i) |
98 | pr_info("%d" , st->pad[i]); |
99 | pr_info("\n" ); |
100 | } |
101 | |
102 | #elif defined(__aarch64__) |
103 | |
104 | /* PV_TIME_ST must have 64-byte alignment */ |
105 | #define STEAL_TIME_SIZE ((sizeof(struct st_time) + 63) & ~63) |
106 | |
107 | #define SMCCC_ARCH_FEATURES 0x80000001 |
108 | #define PV_TIME_FEATURES 0xc5000020 |
109 | #define PV_TIME_ST 0xc5000021 |
110 | |
111 | struct st_time { |
112 | uint32_t rev; |
113 | uint32_t attr; |
114 | uint64_t st_time; |
115 | }; |
116 | |
117 | static int64_t smccc(uint32_t func, uint64_t arg) |
118 | { |
119 | struct arm_smccc_res res; |
120 | |
121 | smccc_hvc(func, arg, 0, 0, 0, 0, 0, 0, &res); |
122 | return res.a0; |
123 | } |
124 | |
125 | static void check_status(struct st_time *st) |
126 | { |
127 | GUEST_ASSERT_EQ(READ_ONCE(st->rev), 0); |
128 | GUEST_ASSERT_EQ(READ_ONCE(st->attr), 0); |
129 | } |
130 | |
131 | static void guest_code(int cpu) |
132 | { |
133 | struct st_time *st; |
134 | int64_t status; |
135 | |
136 | status = smccc(SMCCC_ARCH_FEATURES, PV_TIME_FEATURES); |
137 | GUEST_ASSERT_EQ(status, 0); |
138 | status = smccc(PV_TIME_FEATURES, PV_TIME_FEATURES); |
139 | GUEST_ASSERT_EQ(status, 0); |
140 | status = smccc(PV_TIME_FEATURES, PV_TIME_ST); |
141 | GUEST_ASSERT_EQ(status, 0); |
142 | |
143 | status = smccc(PV_TIME_ST, 0); |
144 | GUEST_ASSERT_NE(status, -1); |
145 | GUEST_ASSERT_EQ(status, (ulong)st_gva[cpu]); |
146 | |
147 | st = (struct st_time *)status; |
148 | GUEST_SYNC(0); |
149 | |
150 | check_status(st); |
151 | WRITE_ONCE(guest_stolen_time[cpu], st->st_time); |
152 | GUEST_SYNC(1); |
153 | |
154 | check_status(st); |
155 | WRITE_ONCE(guest_stolen_time[cpu], st->st_time); |
156 | GUEST_DONE(); |
157 | } |
158 | |
159 | static bool is_steal_time_supported(struct kvm_vcpu *vcpu) |
160 | { |
161 | struct kvm_device_attr dev = { |
162 | .group = KVM_ARM_VCPU_PVTIME_CTRL, |
163 | .attr = KVM_ARM_VCPU_PVTIME_IPA, |
164 | }; |
165 | |
166 | return !__vcpu_ioctl(vcpu, KVM_HAS_DEVICE_ATTR, &dev); |
167 | } |
168 | |
169 | static void steal_time_init(struct kvm_vcpu *vcpu, uint32_t i) |
170 | { |
171 | struct kvm_vm *vm = vcpu->vm; |
172 | uint64_t st_ipa; |
173 | int ret; |
174 | |
175 | struct kvm_device_attr dev = { |
176 | .group = KVM_ARM_VCPU_PVTIME_CTRL, |
177 | .attr = KVM_ARM_VCPU_PVTIME_IPA, |
178 | .addr = (uint64_t)&st_ipa, |
179 | }; |
180 | |
181 | vcpu_ioctl(vcpu, KVM_HAS_DEVICE_ATTR, &dev); |
182 | |
183 | /* ST_GPA_BASE is identity mapped */ |
184 | st_gva[i] = (void *)(ST_GPA_BASE + i * STEAL_TIME_SIZE); |
185 | sync_global_to_guest(vm, st_gva[i]); |
186 | |
187 | st_ipa = (ulong)st_gva[i] | 1; |
188 | ret = __vcpu_ioctl(vcpu, KVM_SET_DEVICE_ATTR, &dev); |
189 | TEST_ASSERT(ret == -1 && errno == EINVAL, "Bad IPA didn't report EINVAL" ); |
190 | |
191 | st_ipa = (ulong)st_gva[i]; |
192 | vcpu_ioctl(vcpu, KVM_SET_DEVICE_ATTR, &dev); |
193 | |
194 | ret = __vcpu_ioctl(vcpu, KVM_SET_DEVICE_ATTR, &dev); |
195 | TEST_ASSERT(ret == -1 && errno == EEXIST, "Set IPA twice without EEXIST" ); |
196 | } |
197 | |
198 | static void steal_time_dump(struct kvm_vm *vm, uint32_t vcpu_idx) |
199 | { |
200 | struct st_time *st = addr_gva2hva(vm, (ulong)st_gva[vcpu_idx]); |
201 | |
202 | pr_info("VCPU%d:\n" , vcpu_idx); |
203 | pr_info(" rev: %d\n" , st->rev); |
204 | pr_info(" attr: %d\n" , st->attr); |
205 | pr_info(" st_time: %ld\n" , st->st_time); |
206 | } |
207 | |
208 | #elif defined(__riscv) |
209 | |
210 | /* SBI STA shmem must have 64-byte alignment */ |
211 | #define STEAL_TIME_SIZE ((sizeof(struct sta_struct) + 63) & ~63) |
212 | |
213 | static vm_paddr_t st_gpa[NR_VCPUS]; |
214 | |
215 | struct sta_struct { |
216 | uint32_t sequence; |
217 | uint32_t flags; |
218 | uint64_t steal; |
219 | uint8_t preempted; |
220 | uint8_t pad[47]; |
221 | } __packed; |
222 | |
223 | static void sta_set_shmem(vm_paddr_t gpa, unsigned long flags) |
224 | { |
225 | unsigned long lo = (unsigned long)gpa; |
226 | #if __riscv_xlen == 32 |
227 | unsigned long hi = (unsigned long)(gpa >> 32); |
228 | #else |
229 | unsigned long hi = gpa == -1 ? -1 : 0; |
230 | #endif |
231 | struct sbiret ret = sbi_ecall(SBI_EXT_STA, 0, lo, hi, flags, 0, 0, 0); |
232 | |
233 | GUEST_ASSERT(ret.value == 0 && ret.error == 0); |
234 | } |
235 | |
236 | static void check_status(struct sta_struct *st) |
237 | { |
238 | GUEST_ASSERT(!(READ_ONCE(st->sequence) & 1)); |
239 | GUEST_ASSERT(READ_ONCE(st->flags) == 0); |
240 | GUEST_ASSERT(READ_ONCE(st->preempted) == 0); |
241 | } |
242 | |
243 | static void guest_code(int cpu) |
244 | { |
245 | struct sta_struct *st = st_gva[cpu]; |
246 | uint32_t sequence; |
247 | long out_val = 0; |
248 | bool probe; |
249 | |
250 | probe = guest_sbi_probe_extension(SBI_EXT_STA, &out_val); |
251 | GUEST_ASSERT(probe && out_val == 1); |
252 | |
253 | sta_set_shmem(st_gpa[cpu], 0); |
254 | GUEST_SYNC(0); |
255 | |
256 | check_status(st); |
257 | WRITE_ONCE(guest_stolen_time[cpu], st->steal); |
258 | sequence = READ_ONCE(st->sequence); |
259 | check_status(st); |
260 | GUEST_SYNC(1); |
261 | |
262 | check_status(st); |
263 | GUEST_ASSERT(sequence < READ_ONCE(st->sequence)); |
264 | WRITE_ONCE(guest_stolen_time[cpu], st->steal); |
265 | check_status(st); |
266 | GUEST_DONE(); |
267 | } |
268 | |
269 | static bool is_steal_time_supported(struct kvm_vcpu *vcpu) |
270 | { |
271 | uint64_t id = RISCV_SBI_EXT_REG(KVM_RISCV_SBI_EXT_STA); |
272 | unsigned long enabled; |
273 | |
274 | vcpu_get_reg(vcpu, id, &enabled); |
275 | TEST_ASSERT(enabled == 0 || enabled == 1, "Expected boolean result" ); |
276 | |
277 | return enabled; |
278 | } |
279 | |
280 | static void steal_time_init(struct kvm_vcpu *vcpu, uint32_t i) |
281 | { |
282 | /* ST_GPA_BASE is identity mapped */ |
283 | st_gva[i] = (void *)(ST_GPA_BASE + i * STEAL_TIME_SIZE); |
284 | st_gpa[i] = addr_gva2gpa(vcpu->vm, (vm_vaddr_t)st_gva[i]); |
285 | sync_global_to_guest(vcpu->vm, st_gva[i]); |
286 | sync_global_to_guest(vcpu->vm, st_gpa[i]); |
287 | } |
288 | |
289 | static void steal_time_dump(struct kvm_vm *vm, uint32_t vcpu_idx) |
290 | { |
291 | struct sta_struct *st = addr_gva2hva(vm, (ulong)st_gva[vcpu_idx]); |
292 | int i; |
293 | |
294 | pr_info("VCPU%d:\n" , vcpu_idx); |
295 | pr_info(" sequence: %d\n" , st->sequence); |
296 | pr_info(" flags: %d\n" , st->flags); |
297 | pr_info(" steal: %" PRIu64"\n" , st->steal); |
298 | pr_info(" preempted: %d\n" , st->preempted); |
299 | pr_info(" pad: " ); |
300 | for (i = 0; i < 47; ++i) |
301 | pr_info("%d" , st->pad[i]); |
302 | pr_info("\n" ); |
303 | } |
304 | |
305 | #endif |
306 | |
307 | static void *do_steal_time(void *arg) |
308 | { |
309 | struct timespec ts, stop; |
310 | |
311 | clock_gettime(CLOCK_MONOTONIC, &ts); |
312 | stop = timespec_add_ns(ts, MIN_RUN_DELAY_NS); |
313 | |
314 | while (1) { |
315 | clock_gettime(CLOCK_MONOTONIC, &ts); |
316 | if (timespec_to_ns(timespec_sub(ts, stop)) >= 0) |
317 | break; |
318 | } |
319 | |
320 | return NULL; |
321 | } |
322 | |
323 | static void run_vcpu(struct kvm_vcpu *vcpu) |
324 | { |
325 | struct ucall uc; |
326 | |
327 | vcpu_run(vcpu); |
328 | |
329 | switch (get_ucall(vcpu, &uc)) { |
330 | case UCALL_SYNC: |
331 | case UCALL_DONE: |
332 | break; |
333 | case UCALL_ABORT: |
334 | REPORT_GUEST_ASSERT(uc); |
335 | default: |
336 | TEST_ASSERT(false, "Unexpected exit: %s" , |
337 | exit_reason_str(vcpu->run->exit_reason)); |
338 | } |
339 | } |
340 | |
341 | int main(int ac, char **av) |
342 | { |
343 | struct kvm_vcpu *vcpus[NR_VCPUS]; |
344 | struct kvm_vm *vm; |
345 | pthread_attr_t attr; |
346 | pthread_t thread; |
347 | cpu_set_t cpuset; |
348 | unsigned int gpages; |
349 | long stolen_time; |
350 | long run_delay; |
351 | bool verbose; |
352 | int i; |
353 | |
354 | verbose = ac > 1 && (!strncmp(av[1], "-v" , 3) || !strncmp(av[1], "--verbose" , 10)); |
355 | |
356 | /* Set CPU affinity so we can force preemption of the VCPU */ |
357 | CPU_ZERO(&cpuset); |
358 | CPU_SET(0, &cpuset); |
359 | pthread_attr_init(&attr); |
360 | pthread_attr_setaffinity_np(&attr, sizeof(cpu_set_t), &cpuset); |
361 | pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset); |
362 | |
363 | /* Create a VM and an identity mapped memslot for the steal time structure */ |
364 | vm = vm_create_with_vcpus(NR_VCPUS, guest_code, vcpus); |
365 | gpages = vm_calc_num_guest_pages(VM_MODE_DEFAULT, STEAL_TIME_SIZE * NR_VCPUS); |
366 | vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, ST_GPA_BASE, 1, gpages, 0); |
367 | virt_map(vm, ST_GPA_BASE, ST_GPA_BASE, gpages); |
368 | |
369 | TEST_REQUIRE(is_steal_time_supported(vcpu: vcpus[0])); |
370 | |
371 | /* Run test on each VCPU */ |
372 | for (i = 0; i < NR_VCPUS; ++i) { |
373 | steal_time_init(vcpu: vcpus[i], i); |
374 | |
375 | vcpu_args_set(vcpus[i], 1, i); |
376 | |
377 | /* First VCPU run initializes steal-time */ |
378 | run_vcpu(vcpu: vcpus[i]); |
379 | |
380 | /* Second VCPU run, expect guest stolen time to be <= run_delay */ |
381 | run_vcpu(vcpu: vcpus[i]); |
382 | sync_global_from_guest(vm, guest_stolen_time[i]); |
383 | stolen_time = guest_stolen_time[i]; |
384 | run_delay = get_run_delay(); |
385 | TEST_ASSERT(stolen_time <= run_delay, |
386 | "Expected stolen time <= %ld, got %ld" , |
387 | run_delay, stolen_time); |
388 | |
389 | /* Steal time from the VCPU. The steal time thread has the same CPU affinity as the VCPUs. */ |
390 | run_delay = get_run_delay(); |
391 | pthread_create(&thread, &attr, do_steal_time, NULL); |
392 | do |
393 | sched_yield(); |
394 | while (get_run_delay() - run_delay < MIN_RUN_DELAY_NS); |
395 | pthread_join(thread, NULL); |
396 | run_delay = get_run_delay() - run_delay; |
397 | TEST_ASSERT(run_delay >= MIN_RUN_DELAY_NS, |
398 | "Expected run_delay >= %ld, got %ld" , |
399 | MIN_RUN_DELAY_NS, run_delay); |
400 | |
401 | /* Run VCPU again to confirm stolen time is consistent with run_delay */ |
402 | run_vcpu(vcpu: vcpus[i]); |
403 | sync_global_from_guest(vm, guest_stolen_time[i]); |
404 | stolen_time = guest_stolen_time[i] - stolen_time; |
405 | TEST_ASSERT(stolen_time >= run_delay, |
406 | "Expected stolen time >= %ld, got %ld" , |
407 | run_delay, stolen_time); |
408 | |
409 | if (verbose) { |
410 | pr_info("VCPU%d: total-stolen-time=%ld test-stolen-time=%ld" , i, |
411 | guest_stolen_time[i], stolen_time); |
412 | if (stolen_time == run_delay) |
413 | pr_info(" (BONUS: guest test-stolen-time even exactly matches test-run_delay)" ); |
414 | pr_info("\n" ); |
415 | steal_time_dump(vm, vcpu_idx: i); |
416 | } |
417 | } |
418 | |
419 | return 0; |
420 | } |
421 | |