EXP: Fine-grained timer diagnostics

Signed-off-by: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
diff --git a/include/linux/init_task.h b/include/linux/init_task.h
index 3c07ace..5bfdeabe 100644
--- a/include/linux/init_task.h
+++ b/include/linux/init_task.h
@@ -289,6 +289,8 @@ extern struct cred init_cred;
 	INIT_FTRACE_GRAPH						\
 	INIT_TRACE_RECURSION						\
 	INIT_TASK_RCU_PREEMPT(tsk)					\
+	.rcu_trace_timers = 0,						\
+	.rcu_timer = NULL,						\
 	INIT_TASK_RCU_TASKS(tsk)					\
 	INIT_CPUSET_SEQ(tsk)						\
 	INIT_RT_MUTEXES(tsk)						\
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 4d3a2a7..68d98e6 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -586,6 +586,8 @@ struct task_struct {
 	struct list_head		rcu_node_entry;
 	struct rcu_node			*rcu_blocked_node;
 #endif /* #ifdef CONFIG_PREEMPT_RCU */
+	u8				rcu_trace_timers;
+	struct timer_list		*rcu_timer;
 
 #ifdef CONFIG_TASKS_RCU
 	unsigned long			rcu_tasks_nvcsw;
diff --git a/include/linux/timer.h b/include/linux/timer.h
index e6789b8..ee8534c 100644
--- a/include/linux/timer.h
+++ b/include/linux/timer.h
@@ -19,6 +19,7 @@ struct timer_list {
 	void			(*function)(unsigned long);
 	unsigned long		data;
 	u32			flags;
+	struct task_struct	*rcu_timer_task;
 
 #ifdef CONFIG_LOCKDEP
 	struct lockdep_map	lockdep_map;
diff --git a/kernel/fork.c b/kernel/fork.c
index 1064618..88167d4 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -1504,6 +1504,8 @@ static inline void rcu_copy_process(struct task_struct *p)
 	INIT_LIST_HEAD(&p->rcu_tasks_holdout_list);
 	p->rcu_tasks_idle_cpu = -1;
 #endif /* #ifdef CONFIG_TASKS_RCU */
+	p->rcu_trace_timers = 0;
+	p->rcu_timer = NULL;
 }
 
 /*
diff --git a/kernel/rcu/tree.c b/kernel/rcu/tree.c
index e4fe06d..b871f51 100644
--- a/kernel/rcu/tree.c
+++ b/kernel/rcu/tree.c
@@ -4058,6 +4058,7 @@ static int __init rcu_spawn_gp_kthread(void)
 		rnp = rcu_get_root(rsp);
 		raw_spin_lock_irqsave_rcu_node(rnp, flags);
 		rsp->gp_kthread = t;
+		t->rcu_trace_timers = 1;
 		if (kthread_prio) {
 			sp.sched_priority = kthread_prio;
 			sched_setscheduler_nocheck(t, SCHED_FIFO, &sp);
diff --git a/kernel/time/timer.c b/kernel/time/timer.c
index f2674a0..3c92414 100644
--- a/kernel/time/timer.c
+++ b/kernel/time/timer.c
@@ -1739,11 +1739,16 @@ signed long __sched schedule_timeout(signed long timeout)
 	expire = timeout + jiffies;
 
 	setup_timer_on_stack(&timer, process_timeout, (unsigned long)current);
+	if (current->rcu_trace_timers) {
+		current->rcu_timer = &timer;
+		timer.rcu_timer_task = current;
+	}
 	__mod_timer(&timer, expire, false);
 	schedule();
 	del_singleshot_timer_sync(&timer);
 
 	/* Remove the timer from the object tracker */
+	current->rcu_timer = NULL;
 	destroy_timer_on_stack(&timer);
 
 	timeout = expire - jiffies;
@@ -1799,6 +1804,8 @@ static void migrate_timer_list(struct timer_base *new_base, struct hlist_head *h
 		timer = hlist_entry(head->first, struct timer_list, entry);
 		detach_timer(timer, false);
 		timer->flags = (timer->flags & ~TIMER_BASEMASK) | cpu;
+		if (timer->rcu_timer_task)
+			pr_info("Migrated GP kthread timer\n");
 		internal_add_timer(new_base, timer);
 	}
 }