diff --git a/target-arm/cpu-qom.h b/target-arm/cpu-qom.h
index eae0a7b9c908..104cc67e82d2 100644
--- a/target-arm/cpu-qom.h
+++ b/target-arm/cpu-qom.h
@@ -192,6 +192,9 @@ extern const struct VMStateDescription vmstate_arm_cpu;
 void register_cp_regs_for_features(ARMCPU *cpu);
 void init_cpreg_list(ARMCPU *cpu);
 
+bool arm_cpu_do_hvc(CPUState *cs);
+bool arm_cpu_do_smc(CPUState *cs);
+
 void arm_cpu_do_interrupt(CPUState *cpu);
 void arm_v7m_cpu_do_interrupt(CPUState *cpu);
 
diff --git a/target-arm/cpu.h b/target-arm/cpu.h
index 51bedc826299..d235929f4c12 100644
--- a/target-arm/cpu.h
+++ b/target-arm/cpu.h
@@ -51,6 +51,8 @@
 #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
+#define EXCP_SMC            12
 
 #define ARMV7M_EXCP_RESET   1
 #define ARMV7M_EXCP_NMI     2
diff --git a/target-arm/helper-a64.c b/target-arm/helper-a64.c
index 89b913ee9396..1f8072ab141b 100644
--- a/target-arm/helper-a64.c
+++ b/target-arm/helper-a64.c
@@ -485,6 +485,22 @@ void aarch64_cpu_do_interrupt(CPUState *cs)
     case EXCP_FIQ:
         addr += 0x100;
         break;
+    case EXCP_HVC:
+        if (arm_cpu_do_hvc(cs)) {
+            return;
+        }
+        /* Treat as unallocated encoding */
+        qemu_log_mask(LOG_GUEST_ERROR, "HVC not implemented on this CPU\n");
+        env->exception.syndrome = syn_uncategorized();
+        break;
+    case EXCP_SMC:
+        if (arm_cpu_do_smc(cs)) {
+            return;
+        }
+        /* Treat as unallocated encoding */
+        qemu_log_mask(LOG_GUEST_ERROR, "SMC not implemented on this CPU\n");
+        env->exception.syndrome = syn_uncategorized();
+        break;
     default:
         cpu_abort(cs, "Unhandled exception 0x%x\n", cs->exception_index);
     }
diff --git a/target-arm/helper.c b/target-arm/helper.c
index 2b95f33872cb..51a01a815b7b 100644
--- a/target-arm/helper.c
+++ b/target-arm/helper.c
@@ -3497,6 +3497,16 @@ void arm_v7m_cpu_do_interrupt(CPUState *cs)
     env->thumb = addr & 1;
 }
 
+bool arm_cpu_do_hvc(CPUState *cs)
+{
+    return false;
+}
+
+bool arm_cpu_do_smc(CPUState *cs)
+{
+    return false;
+}
+
 /* Handle a CPU exception.  */
 void arm_cpu_do_interrupt(CPUState *cs)
 {
@@ -3599,6 +3609,28 @@ void arm_cpu_do_interrupt(CPUState *cs)
         mask = CPSR_A | CPSR_I | CPSR_F;
         offset = 4;
         break;
+    case EXCP_HVC:
+        if (arm_cpu_do_hvc(cs)) {
+            return;
+        }
+        qemu_log_mask(LOG_GUEST_ERROR, "HVC not implemented on this CPU\n");
+        goto hvc_unallocated;
+    case EXCP_SMC:
+        if (arm_cpu_do_smc(cs)) {
+            return;
+        }
+        qemu_log_mask(LOG_GUEST_ERROR, "SMC not implemented on this CPU\n");
+    hvc_unallocated:
+        /* Treat as unallocated encoding */
+        new_mode = ARM_CPU_MODE_UND;
+        addr = 0x04;
+        mask = CPSR_I;
+        if (env->thumb) {
+            offset = 2;
+        } else {
+            offset = 4;
+        }
+        break;
     default:
         cpu_abort(cs, "Unhandled exception 0x%x\n", cs->exception_index);
         return; /* Never happens.  Keep compiler happy.  */
diff --git a/target-arm/internals.h b/target-arm/internals.h
index 53c2e3cf3e7e..caab98e6b508 100644
--- a/target-arm/internals.h
+++ b/target-arm/internals.h
@@ -210,6 +210,26 @@ static inline uint32_t syn_aa32_svc(uint32_t imm16, bool is_thumb)
         | (is_thumb ? 0 : ARM_EL_IL);
 }
 
