diff --git a/target/arm/cpu64.c b/target/arm/cpu64.c
index 62d36f9e8d..a72dfd3d98 100644
--- a/target/arm/cpu64.c
+++ b/target/arm/cpu64.c
@@ -665,6 +665,13 @@ static void aarch64_max_initfn(Object *obj)
 
         t = cpu->isar.id_aa64pfr1;
         t = FIELD_DP64(t, ID_AA64PFR1, BT, 1);
+#ifdef CONFIG_USER_ONLY
+        /* All MTE instructions and processor states are supported for user
+         * mode. This corresponds to feature field value 1 (field value 2
+         * implies MemTag memory support).
+         */
+        t = FIELD_DP64(t, ID_AA64PFR1, MTE, 1);
+#endif
         cpu->isar.id_aa64pfr1 = t;
 
         t = cpu->isar.id_aa64mmfr1;
diff --git a/target/arm/helper.c b/target/arm/helper.c
index 38500e4f92..ed56198623 100644
--- a/target/arm/helper.c
+++ b/target/arm/helper.c
@@ -6839,6 +6839,33 @@ static const ARMCPRegInfo dcpodp_reg[] = {
 };
 #endif /*CONFIG_USER_ONLY*/
 
+static uint64_t tco_read(CPUARMState *env, const ARMCPRegInfo *ri)
+{
+    return env->pstate & PSTATE_TCO;
+}
+
+static void tco_write(CPUARMState *env, const ARMCPRegInfo *ri, uint64_t val)
+{
+    if (val & PSTATE_TCO) {
+        env->pstate |= PSTATE_TCO;
+    } else {
+        env->pstate &= ~PSTATE_TCO;
+    }
+}
+
+static const ARMCPRegInfo mte_reginfo[] = {
+    { .name = "DC_GVA", .state = ARM_CP_STATE_AA64,
+      .opc0 = 1, .opc1 = 3, .crn = 7, .crm = 4, .opc2 = 3,
+      .access = PL0_W, .type = ARM_CP_NOP },
+    { .name = "DC_GZVA", .state = ARM_CP_STATE_AA64,
+      .opc0 = 1, .opc1 = 3, .crn = 7, .crm = 4, .opc2 = 4,
+      .access = PL0_W, .type = ARM_CP_DC_ZVA },
+    { .name = "TCO", .state = ARM_CP_STATE_AA64,
+      .opc0 = 3, .opc1 = 3, .crn = 4, .crm = 2, .opc2 = 7,
+      .access = PL0_RW, .type = ARM_CP_NO_RAW,
+      .readfn = tco_read, .writefn = tco_write },
+    REGINFO_SENTINEL
+};
 #endif
 
 static CPAccessResult access_predinv(CPUARMState *env, const ARMCPRegInfo *ri,
@@ -7956,6 +7983,9 @@ void register_cp_regs_for_features(ARMCPU *cpu)
         }
     }
 #endif /*CONFIG_USER_ONLY*/
+    if (cpu_isar_feature(aa64_mte, cpu)) {
+        define_arm_cp_regs(cpu, mte_reginfo);
+    }
 #endif
 
     if (cpu_isar_feature(any_predinv, cpu)) {
diff --git a/target/arm/translate-a64.c b/target/arm/translate-a64.c
index 8fffb52203..6d86480043 100644
--- a/target/arm/translate-a64.c
+++ b/target/arm/translate-a64.c
@@ -830,6 +830,31 @@ static void gen_adc_CC(int sf, TCGv_i64 dest, TCGv_i64 t0, TCGv_i64 t1)
     }
 }
 
