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#define _GNU_SOURCE
15
16#include <linux/psci.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 uint64_t psci_cpu_on(uint64_t target_cpu, uint64_t entry_addr,
26			    uint64_t context_id)
27{
28	struct arm_smccc_res res;
29
30	smccc_hvc(PSCI_0_2_FN64_CPU_ON, target_cpu, entry_addr, context_id,
31		  0, 0, 0, 0, &res);
32
33	return res.a0;
34}
35
36static uint64_t psci_affinity_info(uint64_t target_affinity,
37				   uint64_t lowest_affinity_level)
38{
39	struct arm_smccc_res res;
40
41	smccc_hvc(PSCI_0_2_FN64_AFFINITY_INFO, target_affinity, lowest_affinity_level,
42		  0, 0, 0, 0, 0, &res);
43
44	return res.a0;
45}
46
47static uint64_t psci_system_suspend(uint64_t entry_addr, uint64_t context_id)
48{
49	struct arm_smccc_res res;
50
51	smccc_hvc(PSCI_1_0_FN64_SYSTEM_SUSPEND, entry_addr, context_id,
52		  0, 0, 0, 0, 0, &res);
53
54	return res.a0;
55}
56
57static uint64_t psci_features(uint32_t func_id)
58{
59	struct arm_smccc_res res;
60
61	smccc_hvc(PSCI_1_0_FN_PSCI_FEATURES, func_id, 0, 0, 0, 0, 0, 0, &res);
62
63	return res.a0;
64}
65
66static void vcpu_power_off(struct kvm_vcpu *vcpu)
67{
68	struct kvm_mp_state mp_state = {
69		.mp_state = KVM_MP_STATE_STOPPED,
70	};
71
72	vcpu_mp_state_set(vcpu, &mp_state);
73}
74
75static struct kvm_vm *setup_vm(void *guest_code, struct kvm_vcpu **source,
76			       struct kvm_vcpu **target)
77{
78	struct kvm_vcpu_init init;
79	struct kvm_vm *vm;
80
81	vm = vm_create(2);
82
83	vm_ioctl(vm, KVM_ARM_PREFERRED_TARGET, &init);
84	init.features[0] |= (1 << KVM_ARM_VCPU_PSCI_0_2);
85
86	*source = aarch64_vcpu_add(vm, 0, &init, guest_code);
87	*target = aarch64_vcpu_add(vm, 1, &init, guest_code);
88
89	return vm;
90}
91
92static void enter_guest(struct kvm_vcpu *vcpu)
93{
94	struct ucall uc;
95
96	vcpu_run(vcpu);
97	if (get_ucall(vcpu, &uc) == UCALL_ABORT)
98		REPORT_GUEST_ASSERT(uc);
99}
100
101static void assert_vcpu_reset(struct kvm_vcpu *vcpu)
102{
103	uint64_t obs_pc, obs_x0;
104
105	vcpu_get_reg(vcpu, ARM64_CORE_REG(regs.pc), &obs_pc);
106	vcpu_get_reg(vcpu, ARM64_CORE_REG(regs.regs[0]), &obs_x0);
107
108	TEST_ASSERT(obs_pc == CPU_ON_ENTRY_ADDR,
109		    "unexpected target cpu pc: %lx (expected: %lx)",
110		    obs_pc, CPU_ON_ENTRY_ADDR);
111	TEST_ASSERT(obs_x0 == CPU_ON_CONTEXT_ID,
112		    "unexpected target context id: %lx (expected: %lx)",
113		    obs_x0, CPU_ON_CONTEXT_ID);
114}
115
116static void guest_test_cpu_on(uint64_t target_cpu)
117{
118	uint64_t target_state;
119
120	GUEST_ASSERT(!psci_cpu_on(target_cpu, CPU_ON_ENTRY_ADDR, CPU_ON_CONTEXT_ID));
121
122	do {
123		target_state = psci_affinity_info(target_cpu, 0);
124
125		GUEST_ASSERT((target_state == PSCI_0_2_AFFINITY_LEVEL_ON) ||
126			     (target_state == PSCI_0_2_AFFINITY_LEVEL_OFF));
127	} while (target_state != PSCI_0_2_AFFINITY_LEVEL_ON);
128
129	GUEST_DONE();
130}
131
132static void host_test_cpu_on(void)
133{
134	struct kvm_vcpu *source, *target;
135	uint64_t target_mpidr;
136	struct kvm_vm *vm;
137	struct ucall uc;
138
139	vm = setup_vm(guest_test_cpu_on, &source, &target);
140
141	/*
142	 * make sure the target is already off when executing the test.
143	 */
144	vcpu_power_off(target);
145
146	vcpu_get_reg(target, KVM_ARM64_SYS_REG(SYS_MPIDR_EL1), &target_mpidr);
147	vcpu_args_set(source, 1, target_mpidr & MPIDR_HWID_BITMASK);
148	enter_guest(source);
149
150	if (get_ucall(source, &uc) != UCALL_DONE)
151		TEST_FAIL("Unhandled ucall: %lu", uc.cmd);
152
153	assert_vcpu_reset(target);
154	kvm_vm_free(vm);
155}
156
157static void guest_test_system_suspend(void)
158{
159	uint64_t ret;
160
161	/* assert that SYSTEM_SUSPEND is discoverable */
162	GUEST_ASSERT(!psci_features(PSCI_1_0_FN_SYSTEM_SUSPEND));
163	GUEST_ASSERT(!psci_features(PSCI_1_0_FN64_SYSTEM_SUSPEND));
164
165	ret = psci_system_suspend(CPU_ON_ENTRY_ADDR, CPU_ON_CONTEXT_ID);
166	GUEST_SYNC(ret);
167}
168
169static void host_test_system_suspend(void)
170{
171	struct kvm_vcpu *source, *target;
172	struct kvm_run *run;
173	struct kvm_vm *vm;
174
175	vm = setup_vm(guest_test_system_suspend, &source, &target);
176	vm_enable_cap(vm, KVM_CAP_ARM_SYSTEM_SUSPEND, 0);
177
178	vcpu_power_off(target);
179	run = source->run;
180
181	enter_guest(source);
182
183	TEST_ASSERT_KVM_EXIT_REASON(source, KVM_EXIT_SYSTEM_EVENT);
184	TEST_ASSERT(run->system_event.type == KVM_SYSTEM_EVENT_SUSPEND,
185		    "Unhandled system event: %u (expected: %u)",
186		    run->system_event.type, KVM_SYSTEM_EVENT_SUSPEND);
187
188	kvm_vm_free(vm);
189}
190
191int main(void)
192{
193	TEST_REQUIRE(kvm_has_cap(KVM_CAP_ARM_SYSTEM_SUSPEND));
194
195	host_test_cpu_on();
196	host_test_system_suspend();
197	return 0;
198}
199