powerpc: Add support for setting SPRN_TIDR
authorSukadev Bhattiprolu <sukadev@linux.vnet.ibm.com>
Wed, 8 Nov 2017 02:23:53 +0000 (18:23 -0800)
committerMichael Ellerman <mpe@ellerman.id.au>
Sat, 11 Nov 2017 22:03:09 +0000 (09:03 +1100)
We need the SPRN_TIDR to be set for use with fast thread-wakeup (core-
to-core wakeup) and also with CAPI.

Each thread in a process needs to have a unique id within the process.
But for now, we assign globally unique thread ids to all threads in
the system.

Signed-off-by: Sukadev Bhattiprolu <sukadev@linux.vnet.ibm.com>
Signed-off-by: Philippe Bergheaud <felix@linux.vnet.ibm.com>
Signed-off-by: Christophe Lombard <clombard@linux.vnet.ibm.com>
[mpe: Simplify tidr clearing on fork() and ctx switch code]
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
arch/powerpc/include/asm/processor.h
arch/powerpc/include/asm/switch_to.h
arch/powerpc/kernel/process.c

index fab7ff8..58cc212 100644 (file)
@@ -329,6 +329,7 @@ struct thread_struct {
         */
        int             dscr_inherit;
        unsigned long   ppr;    /* used to save/restore SMT priority */
+       unsigned long   tidr;
 #endif
 #ifdef CONFIG_PPC_BOOK3S_64
        unsigned long   tar;
index 17c8380..f5da32f 100644 (file)
@@ -91,4 +91,7 @@ static inline void clear_task_ebb(struct task_struct *t)
 #endif
 }
 
+extern int set_thread_tidr(struct task_struct *t);
+extern void clear_thread_tidr(struct task_struct *t);
+
 #endif /* _ASM_POWERPC_SWITCH_TO_H */
index 4083b73..887ae50 100644 (file)
@@ -1136,6 +1136,10 @@ static inline void restore_sprs(struct thread_struct *old_thread,
                if (old_thread->tar != new_thread->tar)
                        mtspr(SPRN_TAR, new_thread->tar);
        }
+
+       if (cpu_has_feature(CPU_FTR_ARCH_300) &&
+           old_thread->tidr != new_thread->tidr)
+               mtspr(SPRN_TIDR, new_thread->tidr);
 #endif
 }
 
@@ -1451,6 +1455,116 @@ void flush_thread(void)
 #endif /* CONFIG_HAVE_HW_BREAKPOINT */
 }
 
+#ifdef CONFIG_PPC64
+static DEFINE_SPINLOCK(vas_thread_id_lock);
+static DEFINE_IDA(vas_thread_ida);
+
+/*
+ * We need to assign a unique thread id to each thread in a process.
+ *
+ * This thread id, referred to as TIDR, and separate from the Linux's tgid,
+ * is intended to be used to direct an ASB_Notify from the hardware to the
+ * thread, when a suitable event occurs in the system.
+ *
+ * One such event is a "paste" instruction in the context of Fast Thread
+ * Wakeup (aka Core-to-core wake up in the Virtual Accelerator Switchboard
+ * (VAS) in POWER9.
+ *
+ * To get a unique TIDR per process we could simply reuse task_pid_nr() but
+ * the problem is that task_pid_nr() is not yet available copy_thread() is
+ * called. Fixing that would require changing more intrusive arch-neutral
+ * code in code path in copy_process()?.
+ *
+ * Further, to assign unique TIDRs within each process, we need an atomic
+ * field (or an IDR) in task_struct, which again intrudes into the arch-
+ * neutral code. So try to assign globally unique TIDRs for now.
+ *
+ * NOTE: TIDR 0 indicates that the thread does not need a TIDR value.
+ *      For now, only threads that expect to be notified by the VAS
+ *      hardware need a TIDR value and we assign values > 0 for those.
+ */
+#define MAX_THREAD_CONTEXT     ((1 << 16) - 1)
+static int assign_thread_tidr(void)
+{
+       int index;
+       int err;
+
+again:
+       if (!ida_pre_get(&vas_thread_ida, GFP_KERNEL))
+               return -ENOMEM;
+
+       spin_lock(&vas_thread_id_lock);
+       err = ida_get_new_above(&vas_thread_ida, 1, &index);
+       spin_unlock(&vas_thread_id_lock);
+
+       if (err == -EAGAIN)
+               goto again;
+       else if (err)
+               return err;
+
+       if (index > MAX_THREAD_CONTEXT) {
+               spin_lock(&vas_thread_id_lock);
+               ida_remove(&vas_thread_ida, index);
+               spin_unlock(&vas_thread_id_lock);
+               return -ENOMEM;
+       }
+
+       return index;
+}
+
+static void free_thread_tidr(int id)
+{
+       spin_lock(&vas_thread_id_lock);
+       ida_remove(&vas_thread_ida, id);
+       spin_unlock(&vas_thread_id_lock);
+}
+
+/*
+ * Clear any TIDR value assigned to this thread.
+ */
+void clear_thread_tidr(struct task_struct *t)
+{
+       if (!t->thread.tidr)
+               return;
+
+       if (!cpu_has_feature(CPU_FTR_ARCH_300)) {
+               WARN_ON_ONCE(1);
+               return;
+       }
+
+       mtspr(SPRN_TIDR, 0);
+       free_thread_tidr(t->thread.tidr);
+       t->thread.tidr = 0;
+}
+
+void arch_release_task_struct(struct task_struct *t)
+{
+       clear_thread_tidr(t);
+}
+
+/*
+ * Assign a unique TIDR (thread id) for task @t and set it in the thread
+ * structure. For now, we only support setting TIDR for 'current' task.
+ */
+int set_thread_tidr(struct task_struct *t)
+{
+       if (!cpu_has_feature(CPU_FTR_ARCH_300))
+               return -EINVAL;
+
+       if (t != current)
+               return -EINVAL;
+
+       t->thread.tidr = assign_thread_tidr();
+       if (t->thread.tidr < 0)
+               return t->thread.tidr;
+
+       mtspr(SPRN_TIDR, t->thread.tidr);
+
+       return 0;
+}
+
+#endif /* CONFIG_PPC64 */
+
 void
 release_thread(struct task_struct *t)
 {
@@ -1597,6 +1711,8 @@ int copy_thread(unsigned long clone_flags, unsigned long usp,
        }
        if (cpu_has_feature(CPU_FTR_HAS_PPR))
                p->thread.ppr = INIT_PPR;
+
+       p->thread.tidr = 0;
 #endif
        kregs->nip = ppc_function_entry(f);
        return 0;