| /* |
| * SPDX-License-Identifier: BSD-2-Clause |
| * |
| * Copyright (c) 2024 Rivos Inc. |
| * |
| * Authors: |
| * Clément Léger <cleger@rivosinc.com> |
| */ |
| |
| #include <sbi/sbi_console.h> |
| #include <sbi/sbi_bitmap.h> |
| #include <sbi/sbi_ecall_interface.h> |
| #include <sbi/sbi_error.h> |
| #include <sbi/sbi_hart.h> |
| #include <sbi/sbi_heap.h> |
| #include <sbi/sbi_scratch.h> |
| #include <sbi/sbi_string.h> |
| #include <sbi/sbi_types.h> |
| |
| #include <sbi/riscv_asm.h> |
| #include <sbi/riscv_encoding.h> |
| |
| /** Offset of pointer to FWFT HART state in scratch space */ |
| static unsigned long fwft_ptr_offset; |
| |
| #define fwft_get_hart_state_ptr(__scratch) \ |
| sbi_scratch_read_type((__scratch), void *, fwft_ptr_offset) |
| |
| #define fwft_thishart_state_ptr() \ |
| fwft_get_hart_state_ptr(sbi_scratch_thishart_ptr()) |
| |
| #define fwft_set_hart_state_ptr(__scratch, __phs) \ |
| sbi_scratch_write_type((__scratch), void *, fwft_ptr_offset, (__phs)) |
| |
| #define MIS_DELEG (1UL << CAUSE_MISALIGNED_LOAD | 1UL << CAUSE_MISALIGNED_STORE) |
| |
| struct fwft_config; |
| |
| struct fwft_feature { |
| enum sbi_fwft_feature_t id; |
| int (*supported)(struct fwft_config *conf); |
| int (*set)(struct fwft_config *conf, unsigned long value); |
| int (*get)(struct fwft_config *conf, unsigned long *value); |
| }; |
| |
| struct fwft_config { |
| const struct fwft_feature *feature; |
| unsigned long flags; |
| }; |
| |
| struct fwft_hart_state { |
| unsigned int config_count; |
| struct fwft_config configs[]; |
| }; |
| |
| static const unsigned long 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, |
| }; |
| |
| static bool fwft_is_defined_feature(enum sbi_fwft_feature_t feature) |
| { |
| int i; |
| |
| for (i = 0; i < array_size(fwft_defined_features); i++) { |
| if (fwft_defined_features[i] == feature) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static int fwft_misaligned_delegation_supported(struct fwft_config *conf) |
| { |
| if (!misa_extension('S')) |
| return SBI_ENOTSUPP; |
| |
| return SBI_OK; |
| } |
| |
| static int fwft_set_misaligned_delegation(struct fwft_config *conf, |
| unsigned long value) |
| { |
| if (value == 1) |
| csr_set(CSR_MEDELEG, MIS_DELEG); |
| else if (value == 0) |
| csr_clear(CSR_MEDELEG, MIS_DELEG); |
| else |
| return SBI_EINVAL; |
| |
| return SBI_OK; |
| } |
| |
| static int fwft_get_misaligned_delegation(struct fwft_config *conf, |
| unsigned long *value) |
| { |
| *value = (csr_read(CSR_MEDELEG) & MIS_DELEG) != 0; |
| |
| return SBI_OK; |
| } |
| |
| static int fwft_adue_supported(struct fwft_config *conf) |
| { |
| if (!sbi_hart_has_extension(sbi_scratch_thishart_ptr(), |
| SBI_HART_EXT_SVADU)) |
| return SBI_ENOTSUPP; |
| |
| return SBI_OK; |
| } |
| |
| static int fwft_set_adue(struct fwft_config *conf, unsigned long value) |
| { |
| if (value == 1) |
| #if __riscv_xlen == 32 |
| csr_set(CSR_MENVCFGH, ENVCFG_ADUE >> 32); |
| #else |
| csr_set(CSR_MENVCFG, ENVCFG_ADUE); |
| #endif |
| else if (value == 0) |
| #if __riscv_xlen == 32 |
| csr_clear(CSR_MENVCFGH, ENVCFG_ADUE >> 32); |
| #else |
| csr_clear(CSR_MENVCFG, ENVCFG_ADUE); |
| #endif |
| else |
| return SBI_EINVAL; |
| |
| return SBI_OK; |
| } |
| |
| static int fwft_get_adue(struct fwft_config *conf, unsigned long *value) |
| { |
| unsigned long cfg; |
| |
| #if __riscv_xlen == 32 |
| cfg = csr_read(CSR_MENVCFGH) & (ENVCFG_ADUE >> 32); |
| #else |
| cfg = csr_read(CSR_MENVCFG) & ENVCFG_ADUE; |
| #endif |
| *value = cfg != 0; |
| |
| return SBI_OK; |
| } |
| |
| static struct fwft_config* get_feature_config(enum sbi_fwft_feature_t feature) |
| { |
| int i; |
| struct fwft_hart_state *fhs = fwft_thishart_state_ptr(); |
| |
| if (feature & SBI_FWFT_GLOBAL_FEATURE_BIT) |
| return NULL; |
| |
| for (i = 0; i < fhs->config_count; i++){ |
| if (feature == fhs->configs[i].feature->id) |
| return &fhs->configs[i]; |
| } |
| |
| return NULL; |
| } |
| |
| static int fwft_get_feature(enum sbi_fwft_feature_t feature, |
| struct fwft_config **conf) |
| { |
| int ret; |
| struct fwft_config *tconf; |
| |
| tconf = get_feature_config(feature); |
| if (!tconf) { |
| if (fwft_is_defined_feature(feature)) |
| return SBI_ENOTSUPP; |
| |
| return SBI_EDENIED; |
| } |
| |
| if (tconf->feature->supported) { |
| ret = tconf->feature->supported(tconf); |
| if (ret) |
| return ret; |
| } |
| *conf = tconf; |
| |
| return SBI_SUCCESS; |
| } |
| |
| int sbi_fwft_set(enum sbi_fwft_feature_t feature, unsigned long value, |
| unsigned long flags) |
| { |
| int ret; |
| struct fwft_config *conf; |
| |
| ret = fwft_get_feature(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_EDENIED; |
| |
| ret = conf->feature->set(conf, value); |
| if (ret) |
| return ret; |
| |
| conf->flags = flags; |
| |
| return SBI_OK; |
| } |
| |
| int sbi_fwft_get(enum sbi_fwft_feature_t feature, unsigned long *out_val) |
| { |
| int ret; |
| struct fwft_config *conf; |
| |
| ret = fwft_get_feature(feature, &conf); |
| if (ret) |
| return ret; |
| |
| return conf->feature->get(conf, out_val); |
| } |
| |
| static const struct fwft_feature features[] = |
| { |
| { |
| .id = SBI_FWFT_MISALIGNED_EXC_DELEG, |
| .supported = fwft_misaligned_delegation_supported, |
| .set = fwft_set_misaligned_delegation, |
| .get = fwft_get_misaligned_delegation, |
| }, |
| { |
| .id = SBI_FWFT_PTE_AD_HW_UPDATING, |
| .supported = fwft_adue_supported, |
| .set = fwft_set_adue, |
| .get = fwft_get_adue, |
| }, |
| }; |
| |
| int sbi_fwft_init(struct sbi_scratch *scratch, bool cold_boot) |
| { |
| int i; |
| struct fwft_hart_state *fhs; |
| |
| if (cold_boot) { |
| fwft_ptr_offset = sbi_scratch_alloc_type_offset(void *); |
| if (!fwft_ptr_offset) |
| return SBI_ENOMEM; |
| } |
| |
| fhs = fwft_get_hart_state_ptr(scratch); |
| if (!fhs) { |
| fhs = sbi_zalloc(sizeof(fhs) + array_size(features) * sizeof(struct fwft_config)); |
| if (!fhs) |
| return SBI_ENOMEM; |
| |
| fhs->config_count = array_size(features); |
| for (i = 0; i < array_size(features); i++) |
| fhs->configs[i].feature = &features[i]; |
| |
| fwft_set_hart_state_ptr(scratch, fhs); |
| } |
| |
| return 0; |
| } |