+/* Extract an Allocation Tag from an address */
+static void gen_extract_tag(TCGv_i64 dest, TCGv_i64 source)
+{
+    TCGv_i64 sign = tcg_temp_new_i64();
+
+    /* See ARMv8.5-A AllocationTagFromAddress pseudocode */
+    tcg_gen_extract_i64(sign, source, 55, 1);
+    tcg_gen_extract_i64(dest, source, 56, 4);
+    tcg_gen_add_i64(dest, dest, sign);
+    tcg_gen_andi_i64(dest, dest, 0xf);
+    tcg_temp_free_i64(sign);
+}
+
+/* Deposit an Allocation Tag into an address */
+static void gen_deposit_tag(TCGv_i64 dest, TCGv_i64 source, TCGv_i64 tag)
+{
+    TCGv_i64 tmp = tcg_temp_new_i64();
+
+    /* See ARMv8.5-A AddressWithAllocationTag pseudocode */
+    tcg_gen_extract_i64(tmp, source, 55, 1);
+    tcg_gen_sub_i64(tmp, tag, tmp);
+    tcg_gen_deposit_i64(dest, source, tmp, 56, 4);
+    tcg_temp_free_i64(tmp);
+}
+
 /*
  * Load/Store generators
  */
@@ -1650,6 +1675,19 @@ static void handle_msr_i(DisasContext *s, uint32_t insn,
         tcg_temp_free_i32(t1);
         break;
 
+    case 0x1c: /* TCO */
+        if (!dc_isar_feature(aa64_mte, s)) {
+            unallocated_encoding(s);
+            return;
+        }
+        if (crm & 1) {
+            set_pstate_bits(PSTATE_TCO);
+        } else {
+            clear_pstate_bits(PSTATE_TCO);
+        }
+        s->base.is_jmp = DISAS_NEXT;
+        break;
+
     case 0x1e: /* DAIFSet */
         t1 = tcg_const_i32(crm);
         gen_helper_msr_i_daifset(cpu_env, t1);
@@ -2678,6 +2716,82 @@ static void disas_ld_lit(DisasContext *s, uint32_t insn)
     tcg_temp_free_i64(clean_addr);
 }
 
+/* LDG, LDGV(EL1), ST(Z)(2)G, STGV */
+static void disas_ldst_memtag(DisasContext *s, uint32_t insn)
+{
+    int opc = extract32(insn, 22, 2);
+    int op2 = extract32(insn, 10, 2);
+    bool tuple = opc & 2;
+    bool zero_data = opc & 1;
+    bool is_store = op2 != 0;
+    bool postindex = op2 == 1;
+    bool wback = op2 != 2;
+    uint64_t offset = sextract64(insn, 12, 9) << ARM_LOG2_TAG_GRANULE;
+    int rn = extract32(insn, 5, 5);
+    int rt = extract32(insn, 0, 5);
+    TCGv_i64 tcg_addr;
+
+    if (!dc_isar_feature(aa64_mte, s) || extract32(insn, 30, 2) != 3) {
+        unallocated_encoding(s);
+        return;
+    }
+
+    if (op2 == 0 && opc != 1) {
+        unallocated_encoding(s);
+        return;
+    }
+
+    if (rn == 31) {
+        gen_check_sp_alignment(s);
+    }
+
+    tcg_addr = read_cpu_reg_sp(s, rn, 1);
+
+    if (!postindex) {
+        tcg_gen_addi_i64(tcg_addr, tcg_addr, offset);
+    }
+
+    if (is_store) {
+        /* Store Allocation Tag (STG, ST2G, STZG, STZ2G) */
+        if (zero_data) {
+            TCGv_i64 ptr = tcg_temp_new_i64();
+            TCGv_i64 zero = tcg_const_i64(0);
+
+            do_gpr_st(s, zero, tcg_addr, 3, false, 0, false, false);
+            tcg_gen_addi_i64(ptr, tcg_addr, 8);
+            do_gpr_st(s, zero, ptr, 3, false, 0, false, false);
+
+            if (tuple) {
+                tcg_gen_addi_i64(ptr, ptr, 8);
+                do_gpr_st(s, zero, ptr, 3, false, 0, false, false);
+                tcg_gen_addi_i64(ptr, ptr, 8);
+                do_gpr_st(s, zero, ptr, 3, false, 0, false, false);
+            }
+
+            tcg_temp_free_i64(zero);
+            tcg_temp_free_i64(ptr);
+        }
+        /* Write access permission should be checked here.
+         * If zero_data is true, do_gpr_st() does it but elsewise not.
+         */
+
+        if (wback) {
+            if (postindex) {
+                tcg_gen_addi_i64(tcg_addr, tcg_addr, offset);
+            }
+            tcg_gen_mov_i64(cpu_reg_sp(s, rn), tcg_addr);
+        }
+    } else {
+        /* Load Allocation Tag (LDG) */
+        TCGv_i64 tcg_tag = tcg_const_i64(0);
+
+        tcg_gen_andi_i64(tcg_addr, tcg_addr, ~(ARM_TAG_GRANULE - 1));
+        /* In principles, read access permission should be checked here. */
+        gen_deposit_tag(cpu_reg(s, rt), tcg_addr, tcg_tag);
+        tcg_temp_free_i64(tcg_tag);
+    }
+}
+
 /*
  * LDNP (Load Pair - non-temporal hint)
  * LDP (Load Pair - non vector)
@@ -2688,6 +2802,7 @@ static void disas_ld_lit(DisasContext *s, uint32_t insn)
  * LDP (Load Pair of SIMD&FP)
  * STNP (Store Pair of SIMD&FP - non-temporal hint)
  * STP (Store Pair of SIMD&FP)
+ * STGP (Store Pair - allocate tag)
  *
  *  31 30 29   27  26  25 24   23  22 21   15 14   10 9    5 4    0
  * +-----+-------+---+---+-------+---+-----------------------------+
@@ -2736,8 +2851,12 @@ static void disas_ldst_pair(DisasContext *s, uint32_t insn)
         size = 2 + extract32(opc, 1, 1);
         is_signed = extract32(opc, 0, 1);
         if (!is_load && is_signed) {
-            unallocated_encoding(s);
-            return;
+            if (size == 3 || !dc_isar_feature(aa64_mte, s)) {
+                unallocated_encoding(s);
+                return;
+            }
+
+            size = 3; /* sic! */
         }
     }
 
