diff mbox

[v1,13/16] target-arm: A64: Emulate the HVC insn

Message ID 1401434911-26992-14-git-send-email-edgar.iglesias@gmail.com
State New
Headers show

Commit Message

Edgar E. Iglesias May 30, 2014, 7:28 a.m. UTC
From: "Edgar E. Iglesias" <edgar.iglesias@xilinx.com>

Signed-off-by: Edgar E. Iglesias <edgar.iglesias@xilinx.com>
---
 target-arm/cpu.h           |  7 ++-----
 target-arm/helper-a64.c    |  1 +
 target-arm/helper.c        | 39 +++++++++++++++++++++++++++++++++++++++
 target-arm/helper.h        |  1 +
 target-arm/internals.h     |  6 ++++++
 target-arm/op_helper.c     | 21 +++++++++++++++++++++
 target-arm/translate-a64.c | 21 ++++++++++++++++-----
 7 files changed, 86 insertions(+), 10 deletions(-)

Comments

Alex Bennée June 3, 2014, 10:41 a.m. UTC | #1
Edgar E. Iglesias writes:

> From: "Edgar E. Iglesias" <edgar.iglesias@xilinx.com>
>
> Signed-off-by: Edgar E. Iglesias <edgar.iglesias@xilinx.com>
> ---
>  target-arm/cpu.h           |  7 ++-----
>  target-arm/helper-a64.c    |  1 +
>  target-arm/helper.c        | 39 +++++++++++++++++++++++++++++++++++++++
>  target-arm/helper.h        |  1 +
>  target-arm/internals.h     |  6 ++++++
>  target-arm/op_helper.c     | 21 +++++++++++++++++++++
>  target-arm/translate-a64.c | 21 ++++++++++++++++-----
>  7 files changed, 86 insertions(+), 10 deletions(-)
>
> diff --git a/target-arm/cpu.h b/target-arm/cpu.h
> index 66c58bd..1a26ed4 100644
> --- a/target-arm/cpu.h
> +++ b/target-arm/cpu.h
> @@ -51,6 +51,7 @@
>  #define EXCP_EXCEPTION_EXIT  8   /* Return from v7M exception.  */
>  #define EXCP_KERNEL_TRAP     9   /* Jumped to kernel code page.  */
>  #define EXCP_STREX          10
> +#define EXCP_HVC            11   /* HyperVisor Call */
>  
>  #define ARMV7M_EXCP_RESET   1
>  #define ARMV7M_EXCP_NMI     2
> @@ -715,11 +716,7 @@ static inline bool arm_el_is_aa64(CPUARMState *env, int el)
>  }
>  
>  void arm_cpu_list(FILE *f, fprintf_function cpu_fprintf);
> -static inline unsigned int arm_excp_target_el(CPUState *cs,
> -                                              unsigned int excp_idx)
> -{
> -    return 1;
> -}
> +unsigned int arm_excp_target_el(CPUState *cs, unsigned int excp_idx);

If the earlier commit had added this in the final place to start with
the functional diff would be clearer (although it's easy to eyeball in
this case).

>  
>  /* Interface between CPU and Interrupt controller.  */
>  void armv7m_nvic_set_pending(void *opaque, int irq);
> diff --git a/target-arm/helper-a64.c b/target-arm/helper-a64.c
> index c91005f..974fa66 100644
> --- a/target-arm/helper-a64.c
> +++ b/target-arm/helper-a64.c
> @@ -475,6 +475,7 @@ void aarch64_cpu_do_interrupt(CPUState *cs)
>      case EXCP_BKPT:
>      case EXCP_UDEF:
>      case EXCP_SWI:
> +    case EXCP_HVC:
>          env->cp15.esr_el[new_el] = env->exception.syndrome;
>          break;
>      case EXCP_IRQ:
> diff --git a/target-arm/helper.c b/target-arm/helper.c
> index b760748..5b2070c 100644
> --- a/target-arm/helper.c
> +++ b/target-arm/helper.c
> @@ -3208,6 +3208,11 @@ uint32_t HELPER(get_r13_banked)(CPUARMState *env, uint32_t mode)
>      return 0;
>  }
>  
> +unsigned int arm_excp_target_el(CPUState *cs, unsigned int excp_idx)
> +{
> +    return 1;
> +}
> +
>  #else
>  
>  /* Map CPU modes onto saved register banks.  */
> @@ -3263,6 +3268,40 @@ void switch_mode(CPUARMState *env, int mode)
>      env->spsr = env->banked_spsr[i];
>  }
>  
> +/*
> + * Determine the target EL for a given exception type.
> + */
> +unsigned int arm_excp_target_el(CPUState *cs, unsigned int excp_idx)
> +{
> +    CPUARMState *env = cs->env_ptr;
> +    unsigned int cur_el = arm_current_pl(env);
> +    unsigned int target_el = 1;
> +    bool route_to_el2 = false;
> +    /* FIXME: Use actual secure state.  */
> +    bool secure = false;

Should this be here?

<snip>
>  static inline void arm_log_exception(int idx)
> @@ -204,6 +205,11 @@ static inline uint32_t syn_aa64_svc(uint32_t imm16)
>      return (EC_AA64_SVC << ARM_EL_EC_SHIFT) | ARM_EL_IL | (imm16 & 0xffff);
>  }
>  
> +static inline uint32_t syn_aa64_hvc(uint32_t imm16)
> +{
> +    return (EC_AA64_HVC << ARM_EL_EC_SHIFT) | ARM_EL_IL | (imm16 & 0xffff);
> +}

