diff mbox series

[v2,08/23] target/arm: Move all v7m insn helpers into their own file

Message ID 20190615154352.26824-9-philmd@redhat.com
State New
Headers show
Series Support disabling TCG on ARM | expand

Commit Message

Philippe Mathieu-Daudé June 15, 2019, 3:43 p.m. UTC
From: Samuel Ortiz <sameo@linux.intel.com>

In preparation for supporting TCG disablement on ARM, we move most
of TCG related v7m helpers and APIs into their own file.

Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
[PMD: Patch rewritten]
Signed-off-by: Philippe Mathieu-Daudé <philmd@redhat.com>
---
Is there a way to not use $CONFIG_USER_ONLY?

 target/arm/Makefile.objs |   1 +
 target/arm/helper.c      | 633 -------------------------------------
 target/arm/v7m_helper.c  | 654 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 655 insertions(+), 633 deletions(-)
 create mode 100644 target/arm/v7m_helper.c

Comments

Alex Bennée June 17, 2019, 11:42 a.m. UTC | #1
Philippe Mathieu-Daudé <philmd@redhat.com> writes:

> From: Samuel Ortiz <sameo@linux.intel.com>
>
> In preparation for supporting TCG disablement on ARM, we move most
> of TCG related v7m helpers and APIs into their own file.
>
> Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
> [PMD: Patch rewritten]
> Signed-off-by: Philippe Mathieu-Daudé <philmd@redhat.com>
> ---
> Is there a way to not use $CONFIG_USER_ONLY?

Is this because the CONFIG_ARM_V7M symbol only appears for softmmu
targets but we still want vXm -cpu's for user mode?