@@ -3728,12 +3847,15 @@ static void disas_ldst(DisasContext *s, uint32_t insn)
         disas_ldst_single_struct(s, insn);
         break;
     case 0x19: /* LDAPR/STLR (unscaled immediate) */
-        if (extract32(insn, 10, 2) != 0 ||
-            extract32(insn, 21, 1) != 0) {
+        if (extract32(insn, 10, 2) != 0) {
             unallocated_encoding(s);
             break;
         }
-        disas_ldst_ldapr_stlr(s, insn);
+        if (extract32(insn, 21, 1) != 0) {
+            disas_ldst_memtag(s, insn);
+        } else {
+            disas_ldst_ldapr_stlr(s, insn);
+        }
         break;
     default:
         unallocated_encoding(s);
@@ -3791,10 +3913,12 @@ static void disas_add_sub_imm(DisasContext *s, uint32_t insn)
     bool setflags = extract32(insn, 29, 1);
     bool sub_op = extract32(insn, 30, 1);
     bool is_64bit = extract32(insn, 31, 1);
+    bool memtag = false;
 
     TCGv_i64 tcg_rn = cpu_reg_sp(s, rn);
     TCGv_i64 tcg_rd = setflags ? cpu_reg(s, rd) : cpu_reg_sp(s, rd);
     TCGv_i64 tcg_result;
+    TCGv_i64 tcg_rtag;
 
     switch (shift) {
     case 0x0:
@@ -3802,11 +3926,23 @@ static void disas_add_sub_imm(DisasContext *s, uint32_t insn)
     case 0x1:
         imm <<= 12;
         break;
+    case 0x2:
+        memtag = true;
+
+        if (dc_isar_feature(aa64_mte, s) && is_64bit && !setflags) {
+            imm = extract32(imm, 6, 6) << ARM_LOG2_TAG_GRANULE;
+            break;
+        }
+        /* fall through */
     default:
         unallocated_encoding(s);
         return;
     }
 
+    if (memtag) {
+        tcg_rtag = tcg_const_i64(0);
+    }
+
     tcg_result = tcg_temp_new_i64();
     if (!setflags) {
         if (sub_op) {
@@ -3824,6 +3960,11 @@ static void disas_add_sub_imm(DisasContext *s, uint32_t insn)
         tcg_temp_free_i64(tcg_imm);
     }
 
+    if (memtag) {
+        gen_deposit_tag(tcg_result, tcg_result, tcg_rtag);
+        tcg_temp_free_i64(tcg_rtag);
+    }
+
     if (is_64bit) {
         tcg_gen_mov_i64(tcg_rd, tcg_result);
     } else {
@@ -5281,6 +5422,72 @@ static void handle_crc32(DisasContext *s,
     tcg_temp_free_i32(tcg_bytes);
 }
 
+/* SUBP, SUBPS (incl. CCMP) */
+static void handle_subp(DisasContext *s, bool setflags, unsigned int sf,
+                        unsigned int rm, unsigned int rn, unsigned int rd)
+{
+    TCGv_i64 tcg_addr1, tcg_addr2;
+    TCGv_i64 tcg_result;
+
+    if (!sf || !dc_isar_feature(aa64_mte, s)) {
+        unallocated_encoding(s);
+        return;
+    }
+
+    tcg_addr1 = tcg_temp_new_i64();
+    tcg_addr2 = tcg_temp_new_i64();
+    tcg_result = cpu_reg(s, rd);
+
+    tcg_gen_sextract_i64(tcg_addr1, cpu_reg_sp(s, rn), 0, 56);
+    tcg_gen_sextract_i64(tcg_addr2, cpu_reg_sp(s, rm), 0, 56);
+
+    if (setflags) {
+        gen_sub_CC(1, tcg_result, tcg_addr1, tcg_addr2);
+    } else {
+        tcg_gen_sub_i64(tcg_result, tcg_addr1, tcg_addr2);
+    }
+
+    tcg_temp_free_i64(tcg_addr2);
+    tcg_temp_free_i64(tcg_addr1);
+}
+
+static void handle_irg(DisasContext *s, unsigned int sf,
+                       unsigned int rm, unsigned int rn, unsigned int rd)
+{
+    TCGv_i64 rtag;
+
+    if (!sf || !dc_isar_feature(aa64_mte, s)) {
+        unallocated_encoding(s);
+        return;
+    }
+
+    rtag = tcg_temp_new_i64();
+    tcg_gen_movi_i64(rtag, 0);
+    gen_deposit_tag(cpu_reg_sp(s, rd), cpu_reg_sp(s, rn), rtag);
+    tcg_temp_free_i64(rtag);
+}
+
+static void handle_gmi(DisasContext *s, unsigned int sf,
+                       unsigned int rm, unsigned int rn, unsigned int rd)
+{
+    TCGv_i64 tcg_one, tcg_tag;
+
+    if (!sf || !dc_isar_feature(aa64_mte, s)) {
+        unallocated_encoding(s);
+        return;
+    }
+
+    tcg_one = tcg_const_i64(1);
+    tcg_tag = tcg_temp_new_i64();
+
+    gen_extract_tag(tcg_tag, cpu_reg_sp(s, rn));
+    tcg_gen_shl_i64(tcg_tag, tcg_one, tcg_tag);
+    tcg_gen_or_i64(cpu_reg(s, rd), cpu_reg(s, rm), tcg_tag);
+
+    tcg_temp_free_i64(tcg_tag);
+    tcg_temp_free_i64(tcg_one);
+}
+
 /* Data-processing (2 source)
  *   31   30  29 28             21 20  16 15    10 9    5 4    0
  * +----+---+---+-----------------+------+--------+------+------+
@@ -5297,17 +5504,32 @@ static void disas_data_proc_2src(DisasContext *s, uint32_t insn)
     rd = extract32(insn, 0, 5);
 
     if (extract32(insn, 29, 1)) {
-        unallocated_encoding(s);
-        return;
+        switch (opcode) {
+        case 0:
+            handle_subp(s, true, sf, rm, rn, rd);
+            break;
+        default:
+            unallocated_encoding(s);
+            return;
+        }
     }
 
     switch (opcode) {
+    case 0:
+        handle_subp(s, false, sf, rm, rn, rd);
+        break;
     case 2: /* UDIV */
         handle_div(s, false, sf, rm, rn, rd);
         break;
     case 3: /* SDIV */
         handle_div(s, true, sf, rm, rn, rd);
         break;
+    case 4:
+        handle_irg(s, sf, rm, rn, rd);
+        break;
+    case 5:
+        handle_gmi(s, sf, rm, rn, rd);
+        break;
     case 8: /* LSLV */
         handle_shift_reg(s, A64_SHIFT_TYPE_LSL, sf, rm, rn, rd);
         break;
