write_sysreg(__scs_new, sysreg);                        \
 } while (0)
 
+#define sysreg_clear_set_s(sysreg, clear, set) do {                    \
+       u64 __scs_val = read_sysreg_s(sysreg);                          \
+       u64 __scs_new = (__scs_val & ~(u64)(clear)) | (set);            \
+       if (__scs_new != __scs_val)                                     \
+               write_sysreg_s(__scs_new, sysreg);                      \
+} while (0)
+
 #endif
 
 #endif /* __ASM_SYSREG_H */
 
        preempt_enable();
 }
 
+static void update_gcr_el1_excl(u64 incl)
+{
+       u64 excl = ~incl & SYS_GCR_EL1_EXCL_MASK;
+
+       /*
+        * Note that 'incl' is an include mask (controlled by the user via
+        * prctl()) while GCR_EL1 accepts an exclude mask.
+        * No need for ISB since this only affects EL0 currently, implicit
+        * with ERET.
+        */
+       sysreg_clear_set_s(SYS_GCR_EL1, SYS_GCR_EL1_EXCL_MASK, excl);
+}
+
+static void set_gcr_el1_excl(u64 incl)
+{
+       current->thread.gcr_user_incl = incl;
+       update_gcr_el1_excl(incl);
+}
+
 void flush_mte_state(void)
 {
        if (!system_supports_mte())
        clear_thread_flag(TIF_MTE_ASYNC_FAULT);
        /* disable tag checking */
        set_sctlr_el1_tcf0(SCTLR_EL1_TCF0_NONE);
+       /* reset tag generation mask */
+       set_gcr_el1_excl(0);
 }
 
 void mte_thread_switch(struct task_struct *next)
        /* 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);
+       update_gcr_el1_excl(next->thread.gcr_user_incl);
 }
 
 long set_mte_ctrl(unsigned long arg)
        }
 
        set_sctlr_el1_tcf0(tcf0);
+       set_gcr_el1_excl((arg & PR_MTE_TAG_MASK) >> PR_MTE_TAG_SHIFT);
 
        return 0;
 }
 
 long get_mte_ctrl(void)
 {
+       unsigned long ret;
+
        if (!system_supports_mte())
                return 0;
 
+       ret = current->thread.gcr_user_incl << PR_MTE_TAG_SHIFT;
+
        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;
+               ret |= PR_MTE_TCF_SYNC;
+               break;
        case SCTLR_EL1_TCF0_ASYNC:
-               return PR_MTE_TCF_ASYNC;
+               ret |= PR_MTE_TCF_ASYNC;
+               break;
        }
 
-       return 0;
+       return ret;
 }
 
 # 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)
+/* MTE tag inclusion mask */
+# define PR_MTE_TAG_SHIFT              3
+# define PR_MTE_TAG_MASK               (0xffffUL << PR_MTE_TAG_SHIFT)
 
 /* Control reclaim behavior when allocating memory */
 #define PR_SET_IO_FLUSHER              57