>
>  target/arm/Makefile.objs |   1 +
>  target/arm/helper.c      | 633 -------------------------------------
>  target/arm/v7m_helper.c  | 654 +++++++++++++++++++++++++++++++++++++++
>  3 files changed, 655 insertions(+), 633 deletions(-)
>  create mode 100644 target/arm/v7m_helper.c
>
> diff --git a/target/arm/Makefile.objs b/target/arm/Makefile.objs
> index 72b42f825f..5f3f965cc6 100644
> --- a/target/arm/Makefile.objs
> +++ b/target/arm/Makefile.objs
> @@ -35,6 +35,7 @@ obj-y += translate.o op_helper.o
>  obj-y += crypto_helper.o
>  obj-y += iwmmxt_helper.o vec_helper.o
>  obj-y += neon_helper.o vfp_helper.o
> +obj-$(call lor,$(CONFIG_USER_ONLY),$(CONFIG_ARM_V7M)) += v7m_helper.o
>
>  obj-$(TARGET_AARCH64) += translate-a64.o helper-a64.o
>  obj-$(TARGET_AARCH64) += translate-sve.o sve_helper.o
> diff --git a/target/arm/helper.c b/target/arm/helper.c
> index a1e74cc471..a829086c6d 100644
> --- a/target/arm/helper.c
> +++ b/target/arm/helper.c
> @@ -20,7 +20,6 @@
>  #include "qemu/crc32c.h"
>  #include "qemu/qemu-print.h"
>  #include "exec/exec-all.h"
> -#include "exec/cpu_ldst.h"
>  #include "arm_ldst.h"
>  #include <zlib.h> /* For crc32 */
>  #include "hw/semihosting/semihost.h"
> @@ -7456,75 +7455,6 @@ uint32_t HELPER(rbit)(uint32_t x)
>
>  #ifdef CONFIG_USER_ONLY
>
> -/* These should probably raise undefined insn exceptions.  */
> -void HELPER(v7m_msr)(CPUARMState *env, uint32_t reg, uint32_t val)
> -{
> -    ARMCPU *cpu = env_archcpu(env);
> -
> -    cpu_abort(CPU(cpu), "v7m_msr %d\n", reg);
> -}
> -
> -uint32_t HELPER(v7m_mrs)(CPUARMState *env, uint32_t reg)
> -{
> -    ARMCPU *cpu = env_archcpu(env);
> -
> -    cpu_abort(CPU(cpu), "v7m_mrs %d\n", reg);
> -    return 0;
> -}
> -
> -void HELPER(v7m_bxns)(CPUARMState *env, uint32_t dest)
> -{
> -    /* translate.c should never generate calls here in user-only mode */
> -    g_assert_not_reached();
> -}
> -
> -void HELPER(v7m_blxns)(CPUARMState *env, uint32_t dest)
> -{
> -    /* translate.c should never generate calls here in user-only mode */
> -    g_assert_not_reached();
> -}
> -
> -void HELPER(v7m_preserve_fp_state)(CPUARMState *env)
> -{
> -    /* translate.c should never generate calls here in user-only mode */
> -    g_assert_not_reached();
> -}
> -
> -void HELPER(v7m_vlstm)(CPUARMState *env, uint32_t fptr)
> -{
> -    /* translate.c should never generate calls here in user-only mode */
> -    g_assert_not_reached();
> -}
> -
> -void HELPER(v7m_vlldm)(CPUARMState *env, uint32_t fptr)
> -{
> -    /* translate.c should never generate calls here in user-only mode */
> -    g_assert_not_reached();
> -}
> -
> -uint32_t HELPER(v7m_tt)(CPUARMState *env, uint32_t addr, uint32_t op)
> -{
> -    /*
> -     * The TT instructions can be used by unprivileged code, but in
> -     * user-only emulation we don't have the MPU.
> -     * Luckily since we know we are NonSecure unprivileged (and that in
> -     * turn means that the A flag wasn't specified), all the bits in the
> -     * register must be zero:
> -     *  IREGION: 0 because IRVALID is 0
> -     *  IRVALID: 0 because NS
> -     *  S: 0 because NS
> -     *  NSRW: 0 because NS
> -     *  NSR: 0 because NS
> -     *  RW: 0 because unpriv and A flag not set
> -     *  R: 0 because unpriv and A flag not set
> -     *  SRVALID: 0 because NS
> -     *  MRVALID: 0 because unpriv and A flag not set
> -     *  SREGION: 0 becaus SRVALID is 0
> -     *  MREGION: 0 because MRVALID is 0
> -     */
> -    return 0;
> -}
> -
>  void switch_mode(CPUARMState *env, int mode)
>  {
>      ARMCPU *cpu = env_archcpu(env);
> @@ -8048,109 +7978,6 @@ void switch_v7m_security_state(CPUARMState *env, bool new_secstate)
>      }
>  }
>
> -void HELPER(v7m_bxns)(CPUARMState *env, uint32_t dest)
> -{
> -    /*
> -     * Handle v7M BXNS:
> -     *  - if the return value is a magic value, do exception return (like BX)
> -     *  - otherwise bit 0 of the return value is the target security state
> -     */
> -    uint32_t min_magic;
> -
> -    if (arm_feature(env, ARM_FEATURE_M_SECURITY)) {
> -        /* Covers FNC_RETURN and EXC_RETURN magic */
> -        min_magic = FNC_RETURN_MIN_MAGIC;
> -    } else {
> -        /* EXC_RETURN magic only */
> -        min_magic = EXC_RETURN_MIN_MAGIC;
> -    }
> -
> -    if (dest >= min_magic) {
> -        /*
> -         * This is an exception return magic value; put it where
> -         * do_v7m_exception_exit() expects and raise EXCEPTION_EXIT.
> -         * Note that if we ever add gen_ss_advance() singlestep support to
> -         * M profile this should count as an "instruction execution complete"
> -         * event (compare gen_bx_excret_final_code()).
> -         */
> -        env->regs[15] = dest & ~1;
> -        env->thumb = dest & 1;
> -        HELPER(exception_internal)(env, EXCP_EXCEPTION_EXIT);
> -        /* notreached */
> -    }
> -
> -    /* translate.c should have made BXNS UNDEF unless we're secure */
> -    assert(env->v7m.secure);
> -
> -    if (!(dest & 1)) {
> -        env->v7m.control[M_REG_S] &= ~R_V7M_CONTROL_SFPA_MASK;
> -    }
> -    switch_v7m_security_state(env, dest & 1);
> -    env->thumb = 1;
> -    env->regs[15] = dest & ~1;
> -}
> -
> -void HELPER(v7m_blxns)(CPUARMState *env, uint32_t dest)
> -{
> -    /*
> -     * Handle v7M BLXNS:
> -     *  - bit 0 of the destination address is the target security state
> -     */
> -
> -    /* At this point regs[15] is the address just after the BLXNS */
> -    uint32_t nextinst = env->regs[15] | 1;
> -    uint32_t sp = env->regs[13] - 8;
> -    uint32_t saved_psr;
> -
> -    /* translate.c will have made BLXNS UNDEF unless we're secure */
> -    assert(env->v7m.secure);
> -
> -    if (dest & 1) {
> -        /*
> -         * Target is Secure, so this is just a normal BLX,
> -         * except that the low bit doesn't indicate Thumb/not.
> -         */
> -        env->regs[14] = nextinst;
> -        env->thumb = 1;
> -        env->regs[15] = dest & ~1;
> -        return;
> -    }
> -
> -    /* Target is non-secure: first push a stack frame */
> -    if (!QEMU_IS_ALIGNED(sp, 8)) {
> -        qemu_log_mask(LOG_GUEST_ERROR,
> -                      "BLXNS with misaligned SP is UNPREDICTABLE\n");
> -    }
> -
> -    if (sp < v7m_sp_limit(env)) {
> -        raise_exception(env, EXCP_STKOF, 0, 1);
> -    }
> -
> -    saved_psr = env->v7m.exception;
> -    if (env->v7m.control[M_REG_S] & R_V7M_CONTROL_SFPA_MASK) {
> -        saved_psr |= XPSR_SFPA;
> -    }
> -
> -    /* Note that these stores can throw exceptions on MPU faults */
> -    cpu_stl_data(env, sp, nextinst);
> -    cpu_stl_data(env, sp + 4, saved_psr);
> -
> -    env->regs[13] = sp;
> -    env->regs[14] = 0xfeffffff;
> -    if (arm_v7m_is_handler_mode(env)) {
> -        /*
> -         * Write a dummy value to IPSR, to avoid leaking the current secure
> -         * exception number to non-secure code. This is guaranteed not
> -         * to cause write_v7m_exception() to actually change stacks.
> -         */
> -        write_v7m_exception(env, 1);
> -    }
> -    env->v7m.control[M_REG_S] &= ~R_V7M_CONTROL_SFPA_MASK;
> -    switch_v7m_security_state(env, 0);
> -    env->thumb = 1;
> -    env->regs[15] = dest;
> -}
> -
>  static uint32_t *get_v7m_sp_ptr(CPUARMState *env, bool secure, bool threadmode,
>                                  bool spsel)
>  {
> @@ -12760,466 +12587,6 @@ hwaddr arm_cpu_get_phys_page_attrs_debug(CPUState *cs, vaddr addr,
>      return phys_addr;
>  }
>
> -uint32_t HELPER(v7m_mrs)(CPUARMState *env, uint32_t reg)
> -{
> -    uint32_t mask;
> -    unsigned el = arm_current_el(env);
> -
> -    /* First handle registers which unprivileged can read */
> -
> -    switch (reg) {
> -    case 0 ... 7: /* xPSR sub-fields */
> -        mask = 0;
> -        if ((reg & 1) && el) {
> -            mask |= XPSR_EXCP; /* IPSR (unpriv. reads as zero) */
> -        }
> -        if (!(reg & 4)) {
> -            mask |= XPSR_NZCV | XPSR_Q; /* APSR */
> -            if (arm_feature(env, ARM_FEATURE_THUMB_DSP)) {
> -                mask |= XPSR_GE;
> -            }
> -        }
> -        /* EPSR reads as zero */
> -        return xpsr_read(env) & mask;
> -        break;
> -    case 20: /* CONTROL */
> -    {
> -        uint32_t value = env->v7m.control[env->v7m.secure];
> -        if (!env->v7m.secure) {
> -            /* SFPA is RAZ/WI from NS; FPCA is stored in the M_REG_S bank */
> -            value |= env->v7m.control[M_REG_S] & R_V7M_CONTROL_FPCA_MASK;
> -        }
> -        return value;
> -    }
> -    case 0x94: /* CONTROL_NS */
> -        /*
> -         * We have to handle this here because unprivileged Secure code
> -         * can read the NS CONTROL register.
> -         */
> -        if (!env->v7m.secure) {
> -            return 0;
> -        }
> -        return env->v7m.control[M_REG_NS] |
> -            (env->v7m.control[M_REG_S] & R_V7M_CONTROL_FPCA_MASK);
> -    }
> -
> -    if (el == 0) {
> -        return 0; /* unprivileged reads others as zero */
> -    }
> -
> -    if (arm_feature(env, ARM_FEATURE_M_SECURITY)) {
> -        switch (reg) {
> -        case 0x88: /* MSP_NS */
> -            if (!env->v7m.secure) {
> -                return 0;
> -            }
> -            return env->v7m.other_ss_msp;
> -        case 0x89: /* PSP_NS */
> -            if (!env->v7m.secure) {
> -                return 0;
> -            }
> -            return env->v7m.other_ss_psp;
> -        case 0x8a: /* MSPLIM_NS */
> -            if (!env->v7m.secure) {
> -                return 0;
> -            }
> -            return env->v7m.msplim[M_REG_NS];
> -        case 0x8b: /* PSPLIM_NS */
> -            if (!env->v7m.secure) {
> -                return 0;
> -            }
> -            return env->v7m.psplim[M_REG_NS];
> -        case 0x90: /* PRIMASK_NS */
> -            if (!env->v7m.secure) {
> -                return 0;
> -            }
> -            return env->v7m.primask[M_REG_NS];
> -        case 0x91: /* BASEPRI_NS */
> -            if (!env->v7m.secure) {
> -                return 0;
> -            }
> -            return env->v7m.basepri[M_REG_NS];
> -        case 0x93: /* FAULTMASK_NS */
> -            if (!env->v7m.secure) {
> -                return 0;
> -            }
> -            return env->v7m.faultmask[M_REG_NS];
> -        case 0x98: /* SP_NS */
> -        {
> -            /*
> -             * This gives the non-secure SP selected based on whether we're
> -             * currently in handler mode or not, using the NS CONTROL.SPSEL.
> -             */
> -            bool spsel = env->v7m.control[M_REG_NS] & R_V7M_CONTROL_SPSEL_MASK;
> -
> -            if (!env->v7m.secure) {
> -                return 0;
> -            }
> -            if (!arm_v7m_is_handler_mode(env) && spsel) {
> -                return env->v7m.other_ss_psp;
> -            } else {
> -                return env->v7m.other_ss_msp;
> -            }
> -        }
> -        default:
> -            break;
> -        }
> -    }
> -
> -    switch (reg) {
> -    case 8: /* MSP */
> -        return v7m_using_psp(env) ? env->v7m.other_sp : env->regs[13];
> -    case 9: /* PSP */
> -        return v7m_using_psp(env) ? env->regs[13] : env->v7m.other_sp;
> -    case 10: /* MSPLIM */
> -        if (!arm_feature(env, ARM_FEATURE_V8)) {
> -            goto bad_reg;
> -        }
> -        return env->v7m.msplim[env->v7m.secure];
> -    case 11: /* PSPLIM */
> -        if (!arm_feature(env, ARM_FEATURE_V8)) {
> -            goto bad_reg;
> -        }
> -        return env->v7m.psplim[env->v7m.secure];
> -    case 16: /* PRIMASK */
> -        return env->v7m.primask[env->v7m.secure];
> -    case 17: /* BASEPRI */
> -    case 18: /* BASEPRI_MAX */
> -        return env->v7m.basepri[env->v7m.secure];
> -    case 19: /* FAULTMASK */
> -        return env->v7m.faultmask[env->v7m.secure];
> -    default:
> -    bad_reg:
> -        qemu_log_mask(LOG_GUEST_ERROR, "Attempt to read unknown special"
> -                                       " register %d\n", reg);
> -        return 0;
> -    }
> -}
> -
> -void HELPER(v7m_msr)(CPUARMState *env, uint32_t maskreg, uint32_t val)
> -{
> -    /*
> -     * We're passed bits [11..0] of the instruction; extract
> -     * SYSm and the mask bits.
> -     * Invalid combinations of SYSm and mask are UNPREDICTABLE;
> -     * we choose to treat them as if the mask bits were valid.
> -     * NB that the pseudocode 'mask' variable is bits [11..10],
> -     * whereas ours is [11..8].
> -     */
> -    uint32_t mask = extract32(maskreg, 8, 4);
> -    uint32_t reg = extract32(maskreg, 0, 8);
> -    int cur_el = arm_current_el(env);
> -
> -    if (cur_el == 0 && reg > 7 && reg != 20) {
> -        /*
> -         * only xPSR sub-fields and CONTROL.SFPA may be written by
> -         * unprivileged code
> -         */
> -        return;
> -    }
> -
> -    if (arm_feature(env, ARM_FEATURE_M_SECURITY)) {
> -        switch (reg) {
> -        case 0x88: /* MSP_NS */
> -            if (!env->v7m.secure) {
> -                return;
> -            }
> -            env->v7m.other_ss_msp = val;
> -            return;
> -        case 0x89: /* PSP_NS */
> -            if (!env->v7m.secure) {
> -                return;
> -            }
> -            env->v7m.other_ss_psp = val;
> -            return;
> -        case 0x8a: /* MSPLIM_NS */
> -            if (!env->v7m.secure) {
> -                return;
> -            }
> -            env->v7m.msplim[M_REG_NS] = val & ~7;
> -            return;
> -        case 0x8b: /* PSPLIM_NS */
> -            if (!env->v7m.secure) {
> -                return;
> -            }
> -            env->v7m.psplim[M_REG_NS] = val & ~7;
> -            return;
> -        case 0x90: /* PRIMASK_NS */
> -            if (!env->v7m.secure) {
> -                return;
> -            }
> -            env->v7m.primask[M_REG_NS] = val & 1;
> -            return;
> -        case 0x91: /* BASEPRI_NS */
> -            if (!env->v7m.secure || !arm_feature(env, ARM_FEATURE_M_MAIN)) {
> -                return;
> -            }
> -            env->v7m.basepri[M_REG_NS] = val & 0xff;
> -            return;
> -        case 0x93: /* FAULTMASK_NS */
> -            if (!env->v7m.secure || !arm_feature(env, ARM_FEATURE_M_MAIN)) {
> -                return;
> -            }
> -            env->v7m.faultmask[M_REG_NS] = val & 1;
> -            return;
> -        case 0x94: /* CONTROL_NS */
> -            if (!env->v7m.secure) {
> -                return;
> -            }
> -            write_v7m_control_spsel_for_secstate(env,
> -                                                 val & R_V7M_CONTROL_SPSEL_MASK,
> -                                                 M_REG_NS);
> -            if (arm_feature(env, ARM_FEATURE_M_MAIN)) {
> -                env->v7m.control[M_REG_NS] &= ~R_V7M_CONTROL_NPRIV_MASK;
> -                env->v7m.control[M_REG_NS] |= val & R_V7M_CONTROL_NPRIV_MASK;
> -            }
> -            /*
> -             * SFPA is RAZ/WI from NS. FPCA is RO if NSACR.CP10 == 0,
> -             * RES0 if the FPU is not present, and is stored in the S bank
> -             */
> -            if (arm_feature(env, ARM_FEATURE_VFP) &&
> -                extract32(env->v7m.nsacr, 10, 1)) {
> -                env->v7m.control[M_REG_S] &= ~R_V7M_CONTROL_FPCA_MASK;
> -                env->v7m.control[M_REG_S] |= val & R_V7M_CONTROL_FPCA_MASK;
> -            }
> -            return;
> -        case 0x98: /* SP_NS */
> -        {
> -            /*
> -             * This gives the non-secure SP selected based on whether we're
> -             * currently in handler mode or not, using the NS CONTROL.SPSEL.
> -             */
> -            bool spsel = env->v7m.control[M_REG_NS] & R_V7M_CONTROL_SPSEL_MASK;
> -            bool is_psp = !arm_v7m_is_handler_mode(env) && spsel;
> -            uint32_t limit;
> -
> -            if (!env->v7m.secure) {
> -                return;
> -            }
> -
> -            limit = is_psp ? env->v7m.psplim[false] : env->v7m.msplim[false];
> -
> -            if (val < limit) {
> -                CPUState *cs = env_cpu(env);
> -
> -                cpu_restore_state(cs, GETPC(), true);
> -                raise_exception(env, EXCP_STKOF, 0, 1);
> -            }
> -
> -            if (is_psp) {
> -                env->v7m.other_ss_psp = val;
> -            } else {
> -                env->v7m.other_ss_msp = val;
> -            }
> -            return;
> -        }
> -        default:
> -            break;
> -        }
> -    }
> -
> -    switch (reg) {
> -    case 0 ... 7: /* xPSR sub-fields */
> -        /* only APSR is actually writable */
> -        if (!(reg & 4)) {
> -            uint32_t apsrmask = 0;
> -
> -            if (mask & 8) {
> -                apsrmask |= XPSR_NZCV | XPSR_Q;
> -            }
> -            if ((mask & 4) && arm_feature(env, ARM_FEATURE_THUMB_DSP)) {
> -                apsrmask |= XPSR_GE;
> -            }
> -            xpsr_write(env, val, apsrmask);
> -        }
> -        break;
> -    case 8: /* MSP */
> -        if (v7m_using_psp(env)) {
> -            env->v7m.other_sp = val;
> -        } else {
> -            env->regs[13] = val;
> -        }
> -        break;
> -    case 9: /* PSP */
> -        if (v7m_using_psp(env)) {
> -            env->regs[13] = val;
> -        } else {
> -            env->v7m.other_sp = val;
> -        }
> -        break;
> -    case 10: /* MSPLIM */
> -        if (!arm_feature(env, ARM_FEATURE_V8)) {
> -            goto bad_reg;
> -        }
> -        env->v7m.msplim[env->v7m.secure] = val & ~7;
> -        break;
> -    case 11: /* PSPLIM */
> -        if (!arm_feature(env, ARM_FEATURE_V8)) {
> -            goto bad_reg;
> -        }
> -        env->v7m.psplim[env->v7m.secure] = val & ~7;
> -        break;
> -    case 16: /* PRIMASK */
> -        env->v7m.primask[env->v7m.secure] = val & 1;
> -        break;
> -    case 17: /* BASEPRI */
> -        if (!arm_feature(env, ARM_FEATURE_M_MAIN)) {
> -            goto bad_reg;
> -        }
> -        env->v7m.basepri[env->v7m.secure] = val & 0xff;
> -        break;
> -    case 18: /* BASEPRI_MAX */
> -        if (!arm_feature(env, ARM_FEATURE_M_MAIN)) {
> -            goto bad_reg;
> -        }
> -        val &= 0xff;
> -        if (val != 0 && (val < env->v7m.basepri[env->v7m.secure]
> -                         || env->v7m.basepri[env->v7m.secure] == 0)) {
> -            env->v7m.basepri[env->v7m.secure] = val;
> -        }
> -        break;
> -    case 19: /* FAULTMASK */
> -        if (!arm_feature(env, ARM_FEATURE_M_MAIN)) {
> -            goto bad_reg;
> -        }
> -        env->v7m.faultmask[env->v7m.secure] = val & 1;
> -        break;
> -    case 20: /* CONTROL */
> -        /*
> -         * Writing to the SPSEL bit only has an effect if we are in
> -         * thread mode; other bits can be updated by any privileged code.
> -         * write_v7m_control_spsel() deals with updating the SPSEL bit in
> -         * env->v7m.control, so we only need update the others.
> -         * For v7M, we must just ignore explicit writes to SPSEL in handler
> -         * mode; for v8M the write is permitted but will have no effect.
> -         * All these bits are writes-ignored from non-privileged code,
> -         * except for SFPA.
> -         */
> -        if (cur_el > 0 && (arm_feature(env, ARM_FEATURE_V8) ||
> -                           !arm_v7m_is_handler_mode(env))) {
> -            write_v7m_control_spsel(env, (val & R_V7M_CONTROL_SPSEL_MASK) != 0);
> -        }
> -        if (cur_el > 0 && arm_feature(env, ARM_FEATURE_M_MAIN)) {
> -            env->v7m.control[env->v7m.secure] &= ~R_V7M_CONTROL_NPRIV_MASK;
> -            env->v7m.control[env->v7m.secure] |= val & R_V7M_CONTROL_NPRIV_MASK;
> -        }
> -        if (arm_feature(env, ARM_FEATURE_VFP)) {
> -            /*
> -             * SFPA is RAZ/WI from NS or if no FPU.
> -             * FPCA is RO if NSACR.CP10 == 0, RES0 if the FPU is not present.
> -             * Both are stored in the S bank.
> -             */
> -            if (env->v7m.secure) {
> -                env->v7m.control[M_REG_S] &= ~R_V7M_CONTROL_SFPA_MASK;
> -                env->v7m.control[M_REG_S] |= val & R_V7M_CONTROL_SFPA_MASK;
> -            }
> -            if (cur_el > 0 &&
> -                (env->v7m.secure || !arm_feature(env, ARM_FEATURE_M_SECURITY) ||
> -                 extract32(env->v7m.nsacr, 10, 1))) {
> -                env->v7m.control[M_REG_S] &= ~R_V7M_CONTROL_FPCA_MASK;
> -                env->v7m.control[M_REG_S] |= val & R_V7M_CONTROL_FPCA_MASK;
> -            }
> -        }
> -        break;
> -    default:
> -    bad_reg:
> -        qemu_log_mask(LOG_GUEST_ERROR, "Attempt to write unknown special"
> -                                       " register %d\n", reg);
> -        return;
> -    }
> -}
> -
> -uint32_t HELPER(v7m_tt)(CPUARMState *env, uint32_t addr, uint32_t op)
> -{
> -    /* Implement the TT instruction. op is bits [7:6] of the insn. */
> -    bool forceunpriv = op & 1;
> -    bool alt = op & 2;
> -    V8M_SAttributes sattrs = {};
> -    uint32_t tt_resp;
> -    bool r, rw, nsr, nsrw, mrvalid;
> -    int prot;
> -    ARMMMUFaultInfo fi = {};
> -    MemTxAttrs attrs = {};
> -    hwaddr phys_addr;
> -    ARMMMUIdx mmu_idx;
> -    uint32_t mregion;
> -    bool targetpriv;
> -    bool targetsec = env->v7m.secure;
> -    bool is_subpage;
> -
> -    /*
> -     * Work out what the security state and privilege level we're
> -     * interested in is...
> -     */
> -    if (alt) {
> -        targetsec = !targetsec;
> -    }
> -
> -    if (forceunpriv) {
> -        targetpriv = false;
> -    } else {
> -        targetpriv = arm_v7m_is_handler_mode(env) ||
> -            !(env->v7m.control[targetsec] & R_V7M_CONTROL_NPRIV_MASK);
> -    }
> -
> -    /* ...and then figure out which MMU index this is */
> -    mmu_idx = arm_v7m_mmu_idx_for_secstate_and_priv(env, targetsec, targetpriv);
> -
> -    /*
> -     * We know that the MPU and SAU don't care about the access type
> -     * for our purposes beyond that we don't want to claim to be
> -     * an insn fetch, so we arbitrarily call this a read.
> -     */
> -
> -    /*
> -     * MPU region info only available for privileged or if
> -     * inspecting the other MPU state.
> -     */
> -    if (arm_current_el(env) != 0 || alt) {
> -        /* We can ignore the return value as prot is always set */
> -        pmsav8_mpu_lookup(env, addr, MMU_DATA_LOAD, mmu_idx,
> -                          &phys_addr, &attrs, &prot, &is_subpage,
> -                          &fi, &mregion);
> -        if (mregion == -1) {
> -            mrvalid = false;
> -            mregion = 0;
> -        } else {
> -            mrvalid = true;
> -        }
> -        r = prot & PAGE_READ;
> -        rw = prot & PAGE_WRITE;
> -    } else {
> -        r = false;
> -        rw = false;
> -        mrvalid = false;
> -        mregion = 0;
> -    }
> -
> -    if (env->v7m.secure) {
> -        v8m_security_lookup(env, addr, MMU_DATA_LOAD, mmu_idx, &sattrs);
> -        nsr = sattrs.ns && r;
> -        nsrw = sattrs.ns && rw;
> -    } else {
> -        sattrs.ns = true;
> -        nsr = false;
> -        nsrw = false;
> -    }
> -
> -    tt_resp = (sattrs.iregion << 24) |
> -        (sattrs.irvalid << 23) |
> -        ((!sattrs.ns) << 22) |
> -        (nsrw << 21) |
> -        (nsr << 20) |
> -        (rw << 19) |
> -        (r << 18) |
> -        (sattrs.srvalid << 17) |
> -        (mrvalid << 16) |
> -        (sattrs.sregion << 8) |
> -        mregion;
> -
> -    return tt_resp;
> -}
> -
>  #endif
>
>  bool arm_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
> diff --git a/target/arm/v7m_helper.c b/target/arm/v7m_helper.c
> new file mode 100644
> index 0000000000..321154966e
> --- /dev/null
> +++ b/target/arm/v7m_helper.c
> @@ -0,0 +1,654 @@
> +/*
> + * ARM v7-M helpers.
> + *
> + * This code is licensed under the GNU GPL v2 or later.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +#include "qemu/osdep.h"
> +#include "sysemu/sysemu.h"
> +#include "cpu.h"
> +#include "internals.h"
> +#include "exec/helper-proto.h"
> +#include "exec/exec-all.h"
> +#include "arm_ldst.h"
> +#include "hw/semihosting/semihost.h"
> +#include "fpu/softfloat.h"
> +
> +#if defined(CONFIG_USER_ONLY)
> +
> +/* These should probably raise undefined insn exceptions.  */
> +void HELPER(v7m_msr)(CPUARMState *env, uint32_t reg, uint32_t val)
> +{
> +    ARMCPU *cpu = env_archcpu(env);
> +
> +    cpu_abort(CPU(cpu), "v7m_msr %d\n", reg);
> +}
> +
> +uint32_t HELPER(v7m_mrs)(CPUARMState *env, uint32_t reg)
> +{
> +    ARMCPU *cpu = env_archcpu(env);
> +
> +    cpu_abort(CPU(cpu), "v7m_mrs %d\n", reg);
> +    return 0;
> +}
> +
> +void HELPER(v7m_bxns)(CPUARMState *env, uint32_t dest)
> +{
> +    /* translate.c should never generate calls here in user-only mode */
> +    g_assert_not_reached();
> +}
> +
> +void HELPER(v7m_blxns)(CPUARMState *env, uint32_t dest)
> +{
> +    /* translate.c should never generate calls here in user-only mode */
> +    g_assert_not_reached();
> +}
> +
> +void HELPER(v7m_preserve_fp_state)(CPUARMState *env)
> +{
> +    /* translate.c should never generate calls here in user-only mode */
> +    g_assert_not_reached();
> +}
> +
> +void HELPER(v7m_vlstm)(CPUARMState *env, uint32_t fptr)
> +{
> +    /* translate.c should never generate calls here in user-only mode */
> +    g_assert_not_reached();
> +}
> +
> +void HELPER(v7m_vlldm)(CPUARMState *env, uint32_t fptr)
> +{
> +    /* translate.c should never generate calls here in user-only mode */
> +    g_assert_not_reached();
> +}
> +
> +uint32_t HELPER(v7m_tt)(CPUARMState *env, uint32_t addr, uint32_t op)
> +{
> +    /*
> +     * The TT instructions can be used by unprivileged code, but in
> +     * user-only emulation we don't have the MPU.
> +     * Luckily since we know we are NonSecure unprivileged (and that in
> +     * turn means that the A flag wasn't specified), all the bits in the
> +     * register must be zero:
> +     *  IREGION: 0 because IRVALID is 0
> +     *  IRVALID: 0 because NS
> +     *  S: 0 because NS
> +     *  NSRW: 0 because NS
> +     *  NSR: 0 because NS
> +     *  RW: 0 because unpriv and A flag not set
> +     *  R: 0 because unpriv and A flag not set
> +     *  SRVALID: 0 because NS
> +     *  MRVALID: 0 because unpriv and A flag not set
> +     *  SREGION: 0 becaus SRVALID is 0
> +     *  MREGION: 0 because MRVALID is 0
> +     */
> +    return 0;
> +}
> +
> +#else
> +
> +void HELPER(v7m_bxns)(CPUARMState *env, uint32_t dest)
> +{
> +    /*
> +     * Handle v7M BXNS:
> +     *  - if the return value is a magic value, do exception return (like BX)
> +     *  - otherwise bit 0 of the return value is the target security state
> +     */
> +    uint32_t min_magic;
> +
> +    if (arm_feature(env, ARM_FEATURE_M_SECURITY)) {
> +        /* Covers FNC_RETURN and EXC_RETURN magic */
> +        min_magic = FNC_RETURN_MIN_MAGIC;
> +    } else {
> +        /* EXC_RETURN magic only */
> +        min_magic = EXC_RETURN_MIN_MAGIC;
> +    }
> +
> +    if (dest >= min_magic) {
> +        /*
> +         * This is an exception return magic value; put it where
> +         * do_v7m_exception_exit() expects and raise EXCEPTION_EXIT.
> +         * Note that if we ever add gen_ss_advance() singlestep support to
> +         * M profile this should count as an "instruction execution complete"
> +         * event (compare gen_bx_excret_final_code()).
> +         */
> +        env->regs[15] = dest & ~1;
> +        env->thumb = dest & 1;
> +        HELPER(exception_internal)(env, EXCP_EXCEPTION_EXIT);
> +        /* notreached */
> +    }
> +
> +    /* translate.c should have made BXNS UNDEF unless we're secure */
> +    assert(env->v7m.secure);
> +
> +    if (!(dest & 1)) {
> +        env->v7m.control[M_REG_S] &= ~R_V7M_CONTROL_SFPA_MASK;
> +    }
> +    switch_v7m_security_state(env, dest & 1);
> +    env->thumb = 1;
> +    env->regs[15] = dest & ~1;
> +}
> +
> +void HELPER(v7m_blxns)(CPUARMState *env, uint32_t dest)
> +{
> +    /*
> +     * Handle v7M BLXNS:
> +     *  - bit 0 of the destination address is the target security state
> +     */
> +
> +    /* At this point regs[15] is the address just after the BLXNS */
> +    uint32_t nextinst = env->regs[15] | 1;
> +    uint32_t sp = env->regs[13] - 8;
> +    uint32_t saved_psr;
> +
> +    /* translate.c will have made BLXNS UNDEF unless we're secure */
> +    assert(env->v7m.secure);
> +
> +    if (dest & 1) {
> +        /*
> +         * Target is Secure, so this is just a normal BLX,
> +         * except that the low bit doesn't indicate Thumb/not.
> +         */
> +        env->regs[14] = nextinst;
> +        env->thumb = 1;
> +        env->regs[15] = dest & ~1;
> +        return;
> +    }
> +
> +    /* Target is non-secure: first push a stack frame */
> +    if (!QEMU_IS_ALIGNED(sp, 8)) {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                      "BLXNS with misaligned SP is UNPREDICTABLE\n");
> +    }
> +
> +    if (sp < v7m_sp_limit(env)) {
> +        raise_exception(env, EXCP_STKOF, 0, 1);
> +    }
> +
> +    saved_psr = env->v7m.exception;
> +    if (env->v7m.control[M_REG_S] & R_V7M_CONTROL_SFPA_MASK) {
> +        saved_psr |= XPSR_SFPA;
> +    }
> +
> +    /* Note that these stores can throw exceptions on MPU faults */
> +    cpu_stl_data(env, sp, nextinst);
> +    cpu_stl_data(env, sp + 4, saved_psr);
> +
> +    env->regs[13] = sp;
> +    env->regs[14] = 0xfeffffff;
> +    if (arm_v7m_is_handler_mode(env)) {
> +        /*
> +         * Write a dummy value to IPSR, to avoid leaking the current secure
> +         * exception number to non-secure code. This is guaranteed not
> +         * to cause write_v7m_exception() to actually change stacks.
> +         */
> +        write_v7m_exception(env, 1);
> +    }
> +    env->v7m.control[M_REG_S] &= ~R_V7M_CONTROL_SFPA_MASK;
> +    switch_v7m_security_state(env, 0);
> +    env->thumb = 1;
> +    env->regs[15] = dest;
> +}
> +
> +uint32_t HELPER(v7m_mrs)(CPUARMState *env, uint32_t reg)
> +{
> +    uint32_t mask;
> +    unsigned el = arm_current_el(env);
> +
> +    /* First handle registers which unprivileged can read */
> +
> +    switch (reg) {
> +    case 0 ... 7: /* xPSR sub-fields */
> +        mask = 0;
> +        if ((reg & 1) && el) {
> +            mask |= XPSR_EXCP; /* IPSR (unpriv. reads as zero) */
> +        }
> +        if (!(reg & 4)) {
> +            mask |= XPSR_NZCV | XPSR_Q; /* APSR */
> +            if (arm_feature(env, ARM_FEATURE_THUMB_DSP)) {
> +                mask |= XPSR_GE;
> +            }
> +        }
> +        /* EPSR reads as zero */
> +        return xpsr_read(env) & mask;
> +        break;
> +    case 20: /* CONTROL */
> +    {
> +        uint32_t value = env->v7m.control[env->v7m.secure];
> +        if (!env->v7m.secure) {
> +            /* SFPA is RAZ/WI from NS; FPCA is stored in the M_REG_S bank */
> +            value |= env->v7m.control[M_REG_S] & R_V7M_CONTROL_FPCA_MASK;
> +        }
> +        return value;
> +    }
> +    case 0x94: /* CONTROL_NS */
> +        /*
> +         * We have to handle this here because unprivileged Secure code
> +         * can read the NS CONTROL register.
> +         */
> +        if (!env->v7m.secure) {
> +            return 0;
> +        }
> +        return env->v7m.control[M_REG_NS] |
> +            (env->v7m.control[M_REG_S] & R_V7M_CONTROL_FPCA_MASK);
> +    }
> +
> +    if (el == 0) {
> +        return 0; /* unprivileged reads others as zero */
> +    }
> +
> +    if (arm_feature(env, ARM_FEATURE_M_SECURITY)) {
> +        switch (reg) {
> +        case 0x88: /* MSP_NS */
> +            if (!env->v7m.secure) {
> +                return 0;
> +            }
> +            return env->v7m.other_ss_msp;
> +        case 0x89: /* PSP_NS */
> +            if (!env->v7m.secure) {
> +                return 0;
> +            }
> +            return env->v7m.other_ss_psp;
> +        case 0x8a: /* MSPLIM_NS */
> +            if (!env->v7m.secure) {
> +                return 0;
> +            }
> +            return env->v7m.msplim[M_REG_NS];
> +        case 0x8b: /* PSPLIM_NS */
> +            if (!env->v7m.secure) {
> +                return 0;
> +            }
> +            return env->v7m.psplim[M_REG_NS];
> +        case 0x90: /* PRIMASK_NS */
> +            if (!env->v7m.secure) {
> +                return 0;
> +            }
> +            return env->v7m.primask[M_REG_NS];
> +        case 0x91: /* BASEPRI_NS */
> +            if (!env->v7m.secure) {
> +                return 0;
> +            }
> +            return env->v7m.basepri[M_REG_NS];
> +        case 0x93: /* FAULTMASK_NS */
> +            if (!env->v7m.secure) {
> +                return 0;
> +            }
> +            return env->v7m.faultmask[M_REG_NS];
> +        case 0x98: /* SP_NS */
> +        {
> +            /*
> +             * This gives the non-secure SP selected based on whether we're
> +             * currently in handler mode or not, using the NS CONTROL.SPSEL.
> +             */
> +            bool spsel = env->v7m.control[M_REG_NS] & R_V7M_CONTROL_SPSEL_MASK;
> +
> +            if (!env->v7m.secure) {
> +                return 0;
> +            }
> +            if (!arm_v7m_is_handler_mode(env) && spsel) {
> +                return env->v7m.other_ss_psp;
> +            } else {
> +                return env->v7m.other_ss_msp;
> +            }
> +        }
> +        default:
> +            break;
> +        }
> +    }
> +
> +    switch (reg) {
> +    case 8: /* MSP */
> +        return v7m_using_psp(env) ? env->v7m.other_sp : env->regs[13];
> +    case 9: /* PSP */
> +        return v7m_using_psp(env) ? env->regs[13] : env->v7m.other_sp;
> +    case 10: /* MSPLIM */
> +        if (!arm_feature(env, ARM_FEATURE_V8)) {
> +            goto bad_reg;
> +        }
> +        return env->v7m.msplim[env->v7m.secure];
> +    case 11: /* PSPLIM */
> +        if (!arm_feature(env, ARM_FEATURE_V8)) {
> +            goto bad_reg;
> +        }
> +        return env->v7m.psplim[env->v7m.secure];
> +    case 16: /* PRIMASK */
> +        return env->v7m.primask[env->v7m.secure];
> +    case 17: /* BASEPRI */
> +    case 18: /* BASEPRI_MAX */
> +        return env->v7m.basepri[env->v7m.secure];
> +    case 19: /* FAULTMASK */
> +        return env->v7m.faultmask[env->v7m.secure];
> +    default:
> +    bad_reg:
> +        qemu_log_mask(LOG_GUEST_ERROR, "Attempt to read unknown special"
> +                                       " register %d\n", reg);
> +        return 0;
> +    }
> +}
> +
> +void HELPER(v7m_msr)(CPUARMState *env, uint32_t maskreg, uint32_t val)
> +{
> +    /*
> +     * We're passed bits [11..0] of the instruction; extract
> +     * SYSm and the mask bits.
> +     * Invalid combinations of SYSm and mask are UNPREDICTABLE;
> +     * we choose to treat them as if the mask bits were valid.
> +     * NB that the pseudocode 'mask' variable is bits [11..10],
> +     * whereas ours is [11..8].
> +     */
> +    uint32_t mask = extract32(maskreg, 8, 4);
> +    uint32_t reg = extract32(maskreg, 0, 8);
> +    int cur_el = arm_current_el(env);
> +
> +    if (cur_el == 0 && reg > 7 && reg != 20) {
> +        /*
> +         * only xPSR sub-fields and CONTROL.SFPA may be written by
> +         * unprivileged code
> +         */
> +        return;
> +    }
> +
> +    if (arm_feature(env, ARM_FEATURE_M_SECURITY)) {
> +        switch (reg) {
> +        case 0x88: /* MSP_NS */
> +            if (!env->v7m.secure) {
> +                return;
> +            }
> +            env->v7m.other_ss_msp = val;
> +            return;
> +        case 0x89: /* PSP_NS */
> +            if (!env->v7m.secure) {
> +                return;
> +            }
> +            env->v7m.other_ss_psp = val;
> +            return;
> +        case 0x8a: /* MSPLIM_NS */
> +            if (!env->v7m.secure) {
> +                return;
> +            }
> +            env->v7m.msplim[M_REG_NS] = val & ~7;
> +            return;
> +        case 0x8b: /* PSPLIM_NS */
> +            if (!env->v7m.secure) {
> +                return;
> +            }
> +            env->v7m.psplim[M_REG_NS] = val & ~7;
> +            return;
> +        case 0x90: /* PRIMASK_NS */
> +            if (!env->v7m.secure) {
> +                return;
> +            }
> +            env->v7m.primask[M_REG_NS] = val & 1;
> +            return;
> +        case 0x91: /* BASEPRI_NS */
> +            if (!env->v7m.secure || !arm_feature(env, ARM_FEATURE_M_MAIN)) {
> +                return;
> +            }
> +            env->v7m.basepri[M_REG_NS] = val & 0xff;
> +            return;
> +        case 0x93: /* FAULTMASK_NS */
> +            if (!env->v7m.secure || !arm_feature(env, ARM_FEATURE_M_MAIN)) {
> +                return;
> +            }
> +            env->v7m.faultmask[M_REG_NS] = val & 1;
> +            return;
> +        case 0x94: /* CONTROL_NS */
> +            if (!env->v7m.secure) {
> +                return;
> +            }
> +            write_v7m_control_spsel_for_secstate(env,
> +                                                 val & R_V7M_CONTROL_SPSEL_MASK,
> +                                                 M_REG_NS);
> +            if (arm_feature(env, ARM_FEATURE_M_MAIN)) {
> +                env->v7m.control[M_REG_NS] &= ~R_V7M_CONTROL_NPRIV_MASK;
> +                env->v7m.control[M_REG_NS] |= val & R_V7M_CONTROL_NPRIV_MASK;
> +            }
> +            /*
> +             * SFPA is RAZ/WI from NS. FPCA is RO if NSACR.CP10 == 0,
> +             * RES0 if the FPU is not present, and is stored in the S bank
> +             */
> +            if (arm_feature(env, ARM_FEATURE_VFP) &&
> +                extract32(env->v7m.nsacr, 10, 1)) {
> +                env->v7m.control[M_REG_S] &= ~R_V7M_CONTROL_FPCA_MASK;
> +                env->v7m.control[M_REG_S] |= val & R_V7M_CONTROL_FPCA_MASK;
> +            }
> +            return;
> +        case 0x98: /* SP_NS */
> +        {
> +            /*
> +             * This gives the non-secure SP selected based on whether we're
> +             * currently in handler mode or not, using the NS CONTROL.SPSEL.
> +             */
> +            bool spsel = env->v7m.control[M_REG_NS] & R_V7M_CONTROL_SPSEL_MASK;
> +            bool is_psp = !arm_v7m_is_handler_mode(env) && spsel;
> +            uint32_t limit;
> +
> +            if (!env->v7m.secure) {
> +                return;
> +            }
> +
> +            limit = is_psp ? env->v7m.psplim[false] : env->v7m.msplim[false];
> +
> +            if (val < limit) {
> +                CPUState *cs = env_cpu(env);
> +
> +                cpu_restore_state(cs, GETPC(), true);
> +                raise_exception(env, EXCP_STKOF, 0, 1);
> +            }
> +
> +            if (is_psp) {
> +                env->v7m.other_ss_psp = val;
> +            } else {
> +                env->v7m.other_ss_msp = val;
> +            }
> +            return;
> +        }
> +        default:
> +            break;
> +        }
> +    }
> +
> +    switch (reg) {
> +    case 0 ... 7: /* xPSR sub-fields */
> +        /* only APSR is actually writable */
> +        if (!(reg & 4)) {
> +            uint32_t apsrmask = 0;
> +
> +            if (mask & 8) {
> +                apsrmask |= XPSR_NZCV | XPSR_Q;
> +            }
> +            if ((mask & 4) && arm_feature(env, ARM_FEATURE_THUMB_DSP)) {
> +                apsrmask |= XPSR_GE;
> +            }
> +            xpsr_write(env, val, apsrmask);
> +        }
> +        break;
> +    case 8: /* MSP */
> +        if (v7m_using_psp(env)) {
> +            env->v7m.other_sp = val;
> +        } else {
> +            env->regs[13] = val;
> +        }
> +        break;
> +    case 9: /* PSP */
> +        if (v7m_using_psp(env)) {
> +            env->regs[13] = val;
> +        } else {
> +            env->v7m.other_sp = val;
> +        }
> +        break;
> +    case 10: /* MSPLIM */
> +        if (!arm_feature(env, ARM_FEATURE_V8)) {
> +            goto bad_reg;
> +        }
> +        env->v7m.msplim[env->v7m.secure] = val & ~7;
> +        break;
> +    case 11: /* PSPLIM */
> +        if (!arm_feature(env, ARM_FEATURE_V8)) {
> +            goto bad_reg;
> +        }
> +        env->v7m.psplim[env->v7m.secure] = val & ~7;
> +        break;
> +    case 16: /* PRIMASK */
> +        env->v7m.primask[env->v7m.secure] = val & 1;
> +        break;
> +    case 17: /* BASEPRI */
> +        if (!arm_feature(env, ARM_FEATURE_M_MAIN)) {
> +            goto bad_reg;
> +        }
> +        env->v7m.basepri[env->v7m.secure] = val & 0xff;
> +        break;
> +    case 18: /* BASEPRI_MAX */
> +        if (!arm_feature(env, ARM_FEATURE_M_MAIN)) {
> +            goto bad_reg;
> +        }
> +        val &= 0xff;
> +        if (val != 0 && (val < env->v7m.basepri[env->v7m.secure]
> +                         || env->v7m.basepri[env->v7m.secure] == 0)) {
> +            env->v7m.basepri[env->v7m.secure] = val;
> +        }
> +        break;
> +    case 19: /* FAULTMASK */
> +        if (!arm_feature(env, ARM_FEATURE_M_MAIN)) {
> +            goto bad_reg;
> +        }
> +        env->v7m.faultmask[env->v7m.secure] = val & 1;
> +        break;
> +    case 20: /* CONTROL */
> +        /*
> +         * Writing to the SPSEL bit only has an effect if we are in
> +         * thread mode; other bits can be updated by any privileged code.
> +         * write_v7m_control_spsel() deals with updating the SPSEL bit in
> +         * env->v7m.control, so we only need update the others.
> +         * For v7M, we must just ignore explicit writes to SPSEL in handler
> +         * mode; for v8M the write is permitted but will have no effect.
> +         * All these bits are writes-ignored from non-privileged code,
> +         * except for SFPA.
> +         */
> +        if (cur_el > 0 && (arm_feature(env, ARM_FEATURE_V8) ||
> +                           !arm_v7m_is_handler_mode(env))) {
> +            write_v7m_control_spsel(env, (val & R_V7M_CONTROL_SPSEL_MASK) != 0);
> +        }
> +        if (cur_el > 0 && arm_feature(env, ARM_FEATURE_M_MAIN)) {
> +            env->v7m.control[env->v7m.secure] &= ~R_V7M_CONTROL_NPRIV_MASK;
> +            env->v7m.control[env->v7m.secure] |= val & R_V7M_CONTROL_NPRIV_MASK;
> +        }
> +        if (arm_feature(env, ARM_FEATURE_VFP)) {
> +            /*
> +             * SFPA is RAZ/WI from NS or if no FPU.
> +             * FPCA is RO if NSACR.CP10 == 0, RES0 if the FPU is not present.
> +             * Both are stored in the S bank.
> +             */
> +            if (env->v7m.secure) {
> +                env->v7m.control[M_REG_S] &= ~R_V7M_CONTROL_SFPA_MASK;
> +                env->v7m.control[M_REG_S] |= val & R_V7M_CONTROL_SFPA_MASK;
> +            }
> +            if (cur_el > 0 &&
> +                (env->v7m.secure || !arm_feature(env, ARM_FEATURE_M_SECURITY) ||
> +                 extract32(env->v7m.nsacr, 10, 1))) {
> +                env->v7m.control[M_REG_S] &= ~R_V7M_CONTROL_FPCA_MASK;
> +                env->v7m.control[M_REG_S] |= val & R_V7M_CONTROL_FPCA_MASK;
> +            }
> +        }
> +        break;
> +    default:
> +    bad_reg:
> +        qemu_log_mask(LOG_GUEST_ERROR, "Attempt to write unknown special"
> +                                       " register %d\n", reg);
> +        return;
> +    }
> +}
> +
> +uint32_t HELPER(v7m_tt)(CPUARMState *env, uint32_t addr, uint32_t op)
> +{
> +    /* Implement the TT instruction. op is bits [7:6] of the insn. */
> +    bool forceunpriv = op & 1;
> +    bool alt = op & 2;
> +    V8M_SAttributes sattrs = {};
> +    uint32_t tt_resp;
> +    bool r, rw, nsr, nsrw, mrvalid;
> +    int prot;
> +    ARMMMUFaultInfo fi = {};
> +    MemTxAttrs attrs = {};
> +    hwaddr phys_addr;
> +    ARMMMUIdx mmu_idx;
> +    uint32_t mregion;
> +    bool targetpriv;
> +    bool targetsec = env->v7m.secure;
> +    bool is_subpage;
> +
> +    /*
> +     * Work out what the security state and privilege level we're
> +     * interested in is...
> +     */
> +    if (alt) {
> +        targetsec = !targetsec;
> +    }
> +
> +    if (forceunpriv) {
> +        targetpriv = false;
> +    } else {
> +        targetpriv = arm_v7m_is_handler_mode(env) ||
> +            !(env->v7m.control[targetsec] & R_V7M_CONTROL_NPRIV_MASK);
> +    }
> +
> +    /* ...and then figure out which MMU index this is */
> +    mmu_idx = arm_v7m_mmu_idx_for_secstate_and_priv(env, targetsec, targetpriv);
> +
> +    /*
> +     * We know that the MPU and SAU don't care about the access type
> +     * for our purposes beyond that we don't want to claim to be
> +     * an insn fetch, so we arbitrarily call this a read.
> +     */
> +
> +    /*
> +     * MPU region info only available for privileged or if
> +     * inspecting the other MPU state.
> +     */
> +    if (arm_current_el(env) != 0 || alt) {
> +        /* We can ignore the return value as prot is always set */
> +        pmsav8_mpu_lookup(env, addr, MMU_DATA_LOAD, mmu_idx,
> +                          &phys_addr, &attrs, &prot, &is_subpage,
> +                          &fi, &mregion);
> +        if (mregion == -1) {
> +            mrvalid = false;
> +            mregion = 0;
> +        } else {
> +            mrvalid = true;
> +        }
> +        r = prot & PAGE_READ;
> +        rw = prot & PAGE_WRITE;
> +    } else {
> +        r = false;
> +        rw = false;
> +        mrvalid = false;
> +        mregion = 0;
> +    }
> +
> +    if (env->v7m.secure) {
> +        v8m_security_lookup(env, addr, MMU_DATA_LOAD, mmu_idx, &sattrs);
> +        nsr = sattrs.ns && r;
> +        nsrw = sattrs.ns && rw;
> +    } else {
> +        sattrs.ns = true;
> +        nsr = false;
> +        nsrw = false;
> +    }
> +
> +    tt_resp = (sattrs.iregion << 24) |
> +        (sattrs.irvalid << 23) |
> +        ((!sattrs.ns) << 22) |
> +        (nsrw << 21) |
> +        (nsr << 20) |
> +        (rw << 19) |
> +        (r << 18) |
> +        (sattrs.srvalid << 17) |
> +        (mrvalid << 16) |
> +        (sattrs.sregion << 8) |
> +        mregion;
> +
> +    return tt_resp;
> +}
> +
> +#endif /* CONFIG_USER_ONLY */


