Add emulation for load/store exclusive instructions (DDI 0487 C3.3.6).
Exclusive monitors have no meaning on emulated MMIO accesses, so STXR
always reports success (Rs=0) and LDXR does not set a monitor.
Instruction coverage:
- STXR/STLXR: exclusive store, 8/16/32/64-bit
- LDXR/LDAXR: exclusive load, 8/16/32/64-bit
- STXP/STLXP: exclusive store pair, 32/64-bit
- LDXP/LDAXP: exclusive load pair, 32/64-bit
STXP/LDXP use two explicit decode patterns (sz=2, sz=3) for the
32/64-bit size variants.
Signed-off-by: Lucas Amaral <lucaaamaral@gmail.com>
---
target/arm/emulate/a64-ldst.decode | 22 ++++++++++
target/arm/emulate/arm_emulate.c | 70 ++++++++++++++++++++++++++++++
2 files changed, 92 insertions(+)
diff --git a/target/arm/emulate/a64-ldst.decode b/target/arm/emulate/a64-ldst.decode
index f3de3f86..fadf6fd2 100644
--- a/target/arm/emulate/a64-ldst.decode
+++ b/target/arm/emulate/a64-ldst.decode
@@ -10,6 +10,9 @@
# 'u' flag: 0 = 9-bit signed immediate (byte offset), 1 = 12-bit unsigned (needs << sz)
&ldst_imm rt rn imm sz sign w p unpriv ext u
+# Load/store exclusive
+&stxr rn rt rt2 rs sz lasr
+
# Load/store pair (GPR and SIMD/FP)
&ldstpair rt2 rt rn imm sz sign w p
@@ -18,6 +21,9 @@
### Format templates
+# Exclusives
+@stxr sz:2 ...... ... rs:5 lasr:1 rt2:5 rn:5 rt:5 &stxr
+
# Load/store immediate (9-bit signed)
@ldst_imm .. ... . .. .. . imm:s9 .. rn:5 rt:5 &ldst_imm u=0 unpriv=0 p=0 w=0
@ldst_imm_pre .. ... . .. .. . imm:s9 .. rn:5 rt:5 &ldst_imm u=0 unpriv=0 p=0 w=1
@@ -134,6 +140,22 @@ STR_v_i 00 111 1 01 10 ............ ..... ..... @ldst_uimm sign=
LDR_v_i sz:2 111 1 01 01 ............ ..... ..... @ldst_uimm sign=0 ext=0
LDR_v_i 00 111 1 01 11 ............ ..... ..... @ldst_uimm sign=0 ext=0 sz=4
+### Load/store exclusive
+
+# STXR / STLXR (sz encodes 8/16/32/64-bit)
+STXR .. 001000 000 ..... . ..... ..... ..... @stxr
+
+# LDXR / LDAXR
+LDXR .. 001000 010 ..... . ..... ..... ..... @stxr
+
+# STXP / STLXP (bit[31]=1, bit[30]=sf → sz=2 for 32-bit, sz=3 for 64-bit)
+STXP 10 001000 001 rs:5 lasr:1 rt2:5 rn:5 rt:5 &stxr sz=2
+STXP 11 001000 001 rs:5 lasr:1 rt2:5 rn:5 rt:5 &stxr sz=3
+
+# LDXP / LDAXP
+LDXP 10 001000 011 rs:5 lasr:1 rt2:5 rn:5 rt:5 &stxr sz=2
+LDXP 11 001000 011 rs:5 lasr:1 rt2:5 rn:5 rt:5 &stxr sz=3
+
### Load/store pair — non-temporal (STNP/LDNP)
# STNP/LDNP: offset only, no writeback. Non-temporal hint ignored.
diff --git a/target/arm/emulate/arm_emulate.c b/target/arm/emulate/arm_emulate.c
index 2d86b90f..7f876355 100644
--- a/target/arm/emulate/arm_emulate.c
+++ b/target/arm/emulate/arm_emulate.c
@@ -477,6 +477,76 @@ static bool trans_LDR_v(DisasContext *ctx, arg_ldst *a)
return true;
}
+/*
+ * Load/store exclusive: STXR, LDXR, STXP, LDXP
+ * (DDI 0487 C3.3.6)
+ *
+ * Exclusive monitors have no meaning on MMIO. STXR always reports
+ * success (Rs=0) and LDXR does not set an exclusive monitor.
+ */
+
+static bool trans_STXR(DisasContext *ctx, arg_stxr *a)
+{
+ int esize = 1 << a->sz;
+ uint64_t va = base_read(ctx, a->rn);
+ uint8_t buf[8];
+
+ mem_st(ctx, buf, esize, gpr_read(ctx, a->rt));
+ if (mem_write(ctx, va, buf, esize) != 0) {
+ return true;
+ }
+
+ /* Report success -- no exclusive monitor on emulated access */
+ gpr_write(ctx, a->rs, 0);
+ return true;
+}
+
+static bool trans_LDXR(DisasContext *ctx, arg_stxr *a)
+{
+ int esize = 1 << a->sz;
+ uint64_t va = base_read(ctx, a->rn);
+ uint8_t buf[8];
+
+ if (mem_read(ctx, va, buf, esize) != 0) {
+ return true;
+ }
+
+ gpr_write(ctx, a->rt, mem_ld(ctx, buf, esize));
+ return true;
+}
+
+static bool trans_STXP(DisasContext *ctx, arg_stxr *a)
+{
+ int esize = 1 << a->sz; /* sz=2->4, sz=3->8 */
+ uint64_t va = base_read(ctx, a->rn);
+ uint8_t buf[16];
+
+ mem_st(ctx, buf, esize, gpr_read(ctx, a->rt));
+ mem_st(ctx, buf + esize, esize, gpr_read(ctx, a->rt2));
+
+ if (mem_write(ctx, va, buf, 2 * esize) != 0) {
+ return true;
+ }
+
+ gpr_write(ctx, a->rs, 0); /* success */
+ return true;
+}
+
+static bool trans_LDXP(DisasContext *ctx, arg_stxr *a)
+{
+ int esize = 1 << a->sz;
+ uint64_t va = base_read(ctx, a->rn);
+ uint8_t buf[16];
+
+ if (mem_read(ctx, va, buf, 2 * esize) != 0) {
+ return true;
+ }
+
+ gpr_write(ctx, a->rt, mem_ld(ctx, buf, esize));
+ gpr_write(ctx, a->rt2, mem_ld(ctx, buf + esize, esize));
+ return true;
+}
+
/* PRFM, DC cache maintenance -- treated as NOP */
static bool trans_NOP(DisasContext *ctx, arg_NOP *a)
{
--
2.52.0
> On 10. Apr 2026, at 00:06, Lucas Amaral <lucaaamaral@gmail.com> wrote:
>
> Add emulation for load/store exclusive instructions (DDI 0487 C3.3.6).
> Exclusive monitors have no meaning on emulated MMIO accesses, so STXR
> always reports success (Rs=0) and LDXR does not set a monitor.
Do people actually use those?
And if so I wonder if that’s an application bug…
If needed to get apps running,
Reviewed-by: Mohamed Mediouni <mohamed@unpredictable.fr>
As we do similar things for x86 anyhow...
>
> Instruction coverage:
> - STXR/STLXR: exclusive store, 8/16/32/64-bit
> - LDXR/LDAXR: exclusive load, 8/16/32/64-bit
> - STXP/STLXP: exclusive store pair, 32/64-bit
> - LDXP/LDAXP: exclusive load pair, 32/64-bit
>
> STXP/LDXP use two explicit decode patterns (sz=2, sz=3) for the
> 32/64-bit size variants.
>
> Signed-off-by: Lucas Amaral <lucaaamaral@gmail.com>
> ---
> target/arm/emulate/a64-ldst.decode | 22 ++++++++++
> target/arm/emulate/arm_emulate.c | 70 ++++++++++++++++++++++++++++++
> 2 files changed, 92 insertions(+)
>
> diff --git a/target/arm/emulate/a64-ldst.decode b/target/arm/emulate/a64-ldst.decode
> index f3de3f86..fadf6fd2 100644
> --- a/target/arm/emulate/a64-ldst.decode
> +++ b/target/arm/emulate/a64-ldst.decode
> @@ -10,6 +10,9 @@
> # 'u' flag: 0 = 9-bit signed immediate (byte offset), 1 = 12-bit unsigned (needs << sz)
> &ldst_imm rt rn imm sz sign w p unpriv ext u
>
> +# Load/store exclusive
> +&stxr rn rt rt2 rs sz lasr
> +
> # Load/store pair (GPR and SIMD/FP)
> &ldstpair rt2 rt rn imm sz sign w p
>
> @@ -18,6 +21,9 @@
>
> ### Format templates
>
> +# Exclusives
> +@stxr sz:2 ...... ... rs:5 lasr:1 rt2:5 rn:5 rt:5 &stxr
> +
> # Load/store immediate (9-bit signed)
> @ldst_imm .. ... . .. .. . imm:s9 .. rn:5 rt:5 &ldst_imm u=0 unpriv=0 p=0 w=0
> @ldst_imm_pre .. ... . .. .. . imm:s9 .. rn:5 rt:5 &ldst_imm u=0 unpriv=0 p=0 w=1
> @@ -134,6 +140,22 @@ STR_v_i 00 111 1 01 10 ............ ..... ..... @ldst_uimm sign=
> LDR_v_i sz:2 111 1 01 01 ............ ..... ..... @ldst_uimm sign=0 ext=0
> LDR_v_i 00 111 1 01 11 ............ ..... ..... @ldst_uimm sign=0 ext=0 sz=4
>
> +### Load/store exclusive
> +
> +# STXR / STLXR (sz encodes 8/16/32/64-bit)
> +STXR .. 001000 000 ..... . ..... ..... ..... @stxr
> +
> +# LDXR / LDAXR
> +LDXR .. 001000 010 ..... . ..... ..... ..... @stxr
> +
> +# STXP / STLXP (bit[31]=1, bit[30]=sf → sz=2 for 32-bit, sz=3 for 64-bit)
> +STXP 10 001000 001 rs:5 lasr:1 rt2:5 rn:5 rt:5 &stxr sz=2
> +STXP 11 001000 001 rs:5 lasr:1 rt2:5 rn:5 rt:5 &stxr sz=3
> +
> +# LDXP / LDAXP
> +LDXP 10 001000 011 rs:5 lasr:1 rt2:5 rn:5 rt:5 &stxr sz=2
> +LDXP 11 001000 011 rs:5 lasr:1 rt2:5 rn:5 rt:5 &stxr sz=3
> +
> ### Load/store pair — non-temporal (STNP/LDNP)
>
> # STNP/LDNP: offset only, no writeback. Non-temporal hint ignored.
> diff --git a/target/arm/emulate/arm_emulate.c b/target/arm/emulate/arm_emulate.c
> index 2d86b90f..7f876355 100644
> --- a/target/arm/emulate/arm_emulate.c
> +++ b/target/arm/emulate/arm_emulate.c
> @@ -477,6 +477,76 @@ static bool trans_LDR_v(DisasContext *ctx, arg_ldst *a)
> return true;
> }
>
> +/*
> + * Load/store exclusive: STXR, LDXR, STXP, LDXP
> + * (DDI 0487 C3.3.6)
> + *
> + * Exclusive monitors have no meaning on MMIO. STXR always reports
> + * success (Rs=0) and LDXR does not set an exclusive monitor.
> + */
> +
> +static bool trans_STXR(DisasContext *ctx, arg_stxr *a)
> +{
> + int esize = 1 << a->sz;
> + uint64_t va = base_read(ctx, a->rn);
> + uint8_t buf[8];
> +
> + mem_st(ctx, buf, esize, gpr_read(ctx, a->rt));
> + if (mem_write(ctx, va, buf, esize) != 0) {
> + return true;
> + }
> +
> + /* Report success -- no exclusive monitor on emulated access */
> + gpr_write(ctx, a->rs, 0);
> + return true;
> +}
> +
> +static bool trans_LDXR(DisasContext *ctx, arg_stxr *a)
> +{
> + int esize = 1 << a->sz;
> + uint64_t va = base_read(ctx, a->rn);
> + uint8_t buf[8];
> +
> + if (mem_read(ctx, va, buf, esize) != 0) {
> + return true;
> + }
> +
> + gpr_write(ctx, a->rt, mem_ld(ctx, buf, esize));
> + return true;
> +}
> +
> +static bool trans_STXP(DisasContext *ctx, arg_stxr *a)
> +{
> + int esize = 1 << a->sz; /* sz=2->4, sz=3->8 */
> + uint64_t va = base_read(ctx, a->rn);
> + uint8_t buf[16];
> +
> + mem_st(ctx, buf, esize, gpr_read(ctx, a->rt));
> + mem_st(ctx, buf + esize, esize, gpr_read(ctx, a->rt2));
> +
> + if (mem_write(ctx, va, buf, 2 * esize) != 0) {
> + return true;
> + }
> +
> + gpr_write(ctx, a->rs, 0); /* success */
> + return true;
> +}
> +
> +static bool trans_LDXP(DisasContext *ctx, arg_stxr *a)
> +{
> + int esize = 1 << a->sz;
> + uint64_t va = base_read(ctx, a->rn);
> + uint8_t buf[16];
> +
> + if (mem_read(ctx, va, buf, 2 * esize) != 0) {
> + return true;
> + }
> +
> + gpr_write(ctx, a->rt, mem_ld(ctx, buf, esize));
> + gpr_write(ctx, a->rt2, mem_ld(ctx, buf + esize, esize));
> + return true;
> +}
> +
> /* PRFM, DC cache maintenance -- treated as NOP */
> static bool trans_NOP(DisasContext *ctx, arg_NOP *a)
> {
> --
> 2.52.0
>
>
© 2016 - 2026 Red Hat, Inc.