| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2025 Rivos Inc. |
| * |
| * Authors: |
| * Clément Léger <cleger@rivosinc.com> |
| */ |
| |
| #include <linux/errno.h> |
| #include <linux/err.h> |
| #include <linux/kvm_host.h> |
| #include <asm/cpufeature.h> |
| #include <asm/sbi.h> |
| #include <asm/kvm_vcpu_sbi.h> |
| #include <asm/kvm_vcpu_sbi_fwft.h> |
| |
| #define MIS_DELEG (BIT_ULL(EXC_LOAD_MISALIGNED) | BIT_ULL(EXC_STORE_MISALIGNED)) |
| |
| struct kvm_sbi_fwft_feature { |
| /** |
| * @id: Feature ID |
| */ |
| enum sbi_fwft_feature_t id; |
| |
| /** |
| * @first_reg_num: ONE_REG index of the first ONE_REG register |
| */ |
| unsigned long first_reg_num; |
| |
| /** |
| * @supported: Check if the feature is supported on the vcpu |
| * |
| * This callback is optional, if not provided the feature is assumed to |
| * be supported |
| */ |
| bool (*supported)(struct kvm_vcpu *vcpu); |
| |
| /** |
| * @reset: Reset the feature value irrespective whether feature is supported or not |
| * |
| * This callback is mandatory |
| */ |
| void (*reset)(struct kvm_vcpu *vcpu); |
| |
| /** |
| * @set: Set the feature value |
| * |
| * Return SBI_SUCCESS on success or an SBI error (SBI_ERR_*) |
| * |
| * This callback is mandatory |
| */ |
| long (*set)(struct kvm_vcpu *vcpu, struct kvm_sbi_fwft_config *conf, |
| bool one_reg_access, unsigned long value); |
| |
| /** |
| * @get: Get the feature current value |
| * |
| * Return SBI_SUCCESS on success or an SBI error (SBI_ERR_*) |
| * |
| * This callback is mandatory |
| */ |
| long (*get)(struct kvm_vcpu *vcpu, struct kvm_sbi_fwft_config *conf, |
| bool one_reg_access, unsigned long *value); |
| }; |
| |
| static const enum sbi_fwft_feature_t kvm_fwft_defined_features[] = { |
| SBI_FWFT_MISALIGNED_EXC_DELEG, |
| SBI_FWFT_LANDING_PAD, |
| SBI_FWFT_SHADOW_STACK, |
| SBI_FWFT_DOUBLE_TRAP, |
| SBI_FWFT_PTE_AD_HW_UPDATING, |
| SBI_FWFT_POINTER_MASKING_PMLEN, |
| }; |
| |
| static bool kvm_fwft_is_defined_feature(enum sbi_fwft_feature_t feature) |
| { |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(kvm_fwft_defined_features); i++) { |
| if (kvm_fwft_defined_features[i] == feature) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static bool kvm_sbi_fwft_misaligned_delegation_supported(struct kvm_vcpu *vcpu) |
| { |
| return misaligned_traps_can_delegate(); |
| } |
| |
| static void kvm_sbi_fwft_reset_misaligned_delegation(struct kvm_vcpu *vcpu) |
| { |
| struct kvm_vcpu_config *cfg = &vcpu->arch.cfg; |
| |
| cfg->hedeleg &= ~MIS_DELEG; |
| } |
| |
| static long kvm_sbi_fwft_set_misaligned_delegation(struct kvm_vcpu *vcpu, |
| struct kvm_sbi_fwft_config *conf, |
| bool one_reg_access, unsigned long value) |
| { |
| struct kvm_vcpu_config *cfg = &vcpu->arch.cfg; |
| |
| if (value == 1) { |
| cfg->hedeleg |= MIS_DELEG; |
| if (!one_reg_access) |
| csr_set(CSR_HEDELEG, MIS_DELEG); |
| } else if (value == 0) { |
| cfg->hedeleg &= ~MIS_DELEG; |
| if (!one_reg_access) |
| csr_clear(CSR_HEDELEG, MIS_DELEG); |
| } else { |
| return SBI_ERR_INVALID_PARAM; |
| } |
| |
| return SBI_SUCCESS; |
| } |
| |
| static long kvm_sbi_fwft_get_misaligned_delegation(struct kvm_vcpu *vcpu, |
| struct kvm_sbi_fwft_config *conf, |
| bool one_reg_access, unsigned long *value) |
| { |
| struct kvm_vcpu_config *cfg = &vcpu->arch.cfg; |
| |
| *value = (cfg->hedeleg & MIS_DELEG) == MIS_DELEG; |
| return SBI_SUCCESS; |
| } |
| |
| #ifndef CONFIG_32BIT |
| |
| static bool try_to_set_pmm(unsigned long value) |
| { |
| csr_set(CSR_HENVCFG, value); |
| return (csr_read_clear(CSR_HENVCFG, ENVCFG_PMM) & ENVCFG_PMM) == value; |
| } |
| |
| static bool kvm_sbi_fwft_pointer_masking_pmlen_supported(struct kvm_vcpu *vcpu) |
| { |
| struct kvm_sbi_fwft *fwft = vcpu_to_fwft(vcpu); |
| |
| if (!riscv_isa_extension_available(vcpu->arch.isa, SMNPM)) |
| return false; |
| |
| fwft->have_vs_pmlen_7 = try_to_set_pmm(ENVCFG_PMM_PMLEN_7); |
| fwft->have_vs_pmlen_16 = try_to_set_pmm(ENVCFG_PMM_PMLEN_16); |
| |
| return fwft->have_vs_pmlen_7 || fwft->have_vs_pmlen_16; |
| } |
| |
| static void kvm_sbi_fwft_reset_pointer_masking_pmlen(struct kvm_vcpu *vcpu) |
| { |
| vcpu->arch.cfg.henvcfg &= ~ENVCFG_PMM; |
| } |
| |
| static long kvm_sbi_fwft_set_pointer_masking_pmlen(struct kvm_vcpu *vcpu, |
| struct kvm_sbi_fwft_config *conf, |
| bool one_reg_access, unsigned long value) |
| { |
| struct kvm_sbi_fwft *fwft = vcpu_to_fwft(vcpu); |
| unsigned long pmm; |
| |
| switch (value) { |
| case 0: |
| pmm = ENVCFG_PMM_PMLEN_0; |
| break; |
| case 7: |
| if (!fwft->have_vs_pmlen_7) |
| return SBI_ERR_INVALID_PARAM; |
| pmm = ENVCFG_PMM_PMLEN_7; |
| break; |
| case 16: |
| if (!fwft->have_vs_pmlen_16) |
| return SBI_ERR_INVALID_PARAM; |
| pmm = ENVCFG_PMM_PMLEN_16; |
| break; |
| default: |
| return SBI_ERR_INVALID_PARAM; |
| } |
| |
| vcpu->arch.cfg.henvcfg &= ~ENVCFG_PMM; |
| vcpu->arch.cfg.henvcfg |= pmm; |
| |
| /* |
| * Instead of waiting for vcpu_load/put() to update HENVCFG CSR, |
| * update here so that VCPU see's pointer masking mode change |
| * immediately. |
| */ |
| if (!one_reg_access) |
| csr_write(CSR_HENVCFG, vcpu->arch.cfg.henvcfg); |
| |
| return SBI_SUCCESS; |
| } |
| |
| static long kvm_sbi_fwft_get_pointer_masking_pmlen(struct kvm_vcpu *vcpu, |
| struct kvm_sbi_fwft_config *conf, |
| bool one_reg_access, unsigned long *value) |
| { |
| switch (vcpu->arch.cfg.henvcfg & ENVCFG_PMM) { |
| case ENVCFG_PMM_PMLEN_0: |
| *value = 0; |
| break; |
| case ENVCFG_PMM_PMLEN_7: |
| *value = 7; |
| break; |
| case ENVCFG_PMM_PMLEN_16: |
| *value = 16; |
| break; |
| default: |
| return SBI_ERR_FAILURE; |
| } |
| |
| return SBI_SUCCESS; |
| } |
| |
| #endif |
| |
| static const struct kvm_sbi_fwft_feature features[] = { |
| { |
| .id = SBI_FWFT_MISALIGNED_EXC_DELEG, |
| .first_reg_num = offsetof(struct kvm_riscv_sbi_fwft, misaligned_deleg.enable) / |
| sizeof(unsigned long), |
| .supported = kvm_sbi_fwft_misaligned_delegation_supported, |
| .reset = kvm_sbi_fwft_reset_misaligned_delegation, |
| .set = kvm_sbi_fwft_set_misaligned_delegation, |
| .get = kvm_sbi_fwft_get_misaligned_delegation, |
| }, |
| #ifndef CONFIG_32BIT |
| { |
| .id = SBI_FWFT_POINTER_MASKING_PMLEN, |
| .first_reg_num = offsetof(struct kvm_riscv_sbi_fwft, pointer_masking.enable) / |
| sizeof(unsigned long), |
| .supported = kvm_sbi_fwft_pointer_masking_pmlen_supported, |
| .reset = kvm_sbi_fwft_reset_pointer_masking_pmlen, |
| .set = kvm_sbi_fwft_set_pointer_masking_pmlen, |
| .get = kvm_sbi_fwft_get_pointer_masking_pmlen, |
| }, |
| #endif |
| }; |
| |
| static const struct kvm_sbi_fwft_feature *kvm_sbi_fwft_regnum_to_feature(unsigned long reg_num) |
| { |
| const struct kvm_sbi_fwft_feature *feature; |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(features); i++) { |
| feature = &features[i]; |
| if (feature->first_reg_num <= reg_num && reg_num < (feature->first_reg_num + 3)) |
| return feature; |
| } |
| |
| return NULL; |
| } |
| |
| static struct kvm_sbi_fwft_config * |
| kvm_sbi_fwft_get_config(struct kvm_vcpu *vcpu, enum sbi_fwft_feature_t feature) |
| { |
| int i; |
| struct kvm_sbi_fwft *fwft = vcpu_to_fwft(vcpu); |
| |
| for (i = 0; i < ARRAY_SIZE(features); i++) { |
| if (fwft->configs[i].feature->id == feature) |
| return &fwft->configs[i]; |
| } |
| |
| return NULL; |
| } |
| |
| static int kvm_fwft_get_feature(struct kvm_vcpu *vcpu, u32 feature, |
| struct kvm_sbi_fwft_config **conf) |
| { |
| struct kvm_sbi_fwft_config *tconf; |
| |
| tconf = kvm_sbi_fwft_get_config(vcpu, feature); |
| if (!tconf) { |
| if (kvm_fwft_is_defined_feature(feature)) |
| return SBI_ERR_NOT_SUPPORTED; |
| |
| return SBI_ERR_DENIED; |
| } |
| |
| if (!tconf->supported || !tconf->enabled) |
| return SBI_ERR_NOT_SUPPORTED; |
| |
| *conf = tconf; |
| |
| return SBI_SUCCESS; |
| } |
| |
| static int kvm_sbi_fwft_set(struct kvm_vcpu *vcpu, u32 feature, |
| unsigned long value, unsigned long flags) |
| { |
| int ret; |
| struct kvm_sbi_fwft_config *conf; |
| |
| ret = kvm_fwft_get_feature(vcpu, feature, &conf); |
| if (ret) |
| return ret; |
| |
| if ((flags & ~SBI_FWFT_SET_FLAG_LOCK) != 0) |
| return SBI_ERR_INVALID_PARAM; |
| |
| if (conf->flags & SBI_FWFT_SET_FLAG_LOCK) |
| return SBI_ERR_DENIED_LOCKED; |
| |
| conf->flags = flags; |
| |
| return conf->feature->set(vcpu, conf, false, value); |
| } |
| |
| static int kvm_sbi_fwft_get(struct kvm_vcpu *vcpu, unsigned long feature, |
| unsigned long *value) |
| { |
| int ret; |
| struct kvm_sbi_fwft_config *conf; |
| |
| ret = kvm_fwft_get_feature(vcpu, feature, &conf); |
| if (ret) |
| return ret; |
| |
| return conf->feature->get(vcpu, conf, false, value); |
| } |
| |
| static int kvm_sbi_ext_fwft_handler(struct kvm_vcpu *vcpu, struct kvm_run *run, |
| struct kvm_vcpu_sbi_return *retdata) |
| { |
| int ret; |
| struct kvm_cpu_context *cp = &vcpu->arch.guest_context; |
| unsigned long funcid = cp->a6; |
| |
| switch (funcid) { |
| case SBI_EXT_FWFT_SET: |
| ret = kvm_sbi_fwft_set(vcpu, cp->a0, cp->a1, cp->a2); |
| break; |
| case SBI_EXT_FWFT_GET: |
| ret = kvm_sbi_fwft_get(vcpu, cp->a0, &retdata->out_val); |
| break; |
| default: |
| ret = SBI_ERR_NOT_SUPPORTED; |
| break; |
| } |
| |
| retdata->err_val = ret; |
| |
| return 0; |
| } |
| |
| static int kvm_sbi_ext_fwft_init(struct kvm_vcpu *vcpu) |
| { |
| struct kvm_sbi_fwft *fwft = vcpu_to_fwft(vcpu); |
| const struct kvm_sbi_fwft_feature *feature; |
| struct kvm_sbi_fwft_config *conf; |
| int i; |
| |
| fwft->configs = kcalloc(ARRAY_SIZE(features), sizeof(struct kvm_sbi_fwft_config), |
| GFP_KERNEL); |
| if (!fwft->configs) |
| return -ENOMEM; |
| |
| for (i = 0; i < ARRAY_SIZE(features); i++) { |
| feature = &features[i]; |
| conf = &fwft->configs[i]; |
| if (feature->supported) |
| conf->supported = feature->supported(vcpu); |
| else |
| conf->supported = true; |
| |
| conf->enabled = conf->supported; |
| conf->feature = feature; |
| } |
| |
| return 0; |
| } |
| |
| static void kvm_sbi_ext_fwft_deinit(struct kvm_vcpu *vcpu) |
| { |
| struct kvm_sbi_fwft *fwft = vcpu_to_fwft(vcpu); |
| |
| kfree(fwft->configs); |
| } |
| |
| static void kvm_sbi_ext_fwft_reset(struct kvm_vcpu *vcpu) |
| { |
| struct kvm_sbi_fwft *fwft = vcpu_to_fwft(vcpu); |
| const struct kvm_sbi_fwft_feature *feature; |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(features); i++) { |
| fwft->configs[i].flags = 0; |
| feature = &features[i]; |
| if (feature->reset) |
| feature->reset(vcpu); |
| } |
| } |
| |
| static unsigned long kvm_sbi_ext_fwft_get_reg_count(struct kvm_vcpu *vcpu) |
| { |
| unsigned long max_reg_count = sizeof(struct kvm_riscv_sbi_fwft) / sizeof(unsigned long); |
| const struct kvm_sbi_fwft_feature *feature; |
| struct kvm_sbi_fwft_config *conf; |
| unsigned long reg, ret = 0; |
| |
| for (reg = 0; reg < max_reg_count; reg++) { |
| feature = kvm_sbi_fwft_regnum_to_feature(reg); |
| if (!feature) |
| continue; |
| |
| conf = kvm_sbi_fwft_get_config(vcpu, feature->id); |
| if (!conf || !conf->supported) |
| continue; |
| |
| ret++; |
| } |
| |
| return ret; |
| } |
| |
| static int kvm_sbi_ext_fwft_get_reg_id(struct kvm_vcpu *vcpu, int index, u64 *reg_id) |
| { |
| int reg, max_reg_count = sizeof(struct kvm_riscv_sbi_fwft) / sizeof(unsigned long); |
| const struct kvm_sbi_fwft_feature *feature; |
| struct kvm_sbi_fwft_config *conf; |
| int idx = 0; |
| |
| for (reg = 0; reg < max_reg_count; reg++) { |
| feature = kvm_sbi_fwft_regnum_to_feature(reg); |
| if (!feature) |
| continue; |
| |
| conf = kvm_sbi_fwft_get_config(vcpu, feature->id); |
| if (!conf || !conf->supported) |
| continue; |
| |
| if (index == idx) { |
| *reg_id = KVM_REG_RISCV | |
| (IS_ENABLED(CONFIG_32BIT) ? |
| KVM_REG_SIZE_U32 : KVM_REG_SIZE_U64) | |
| KVM_REG_RISCV_SBI_STATE | |
| KVM_REG_RISCV_SBI_FWFT | reg; |
| return 0; |
| } |
| |
| idx++; |
| } |
| |
| return -ENOENT; |
| } |
| |
| static int kvm_sbi_ext_fwft_get_reg(struct kvm_vcpu *vcpu, unsigned long reg_num, |
| unsigned long reg_size, void *reg_val) |
| { |
| const struct kvm_sbi_fwft_feature *feature; |
| struct kvm_sbi_fwft_config *conf; |
| unsigned long *value; |
| int ret = 0; |
| |
| if (reg_size != sizeof(unsigned long)) |
| return -EINVAL; |
| value = reg_val; |
| |
| feature = kvm_sbi_fwft_regnum_to_feature(reg_num); |
| if (!feature) |
| return -ENOENT; |
| |
| conf = kvm_sbi_fwft_get_config(vcpu, feature->id); |
| if (!conf || !conf->supported) |
| return -ENOENT; |
| |
| switch (reg_num - feature->first_reg_num) { |
| case 0: |
| *value = conf->enabled; |
| break; |
| case 1: |
| *value = conf->flags; |
| break; |
| case 2: |
| ret = conf->feature->get(vcpu, conf, true, value); |
| break; |
| default: |
| return -ENOENT; |
| } |
| |
| return sbi_err_map_linux_errno(ret); |
| } |
| |
| static int kvm_sbi_ext_fwft_set_reg(struct kvm_vcpu *vcpu, unsigned long reg_num, |
| unsigned long reg_size, const void *reg_val) |
| { |
| const struct kvm_sbi_fwft_feature *feature; |
| struct kvm_sbi_fwft_config *conf; |
| unsigned long value; |
| int ret = 0; |
| |
| if (reg_size != sizeof(unsigned long)) |
| return -EINVAL; |
| value = *(const unsigned long *)reg_val; |
| |
| feature = kvm_sbi_fwft_regnum_to_feature(reg_num); |
| if (!feature) |
| return -ENOENT; |
| |
| conf = kvm_sbi_fwft_get_config(vcpu, feature->id); |
| if (!conf || !conf->supported) |
| return -ENOENT; |
| |
| switch (reg_num - feature->first_reg_num) { |
| case 0: |
| switch (value) { |
| case 0: |
| conf->enabled = false; |
| break; |
| case 1: |
| conf->enabled = true; |
| break; |
| default: |
| return -EINVAL; |
| } |
| break; |
| case 1: |
| conf->flags = value & SBI_FWFT_SET_FLAG_LOCK; |
| break; |
| case 2: |
| ret = conf->feature->set(vcpu, conf, true, value); |
| break; |
| default: |
| return -ENOENT; |
| } |
| |
| return sbi_err_map_linux_errno(ret); |
| } |
| |
| const struct kvm_vcpu_sbi_extension vcpu_sbi_ext_fwft = { |
| .extid_start = SBI_EXT_FWFT, |
| .extid_end = SBI_EXT_FWFT, |
| .handler = kvm_sbi_ext_fwft_handler, |
| .init = kvm_sbi_ext_fwft_init, |
| .deinit = kvm_sbi_ext_fwft_deinit, |
| .reset = kvm_sbi_ext_fwft_reset, |
| .state_reg_subtype = KVM_REG_RISCV_SBI_FWFT, |
| .get_state_reg_count = kvm_sbi_ext_fwft_get_reg_count, |
| .get_state_reg_id = kvm_sbi_ext_fwft_get_reg_id, |
| .get_state_reg = kvm_sbi_ext_fwft_get_reg, |
| .set_state_reg = kvm_sbi_ext_fwft_set_reg, |
| }; |