root/arch/riscv/kvm/isa.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2026 Qualcomm Technologies, Inc.
 */

#include <linux/cpufeature.h>
#include <linux/errno.h>
#include <linux/kvm_host.h>
#include <linux/nospec.h>
#include <linux/pgtable.h>
#include <asm/kvm_isa.h>
#include <asm/vector.h>

#define KVM_ISA_EXT_ARR(ext)            \
[KVM_RISCV_ISA_EXT_##ext] = RISCV_ISA_EXT_##ext

/* Mapping between KVM ISA Extension ID & guest ISA extension ID */
static const unsigned long kvm_isa_ext_arr[] = {
        /* Single letter extensions (alphabetically sorted) */
        [KVM_RISCV_ISA_EXT_A] = RISCV_ISA_EXT_a,
        [KVM_RISCV_ISA_EXT_C] = RISCV_ISA_EXT_c,
        [KVM_RISCV_ISA_EXT_D] = RISCV_ISA_EXT_d,
        [KVM_RISCV_ISA_EXT_F] = RISCV_ISA_EXT_f,
        [KVM_RISCV_ISA_EXT_H] = RISCV_ISA_EXT_h,
        [KVM_RISCV_ISA_EXT_I] = RISCV_ISA_EXT_i,
        [KVM_RISCV_ISA_EXT_M] = RISCV_ISA_EXT_m,
        [KVM_RISCV_ISA_EXT_V] = RISCV_ISA_EXT_v,
        /* Multi letter extensions (alphabetically sorted) */
        KVM_ISA_EXT_ARR(SMNPM),
        KVM_ISA_EXT_ARR(SMSTATEEN),
        KVM_ISA_EXT_ARR(SSAIA),
        KVM_ISA_EXT_ARR(SSCOFPMF),
        KVM_ISA_EXT_ARR(SSNPM),
        KVM_ISA_EXT_ARR(SSTC),
        KVM_ISA_EXT_ARR(SVADE),
        KVM_ISA_EXT_ARR(SVADU),
        KVM_ISA_EXT_ARR(SVINVAL),
        KVM_ISA_EXT_ARR(SVNAPOT),
        KVM_ISA_EXT_ARR(SVPBMT),
        KVM_ISA_EXT_ARR(SVVPTC),
        KVM_ISA_EXT_ARR(ZAAMO),
        KVM_ISA_EXT_ARR(ZABHA),
        KVM_ISA_EXT_ARR(ZACAS),
        KVM_ISA_EXT_ARR(ZALASR),
        KVM_ISA_EXT_ARR(ZALRSC),
        KVM_ISA_EXT_ARR(ZAWRS),
        KVM_ISA_EXT_ARR(ZBA),
        KVM_ISA_EXT_ARR(ZBB),
        KVM_ISA_EXT_ARR(ZBC),
        KVM_ISA_EXT_ARR(ZBKB),
        KVM_ISA_EXT_ARR(ZBKC),
        KVM_ISA_EXT_ARR(ZBKX),
        KVM_ISA_EXT_ARR(ZBS),
        KVM_ISA_EXT_ARR(ZCA),
        KVM_ISA_EXT_ARR(ZCB),
        KVM_ISA_EXT_ARR(ZCD),
        KVM_ISA_EXT_ARR(ZCF),
        KVM_ISA_EXT_ARR(ZCLSD),
        KVM_ISA_EXT_ARR(ZCMOP),
        KVM_ISA_EXT_ARR(ZFA),
        KVM_ISA_EXT_ARR(ZFBFMIN),
        KVM_ISA_EXT_ARR(ZFH),
        KVM_ISA_EXT_ARR(ZFHMIN),
        KVM_ISA_EXT_ARR(ZICBOM),
        KVM_ISA_EXT_ARR(ZICBOP),
        KVM_ISA_EXT_ARR(ZICBOZ),
        KVM_ISA_EXT_ARR(ZICCRSE),
        KVM_ISA_EXT_ARR(ZICNTR),
        KVM_ISA_EXT_ARR(ZICOND),
        KVM_ISA_EXT_ARR(ZICSR),
        KVM_ISA_EXT_ARR(ZIFENCEI),
        KVM_ISA_EXT_ARR(ZIHINTNTL),
        KVM_ISA_EXT_ARR(ZIHINTPAUSE),
        KVM_ISA_EXT_ARR(ZIHPM),
        KVM_ISA_EXT_ARR(ZILSD),
        KVM_ISA_EXT_ARR(ZIMOP),
        KVM_ISA_EXT_ARR(ZKND),
        KVM_ISA_EXT_ARR(ZKNE),
        KVM_ISA_EXT_ARR(ZKNH),
        KVM_ISA_EXT_ARR(ZKR),
        KVM_ISA_EXT_ARR(ZKSED),
        KVM_ISA_EXT_ARR(ZKSH),
        KVM_ISA_EXT_ARR(ZKT),
        KVM_ISA_EXT_ARR(ZTSO),
        KVM_ISA_EXT_ARR(ZVBB),
        KVM_ISA_EXT_ARR(ZVBC),
        KVM_ISA_EXT_ARR(ZVFBFMIN),
        KVM_ISA_EXT_ARR(ZVFBFWMA),
        KVM_ISA_EXT_ARR(ZVFH),
        KVM_ISA_EXT_ARR(ZVFHMIN),
        KVM_ISA_EXT_ARR(ZVKB),
        KVM_ISA_EXT_ARR(ZVKG),
        KVM_ISA_EXT_ARR(ZVKNED),
        KVM_ISA_EXT_ARR(ZVKNHA),
        KVM_ISA_EXT_ARR(ZVKNHB),
        KVM_ISA_EXT_ARR(ZVKSED),
        KVM_ISA_EXT_ARR(ZVKSH),
        KVM_ISA_EXT_ARR(ZVKT),
};

unsigned long kvm_riscv_base2isa_ext(unsigned long base_ext)
{
        unsigned long i;

        for (i = 0; i < KVM_RISCV_ISA_EXT_MAX; i++) {
                if (kvm_isa_ext_arr[i] == base_ext)
                        return i;
        }

        return KVM_RISCV_ISA_EXT_MAX;
}

int __kvm_riscv_isa_check_host(unsigned long kvm_ext, unsigned long *base_ext)
{
        unsigned long host_ext;

        if (kvm_ext >= KVM_RISCV_ISA_EXT_MAX ||
            kvm_ext >= ARRAY_SIZE(kvm_isa_ext_arr))
                return -ENOENT;

        kvm_ext = array_index_nospec(kvm_ext, ARRAY_SIZE(kvm_isa_ext_arr));
        switch (kvm_isa_ext_arr[kvm_ext]) {
        case RISCV_ISA_EXT_SMNPM:
                /*
                 * Pointer masking effective in (H)S-mode is provided by the
                 * Smnpm extension, so that extension is reported to the guest,
                 * even though the CSR bits for configuring VS-mode pointer
                 * masking on the host side are part of the Ssnpm extension.
                 */
                host_ext = RISCV_ISA_EXT_SSNPM;
                break;
        default:
                host_ext = kvm_isa_ext_arr[kvm_ext];
                break;
        }

        if (!__riscv_isa_extension_available(NULL, host_ext))
                return -ENOENT;

        if (base_ext)
                *base_ext = kvm_isa_ext_arr[kvm_ext];

        return 0;
}

bool kvm_riscv_isa_enable_allowed(unsigned long ext)
{
        switch (ext) {
        case KVM_RISCV_ISA_EXT_H:
                return false;
        case KVM_RISCV_ISA_EXT_SSCOFPMF:
                /* Sscofpmf depends on interrupt filtering defined in ssaia */
                return !kvm_riscv_isa_check_host(SSAIA);
        case KVM_RISCV_ISA_EXT_SVADU:
                /*
                 * The henvcfg.ADUE is read-only zero if menvcfg.ADUE is zero.
                 * Guest OS can use Svadu only when host OS enable Svadu.
                 */
                return arch_has_hw_pte_young();
        case KVM_RISCV_ISA_EXT_V:
                return riscv_v_vstate_ctrl_user_allowed();
        default:
                break;
        }

        return true;
}

bool kvm_riscv_isa_disable_allowed(unsigned long ext)
{
        switch (ext) {
        /* Extensions which don't have any mechanism to disable */
        case KVM_RISCV_ISA_EXT_A:
        case KVM_RISCV_ISA_EXT_C:
        case KVM_RISCV_ISA_EXT_I:
        case KVM_RISCV_ISA_EXT_M:
        /* There is not architectural config bit to disable sscofpmf completely */
        case KVM_RISCV_ISA_EXT_SSCOFPMF:
        case KVM_RISCV_ISA_EXT_SSNPM:
        case KVM_RISCV_ISA_EXT_SSTC:
        case KVM_RISCV_ISA_EXT_SVINVAL:
        case KVM_RISCV_ISA_EXT_SVNAPOT:
        case KVM_RISCV_ISA_EXT_SVVPTC:
        case KVM_RISCV_ISA_EXT_ZAAMO:
        case KVM_RISCV_ISA_EXT_ZABHA:
        case KVM_RISCV_ISA_EXT_ZACAS:
        case KVM_RISCV_ISA_EXT_ZALASR:
        case KVM_RISCV_ISA_EXT_ZALRSC:
        case KVM_RISCV_ISA_EXT_ZAWRS:
        case KVM_RISCV_ISA_EXT_ZBA:
        case KVM_RISCV_ISA_EXT_ZBB:
        case KVM_RISCV_ISA_EXT_ZBC:
        case KVM_RISCV_ISA_EXT_ZBKB:
        case KVM_RISCV_ISA_EXT_ZBKC:
        case KVM_RISCV_ISA_EXT_ZBKX:
        case KVM_RISCV_ISA_EXT_ZBS:
        case KVM_RISCV_ISA_EXT_ZCA:
        case KVM_RISCV_ISA_EXT_ZCB:
        case KVM_RISCV_ISA_EXT_ZCD:
        case KVM_RISCV_ISA_EXT_ZCF:
        case KVM_RISCV_ISA_EXT_ZCMOP:
        case KVM_RISCV_ISA_EXT_ZFA:
        case KVM_RISCV_ISA_EXT_ZFBFMIN:
        case KVM_RISCV_ISA_EXT_ZFH:
        case KVM_RISCV_ISA_EXT_ZFHMIN:
        case KVM_RISCV_ISA_EXT_ZICBOP:
        case KVM_RISCV_ISA_EXT_ZICCRSE:
        case KVM_RISCV_ISA_EXT_ZICNTR:
        case KVM_RISCV_ISA_EXT_ZICOND:
        case KVM_RISCV_ISA_EXT_ZICSR:
        case KVM_RISCV_ISA_EXT_ZIFENCEI:
        case KVM_RISCV_ISA_EXT_ZIHINTNTL:
        case KVM_RISCV_ISA_EXT_ZIHINTPAUSE:
        case KVM_RISCV_ISA_EXT_ZIHPM:
        case KVM_RISCV_ISA_EXT_ZIMOP:
        case KVM_RISCV_ISA_EXT_ZKND:
        case KVM_RISCV_ISA_EXT_ZKNE:
        case KVM_RISCV_ISA_EXT_ZKNH:
        case KVM_RISCV_ISA_EXT_ZKR:
        case KVM_RISCV_ISA_EXT_ZKSED:
        case KVM_RISCV_ISA_EXT_ZKSH:
        case KVM_RISCV_ISA_EXT_ZKT:
        case KVM_RISCV_ISA_EXT_ZTSO:
        case KVM_RISCV_ISA_EXT_ZVBB:
        case KVM_RISCV_ISA_EXT_ZVBC:
        case KVM_RISCV_ISA_EXT_ZVFBFMIN:
        case KVM_RISCV_ISA_EXT_ZVFBFWMA:
        case KVM_RISCV_ISA_EXT_ZVFH:
        case KVM_RISCV_ISA_EXT_ZVFHMIN:
        case KVM_RISCV_ISA_EXT_ZVKB:
        case KVM_RISCV_ISA_EXT_ZVKG:
        case KVM_RISCV_ISA_EXT_ZVKNED:
        case KVM_RISCV_ISA_EXT_ZVKNHA:
        case KVM_RISCV_ISA_EXT_ZVKNHB:
        case KVM_RISCV_ISA_EXT_ZVKSED:
        case KVM_RISCV_ISA_EXT_ZVKSH:
        case KVM_RISCV_ISA_EXT_ZVKT:
                return false;
        /* Extensions which can be disabled using Smstateen */
        case KVM_RISCV_ISA_EXT_SSAIA:
                return riscv_has_extension_unlikely(RISCV_ISA_EXT_SMSTATEEN);
        case KVM_RISCV_ISA_EXT_SVADE:
                /*
                 * The henvcfg.ADUE is read-only zero if menvcfg.ADUE is zero.
                 * Svade can't be disabled unless we support Svadu.
                 */
                return arch_has_hw_pte_young();
        default:
                break;
        }

        return true;
}