The mask seems superfluous (as it is for arm_log_exception)

> +
>  static inline uint32_t syn_aa32_svc(uint32_t imm16, bool is_thumb)
>  {
>      return (EC_AA32_SVC << ARM_EL_EC_SHIFT) | (imm16 & 0xffff)
> diff --git a/target-arm/op_helper.c b/target-arm/op_helper.c
> index 581dc09..6bf34b0 100644
> --- a/target-arm/op_helper.c
> +++ b/target-arm/op_helper.c
> @@ -384,6 +384,27 @@ void HELPER(msr_i_pstate)(CPUARMState *env, uint32_t op, uint32_t imm)
>      }
>  }
>  
> +void HELPER(hvc)(CPUARMState *env, uint32_t syndrome)
> +{
> +    bool udef;
> +
> +    /* We've already checked that EL2 exists at translation time.
> +     * EL3.HCE has priority over EL2.HCD.
> +     */
> +    if (arm_feature(env, ARM_FEATURE_EL3)) {
> +        udef = !(env->cp15.scr_el3 & SCR_HCE);
> +    } else {
> +        udef = env->cp15.hcr_el2 & HCR_HCD;
> +    }
> +
> +    if (udef) {
> +        env->exception.syndrome = syn_uncategorized();
> +        raise_exception(env, EXCP_UDEF);
> +    }
> +    env->exception.syndrome = syndrome;
> +    raise_exception(env, EXCP_HVC);
> +}
> +
>  void HELPER(exception_return)(CPUARMState *env)
>  {
>      int cur_el = arm_current_pl(env);
> diff --git a/target-arm/translate-a64.c b/target-arm/translate-a64.c
> index 9f964df..3981ee1 100644
> --- a/target-arm/translate-a64.c
> +++ b/target-arm/translate-a64.c
> @@ -1433,17 +1433,28 @@ 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);
> +    TCGv_i32 tmp;
>  
>      switch (opc) {
>      case 0:
> -        /* SVC, HVC, SMC; since we don't support the Virtualization
> -         * or TrustZone extensions these all UNDEF except SVC.
> -         */
> -        if (op2_ll != 1) {
> +        switch (op2_ll) {
> +        case 1:
> +            gen_exception_insn(s, 0, EXCP_SWI, syn_aa64_svc(imm16));
> +            break;
> +        case 2:
> +            if (!arm_dc_feature(s, ARM_FEATURE_EL2) || s->current_pl == 0) {
> +                unallocated_encoding(s);
> +                break;
> +            }
> +            tmp = tcg_const_i32(syn_aa64_hvc(imm16));
> +            gen_a64_set_pc_im(s->pc);
> +            gen_helper_hvc(cpu_env, tmp);
> +            tcg_temp_free_i32(tmp);
> +            break;
> +        default:
>              unallocated_encoding(s);
>              break;
>          }
> -        gen_exception_insn(s, 0, EXCP_SWI, syn_aa64_svc(imm16));
>          break;
>      case 1:
>          if (op2_ll != 0) {
Edgar E. Iglesias June 4, 2014, 7:01 a.m. UTC | #2
On Tue, Jun 03, 2014 at 11:41:25AM +0100, Alex Bennée wrote:
> 
> Edgar E. Iglesias writes:
> 
> > From: "Edgar E. Iglesias" <edgar.iglesias@xilinx.com>
> >
> > Signed-off-by: Edgar E. Iglesias <edgar.iglesias@xilinx.com>
> > ---
> >  target-arm/cpu.h           |  7 ++-----
> >  target-arm/helper-a64.c    |  1 +
> >  target-arm/helper.c        | 39 +++++++++++++++++++++++++++++++++++++++
> >  target-arm/helper.h        |  1 +
> >  target-arm/internals.h     |  6 ++++++
> >  target-arm/op_helper.c     | 21 +++++++++++++++++++++
> >  target-arm/translate-a64.c | 21 ++++++++++++++++-----
> >  7 files changed, 86 insertions(+), 10 deletions(-)
> >
> > diff --git a/target-arm/cpu.h b/target-arm/cpu.h
> > index 66c58bd..1a26ed4 100644
> > --- a/target-arm/cpu.h
> > +++ b/target-arm/cpu.h
> > @@ -51,6 +51,7 @@
> >  #define EXCP_EXCEPTION_EXIT  8   /* Return from v7M exception.  */
> >  #define EXCP_KERNEL_TRAP     9   /* Jumped to kernel code page.  */
> >  #define EXCP_STREX          10
> > +#define EXCP_HVC            11   /* HyperVisor Call */
> >  
> >  #define ARMV7M_EXCP_RESET   1
> >  #define ARMV7M_EXCP_NMI     2
> > @@ -715,11 +716,7 @@ static inline bool arm_el_is_aa64(CPUARMState *env, int el)
> >  }
> >  
> >  void arm_cpu_list(FILE *f, fprintf_function cpu_fprintf);
> > -static inline unsigned int arm_excp_target_el(CPUState *cs,
> > -                                              unsigned int excp_idx)
> > -{
> > -    return 1;
> > -}
> > +unsigned int arm_excp_target_el(CPUState *cs, unsigned int excp_idx);
> 
> If the earlier commit had added this in the final place to start with
> the functional diff would be clearer (although it's easy to eyeball in
> this case).

Right, I can change that for v2.


> 
> >  
> >  /* Interface between CPU and Interrupt controller.  */
> >  void armv7m_nvic_set_pending(void *opaque, int irq);
> > diff --git a/target-arm/helper-a64.c b/target-arm/helper-a64.c
> > index c91005f..974fa66 100644
> > --- a/target-arm/helper-a64.c
> > +++ b/target-arm/helper-a64.c
> > @@ -475,6 +475,7 @@ void aarch64_cpu_do_interrupt(CPUState *cs)
> >      case EXCP_BKPT:
> >      case EXCP_UDEF:
> >      case EXCP_SWI:
> > +    case EXCP_HVC:
> >          env->cp15.esr_el[new_el] = env->exception.syndrome;
> >          break;
> >      case EXCP_IRQ:
> > diff --git a/target-arm/helper.c b/target-arm/helper.c
> > index b760748..5b2070c 100644
> > --- a/target-arm/helper.c
> > +++ b/target-arm/helper.c
> > @@ -3208,6 +3208,11 @@ uint32_t HELPER(get_r13_banked)(CPUARMState *env, uint32_t mode)
> >      return 0;
> >  }
> >  
> > +unsigned int arm_excp_target_el(CPUState *cs, unsigned int excp_idx)
> > +{
> > +    return 1;
> > +}
> > +
> >  #else
> >  
> >  /* Map CPU modes onto saved register banks.  */
> > @@ -3263,6 +3268,40 @@ void switch_mode(CPUARMState *env, int mode)
> >      env->spsr = env->banked_spsr[i];
> >  }
> >  
> > +/*
> > + * Determine the target EL for a given exception type.
> > + */
> > +unsigned int arm_excp_target_el(CPUState *cs, unsigned int excp_idx)
> > +{
> > +    CPUARMState *env = cs->env_ptr;
> > +    unsigned int cur_el = arm_current_pl(env);
> > +    unsigned int target_el = 1;
> > +    bool route_to_el2 = false;
> > +    /* FIXME: Use actual secure state.  */
> > +    bool secure = false;
> 
> Should this be here?

I've put it there to make it easier for the TZ patches to identify the
places they need to update. + it allows me to code the conditions for
the exception routing (wrt S/NS).


> 
> <snip>
> >  static inline void arm_log_exception(int idx)
> > @@ -204,6 +205,11 @@ static inline uint32_t syn_aa64_svc(uint32_t imm16)
> >      return (EC_AA64_SVC << ARM_EL_EC_SHIFT) | ARM_EL_IL | (imm16 & 0xffff);
> >  }
> >  
> > +static inline uint32_t syn_aa64_hvc(uint32_t imm16)
> > +{
> > +    return (EC_AA64_HVC << ARM_EL_EC_SHIFT) | ARM_EL_IL | (imm16 & 0xffff);
> > +}
> 
> The mask seems superfluous (as it is for arm_log_exception)

