162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2020 - Google LLC
462306a36Sopenharmony_ci * Author: David Brazdil <dbrazdil@google.com>
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include <asm/kvm_asm.h>
862306a36Sopenharmony_ci#include <asm/kvm_hyp.h>
962306a36Sopenharmony_ci#include <asm/kvm_mmu.h>
1062306a36Sopenharmony_ci#include <linux/arm-smccc.h>
1162306a36Sopenharmony_ci#include <linux/kvm_host.h>
1262306a36Sopenharmony_ci#include <uapi/linux/psci.h>
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#include <nvhe/memory.h>
1562306a36Sopenharmony_ci#include <nvhe/trap_handler.h>
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_civoid kvm_hyp_cpu_entry(unsigned long r0);
1862306a36Sopenharmony_civoid kvm_hyp_cpu_resume(unsigned long r0);
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_civoid __noreturn __host_enter(struct kvm_cpu_context *host_ctxt);
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci/* Config options set by the host. */
2362306a36Sopenharmony_cistruct kvm_host_psci_config __ro_after_init kvm_host_psci_config;
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#define INVALID_CPU_ID	UINT_MAX
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_cistruct psci_boot_args {
2862306a36Sopenharmony_ci	atomic_t lock;
2962306a36Sopenharmony_ci	unsigned long pc;
3062306a36Sopenharmony_ci	unsigned long r0;
3162306a36Sopenharmony_ci};
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci#define PSCI_BOOT_ARGS_UNLOCKED		0
3462306a36Sopenharmony_ci#define PSCI_BOOT_ARGS_LOCKED		1
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci#define PSCI_BOOT_ARGS_INIT					\
3762306a36Sopenharmony_ci	((struct psci_boot_args){				\
3862306a36Sopenharmony_ci		.lock = ATOMIC_INIT(PSCI_BOOT_ARGS_UNLOCKED),	\
3962306a36Sopenharmony_ci	})
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_cistatic DEFINE_PER_CPU(struct psci_boot_args, cpu_on_args) = PSCI_BOOT_ARGS_INIT;
4262306a36Sopenharmony_cistatic DEFINE_PER_CPU(struct psci_boot_args, suspend_args) = PSCI_BOOT_ARGS_INIT;
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci#define	is_psci_0_1(what, func_id)					\
4562306a36Sopenharmony_ci	(kvm_host_psci_config.psci_0_1_ ## what ## _implemented &&	\
4662306a36Sopenharmony_ci	 (func_id) == kvm_host_psci_config.function_ids_0_1.what)
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_cistatic bool is_psci_0_1_call(u64 func_id)
4962306a36Sopenharmony_ci{
5062306a36Sopenharmony_ci	return (is_psci_0_1(cpu_suspend, func_id) ||
5162306a36Sopenharmony_ci		is_psci_0_1(cpu_on, func_id) ||
5262306a36Sopenharmony_ci		is_psci_0_1(cpu_off, func_id) ||
5362306a36Sopenharmony_ci		is_psci_0_1(migrate, func_id));
5462306a36Sopenharmony_ci}
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_cistatic bool is_psci_0_2_call(u64 func_id)
5762306a36Sopenharmony_ci{
5862306a36Sopenharmony_ci	/* SMCCC reserves IDs 0x00-1F with the given 32/64-bit base for PSCI. */
5962306a36Sopenharmony_ci	return (PSCI_0_2_FN(0) <= func_id && func_id <= PSCI_0_2_FN(31)) ||
6062306a36Sopenharmony_ci	       (PSCI_0_2_FN64(0) <= func_id && func_id <= PSCI_0_2_FN64(31));
6162306a36Sopenharmony_ci}
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_cistatic unsigned long psci_call(unsigned long fn, unsigned long arg0,
6462306a36Sopenharmony_ci			       unsigned long arg1, unsigned long arg2)
6562306a36Sopenharmony_ci{
6662306a36Sopenharmony_ci	struct arm_smccc_res res;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	arm_smccc_1_1_smc(fn, arg0, arg1, arg2, &res);
6962306a36Sopenharmony_ci	return res.a0;
7062306a36Sopenharmony_ci}
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_cistatic unsigned long psci_forward(struct kvm_cpu_context *host_ctxt)
7362306a36Sopenharmony_ci{
7462306a36Sopenharmony_ci	return psci_call(cpu_reg(host_ctxt, 0), cpu_reg(host_ctxt, 1),
7562306a36Sopenharmony_ci			 cpu_reg(host_ctxt, 2), cpu_reg(host_ctxt, 3));
7662306a36Sopenharmony_ci}
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_cistatic unsigned int find_cpu_id(u64 mpidr)
7962306a36Sopenharmony_ci{
8062306a36Sopenharmony_ci	unsigned int i;
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	/* Reject invalid MPIDRs */
8362306a36Sopenharmony_ci	if (mpidr & ~MPIDR_HWID_BITMASK)
8462306a36Sopenharmony_ci		return INVALID_CPU_ID;
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	for (i = 0; i < NR_CPUS; i++) {
8762306a36Sopenharmony_ci		if (cpu_logical_map(i) == mpidr)
8862306a36Sopenharmony_ci			return i;
8962306a36Sopenharmony_ci	}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	return INVALID_CPU_ID;
9262306a36Sopenharmony_ci}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_cistatic __always_inline bool try_acquire_boot_args(struct psci_boot_args *args)
9562306a36Sopenharmony_ci{
9662306a36Sopenharmony_ci	return atomic_cmpxchg_acquire(&args->lock,
9762306a36Sopenharmony_ci				      PSCI_BOOT_ARGS_UNLOCKED,
9862306a36Sopenharmony_ci				      PSCI_BOOT_ARGS_LOCKED) ==
9962306a36Sopenharmony_ci		PSCI_BOOT_ARGS_UNLOCKED;
10062306a36Sopenharmony_ci}
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_cistatic __always_inline void release_boot_args(struct psci_boot_args *args)
10362306a36Sopenharmony_ci{
10462306a36Sopenharmony_ci	atomic_set_release(&args->lock, PSCI_BOOT_ARGS_UNLOCKED);
10562306a36Sopenharmony_ci}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_cistatic int psci_cpu_on(u64 func_id, struct kvm_cpu_context *host_ctxt)
10862306a36Sopenharmony_ci{
10962306a36Sopenharmony_ci	DECLARE_REG(u64, mpidr, host_ctxt, 1);
11062306a36Sopenharmony_ci	DECLARE_REG(unsigned long, pc, host_ctxt, 2);
11162306a36Sopenharmony_ci	DECLARE_REG(unsigned long, r0, host_ctxt, 3);
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	unsigned int cpu_id;
11462306a36Sopenharmony_ci	struct psci_boot_args *boot_args;
11562306a36Sopenharmony_ci	struct kvm_nvhe_init_params *init_params;
11662306a36Sopenharmony_ci	int ret;
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	/*
11962306a36Sopenharmony_ci	 * Find the logical CPU ID for the given MPIDR. The search set is
12062306a36Sopenharmony_ci	 * the set of CPUs that were online at the point of KVM initialization.
12162306a36Sopenharmony_ci	 * Booting other CPUs is rejected because their cpufeatures were not
12262306a36Sopenharmony_ci	 * checked against the finalized capabilities. This could be relaxed
12362306a36Sopenharmony_ci	 * by doing the feature checks in hyp.
12462306a36Sopenharmony_ci	 */
12562306a36Sopenharmony_ci	cpu_id = find_cpu_id(mpidr);
12662306a36Sopenharmony_ci	if (cpu_id == INVALID_CPU_ID)
12762306a36Sopenharmony_ci		return PSCI_RET_INVALID_PARAMS;
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	boot_args = per_cpu_ptr(&cpu_on_args, cpu_id);
13062306a36Sopenharmony_ci	init_params = per_cpu_ptr(&kvm_init_params, cpu_id);
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	/* Check if the target CPU is already being booted. */
13362306a36Sopenharmony_ci	if (!try_acquire_boot_args(boot_args))
13462306a36Sopenharmony_ci		return PSCI_RET_ALREADY_ON;
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	boot_args->pc = pc;
13762306a36Sopenharmony_ci	boot_args->r0 = r0;
13862306a36Sopenharmony_ci	wmb();
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	ret = psci_call(func_id, mpidr,
14162306a36Sopenharmony_ci			__hyp_pa(&kvm_hyp_cpu_entry),
14262306a36Sopenharmony_ci			__hyp_pa(init_params));
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	/* If successful, the lock will be released by the target CPU. */
14562306a36Sopenharmony_ci	if (ret != PSCI_RET_SUCCESS)
14662306a36Sopenharmony_ci		release_boot_args(boot_args);
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	return ret;
14962306a36Sopenharmony_ci}
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_cistatic int psci_cpu_suspend(u64 func_id, struct kvm_cpu_context *host_ctxt)
15262306a36Sopenharmony_ci{
15362306a36Sopenharmony_ci	DECLARE_REG(u64, power_state, host_ctxt, 1);
15462306a36Sopenharmony_ci	DECLARE_REG(unsigned long, pc, host_ctxt, 2);
15562306a36Sopenharmony_ci	DECLARE_REG(unsigned long, r0, host_ctxt, 3);
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	struct psci_boot_args *boot_args;
15862306a36Sopenharmony_ci	struct kvm_nvhe_init_params *init_params;
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	boot_args = this_cpu_ptr(&suspend_args);
16162306a36Sopenharmony_ci	init_params = this_cpu_ptr(&kvm_init_params);
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	/*
16462306a36Sopenharmony_ci	 * No need to acquire a lock before writing to boot_args because a core
16562306a36Sopenharmony_ci	 * can only suspend itself. Racy CPU_ON calls use a separate struct.
16662306a36Sopenharmony_ci	 */
16762306a36Sopenharmony_ci	boot_args->pc = pc;
16862306a36Sopenharmony_ci	boot_args->r0 = r0;
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	/*
17162306a36Sopenharmony_ci	 * Will either return if shallow sleep state, or wake up into the entry
17262306a36Sopenharmony_ci	 * point if it is a deep sleep state.
17362306a36Sopenharmony_ci	 */
17462306a36Sopenharmony_ci	return psci_call(func_id, power_state,
17562306a36Sopenharmony_ci			 __hyp_pa(&kvm_hyp_cpu_resume),
17662306a36Sopenharmony_ci			 __hyp_pa(init_params));
17762306a36Sopenharmony_ci}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_cistatic int psci_system_suspend(u64 func_id, struct kvm_cpu_context *host_ctxt)
18062306a36Sopenharmony_ci{
18162306a36Sopenharmony_ci	DECLARE_REG(unsigned long, pc, host_ctxt, 1);
18262306a36Sopenharmony_ci	DECLARE_REG(unsigned long, r0, host_ctxt, 2);
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	struct psci_boot_args *boot_args;
18562306a36Sopenharmony_ci	struct kvm_nvhe_init_params *init_params;
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	boot_args = this_cpu_ptr(&suspend_args);
18862306a36Sopenharmony_ci	init_params = this_cpu_ptr(&kvm_init_params);
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	/*
19162306a36Sopenharmony_ci	 * No need to acquire a lock before writing to boot_args because a core
19262306a36Sopenharmony_ci	 * can only suspend itself. Racy CPU_ON calls use a separate struct.
19362306a36Sopenharmony_ci	 */
19462306a36Sopenharmony_ci	boot_args->pc = pc;
19562306a36Sopenharmony_ci	boot_args->r0 = r0;
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci	/* Will only return on error. */
19862306a36Sopenharmony_ci	return psci_call(func_id,
19962306a36Sopenharmony_ci			 __hyp_pa(&kvm_hyp_cpu_resume),
20062306a36Sopenharmony_ci			 __hyp_pa(init_params), 0);
20162306a36Sopenharmony_ci}
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ciasmlinkage void __noreturn __kvm_host_psci_cpu_entry(bool is_cpu_on)
20462306a36Sopenharmony_ci{
20562306a36Sopenharmony_ci	struct psci_boot_args *boot_args;
20662306a36Sopenharmony_ci	struct kvm_cpu_context *host_ctxt;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	host_ctxt = &this_cpu_ptr(&kvm_host_data)->host_ctxt;
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	if (is_cpu_on)
21162306a36Sopenharmony_ci		boot_args = this_cpu_ptr(&cpu_on_args);
21262306a36Sopenharmony_ci	else
21362306a36Sopenharmony_ci		boot_args = this_cpu_ptr(&suspend_args);
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	cpu_reg(host_ctxt, 0) = boot_args->r0;
21662306a36Sopenharmony_ci	write_sysreg_el2(boot_args->pc, SYS_ELR);
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	if (is_cpu_on)
21962306a36Sopenharmony_ci		release_boot_args(boot_args);
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	__host_enter(host_ctxt);
22262306a36Sopenharmony_ci}
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_cistatic unsigned long psci_0_1_handler(u64 func_id, struct kvm_cpu_context *host_ctxt)
22562306a36Sopenharmony_ci{
22662306a36Sopenharmony_ci	if (is_psci_0_1(cpu_off, func_id) || is_psci_0_1(migrate, func_id))
22762306a36Sopenharmony_ci		return psci_forward(host_ctxt);
22862306a36Sopenharmony_ci	if (is_psci_0_1(cpu_on, func_id))
22962306a36Sopenharmony_ci		return psci_cpu_on(func_id, host_ctxt);
23062306a36Sopenharmony_ci	if (is_psci_0_1(cpu_suspend, func_id))
23162306a36Sopenharmony_ci		return psci_cpu_suspend(func_id, host_ctxt);
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	return PSCI_RET_NOT_SUPPORTED;
23462306a36Sopenharmony_ci}
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_cistatic unsigned long psci_0_2_handler(u64 func_id, struct kvm_cpu_context *host_ctxt)
23762306a36Sopenharmony_ci{
23862306a36Sopenharmony_ci	switch (func_id) {
23962306a36Sopenharmony_ci	case PSCI_0_2_FN_PSCI_VERSION:
24062306a36Sopenharmony_ci	case PSCI_0_2_FN_CPU_OFF:
24162306a36Sopenharmony_ci	case PSCI_0_2_FN64_AFFINITY_INFO:
24262306a36Sopenharmony_ci	case PSCI_0_2_FN64_MIGRATE:
24362306a36Sopenharmony_ci	case PSCI_0_2_FN_MIGRATE_INFO_TYPE:
24462306a36Sopenharmony_ci	case PSCI_0_2_FN64_MIGRATE_INFO_UP_CPU:
24562306a36Sopenharmony_ci		return psci_forward(host_ctxt);
24662306a36Sopenharmony_ci	/*
24762306a36Sopenharmony_ci	 * SYSTEM_OFF/RESET should not return according to the spec.
24862306a36Sopenharmony_ci	 * Allow it so as to stay robust to broken firmware.
24962306a36Sopenharmony_ci	 */
25062306a36Sopenharmony_ci	case PSCI_0_2_FN_SYSTEM_OFF:
25162306a36Sopenharmony_ci	case PSCI_0_2_FN_SYSTEM_RESET:
25262306a36Sopenharmony_ci		return psci_forward(host_ctxt);
25362306a36Sopenharmony_ci	case PSCI_0_2_FN64_CPU_SUSPEND:
25462306a36Sopenharmony_ci		return psci_cpu_suspend(func_id, host_ctxt);
25562306a36Sopenharmony_ci	case PSCI_0_2_FN64_CPU_ON:
25662306a36Sopenharmony_ci		return psci_cpu_on(func_id, host_ctxt);
25762306a36Sopenharmony_ci	default:
25862306a36Sopenharmony_ci		return PSCI_RET_NOT_SUPPORTED;
25962306a36Sopenharmony_ci	}
26062306a36Sopenharmony_ci}
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_cistatic unsigned long psci_1_0_handler(u64 func_id, struct kvm_cpu_context *host_ctxt)
26362306a36Sopenharmony_ci{
26462306a36Sopenharmony_ci	switch (func_id) {
26562306a36Sopenharmony_ci	case PSCI_1_0_FN_PSCI_FEATURES:
26662306a36Sopenharmony_ci	case PSCI_1_0_FN_SET_SUSPEND_MODE:
26762306a36Sopenharmony_ci	case PSCI_1_1_FN64_SYSTEM_RESET2:
26862306a36Sopenharmony_ci		return psci_forward(host_ctxt);
26962306a36Sopenharmony_ci	case PSCI_1_0_FN64_SYSTEM_SUSPEND:
27062306a36Sopenharmony_ci		return psci_system_suspend(func_id, host_ctxt);
27162306a36Sopenharmony_ci	default:
27262306a36Sopenharmony_ci		return psci_0_2_handler(func_id, host_ctxt);
27362306a36Sopenharmony_ci	}
27462306a36Sopenharmony_ci}
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_cibool kvm_host_psci_handler(struct kvm_cpu_context *host_ctxt, u32 func_id)
27762306a36Sopenharmony_ci{
27862306a36Sopenharmony_ci	unsigned long ret;
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	switch (kvm_host_psci_config.version) {
28162306a36Sopenharmony_ci	case PSCI_VERSION(0, 1):
28262306a36Sopenharmony_ci		if (!is_psci_0_1_call(func_id))
28362306a36Sopenharmony_ci			return false;
28462306a36Sopenharmony_ci		ret = psci_0_1_handler(func_id, host_ctxt);
28562306a36Sopenharmony_ci		break;
28662306a36Sopenharmony_ci	case PSCI_VERSION(0, 2):
28762306a36Sopenharmony_ci		if (!is_psci_0_2_call(func_id))
28862306a36Sopenharmony_ci			return false;
28962306a36Sopenharmony_ci		ret = psci_0_2_handler(func_id, host_ctxt);
29062306a36Sopenharmony_ci		break;
29162306a36Sopenharmony_ci	default:
29262306a36Sopenharmony_ci		if (!is_psci_0_2_call(func_id))
29362306a36Sopenharmony_ci			return false;
29462306a36Sopenharmony_ci		ret = psci_1_0_handler(func_id, host_ctxt);
29562306a36Sopenharmony_ci		break;
29662306a36Sopenharmony_ci	}
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci	cpu_reg(host_ctxt, 0) = ret;
29962306a36Sopenharmony_ci	cpu_reg(host_ctxt, 1) = 0;
30062306a36Sopenharmony_ci	cpu_reg(host_ctxt, 2) = 0;
30162306a36Sopenharmony_ci	cpu_reg(host_ctxt, 3) = 0;
30262306a36Sopenharmony_ci	return true;
30362306a36Sopenharmony_ci}
304