Commit 5089493e45 for qemu.org
commit 5089493e45d3be2bae9ffd2a22186f57b0cabe3f
Author: wangyang <wangyang25@otcaix.iscas.ac.cn>
Date: Fri Sep 11 09:54:58 2026 +0800
linux-user/riscv: invalidate reservations after stores
In linux-user parallel execution, AMO and ordinary integer stores do not
invalidate load reservations held by another hart. A store that preserves
the numeric value can therefore leave the reservation intact and allow a
later SC to succeed.
Use the existing EXCP_ATOMIC path to re-execute LR, SC, AMO, and ordinary
integer store instructions in a serial context. The completed store and
reservation invalidation then cannot be interleaved with another guest
hart. Track the LR access size and invalidate every other-hart reservation
whose byte range overlaps the completed store. A successful SC uses the
same invalidation helper.
Clear the reservation when cloning a new linux-user RISC-V hart so that a
child cannot inherit the parent's reservation state.
The change is limited to linux-user TCG and the base scalar integer/
A-extension translator paths.
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/4149
Signed-off-by: wangyang <wangyang25@otcaix.iscas.ac.cn>
Signed-off-by: Helge Deller <deller@gmx.de>
diff --git a/linux-user/riscv/target_cpu.h b/linux-user/riscv/target_cpu.h
index 9c642367a3..eff18b37ba 100644
--- a/linux-user/riscv/target_cpu.h
+++ b/linux-user/riscv/target_cpu.h
@@ -9,6 +9,8 @@ static inline void cpu_clone_regs_child(CPURISCVState *env, target_ulong newsp,
}
env->gpr[xA0] = 0;
+ env->load_res = -1;
+ env->load_res_size = 0;
}
static inline void cpu_clone_regs_parent(CPURISCVState *env, unsigned flags)
diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c
index 5fff9d745e..da4c1a090b 100644
--- a/target/riscv/cpu.c
+++ b/target/riscv/cpu.c
@@ -1084,6 +1084,9 @@ static void riscv_cpu_reset_hold(Object *obj, ResetType type)
env->xl = riscv_cpu_mxl(env);
cs->exception_index = RISCV_EXCP_NONE;
env->load_res = -1;
+#ifdef CONFIG_USER_ONLY
+ env->load_res_size = 0;
+#endif
set_default_nan_mode(1, &env->fp_status);
/* Default NaN value: sign bit clear, frac msb set */
set_float_default_nan_pattern(0b01000000, &env->fp_status);
diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
index c2138dbd4b..8c8ffe94cd 100644
--- a/target/riscv/cpu.h
+++ b/target/riscv/cpu.h
@@ -264,6 +264,9 @@ struct CPUArchState {
uint64_t pc;
uint64_t load_res;
+#ifdef CONFIG_USER_ONLY
+ uint64_t load_res_size;
+#endif
uint64_t load_val;
/* Floating-Point state */
diff --git a/target/riscv/helper.h b/target/riscv/helper.h
index 4fc2d3a155..aab92bf132 100644
--- a/target/riscv/helper.h
+++ b/target/riscv/helper.h
@@ -1357,3 +1357,8 @@ DEF_HELPER_1(ssamoswap_disabled, void, env)
/* Zalrsc SC write probe */
DEF_HELPER_FLAGS_3(sc_probe_write, TCG_CALL_NO_WG, void, env, tl, tl)
+
+#ifdef CONFIG_USER_ONLY
+/* Invalidate reservations overlapping a completed linux-user store. */
+DEF_HELPER_3(riscv_invalidate_reservations, void, env, tl, tl)
+#endif
diff --git a/target/riscv/tcg/insn_trans/trans_rva.c.inc b/target/riscv/tcg/insn_trans/trans_rva.c.inc
index 44c1696fe4..248a8c60d7 100644
--- a/target/riscv/tcg/insn_trans/trans_rva.c.inc
+++ b/target/riscv/tcg/insn_trans/trans_rva.c.inc
@@ -32,6 +32,11 @@
static bool gen_lr(DisasContext *ctx, arg_atomic *a, MemOp mop)
{
+#ifdef CONFIG_USER_ONLY
+ if (!gen_riscv_reservation_serialize(ctx)) {
+ return false;
+ }
+#endif
TCGv src1;
mop |= MO_ALIGN;
@@ -53,6 +58,9 @@ static bool gen_lr(DisasContext *ctx, arg_atomic *a, MemOp mop)
/* Put addr in load_res, data in load_val. */
tcg_gen_mov_tl(load_res, src1);
+#ifdef CONFIG_USER_ONLY
+ tcg_gen_movi_tl(load_res_size, memop_size(mop));
+#endif
gen_set_gpr(ctx, a->rd, load_val);
return true;
@@ -60,9 +68,17 @@ static bool gen_lr(DisasContext *ctx, arg_atomic *a, MemOp mop)
static bool gen_sc(DisasContext *ctx, arg_atomic *a, MemOp mop)
{
+#ifdef CONFIG_USER_ONLY
+ if (!gen_riscv_reservation_serialize(ctx)) {
+ return false;
+ }
+#endif
TCGv dest, src1, src2;
TCGLabel *l1 = gen_new_label();
TCGLabel *l2 = gen_new_label();
+#ifdef CONFIG_USER_ONLY
+ TCGLabel *l3 = gen_new_label();
+#endif
mop |= MO_ALIGN;
mop |= ctx->mo_endianness;
@@ -79,6 +95,11 @@ static bool gen_sc(DisasContext *ctx, arg_atomic *a, MemOp mop)
src2 = get_gpr(ctx, a->rs2, EXT_NONE);
tcg_gen_atomic_cmpxchg_tl(dest, load_res, load_val, src2,
ctx->mem_idx, mop);
+#ifdef CONFIG_USER_ONLY
+ tcg_gen_brcond_tl(TCG_COND_NE, dest, load_val, l3);
+ gen_riscv_invalidate_reservations(src1, mop);
+ gen_set_label(l3);
+#endif
tcg_gen_setcond_tl(TCG_COND_NE, dest, dest, load_val);
gen_set_gpr(ctx, a->rd, dest);
tcg_gen_br(l2);
@@ -104,6 +125,9 @@ static bool gen_sc(DisasContext *ctx, arg_atomic *a, MemOp mop)
* an SC to any address, in between an LR and SC pair.
*/
tcg_gen_movi_tl(load_res, -1);
+#ifdef CONFIG_USER_ONLY
+ tcg_gen_movi_tl(load_res_size, 0);
+#endif
return true;
}
diff --git a/target/riscv/tcg/insn_trans/trans_rvi.c.inc b/target/riscv/tcg/insn_trans/trans_rvi.c.inc
index cc1b5dbbad..40b4c9aa4a 100644
--- a/target/riscv/tcg/insn_trans/trans_rvi.c.inc
+++ b/target/riscv/tcg/insn_trans/trans_rvi.c.inc
@@ -489,6 +489,9 @@ static bool gen_store_tl(DisasContext *ctx, arg_sb *a, MemOp memop)
}
tcg_gen_qemu_st_tl(data, addr, ctx->mem_idx, memop);
+#ifdef CONFIG_USER_ONLY
+ gen_riscv_invalidate_reservations(addr, memop);
+#endif
return true;
}
@@ -518,11 +521,19 @@ static bool gen_store_i128(DisasContext *ctx, arg_sb *a, MemOp memop)
}
tcg_gen_qemu_st_i128(t16, addrl, ctx->mem_idx, memop);
}
+#ifdef CONFIG_USER_ONLY
+ gen_riscv_invalidate_reservations(addrl, memop);
+#endif
return true;
}
static bool gen_store(DisasContext *ctx, arg_sb *a, MemOp memop)
{
+#ifdef CONFIG_USER_ONLY
+ if (!gen_riscv_reservation_serialize(ctx)) {
+ return false;
+ }
+#endif
memop |= ctx->mo_endianness;
if (ctx->cfg_ptr->ext_zama16b) {
memop |= MO_ATOM_WITHIN16;
diff --git a/target/riscv/tcg/insn_trans/trans_rvzawrs.c.inc b/target/riscv/tcg/insn_trans/trans_rvzawrs.c.inc
index 0eef033838..1831bbcfc0 100644
--- a/target/riscv/tcg/insn_trans/trans_rvzawrs.c.inc
+++ b/target/riscv/tcg/insn_trans/trans_rvzawrs.c.inc
@@ -32,6 +32,9 @@ static bool trans_wrs_sto(DisasContext *ctx, arg_wrs_sto *a)
/* Clear the load reservation (if any). */
tcg_gen_movi_tl(load_res, -1);
+#ifdef CONFIG_USER_ONLY
+ tcg_gen_movi_tl(load_res_size, 0);
+#endif
gen_update_pc(ctx, ctx->cur_insn_len);
tcg_gen_exit_tb(NULL, 0);
diff --git a/target/riscv/tcg/op_helper.c b/target/riscv/tcg/op_helper.c
index 3e94005d2b..b67c7718db 100644
--- a/target/riscv/tcg/op_helper.c
+++ b/target/riscv/tcg/op_helper.c
@@ -19,6 +19,9 @@
*/
#include "qemu/osdep.h"
+#ifdef CONFIG_USER_ONLY
+#include "qemu/rcu.h"
+#endif
#include "cpu.h"
#include "target/riscv/tcg/csr.h"
#ifndef CONFIG_USER_ONLY
@@ -298,6 +301,38 @@ void helper_sc_probe_write(CPURISCVState *env, target_ulong addr,
probe_write(env, addr, size, mmu_idx, ra);
}
+#ifdef CONFIG_USER_ONLY
+
+void helper_riscv_invalidate_reservations(CPURISCVState *env,
+ target_ulong addr,
+ target_ulong size)
+{
+ CPUState *cpu;
+
+ /* EXCP_ATOMIC keeps other guest harts out while this list is updated. */
+ WITH_RCU_READ_LOCK_GUARD() {
+ CPU_FOREACH(cpu) {
+ CPURISCVState *other_env = cpu_env(cpu);
+ target_ulong reservation = other_env->load_res;
+ target_ulong reservation_size = other_env->load_res_size;
+ bool overlap;
+
+ if (other_env == env || reservation == (target_ulong)-1) {
+ continue;
+ }
+ overlap = reservation < addr
+ ? addr - reservation < reservation_size
+ : reservation - addr < size;
+ if (overlap) {
+ other_env->load_res = -1;
+ other_env->load_res_size = 0;
+ }
+ }
+ }
+}
+
+#endif
+
#ifndef CONFIG_USER_ONLY
target_ulong helper_sret(CPURISCVState *env)
diff --git a/target/riscv/tcg/translate.c b/target/riscv/tcg/translate.c
index 9684dbe752..cce3b6dc72 100644
--- a/target/riscv/tcg/translate.c
+++ b/target/riscv/tcg/translate.c
@@ -42,6 +42,9 @@ static TCGv cpu_gpr[32], cpu_gprh[32], cpu_pc;
static TCGv_i64 cpu_fpr[32]; /* assume F and D extensions */
static TCGv_i32 cpu_vl, cpu_vstart;
static TCGv load_res;
+#ifdef CONFIG_USER_ONLY
+static TCGv load_res_size;
+#endif
static TCGv load_val;
/*
@@ -1141,10 +1144,34 @@ static bool gen_unary_per_ol(DisasContext *ctx, arg_r2 *a, DisasExtend ext,
return gen_unary(ctx, a, ext, f_tl);
}
+#ifdef CONFIG_USER_ONLY
+static bool gen_riscv_reservation_serialize(DisasContext *ctx)
+{
+ /* Keep the memory operation and reservation update in one guest step. */
+ if (tb_cflags(ctx->base.tb) & CF_PARALLEL) {
+ gen_helper_exit_atomic(tcg_env);
+ ctx->base.is_jmp = DISAS_NORETURN;
+ return false;
+ }
+ return true;
+}
+
+static void gen_riscv_invalidate_reservations(TCGv addr, MemOp mop)
+{
+ gen_helper_riscv_invalidate_reservations(tcg_env, addr,
+ tcg_constant_tl(memop_size(mop)));
+}
+#endif
+
static bool gen_amo(DisasContext *ctx, arg_atomic *a,
void(*func)(TCGv, TCGv, TCGv, TCGArg, MemOp),
MemOp mop)
{
+#ifdef CONFIG_USER_ONLY
+ if (!gen_riscv_reservation_serialize(ctx)) {
+ return false;
+ }
+#endif
TCGv dest = dest_gpr(ctx, a->rd);
TCGv src1, src2 = get_gpr(ctx, a->rs2, EXT_NONE);
MemOp size = mop & MO_SIZE;
@@ -1159,6 +1186,9 @@ static bool gen_amo(DisasContext *ctx, arg_atomic *a,
decode_save_opc(ctx, RISCV_UW2_ALWAYS_STORE_AMO);
src1 = get_address(ctx, a->rs1, 0);
func(dest, src1, src2, ctx->mem_idx, mop);
+#ifdef CONFIG_USER_ONLY
+ gen_riscv_invalidate_reservations(src1, mop);
+#endif
gen_set_gpr(ctx, a->rd, dest);
return true;
@@ -1482,6 +1512,10 @@ void riscv_translate_init(void)
size_t pc_offset = offsetof(CPURISCVState, pc) + field_offset;
size_t res_offset = offsetof(CPURISCVState, load_res) + field_offset;
size_t val_offset = offsetof(CPURISCVState, load_val) + field_offset;
+#ifdef CONFIG_USER_ONLY
+ size_t res_size_offset = offsetof(CPURISCVState, load_res_size)
+ + field_offset;
+#endif
for (i = 1; i < 32; i++) {
cpu_gpr[i] = tcg_global_mem_new(tcg_env,
@@ -1501,5 +1535,9 @@ void riscv_translate_init(void)
cpu_vl = tcg_global_mem_new_i32(tcg_env, vl_offset, "vl");
cpu_vstart = tcg_global_mem_new_i32(tcg_env, vstart_offset, "vstart");
load_res = tcg_global_mem_new(tcg_env, res_offset, "load_res");
+#ifdef CONFIG_USER_ONLY
+ load_res_size = tcg_global_mem_new(tcg_env, res_size_offset,
+ "load_res_size");
+#endif
load_val = tcg_global_mem_new(tcg_env, val_offset, "load_val");
}