Files
opensbi/lib/sbi/sbi_fwft.c
T
Andrew Jones 5f13f9e13f lib: sbi: Fix hw a/d updating defaults
The Svade dt-binding description states that Svadu should only
be enabled at boot time when only Svadu is present in the DT.
Ensure that's the case. Also, when only Svadu is supported,
disable FWFT.PTE_AD_HW_UPDATING, as we need both to support
toggling.

Signed-off-by: Andrew Jones <andrew.jones@oss.qualcomm.com>
Reviewed-by: Radim Krčmář <radim.krcmar@oss.qualcomm.com>
Link: https://lore.kernel.org/r/20260401220845.190680-1-andrew.jones@oss.qualcomm.com
Signed-off-by: Anup Patel <anup@brainfault.org>
2026-05-11 19:16:20 +05:30

453 lines
9.6 KiB
C

/*
* 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_hfence.h>
#include <sbi/sbi_scratch.h>
#include <sbi/sbi_string.h>
#include <sbi/sbi_tlb.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,
SBI_FWFT_POINTER_MASKING_PMLEN,
};
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_menvcfg_set_bit(unsigned long value, unsigned long bit)
{
if (value == 1) {
if (bit >= 32 && __riscv_xlen == 32)
csr_set(CSR_MENVCFGH, _ULL(1) << (bit - 32));
else
csr_set(CSR_MENVCFG, _ULL(1) << bit);
} else if (value == 0) {
if (bit >= 32 && __riscv_xlen == 32)
csr_clear(CSR_MENVCFGH, _ULL(1) << (bit - 32));
else
csr_clear(CSR_MENVCFG, _ULL(1) << bit);
} else {
return SBI_EINVAL;
}
return SBI_OK;
}
static int fwft_menvcfg_read_bit(unsigned long *value, unsigned long bit)
{
unsigned long cfg;
if (bit >= 32 && __riscv_xlen == 32)
cfg = csr_read(CSR_MENVCFGH) & (_ULL(1) << (bit - 32));
else
cfg = csr_read(CSR_MENVCFG) & (_ULL(1) << bit);
*value = cfg != 0;
return SBI_OK;
}
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_double_trap_supported(struct fwft_config *conf)
{
if (!sbi_hart_has_extension(sbi_scratch_thishart_ptr(),
SBI_HART_EXT_SSDBLTRP))
return SBI_ENOTSUPP;
return SBI_OK;
}
static int fwft_set_double_trap(struct fwft_config *conf, unsigned long value)
{
return fwft_menvcfg_set_bit(value, ENVCFG_DTE_SHIFT);
}
static int fwft_get_double_trap(struct fwft_config *conf, unsigned long *value)
{
return fwft_menvcfg_read_bit(value, ENVCFG_DTE_SHIFT);
}
static int fwft_adue_supported(struct fwft_config *conf)
{
/*
* FWFT.PTE_AD_HW_UPDATING is only supported when both Svade and Svadu
* are supported. We need both in order to support toggling and to
* ensure the reset value of zero is valid (it wouldn't be when only
* Svadu is supported).
*/
if (!sbi_hart_has_extension(sbi_scratch_thishart_ptr(),
SBI_HART_EXT_SVADU) ||
!sbi_hart_has_extension(sbi_scratch_thishart_ptr(),
SBI_HART_EXT_SVADE))
return SBI_ENOTSUPP;
return SBI_OK;
}
static int fwft_set_adue(struct fwft_config *conf, unsigned long value)
{
int res = fwft_menvcfg_set_bit(value, ENVCFG_ADUE_SHIFT);
if (res == SBI_OK) {
__sbi_sfence_vma_all();
if (misa_extension('H'))
__sbi_hfence_gvma_all();
}
return res;
}
static int fwft_get_adue(struct fwft_config *conf, unsigned long *value)
{
return fwft_menvcfg_read_bit(value, ENVCFG_ADUE_SHIFT);
}
static int fwft_lpad_supported(struct fwft_config *conf)
{
if (!sbi_hart_has_extension(sbi_scratch_thishart_ptr(),
SBI_HART_EXT_ZICFILP))
return SBI_ENOTSUPP;
return SBI_OK;
}
static int fwft_enable_lpad(struct fwft_config *conf, unsigned long value)
{
return fwft_menvcfg_set_bit(value, ENVCFG_LPE_SHIFT);
}
static int fwft_get_lpad(struct fwft_config *conf, unsigned long *value)
{
return fwft_menvcfg_read_bit(value, ENVCFG_LPE_SHIFT);
}
static int fwft_sstack_supported(struct fwft_config *conf)
{
if (!sbi_hart_has_extension(sbi_scratch_thishart_ptr(),
SBI_HART_EXT_ZICFISS))
return SBI_ENOTSUPP;
return SBI_OK;
}
static int fwft_enable_sstack(struct fwft_config *conf, unsigned long value)
{
return fwft_menvcfg_set_bit(value, ENVCFG_SSE_SHIFT);
}
static int fwft_get_sstack(struct fwft_config *conf, unsigned long *value)
{
return fwft_menvcfg_read_bit(value, ENVCFG_SSE_SHIFT);
}
#if __riscv_xlen > 32
static int fwft_pmlen_supported(struct fwft_config *conf)
{
if (!sbi_hart_has_extension(sbi_scratch_thishart_ptr(),
SBI_HART_EXT_SMNPM))
return SBI_ENOTSUPP;
return SBI_OK;
}
static int fwft_set_pmlen(struct fwft_config *conf, unsigned long value)
{
unsigned long pmm, prev;
switch (value) {
case 0:
pmm = ENVCFG_PMM_PMLEN_0;
break;
case 7:
pmm = ENVCFG_PMM_PMLEN_7;
break;
case 16:
pmm = ENVCFG_PMM_PMLEN_16;
break;
default:
return SBI_EINVAL;
}
prev = csr_read_clear(CSR_MENVCFG, ENVCFG_PMM);
csr_set(CSR_MENVCFG, pmm);
if ((csr_read(CSR_MENVCFG) & ENVCFG_PMM) != pmm) {
csr_write(CSR_MENVCFG, prev);
return SBI_EINVAL;
}
return SBI_OK;
}
static int fwft_get_pmlen(struct fwft_config *conf, unsigned long *value)
{
switch (csr_read(CSR_MENVCFG) & 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_EFAIL;
}
return SBI_OK;
}
#endif
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;
}
static void fwft_clear_config_lock(enum sbi_fwft_feature_t feature)
{
int ret;
struct fwft_config *conf;
ret = fwft_get_feature(feature, &conf);
if (ret)
return;
conf->flags &= ~SBI_FWFT_SET_FLAG_LOCK;
}
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_EINVAL;
if (conf->flags & SBI_FWFT_SET_FLAG_LOCK)
return SBI_EDENIED_LOCKED;
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_LANDING_PAD,
.supported = fwft_lpad_supported,
.set = fwft_enable_lpad,
.get = fwft_get_lpad,
},
{
.id = SBI_FWFT_SHADOW_STACK,
.supported = fwft_sstack_supported,
.set = fwft_enable_sstack,
.get = fwft_get_sstack,
},
{
.id = SBI_FWFT_DOUBLE_TRAP,
.supported = fwft_double_trap_supported,
.set = fwft_set_double_trap,
.get = fwft_get_double_trap,
},
{
.id = SBI_FWFT_PTE_AD_HW_UPDATING,
.supported = fwft_adue_supported,
.set = fwft_set_adue,
.get = fwft_get_adue,
},
#if __riscv_xlen > 32
{
.id = SBI_FWFT_POINTER_MASKING_PMLEN,
.supported = fwft_pmlen_supported,
.set = fwft_set_pmlen,
.get = fwft_get_pmlen,
},
#endif
};
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);
}
for (i = 0; i < array_size(features); i++)
fwft_clear_config_lock(features[i].id);
return 0;
}