void mte_sync_tags(pte_t *ptep, pte_t pte);
 void mte_copy_page_tags(void *kto, const void *kfrom);
 void flush_mte_state(void);
+void mte_thread_switch(struct task_struct *next);
+long set_mte_ctrl(unsigned long arg);
+long get_mte_ctrl(void);
 
 #else
 
 static inline void flush_mte_state(void)
 {
 }
+static inline void mte_thread_switch(struct task_struct *next)
+{
+}
+static inline long set_mte_ctrl(unsigned long arg)
+{
+       return 0;
+}
+static inline long get_mte_ctrl(void)
+{
+       return 0;
+}
 
 #endif
 
 
 
 #include <linux/bitops.h>
 #include <linux/mm.h>
+#include <linux/prctl.h>
+#include <linux/sched.h>
 #include <linux/string.h>
 #include <linux/thread_info.h>
 
        return ret;
 }
 
+static void update_sctlr_el1_tcf0(u64 tcf0)
+{
+       /* ISB required for the kernel uaccess routines */
+       sysreg_clear_set(sctlr_el1, SCTLR_EL1_TCF0_MASK, tcf0);
+       isb();
+}
+
+static void set_sctlr_el1_tcf0(u64 tcf0)
+{
+       /*
+        * mte_thread_switch() checks current->thread.sctlr_tcf0 as an
+        * optimisation. Disable preemption so that it does not see
+        * the variable update before the SCTLR_EL1.TCF0 one.
+        */
+       preempt_disable();
+       current->thread.sctlr_tcf0 = tcf0;
+       update_sctlr_el1_tcf0(tcf0);
+       preempt_enable();
+}
+
 void flush_mte_state(void)
 {
        if (!system_supports_mte())
        dsb(ish);
        write_sysreg_s(0, SYS_TFSRE0_EL1);
        clear_thread_flag(TIF_MTE_ASYNC_FAULT);
+       /* disable tag checking */
+       set_sctlr_el1_tcf0(SCTLR_EL1_TCF0_NONE);
+}
+
+void mte_thread_switch(struct task_struct *next)
+{
+       if (!system_supports_mte())
+               return;
+
+       /* avoid expensive SCTLR_EL1 accesses if no change */
+       if (current->thread.sctlr_tcf0 != next->thread.sctlr_tcf0)
+               update_sctlr_el1_tcf0(next->thread.sctlr_tcf0);
+}
+
+long set_mte_ctrl(unsigned long arg)
+{
+       u64 tcf0;
+
+       if (!system_supports_mte())
+               return 0;
+
+       switch (arg & PR_MTE_TCF_MASK) {
+       case PR_MTE_TCF_NONE:
+               tcf0 = SCTLR_EL1_TCF0_NONE;
+               break;
+       case PR_MTE_TCF_SYNC:
+               tcf0 = SCTLR_EL1_TCF0_SYNC;
+               break;
+       case PR_MTE_TCF_ASYNC:
+               tcf0 = SCTLR_EL1_TCF0_ASYNC;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       set_sctlr_el1_tcf0(tcf0);
+
+       return 0;
+}
+
+long get_mte_ctrl(void)
+{
+       if (!system_supports_mte())
+               return 0;
+
+       switch (current->thread.sctlr_tcf0) {
+       case SCTLR_EL1_TCF0_NONE:
+               return PR_MTE_TCF_NONE;
+       case SCTLR_EL1_TCF0_SYNC:
+               return PR_MTE_TCF_SYNC;
+       case SCTLR_EL1_TCF0_ASYNC:
+               return PR_MTE_TCF_ASYNC;
+       }
+
+       return 0;
 }
 
         */
        dsb(ish);
 
+       /*
+        * MTE thread switching must happen after the DSB above to ensure that
+        * any asynchronous tag check faults have been logged in the TFSR*_EL1
+        * registers.
+        */
+       mte_thread_switch(next);
+
        /* the actual thread switch */
        last = cpu_switch_to(prev, next);
 
 
 long set_tagged_addr_ctrl(unsigned long arg)
 {
+       unsigned long valid_mask = PR_TAGGED_ADDR_ENABLE;
+
        if (is_compat_task())
                return -EINVAL;
-       if (arg & ~PR_TAGGED_ADDR_ENABLE)
+
+       if (system_supports_mte())
+               valid_mask |= PR_MTE_TCF_MASK;
+
+       if (arg & ~valid_mask)
                return -EINVAL;
 
        /*
        if (arg & PR_TAGGED_ADDR_ENABLE && tagged_addr_disabled)
                return -EINVAL;
 
+       if (set_mte_ctrl(arg) != 0)
+               return -EINVAL;
+
        update_thread_flag(TIF_TAGGED_ADDR, arg & PR_TAGGED_ADDR_ENABLE);
 
        return 0;
 
 long get_tagged_addr_ctrl(void)
 {
+       long ret = 0;
+
        if (is_compat_task())
                return -EINVAL;
 
        if (test_thread_flag(TIF_TAGGED_ADDR))
-               return PR_TAGGED_ADDR_ENABLE;
+               ret = PR_TAGGED_ADDR_ENABLE;
 
-       return 0;
+       ret |= get_mte_ctrl();
+
+       return ret;
 }
 
 /*
 
 #define PR_SET_TAGGED_ADDR_CTRL                55
 #define PR_GET_TAGGED_ADDR_CTRL                56
 # define PR_TAGGED_ADDR_ENABLE         (1UL << 0)
+/* MTE tag check fault modes */
+# define PR_MTE_TCF_SHIFT              1
+# define PR_MTE_TCF_NONE               (0UL << PR_MTE_TCF_SHIFT)
+# define PR_MTE_TCF_SYNC               (1UL << PR_MTE_TCF_SHIFT)
+# define PR_MTE_TCF_ASYNC              (2UL << PR_MTE_TCF_SHIFT)
+# define PR_MTE_TCF_MASK               (3UL << PR_MTE_TCF_SHIFT)
 
 /* Control reclaim behavior when allocating memory */
 #define PR_SET_IO_FLUSHER              57