blob: 89568ccce01de5adbb7ca8d49bd8836f0e9099b9 [file] [edit]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2019 Western Digital Corporation or its affiliates.
*
* Authors:
* Anup Patel <anup.patel@wdc.com>
*/
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/kvm_host.h>
#include <linux/cpu_pm.h>
#include <asm/cpufeature.h>
#include <asm/kvm_mmu.h>
#include <asm/kvm_nacl.h>
#include <asm/sbi.h>
#include <asm/kvm_vcpu_vector.h>
static DEFINE_PER_CPU(bool, kvm_riscv_virtualization_enabled);
DEFINE_STATIC_KEY_FALSE(kvm_riscv_vsstage_tlb_no_gpa);
static void kvm_riscv_setup_vendor_features(void)
{
/* Andes AX66: split two-stage TLBs */
if (riscv_cached_mvendorid(0) == ANDES_VENDOR_ID &&
(riscv_cached_marchid(0) & 0xFFFF) == 0x8A66) {
static_branch_enable(&kvm_riscv_vsstage_tlb_no_gpa);
kvm_info("VS-stage TLB does not cache guest physical address and VMID\n");
}
}
long kvm_arch_dev_ioctl(struct file *filp,
unsigned int ioctl, unsigned long arg)
{
return -EINVAL;
}
/* Initialize hypervisor CSRs - called during CPU online and non-retention idle resume */
static void kvm_riscv_csr_init(void)
{
csr_write(CSR_HEDELEG, 0);
csr_write(CSR_HIDELEG, 0);
/* VS should access only the time counter directly. Everything else should trap */
csr_write(CSR_HCOUNTEREN, 0x02);
csr_write(CSR_HVIP, 0);
}
/* Clear hypervisor CSRs - called during CPU offline and non-retention idle entry */
static void kvm_riscv_csr_cleanup(void)
{
/*
* After clearing the hideleg CSR, the host kernel will receive
* spurious interrupts if hvip CSR has pending interrupts and the
* corresponding enable bits in vsie CSR are asserted. To avoid it,
* hvip CSR and vsie CSR must be cleared before clearing hideleg CSR.
*/
csr_write(CSR_VSIE, 0);
csr_write(CSR_HVIP, 0);
csr_write(CSR_HEDELEG, 0);
csr_write(CSR_HIDELEG, 0);
kvm_riscv_clear_former_vcpu();
}
int kvm_arch_enable_virtualization_cpu(void)
{
int rc;
rc = kvm_riscv_nacl_enable();
if (rc)
return rc;
kvm_riscv_csr_init();
kvm_riscv_aia_enable();
__this_cpu_write(kvm_riscv_virtualization_enabled, true);
return 0;
}
void kvm_arch_disable_virtualization_cpu(void)
{
kvm_riscv_aia_disable();
kvm_riscv_csr_cleanup();
kvm_riscv_nacl_disable();
__this_cpu_write(kvm_riscv_virtualization_enabled, false);
}
static int kvm_riscv_cpu_pm_notifier(struct notifier_block *self, unsigned long cmd, void *v)
{
switch (cmd) {
case CPU_PM_EXIT:
case CPU_PM_ENTER_FAILED:
/*
* Only restore hypervisor state if KVM virtualization is
* enabled on this CPU. This prevents unintentional re-enabling
* of virtualization after it has been explicitly disabled.
*/
if (__this_cpu_read(kvm_riscv_virtualization_enabled)) {
kvm_riscv_csr_init();
kvm_riscv_aia_pm_exit();
}
return NOTIFY_OK;
case CPU_PM_ENTER:
/*
* Only save and clear hypervisor state if KVM virtualization
* is enabled on this CPU.
*/
if (__this_cpu_read(kvm_riscv_virtualization_enabled)) {
kvm_riscv_aia_pm_enter();
kvm_riscv_csr_cleanup();
}
return NOTIFY_OK;
default:
break;
}
return NOTIFY_DONE;
}
static struct notifier_block kvm_riscv_cpu_pm_nb = {
.notifier_call = kvm_riscv_cpu_pm_notifier,
};
static void kvm_riscv_teardown(void)
{
kvm_riscv_aia_exit();
kvm_riscv_nacl_exit();
kvm_riscv_v_exit();
kvm_unregister_perf_callbacks();
}
static int __init riscv_kvm_init(void)
{
int rc;
char slist[64];
const char *str;
if (!riscv_isa_extension_available(NULL, H)) {
kvm_info("hypervisor extension not available\n");
return -ENODEV;
}
if (sbi_spec_is_0_1()) {
kvm_info("require SBI v0.2 or higher\n");
return -ENODEV;
}
if (!sbi_probe_extension(SBI_EXT_RFENCE)) {
kvm_info("require SBI RFENCE extension\n");
return -ENODEV;
}
rc = kvm_riscv_nacl_init();
if (rc && rc != -ENODEV)
return rc;
kvm_riscv_gstage_mode_detect();
switch (kvm_riscv_gstage_max_pgd_levels) {
case 2:
str = "Sv32x4";
break;
case 3:
str = "Sv39x4";
break;
case 4:
str = "Sv48x4";
break;
case 5:
str = "Sv57x4";
break;
default:
kvm_riscv_nacl_exit();
return -ENODEV;
}
kvm_riscv_gstage_vmid_detect();
rc = kvm_riscv_aia_init();
if (rc && rc != -ENODEV) {
kvm_riscv_nacl_exit();
return rc;
}
kvm_info("hypervisor extension available\n");
if (kvm_riscv_nacl_available()) {
rc = 0;
slist[0] = '\0';
if (kvm_riscv_nacl_sync_csr_available()) {
if (rc)
strcat(slist, ", ");
strcat(slist, "sync_csr");
rc++;
}
if (kvm_riscv_nacl_sync_hfence_available()) {
if (rc)
strcat(slist, ", ");
strcat(slist, "sync_hfence");
rc++;
}
if (kvm_riscv_nacl_sync_sret_available()) {
if (rc)
strcat(slist, ", ");
strcat(slist, "sync_sret");
rc++;
}
if (kvm_riscv_nacl_autoswap_csr_available()) {
if (rc)
strcat(slist, ", ");
strcat(slist, "autoswap_csr");
rc++;
}
kvm_info("using SBI nested acceleration with %s\n",
(rc) ? slist : "no features");
}
kvm_info("highest G-stage page table mode is %s\n", str);
kvm_info("VMID %ld bits available\n", kvm_riscv_gstage_vmid_bits());
kvm_riscv_setup_vendor_features();
kvm_riscv_v_init();
kvm_register_perf_callbacks();
/* Register CPU PM notifier for CPU idle non-retention states */
if (IS_ENABLED(CONFIG_CPU_PM)) {
rc = cpu_pm_register_notifier(&kvm_riscv_cpu_pm_nb);
if (rc) {
kvm_err("Failed to register CPU PM notifier: %d\n", rc);
goto err_teardown;
}
}
rc = kvm_init(sizeof(struct kvm_vcpu), 0, THIS_MODULE);
if (rc)
goto err_unregister_cpu_pm;
if (kvm_riscv_aia_available())
kvm_info("AIA available with %d guest external interrupts\n",
atomic_read(&kvm_riscv_aia_nr_hgei));
return 0;
err_unregister_cpu_pm:
if (IS_ENABLED(CONFIG_CPU_PM))
cpu_pm_unregister_notifier(&kvm_riscv_cpu_pm_nb);
err_teardown:
kvm_riscv_teardown();
return rc;
}
module_init(riscv_kvm_init);
static void __exit riscv_kvm_exit(void)
{
kvm_exit();
/* Unregister CPU PM notifier */
if (IS_ENABLED(CONFIG_CPU_PM))
cpu_pm_unregister_notifier(&kvm_riscv_cpu_pm_nb);
kvm_riscv_teardown();
}
module_exit(riscv_kvm_exit);