Sorry, can you clarify what you mean here? Are you refering to the imm16?


> 
> > +
> >  static inline uint32_t syn_aa32_svc(uint32_t imm16, bool is_thumb)
> >  {
> >      return (EC_AA32_SVC << ARM_EL_EC_SHIFT) | (imm16 & 0xffff)
> > diff --git a/target-arm/op_helper.c b/target-arm/op_helper.c
> > index 581dc09..6bf34b0 100644
> > --- a/target-arm/op_helper.c
> > +++ b/target-arm/op_helper.c
> > @@ -384,6 +384,27 @@ void HELPER(msr_i_pstate)(CPUARMState *env, uint32_t op, uint32_t imm)
> >      }
> >  }
> >  
> > +void HELPER(hvc)(CPUARMState *env, uint32_t syndrome)
> > +{
> > +    bool udef;
> > +
> > +    /* We've already checked that EL2 exists at translation time.
> > +     * EL3.HCE has priority over EL2.HCD.
> > +     */
> > +    if (arm_feature(env, ARM_FEATURE_EL3)) {
> > +        udef = !(env->cp15.scr_el3 & SCR_HCE);
> > +    } else {
> > +        udef = env->cp15.hcr_el2 & HCR_HCD;
> > +    }
> > +
> > +    if (udef) {
> > +        env->exception.syndrome = syn_uncategorized();
> > +        raise_exception(env, EXCP_UDEF);
> > +    }
> > +    env->exception.syndrome = syndrome;
> > +    raise_exception(env, EXCP_HVC);
> > +}
> > +
> >  void HELPER(exception_return)(CPUARMState *env)
> >  {
> >      int cur_el = arm_current_pl(env);
> > diff --git a/target-arm/translate-a64.c b/target-arm/translate-a64.c
> > index 9f964df..3981ee1 100644
> > --- a/target-arm/translate-a64.c
> > +++ b/target-arm/translate-a64.c
> > @@ -1433,17 +1433,28 @@ 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);
> > +    TCGv_i32 tmp;
> >  
> >      switch (opc) {
> >      case 0:
> > -        /* SVC, HVC, SMC; since we don't support the Virtualization
> > -         * or TrustZone extensions these all UNDEF except SVC.
> > -         */
> > -        if (op2_ll != 1) {
> > +        switch (op2_ll) {
> > +        case 1:
> > +            gen_exception_insn(s, 0, EXCP_SWI, syn_aa64_svc(imm16));
> > +            break;
> > +        case 2:
> > +            if (!arm_dc_feature(s, ARM_FEATURE_EL2) || s->current_pl == 0) {
> > +                unallocated_encoding(s);
> > +                break;
> > +            }
> > +            tmp = tcg_const_i32(syn_aa64_hvc(imm16));
> > +            gen_a64_set_pc_im(s->pc);
> > +            gen_helper_hvc(cpu_env, tmp);
> > +            tcg_temp_free_i32(tmp);
> > +            break;
> > +        default:
> >              unallocated_encoding(s);
> >              break;
> >          }
> > -        gen_exception_insn(s, 0, EXCP_SWI, syn_aa64_svc(imm16));
> >          break;
> >      case 1:
> >          if (op2_ll != 0) {
> 
> -- 
> Alex Bennée
Edgar E. Iglesias June 4, 2014, 3:03 p.m. UTC | #3
On Wed, Jun 04, 2014 at 08:26:51AM +0100, Alex Benn�e wrote:
> 
> Edgar E. Iglesias writes:
> 
> > On Tue, Jun 03, 2014 at 11:41:25AM +0100, Alex Benn?e wrote:
> >> 
> >> Edgar E. Iglesias writes:
> >> >  static inline void arm_log_exception(int idx)
> >> > @@ -204,6 +205,11 @@ static inline uint32_t syn_aa64_svc(uint32_t imm16)
> >> >      return (EC_AA64_SVC << ARM_EL_EC_SHIFT) | ARM_EL_IL | (imm16 & 0xffff);
> >> >  }
> >> >  
> >> > +static inline uint32_t syn_aa64_hvc(uint32_t imm16)
> >> > +{
> >> > +    return (EC_AA64_HVC << ARM_EL_EC_SHIFT) | ARM_EL_IL | (imm16 & 0xffff);
> >> > +}
> >> 
> >> The mask seems superfluous (as it is for arm_log_exception)
> >
> > Sorry, can you clarify what you mean here? Are you refering to the imm16?
> 
> Yes the imm16. It's the result of an extract32(..,..,16) so I can't see
> how it wouldn't already be correctly masked.

Right, so my first version here had a uint16_t imm16, but I changed
it to keep it consistent with the other functions. I'm happy to
change things to match their use with additional patches. I'll do
something for v2.
diff mbox

Patch

diff --git a/target-arm/cpu.h b/target-arm/cpu.h
index 66c58bd..1a26ed4 100644
--- a/target-arm/cpu.h
+++ b/target-arm/cpu.h
@@ -51,6 +51,7 @@ 
 #define EXCP_EXCEPTION_EXIT  8   /* Return from v7M exception.  */
 #define EXCP_KERNEL_TRAP     9   /* Jumped to kernel code page.  */
 #define EXCP_STREX          10
+#define EXCP_HVC            11   /* HyperVisor Call */
 
 #define ARMV7M_EXCP_RESET   1
 #define ARMV7M_EXCP_NMI     2
@@ -715,11 +716,7 @@  static inline bool arm_el_is_aa64(CPUARMState *env, int el)
 }
 
 void arm_cpu_list(FILE *f, fprintf_function cpu_fprintf);
-static inline unsigned int arm_excp_target_el(CPUState *cs,
-                                              unsigned int excp_idx)
-{
-    return 1;
-}
+unsigned int arm_excp_target_el(CPUState *cs, unsigned int excp_idx);
 
 /* Interface between CPU and Interrupt controller.  */
 void armv7m_nvic_set_pending(void *opaque, int irq);
diff --git a/target-arm/helper-a64.c b/target-arm/helper-a64.c
index c91005f..974fa66 100644
--- a/target-arm/helper-a64.c
+++ b/target-arm/helper-a64.c
@@ -475,6 +475,7 @@  void aarch64_cpu_do_interrupt(CPUState *cs)
     case EXCP_BKPT:
     case EXCP_UDEF:
     case EXCP_SWI:
+    case EXCP_HVC:
         env->cp15.esr_el[new_el] = env->exception.syndrome;
         break;
     case EXCP_IRQ:
diff --git a/target-arm/helper.c b/target-arm/helper.c
index b760748..5b2070c 100644
--- a/target-arm/helper.c
+++ b/target-arm/helper.c
@@ -3208,6 +3208,11 @@  uint32_t HELPER(get_r13_banked)(CPUARMState *env, uint32_t mode)
     return 0;
 }
 
