Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * psci_test - Tests relating to KVM's PSCI implementation.
4 *
5 * Copyright (c) 2021 Google LLC.
6 *
7 * This test includes:
8 * - A regression test for a race between KVM servicing the PSCI CPU_ON call
9 * and userspace reading the targeted vCPU's registers.
10 * - A test for KVM's handling of PSCI SYSTEM_SUSPEND and the associated
11 * KVM_SYSTEM_EVENT_SUSPEND UAPI.
12 */
13
14#include <linux/kernel.h>
15#include <linux/psci.h>
16#include <asm/cputype.h>
17
18#include "kvm_util.h"
19#include "processor.h"
20#include "test_util.h"
21
22#define CPU_ON_ENTRY_ADDR 0xfeedf00dul
23#define CPU_ON_CONTEXT_ID 0xdeadc0deul
24
25static u64 psci_cpu_on(u64 target_cpu, u64 entry_addr, u64 context_id)
26{
27 struct arm_smccc_res res;
28
29 do_smccc(PSCI_0_2_FN64_CPU_ON, target_cpu, entry_addr, context_id,
30 0, 0, 0, 0, &res);
31
32 return res.a0;
33}
34
35static u64 psci_affinity_info(u64 target_affinity, u64 lowest_affinity_level)
36{
37 struct arm_smccc_res res;
38
39 do_smccc(PSCI_0_2_FN64_AFFINITY_INFO, target_affinity, lowest_affinity_level,
40 0, 0, 0, 0, 0, &res);
41
42 return res.a0;
43}
44
45static u64 psci_system_suspend(u64 entry_addr, u64 context_id)
46{
47 struct arm_smccc_res res;
48
49 do_smccc(PSCI_1_0_FN64_SYSTEM_SUSPEND, entry_addr, context_id,
50 0, 0, 0, 0, 0, &res);
51
52 return res.a0;
53}
54
55static u64 psci_system_off2(u64 type, u64 cookie)
56{
57 struct arm_smccc_res res;
58
59 do_smccc(PSCI_1_3_FN64_SYSTEM_OFF2, type, cookie, 0, 0, 0, 0, 0, &res);
60
61 return res.a0;
62}
63
64static u64 psci_features(u32 func_id)
65{
66 struct arm_smccc_res res;
67
68 do_smccc(PSCI_1_0_FN_PSCI_FEATURES, func_id, 0, 0, 0, 0, 0, 0, &res);
69
70 return res.a0;
71}
72
73static void vcpu_power_off(struct kvm_vcpu *vcpu)
74{
75 struct kvm_mp_state mp_state = {
76 .mp_state = KVM_MP_STATE_STOPPED,
77 };
78
79 vcpu_mp_state_set(vcpu, &mp_state);
80}
81
82static struct kvm_vm *setup_vm(void *guest_code, struct kvm_vcpu **source,
83 struct kvm_vcpu **target)
84{
85 struct kvm_vcpu_init init;
86 struct kvm_vm *vm;
87
88 vm = vm_create(2);
89
90 kvm_get_default_vcpu_target(vm, &init);
91 init.features[0] |= (1 << KVM_ARM_VCPU_PSCI_0_2);
92
93 *source = aarch64_vcpu_add(vm, 0, &init, guest_code);
94 *target = aarch64_vcpu_add(vm, 1, &init, guest_code);
95
96 kvm_arch_vm_finalize_vcpus(vm);
97 return vm;
98}
99
100static void enter_guest(struct kvm_vcpu *vcpu)
101{
102 struct ucall uc;
103
104 vcpu_run(vcpu);
105 if (get_ucall(vcpu, &uc) == UCALL_ABORT)
106 REPORT_GUEST_ASSERT(uc);
107}
108
109static void assert_vcpu_reset(struct kvm_vcpu *vcpu)
110{
111 u64 obs_pc, obs_x0;
112
113 obs_pc = vcpu_get_reg(vcpu, ARM64_CORE_REG(regs.pc));
114 obs_x0 = vcpu_get_reg(vcpu, ARM64_CORE_REG(regs.regs[0]));
115
116 TEST_ASSERT(obs_pc == CPU_ON_ENTRY_ADDR,
117 "unexpected target cpu pc: %lx (expected: %lx)",
118 obs_pc, CPU_ON_ENTRY_ADDR);
119 TEST_ASSERT(obs_x0 == CPU_ON_CONTEXT_ID,
120 "unexpected target context id: %lx (expected: %lx)",
121 obs_x0, CPU_ON_CONTEXT_ID);
122}
123
124static void guest_test_cpu_on(u64 target_cpu)
125{
126 u64 target_state;
127
128 GUEST_ASSERT(!psci_cpu_on(target_cpu, CPU_ON_ENTRY_ADDR, CPU_ON_CONTEXT_ID));
129
130 do {
131 target_state = psci_affinity_info(target_cpu, 0);
132
133 GUEST_ASSERT((target_state == PSCI_0_2_AFFINITY_LEVEL_ON) ||
134 (target_state == PSCI_0_2_AFFINITY_LEVEL_OFF));
135 } while (target_state != PSCI_0_2_AFFINITY_LEVEL_ON);
136
137 GUEST_DONE();
138}
139
140static void host_test_cpu_on(void)
141{
142 struct kvm_vcpu *source, *target;
143 u64 target_mpidr;
144 struct kvm_vm *vm;
145 struct ucall uc;
146
147 vm = setup_vm(guest_test_cpu_on, &source, &target);
148
149 /*
150 * make sure the target is already off when executing the test.
151 */
152 vcpu_power_off(target);
153
154 target_mpidr = vcpu_get_reg(target, KVM_ARM64_SYS_REG(SYS_MPIDR_EL1));
155 vcpu_args_set(source, 1, target_mpidr & MPIDR_HWID_BITMASK);
156 enter_guest(source);
157
158 if (get_ucall(source, &uc) != UCALL_DONE)
159 TEST_FAIL("Unhandled ucall: %lu", uc.cmd);
160
161 assert_vcpu_reset(target);
162 kvm_vm_free(vm);
163}
164
165static void guest_test_system_suspend(void)
166{
167 u64 ret;
168
169 /* assert that SYSTEM_SUSPEND is discoverable */
170 GUEST_ASSERT(!psci_features(PSCI_1_0_FN_SYSTEM_SUSPEND));
171 GUEST_ASSERT(!psci_features(PSCI_1_0_FN64_SYSTEM_SUSPEND));
172
173 ret = psci_system_suspend(CPU_ON_ENTRY_ADDR, CPU_ON_CONTEXT_ID);
174 GUEST_SYNC(ret);
175}
176
177static void host_test_system_suspend(void)
178{
179 struct kvm_vcpu *source, *target;
180 struct kvm_run *run;
181 struct kvm_vm *vm;
182
183 vm = setup_vm(guest_test_system_suspend, &source, &target);
184 vm_enable_cap(vm, KVM_CAP_ARM_SYSTEM_SUSPEND, 0);
185
186 vcpu_power_off(target);
187 run = source->run;
188
189 enter_guest(source);
190
191 TEST_ASSERT_KVM_EXIT_REASON(source, KVM_EXIT_SYSTEM_EVENT);
192 TEST_ASSERT(run->system_event.type == KVM_SYSTEM_EVENT_SUSPEND,
193 "Unhandled system event: %u (expected: %u)",
194 run->system_event.type, KVM_SYSTEM_EVENT_SUSPEND);
195
196 kvm_vm_free(vm);
197}
198
199static void guest_test_system_off2(void)
200{
201 u64 ret;
202
203 /* assert that SYSTEM_OFF2 is discoverable */
204 GUEST_ASSERT(psci_features(PSCI_1_3_FN_SYSTEM_OFF2) &
205 PSCI_1_3_OFF_TYPE_HIBERNATE_OFF);
206 GUEST_ASSERT(psci_features(PSCI_1_3_FN64_SYSTEM_OFF2) &
207 PSCI_1_3_OFF_TYPE_HIBERNATE_OFF);
208
209 /* With non-zero 'cookie' field, it should fail */
210 ret = psci_system_off2(PSCI_1_3_OFF_TYPE_HIBERNATE_OFF, 1);
211 GUEST_ASSERT(ret == PSCI_RET_INVALID_PARAMS);
212
213 /*
214 * This would normally never return, so KVM sets the return value
215 * to PSCI_RET_INTERNAL_FAILURE. The test case *does* return, so
216 * that it can test both values for HIBERNATE_OFF.
217 */
218 ret = psci_system_off2(PSCI_1_3_OFF_TYPE_HIBERNATE_OFF, 0);
219 GUEST_ASSERT(ret == PSCI_RET_INTERNAL_FAILURE);
220
221 /*
222 * Revision F.b of the PSCI v1.3 specification documents zero as an
223 * alias for HIBERNATE_OFF, since that's the value used in earlier
224 * revisions of the spec and some implementations in the field.
225 */
226 ret = psci_system_off2(0, 1);
227 GUEST_ASSERT(ret == PSCI_RET_INVALID_PARAMS);
228
229 ret = psci_system_off2(0, 0);
230 GUEST_ASSERT(ret == PSCI_RET_INTERNAL_FAILURE);
231
232 GUEST_DONE();
233}
234
235static void host_test_system_off2(void)
236{
237 struct kvm_vcpu *source, *target;
238 struct kvm_mp_state mps;
239 u64 psci_version = 0;
240 int nr_shutdowns = 0;
241 struct kvm_run *run;
242 struct ucall uc;
243
244 setup_vm(guest_test_system_off2, &source, &target);
245
246 psci_version = vcpu_get_reg(target, KVM_REG_ARM_PSCI_VERSION);
247
248 TEST_ASSERT(psci_version >= PSCI_VERSION(1, 3),
249 "Unexpected PSCI version %lu.%lu",
250 PSCI_VERSION_MAJOR(psci_version),
251 PSCI_VERSION_MINOR(psci_version));
252
253 vcpu_power_off(target);
254 run = source->run;
255
256 enter_guest(source);
257 while (run->exit_reason == KVM_EXIT_SYSTEM_EVENT) {
258 TEST_ASSERT(run->system_event.type == KVM_SYSTEM_EVENT_SHUTDOWN,
259 "Unhandled system event: %u (expected: %u)",
260 run->system_event.type, KVM_SYSTEM_EVENT_SHUTDOWN);
261 TEST_ASSERT(run->system_event.ndata >= 1,
262 "Unexpected amount of system event data: %u (expected, >= 1)",
263 run->system_event.ndata);
264 TEST_ASSERT(run->system_event.data[0] & KVM_SYSTEM_EVENT_SHUTDOWN_FLAG_PSCI_OFF2,
265 "PSCI_OFF2 flag not set. Flags %llu (expected %llu)",
266 run->system_event.data[0], KVM_SYSTEM_EVENT_SHUTDOWN_FLAG_PSCI_OFF2);
267
268 nr_shutdowns++;
269
270 /* Restart the vCPU */
271 mps.mp_state = KVM_MP_STATE_RUNNABLE;
272 vcpu_mp_state_set(source, &mps);
273
274 enter_guest(source);
275 }
276
277 TEST_ASSERT(get_ucall(source, &uc) == UCALL_DONE, "Guest did not exit cleanly");
278 TEST_ASSERT(nr_shutdowns == 2, "Two shutdown events were expected, but saw %d", nr_shutdowns);
279}
280
281int main(void)
282{
283 TEST_REQUIRE(kvm_has_cap(KVM_CAP_ARM_SYSTEM_SUSPEND));
284
285 host_test_cpu_on();
286 host_test_system_suspend();
287 host_test_system_off2();
288 return 0;
289}