162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * psci_test - Tests relating to KVM's PSCI implementation.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (c) 2021 Google LLC.
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * This test includes:
862306a36Sopenharmony_ci *  - A regression test for a race between KVM servicing the PSCI CPU_ON call
962306a36Sopenharmony_ci *    and userspace reading the targeted vCPU's registers.
1062306a36Sopenharmony_ci *  - A test for KVM's handling of PSCI SYSTEM_SUSPEND and the associated
1162306a36Sopenharmony_ci *    KVM_SYSTEM_EVENT_SUSPEND UAPI.
1262306a36Sopenharmony_ci */
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#define _GNU_SOURCE
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include <linux/psci.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#include "kvm_util.h"
1962306a36Sopenharmony_ci#include "processor.h"
2062306a36Sopenharmony_ci#include "test_util.h"
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#define CPU_ON_ENTRY_ADDR 0xfeedf00dul
2362306a36Sopenharmony_ci#define CPU_ON_CONTEXT_ID 0xdeadc0deul
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_cistatic uint64_t psci_cpu_on(uint64_t target_cpu, uint64_t entry_addr,
2662306a36Sopenharmony_ci			    uint64_t context_id)
2762306a36Sopenharmony_ci{
2862306a36Sopenharmony_ci	struct arm_smccc_res res;
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci	smccc_hvc(PSCI_0_2_FN64_CPU_ON, target_cpu, entry_addr, context_id,
3162306a36Sopenharmony_ci		  0, 0, 0, 0, &res);
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci	return res.a0;
3462306a36Sopenharmony_ci}
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_cistatic uint64_t psci_affinity_info(uint64_t target_affinity,
3762306a36Sopenharmony_ci				   uint64_t lowest_affinity_level)
3862306a36Sopenharmony_ci{
3962306a36Sopenharmony_ci	struct arm_smccc_res res;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	smccc_hvc(PSCI_0_2_FN64_AFFINITY_INFO, target_affinity, lowest_affinity_level,
4262306a36Sopenharmony_ci		  0, 0, 0, 0, 0, &res);
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	return res.a0;
4562306a36Sopenharmony_ci}
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_cistatic uint64_t psci_system_suspend(uint64_t entry_addr, uint64_t context_id)
4862306a36Sopenharmony_ci{
4962306a36Sopenharmony_ci	struct arm_smccc_res res;
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci	smccc_hvc(PSCI_1_0_FN64_SYSTEM_SUSPEND, entry_addr, context_id,
5262306a36Sopenharmony_ci		  0, 0, 0, 0, 0, &res);
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	return res.a0;
5562306a36Sopenharmony_ci}
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_cistatic uint64_t psci_features(uint32_t func_id)
5862306a36Sopenharmony_ci{
5962306a36Sopenharmony_ci	struct arm_smccc_res res;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	smccc_hvc(PSCI_1_0_FN_PSCI_FEATURES, func_id, 0, 0, 0, 0, 0, 0, &res);
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	return res.a0;
6462306a36Sopenharmony_ci}
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_cistatic void vcpu_power_off(struct kvm_vcpu *vcpu)
6762306a36Sopenharmony_ci{
6862306a36Sopenharmony_ci	struct kvm_mp_state mp_state = {
6962306a36Sopenharmony_ci		.mp_state = KVM_MP_STATE_STOPPED,
7062306a36Sopenharmony_ci	};
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	vcpu_mp_state_set(vcpu, &mp_state);
7362306a36Sopenharmony_ci}
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_cistatic struct kvm_vm *setup_vm(void *guest_code, struct kvm_vcpu **source,
7662306a36Sopenharmony_ci			       struct kvm_vcpu **target)
7762306a36Sopenharmony_ci{
7862306a36Sopenharmony_ci	struct kvm_vcpu_init init;
7962306a36Sopenharmony_ci	struct kvm_vm *vm;
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	vm = vm_create(2);
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	vm_ioctl(vm, KVM_ARM_PREFERRED_TARGET, &init);
8462306a36Sopenharmony_ci	init.features[0] |= (1 << KVM_ARM_VCPU_PSCI_0_2);
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	*source = aarch64_vcpu_add(vm, 0, &init, guest_code);
8762306a36Sopenharmony_ci	*target = aarch64_vcpu_add(vm, 1, &init, guest_code);
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	return vm;
9062306a36Sopenharmony_ci}
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_cistatic void enter_guest(struct kvm_vcpu *vcpu)
9362306a36Sopenharmony_ci{
9462306a36Sopenharmony_ci	struct ucall uc;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	vcpu_run(vcpu);
9762306a36Sopenharmony_ci	if (get_ucall(vcpu, &uc) == UCALL_ABORT)
9862306a36Sopenharmony_ci		REPORT_GUEST_ASSERT(uc);
9962306a36Sopenharmony_ci}
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_cistatic void assert_vcpu_reset(struct kvm_vcpu *vcpu)
10262306a36Sopenharmony_ci{
10362306a36Sopenharmony_ci	uint64_t obs_pc, obs_x0;
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	vcpu_get_reg(vcpu, ARM64_CORE_REG(regs.pc), &obs_pc);
10662306a36Sopenharmony_ci	vcpu_get_reg(vcpu, ARM64_CORE_REG(regs.regs[0]), &obs_x0);
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	TEST_ASSERT(obs_pc == CPU_ON_ENTRY_ADDR,
10962306a36Sopenharmony_ci		    "unexpected target cpu pc: %lx (expected: %lx)",
11062306a36Sopenharmony_ci		    obs_pc, CPU_ON_ENTRY_ADDR);
11162306a36Sopenharmony_ci	TEST_ASSERT(obs_x0 == CPU_ON_CONTEXT_ID,
11262306a36Sopenharmony_ci		    "unexpected target context id: %lx (expected: %lx)",
11362306a36Sopenharmony_ci		    obs_x0, CPU_ON_CONTEXT_ID);
11462306a36Sopenharmony_ci}
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_cistatic void guest_test_cpu_on(uint64_t target_cpu)
11762306a36Sopenharmony_ci{
11862306a36Sopenharmony_ci	uint64_t target_state;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	GUEST_ASSERT(!psci_cpu_on(target_cpu, CPU_ON_ENTRY_ADDR, CPU_ON_CONTEXT_ID));
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	do {
12362306a36Sopenharmony_ci		target_state = psci_affinity_info(target_cpu, 0);
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci		GUEST_ASSERT((target_state == PSCI_0_2_AFFINITY_LEVEL_ON) ||
12662306a36Sopenharmony_ci			     (target_state == PSCI_0_2_AFFINITY_LEVEL_OFF));
12762306a36Sopenharmony_ci	} while (target_state != PSCI_0_2_AFFINITY_LEVEL_ON);
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	GUEST_DONE();
13062306a36Sopenharmony_ci}
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_cistatic void host_test_cpu_on(void)
13362306a36Sopenharmony_ci{
13462306a36Sopenharmony_ci	struct kvm_vcpu *source, *target;
13562306a36Sopenharmony_ci	uint64_t target_mpidr;
13662306a36Sopenharmony_ci	struct kvm_vm *vm;
13762306a36Sopenharmony_ci	struct ucall uc;
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	vm = setup_vm(guest_test_cpu_on, &source, &target);
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	/*
14262306a36Sopenharmony_ci	 * make sure the target is already off when executing the test.
14362306a36Sopenharmony_ci	 */
14462306a36Sopenharmony_ci	vcpu_power_off(target);
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	vcpu_get_reg(target, KVM_ARM64_SYS_REG(SYS_MPIDR_EL1), &target_mpidr);
14762306a36Sopenharmony_ci	vcpu_args_set(source, 1, target_mpidr & MPIDR_HWID_BITMASK);
14862306a36Sopenharmony_ci	enter_guest(source);
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	if (get_ucall(source, &uc) != UCALL_DONE)
15162306a36Sopenharmony_ci		TEST_FAIL("Unhandled ucall: %lu", uc.cmd);
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	assert_vcpu_reset(target);
15462306a36Sopenharmony_ci	kvm_vm_free(vm);
15562306a36Sopenharmony_ci}
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_cistatic void guest_test_system_suspend(void)
15862306a36Sopenharmony_ci{
15962306a36Sopenharmony_ci	uint64_t ret;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	/* assert that SYSTEM_SUSPEND is discoverable */
16262306a36Sopenharmony_ci	GUEST_ASSERT(!psci_features(PSCI_1_0_FN_SYSTEM_SUSPEND));
16362306a36Sopenharmony_ci	GUEST_ASSERT(!psci_features(PSCI_1_0_FN64_SYSTEM_SUSPEND));
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	ret = psci_system_suspend(CPU_ON_ENTRY_ADDR, CPU_ON_CONTEXT_ID);
16662306a36Sopenharmony_ci	GUEST_SYNC(ret);
16762306a36Sopenharmony_ci}
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_cistatic void host_test_system_suspend(void)
17062306a36Sopenharmony_ci{
17162306a36Sopenharmony_ci	struct kvm_vcpu *source, *target;
17262306a36Sopenharmony_ci	struct kvm_run *run;
17362306a36Sopenharmony_ci	struct kvm_vm *vm;
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	vm = setup_vm(guest_test_system_suspend, &source, &target);
17662306a36Sopenharmony_ci	vm_enable_cap(vm, KVM_CAP_ARM_SYSTEM_SUSPEND, 0);
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	vcpu_power_off(target);
17962306a36Sopenharmony_ci	run = source->run;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	enter_guest(source);
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	TEST_ASSERT_KVM_EXIT_REASON(source, KVM_EXIT_SYSTEM_EVENT);
18462306a36Sopenharmony_ci	TEST_ASSERT(run->system_event.type == KVM_SYSTEM_EVENT_SUSPEND,
18562306a36Sopenharmony_ci		    "Unhandled system event: %u (expected: %u)",
18662306a36Sopenharmony_ci		    run->system_event.type, KVM_SYSTEM_EVENT_SUSPEND);
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	kvm_vm_free(vm);
18962306a36Sopenharmony_ci}
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ciint main(void)
19262306a36Sopenharmony_ci{
19362306a36Sopenharmony_ci	TEST_REQUIRE(kvm_has_cap(KVM_CAP_ARM_SYSTEM_SUSPEND));
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	host_test_cpu_on();
19662306a36Sopenharmony_ci	host_test_system_suspend();
19762306a36Sopenharmony_ci	return 0;
19862306a36Sopenharmony_ci}
199