| // 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_ISA_EXT_ARR(A), | |
| KVM_ISA_EXT_ARR(C), | |
| KVM_ISA_EXT_ARR(D), | |
| KVM_ISA_EXT_ARR(F), | |
| KVM_ISA_EXT_ARR(H), | |
| KVM_ISA_EXT_ARR(I), | |
| KVM_ISA_EXT_ARR(M), | |
| KVM_ISA_EXT_ARR(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(ZICFILP), | |
| KVM_ISA_EXT_ARR(ZICFISS), | |
| 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; | |
| } |