diff mbox

[v4,05/21] target-arm: Add support for generating exceptions with syndrome information

Message ID 1394134385-1727-6-git-send-email-peter.maydell@linaro.org
State New
Headers show

Commit Message

Peter Maydell March 6, 2014, 7:32 p.m. UTC
Add new helpers exception_with_syndrome (for generating an exception
with syndrome information) and exception_uncategorized (for generating
an exception with "Unknown or Uncategorized Reason", which have a syndrome
register value of zero), and use them to generate the correct syndrome
information for exceptions which are raised directly from generated code.

This patch includes moving the A32/T32 gen_exception_insn functions
further up in the source file; they will be needed for "VFP/Neon disabled"
exception generation later.

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
---
 target-arm/helper.h        |   3 +-
 target-arm/internals.h     |  14 ++++++
 target-arm/op_helper.c     |  19 ++++++++-
 target-arm/translate-a64.c |  49 +++++++++++++++------
 target-arm/translate.c     | 103 ++++++++++++++++++++++++++++-----------------
 target-arm/translate.h     |   4 ++
 6 files changed, 138 insertions(+), 54 deletions(-)

Comments

Peter Crosthwaite March 17, 2014, 3:19 a.m. UTC | #1
On Fri, Mar 7, 2014 at 5:32 AM, Peter Maydell <peter.maydell@linaro.org> wrote:
> Add new helpers exception_with_syndrome (for generating an exception
> with syndrome information) and exception_uncategorized (for generating
> an exception with "Unknown or Uncategorized Reason", which have a syndrome
> register value of zero), and use them to generate the correct syndrome
> information for exceptions which are raised directly from generated code.
>
> This patch includes moving the A32/T32 gen_exception_insn functions
> further up in the source file; they will be needed for "VFP/Neon disabled"
> exception generation later.
>
> Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
> ---
>  target-arm/helper.h        |   3 +-
>  target-arm/internals.h     |  14 ++++++
>  target-arm/op_helper.c     |  19 ++++++++-
>  target-arm/translate-a64.c |  49 +++++++++++++++------
>  target-arm/translate.c     | 103 ++++++++++++++++++++++++++++-----------------
>  target-arm/translate.h     |   4 ++
>  6 files changed, 138 insertions(+), 54 deletions(-)
>
> diff --git a/target-arm/helper.h b/target-arm/helper.h
> index 7f23cb8..2729ea5 100644
> --- a/target-arm/helper.h
> +++ b/target-arm/helper.h
> @@ -48,7 +48,8 @@ DEF_HELPER_FLAGS_2(usad8, TCG_CALL_NO_RWG_SE, i32, i32, i32)
>
>  DEF_HELPER_FLAGS_3(sel_flags, TCG_CALL_NO_RWG_SE,
>                     i32, i32, i32, i32)
> -DEF_HELPER_2(exception, void, env, i32)
> +DEF_HELPER_2(exception_internal, void, env, i32)
> +DEF_HELPER_3(exception_with_syndrome, void, env, i32, i32)
>  DEF_HELPER_1(wfi, void, env)
>
>  DEF_HELPER_3(cpsr_write, void, env, i32, i32)
> diff --git a/target-arm/internals.h b/target-arm/internals.h
> index 2c0db20..9bec4e1 100644
> --- a/target-arm/internals.h
> +++ b/target-arm/internals.h
> @@ -25,6 +25,20 @@
>  #ifndef TARGET_ARM_INTERNALS_H
>  #define TARGET_ARM_INTERNALS_H
>
> +static inline bool excp_is_internal(int excp)
> +{
> +    /* Return true if this exception number represents a QEMU-internal
> +     * exception that will not be passed to the guest.
> +     */
> +    return excp == EXCP_INTERRUPT
> +        || excp == EXCP_HLT
> +        || excp == EXCP_DEBUG
> +        || excp == EXCP_HALTED
> +        || excp == EXCP_EXCEPTION_EXIT
> +        || excp == EXCP_KERNEL_TRAP
> +        || excp == EXCP_STREX;
> +}
> +
>  /* Scale factor for generic timers, ie number of ns per tick.
>   * This gives a 62.5MHz timer.
>   */
> diff --git a/target-arm/op_helper.c b/target-arm/op_helper.c
> index bef2cf6..b1db672 100644
> --- a/target-arm/op_helper.c
> +++ b/target-arm/op_helper.c
> @@ -226,9 +226,26 @@ void HELPER(wfi)(CPUARMState *env)
>      cpu_loop_exit(env);
>  }
>
> -void HELPER(exception)(CPUARMState *env, uint32_t excp)
> +/* Raise an internal-to-QEMU exception. This is limited to only
> + * those EXCP values which are special cases for QEMU to interrupt
> + * execution and not to be used for exceptions which are passed to
> + * the guest (those must all have syndrome information and thus should
> + * use exception_with_syndrome).
> + */
> +void HELPER(exception_internal)(CPUARMState *env, uint32_t excp)
> +{
> +    assert(excp_is_internal(excp));
> +    env->exception_index = excp;
> +    cpu_loop_exit(env);
> +}
> +
> +/* Raise an exception with the specified syndrome register value */
> +void HELPER(exception_with_syndrome)(CPUARMState *env, uint32_t excp,
> +                                     uint32_t syndrome)
>  {
> +    assert(!excp_is_internal(excp));
>      env->exception_index = excp;
> +    env->exception.syndrome = syndrome;
>      cpu_loop_exit(env);
>  }
>
> diff --git a/target-arm/translate-a64.c b/target-arm/translate-a64.c
> index a4f9258..b32068e 100644
> --- a/target-arm/translate-a64.c
> +++ b/target-arm/translate-a64.c
> @@ -173,18 +173,37 @@ void gen_a64_set_pc_im(uint64_t val)
>      tcg_gen_movi_i64(cpu_pc, val);
>  }
>
> -static void gen_exception(int excp)
> +static void gen_exception_internal(int excp)
>  {
> -    TCGv_i32 tmp = tcg_temp_new_i32();
> -    tcg_gen_movi_i32(tmp, excp);
> -    gen_helper_exception(cpu_env, tmp);
> -    tcg_temp_free_i32(tmp);
> +    TCGv_i32 tcg_excp = tcg_const_i32(excp);
> +
> +    assert(excp_is_internal(excp));
> +    gen_helper_exception_internal(cpu_env, tcg_excp);
> +    tcg_temp_free_i32(tcg_excp);
> +}
> +
> +static void gen_exception(int excp, uint32_t syndrome)
> +{
> +    TCGv_i32 tcg_excp = tcg_const_i32(excp);
> +    TCGv_i32 tcg_syn = tcg_const_i32(syndrome);
> +
> +    gen_helper_exception_with_syndrome(cpu_env, tcg_excp, tcg_syn);
> +    tcg_temp_free_i32(tcg_syn);
> +    tcg_temp_free_i32(tcg_excp);
> +}
> +
> +static void gen_exception_internal_insn(DisasContext *s, int offset, int excp)
> +{
> +    gen_a64_set_pc_im(s->pc - offset);
> +    gen_exception_internal(excp);
> +    s->is_jmp = DISAS_EXC;
>  }
>
> -static void gen_exception_insn(DisasContext *s, int offset, int excp)
> +static void gen_exception_insn(DisasContext *s, int offset, int excp,
> +                               uint32_t syndrome)
>  {
>      gen_a64_set_pc_im(s->pc - offset);
> -    gen_exception(excp);
> +    gen_exception(excp, syndrome);
>      s->is_jmp = DISAS_EXC;
>  }
>
> @@ -216,7 +235,7 @@ static inline void gen_goto_tb(DisasContext *s, int n, uint64_t dest)
>      } else {
>          gen_a64_set_pc_im(dest);
>          if (s->singlestep_enabled) {
> -            gen_exception(EXCP_DEBUG);
> +            gen_exception_internal(EXCP_DEBUG);
>          }
>          tcg_gen_exit_tb(0);
>          s->is_jmp = DISAS_JUMP;
> @@ -225,7 +244,8 @@ static inline void gen_goto_tb(DisasContext *s, int n, uint64_t dest)
>
>  static void unallocated_encoding(DisasContext *s)
>  {
> -    gen_exception_insn(s, 4, EXCP_UDEF);
> +    /* Unallocated and reserved encodings are uncategorized */
> +    gen_exception_insn(s, 4, EXCP_UDEF, syn_uncategorized());
>  }
>
>  #define unsupported_encoding(s, insn)                                    \
> @@ -1370,6 +1390,7 @@ static void disas_exc(DisasContext *s, uint32_t insn)
>  {
>      int opc = extract32(insn, 21, 3);
>      int op2_ll = extract32(insn, 0, 5);
> +    int imm16 = extract32(insn, 5, 16);
>
>      switch (opc) {
>      case 0:
> @@ -1380,7 +1401,7 @@ static void disas_exc(DisasContext *s, uint32_t insn)
>              unallocated_encoding(s);
>              break;
>          }
> -        gen_exception_insn(s, 0, EXCP_SWI);
> +        gen_exception_insn(s, 0, EXCP_SWI, syn_aa64_svc(imm16));
>          break;
>      case 1:
>          if (op2_ll != 0) {
> @@ -1388,7 +1409,7 @@ static void disas_exc(DisasContext *s, uint32_t insn)
>              break;
>          }
>          /* BRK */
> -        gen_exception_insn(s, 0, EXCP_BKPT);
> +        gen_exception_insn(s, 0, EXCP_BKPT, syn_aa64_bkpt(imm16));
>          break;
>      case 2:
>          if (op2_ll != 0) {
> @@ -1537,7 +1558,7 @@ static void gen_store_exclusive(DisasContext *s, int rd, int rt, int rt2,
>      tcg_gen_mov_i64(cpu_exclusive_test, addr);
>      tcg_gen_movi_i32(cpu_exclusive_info,
>                       size | is_pair << 2 | (rd << 4) | (rt << 9) | (rt2 << 14));
> -    gen_exception_insn(s, 4, EXCP_STREX);
> +    gen_exception_internal_insn(s, 4, EXCP_STREX);
>  }
>  #else
>  static void gen_store_exclusive(DisasContext *s, int rd, int rt, int rt2,
> @@ -9108,7 +9129,7 @@ void gen_intermediate_code_internal_a64(ARMCPU *cpu,
>          if (unlikely(!QTAILQ_EMPTY(&env->breakpoints))) {
>              QTAILQ_FOREACH(bp, &env->breakpoints, entry) {
>                  if (bp->pc == dc->pc) {
> -                    gen_exception_insn(dc, 0, EXCP_DEBUG);
> +                    gen_exception_internal_insn(dc, 0, EXCP_DEBUG);
>                      /* Advance PC so that clearing the breakpoint will
>                         invalidate this TB.  */
>                      dc->pc += 2;
> @@ -9171,7 +9192,7 @@ void gen_intermediate_code_internal_a64(ARMCPU *cpu,
>          if (dc->is_jmp != DISAS_JUMP) {
>              gen_a64_set_pc_im(dc->pc);
>          }
> -        gen_exception(EXCP_DEBUG);
> +        gen_exception_internal(EXCP_DEBUG);
>      } else {
>          switch (dc->is_jmp) {
>          case DISAS_NEXT:
> diff --git a/target-arm/translate.c b/target-arm/translate.c
> index 9a81222..094be07 100644
> --- a/target-arm/translate.c
> +++ b/target-arm/translate.c
> @@ -183,12 +183,23 @@ static inline void gen_set_cpsr(TCGv_i32 var, uint32_t mask)
>  /* Set NZCV flags from the high 4 bits of var.  */
>  #define gen_set_nzcv(var) gen_set_cpsr(var, CPSR_NZCV)
>
> -static void gen_exception(int excp)
> +static void gen_exception_internal(int excp)
>  {
> -    TCGv_i32 tmp = tcg_temp_new_i32();
> -    tcg_gen_movi_i32(tmp, excp);
> -    gen_helper_exception(cpu_env, tmp);
> -    tcg_temp_free_i32(tmp);
> +    TCGv_i32 tcg_excp = tcg_const_i32(excp);
> +
> +    assert(excp_is_internal(excp));
> +    gen_helper_exception_internal(cpu_env, tcg_excp);
> +    tcg_temp_free_i32(tcg_excp);
> +}
> +

AFAICT this is identical to gen_exception_internal in translate-a64.c.
Can they be de-static'd and prototyped in internals.h?

> +static void gen_exception(int excp, uint32_t syndrome)
> +{
> +    TCGv_i32 tcg_excp = tcg_const_i32(excp);
> +    TCGv_i32 tcg_syn = tcg_const_i32(syndrome);
> +
> +    gen_helper_exception_with_syndrome(cpu_env, tcg_excp, tcg_syn);
> +    tcg_temp_free_i32(tcg_syn);
> +    tcg_temp_free_i32(tcg_excp);
>  }
>

And here.

Otherwise

Reviewed-by: Peter Crosthwaite <peter.crosthwaite@xilinx.com>

(I haven't gone line-line checking all arguments against TRM, but the
schema and framework is good).

Regards,
Peter

>  static void gen_smul_dual(TCGv_i32 a, TCGv_i32 b)
> @@ -900,6 +911,33 @@ static inline void gen_set_pc_im(DisasContext *s, target_ulong val)
>      tcg_gen_movi_i32(cpu_R[15], val);
>  }
>
> +static inline void
> +gen_set_condexec (DisasContext *s)
> +{
> +    if (s->condexec_mask) {
> +        uint32_t val = (s->condexec_cond << 4) | (s->condexec_mask >> 1);
> +        TCGv_i32 tmp = tcg_temp_new_i32();
> +        tcg_gen_movi_i32(tmp, val);
> +        store_cpu_field(tmp, condexec_bits);
> +    }
> +}
> +
> +static void gen_exception_internal_insn(DisasContext *s, int offset, int excp)
> +{
> +    gen_set_condexec(s);
> +    gen_set_pc_im(s, s->pc - offset);
> +    gen_exception_internal(excp);
> +    s->is_jmp = DISAS_JUMP;
> +}
> +
> +static void gen_exception_insn(DisasContext *s, int offset, int excp, int syn)
> +{
> +    gen_set_condexec(s);
> +    gen_set_pc_im(s, s->pc - offset);
> +    gen_exception(excp, syn);
> +    s->is_jmp = DISAS_JUMP;
> +}
> +
>  /* Force a TB lookup after an instruction that changes the CPU state.  */
>  static inline void gen_lookup_tb(DisasContext *s)
>  {
> @@ -3913,25 +3951,6 @@ static void gen_rfe(DisasContext *s, TCGv_i32 pc, TCGv_i32 cpsr)
>      s->is_jmp = DISAS_UPDATE;
>  }
>
> -static inline void
> -gen_set_condexec (DisasContext *s)
> -{
> -    if (s->condexec_mask) {
> -        uint32_t val = (s->condexec_cond << 4) | (s->condexec_mask >> 1);
> -        TCGv_i32 tmp = tcg_temp_new_i32();
> -        tcg_gen_movi_i32(tmp, val);
> -        store_cpu_field(tmp, condexec_bits);
> -    }
> -}
> -
> -static void gen_exception_insn(DisasContext *s, int offset, int excp)
> -{
> -    gen_set_condexec(s);
> -    gen_set_pc_im(s, s->pc - offset);
> -    gen_exception(excp);
> -    s->is_jmp = DISAS_JUMP;
> -}
> -
>  static void gen_nop_hint(DisasContext *s, int val)
>  {
>      switch (val) {
> @@ -7141,7 +7160,7 @@ static void gen_store_exclusive(DisasContext *s, int rd, int rt, int rt2,
>      tcg_gen_extu_i32_i64(cpu_exclusive_test, addr);
>      tcg_gen_movi_i32(cpu_exclusive_info,
>                       size | (rd << 4) | (rt << 8) | (rt2 << 12));
> -    gen_exception_insn(s, 4, EXCP_STREX);
> +    gen_exception_internal_insn(s, 4, EXCP_STREX);
>  }
>  #else
>  static void gen_store_exclusive(DisasContext *s, int rd, int rt, int rt2,
> @@ -7651,6 +7670,8 @@ static void disas_arm_insn(CPUARMState * env, DisasContext *s)
>              store_reg(s, rd, tmp);
>              break;
>          case 7:
> +        {
> +            int imm16 = extract32(insn, 0, 4) | (extract32(insn, 8, 12) << 4);
>              /* SMC instruction (op1 == 3)
>                 and undefined instructions (op1 == 0 || op1 == 2)
>                 will trap */
> @@ -7659,8 +7680,9 @@ static void disas_arm_insn(CPUARMState * env, DisasContext *s)
>              }
>              /* bkpt */
>              ARCH(5);
> -            gen_exception_insn(s, 4, EXCP_BKPT);
> +            gen_exception_insn(s, 4, EXCP_BKPT, syn_aa32_bkpt(imm16, false));
>              break;
> +        }
>          case 0x8: /* signed multiply */
>          case 0xa:
>          case 0xc:
> @@ -8667,11 +8689,12 @@ static void disas_arm_insn(CPUARMState * env, DisasContext *s)
>          case 0xf:
>              /* swi */
>              gen_set_pc_im(s, s->pc);
> +            s->svc_imm = extract32(insn, 0, 24);
>              s->is_jmp = DISAS_SWI;
>              break;
>          default:
>          illegal_op:
> -            gen_exception_insn(s, 4, EXCP_UDEF);
> +            gen_exception_insn(s, 4, EXCP_UDEF, syn_uncategorized());
>              break;
>          }
>      }
> @@ -10482,9 +10505,12 @@ static void disas_thumb_insn(CPUARMState *env, DisasContext *s)
>              break;
>
>          case 0xe: /* bkpt */
> +        {
> +            int imm8 = extract32(insn, 0, 8);
>              ARCH(5);
> -            gen_exception_insn(s, 2, EXCP_BKPT);
> +            gen_exception_insn(s, 2, EXCP_BKPT, syn_aa32_bkpt(imm8, true));
>              break;
> +        }
>
>          case 0xa: /* rev */
>              ARCH(6);
> @@ -10601,6 +10627,7 @@ static void disas_thumb_insn(CPUARMState *env, DisasContext *s)
>          if (cond == 0xf) {
>              /* swi */
>              gen_set_pc_im(s, s->pc);
> +            s->svc_imm = extract32(insn, 0, 8);
>              s->is_jmp = DISAS_SWI;
>              break;
>          }
> @@ -10636,11 +10663,11 @@ static void disas_thumb_insn(CPUARMState *env, DisasContext *s)
>      }
>      return;
>  undef32:
> -    gen_exception_insn(s, 4, EXCP_UDEF);
> +    gen_exception_insn(s, 4, EXCP_UDEF, syn_uncategorized());
>      return;
>  illegal_op:
>  undef:
> -    gen_exception_insn(s, 2, EXCP_UDEF);
> +    gen_exception_insn(s, 2, EXCP_UDEF, syn_uncategorized());
>  }
>
>  /* generate intermediate code in gen_opc_buf and gen_opparam_buf for
> @@ -10761,7 +10788,7 @@ static inline void gen_intermediate_code_internal(ARMCPU *cpu,
>          if (dc->pc >= 0xffff0000) {
>              /* We always get here via a jump, so know we are not in a
>                 conditional execution block.  */
> -            gen_exception(EXCP_KERNEL_TRAP);
> +            gen_exception_internal(EXCP_KERNEL_TRAP);
>              dc->is_jmp = DISAS_UPDATE;
>              break;
>          }
> @@ -10769,7 +10796,7 @@ static inline void gen_intermediate_code_internal(ARMCPU *cpu,
>          if (dc->pc >= 0xfffffff0 && IS_M(env)) {
>              /* We always get here via a jump, so know we are not in a
>                 conditional execution block.  */
> -            gen_exception(EXCP_EXCEPTION_EXIT);
> +            gen_exception_internal(EXCP_EXCEPTION_EXIT);
>              dc->is_jmp = DISAS_UPDATE;
>              break;
>          }
> @@ -10778,7 +10805,7 @@ static inline void gen_intermediate_code_internal(ARMCPU *cpu,
>          if (unlikely(!QTAILQ_EMPTY(&env->breakpoints))) {
>              QTAILQ_FOREACH(bp, &env->breakpoints, entry) {
>                  if (bp->pc == dc->pc) {
> -                    gen_exception_insn(dc, 0, EXCP_DEBUG);
> +                    gen_exception_internal_insn(dc, 0, EXCP_DEBUG);
>                      /* Advance PC so that clearing the breakpoint will
>                         invalidate this TB.  */
>                      dc->pc += 2;
> @@ -10858,9 +10885,9 @@ static inline void gen_intermediate_code_internal(ARMCPU *cpu,
>          if (dc->condjmp) {
>              gen_set_condexec(dc);
>              if (dc->is_jmp == DISAS_SWI) {
> -                gen_exception(EXCP_SWI);
> +                gen_exception(EXCP_SWI, syn_aa32_svc(dc->svc_imm, dc->thumb));
>              } else {
> -                gen_exception(EXCP_DEBUG);
> +                gen_exception_internal(EXCP_DEBUG);
>              }
>              gen_set_label(dc->condlabel);
>          }
> @@ -10870,11 +10897,11 @@ static inline void gen_intermediate_code_internal(ARMCPU *cpu,
>          }
>          gen_set_condexec(dc);
>          if (dc->is_jmp == DISAS_SWI && !dc->condjmp) {
> -            gen_exception(EXCP_SWI);
> +            gen_exception(EXCP_SWI, syn_aa32_svc(dc->svc_imm, dc->thumb));
>          } else {
>              /* FIXME: Single stepping a WFI insn will not halt
>                 the CPU.  */
> -            gen_exception(EXCP_DEBUG);
> +            gen_exception_internal(EXCP_DEBUG);
>          }
>      } else {
>          /* While branches must always occur at the end of an IT block,
> @@ -10903,7 +10930,7 @@ static inline void gen_intermediate_code_internal(ARMCPU *cpu,
>              gen_helper_wfi(cpu_env);
>              break;
>          case DISAS_SWI:
> -            gen_exception(EXCP_SWI);
> +            gen_exception(EXCP_SWI, syn_aa32_svc(dc->svc_imm, dc->thumb));
>              break;
>          }
>          if (dc->condjmp) {
> diff --git a/target-arm/translate.h b/target-arm/translate.h
> index 889a031..4d3d363 100644
> --- a/target-arm/translate.h
> +++ b/target-arm/translate.h
> @@ -23,6 +23,10 @@ typedef struct DisasContext {
>      int vfp_enabled;
>      int vec_len;
>      int vec_stride;
> +    /* Immediate value in AArch32 SVC insn; must be set if is_jmp == DISAS_SWI
> +     * so that top level loop can generate correct syndrome information.
> +     */
> +    uint32_t svc_imm;
>      int aarch64;
>      int current_pl;
>      GHashTable *cp_regs;
> --
> 1.9.0
>
>
Peter Maydell March 17, 2014, 12:40 p.m. UTC | #2
On 17 March 2014 03:19, Peter Crosthwaite <peter.crosthwaite@xilinx.com> wrote:
> On Fri, Mar 7, 2014 at 5:32 AM, Peter Maydell <peter.maydell@linaro.org> wrote:
>> -static void gen_exception(int excp)
>> +static void gen_exception_internal(int excp)
>>  {
>> -    TCGv_i32 tmp = tcg_temp_new_i32();
>> -    tcg_gen_movi_i32(tmp, excp);
>> -    gen_helper_exception(cpu_env, tmp);
>> -    tcg_temp_free_i32(tmp);
>> +    TCGv_i32 tcg_excp = tcg_const_i32(excp);
>> +
>> +    assert(excp_is_internal(excp));
>> +    gen_helper_exception_internal(cpu_env, tcg_excp);
>> +    tcg_temp_free_i32(tcg_excp);
>> +}
>> +
>
> AFAICT this is identical to gen_exception_internal in translate-a64.c.
> Can they be de-static'd and prototyped in internals.h?

This is true, but it would break the current situation we
have where translate.c and translate-a64.c are entirely
independent and you never have to worry about breaking
one if you make changes to the other, which is why I didn't
do it. Maybe that's not very important, but it didn't seem
worth going against for the sake of a couple of helpers just
a few lines long. (If we do make them common then translate.h
would be the right place for the prototypes.)

thanks
-- PMM
diff mbox

Patch

diff --git a/target-arm/helper.h b/target-arm/helper.h
index 7f23cb8..2729ea5 100644
--- a/target-arm/helper.h
+++ b/target-arm/helper.h
@@ -48,7 +48,8 @@  DEF_HELPER_FLAGS_2(usad8, TCG_CALL_NO_RWG_SE, i32, i32, i32)
 
 DEF_HELPER_FLAGS_3(sel_flags, TCG_CALL_NO_RWG_SE,
                    i32, i32, i32, i32)
-DEF_HELPER_2(exception, void, env, i32)
+DEF_HELPER_2(exception_internal, void, env, i32)
+DEF_HELPER_3(exception_with_syndrome, void, env, i32, i32)
 DEF_HELPER_1(wfi, void, env)
 
 DEF_HELPER_3(cpsr_write, void, env, i32, i32)
diff --git a/target-arm/internals.h b/target-arm/internals.h
index 2c0db20..9bec4e1 100644
--- a/target-arm/internals.h
+++ b/target-arm/internals.h
@@ -25,6 +25,20 @@ 
 #ifndef TARGET_ARM_INTERNALS_H
 #define TARGET_ARM_INTERNALS_H
 
+static inline bool excp_is_internal(int excp)
+{
+    /* Return true if this exception number represents a QEMU-internal
+     * exception that will not be passed to the guest.
+     */
+    return excp == EXCP_INTERRUPT
+        || excp == EXCP_HLT
+        || excp == EXCP_DEBUG
+        || excp == EXCP_HALTED
+        || excp == EXCP_EXCEPTION_EXIT
+        || excp == EXCP_KERNEL_TRAP
+        || excp == EXCP_STREX;
+}
+
 /* Scale factor for generic timers, ie number of ns per tick.
  * This gives a 62.5MHz timer.
  */
diff --git a/target-arm/op_helper.c b/target-arm/op_helper.c
index bef2cf6..b1db672 100644
--- a/target-arm/op_helper.c
+++ b/target-arm/op_helper.c
@@ -226,9 +226,26 @@  void HELPER(wfi)(CPUARMState *env)
     cpu_loop_exit(env);
 }
 
-void HELPER(exception)(CPUARMState *env, uint32_t excp)
+/* Raise an internal-to-QEMU exception. This is limited to only
+ * those EXCP values which are special cases for QEMU to interrupt
+ * execution and not to be used for exceptions which are passed to
+ * the guest (those must all have syndrome information and thus should
+ * use exception_with_syndrome).
+ */
+void HELPER(exception_internal)(CPUARMState *env, uint32_t excp)
+{
+    assert(excp_is_internal(excp));
+    env->exception_index = excp;
+    cpu_loop_exit(env);
+}
+
+/* Raise an exception with the specified syndrome register value */
+void HELPER(exception_with_syndrome)(CPUARMState *env, uint32_t excp,
+                                     uint32_t syndrome)
 {
+    assert(!excp_is_internal(excp));
     env->exception_index = excp;
+    env->exception.syndrome = syndrome;
     cpu_loop_exit(env);
 }
 
diff --git a/target-arm/translate-a64.c b/target-arm/translate-a64.c
index a4f9258..b32068e 100644
--- a/target-arm/translate-a64.c
+++ b/target-arm/translate-a64.c
@@ -173,18 +173,37 @@  void gen_a64_set_pc_im(uint64_t val)
     tcg_gen_movi_i64(cpu_pc, val);
 }
 
-static void gen_exception(int excp)
+static void gen_exception_internal(int excp)
 {
-    TCGv_i32 tmp = tcg_temp_new_i32();
-    tcg_gen_movi_i32(tmp, excp);
-    gen_helper_exception(cpu_env, tmp);
-    tcg_temp_free_i32(tmp);
+    TCGv_i32 tcg_excp = tcg_const_i32(excp);
+
+    assert(excp_is_internal(excp));
+    gen_helper_exception_internal(cpu_env, tcg_excp);
+    tcg_temp_free_i32(tcg_excp);
+}
+
+static void gen_exception(int excp, uint32_t syndrome)
+{
+    TCGv_i32 tcg_excp = tcg_const_i32(excp);
+    TCGv_i32 tcg_syn = tcg_const_i32(syndrome);
+
+    gen_helper_exception_with_syndrome(cpu_env, tcg_excp, tcg_syn);
+    tcg_temp_free_i32(tcg_syn);
+    tcg_temp_free_i32(tcg_excp);
+}
+
+static void gen_exception_internal_insn(DisasContext *s, int offset, int excp)
+{
+    gen_a64_set_pc_im(s->pc - offset);
+    gen_exception_internal(excp);
+    s->is_jmp = DISAS_EXC;
 }
 
-static void gen_exception_insn(DisasContext *s, int offset, int excp)
+static void gen_exception_insn(DisasContext *s, int offset, int excp,
+                               uint32_t syndrome)
 {
     gen_a64_set_pc_im(s->pc - offset);
-    gen_exception(excp);
+    gen_exception(excp, syndrome);
     s->is_jmp = DISAS_EXC;
 }
 
@@ -216,7 +235,7 @@  static inline void gen_goto_tb(DisasContext *s, int n, uint64_t dest)
     } else {
         gen_a64_set_pc_im(dest);
         if (s->singlestep_enabled) {
-            gen_exception(EXCP_DEBUG);
+            gen_exception_internal(EXCP_DEBUG);
         }
         tcg_gen_exit_tb(0);
         s->is_jmp = DISAS_JUMP;
@@ -225,7 +244,8 @@  static inline void gen_goto_tb(DisasContext *s, int n, uint64_t dest)
 
 static void unallocated_encoding(DisasContext *s)
 {
-    gen_exception_insn(s, 4, EXCP_UDEF);
+    /* Unallocated and reserved encodings are uncategorized */
+    gen_exception_insn(s, 4, EXCP_UDEF, syn_uncategorized());
 }
 
 #define unsupported_encoding(s, insn)                                    \
@@ -1370,6 +1390,7 @@  static void disas_exc(DisasContext *s, uint32_t insn)
 {
     int opc = extract32(insn, 21, 3);
     int op2_ll = extract32(insn, 0, 5);
+    int imm16 = extract32(insn, 5, 16);
 
     switch (opc) {
     case 0:
@@ -1380,7 +1401,7 @@  static void disas_exc(DisasContext *s, uint32_t insn)
             unallocated_encoding(s);
             break;
         }
-        gen_exception_insn(s, 0, EXCP_SWI);
+        gen_exception_insn(s, 0, EXCP_SWI, syn_aa64_svc(imm16));
         break;
     case 1:
         if (op2_ll != 0) {
@@ -1388,7 +1409,7 @@  static void disas_exc(DisasContext *s, uint32_t insn)
             break;
         }
         /* BRK */
-        gen_exception_insn(s, 0, EXCP_BKPT);
+        gen_exception_insn(s, 0, EXCP_BKPT, syn_aa64_bkpt(imm16));
         break;
     case 2:
         if (op2_ll != 0) {
@@ -1537,7 +1558,7 @@  static void gen_store_exclusive(DisasContext *s, int rd, int rt, int rt2,
     tcg_gen_mov_i64(cpu_exclusive_test, addr);
     tcg_gen_movi_i32(cpu_exclusive_info,
                      size | is_pair << 2 | (rd << 4) | (rt << 9) | (rt2 << 14));
-    gen_exception_insn(s, 4, EXCP_STREX);
+    gen_exception_internal_insn(s, 4, EXCP_STREX);
 }
 #else
 static void gen_store_exclusive(DisasContext *s, int rd, int rt, int rt2,
@@ -9108,7 +9129,7 @@  void gen_intermediate_code_internal_a64(ARMCPU *cpu,
         if (unlikely(!QTAILQ_EMPTY(&env->breakpoints))) {
             QTAILQ_FOREACH(bp, &env->breakpoints, entry) {
                 if (bp->pc == dc->pc) {
-                    gen_exception_insn(dc, 0, EXCP_DEBUG);
+                    gen_exception_internal_insn(dc, 0, EXCP_DEBUG);
                     /* Advance PC so that clearing the breakpoint will
                        invalidate this TB.  */
                     dc->pc += 2;
@@ -9171,7 +9192,7 @@  void gen_intermediate_code_internal_a64(ARMCPU *cpu,
         if (dc->is_jmp != DISAS_JUMP) {
             gen_a64_set_pc_im(dc->pc);
         }
-        gen_exception(EXCP_DEBUG);
+        gen_exception_internal(EXCP_DEBUG);
     } else {
         switch (dc->is_jmp) {
         case DISAS_NEXT:
diff --git a/target-arm/translate.c b/target-arm/translate.c
index 9a81222..094be07 100644
--- a/target-arm/translate.c
+++ b/target-arm/translate.c
@@ -183,12 +183,23 @@  static inline void gen_set_cpsr(TCGv_i32 var, uint32_t mask)
 /* Set NZCV flags from the high 4 bits of var.  */
 #define gen_set_nzcv(var) gen_set_cpsr(var, CPSR_NZCV)
 
-static void gen_exception(int excp)
+static void gen_exception_internal(int excp)
 {
-    TCGv_i32 tmp = tcg_temp_new_i32();
-    tcg_gen_movi_i32(tmp, excp);
-    gen_helper_exception(cpu_env, tmp);
-    tcg_temp_free_i32(tmp);
+    TCGv_i32 tcg_excp = tcg_const_i32(excp);
+
+    assert(excp_is_internal(excp));
+    gen_helper_exception_internal(cpu_env, tcg_excp);
+    tcg_temp_free_i32(tcg_excp);
+}
+
+static void gen_exception(int excp, uint32_t syndrome)
+{
+    TCGv_i32 tcg_excp = tcg_const_i32(excp);
+    TCGv_i32 tcg_syn = tcg_const_i32(syndrome);
+
+    gen_helper_exception_with_syndrome(cpu_env, tcg_excp, tcg_syn);
+    tcg_temp_free_i32(tcg_syn);
+    tcg_temp_free_i32(tcg_excp);
 }
 
 static void gen_smul_dual(TCGv_i32 a, TCGv_i32 b)
@@ -900,6 +911,33 @@  static inline void gen_set_pc_im(DisasContext *s, target_ulong val)
     tcg_gen_movi_i32(cpu_R[15], val);
 }
 
+static inline void
+gen_set_condexec (DisasContext *s)
+{
+    if (s->condexec_mask) {
+        uint32_t val = (s->condexec_cond << 4) | (s->condexec_mask >> 1);
+        TCGv_i32 tmp = tcg_temp_new_i32();
+        tcg_gen_movi_i32(tmp, val);
+        store_cpu_field(tmp, condexec_bits);
+    }
+}
+
+static void gen_exception_internal_insn(DisasContext *s, int offset, int excp)
+{
+    gen_set_condexec(s);
+    gen_set_pc_im(s, s->pc - offset);
+    gen_exception_internal(excp);
+    s->is_jmp = DISAS_JUMP;
+}
+
+static void gen_exception_insn(DisasContext *s, int offset, int excp, int syn)
+{
+    gen_set_condexec(s);
+    gen_set_pc_im(s, s->pc - offset);
+    gen_exception(excp, syn);
+    s->is_jmp = DISAS_JUMP;
+}
+
 /* Force a TB lookup after an instruction that changes the CPU state.  */
 static inline void gen_lookup_tb(DisasContext *s)
 {
@@ -3913,25 +3951,6 @@  static void gen_rfe(DisasContext *s, TCGv_i32 pc, TCGv_i32 cpsr)
     s->is_jmp = DISAS_UPDATE;
 }
 
-static inline void
-gen_set_condexec (DisasContext *s)
-{
-    if (s->condexec_mask) {
-        uint32_t val = (s->condexec_cond << 4) | (s->condexec_mask >> 1);
-        TCGv_i32 tmp = tcg_temp_new_i32();
-        tcg_gen_movi_i32(tmp, val);
-        store_cpu_field(tmp, condexec_bits);
-    }
-}
-
-static void gen_exception_insn(DisasContext *s, int offset, int excp)
-{
-    gen_set_condexec(s);
-    gen_set_pc_im(s, s->pc - offset);
-    gen_exception(excp);
-    s->is_jmp = DISAS_JUMP;
-}
-
 static void gen_nop_hint(DisasContext *s, int val)
 {
     switch (val) {
@@ -7141,7 +7160,7 @@  static void gen_store_exclusive(DisasContext *s, int rd, int rt, int rt2,
     tcg_gen_extu_i32_i64(cpu_exclusive_test, addr);
     tcg_gen_movi_i32(cpu_exclusive_info,
                      size | (rd << 4) | (rt << 8) | (rt2 << 12));
-    gen_exception_insn(s, 4, EXCP_STREX);
+    gen_exception_internal_insn(s, 4, EXCP_STREX);
 }
 #else
 static void gen_store_exclusive(DisasContext *s, int rd, int rt, int rt2,
@@ -7651,6 +7670,8 @@  static void disas_arm_insn(CPUARMState * env, DisasContext *s)
             store_reg(s, rd, tmp);
             break;
         case 7:
+        {
+            int imm16 = extract32(insn, 0, 4) | (extract32(insn, 8, 12) << 4);
             /* SMC instruction (op1 == 3)
                and undefined instructions (op1 == 0 || op1 == 2)
                will trap */
@@ -7659,8 +7680,9 @@  static void disas_arm_insn(CPUARMState * env, DisasContext *s)
             }
             /* bkpt */
             ARCH(5);
-            gen_exception_insn(s, 4, EXCP_BKPT);
+            gen_exception_insn(s, 4, EXCP_BKPT, syn_aa32_bkpt(imm16, false));
             break;
+        }
         case 0x8: /* signed multiply */
         case 0xa:
         case 0xc:
@@ -8667,11 +8689,12 @@  static void disas_arm_insn(CPUARMState * env, DisasContext *s)
         case 0xf:
             /* swi */
             gen_set_pc_im(s, s->pc);
+            s->svc_imm = extract32(insn, 0, 24);
             s->is_jmp = DISAS_SWI;
             break;
         default:
         illegal_op:
-            gen_exception_insn(s, 4, EXCP_UDEF);
+            gen_exception_insn(s, 4, EXCP_UDEF, syn_uncategorized());
             break;
         }
     }
@@ -10482,9 +10505,12 @@  static void disas_thumb_insn(CPUARMState *env, DisasContext *s)
             break;
 
         case 0xe: /* bkpt */
+        {
+            int imm8 = extract32(insn, 0, 8);
             ARCH(5);
-            gen_exception_insn(s, 2, EXCP_BKPT);
+            gen_exception_insn(s, 2, EXCP_BKPT, syn_aa32_bkpt(imm8, true));
             break;
+        }
 
         case 0xa: /* rev */
             ARCH(6);
@@ -10601,6 +10627,7 @@  static void disas_thumb_insn(CPUARMState *env, DisasContext *s)
         if (cond == 0xf) {
             /* swi */
             gen_set_pc_im(s, s->pc);
+            s->svc_imm = extract32(insn, 0, 8);
             s->is_jmp = DISAS_SWI;
             break;
         }
@@ -10636,11 +10663,11 @@  static void disas_thumb_insn(CPUARMState *env, DisasContext *s)
     }
     return;
 undef32:
-    gen_exception_insn(s, 4, EXCP_UDEF);
+    gen_exception_insn(s, 4, EXCP_UDEF, syn_uncategorized());
     return;
 illegal_op:
 undef:
-    gen_exception_insn(s, 2, EXCP_UDEF);
+    gen_exception_insn(s, 2, EXCP_UDEF, syn_uncategorized());
 }
 
 /* generate intermediate code in gen_opc_buf and gen_opparam_buf for
@@ -10761,7 +10788,7 @@  static inline void gen_intermediate_code_internal(ARMCPU *cpu,
         if (dc->pc >= 0xffff0000) {
             /* We always get here via a jump, so know we are not in a
                conditional execution block.  */
-            gen_exception(EXCP_KERNEL_TRAP);
+            gen_exception_internal(EXCP_KERNEL_TRAP);
             dc->is_jmp = DISAS_UPDATE;
             break;
         }
@@ -10769,7 +10796,7 @@  static inline void gen_intermediate_code_internal(ARMCPU *cpu,
         if (dc->pc >= 0xfffffff0 && IS_M(env)) {
             /* We always get here via a jump, so know we are not in a
                conditional execution block.  */
-            gen_exception(EXCP_EXCEPTION_EXIT);
+            gen_exception_internal(EXCP_EXCEPTION_EXIT);
             dc->is_jmp = DISAS_UPDATE;
             break;
         }
@@ -10778,7 +10805,7 @@  static inline void gen_intermediate_code_internal(ARMCPU *cpu,
         if (unlikely(!QTAILQ_EMPTY(&env->breakpoints))) {
             QTAILQ_FOREACH(bp, &env->breakpoints, entry) {
                 if (bp->pc == dc->pc) {
-                    gen_exception_insn(dc, 0, EXCP_DEBUG);
+                    gen_exception_internal_insn(dc, 0, EXCP_DEBUG);
                     /* Advance PC so that clearing the breakpoint will
                        invalidate this TB.  */
                     dc->pc += 2;
@@ -10858,9 +10885,9 @@  static inline void gen_intermediate_code_internal(ARMCPU *cpu,
         if (dc->condjmp) {
             gen_set_condexec(dc);
             if (dc->is_jmp == DISAS_SWI) {
-                gen_exception(EXCP_SWI);
+                gen_exception(EXCP_SWI, syn_aa32_svc(dc->svc_imm, dc->thumb));
             } else {
-                gen_exception(EXCP_DEBUG);
+                gen_exception_internal(EXCP_DEBUG);
             }
             gen_set_label(dc->condlabel);
         }
@@ -10870,11 +10897,11 @@  static inline void gen_intermediate_code_internal(ARMCPU *cpu,
         }
         gen_set_condexec(dc);
         if (dc->is_jmp == DISAS_SWI && !dc->condjmp) {
-            gen_exception(EXCP_SWI);
+            gen_exception(EXCP_SWI, syn_aa32_svc(dc->svc_imm, dc->thumb));
         } else {
             /* FIXME: Single stepping a WFI insn will not halt
                the CPU.  */
-            gen_exception(EXCP_DEBUG);
+            gen_exception_internal(EXCP_DEBUG);
         }
     } else {
         /* While branches must always occur at the end of an IT block,
@@ -10903,7 +10930,7 @@  static inline void gen_intermediate_code_internal(ARMCPU *cpu,
             gen_helper_wfi(cpu_env);
             break;
         case DISAS_SWI:
-            gen_exception(EXCP_SWI);
+            gen_exception(EXCP_SWI, syn_aa32_svc(dc->svc_imm, dc->thumb));
             break;
         }
         if (dc->condjmp) {
diff --git a/target-arm/translate.h b/target-arm/translate.h
index 889a031..4d3d363 100644
--- a/target-arm/translate.h
+++ b/target-arm/translate.h
@@ -23,6 +23,10 @@  typedef struct DisasContext {
     int vfp_enabled;
     int vec_len;
     int vec_stride;
+    /* Immediate value in AArch32 SVC insn; must be set if is_jmp == DISAS_SWI
+     * so that top level loop can generate correct syndrome information.
+     */
+    uint32_t svc_imm;
     int aarch64;
     int current_pl;
     GHashTable *cp_regs;