All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] target/arm/hvf: emulate ISV=0 data abort instructions
@ 2026-03-09 21:48 Lucas Amaral
  2026-03-10  1:28 ` Mohamed Mediouni
  2026-03-13  2:18 ` [PATCH v2 0/3] target/arm: ISV=0 data abort emulation library Lucas Amaral
  0 siblings, 2 replies; 25+ messages in thread
From: Lucas Amaral @ 2026-03-09 21:48 UTC (permalink / raw)
  To: qemu-devel; +Cc: qemu-arm, agraf, Lucas Amaral

On Apple Silicon, HVF exits with ISV=0 (no syndrome information)
for STP/LDP/STNP/LDNP, SIMD/FP load/stores, and DC cache
maintenance instructions that access MMIO regions.  The existing
code asserted ISV!=0, crashing the VM.

Decode the faulting instruction from guest memory and emulate:
- Load/Store Pair (STP/LDP/STNP/LDNP) for GPR and SIMD registers
- Single Load/Store with writeback and SIMD/FP variants
- DC system instructions (NOP on MMIO regions)
- LDPSW (sign-extending load pair)

For pair instructions, compute the effective virtual address from
the base register to handle page-straddling accesses correctly.
HPFAR_EL2 reports the faulting page, not the effective address,
so using the IPA directly would produce wrong results when an
STP straddles an HVF-mapped / MMIO boundary.

Tested with virtio-gpu Venus blob resources on macOS ARM64.

Signed-off-by: Lucas Amaral <lucaaamaral@gmail.com>
---
 target/arm/hvf/hvf.c | 309 ++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 306 insertions(+), 3 deletions(-)

diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c
index d79469c..87ddcdb 100644
--- a/target/arm/hvf/hvf.c
+++ b/target/arm/hvf/hvf.c
@@ -1871,10 +1871,313 @@ static int hvf_handle_exception(CPUState *cpu, hv_vcpu_exit_exception_t *excp)
         assert(!s1ptw);
 
         /*
-         * TODO: ISV will be 0 for SIMD or SVE accesses.
-         * Inject the exception into the guest.
+         * ISV=0: syndrome doesn't carry access size/register info.
+         * This happens for STP/LDP/STNP/LDNP, SIMD/SVE load/stores,
+         * and DC (data cache) maintenance instructions.
+         *
+         * Sync all CPU state (including TTBR/TCR/SCTLR for page table
+         * walk) and decode the faulting instruction from guest memory.
          */