+unsigned int arm_excp_target_el(CPUState *cs, unsigned int excp_idx)
+{
+    return 1;
+}
+
 #else
 
 /* Map CPU modes onto saved register banks.  */
@@ -3263,6 +3268,40 @@  void switch_mode(CPUARMState *env, int mode)
     env->spsr = env->banked_spsr[i];
 }
 
+/*
+ * Determine the target EL for a given exception type.
+ */
+unsigned int arm_excp_target_el(CPUState *cs, unsigned int excp_idx)
+{
+    CPUARMState *env = cs->env_ptr;
+    unsigned int cur_el = arm_current_pl(env);
+    unsigned int target_el = 1;
+    bool route_to_el2 = false;
+    /* FIXME: Use actual secure state.  */
+    bool secure = false;
+
+    if (!env->aarch64) {
+        /* TODO: Add EL2 and 3 exception handling for AArch32.  */
+        return 1;
+    }
+
+    if (!secure
+        && arm_feature(env, ARM_FEATURE_EL2)
+        && cur_el < 2
+        && (env->cp15.hcr_el2 & HCR_TGE)) {
+        route_to_el2 = true;
+    }
+
+    target_el = MAX(cur_el, route_to_el2 ? 2 : 1);
+
+    switch (excp_idx) {
+    case EXCP_HVC:
+        target_el = MAX(target_el, 2);
+        break;
+    }
+    return target_el;
+}
+
 static void v7m_push(CPUARMState *env, uint32_t val)
 {
     CPUState *cs = CPU(arm_env_get_cpu(env));
diff --git a/target-arm/helper.h b/target-arm/helper.h
index b63fd0f..fb711be 100644
--- a/target-arm/helper.h
+++ b/target-arm/helper.h
@@ -50,6 +50,7 @@  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_1(wfe, void, env)
+DEF_HELPER_2(hvc, void, env, i32)
 
 DEF_HELPER_3(cpsr_write, void, env, i32, i32)
 DEF_HELPER_1(cpsr_read, i32, env)
diff --git a/target-arm/internals.h b/target-arm/internals.h
index 08fa697..b08381c 100644
--- a/target-arm/internals.h
+++ b/target-arm/internals.h
@@ -53,6 +53,7 @@  static const char * const excnames[] = {
     [EXCP_EXCEPTION_EXIT] = "QEMU v7M exception exit",
     [EXCP_KERNEL_TRAP] = "QEMU intercept of kernel commpage",
     [EXCP_STREX] = "QEMU intercept of STREX",
+    [EXCP_HVC] = "Hypervisor Call",
 };
 
 static inline void arm_log_exception(int idx)
@@ -204,6 +205,11 @@  static inline uint32_t syn_aa64_svc(uint32_t imm16)
     return (EC_AA64_SVC << ARM_EL_EC_SHIFT) | ARM_EL_IL | (imm16 & 0xffff);
 }
 
