162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (c) 2021 Western Digital Corporation or its affiliates.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Authors:
662306a36Sopenharmony_ci *     Atish Patra <atish.patra@wdc.com>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/errno.h>
1062306a36Sopenharmony_ci#include <linux/err.h>
1162306a36Sopenharmony_ci#include <linux/kvm_host.h>
1262306a36Sopenharmony_ci#include <asm/sbi.h>
1362306a36Sopenharmony_ci#include <asm/kvm_vcpu_sbi.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_cistatic int kvm_sbi_hsm_vcpu_start(struct kvm_vcpu *vcpu)
1662306a36Sopenharmony_ci{
1762306a36Sopenharmony_ci	struct kvm_cpu_context *reset_cntx;
1862306a36Sopenharmony_ci	struct kvm_cpu_context *cp = &vcpu->arch.guest_context;
1962306a36Sopenharmony_ci	struct kvm_vcpu *target_vcpu;
2062306a36Sopenharmony_ci	unsigned long target_vcpuid = cp->a0;
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci	target_vcpu = kvm_get_vcpu_by_id(vcpu->kvm, target_vcpuid);
2362306a36Sopenharmony_ci	if (!target_vcpu)
2462306a36Sopenharmony_ci		return SBI_ERR_INVALID_PARAM;
2562306a36Sopenharmony_ci	if (!target_vcpu->arch.power_off)
2662306a36Sopenharmony_ci		return SBI_ERR_ALREADY_AVAILABLE;
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci	reset_cntx = &target_vcpu->arch.guest_reset_context;
2962306a36Sopenharmony_ci	/* start address */
3062306a36Sopenharmony_ci	reset_cntx->sepc = cp->a1;
3162306a36Sopenharmony_ci	/* target vcpu id to start */
3262306a36Sopenharmony_ci	reset_cntx->a0 = target_vcpuid;
3362306a36Sopenharmony_ci	/* private data passed from kernel */
3462306a36Sopenharmony_ci	reset_cntx->a1 = cp->a2;
3562306a36Sopenharmony_ci	kvm_make_request(KVM_REQ_VCPU_RESET, target_vcpu);
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	kvm_riscv_vcpu_power_on(target_vcpu);
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	return 0;
4062306a36Sopenharmony_ci}
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic int kvm_sbi_hsm_vcpu_stop(struct kvm_vcpu *vcpu)
4362306a36Sopenharmony_ci{
4462306a36Sopenharmony_ci	if (vcpu->arch.power_off)
4562306a36Sopenharmony_ci		return SBI_ERR_FAILURE;
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	kvm_riscv_vcpu_power_off(vcpu);
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	return 0;
5062306a36Sopenharmony_ci}
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cistatic int kvm_sbi_hsm_vcpu_get_status(struct kvm_vcpu *vcpu)
5362306a36Sopenharmony_ci{
5462306a36Sopenharmony_ci	struct kvm_cpu_context *cp = &vcpu->arch.guest_context;
5562306a36Sopenharmony_ci	unsigned long target_vcpuid = cp->a0;
5662306a36Sopenharmony_ci	struct kvm_vcpu *target_vcpu;
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	target_vcpu = kvm_get_vcpu_by_id(vcpu->kvm, target_vcpuid);
5962306a36Sopenharmony_ci	if (!target_vcpu)
6062306a36Sopenharmony_ci		return SBI_ERR_INVALID_PARAM;
6162306a36Sopenharmony_ci	if (!target_vcpu->arch.power_off)
6262306a36Sopenharmony_ci		return SBI_HSM_STATE_STARTED;
6362306a36Sopenharmony_ci	else if (vcpu->stat.generic.blocking)
6462306a36Sopenharmony_ci		return SBI_HSM_STATE_SUSPENDED;
6562306a36Sopenharmony_ci	else
6662306a36Sopenharmony_ci		return SBI_HSM_STATE_STOPPED;
6762306a36Sopenharmony_ci}
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_cistatic int kvm_sbi_ext_hsm_handler(struct kvm_vcpu *vcpu, struct kvm_run *run,
7062306a36Sopenharmony_ci				   struct kvm_vcpu_sbi_return *retdata)
7162306a36Sopenharmony_ci{
7262306a36Sopenharmony_ci	int ret = 0;
7362306a36Sopenharmony_ci	struct kvm_cpu_context *cp = &vcpu->arch.guest_context;
7462306a36Sopenharmony_ci	struct kvm *kvm = vcpu->kvm;
7562306a36Sopenharmony_ci	unsigned long funcid = cp->a6;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	switch (funcid) {
7862306a36Sopenharmony_ci	case SBI_EXT_HSM_HART_START:
7962306a36Sopenharmony_ci		mutex_lock(&kvm->lock);
8062306a36Sopenharmony_ci		ret = kvm_sbi_hsm_vcpu_start(vcpu);
8162306a36Sopenharmony_ci		mutex_unlock(&kvm->lock);
8262306a36Sopenharmony_ci		break;
8362306a36Sopenharmony_ci	case SBI_EXT_HSM_HART_STOP:
8462306a36Sopenharmony_ci		ret = kvm_sbi_hsm_vcpu_stop(vcpu);
8562306a36Sopenharmony_ci		break;
8662306a36Sopenharmony_ci	case SBI_EXT_HSM_HART_STATUS:
8762306a36Sopenharmony_ci		ret = kvm_sbi_hsm_vcpu_get_status(vcpu);
8862306a36Sopenharmony_ci		if (ret >= 0) {
8962306a36Sopenharmony_ci			retdata->out_val = ret;
9062306a36Sopenharmony_ci			retdata->err_val = 0;
9162306a36Sopenharmony_ci		}
9262306a36Sopenharmony_ci		return 0;
9362306a36Sopenharmony_ci	case SBI_EXT_HSM_HART_SUSPEND:
9462306a36Sopenharmony_ci		switch (cp->a0) {
9562306a36Sopenharmony_ci		case SBI_HSM_SUSPEND_RET_DEFAULT:
9662306a36Sopenharmony_ci			kvm_riscv_vcpu_wfi(vcpu);
9762306a36Sopenharmony_ci			break;
9862306a36Sopenharmony_ci		case SBI_HSM_SUSPEND_NON_RET_DEFAULT:
9962306a36Sopenharmony_ci			ret = SBI_ERR_NOT_SUPPORTED;
10062306a36Sopenharmony_ci			break;
10162306a36Sopenharmony_ci		default:
10262306a36Sopenharmony_ci			ret = SBI_ERR_INVALID_PARAM;
10362306a36Sopenharmony_ci		}
10462306a36Sopenharmony_ci		break;
10562306a36Sopenharmony_ci	default:
10662306a36Sopenharmony_ci		ret = SBI_ERR_NOT_SUPPORTED;
10762306a36Sopenharmony_ci	}
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	retdata->err_val = ret;
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	return 0;
11262306a36Sopenharmony_ci}
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ciconst struct kvm_vcpu_sbi_extension vcpu_sbi_ext_hsm = {
11562306a36Sopenharmony_ci	.extid_start = SBI_EXT_HSM,
11662306a36Sopenharmony_ci	.extid_end = SBI_EXT_HSM,
11762306a36Sopenharmony_ci	.handler = kvm_sbi_ext_hsm_handler,
11862306a36Sopenharmony_ci};
119