-        assert(isv);
+        if (!isv) {
+            ARMCPU *arm_cpu = ARM_CPU(cpu);
+            CPUARMState *env = &arm_cpu->env;
+            uint32_t insn;
+
+            /*
+             * Sync system registers (TTBR, TCR, SCTLR, etc.) from HVF
+             * so cpu_memory_rw_debug can walk guest page tables.
+             */
+            cpu_synchronize_state(cpu);
+
+            if (cpu_memory_rw_debug(cpu, env->pc,
+                                    (uint8_t *)&insn, 4, false) != 0) {
+                error_report("HVF: ISV=0 at ipa=0x%" PRIx64
+                             " -- cannot read insn at pc=0x%" PRIx64,
+                             ipa, (uint64_t)env->pc);
+                goto isv0_inject_fault;
+            }
+            insn = le32_to_cpu(insn);
+
+            /*
+             * System instructions (DC CIVAC, DC CVAC, etc.):
+             * bits [31:22] = 1101010100 identifies MRS/MSR/SYS class.
+             * Cache maintenance on MMIO regions is a harmless NOP.
+             */
+            if ((insn & 0xFFC00000) == 0xD5000000) {
+                advance_pc = true;
+                break;
+            }
+
+            /*
+             * Load/Store Pair (STP/LDP/STNP/LDNP):
+             * bits [29:27] = 101 identifies this instruction class.
+             * Supports both integer (GPR) and SIMD/FP register pairs.
+             */
+            if ((insn & 0x38000000) == 0x28000000) {
+                uint32_t opc = extract32(insn, 30, 2);
+                bool is_vec = extract32(insn, 26, 1);
+                bool is_load = extract32(insn, 22, 1);
+                uint32_t rt = extract32(insn, 0, 5);
+                uint32_t rt2 = extract32(insn, 10, 5);
+                uint32_t rn = extract32(insn, 5, 5);
+                uint32_t type = extract32(insn, 23, 3);
+                bool writeback = (type == 1 || type == 3);
+                uint32_t esize;
+                int32_t imm7 = sextract32(insn, 15, 7);
+
+                if (!is_vec) {
+                    esize = (opc & 2) ? 8 : 4;
+                } else {
+                    esize = 4u << opc;  /* 4, 8, or 16 bytes */
+                }
+
+                int64_t stp_offset = (int64_t)imm7 * esize;
+
+                /*
+                 * Compute the effective virtual address from the base
+                 * register and immediate offset. HPFAR_EL2 reports
+                 * the faulting page, not the effective address, so we
+                 * must derive the VA from the instruction encoding.
+                 *
+                 * After cpu_synchronize_state(), env->xregs[0..30] are
+                 * GPRs and env->xregs[31] is the current SP (restored
+                 * via aarch64_restore_sp).
+                 *
+                 * Using the VA with cpu_memory_rw_debug() correctly
+                 * splits page-straddling accesses via guest page tables.
+                 */
+                uint64_t rn_va = env->xregs[rn];
+                /* post-index: access at unmodified base */
+                uint64_t va = (type == 1) ? rn_va : rn_va + stp_offset;
+
+                if (is_load == iswrite) {
+                    error_report("HVF: ISV=0 load/write mismatch at "
+                                 "ipa=0x%" PRIx64, ipa);
+                    goto isv0_inject_fault;
+                }
+
+                if (iswrite) {
+                    /* Store pair */
+                    if (!is_vec) {
+                        uint64_t val1 = env->xregs[rt];
+                        uint64_t val2 = env->xregs[rt2];
+                        uint8_t buf[16]; /* max 2 x 8 bytes */
+                        memcpy(buf, &val1, esize);
+                        memcpy(buf + esize, &val2, esize);
+                        cpu_memory_rw_debug(cpu, va, buf,
+                                            2 * esize, true);
+                    } else {
+                        /*
+                         * SIMD STP: register data is in env->vfp.zregs[]
+                         * after cpu_synchronize_state().
+                         * esize=4: S reg, esize=8: D reg, esize=16: Q reg
+                         */
+                        uint8_t buf[32]; /* max 2 x 16 bytes */
+                        memcpy(buf, &env->vfp.zregs[rt], esize);
+                        memcpy(buf + esize,
+                               &env->vfp.zregs[rt2], esize);
+                        cpu_memory_rw_debug(cpu, va, buf,
+                                            2 * esize, true);
+                    }
+                } else {
+                    /* Load pair */
+                    if (!is_vec) {
+                        uint64_t val1 = 0, val2 = 0;
+                        uint8_t buf[16];
+                        memset(buf, 0, sizeof(buf));
+                        cpu_memory_rw_debug(cpu, va, buf,
+                                            2 * esize, false);
+                        memcpy(&val1, buf, esize);
+                        memcpy(&val2, buf + esize, esize);
+                        if (opc == 1 && !is_vec) {
+                            /* LDPSW: sign-extend 32-bit to 64-bit */
+                            val1 = (int64_t)(int32_t)val1;
+                            val2 = (int64_t)(int32_t)val2;
+                        }
+                        hvf_set_reg(cpu, rt, val1);
+                        hvf_set_reg(cpu, rt2, val2);
+                    } else {
+                        /* SIMD LDP */
+                        uint8_t buf[32];
+                        memset(buf, 0, sizeof(buf));
+                        cpu_memory_rw_debug(cpu, va, buf,
+                                            2 * esize, false);
+                        memset(&env->vfp.zregs[rt], 0,
+                               sizeof(env->vfp.zregs[rt]));
+                        memset(&env->vfp.zregs[rt2], 0,
+                               sizeof(env->vfp.zregs[rt2]));
+                        memcpy(&env->vfp.zregs[rt], buf, esize);
+                        memcpy(&env->vfp.zregs[rt2],
+                               buf + esize, esize);
+                        cpu->vcpu_dirty = true;
+                    }
+                }
+
+                /* Handle base register writeback (pre/post-index) */
+                if (writeback) {
+                    env->xregs[rn] = env->xregs[rn] + stp_offset;
+                    cpu->vcpu_dirty = true;
+                }
+
+                advance_pc = true;
+                break;
+            }
+
+            /*
+             * Load/Store Register (single):
+             * bits [29:27] = 111, bit [25] = 0.
+             * Covers immediate (unscaled, post-index, pre-index),
+             * unsigned offset, and register offset variants.
+             *
+             * ISV=0 for: writeback variants (pre/post-indexed) and
+             * all SIMD/FP loads/stores.
+             */
+            if ((insn & 0x3A000000) == 0x38000000) {
+                uint32_t size_field = extract32(insn, 30, 2);
+                bool is_vec = extract32(insn, 26, 1);
+                uint32_t opc = extract32(insn, 22, 2);
+                bool is_unsigned = extract32(insn, 24, 1);
+                bool bit21 = extract32(insn, 21, 1);
+                uint32_t rn = extract32(insn, 5, 5);
+                uint32_t rt = extract32(insn, 0, 5);
+                uint32_t sub_type = extract32(insn, 10, 2);
+                bool is_reg = !is_unsigned && bit21;
+
+                /*
+                 * [24]=0, [21]=1, [11:10]!=10 could be atomic ops
+                 * (LDADD, SWP, CAS, etc.) -- not handled.
+                 */
+                if (is_reg && sub_type != 2) {
+                    goto isv0_inject_fault;
+                }
+
+                bool writeback = !is_unsigned && !is_reg
+                                 && (sub_type == 1 || sub_type == 3);
+
+                uint32_t esize;
+                bool is_load;
+                bool is_signed = false;
+                uint32_t sign_extend_to = 0;
+
+                if (!is_vec) {
+                    esize = 1u << size_field;
+                    switch (opc) {
+                    case 0: /* STR */
+                        is_load = false;
+                        break;
+                    case 1: /* LDR */
+                        is_load = true;
+                        break;
+                    case 2:
+                        is_load = true;                      /* LDRS->64 */
+                        is_signed = true;
+                        sign_extend_to = 8;
+                        break;
+                    case 3:
+                        if (size_field == 3) {
+                            /* PRFM -- prefetch is NOP on MMIO */
+                            advance_pc = true;
+                            goto isv0_done;
+                        }
+                        is_load = true;                      /* LDRS->32 */
+                        is_signed = true;
+                        sign_extend_to = 4;
+                        break;
+                    }
+                } else {
+                    /* SIMD/FP: size+opc determines element width */
+                    is_load = (opc & 1);
+                    if (opc >= 2 && size_field == 0) {
+                        esize = 16;  /* Q register (128-bit) */
+                    } else if (opc < 2) {
+                        esize = 1u << size_field;
+                    } else {
+                        goto isv0_inject_fault;
+                    }
+                }
+
+                if (is_load == iswrite) {
+                    error_report("HVF: ISV=0 LDR/STR load/write mismatch "
+                                 "at ipa=0x%" PRIx64, ipa);
+                    goto isv0_inject_fault;
+                }
+
+                /* Perform memory access */
+                if (!is_load) {
+                    if (!is_vec) {
+                        uint64_t val = hvf_get_reg(cpu, rt);
+                        address_space_write(as, ipa,
+                                            MEMTXATTRS_UNSPECIFIED,
+                                            &val, esize);
+                    } else {
+                        address_space_write(as, ipa,
+                                            MEMTXATTRS_UNSPECIFIED,
+                                            &env->vfp.zregs[rt], esize);
+                    }
+                } else {
+                    if (!is_vec) {
+                        uint64_t val = 0;
+                        address_space_read(as, ipa,
+                                           MEMTXATTRS_UNSPECIFIED,
+                                           &val, esize);
+                        if (is_signed) {
+                            switch (esize) {
+                            case 1:
+                                val = (int64_t)(int8_t)val;
+                                break;
+                            case 2:
+                                val = (int64_t)(int16_t)val;
+                                break;
+                            case 4:
+                                val = (int64_t)(int32_t)val;
+                                break;
+                            }
+                            if (sign_extend_to == 4) {
+                                val &= 0xFFFFFFFF;
+                            }
+                        }
+                        hvf_set_reg(cpu, rt, val);
+                    } else {
+                        /* SIMD/FP load */
+                        memset(&env->vfp.zregs[rt], 0,
+                               sizeof(env->vfp.zregs[rt]));
+                        address_space_read(as, ipa,
+                                           MEMTXATTRS_UNSPECIFIED,
+                                           &env->vfp.zregs[rt], esize);
+                        cpu->vcpu_dirty = true;
+                    }
+                }
+
+                /* Base register writeback (post/pre-indexed) */
+                if (writeback) {
+                    int32_t imm9 = sextract32(insn, 12, 9);
+                    env->xregs[rn] = env->xregs[rn] + imm9;
+                    cpu->vcpu_dirty = true;
+                }
+
+                advance_pc = true;
+                goto isv0_done;
+            }
+
+isv0_inject_fault:
+            /*
+             * Inject data abort into guest for unrecognized or
+             * inconsistent ISV=0 instructions.  The guest kernel
+             * will deliver SIGBUS to the faulting process.
+             */
+            {
+                int target_el = 1;
+                bool same_el = arm_current_el(env) == target_el;
+                uint32_t esr = syn_data_abort_no_iss(same_el,
+                    /*fnv=*/1, /*ea=*/0, /*cm=*/0,
+                    /*s1ptw=*/0, iswrite, /*fsc=*/0x10);
+                env->exception.vaddress = ipa;
+                hvf_raise_exception(cpu, EXCP_DATA_ABORT,
+                                    esr, target_el);
+            }
+isv0_done:
+            break;
+        }
 
         /*
          * Emulate MMIO.
-- 
2.52.0



^ permalink raw reply related	[flat|nested] 25+ messages in thread

end of thread, other threads:[~2026-03-19 22:01 UTC | newest]

Thread overview: 25+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-09 21:48 [PATCH] target/arm/hvf: emulate ISV=0 data abort instructions Lucas Amaral
2026-03-10  1:28 ` Mohamed Mediouni
2026-03-10  9:23   ` Peter Maydell
2026-03-13  2:18 ` [PATCH v2 0/3] target/arm: ISV=0 data abort emulation library Lucas Amaral
2026-03-13  2:18   ` [PATCH v2 1/3] target/arm: add AArch64 ISV=0 instruction " Lucas Amaral
2026-03-13  6:33     ` Mohamed Mediouni
2026-03-13  8:59     ` Peter Maydell
2026-03-13  2:18   ` [PATCH v2 2/3] tests: add unit tests for ISV=0 " Lucas Amaral
2026-03-13  2:18   ` [PATCH v2 3/3] target/arm: wire ISV=0 emulation into HVF and WHPX Lucas Amaral
2026-03-15  3:41   ` [PATCH v3 0/6] target/arm: ISV=0 data abort emulation library Lucas Amaral
2026-03-15  3:41     ` [PATCH v3 1/6] target/arm/emulate: add ISV=0 emulation library with load/store immediate Lucas Amaral
2026-03-15  3:41     ` [PATCH v3 2/6] target/arm/emulate: add load/store register offset Lucas Amaral
2026-03-15  3:41     ` [PATCH v3 3/6] target/arm/emulate: add load/store pair Lucas Amaral
2026-03-15  3:41     ` [PATCH v3 4/6] target/arm/emulate: add load/store exclusive Lucas Amaral
2026-03-15  3:41     ` [PATCH v3 5/6] target/arm/emulate: add atomic, compare-and-swap, and PAC load Lucas Amaral
2026-03-15  3:41     ` [PATCH v3 6/6] target/arm/hvf, whpx: wire ISV=0 emulation for data aborts Lucas Amaral
2026-03-16  2:50     ` [PATCH v4 0/6] target/arm: ISV=0 data abort emulation library Lucas Amaral
2026-03-16  2:50       ` [PATCH v4 1/6] target/arm/emulate: add ISV=0 emulation library with load/store immediate Lucas Amaral
2026-03-19 22:00         ` Richard Henderson
2026-03-16  2:50       ` [PATCH v4 2/6] target/arm/emulate: add load/store register offset Lucas Amaral
2026-03-16  2:50       ` [PATCH v4 3/6] target/arm/emulate: add load/store pair Lucas Amaral
2026-03-16  2:50       ` [PATCH v4 4/6] target/arm/emulate: add load/store exclusive Lucas Amaral
2026-03-16  2:50       ` [PATCH v4 5/6] target/arm/emulate: add atomic, compare-and-swap, and PAC load Lucas Amaral
2026-03-16  2:50       ` [PATCH v4 6/6] target/arm/hvf, whpx: wire ISV=0 emulation for data aborts Lucas Amaral
2026-03-17 14:27       ` [PATCH v4 0/6] target/arm: ISV=0 data abort emulation library Alex Bennée

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.