+static inline uint32_t syn_aa64_hvc(uint32_t imm16)
+{
+    return (EC_AA64_HVC << ARM_EL_EC_SHIFT) | ARM_EL_IL | (imm16 & 0xffff);
+}
+
 static inline uint32_t syn_aa32_svc(uint32_t imm16, bool is_thumb)
 {
     return (EC_AA32_SVC << ARM_EL_EC_SHIFT) | (imm16 & 0xffff)
diff --git a/target-arm/op_helper.c b/target-arm/op_helper.c
index 581dc09..6bf34b0 100644
--- a/target-arm/op_helper.c
+++ b/target-arm/op_helper.c
@@ -384,6 +384,27 @@  void HELPER(msr_i_pstate)(CPUARMState *env, uint32_t op, uint32_t imm)
     }
 }
 
+void HELPER(hvc)(CPUARMState *env, uint32_t syndrome)
+{
+    bool udef;
+
+    /* We've already checked that EL2 exists at translation time.
+     * EL3.HCE has priority over EL2.HCD.
+     */
+    if (arm_feature(env, ARM_FEATURE_EL3)) {
+        udef = !(env->cp15.scr_el3 & SCR_HCE);
+    } else {
+        udef = env->cp15.hcr_el2 & HCR_HCD;
+    }
+
+    if (udef) {
+        env->exception.syndrome = syn_uncategorized();
+        raise_exception(env, EXCP_UDEF);
+    }
+    env->exception.syndrome = syndrome;
+    raise_exception(env, EXCP_HVC);
+}
+
 void HELPER(exception_return)(CPUARMState *env)
 {
     int cur_el = arm_current_pl(env);
diff --git a/target-arm/translate-a64.c b/target-arm/translate-a64.c
index 9f964df..3981ee1 100644
--- a/target-arm/translate-a64.c
+++ b/target-arm/translate-a64.c
@@ -1433,17 +1433,28 @@  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);
+    TCGv_i32 tmp;
 
     switch (opc) {
     case 0:
-        /* SVC, HVC, SMC; since we don't support the Virtualization
-         * or TrustZone extensions these all UNDEF except SVC.
-         */
-        if (op2_ll != 1) {
+        switch (op2_ll) {
+        case 1:
+            gen_exception_insn(s, 0, EXCP_SWI, syn_aa64_svc(imm16));
+            break;
+        case 2:
+            if (!arm_dc_feature(s, ARM_FEATURE_EL2) || s->current_pl == 0) {
+                unallocated_encoding(s);
+                break;
+            }
+            tmp = tcg_const_i32(syn_aa64_hvc(imm16));
+            gen_a64_set_pc_im(s->pc);
+            gen_helper_hvc(cpu_env, tmp);
+            tcg_temp_free_i32(tmp);
+            break;
+        default:
             unallocated_encoding(s);
             break;
         }
-        gen_exception_insn(s, 0, EXCP_SWI, syn_aa64_svc(imm16));
         break;
     case 1:
         if (op2_ll != 0) {