diff --git a/meta/recipes-devtools/qemu/qemu.inc b/meta/recipes-devtools/qemu/qemu.inc
index f19cf70dd6..8c93227eb3 100644
--- a/meta/recipes-devtools/qemu/qemu.inc
+++ b/meta/recipes-devtools/qemu/qemu.inc
@@ -35,6 +35,9 @@ SRC_URI = "https://download.qemu.org/${BPN}-${PV}.tar.xz \
            file://qemu-guest-agent.init \
            file://qemu-guest-agent.udev \
            file://0001-ui-sdl2.c-force-disable-SDL_HINT_VIDEO_X11_FORCE_EGL.patch \
+           file://0001-target-riscv-extract-sfence_vma_allowed-from-helper_.patch \
+           file://0002-target-riscv-add-helper_tlb_flush_page.patch \
+           file://0003-target-riscv-use-a-targeted-TLB-page-flush-for-sfenc.patch \
            "
 # file index at download.qemu.org isn't reliable: https://gitlab.com/qemu-project/qemu-web/-/issues/9
 UPSTREAM_CHECK_URI = "https://www.qemu.org"
diff --git a/meta/recipes-devtools/qemu/qemu/0001-target-riscv-extract-sfence_vma_allowed-from-helper_.patch b/meta/recipes-devtools/qemu/qemu/0001-target-riscv-extract-sfence_vma_allowed-from-helper_.patch
new file mode 100644
index 0000000000..2ece946400
--- /dev/null
+++ b/meta/recipes-devtools/qemu/qemu/0001-target-riscv-extract-sfence_vma_allowed-from-helper_.patch
@@ -0,0 +1,56 @@
+From 8507fd6795753697f557d11e144568f1bf8a00cb Mon Sep 17 00:00:00 2001
+From: Trevor Gamblin <tgamblin@baylibre.com>
+Date: Mon, 24 Aug 2026 17:54:21 +0000
+Subject: [PATCH 1/3] target/riscv: extract sfence_vma_allowed() from
+ helper_tlb_flush()
+
+Create a new function to encapsulate the privilege/hypervisor checks
+performed inside helper_tlb_flush(). The idea is to pass GETPC() as an
+argument directly to it inside the helper_tlb_flush() function.
+
+Upstream-Status: Submitted [https://lists.gnu.org/archive/html/qemu-devel/2026-08/msg08496.html]
+
+Signed-off-by: Trevor Gamblin <tgamblin@baylibre.com>
+---
+ target/riscv/tcg/op_helper.c | 19 +++++++++++++------
+ 1 file changed, 13 insertions(+), 6 deletions(-)
+
+diff --git a/target/riscv/tcg/op_helper.c b/target/riscv/tcg/op_helper.c
+index 3e94005d2b..8039df2b48 100644
+--- a/target/riscv/tcg/op_helper.c
++++ b/target/riscv/tcg/op_helper.c
+@@ -588,18 +588,25 @@ void helper_wrs_nto(CPURISCVState *env)
+     }
+ }
+ 
+-void helper_tlb_flush(CPURISCVState *env)
++static bool sfence_vma_allowed(CPURISCVState *env, uintptr_t ra)
+ {
+-    CPUState *cs = env_cpu(env);
+     if (!env->virt_enabled &&
+         (env->priv == PRV_U ||
+          (env->priv == PRV_S && get_field(env->mstatus, MSTATUS_TVM)))) {
+-        riscv_raise_exception(env, RISCV_EXCP_ILLEGAL_INST, GETPC());
++        riscv_raise_exception(env, RISCV_EXCP_ILLEGAL_INST, ra);
++        return false;
+     } else if (env->virt_enabled &&
+                (env->priv == PRV_U || get_field(env->hstatus, HSTATUS_VTVM))) {
+-        riscv_raise_exception(env, RISCV_EXCP_VIRT_INSTRUCTION_FAULT, GETPC());
+-    } else {
+-        tlb_flush(cs);
++        riscv_raise_exception(env, RISCV_EXCP_VIRT_INSTRUCTION_FAULT, ra);
++        return false;
++    }
++    return true;
++}
++
++void helper_tlb_flush(CPURISCVState *env)
++{
++    if (sfence_vma_allowed(env, GETPC())) {
++        tlb_flush(env_cpu(env));
+     }
+ }
+ 
+-- 
+2.55.0
+
diff --git a/meta/recipes-devtools/qemu/qemu/0002-target-riscv-add-helper_tlb_flush_page.patch b/meta/recipes-devtools/qemu/qemu/0002-target-riscv-add-helper_tlb_flush_page.patch
new file mode 100644
index 0000000000..c272a80b08
--- /dev/null
+++ b/meta/recipes-devtools/qemu/qemu/0002-target-riscv-add-helper_tlb_flush_page.patch
@@ -0,0 +1,50 @@
+From eb8773847363989d9396492a3c9bc517ca04959f Mon Sep 17 00:00:00 2001
+From: Trevor Gamblin <tgamblin@baylibre.com>
+Date: Mon, 24 Aug 2026 17:54:45 +0000
+Subject: [PATCH 2/3] target/riscv: add helper_tlb_flush_page()
+
+Add a page-level counterpart to helper_tlb_flush(), and condition its
+internal call to tlb_flush_page() on return value from the new
+sfence_vma_allowed() function.
+
+Upstream-Status: Submitted [https://lists.gnu.org/archive/html/qemu-devel/2026-08/msg08496.html]
+
+Signed-off-by: Trevor Gamblin <tgamblin@baylibre.com>
+---
+ target/riscv/helper.h        | 1 +
+ target/riscv/tcg/op_helper.c | 7 +++++++
+ 2 files changed, 8 insertions(+)
+
+diff --git a/target/riscv/helper.h b/target/riscv/helper.h
+index 4fc2d3a155..652f85a5c7 100644
+--- a/target/riscv/helper.h
++++ b/target/riscv/helper.h
+@@ -137,6 +137,7 @@ DEF_HELPER_1(ctr_clear, void, env)
+ DEF_HELPER_1(wfi, void, env)
+ DEF_HELPER_1(wrs_nto, void, env)
+ DEF_HELPER_1(tlb_flush, void, env)
++DEF_HELPER_2(tlb_flush_page, void, env, tl)
+ DEF_HELPER_1(tlb_flush_all, void, env)
+ DEF_HELPER_4(ctr_add_entry, void, env, tl, tl, tl)
+ /* Native Debug */
+diff --git a/target/riscv/tcg/op_helper.c b/target/riscv/tcg/op_helper.c
+index 8039df2b48..723a45d181 100644
+--- a/target/riscv/tcg/op_helper.c
++++ b/target/riscv/tcg/op_helper.c
+@@ -610,6 +610,13 @@ void helper_tlb_flush(CPURISCVState *env)
+     }
+ }
+ 
++void helper_tlb_flush_page(CPURISCVState *env, target_ulong addr)
++{
++    if (sfence_vma_allowed(env, GETPC())) {
++        tlb_flush_page(env_cpu(env), addr);
++    }
++}
++
+ void helper_tlb_flush_all(CPURISCVState *env)
+ {
+     CPUState *cs = env_cpu(env);
+-- 
+2.55.0
+
diff --git a/meta/recipes-devtools/qemu/qemu/0003-target-riscv-use-a-targeted-TLB-page-flush-for-sfenc.patch b/meta/recipes-devtools/qemu/qemu/0003-target-riscv-use-a-targeted-TLB-page-flush-for-sfenc.patch
new file mode 100644
index 0000000000..36796e34f4
--- /dev/null
+++ b/meta/recipes-devtools/qemu/qemu/0003-target-riscv-use-a-targeted-TLB-page-flush-for-sfenc.patch
@@ -0,0 +1,47 @@
+From 858d6d9a587e4ed44f92e017375c5265bc40934b Mon Sep 17 00:00:00 2001
+From: Trevor Gamblin <tgamblin@baylibre.com>
+Date: Mon, 24 Aug 2026 17:55:00 +0000
+Subject: [PATCH 3/3] target/riscv: use a targeted TLB page flush for
+ sfence.vma with a vaddr operand
+
+sfence.vma unconditionally called helper_tlb_flush(), regardless of the
+rs1 (vaddr) and rs2 (asid) operands, forcing a page-table walk on the
+next access. Use helper_tlb_flush_page() whenever rs1 != 0, falling back
+to the existing full flush for rs1 == 0. This makes RISC-V behaviour
+more similar to ARM's equivalent (tlbi_aa64_vae1_write), which already
+does tlb_flush_page_by_mmuidx() instead of a full flush.
+
+Pass get_address(ctx, a->rs1, 0) to gen_helper_tlb_flush_page() rather
+than get_gpr(), so that the address being passed matches the current
+addr_xl width regardless of '-cpu' input. Otherwise, the raw register
+value can carry garbage above that width and never match the address the
+TLB entry was actually filled under, so the flush silently misses and a
+stale mapping survives.
+
+Upstream-Status: Submitted [https://lists.gnu.org/archive/html/qemu-devel/2026-08/msg08496.html]
+
+Signed-off-by: Trevor Gamblin <tgamblin@baylibre.com>
+---
+ target/riscv/tcg/insn_trans/trans_privileged.c.inc | 6 +++++-
+ 1 file changed, 5 insertions(+), 1 deletion(-)
+
+diff --git a/target/riscv/tcg/insn_trans/trans_privileged.c.inc b/target/riscv/tcg/insn_trans/trans_privileged.c.inc
+index a8eaccef67..8655fa332e 100644
+--- a/target/riscv/tcg/insn_trans/trans_privileged.c.inc
++++ b/target/riscv/tcg/insn_trans/trans_privileged.c.inc
+@@ -155,7 +155,11 @@ static bool trans_sfence_vma(DisasContext *ctx, arg_sfence_vma *a)
+ {
+ #ifndef CONFIG_USER_ONLY
+     decode_save_opc(ctx, 0);
+-    gen_helper_tlb_flush(tcg_env);
++    if (a->rs1 == 0) {
++        gen_helper_tlb_flush(tcg_env);
++    } else {
++        gen_helper_tlb_flush_page(tcg_env, get_address(ctx, a->rs1, 0));
++    }
+     return true;
+ #endif
+     return false;
+-- 
+2.55.0
+