--
Alex Bennée
Philippe Mathieu-Daudé June 17, 2019, 12:12 p.m. UTC | #2
On 6/17/19 1:42 PM, Alex Bennée wrote:
> Philippe Mathieu-Daudé <philmd@redhat.com> writes:
> 
>> From: Samuel Ortiz <sameo@linux.intel.com>
>>
>> In preparation for supporting TCG disablement on ARM, we move most
>> of TCG related v7m helpers and APIs into their own file.
>>
>> Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
>> [PMD: Patch rewritten]
>> Signed-off-by: Philippe Mathieu-Daudé <philmd@redhat.com>
>> ---
>> Is there a way to not use $CONFIG_USER_ONLY?
> 
> Is this because the CONFIG_ARM_V7M symbol only appears for softmmu
> targets but we still want vXm -cpu's for user mode?

No :(

If I use this diff:

-- >8 --
diff --git a/target/arm/helper.h b/target/arm/helper.h
@@ -58,24 +58,26 @@ DEF_HELPER_2(pre_smc, void, env, i32)
 DEF_HELPER_1(check_breakpoints, void, env)

 DEF_HELPER_3(cpsr_write, void, env, i32, i32)
 DEF_HELPER_2(cpsr_write_eret, void, env, i32)
 DEF_HELPER_1(cpsr_read, i32, env)

+#ifndef CONFIG_USER_ONLY
 DEF_HELPER_3(v7m_msr, void, env, i32, i32)
 DEF_HELPER_2(v7m_mrs, i32, env, i32)

 DEF_HELPER_2(v7m_bxns, void, env, i32)
 DEF_HELPER_2(v7m_blxns, void, env, i32)

 DEF_HELPER_3(v7m_tt, i32, env, i32, i32)

 DEF_HELPER_1(v7m_preserve_fp_state, void, env)

 DEF_HELPER_2(v7m_vlstm, void, env, i32)
 DEF_HELPER_2(v7m_vlldm, void, env, i32)
+#endif /* CONFIG_USER_ONLY */

 DEF_HELPER_2(v8m_stackcheck, void, env, i32)
---

I get:

target/arm/translate.c:10607:29: error: nested extern declaration of
‘gen_helper_v7m_mrs’ [-Werror=nested-externs]
target/arm/translate.c: In function ‘disas_thumb_insn’:
target/arm/translate.c:11224:25: error: implicit declaration of function
‘gen_blxns’; did you mean ‘gen_bx’? [-Werror=implicit-function-declaration]
                         gen_blxns(s, rm);
                         ^~~~~~~~~

Because:

static void disas_thumb_insn(DisasContext *s, uint32_t insn)
{
    ...
    switch (insn >> 12) {
    ...
    case 4:
        ...
        if (insn & (1 << 10)) {
            ...
            case 3:
            {
                /* 0b0100_0111_xxxx_xxxx
                 * - branch [and link] exchange thumb register
                 */
                bool link = insn & (1 << 7);

                if (insn & 3) {
                    goto undef;
                }
                if (link) {
                    ARCH(5);
                }
                if ((insn & 4)) {
                    /* BXNS/BLXNS: only exists for v8M with the
                     * security extensions, and always UNDEF if NonSecure.
                     * We don't implement these in the user-only mode
                     * either (in theory you can use them from Secure User
                     * mode but they are too tied in to system emulation.)
                     */
                    if (!s->v8m_secure || IS_USER_ONLY) {
                        goto undef;
                    }
                    if (link) {
                        gen_blxns(s, rm);
                    } else {
                        gen_bxns(s, rm);
                    }
                    break;
                }

Should we add "#ifndef CONFIG_USER_ONLY" all around? I believe we rather
not...

For 'cps' and 'mrs' we have:

                        /* Implemented as NOP in user mode.  */
                        if (IS_USER(s))
                            break;

but not for 'msr'.
diff mbox series

Patch

diff --git a/target/arm/Makefile.objs b/target/arm/Makefile.objs
index 72b42f825f..5f3f965cc6 100644
--- a/target/arm/Makefile.objs
+++ b/target/arm/Makefile.objs
@@ -35,6 +35,7 @@  obj-y += translate.o op_helper.o
 obj-y += crypto_helper.o
 obj-y += iwmmxt_helper.o vec_helper.o
 obj-y += neon_helper.o vfp_helper.o
+obj-$(call lor,$(CONFIG_USER_ONLY),$(CONFIG_ARM_V7M)) += v7m_helper.o
 
 obj-$(TARGET_AARCH64) += translate-a64.o helper-a64.o
 obj-$(TARGET_AARCH64) += translate-sve.o sve_helper.o
diff --git a/target/arm/helper.c b/target/arm/helper.c
index a1e74cc471..a829086c6d 100644
--- a/target/arm/helper.c
+++ b/target/arm/helper.c
@@ -20,7 +20,6 @@ 
 #include "qemu/crc32c.h"
 #include "qemu/qemu-print.h"
 #include "exec/exec-all.h"
-#include "exec/cpu_ldst.h"
 #include "arm_ldst.h"
 #include <zlib.h> /* For crc32 */
 #include "hw/semihosting/semihost.h"
@@ -7456,75 +7455,6 @@  uint32_t HELPER(rbit)(uint32_t x)
 
 #ifdef CONFIG_USER_ONLY
 
-/* These should probably raise undefined insn exceptions.  */
-void HELPER(v7m_msr)(CPUARMState *env, uint32_t reg, uint32_t val)
-{
-    ARMCPU *cpu = env_archcpu(env);
-
-    cpu_abort(CPU(cpu), "v7m_msr %d\n", reg);
-}
-
-uint32_t HELPER(v7m_mrs)(CPUARMState *env, uint32_t reg)
-{
-    ARMCPU *cpu = env_archcpu(env);
-
-    cpu_abort(CPU(cpu), "v7m_mrs %d\n", reg);
-    return 0;
-}
-
-void HELPER(v7m_bxns)(CPUARMState *env, uint32_t dest)
-{
-    /* translate.c should never generate calls here in user-only mode */
-    g_assert_not_reached();
-}
-
-void HELPER(v7m_blxns)(CPUARMState *env, uint32_t dest)
-{
-    /* translate.c should never generate calls here in user-only mode */
-    g_assert_not_reached();
-}
-
-void HELPER(v7m_preserve_fp_state)(CPUARMState *env)
-{
-    /* translate.c should never generate calls here in user-only mode */
-    g_assert_not_reached();
-}
-
-void HELPER(v7m_vlstm)(CPUARMState *env, uint32_t fptr)
-{
-    /* translate.c should never generate calls here in user-only mode */
-    g_assert_not_reached();
-}
-
-void HELPER(v7m_vlldm)(CPUARMState *env, uint32_t fptr)
-{
-    /* translate.c should never generate calls here in user-only mode */
-    g_assert_not_reached();
-}
-
-uint32_t HELPER(v7m_tt)(CPUARMState *env, uint32_t addr, uint32_t op)
-{
-    /*
-     * The TT instructions can be used by unprivileged code, but in
-     * user-only emulation we don't have the MPU.
-     * Luckily since we know we are NonSecure unprivileged (and that in
-     * turn means that the A flag wasn't specified), all the bits in the
-     * register must be zero:
-     *  IREGION: 0 because IRVALID is 0
-     *  IRVALID: 0 because NS
-     *  S: 0 because NS
-     *  NSRW: 0 because NS
-     *  NSR: 0 because NS
-     *  RW: 0 because unpriv and A flag not set
-     *  R: 0 because unpriv and A flag not set
-     *  SRVALID: 0 because NS
-     *  MRVALID: 0 because unpriv and A flag not set
-     *  SREGION: 0 becaus SRVALID is 0
-     *  MREGION: 0 because MRVALID is 0
-     */
-    return 0;
-}
-
 void switch_mode(CPUARMState *env, int mode)
 {
     ARMCPU *cpu = env_archcpu(env);
@@ -8048,109 +7978,6 @@  void switch_v7m_security_state(CPUARMState *env, bool new_secstate)
     }
 }
 
-void HELPER(v7m_bxns)(CPUARMState *env, uint32_t dest)
-{
-    /*
-     * Handle v7M BXNS:
-     *  - if the return value is a magic value, do exception return (like BX)
-     *  - otherwise bit 0 of the return value is the target security state
-     */
-    uint32_t min_magic;
-
-    if (arm_feature(env, ARM_FEATURE_M_SECURITY)) {
-        /* Covers FNC_RETURN and EXC_RETURN magic */
-        min_magic = FNC_RETURN_MIN_MAGIC;
-    } else {
-        /* EXC_RETURN magic only */
-        min_magic = EXC_RETURN_MIN_MAGIC;
-    }
-
-    if (dest >= min_magic) {
-        /*
-         * This is an exception return magic value; put it where
-         * do_v7m_exception_exit() expects and raise EXCEPTION_EXIT.
-         * Note that if we ever add gen_ss_advance() singlestep support to
-         * M profile this should count as an "instruction execution complete"
-         * event (compare gen_bx_excret_final_code()).
-         */
-        env->regs[15] = dest & ~1;
-        env->thumb = dest & 1;
-        HELPER(exception_internal)(env, EXCP_EXCEPTION_EXIT);
-        /* notreached */
-    }
-
-    /* translate.c should have made BXNS UNDEF unless we're secure */
-    assert(env->v7m.secure);
-
-    if (!(dest & 1)) {
-        env->v7m.control[M_REG_S] &= ~R_V7M_CONTROL_SFPA_MASK;
-    }
-    switch_v7m_security_state(env, dest & 1);
-    env->thumb = 1;
-    env->regs[15] = dest & ~1;
-}
-
-void HELPER(v7m_blxns)(CPUARMState *env, uint32_t dest)
-{
-    /*
-     * Handle v7M BLXNS:
-     *  - bit 0 of the destination address is the target security state
-     */
-
-    /* At this point regs[15] is the address just after the BLXNS */
-    uint32_t nextinst = env->regs[15] | 1;
-    uint32_t sp = env->regs[13] - 8;
-    uint32_t saved_psr;
-
-    /* translate.c will have made BLXNS UNDEF unless we're secure */
-    assert(env->v7m.secure);
-
-    if (dest & 1) {
-        /*
-         * Target is Secure, so this is just a normal BLX,
-         * except that the low bit doesn't indicate Thumb/not.
-         */
-        env->regs[14] = nextinst;
-        env->thumb = 1;
-        env->regs[15] = dest & ~1;
-        return;
-    }
-
-    /* Target is non-secure: first push a stack frame */
-    if (!QEMU_IS_ALIGNED(sp, 8)) {
-        qemu_log_mask(LOG_GUEST_ERROR,
-                      "BLXNS with misaligned SP is UNPREDICTABLE\n");
-    }
-
-    if (sp < v7m_sp_limit(env)) {
-        raise_exception(env, EXCP_STKOF, 0, 1);
-    }
-
-    saved_psr = env->v7m.exception;
-    if (env->v7m.control[M_REG_S] & R_V7M_CONTROL_SFPA_MASK) {
-        saved_psr |= XPSR_SFPA;
-    }
-
-    /* Note that these stores can throw exceptions on MPU faults */
-    cpu_stl_data(env, sp, nextinst);
-    cpu_stl_data(env, sp + 4, saved_psr);
-
-    env->regs[13] = sp;
-    env->regs[14] = 0xfeffffff;
-    if (arm_v7m_is_handler_mode(env)) {
-        /*
-         * Write a dummy value to IPSR, to avoid leaking the current secure
-         * exception number to non-secure code. This is guaranteed not
-         * to cause write_v7m_exception() to actually change stacks.
-         */
-        write_v7m_exception(env, 1);
-    }
-    env->v7m.control[M_REG_S] &= ~R_V7M_CONTROL_SFPA_MASK;
-    switch_v7m_security_state(env, 0);
-    env->thumb = 1;
-    env->regs[15] = dest;
-}
-
 static uint32_t *get_v7m_sp_ptr(CPUARMState *env, bool secure, bool threadmode,
                                 bool spsel)
 {
@@ -12760,466 +12587,6 @@  hwaddr arm_cpu_get_phys_page_attrs_debug(CPUState *cs, vaddr addr,
     return phys_addr;
 }
 
-uint32_t HELPER(v7m_mrs)(CPUARMState *env, uint32_t reg)
-{
-    uint32_t mask;
-    unsigned el = arm_current_el(env);
-
-    /* First handle registers which unprivileged can read */
-
-    switch (reg) {
-    case 0 ... 7: /* xPSR sub-fields */
-        mask = 0;
-        if ((reg & 1) && el) {
-            mask |= XPSR_EXCP; /* IPSR (unpriv. reads as zero) */
-        }
-        if (!(reg & 4)) {
-            mask |= XPSR_NZCV | XPSR_Q; /* APSR */
-            if (arm_feature(env, ARM_FEATURE_THUMB_DSP)) {
-                mask |= XPSR_GE;
-            }
-        }
-        /* EPSR reads as zero */
-        return xpsr_read(env) & mask;
-        break;
-    case 20: /* CONTROL */
-    {
-        uint32_t value = env->v7m.control[env->v7m.secure];
-        if (!env->v7m.secure) {
-            /* SFPA is RAZ/WI from NS; FPCA is stored in the M_REG_S bank */
-            value |= env->v7m.control[M_REG_S] & R_V7M_CONTROL_FPCA_MASK;
-        }
-        return value;
-    }
-    case 0x94: /* CONTROL_NS */
-        /*
-         * We have to handle this here because unprivileged Secure code
-         * can read the NS CONTROL register.
-         */
-        if (!env->v7m.secure) {
-            return 0;
-        }
-        return env->v7m.control[M_REG_NS] |
-            (env->v7m.control[M_REG_S] & R_V7M_CONTROL_FPCA_MASK);
-    }
-
-    if (el == 0) {
-        return 0; /* unprivileged reads others as zero */
-    }
-
-    if (arm_feature(env, ARM_FEATURE_M_SECURITY)) {
-        switch (reg) {
-        case 0x88: /* MSP_NS */
-            if (!env->v7m.secure) {
-                return 0;
-            }
-            return env->v7m.other_ss_msp;
-        case 0x89: /* PSP_NS */
-            if (!env->v7m.secure) {
-                return 0;
-            }
-            return env->v7m.other_ss_psp;
-        case 0x8a: /* MSPLIM_NS */
-            if (!env->v7m.secure) {
-                return 0;
-            }
-            return env->v7m.msplim[M_REG_NS];
-        case 0x8b: /* PSPLIM_NS */
-            if (!env->v7m.secure) {
-                return 0;
-            }
-            return env->v7m.psplim[M_REG_NS];
-        case 0x90: /* PRIMASK_NS */
-            if (!env->v7m.secure) {
-                return 0;
-            }
-            return env->v7m.primask[M_REG_NS];
-        case 0x91: /* BASEPRI_NS */
-            if (!env->v7m.secure) {
-                return 0;
-            }
-            return env->v7m.basepri[M_REG_NS];
-        case 0x93: /* FAULTMASK_NS */
-            if (!env->v7m.secure) {
-                return 0;
-            }
-            return env->v7m.faultmask[M_REG_NS];
-        case 0x98: /* SP_NS */
-        {
-            /*
-             * This gives the non-secure SP selected based on whether we're
-             * currently in handler mode or not, using the NS CONTROL.SPSEL.
-             */
-            bool spsel = env->v7m.control[M_REG_NS] & R_V7M_CONTROL_SPSEL_MASK;
-
-            if (!env->v7m.secure) {
-                return 0;
-            }
-            if (!arm_v7m_is_handler_mode(env) && spsel) {
-                return env->v7m.other_ss_psp;
-            } else {
-                return env->v7m.other_ss_msp;
-            }
-        }
-        default:
-            break;
-        }
-    }
-
-    switch (reg) {
-    case 8: /* MSP */
-        return v7m_using_psp(env) ? env->v7m.other_sp : env->regs[13];
-    case 9: /* PSP */
-        return v7m_using_psp(env) ? env->regs[13] : env->v7m.other_sp;
-    case 10: /* MSPLIM */
-        if (!arm_feature(env, ARM_FEATURE_V8)) {
-            goto bad_reg;
-        }
-        return env->v7m.msplim[env->v7m.secure];
-    case 11: /* PSPLIM */
-        if (!arm_feature(env, ARM_FEATURE_V8)) {
-            goto bad_reg;
-        }
-        return env->v7m.psplim[env->v7m.secure];
-    case 16: /* PRIMASK */
-        return env->v7m.primask[env->v7m.secure];
-    case 17: /* BASEPRI */
-    case 18: /* BASEPRI_MAX */
-        return env->v7m.basepri[env->v7m.secure];
-    case 19: /* FAULTMASK */
-        return env->v7m.faultmask[env->v7m.secure];
-    default:
-    bad_reg:
-        qemu_log_mask(LOG_GUEST_ERROR, "Attempt to read unknown special"
-                                       " register %d\n", reg);
-        return 0;
-    }
-}
-
-void HELPER(v7m_msr)(CPUARMState *env, uint32_t maskreg, uint32_t val)
-{
-    /*
-     * We're passed bits [11..0] of the instruction; extract
-     * SYSm and the mask bits.
-     * Invalid combinations of SYSm and mask are UNPREDICTABLE;
-     * we choose to treat them as if the mask bits were valid.
-     * NB that the pseudocode 'mask' variable is bits [11..10],
-     * whereas ours is [11..8].
-     */
-    uint32_t mask = extract32(maskreg, 8, 4);
-    uint32_t reg = extract32(maskreg, 0, 8);
-    int cur_el = arm_current_el(env);
-
-    if (cur_el == 0 && reg > 7 && reg != 20) {
-        /*
-         * only xPSR sub-fields and CONTROL.SFPA may be written by
-         * unprivileged code
-         */
-        return;
-    }
-
-    if (arm_feature(env, ARM_FEATURE_M_SECURITY)) {
-        switch (reg) {
-        case 0x88: /* MSP_NS */
-            if (!env->v7m.secure) {
-                return;
-            }
-            env->v7m.other_ss_msp = val;
-            return;
-        case 0x89: /* PSP_NS */
-            if (!env->v7m.secure) {
-                return;
-            }
-            env->v7m.other_ss_psp = val;
-            return;
-        case 0x8a: /* MSPLIM_NS */
-            if (!env->v7m.secure) {
-                return;
-            }
-            env->v7m.msplim[M_REG_NS] = val & ~7;
-            return;
-        case 0x8b: /* PSPLIM_NS */
-            if (!env->v7m.secure) {
-                return;
-            }
-            env->v7m.psplim[M_REG_NS] = val & ~7;
-            return;
-        case 0x90: /* PRIMASK_NS */
-            if (!env->v7m.secure) {
-                return;
-            }
-            env->v7m.primask[M_REG_NS] = val & 1;
-            return;
-        case 0x91: /* BASEPRI_NS */
-            if (!env->v7m.secure || !arm_feature(env, ARM_FEATURE_M_MAIN)) {
-                return;
-            }
-            env->v7m.basepri[M_REG_NS] = val & 0xff;
-            return;
-        case 0x93: /* FAULTMASK_NS */
-            if (!env->v7m.secure || !arm_feature(env, ARM_FEATURE_M_MAIN)) {
-                return;
-            }
-            env->v7m.faultmask[M_REG_NS] = val & 1;
-            return;
-        case 0x94: /* CONTROL_NS */
-            if (!env->v7m.secure) {
-                return;
-            }
-            write_v7m_control_spsel_for_secstate(env,
-                                                 val & R_V7M_CONTROL_SPSEL_MASK,
-                                                 M_REG_NS);
-            if (arm_feature(env, ARM_FEATURE_M_MAIN)) {
-                env->v7m.control[M_REG_NS] &= ~R_V7M_CONTROL_NPRIV_MASK;
-                env->v7m.control[M_REG_NS] |= val & R_V7M_CONTROL_NPRIV_MASK;
-            }
-            /*
-             * SFPA is RAZ/WI from NS. FPCA is RO if NSACR.CP10 == 0,
-             * RES0 if the FPU is not present, and is stored in the S bank
-             */
-            if (arm_feature(env, ARM_FEATURE_VFP) &&
-                extract32(env->v7m.nsacr, 10, 1)) {
-                env->v7m.control[M_REG_S] &= ~R_V7M_CONTROL_FPCA_MASK;
-                env->v7m.control[M_REG_S] |= val & R_V7M_CONTROL_FPCA_MASK;
-            }
-            return;
-        case 0x98: /* SP_NS */
-        {
-            /*
-             * This gives the non-secure SP selected based on whether we're
-             * currently in handler mode or not, using the NS CONTROL.SPSEL.
-             */
-            bool spsel = env->v7m.control[M_REG_NS] & R_V7M_CONTROL_SPSEL_MASK;
-            bool is_psp = !arm_v7m_is_handler_mode(env) && spsel;
-            uint32_t limit;
-
-            if (!env->v7m.secure) {
-                return;
-            }
-
-            limit = is_psp ? env->v7m.psplim[false] : env->v7m.msplim[false];
-
-            if (val < limit) {
-                CPUState *cs = env_cpu(env);
-
-                cpu_restore_state(cs, GETPC(), true);
-                raise_exception(env, EXCP_STKOF, 0, 1);
-            }
-
-            if (is_psp) {
-                env->v7m.other_ss_psp = val;
-            } else {
-                env->v7m.other_ss_msp = val;
-            }
-            return;
-        }
-        default:
-            break;
-        }
-    }
-
-    switch (reg) {
-    case 0 ... 7: /* xPSR sub-fields */
-        /* only APSR is actually writable */
-        if (!(reg & 4)) {
-            uint32_t apsrmask = 0;
-
-            if (mask & 8) {
-                apsrmask |= XPSR_NZCV | XPSR_Q;
-            }
-            if ((mask & 4) && arm_feature(env, ARM_FEATURE_THUMB_DSP)) {
-                apsrmask |= XPSR_GE;
-            }
-            xpsr_write(env, val, apsrmask);
-        }
-        break;
-    case 8: /* MSP */
-        if (v7m_using_psp(env)) {
-            env->v7m.other_sp = val;
-        } else {
-            env->regs[13] = val;
-        }
-        break;
-    case 9: /* PSP */
-        if (v7m_using_psp(env)) {
-            env->regs[13] = val;
-        } else {
-            env->v7m.other_sp = val;
-        }
-        break;
-    case 10: /* MSPLIM */
-        if (!arm_feature(env, ARM_FEATURE_V8)) {
-            goto bad_reg;
-        }
-        env->v7m.msplim[env->v7m.secure] = val & ~7;
-        break;
-    case 11: /* PSPLIM */
-        if (!arm_feature(env, ARM_FEATURE_V8)) {
-            goto bad_reg;
-        }
-        env->v7m.psplim[env->v7m.secure] = val & ~7;
-        break;
-    case 16: /* PRIMASK */
-        env->v7m.primask[env->v7m.secure] = val & 1;
-        break;
-    case 17: /* BASEPRI */
-        if (!arm_feature(env, ARM_FEATURE_M_MAIN)) {
-            goto bad_reg;
-        }
-        env->v7m.basepri[env->v7m.secure] = val & 0xff;
-        break;
-    case 18: /* BASEPRI_MAX */
-        if (!arm_feature(env, ARM_FEATURE_M_MAIN)) {
-            goto bad_reg;
-        }
-        val &= 0xff;
-        if (val != 0 && (val < env->v7m.basepri[env->v7m.secure]
-                         || env->v7m.basepri[env->v7m.secure] == 0)) {
-            env->v7m.basepri[env->v7m.secure] = val;
-        }
-        break;
-    case 19: /* FAULTMASK */
-        if (!arm_feature(env, ARM_FEATURE_M_MAIN)) {
-            goto bad_reg;
-        }
-        env->v7m.faultmask[env->v7m.secure] = val & 1;
-        break;
-    case 20: /* CONTROL */
-        /*
-         * Writing to the SPSEL bit only has an effect if we are in
-         * thread mode; other bits can be updated by any privileged code.
-         * write_v7m_control_spsel() deals with updating the SPSEL bit in
-         * env->v7m.control, so we only need update the others.
-         * For v7M, we must just ignore explicit writes to SPSEL in handler
-         * mode; for v8M the write is permitted but will have no effect.
-         * All these bits are writes-ignored from non-privileged code,
-         * except for SFPA.
-         */
-        if (cur_el > 0 && (arm_feature(env, ARM_FEATURE_V8) ||
-                           !arm_v7m_is_handler_mode(env))) {
-            write_v7m_control_spsel(env, (val & R_V7M_CONTROL_SPSEL_MASK) != 0);
-        }
-        if (cur_el > 0 && arm_feature(env, ARM_FEATURE_M_MAIN)) {
-            env->v7m.control[env->v7m.secure] &= ~R_V7M_CONTROL_NPRIV_MASK;
-            env->v7m.control[env->v7m.secure] |= val & R_V7M_CONTROL_NPRIV_MASK;
-        }
-        if (arm_feature(env, ARM_FEATURE_VFP)) {
-            /*
-             * SFPA is RAZ/WI from NS or if no FPU.
-             * FPCA is RO if NSACR.CP10 == 0, RES0 if the FPU is not present.
-             * Both are stored in the S bank.
-             */
-            if (env->v7m.secure) {
-                env->v7m.control[M_REG_S] &= ~R_V7M_CONTROL_SFPA_MASK;
-                env->v7m.control[M_REG_S] |= val & R_V7M_CONTROL_SFPA_MASK;
-            }
-            if (cur_el > 0 &&
-                (env->v7m.secure || !arm_feature(env, ARM_FEATURE_M_SECURITY) ||
-                 extract32(env->v7m.nsacr, 10, 1))) {
-                env->v7m.control[M_REG_S] &= ~R_V7M_CONTROL_FPCA_MASK;
-                env->v7m.control[M_REG_S] |= val & R_V7M_CONTROL_FPCA_MASK;
-            }
-        }
-        break;
-    default:
-    bad_reg:
-        qemu_log_mask(LOG_GUEST_ERROR, "Attempt to write unknown special"
-                                       " register %d\n", reg);
-        return;
-    }
-}
-
-uint32_t HELPER(v7m_tt)(CPUARMState *env, uint32_t addr, uint32_t op)
-{
-    /* Implement the TT instruction. op is bits [7:6] of the insn. */
-    bool forceunpriv = op & 1;
-    bool alt = op & 2;
-    V8M_SAttributes sattrs = {};
-    uint32_t tt_resp;
-    bool r, rw, nsr, nsrw, mrvalid;
-    int prot;
-    ARMMMUFaultInfo fi = {};
-    MemTxAttrs attrs = {};
-    hwaddr phys_addr;
-    ARMMMUIdx mmu_idx;
-    uint32_t mregion;
-    bool targetpriv;
-    bool targetsec = env->v7m.secure;
-    bool is_subpage;
-
-    /*
-     * Work out what the security state and privilege level we're
-     * interested in is...
-     */
-    if (alt) {
-        targetsec = !targetsec;
-    }
-
-    if (forceunpriv) {
-        targetpriv = false;
-    } else {
-        targetpriv = arm_v7m_is_handler_mode(env) ||
-            !(env->v7m.control[targetsec] & R_V7M_CONTROL_NPRIV_MASK);
-    }
-
-    /* ...and then figure out which MMU index this is */
-    mmu_idx = arm_v7m_mmu_idx_for_secstate_and_priv(env, targetsec, targetpriv);
-
-    /*
-     * We know that the MPU and SAU don't care about the access type
-     * for our purposes beyond that we don't want to claim to be
-     * an insn fetch, so we arbitrarily call this a read.
-     */
-
-    /*
-     * MPU region info only available for privileged or if
-     * inspecting the other MPU state.
-     */
-    if (arm_current_el(env) != 0 || alt) {
-        /* We can ignore the return value as prot is always set */
-        pmsav8_mpu_lookup(env, addr, MMU_DATA_LOAD, mmu_idx,
-                          &phys_addr, &attrs, &prot, &is_subpage,
-                          &fi, &mregion);
-        if (mregion == -1) {
-            mrvalid = false;
-            mregion = 0;
-        } else {
-            mrvalid = true;
-        }
-        r = prot & PAGE_READ;
-        rw = prot & PAGE_WRITE;
-    } else {
-        r = false;
-        rw = false;
-        mrvalid = false;
-        mregion = 0;
-    }
-
-    if (env->v7m.secure) {
-        v8m_security_lookup(env, addr, MMU_DATA_LOAD, mmu_idx, &sattrs);
-        nsr = sattrs.ns && r;
-        nsrw = sattrs.ns && rw;
-    } else {
-        sattrs.ns = true;
-        nsr = false;
-        nsrw = false;
-    }
-
-    tt_resp = (sattrs.iregion << 24) |
-        (sattrs.irvalid << 23) |
-        ((!sattrs.ns) << 22) |
-        (nsrw << 21) |
-        (nsr << 20) |
-        (rw << 19) |
-        (r << 18) |
-        (sattrs.srvalid << 17) |
-        (mrvalid << 16) |
-        (sattrs.sregion << 8) |
-        mregion;
-
-    return tt_resp;
-}
-
 #endif
 
 bool arm_cpu_tlb_fill(CPUState *cs, vaddr address, int size,
diff --git a/target/arm/v7m_helper.c b/target/arm/v7m_helper.c
new file mode 100644
index 0000000000..321154966e
--- /dev/null
+++ b/target/arm/v7m_helper.c
@@ -0,0 +1,654 @@ 
+/*
+ * ARM v7-M helpers.
+ *
+ * This code is licensed under the GNU GPL v2 or later.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include "qemu/osdep.h"
+#include "sysemu/sysemu.h"
+#include "cpu.h"
+#include "internals.h"
+#include "exec/helper-proto.h"
+#include "exec/exec-all.h"
+#include "arm_ldst.h"
+#include "hw/semihosting/semihost.h"
+#include "fpu/softfloat.h"
+
+#if defined(CONFIG_USER_ONLY)
+
+/* These should probably raise undefined insn exceptions.  */
+void HELPER(v7m_msr)(CPUARMState *env, uint32_t reg, uint32_t val)
+{
+    ARMCPU *cpu = env_archcpu(env);
+
+    cpu_abort(CPU(cpu), "v7m_msr %d\n", reg);
+}
+
+uint32_t HELPER(v7m_mrs)(CPUARMState *env, uint32_t reg)
+{
+    ARMCPU *cpu = env_archcpu(env);
+
+    cpu_abort(CPU(cpu), "v7m_mrs %d\n", reg);
+    return 0;
+}
+
+void HELPER(v7m_bxns)(CPUARMState *env, uint32_t dest)
+{
+    /* translate.c should never generate calls here in user-only mode */
+    g_assert_not_reached();
+}
+
+void HELPER(v7m_blxns)(CPUARMState *env, uint32_t dest)
+{
+    /* translate.c should never generate calls here in user-only mode */
+    g_assert_not_reached();
+}
+
+void HELPER(v7m_preserve_fp_state)(CPUARMState *env)
+{
+    /* translate.c should never generate calls here in user-only mode */
+    g_assert_not_reached();
+}
+
+void HELPER(v7m_vlstm)(CPUARMState *env, uint32_t fptr)
+{
+    /* translate.c should never generate calls here in user-only mode */
+    g_assert_not_reached();
+}
+
+void HELPER(v7m_vlldm)(CPUARMState *env, uint32_t fptr)
+{
+    /* translate.c should never generate calls here in user-only mode */
+    g_assert_not_reached();
+}
+
+uint32_t HELPER(v7m_tt)(CPUARMState *env, uint32_t addr, uint32_t op)
+{
+    /*
+     * The TT instructions can be used by unprivileged code, but in
+     * user-only emulation we don't have the MPU.
+     * Luckily since we know we are NonSecure unprivileged (and that in
+     * turn means that the A flag wasn't specified), all the bits in the
+     * register must be zero:
+     *  IREGION: 0 because IRVALID is 0
+     *  IRVALID: 0 because NS
+     *  S: 0 because NS
+     *  NSRW: 0 because NS
+     *  NSR: 0 because NS
+     *  RW: 0 because unpriv and A flag not set
+     *  R: 0 because unpriv and A flag not set
+     *  SRVALID: 0 because NS
+     *  MRVALID: 0 because unpriv and A flag not set
+     *  SREGION: 0 becaus SRVALID is 0
+     *  MREGION: 0 because MRVALID is 0
+     */
+    return 0;
+}
+
+#else
+
+void HELPER(v7m_bxns)(CPUARMState *env, uint32_t dest)
+{
+    /*
+     * Handle v7M BXNS:
+     *  - if the return value is a magic value, do exception return (like BX)
+     *  - otherwise bit 0 of the return value is the target security state
+     */
+    uint32_t min_magic;
+
+    if (arm_feature(env, ARM_FEATURE_M_SECURITY)) {
+        /* Covers FNC_RETURN and EXC_RETURN magic */
+        min_magic = FNC_RETURN_MIN_MAGIC;
+    } else {
+        /* EXC_RETURN magic only */
+        min_magic = EXC_RETURN_MIN_MAGIC;
+    }
+
+    if (dest >= min_magic) {
+        /*
+         * This is an exception return magic value; put it where
+         * do_v7m_exception_exit() expects and raise EXCEPTION_EXIT.
+         * Note that if we ever add gen_ss_advance() singlestep support to
+         * M profile this should count as an "instruction execution complete"
+         * event (compare gen_bx_excret_final_code()).
+         */
+        env->regs[15] = dest & ~1;
+        env->thumb = dest & 1;
+        HELPER(exception_internal)(env, EXCP_EXCEPTION_EXIT);
+        /* notreached */
+    }
+
+    /* translate.c should have made BXNS UNDEF unless we're secure */
+    assert(env->v7m.secure);
+
+    if (!(dest & 1)) {
+        env->v7m.control[M_REG_S] &= ~R_V7M_CONTROL_SFPA_MASK;
+    }
+    switch_v7m_security_state(env, dest & 1);
+    env->thumb = 1;
+    env->regs[15] = dest & ~1;
+}
+
+void HELPER(v7m_blxns)(CPUARMState *env, uint32_t dest)
+{
+    /*
+     * Handle v7M BLXNS:
+     *  - bit 0 of the destination address is the target security state
+     */
+
+    /* At this point regs[15] is the address just after the BLXNS */
+    uint32_t nextinst = env->regs[15] | 1;
+    uint32_t sp = env->regs[13] - 8;
+    uint32_t saved_psr;
+
+    /* translate.c will have made BLXNS UNDEF unless we're secure */
+    assert(env->v7m.secure);
+
+    if (dest & 1) {
+        /*
+         * Target is Secure, so this is just a normal BLX,
+         * except that the low bit doesn't indicate Thumb/not.
+         */
+        env->regs[14] = nextinst;
+        env->thumb = 1;
+        env->regs[15] = dest & ~1;
+        return;
+    }
+
+    /* Target is non-secure: first push a stack frame */
+    if (!QEMU_IS_ALIGNED(sp, 8)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "BLXNS with misaligned SP is UNPREDICTABLE\n");
+    }
+
+    if (sp < v7m_sp_limit(env)) {
+        raise_exception(env, EXCP_STKOF, 0, 1);
+    }
+
+    saved_psr = env->v7m.exception;
+    if (env->v7m.control[M_REG_S] & R_V7M_CONTROL_SFPA_MASK) {
+        saved_psr |= XPSR_SFPA;
+    }
+
+    /* Note that these stores can throw exceptions on MPU faults */
+    cpu_stl_data(env, sp, nextinst);
+    cpu_stl_data(env, sp + 4, saved_psr);
+
+    env->regs[13] = sp;
+    env->regs[14] = 0xfeffffff;
+    if (arm_v7m_is_handler_mode(env)) {
+        /*
+         * Write a dummy value to IPSR, to avoid leaking the current secure
+         * exception number to non-secure code. This is guaranteed not
+         * to cause write_v7m_exception() to actually change stacks.
+         */
+        write_v7m_exception(env, 1);
+    }
+    env->v7m.control[M_REG_S] &= ~R_V7M_CONTROL_SFPA_MASK;
+    switch_v7m_security_state(env, 0);
+    env->thumb = 1;
+    env->regs[15] = dest;
+}
+
+uint32_t HELPER(v7m_mrs)(CPUARMState *env, uint32_t reg)
+{
+    uint32_t mask;
+    unsigned el = arm_current_el(env);
+
+    /* First handle registers which unprivileged can read */
+
+    switch (reg) {
+    case 0 ... 7: /* xPSR sub-fields */
+        mask = 0;
+        if ((reg & 1) && el) {
+            mask |= XPSR_EXCP; /* IPSR (unpriv. reads as zero) */
+        }
+        if (!(reg & 4)) {
+            mask |= XPSR_NZCV | XPSR_Q; /* APSR */
+            if (arm_feature(env, ARM_FEATURE_THUMB_DSP)) {
+                mask |= XPSR_GE;
+            }
+        }
+        /* EPSR reads as zero */
+        return xpsr_read(env) & mask;
+        break;
+    case 20: /* CONTROL */
+    {
+        uint32_t value = env->v7m.control[env->v7m.secure];
+        if (!env->v7m.secure) {
+            /* SFPA is RAZ/WI from NS; FPCA is stored in the M_REG_S bank */
+            value |= env->v7m.control[M_REG_S] & R_V7M_CONTROL_FPCA_MASK;
+        }
+        return value;
+    }
+    case 0x94: /* CONTROL_NS */
+        /*
+         * We have to handle this here because unprivileged Secure code
+         * can read the NS CONTROL register.
+         */
+        if (!env->v7m.secure) {
+            return 0;
+        }
+        return env->v7m.control[M_REG_NS] |
+            (env->v7m.control[M_REG_S] & R_V7M_CONTROL_FPCA_MASK);
+    }
+
+    if (el == 0) {
+        return 0; /* unprivileged reads others as zero */
+    }
+
+    if (arm_feature(env, ARM_FEATURE_M_SECURITY)) {
+        switch (reg) {
+        case 0x88: /* MSP_NS */
+            if (!env->v7m.secure) {
+                return 0;
+            }
+            return env->v7m.other_ss_msp;
+        case 0x89: /* PSP_NS */
+            if (!env->v7m.secure) {
+                return 0;
+            }
+            return env->v7m.other_ss_psp;
+        case 0x8a: /* MSPLIM_NS */
+            if (!env->v7m.secure) {
+                return 0;
+            }
+            return env->v7m.msplim[M_REG_NS];
+        case 0x8b: /* PSPLIM_NS */
+            if (!env->v7m.secure) {
+                return 0;
+            }
+            return env->v7m.psplim[M_REG_NS];
+        case 0x90: /* PRIMASK_NS */
+            if (!env->v7m.secure) {
+                return 0;
+            }
+            return env->v7m.primask[M_REG_NS];
+        case 0x91: /* BASEPRI_NS */
+            if (!env->v7m.secure) {
+                return 0;
+            }
+            return env->v7m.basepri[M_REG_NS];
+        case 0x93: /* FAULTMASK_NS */
+            if (!env->v7m.secure) {
+                return 0;
+            }
+            return env->v7m.faultmask[M_REG_NS];
+        case 0x98: /* SP_NS */
+        {
+            /*
+             * This gives the non-secure SP selected based on whether we're
+             * currently in handler mode or not, using the NS CONTROL.SPSEL.
+             */
+            bool spsel = env->v7m.control[M_REG_NS] & R_V7M_CONTROL_SPSEL_MASK;
+
+            if (!env->v7m.secure) {
+                return 0;
+            }
+            if (!arm_v7m_is_handler_mode(env) && spsel) {
+                return env->v7m.other_ss_psp;
+            } else {
+                return env->v7m.other_ss_msp;
+            }
+        }
+        default:
+            break;
+        }
+    }
+
+    switch (reg) {
+    case 8: /* MSP */
+        return v7m_using_psp(env) ? env->v7m.other_sp : env->regs[13];
+    case 9: /* PSP */
+        return v7m_using_psp(env) ? env->regs[13] : env->v7m.other_sp;
+    case 10: /* MSPLIM */
+        if (!arm_feature(env, ARM_FEATURE_V8)) {
+            goto bad_reg;
+        }
+        return env->v7m.msplim[env->v7m.secure];
+    case 11: /* PSPLIM */
+        if (!arm_feature(env, ARM_FEATURE_V8)) {
+            goto bad_reg;
+        }
+        return env->v7m.psplim[env->v7m.secure];
+    case 16: /* PRIMASK */
+        return env->v7m.primask[env->v7m.secure];
+    case 17: /* BASEPRI */
+    case 18: /* BASEPRI_MAX */
+        return env->v7m.basepri[env->v7m.secure];
+    case 19: /* FAULTMASK */
+        return env->v7m.faultmask[env->v7m.secure];
+    default:
+    bad_reg:
+        qemu_log_mask(LOG_GUEST_ERROR, "Attempt to read unknown special"
+                                       " register %d\n", reg);
+        return 0;
+    }
+}
+
+void HELPER(v7m_msr)(CPUARMState *env, uint32_t maskreg, uint32_t val)
+{
+    /*
+     * We're passed bits [11..0] of the instruction; extract
+     * SYSm and the mask bits.
+     * Invalid combinations of SYSm and mask are UNPREDICTABLE;
+     * we choose to treat them as if the mask bits were valid.
+     * NB that the pseudocode 'mask' variable is bits [11..10],
+     * whereas ours is [11..8].
+     */
+    uint32_t mask = extract32(maskreg, 8, 4);
+    uint32_t reg = extract32(maskreg, 0, 8);
+    int cur_el = arm_current_el(env);
+
+    if (cur_el == 0 && reg > 7 && reg != 20) {
+        /*
+         * only xPSR sub-fields and CONTROL.SFPA may be written by
+         * unprivileged code
+         */
+        return;
+    }
+
+    if (arm_feature(env, ARM_FEATURE_M_SECURITY)) {
+        switch (reg) {
+        case 0x88: /* MSP_NS */
+            if (!env->v7m.secure) {
+                return;
+            }
+            env->v7m.other_ss_msp = val;
+            return;
+        case 0x89: /* PSP_NS */
+            if (!env->v7m.secure) {
+                return;
+            }
+            env->v7m.other_ss_psp = val;
+            return;
+        case 0x8a: /* MSPLIM_NS */
+            if (!env->v7m.secure) {
+                return;
+            }
+            env->v7m.msplim[M_REG_NS] = val & ~7;
+            return;
+        case 0x8b: /* PSPLIM_NS */
+            if (!env->v7m.secure) {
+                return;
+            }
+            env->v7m.psplim[M_REG_NS] = val & ~7;
+            return;
+        case 0x90: /* PRIMASK_NS */
+            if (!env->v7m.secure) {
+                return;
+            }
+            env->v7m.primask[M_REG_NS] = val & 1;
+            return;
+        case 0x91: /* BASEPRI_NS */
+            if (!env->v7m.secure || !arm_feature(env, ARM_FEATURE_M_MAIN)) {
+                return;
+            }
+            env->v7m.basepri[M_REG_NS] = val & 0xff;
+            return;
+        case 0x93: /* FAULTMASK_NS */
+            if (!env->v7m.secure || !arm_feature(env, ARM_FEATURE_M_MAIN)) {
+                return;
+            }
+            env->v7m.faultmask[M_REG_NS] = val & 1;
+            return;
+        case 0x94: /* CONTROL_NS */
+            if (!env->v7m.secure) {
+                return;
+            }
+            write_v7m_control_spsel_for_secstate(env,
+                                                 val & R_V7M_CONTROL_SPSEL_MASK,
+                                                 M_REG_NS);
+            if (arm_feature(env, ARM_FEATURE_M_MAIN)) {
+                env->v7m.control[M_REG_NS] &= ~R_V7M_CONTROL_NPRIV_MASK;
+                env->v7m.control[M_REG_NS] |= val & R_V7M_CONTROL_NPRIV_MASK;
+            }
+            /*
+             * SFPA is RAZ/WI from NS. FPCA is RO if NSACR.CP10 == 0,
+             * RES0 if the FPU is not present, and is stored in the S bank
+             */
+            if (arm_feature(env, ARM_FEATURE_VFP) &&
+                extract32(env->v7m.nsacr, 10, 1)) {
+                env->v7m.control[M_REG_S] &= ~R_V7M_CONTROL_FPCA_MASK;
+                env->v7m.control[M_REG_S] |= val & R_V7M_CONTROL_FPCA_MASK;
+            }
+            return;
+        case 0x98: /* SP_NS */
+        {
+            /*
+             * This gives the non-secure SP selected based on whether we're
+             * currently in handler mode or not, using the NS CONTROL.SPSEL.
+             */
+            bool spsel = env->v7m.control[M_REG_NS] & R_V7M_CONTROL_SPSEL_MASK;
+            bool is_psp = !arm_v7m_is_handler_mode(env) && spsel;
+            uint32_t limit;
+
+            if (!env->v7m.secure) {
+                return;
+            }
+
+            limit = is_psp ? env->v7m.psplim[false] : env->v7m.msplim[false];
+
+            if (val < limit) {
+                CPUState *cs = env_cpu(env);
+
+                cpu_restore_state(cs, GETPC(), true);
+                raise_exception(env, EXCP_STKOF, 0, 1);
+            }
+
+            if (is_psp) {
+                env->v7m.other_ss_psp = val;
+            } else {
+                env->v7m.other_ss_msp = val;
+            }
+            return;
+        }
+        default:
+            break;
+        }
+    }
+
+    switch (reg) {
+    case 0 ... 7: /* xPSR sub-fields */
+        /* only APSR is actually writable */
+        if (!(reg & 4)) {
+            uint32_t apsrmask = 0;
+
+            if (mask & 8) {
+                apsrmask |= XPSR_NZCV | XPSR_Q;
+            }
+            if ((mask & 4) && arm_feature(env, ARM_FEATURE_THUMB_DSP)) {
+                apsrmask |= XPSR_GE;
+            }
+            xpsr_write(env, val, apsrmask);
+        }
+        break;
+    case 8: /* MSP */
+        if (v7m_using_psp(env)) {
+            env->v7m.other_sp = val;
+        } else {
+            env->regs[13] = val;
+        }
+        break;
+    case 9: /* PSP */
+        if (v7m_using_psp(env)) {
+            env->regs[13] = val;
+        } else {
+            env->v7m.other_sp = val;
+        }
+        break;
+    case 10: /* MSPLIM */
+        if (!arm_feature(env, ARM_FEATURE_V8)) {
+            goto bad_reg;
+        }
+        env->v7m.msplim[env->v7m.secure] = val & ~7;
+        break;
+    case 11: /* PSPLIM */
+        if (!arm_feature(env, ARM_FEATURE_V8)) {
+            goto bad_reg;
+        }
+        env->v7m.psplim[env->v7m.secure] = val & ~7;
+        break;
+    case 16: /* PRIMASK */
+        env->v7m.primask[env->v7m.secure] = val & 1;
+        break;
+    case 17: /* BASEPRI */
+        if (!arm_feature(env, ARM_FEATURE_M_MAIN)) {
+            goto bad_reg;
+        }
+        env->v7m.basepri[env->v7m.secure] = val & 0xff;
+        break;
+    case 18: /* BASEPRI_MAX */
+        if (!arm_feature(env, ARM_FEATURE_M_MAIN)) {
+            goto bad_reg;
+        }
+        val &= 0xff;
+        if (val != 0 && (val < env->v7m.basepri[env->v7m.secure]
+                         || env->v7m.basepri[env->v7m.secure] == 0)) {
+            env->v7m.basepri[env->v7m.secure] = val;
+        }
+        break;
+    case 19: /* FAULTMASK */
+        if (!arm_feature(env, ARM_FEATURE_M_MAIN)) {
+            goto bad_reg;
+        }
+        env->v7m.faultmask[env->v7m.secure] = val & 1;
+        break;
+    case 20: /* CONTROL */
+        /*
+         * Writing to the SPSEL bit only has an effect if we are in
+         * thread mode; other bits can be updated by any privileged code.
+         * write_v7m_control_spsel() deals with updating the SPSEL bit in
+         * env->v7m.control, so we only need update the others.
+         * For v7M, we must just ignore explicit writes to SPSEL in handler
+         * mode; for v8M the write is permitted but will have no effect.
+         * All these bits are writes-ignored from non-privileged code,
+         * except for SFPA.
+         */
+        if (cur_el > 0 && (arm_feature(env, ARM_FEATURE_V8) ||
+                           !arm_v7m_is_handler_mode(env))) {
+            write_v7m_control_spsel(env, (val & R_V7M_CONTROL_SPSEL_MASK) != 0);
+        }
+        if (cur_el > 0 && arm_feature(env, ARM_FEATURE_M_MAIN)) {
+            env->v7m.control[env->v7m.secure] &= ~R_V7M_CONTROL_NPRIV_MASK;
+            env->v7m.control[env->v7m.secure] |= val & R_V7M_CONTROL_NPRIV_MASK;
+        }
+        if (arm_feature(env, ARM_FEATURE_VFP)) {
+            /*
+             * SFPA is RAZ/WI from NS or if no FPU.
+             * FPCA is RO if NSACR.CP10 == 0, RES0 if the FPU is not present.
+             * Both are stored in the S bank.
+             */
+            if (env->v7m.secure) {
+                env->v7m.control[M_REG_S] &= ~R_V7M_CONTROL_SFPA_MASK;
+                env->v7m.control[M_REG_S] |= val & R_V7M_CONTROL_SFPA_MASK;
+            }
+            if (cur_el > 0 &&
+                (env->v7m.secure || !arm_feature(env, ARM_FEATURE_M_SECURITY) ||
+                 extract32(env->v7m.nsacr, 10, 1))) {
+                env->v7m.control[M_REG_S] &= ~R_V7M_CONTROL_FPCA_MASK;
+                env->v7m.control[M_REG_S] |= val & R_V7M_CONTROL_FPCA_MASK;
+            }
+        }
+        break;
+    default:
+    bad_reg:
+        qemu_log_mask(LOG_GUEST_ERROR, "Attempt to write unknown special"
+                                       " register %d\n", reg);
+        return;
+    }
+}
+
+uint32_t HELPER(v7m_tt)(CPUARMState *env, uint32_t addr, uint32_t op)
+{
+    /* Implement the TT instruction. op is bits [7:6] of the insn. */
+    bool forceunpriv = op & 1;
+    bool alt = op & 2;
+    V8M_SAttributes sattrs = {};
+    uint32_t tt_resp;
+    bool r, rw, nsr, nsrw, mrvalid;
+    int prot;
+    ARMMMUFaultInfo fi = {};
+    MemTxAttrs attrs = {};
+    hwaddr phys_addr;
+    ARMMMUIdx mmu_idx;
+    uint32_t mregion;
+    bool targetpriv;
+    bool targetsec = env->v7m.secure;
+    bool is_subpage;
+
+    /*
+     * Work out what the security state and privilege level we're
+     * interested in is...
+     */
+    if (alt) {
+        targetsec = !targetsec;
+    }
+
+    if (forceunpriv) {
+        targetpriv = false;
+    } else {
+        targetpriv = arm_v7m_is_handler_mode(env) ||
+            !(env->v7m.control[targetsec] & R_V7M_CONTROL_NPRIV_MASK);
+    }
+
+    /* ...and then figure out which MMU index this is */
+    mmu_idx = arm_v7m_mmu_idx_for_secstate_and_priv(env, targetsec, targetpriv);
+
+    /*
+     * We know that the MPU and SAU don't care about the access type
+     * for our purposes beyond that we don't want to claim to be
+     * an insn fetch, so we arbitrarily call this a read.
+     */
+
+    /*
+     * MPU region info only available for privileged or if
+     * inspecting the other MPU state.
+     */
+    if (arm_current_el(env) != 0 || alt) {
+        /* We can ignore the return value as prot is always set */
+        pmsav8_mpu_lookup(env, addr, MMU_DATA_LOAD, mmu_idx,
+                          &phys_addr, &attrs, &prot, &is_subpage,
+                          &fi, &mregion);
+        if (mregion == -1) {
+            mrvalid = false;
+            mregion = 0;
+        } else {
+            mrvalid = true;
+        }
+        r = prot & PAGE_READ;
+        rw = prot & PAGE_WRITE;
+    } else {
+        r = false;
+        rw = false;
+        mrvalid = false;
+        mregion = 0;
+    }
+
+    if (env->v7m.secure) {
+        v8m_security_lookup(env, addr, MMU_DATA_LOAD, mmu_idx, &sattrs);
+        nsr = sattrs.ns && r;
+        nsrw = sattrs.ns && rw;
+    } else {
+        sattrs.ns = true;
+        nsr = false;
+        nsrw = false;
+    }
+
+    tt_resp = (sattrs.iregion << 24) |
+        (sattrs.irvalid << 23) |
+        ((!sattrs.ns) << 22) |
+        (nsrw << 21) |
+        (nsr << 20) |
+        (rw << 19) |
+        (r << 18) |
+        (sattrs.srvalid << 17) |
+        (mrvalid << 16) |
+        (sattrs.sregion << 8) |
+        mregion;
+
+    return tt_resp;
+}
+
+#endif /* CONFIG_USER_ONLY */