+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_hvc(uint32_t imm16)
+{
+    return (EC_AA32_HVC << ARM_EL_EC_SHIFT) | ARM_EL_IL | (imm16 & 0xffff);
+}
+
+static inline uint32_t syn_aa64_smc(uint32_t imm16)
+{
+    return (EC_AA64_SMC << ARM_EL_EC_SHIFT) | ARM_EL_IL | (imm16 & 0xffff);
+}
+
+static inline uint32_t syn_aa32_smc(void)
+{
+    return (EC_AA32_SMC << ARM_EL_EC_SHIFT) | ARM_EL_IL;
+}
+
 static inline uint32_t syn_aa64_bkpt(uint32_t imm16)
 {
     return (EC_AA64_BKPT << ARM_EL_EC_SHIFT) | ARM_EL_IL | (imm16 & 0xffff);
diff --git a/target-arm/translate-a64.c b/target-arm/translate-a64.c
index 8e66b6c97282..65e35e3aaec0 100644
--- a/target-arm/translate-a64.c
+++ b/target-arm/translate-a64.c
@@ -1473,20 +1473,28 @@ static void disas_exc(DisasContext *s, uint32_t insn)
 
     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) {
-            unallocated_encoding(s);
-            break;
-        }
         /* For SVC, HVC and SMC we advance the single-step state
          * machine before taking the exception. This is architecturally
          * mandated, to ensure that single-stepping a system call
          * instruction works properly.
          */
-        gen_ss_advance(s);
-        gen_exception_insn(s, 0, EXCP_SWI, syn_aa64_svc(imm16));
+        switch (op2_ll) {
+        case 1:
+            gen_ss_advance(s);
+            gen_exception_insn(s, 0, EXCP_SWI, syn_aa64_svc(imm16));
+            break;
+        case 2:
+            gen_ss_advance(s);
+            gen_exception_insn(s, 0, EXCP_HVC, syn_aa64_hvc(imm16));
+            break;
+        case 3:
+            gen_ss_advance(s);
+            gen_exception_insn(s, 0, EXCP_SMC, syn_aa64_smc(imm16));
+            break;
+        default:
+            unallocated_encoding(s);
+            break;
+        }
         break;
     case 1:
         if (op2_ll != 0) {
diff --git a/target-arm/translate.c b/target-arm/translate.c
index 2c0b1deaea81..a4545ed2bc40 100644
--- a/target-arm/translate.c
+++ b/target-arm/translate.c
@@ -7871,9 +7871,14 @@ static void disas_arm_insn(CPUARMState * env, DisasContext *s)
         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 */
+            /* HVC and SMC instructions */
+            if (op1 == 2) {
+                gen_exception_insn(s, 0, EXCP_HVC, syn_aa32_hvc(imm16));
+                break;
+            } else if (op1 == 3) {
+                gen_exception_insn(s, 0, EXCP_SMC, syn_aa32_smc());
+                break;
+            }
             if (op1 != 1) {
                 goto illegal_op;
             }
@@ -9709,10 +9714,15 @@ static int disas_thumb2_insn(CPUARMState *env, DisasContext *s, uint16_t insn_hw
                     goto illegal_op;
 
                 if (insn & (1 << 26)) {
-                    /* Secure monitor call (v6Z) */
-                    qemu_log_mask(LOG_UNIMP,
-                                  "arm: unimplemented secure monitor call\n");
-                    goto illegal_op; /* not implemented.  */
+                    if (!(insn & (1 << 20))) {
+                        /* Hypervisor call (v7) */
+                        uint32_t imm16 = extract32(insn, 16, 4) << 12;
+                        imm16 |= extract32(insn, 0, 12);
+                        gen_exception_insn(s, 0, EXCP_HVC, syn_aa32_hvc(imm16));
+                    } else {
+                        /* Secure monitor call (v6+) */
+                        gen_exception_insn(s, 0, EXCP_SMC, syn_aa32_smc());
+                    }
                 } else {
                     op = (insn >> 20) & 7;
                     switch (op) {
