Implement runtime big-endian data support by reading the MSTATUS
UBE/SBE/MBE bits to determine data endianness per privilege level.
The key changes are:
- Add riscv_cpu_data_is_big_endian() helper in cpu.h that checks
the appropriate MSTATUS endianness bit based on current privilege
level (MBE for M-mode, SBE for S-mode, UBE for U-mode).
- Update mo_endian() in translate.c to return MO_BE or MO_LE based
on a new 'big_endian' field in DisasContext, rather than the
previous hardcoded MO_TE.
- Update mo_endian_env() in op_helper.c to call the new helper,
giving hypervisor load/store helpers correct runtime endianness.
- Pack the endianness flag into cs_base bit 32 (alongside misa_ext
in bits 0-25) in riscv_get_tb_cpu_state(), ensuring translation
blocks are correctly separated by data endianness.
Note: instruction fetches continue to use MO_LE unconditionally
(from the previous patch), as RISC-V instructions are always
little-endian per the ISA specification.
Signed-off-by: Djordje Todorovic <djordje.todorovic@htecgroup.com>
---
target/riscv/cpu.h | 28 ++++++++++++++++++++++++++++
target/riscv/internals.h | 9 +--------
target/riscv/tcg/tcg-cpu.c | 9 ++++++++-
target/riscv/translate.c | 12 ++++--------
4 files changed, 41 insertions(+), 17 deletions(-)
diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
index 35d1f6362c..ef870d05b3 100644
--- a/target/riscv/cpu.h
+++ b/target/riscv/cpu.h
@@ -703,6 +703,12 @@ FIELD(TB_FLAGS, BCFI_ENABLED, 28, 1)
FIELD(TB_FLAGS, PM_PMM, 29, 2)
FIELD(TB_FLAGS, PM_SIGNEXTEND, 31, 1)
+/*
+ * cs_base carries misa_ext (bits 0-25) plus additional flags.
+ * Bit 32 is used for data endianness since TB_FLAGS has no free bits.
+ */
+#define TB_CSBASE_BIG_ENDIAN (1ULL << 32)
+
#ifdef TARGET_RISCV32
#define riscv_cpu_mxl(env) ((void)(env), MXL_RV32)
#else
@@ -718,6 +724,28 @@ static inline const RISCVCPUConfig *riscv_cpu_cfg(CPURISCVState *env)
return &env_archcpu(env)->cfg;
}
+/*
+ * Return true if data accesses are big-endian for the current privilege
+ * level, based on the MSTATUS MBE/SBE/UBE bits.
+ */
+static inline bool riscv_cpu_data_is_big_endian(CPURISCVState *env)
+{
+#if defined(CONFIG_USER_ONLY)
+ return false;
+#else
+ switch (env->priv) {
+ case PRV_M:
+ return env->mstatus & MSTATUS_MBE;
+ case PRV_S:
+ return env->mstatus & MSTATUS_SBE;
+ case PRV_U:
+ return env->mstatus & MSTATUS_UBE;
+ default:
+ g_assert_not_reached();
+ }
+#endif
+}
+
#if !defined(CONFIG_USER_ONLY)
static inline int cpu_address_mode(CPURISCVState *env)
{
diff --git a/target/riscv/internals.h b/target/riscv/internals.h
index 460346dd6d..e2f0334da8 100644
--- a/target/riscv/internals.h
+++ b/target/riscv/internals.h
@@ -64,14 +64,7 @@ static inline bool mmuidx_2stage(int mmu_idx)
static inline MemOp mo_endian_env(CPURISCVState *env)
{
- /*
- * A couple of bits in MSTATUS set the endianness:
- * - MSTATUS_UBE (User-mode),
- * - MSTATUS_SBE (Supervisor-mode),
- * - MSTATUS_MBE (Machine-mode)
- * but we don't implement that yet.
- */
- return MO_LE;
+ return riscv_cpu_data_is_big_endian(env) ? MO_BE : MO_LE;
}
/* share data between vector helpers and decode code */
diff --git a/target/riscv/tcg/tcg-cpu.c b/target/riscv/tcg/tcg-cpu.c
index 3407191c22..fa42197e98 100644
--- a/target/riscv/tcg/tcg-cpu.c
+++ b/target/riscv/tcg/tcg-cpu.c
@@ -189,10 +189,17 @@ static TCGTBCPUState riscv_get_tb_cpu_state(CPUState *cs)
flags = FIELD_DP32(flags, TB_FLAGS, PM_PMM, riscv_pm_get_pmm(env));
flags = FIELD_DP32(flags, TB_FLAGS, PM_SIGNEXTEND, pm_signext);
+ uint64_t cs_base = env->misa_ext;
+#ifndef CONFIG_USER_ONLY
+ if (riscv_cpu_data_is_big_endian(env)) {
+ cs_base |= TB_CSBASE_BIG_ENDIAN;
+ }
+#endif
+
return (TCGTBCPUState){
.pc = env->xl == MXL_RV32 ? env->pc & UINT32_MAX : env->pc,
.flags = flags,
- .cs_base = env->misa_ext,
+ .cs_base = cs_base,
};
}
diff --git a/target/riscv/translate.c b/target/riscv/translate.c
index 5df5b73849..d7f1f8e466 100644
--- a/target/riscv/translate.c
+++ b/target/riscv/translate.c
@@ -119,6 +119,8 @@ typedef struct DisasContext {
bool fcfi_lp_expected;
/* zicfiss extension, if shadow stack was enabled during TB gen */
bool bcfi_enabled;
+ /* Data endianness from MSTATUS UBE/SBE/MBE */
+ bool big_endian;
} DisasContext;
static inline bool has_ext(DisasContext *ctx, uint32_t ext)
@@ -128,14 +130,7 @@ static inline bool has_ext(DisasContext *ctx, uint32_t ext)
static inline MemOp mo_endian(DisasContext *ctx)
{
- /*
- * A couple of bits in MSTATUS set the endianness:
- * - MSTATUS_UBE (User-mode),
- * - MSTATUS_SBE (Supervisor-mode),
- * - MSTATUS_MBE (Machine-mode)
- * but we don't implement that yet.
- */
- return MO_LE;
+ return ctx->big_endian ? MO_BE : MO_LE;
}
#ifdef TARGET_RISCV32
@@ -1346,6 +1341,7 @@ static void riscv_tr_init_disas_context(DisasContextBase *dcbase, CPUState *cs)
ctx->zero = tcg_constant_tl(0);
ctx->virt_inst_excp = false;
ctx->decoders = cpu->decoders;
+ ctx->big_endian = ctx->base.tb->cs_base & TB_CSBASE_BIG_ENDIAN;
}
static void riscv_tr_tb_start(DisasContextBase *db, CPUState *cpu)
--
2.34.1
I replied to v4 before I saw a v5 was already posted:
https://lore.kernel.org/qemu-devel/d974df44-b46e-4e51-9eec-b46e391c3462@linaro.org/
On 3/25/26 02:40, Djordje Todorovic wrote:
> Implement runtime big-endian data support by reading the MSTATUS
> UBE/SBE/MBE bits to determine data endianness per privilege level.
>
> The key changes are:
>
> - Add riscv_cpu_data_is_big_endian() helper in cpu.h that checks
> the appropriate MSTATUS endianness bit based on current privilege
> level (MBE for M-mode, SBE for S-mode, UBE for U-mode).
>
> - Update mo_endian() in translate.c to return MO_BE or MO_LE based
> on a new 'big_endian' field in DisasContext, rather than the
> previous hardcoded MO_TE.
>
> - Update mo_endian_env() in op_helper.c to call the new helper,
> giving hypervisor load/store helpers correct runtime endianness.
>
> - Pack the endianness flag into cs_base bit 32 (alongside misa_ext
> in bits 0-25) in riscv_get_tb_cpu_state(), ensuring translation
> blocks are correctly separated by data endianness.
>
> Note: instruction fetches continue to use MO_LE unconditionally
> (from the previous patch), as RISC-V instructions are always
> little-endian per the ISA specification.
>
> Signed-off-by: Djordje Todorovic <djordje.todorovic@htecgroup.com>
> ---
> target/riscv/cpu.h | 28 ++++++++++++++++++++++++++++
> target/riscv/internals.h | 9 +--------
> target/riscv/tcg/tcg-cpu.c | 9 ++++++++-
> target/riscv/translate.c | 12 ++++--------
> 4 files changed, 41 insertions(+), 17 deletions(-)
>
> diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
> index 35d1f6362c..ef870d05b3 100644
> --- a/target/riscv/cpu.h
> +++ b/target/riscv/cpu.h
> @@ -703,6 +703,12 @@ FIELD(TB_FLAGS, BCFI_ENABLED, 28, 1)
> FIELD(TB_FLAGS, PM_PMM, 29, 2)
> FIELD(TB_FLAGS, PM_SIGNEXTEND, 31, 1)
>
> +/*
> + * cs_base carries misa_ext (bits 0-25) plus additional flags.
> + * Bit 32 is used for data endianness since TB_FLAGS has no free bits.
> + */
> +#define TB_CSBASE_BIG_ENDIAN (1ULL << 32)
> +
> #ifdef TARGET_RISCV32
> #define riscv_cpu_mxl(env) ((void)(env), MXL_RV32)
> #else
> @@ -718,6 +724,28 @@ static inline const RISCVCPUConfig *riscv_cpu_cfg(CPURISCVState *env)
> return &env_archcpu(env)->cfg;
> }
>
> +/*
> + * Return true if data accesses are big-endian for the current privilege
> + * level, based on the MSTATUS MBE/SBE/UBE bits.
> + */
> +static inline bool riscv_cpu_data_is_big_endian(CPURISCVState *env)
> +{
> +#if defined(CONFIG_USER_ONLY)
> + return false;
> +#else
> + switch (env->priv) {
> + case PRV_M:
> + return env->mstatus & MSTATUS_MBE;
> + case PRV_S:
> + return env->mstatus & MSTATUS_SBE;
> + case PRV_U:
> + return env->mstatus & MSTATUS_UBE;
> + default:
> + g_assert_not_reached();
> + }
> +#endif
> +}
> +
> #if !defined(CONFIG_USER_ONLY)
> static inline int cpu_address_mode(CPURISCVState *env)
> {
> diff --git a/target/riscv/internals.h b/target/riscv/internals.h
> index 460346dd6d..e2f0334da8 100644
> --- a/target/riscv/internals.h
> +++ b/target/riscv/internals.h
> @@ -64,14 +64,7 @@ static inline bool mmuidx_2stage(int mmu_idx)
>
> static inline MemOp mo_endian_env(CPURISCVState *env)
> {
> - /*
> - * A couple of bits in MSTATUS set the endianness:
> - * - MSTATUS_UBE (User-mode),
> - * - MSTATUS_SBE (Supervisor-mode),
> - * - MSTATUS_MBE (Machine-mode)
> - * but we don't implement that yet.
> - */
> - return MO_LE;
> + return riscv_cpu_data_is_big_endian(env) ? MO_BE : MO_LE;
> }
>
> /* share data between vector helpers and decode code */
> diff --git a/target/riscv/tcg/tcg-cpu.c b/target/riscv/tcg/tcg-cpu.c
> index 3407191c22..fa42197e98 100644
> --- a/target/riscv/tcg/tcg-cpu.c
> +++ b/target/riscv/tcg/tcg-cpu.c
> @@ -189,10 +189,17 @@ static TCGTBCPUState riscv_get_tb_cpu_state(CPUState *cs)
> flags = FIELD_DP32(flags, TB_FLAGS, PM_PMM, riscv_pm_get_pmm(env));
> flags = FIELD_DP32(flags, TB_FLAGS, PM_SIGNEXTEND, pm_signext);
>
> + uint64_t cs_base = env->misa_ext;
> +#ifndef CONFIG_USER_ONLY
> + if (riscv_cpu_data_is_big_endian(env)) {
> + cs_base |= TB_CSBASE_BIG_ENDIAN;
> + }
> +#endif
> +
> return (TCGTBCPUState){
> .pc = env->xl == MXL_RV32 ? env->pc & UINT32_MAX : env->pc,
> .flags = flags,
> - .cs_base = env->misa_ext,
> + .cs_base = cs_base,
> };
> }
>
> diff --git a/target/riscv/translate.c b/target/riscv/translate.c
> index 5df5b73849..d7f1f8e466 100644
> --- a/target/riscv/translate.c
> +++ b/target/riscv/translate.c
> @@ -119,6 +119,8 @@ typedef struct DisasContext {
> bool fcfi_lp_expected;
> /* zicfiss extension, if shadow stack was enabled during TB gen */
> bool bcfi_enabled;
> + /* Data endianness from MSTATUS UBE/SBE/MBE */
> + bool big_endian;
> } DisasContext;
>
> static inline bool has_ext(DisasContext *ctx, uint32_t ext)
> @@ -128,14 +130,7 @@ static inline bool has_ext(DisasContext *ctx, uint32_t ext)
>
> static inline MemOp mo_endian(DisasContext *ctx)
> {
> - /*
> - * A couple of bits in MSTATUS set the endianness:
> - * - MSTATUS_UBE (User-mode),
> - * - MSTATUS_SBE (Supervisor-mode),
> - * - MSTATUS_MBE (Machine-mode)
> - * but we don't implement that yet.
> - */
> - return MO_LE;
> + return ctx->big_endian ? MO_BE : MO_LE;
> }
>
> #ifdef TARGET_RISCV32
> @@ -1346,6 +1341,7 @@ static void riscv_tr_init_disas_context(DisasContextBase *dcbase, CPUState *cs)
> ctx->zero = tcg_constant_tl(0);
> ctx->virt_inst_excp = false;
> ctx->decoders = cpu->decoders;
> + ctx->big_endian = ctx->base.tb->cs_base & TB_CSBASE_BIG_ENDIAN;
> }
>
> static void riscv_tr_tb_start(DisasContextBase *db, CPUState *cpu)
© 2016 - 2026 Red Hat, Inc.