mirror of
https://github.com/FEX-Emu/linux.git
synced 2025-01-10 19:43:29 +00:00
23b5c8fa01
(Note: this was reverted, and is now being re-applied in pieces, with this being the fifth and final piece. See below for the reason that it is now felt to be safe to re-apply this.) Commit d09b62d fixed grace-period synchronization, but left some smp_mb() invocations in rcu_process_callbacks() that are no longer needed, but sheer paranoia prevented them from being removed. This commit removes them and provides a proof of correctness in their absence. It also adds a memory barrier to rcu_report_qs_rsp() immediately before the update to rsp->completed in order to handle the theoretical possibility that the compiler or CPU might move massive quantities of code into a lock-based critical section. This also proves that the sheer paranoia was not entirely unjustified, at least from a theoretical point of view. In addition, the old dyntick-idle synchronization depended on the fact that grace periods were many milliseconds in duration, so that it could be assumed that no dyntick-idle CPU could reorder a memory reference across an entire grace period. Unfortunately for this design, the addition of expedited grace periods breaks this assumption, which has the unfortunate side-effect of requiring atomic operations in the functions that track dyntick-idle state for RCU. (There is some hope that the algorithms used in user-level RCU might be applied here, but some work is required to handle the NMIs that user-space applications can happily ignore. For the short term, better safe than sorry.) This proof assumes that neither compiler nor CPU will allow a lock acquisition and release to be reordered, as doing so can result in deadlock. The proof is as follows: 1. A given CPU declares a quiescent state under the protection of its leaf rcu_node's lock. 2. If there is more than one level of rcu_node hierarchy, the last CPU to declare a quiescent state will also acquire the ->lock of the next rcu_node up in the hierarchy, but only after releasing the lower level's lock. The acquisition of this lock clearly cannot occur prior to the acquisition of the leaf node's lock. 3. Step 2 repeats until we reach the root rcu_node structure. Please note again that only one lock is held at a time through this process. The acquisition of the root rcu_node's ->lock must occur after the release of that of the leaf rcu_node. 4. At this point, we set the ->completed field in the rcu_state structure in rcu_report_qs_rsp(). However, if the rcu_node hierarchy contains only one rcu_node, then in theory the code preceding the quiescent state could leak into the critical section. We therefore precede the update of ->completed with a memory barrier. All CPUs will therefore agree that any updates preceding any report of a quiescent state will have happened before the update of ->completed. 5. Regardless of whether a new grace period is needed, rcu_start_gp() will propagate the new value of ->completed to all of the leaf rcu_node structures, under the protection of each rcu_node's ->lock. If a new grace period is needed immediately, this propagation will occur in the same critical section that ->completed was set in, but courtesy of the memory barrier in #4 above, is still seen to follow any pre-quiescent-state activity. 6. When a given CPU invokes __rcu_process_gp_end(), it becomes aware of the end of the old grace period and therefore makes any RCU callbacks that were waiting on that grace period eligible for invocation. If this CPU is the same one that detected the end of the grace period, and if there is but a single rcu_node in the hierarchy, we will still be in the single critical section. In this case, the memory barrier in step #4 guarantees that all callbacks will be seen to execute after each CPU's quiescent state. On the other hand, if this is a different CPU, it will acquire the leaf rcu_node's ->lock, and will again be serialized after each CPU's quiescent state for the old grace period. On the strength of this proof, this commit therefore removes the memory barriers from rcu_process_callbacks() and adds one to rcu_report_qs_rsp(). The effect is to reduce the number of memory barriers by one and to reduce the frequency of execution from about once per scheduling tick per CPU to once per grace period. This was reverted do to hangs found during testing by Yinghai Lu and Ingo Molnar. Frederic Weisbecker supplied Yinghai with tracing that located the underlying problem, and Frederic also provided the fix. The underlying problem was that the HARDIRQ_ENTER() macro from lib/locking-selftest.c invoked irq_enter(), which in turn invokes rcu_irq_enter(), but HARDIRQ_EXIT() invoked __irq_exit(), which does not invoke rcu_irq_exit(). This situation resulted in calls to rcu_irq_enter() that were not balanced by the required calls to rcu_irq_exit(). Therefore, after these locking selftests completed, RCU's dyntick-idle nesting count was a large number (for example, 72), which caused RCU to to conclude that the affected CPU was not in dyntick-idle mode when in fact it was. RCU would therefore incorrectly wait for this dyntick-idle CPU, resulting in hangs. In contrast, with Frederic's patch, which replaces the irq_enter() in HARDIRQ_ENTER() with an __irq_enter(), these tests don't ever call either rcu_irq_enter() or rcu_irq_exit(), which works because the CPU running the test is already marked as not being in dyntick-idle mode. This means that the rcu_irq_enter() and rcu_irq_exit() calls and RCU then has no problem working out which CPUs are in dyntick-idle mode and which are not. The reason that the imbalance was not noticed before the barrier patch was applied is that the old implementation of rcu_enter_nohz() ignored the nesting depth. This could still result in delays, but much shorter ones. Whenever there was a delay, RCU would IPI the CPU with the unbalanced nesting level, which would eventually result in rcu_enter_nohz() being called, which in turn would force RCU to see that the CPU was in dyntick-idle mode. The reason that very few people noticed the problem is that the mismatched irq_enter() vs. __irq_exit() occured only when the kernel was built with CONFIG_DEBUG_LOCKING_API_SELFTESTS. Signed-off-by: Paul E. McKenney <paulmck@linux.vnet.ibm.com> Reviewed-by: Josh Triplett <josh@joshtriplett.org>
502 lines
14 KiB
C
502 lines
14 KiB
C
/*
|
|
* Read-Copy Update tracing for classic implementation
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*
|
|
* Copyright IBM Corporation, 2008
|
|
*
|
|
* Papers: http://www.rdrop.com/users/paulmck/RCU
|
|
*
|
|
* For detailed explanation of Read-Copy Update mechanism see -
|
|
* Documentation/RCU
|
|
*
|
|
*/
|
|
#include <linux/types.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/smp.h>
|
|
#include <linux/rcupdate.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/sched.h>
|
|
#include <asm/atomic.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/module.h>
|
|
#include <linux/completion.h>
|
|
#include <linux/moduleparam.h>
|
|
#include <linux/percpu.h>
|
|
#include <linux/notifier.h>
|
|
#include <linux/cpu.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/seq_file.h>
|
|
|
|
#define RCU_TREE_NONCORE
|
|
#include "rcutree.h"
|
|
|
|
DECLARE_PER_CPU(unsigned int, rcu_cpu_kthread_status);
|
|
DECLARE_PER_CPU(unsigned int, rcu_cpu_kthread_cpu);
|
|
DECLARE_PER_CPU(unsigned int, rcu_cpu_kthread_loops);
|
|
DECLARE_PER_CPU(char, rcu_cpu_has_work);
|
|
|
|
static char convert_kthread_status(unsigned int kthread_status)
|
|
{
|
|
if (kthread_status > RCU_KTHREAD_MAX)
|
|
return '?';
|
|
return "SRWOY"[kthread_status];
|
|
}
|
|
|
|
static void print_one_rcu_data(struct seq_file *m, struct rcu_data *rdp)
|
|
{
|
|
if (!rdp->beenonline)
|
|
return;
|
|
seq_printf(m, "%3d%cc=%lu g=%lu pq=%d pqc=%lu qp=%d",
|
|
rdp->cpu,
|
|
cpu_is_offline(rdp->cpu) ? '!' : ' ',
|
|
rdp->completed, rdp->gpnum,
|
|
rdp->passed_quiesc, rdp->passed_quiesc_completed,
|
|
rdp->qs_pending);
|
|
#ifdef CONFIG_NO_HZ
|
|
seq_printf(m, " dt=%d/%d/%d df=%lu",
|
|
atomic_read(&rdp->dynticks->dynticks),
|
|
rdp->dynticks->dynticks_nesting,
|
|
rdp->dynticks->dynticks_nmi_nesting,
|
|
rdp->dynticks_fqs);
|
|
#endif /* #ifdef CONFIG_NO_HZ */
|
|
seq_printf(m, " of=%lu ri=%lu", rdp->offline_fqs, rdp->resched_ipi);
|
|
seq_printf(m, " ql=%ld qs=%c%c%c%c kt=%d/%c/%d ktl=%x b=%ld",
|
|
rdp->qlen,
|
|
".N"[rdp->nxttail[RCU_NEXT_READY_TAIL] !=
|
|
rdp->nxttail[RCU_NEXT_TAIL]],
|
|
".R"[rdp->nxttail[RCU_WAIT_TAIL] !=
|
|
rdp->nxttail[RCU_NEXT_READY_TAIL]],
|
|
".W"[rdp->nxttail[RCU_DONE_TAIL] !=
|
|
rdp->nxttail[RCU_WAIT_TAIL]],
|
|
".D"[&rdp->nxtlist != rdp->nxttail[RCU_DONE_TAIL]],
|
|
per_cpu(rcu_cpu_has_work, rdp->cpu),
|
|
convert_kthread_status(per_cpu(rcu_cpu_kthread_status,
|
|
rdp->cpu)),
|
|
per_cpu(rcu_cpu_kthread_cpu, rdp->cpu),
|
|
per_cpu(rcu_cpu_kthread_loops, rdp->cpu) & 0xffff,
|
|
rdp->blimit);
|
|
seq_printf(m, " ci=%lu co=%lu ca=%lu\n",
|
|
rdp->n_cbs_invoked, rdp->n_cbs_orphaned, rdp->n_cbs_adopted);
|
|
}
|
|
|
|
#define PRINT_RCU_DATA(name, func, m) \
|
|
do { \
|
|
int _p_r_d_i; \
|
|
\
|
|
for_each_possible_cpu(_p_r_d_i) \
|
|
func(m, &per_cpu(name, _p_r_d_i)); \
|
|
} while (0)
|
|
|
|
static int show_rcudata(struct seq_file *m, void *unused)
|
|
{
|
|
#ifdef CONFIG_TREE_PREEMPT_RCU
|
|
seq_puts(m, "rcu_preempt:\n");
|
|
PRINT_RCU_DATA(rcu_preempt_data, print_one_rcu_data, m);
|
|
#endif /* #ifdef CONFIG_TREE_PREEMPT_RCU */
|
|
seq_puts(m, "rcu_sched:\n");
|
|
PRINT_RCU_DATA(rcu_sched_data, print_one_rcu_data, m);
|
|
seq_puts(m, "rcu_bh:\n");
|
|
PRINT_RCU_DATA(rcu_bh_data, print_one_rcu_data, m);
|
|
return 0;
|
|
}
|
|
|
|
static int rcudata_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, show_rcudata, NULL);
|
|
}
|
|
|
|
static const struct file_operations rcudata_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = rcudata_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static void print_one_rcu_data_csv(struct seq_file *m, struct rcu_data *rdp)
|
|
{
|
|
if (!rdp->beenonline)
|
|
return;
|
|
seq_printf(m, "%d,%s,%lu,%lu,%d,%lu,%d",
|
|
rdp->cpu,
|
|
cpu_is_offline(rdp->cpu) ? "\"N\"" : "\"Y\"",
|
|
rdp->completed, rdp->gpnum,
|
|
rdp->passed_quiesc, rdp->passed_quiesc_completed,
|
|
rdp->qs_pending);
|
|
#ifdef CONFIG_NO_HZ
|
|
seq_printf(m, ",%d,%d,%d,%lu",
|
|
atomic_read(&rdp->dynticks->dynticks),
|
|
rdp->dynticks->dynticks_nesting,
|
|
rdp->dynticks->dynticks_nmi_nesting,
|
|
rdp->dynticks_fqs);
|
|
#endif /* #ifdef CONFIG_NO_HZ */
|
|
seq_printf(m, ",%lu,%lu", rdp->offline_fqs, rdp->resched_ipi);
|
|
seq_printf(m, ",%ld,\"%c%c%c%c\",%d,\"%c\",%ld", rdp->qlen,
|
|
".N"[rdp->nxttail[RCU_NEXT_READY_TAIL] !=
|
|
rdp->nxttail[RCU_NEXT_TAIL]],
|
|
".R"[rdp->nxttail[RCU_WAIT_TAIL] !=
|
|
rdp->nxttail[RCU_NEXT_READY_TAIL]],
|
|
".W"[rdp->nxttail[RCU_DONE_TAIL] !=
|
|
rdp->nxttail[RCU_WAIT_TAIL]],
|
|
".D"[&rdp->nxtlist != rdp->nxttail[RCU_DONE_TAIL]],
|
|
per_cpu(rcu_cpu_has_work, rdp->cpu),
|
|
convert_kthread_status(per_cpu(rcu_cpu_kthread_status,
|
|
rdp->cpu)),
|
|
rdp->blimit);
|
|
seq_printf(m, ",%lu,%lu,%lu\n",
|
|
rdp->n_cbs_invoked, rdp->n_cbs_orphaned, rdp->n_cbs_adopted);
|
|
}
|
|
|
|
static int show_rcudata_csv(struct seq_file *m, void *unused)
|
|
{
|
|
seq_puts(m, "\"CPU\",\"Online?\",\"c\",\"g\",\"pq\",\"pqc\",\"pq\",");
|
|
#ifdef CONFIG_NO_HZ
|
|
seq_puts(m, "\"dt\",\"dt nesting\",\"dt NMI nesting\",\"df\",");
|
|
#endif /* #ifdef CONFIG_NO_HZ */
|
|
seq_puts(m, "\"of\",\"ri\",\"ql\",\"b\",\"ci\",\"co\",\"ca\"\n");
|
|
#ifdef CONFIG_TREE_PREEMPT_RCU
|
|
seq_puts(m, "\"rcu_preempt:\"\n");
|
|
PRINT_RCU_DATA(rcu_preempt_data, print_one_rcu_data_csv, m);
|
|
#endif /* #ifdef CONFIG_TREE_PREEMPT_RCU */
|
|
seq_puts(m, "\"rcu_sched:\"\n");
|
|
PRINT_RCU_DATA(rcu_sched_data, print_one_rcu_data_csv, m);
|
|
seq_puts(m, "\"rcu_bh:\"\n");
|
|
PRINT_RCU_DATA(rcu_bh_data, print_one_rcu_data_csv, m);
|
|
return 0;
|
|
}
|
|
|
|
static int rcudata_csv_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, show_rcudata_csv, NULL);
|
|
}
|
|
|
|
static const struct file_operations rcudata_csv_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = rcudata_csv_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
#ifdef CONFIG_RCU_BOOST
|
|
|
|
static void print_one_rcu_node_boost(struct seq_file *m, struct rcu_node *rnp)
|
|
{
|
|
seq_printf(m, "%d:%d tasks=%c%c%c%c kt=%c ntb=%lu neb=%lu nnb=%lu "
|
|
"j=%04x bt=%04x\n",
|
|
rnp->grplo, rnp->grphi,
|
|
"T."[list_empty(&rnp->blkd_tasks)],
|
|
"N."[!rnp->gp_tasks],
|
|
"E."[!rnp->exp_tasks],
|
|
"B."[!rnp->boost_tasks],
|
|
convert_kthread_status(rnp->boost_kthread_status),
|
|
rnp->n_tasks_boosted, rnp->n_exp_boosts,
|
|
rnp->n_normal_boosts,
|
|
(int)(jiffies & 0xffff),
|
|
(int)(rnp->boost_time & 0xffff));
|
|
seq_printf(m, "%s: nt=%lu egt=%lu bt=%lu nb=%lu ny=%lu nos=%lu\n",
|
|
" balk",
|
|
rnp->n_balk_blkd_tasks,
|
|
rnp->n_balk_exp_gp_tasks,
|
|
rnp->n_balk_boost_tasks,
|
|
rnp->n_balk_notblocked,
|
|
rnp->n_balk_notyet,
|
|
rnp->n_balk_nos);
|
|
}
|
|
|
|
static int show_rcu_node_boost(struct seq_file *m, void *unused)
|
|
{
|
|
struct rcu_node *rnp;
|
|
|
|
rcu_for_each_leaf_node(&rcu_preempt_state, rnp)
|
|
print_one_rcu_node_boost(m, rnp);
|
|
return 0;
|
|
}
|
|
|
|
static int rcu_node_boost_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, show_rcu_node_boost, NULL);
|
|
}
|
|
|
|
static const struct file_operations rcu_node_boost_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = rcu_node_boost_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
/*
|
|
* Create the rcuboost debugfs entry. Standard error return.
|
|
*/
|
|
static int rcu_boost_trace_create_file(struct dentry *rcudir)
|
|
{
|
|
return !debugfs_create_file("rcuboost", 0444, rcudir, NULL,
|
|
&rcu_node_boost_fops);
|
|
}
|
|
|
|
#else /* #ifdef CONFIG_RCU_BOOST */
|
|
|
|
static int rcu_boost_trace_create_file(struct dentry *rcudir)
|
|
{
|
|
return 0; /* There cannot be an error if we didn't create it! */
|
|
}
|
|
|
|
#endif /* #else #ifdef CONFIG_RCU_BOOST */
|
|
|
|
static void print_one_rcu_state(struct seq_file *m, struct rcu_state *rsp)
|
|
{
|
|
unsigned long gpnum;
|
|
int level = 0;
|
|
struct rcu_node *rnp;
|
|
|
|
gpnum = rsp->gpnum;
|
|
seq_printf(m, "c=%lu g=%lu s=%d jfq=%ld j=%x "
|
|
"nfqs=%lu/nfqsng=%lu(%lu) fqlh=%lu\n",
|
|
rsp->completed, gpnum, rsp->signaled,
|
|
(long)(rsp->jiffies_force_qs - jiffies),
|
|
(int)(jiffies & 0xffff),
|
|
rsp->n_force_qs, rsp->n_force_qs_ngp,
|
|
rsp->n_force_qs - rsp->n_force_qs_ngp,
|
|
rsp->n_force_qs_lh);
|
|
for (rnp = &rsp->node[0]; rnp - &rsp->node[0] < NUM_RCU_NODES; rnp++) {
|
|
if (rnp->level != level) {
|
|
seq_puts(m, "\n");
|
|
level = rnp->level;
|
|
}
|
|
seq_printf(m, "%lx/%lx %c%c>%c %d:%d ^%d ",
|
|
rnp->qsmask, rnp->qsmaskinit,
|
|
".G"[rnp->gp_tasks != NULL],
|
|
".E"[rnp->exp_tasks != NULL],
|
|
".T"[!list_empty(&rnp->blkd_tasks)],
|
|
rnp->grplo, rnp->grphi, rnp->grpnum);
|
|
}
|
|
seq_puts(m, "\n");
|
|
}
|
|
|
|
static int show_rcuhier(struct seq_file *m, void *unused)
|
|
{
|
|
#ifdef CONFIG_TREE_PREEMPT_RCU
|
|
seq_puts(m, "rcu_preempt:\n");
|
|
print_one_rcu_state(m, &rcu_preempt_state);
|
|
#endif /* #ifdef CONFIG_TREE_PREEMPT_RCU */
|
|
seq_puts(m, "rcu_sched:\n");
|
|
print_one_rcu_state(m, &rcu_sched_state);
|
|
seq_puts(m, "rcu_bh:\n");
|
|
print_one_rcu_state(m, &rcu_bh_state);
|
|
return 0;
|
|
}
|
|
|
|
static int rcuhier_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, show_rcuhier, NULL);
|
|
}
|
|
|
|
static const struct file_operations rcuhier_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = rcuhier_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static void show_one_rcugp(struct seq_file *m, struct rcu_state *rsp)
|
|
{
|
|
unsigned long flags;
|
|
unsigned long completed;
|
|
unsigned long gpnum;
|
|
unsigned long gpage;
|
|
unsigned long gpmax;
|
|
struct rcu_node *rnp = &rsp->node[0];
|
|
|
|
raw_spin_lock_irqsave(&rnp->lock, flags);
|
|
completed = rsp->completed;
|
|
gpnum = rsp->gpnum;
|
|
if (rsp->completed == rsp->gpnum)
|
|
gpage = 0;
|
|
else
|
|
gpage = jiffies - rsp->gp_start;
|
|
gpmax = rsp->gp_max;
|
|
raw_spin_unlock_irqrestore(&rnp->lock, flags);
|
|
seq_printf(m, "%s: completed=%ld gpnum=%lu age=%ld max=%ld\n",
|
|
rsp->name, completed, gpnum, gpage, gpmax);
|
|
}
|
|
|
|
static int show_rcugp(struct seq_file *m, void *unused)
|
|
{
|
|
#ifdef CONFIG_TREE_PREEMPT_RCU
|
|
show_one_rcugp(m, &rcu_preempt_state);
|
|
#endif /* #ifdef CONFIG_TREE_PREEMPT_RCU */
|
|
show_one_rcugp(m, &rcu_sched_state);
|
|
show_one_rcugp(m, &rcu_bh_state);
|
|
return 0;
|
|
}
|
|
|
|
static int rcugp_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, show_rcugp, NULL);
|
|
}
|
|
|
|
static const struct file_operations rcugp_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = rcugp_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static void print_one_rcu_pending(struct seq_file *m, struct rcu_data *rdp)
|
|
{
|
|
seq_printf(m, "%3d%cnp=%ld "
|
|
"qsp=%ld rpq=%ld cbr=%ld cng=%ld "
|
|
"gpc=%ld gps=%ld nf=%ld nn=%ld\n",
|
|
rdp->cpu,
|
|
cpu_is_offline(rdp->cpu) ? '!' : ' ',
|
|
rdp->n_rcu_pending,
|
|
rdp->n_rp_qs_pending,
|
|
rdp->n_rp_report_qs,
|
|
rdp->n_rp_cb_ready,
|
|
rdp->n_rp_cpu_needs_gp,
|
|
rdp->n_rp_gp_completed,
|
|
rdp->n_rp_gp_started,
|
|
rdp->n_rp_need_fqs,
|
|
rdp->n_rp_need_nothing);
|
|
}
|
|
|
|
static void print_rcu_pendings(struct seq_file *m, struct rcu_state *rsp)
|
|
{
|
|
int cpu;
|
|
struct rcu_data *rdp;
|
|
|
|
for_each_possible_cpu(cpu) {
|
|
rdp = per_cpu_ptr(rsp->rda, cpu);
|
|
if (rdp->beenonline)
|
|
print_one_rcu_pending(m, rdp);
|
|
}
|
|
}
|
|
|
|
static int show_rcu_pending(struct seq_file *m, void *unused)
|
|
{
|
|
#ifdef CONFIG_TREE_PREEMPT_RCU
|
|
seq_puts(m, "rcu_preempt:\n");
|
|
print_rcu_pendings(m, &rcu_preempt_state);
|
|
#endif /* #ifdef CONFIG_TREE_PREEMPT_RCU */
|
|
seq_puts(m, "rcu_sched:\n");
|
|
print_rcu_pendings(m, &rcu_sched_state);
|
|
seq_puts(m, "rcu_bh:\n");
|
|
print_rcu_pendings(m, &rcu_bh_state);
|
|
return 0;
|
|
}
|
|
|
|
static int rcu_pending_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, show_rcu_pending, NULL);
|
|
}
|
|
|
|
static const struct file_operations rcu_pending_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = rcu_pending_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static int show_rcutorture(struct seq_file *m, void *unused)
|
|
{
|
|
seq_printf(m, "rcutorture test sequence: %lu %s\n",
|
|
rcutorture_testseq >> 1,
|
|
(rcutorture_testseq & 0x1) ? "(test in progress)" : "");
|
|
seq_printf(m, "rcutorture update version number: %lu\n",
|
|
rcutorture_vernum);
|
|
return 0;
|
|
}
|
|
|
|
static int rcutorture_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, show_rcutorture, NULL);
|
|
}
|
|
|
|
static const struct file_operations rcutorture_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = rcutorture_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static struct dentry *rcudir;
|
|
|
|
static int __init rcutree_trace_init(void)
|
|
{
|
|
struct dentry *retval;
|
|
|
|
rcudir = debugfs_create_dir("rcu", NULL);
|
|
if (!rcudir)
|
|
goto free_out;
|
|
|
|
retval = debugfs_create_file("rcudata", 0444, rcudir,
|
|
NULL, &rcudata_fops);
|
|
if (!retval)
|
|
goto free_out;
|
|
|
|
retval = debugfs_create_file("rcudata.csv", 0444, rcudir,
|
|
NULL, &rcudata_csv_fops);
|
|
if (!retval)
|
|
goto free_out;
|
|
|
|
if (rcu_boost_trace_create_file(rcudir))
|
|
goto free_out;
|
|
|
|
retval = debugfs_create_file("rcugp", 0444, rcudir, NULL, &rcugp_fops);
|
|
if (!retval)
|
|
goto free_out;
|
|
|
|
retval = debugfs_create_file("rcuhier", 0444, rcudir,
|
|
NULL, &rcuhier_fops);
|
|
if (!retval)
|
|
goto free_out;
|
|
|
|
retval = debugfs_create_file("rcu_pending", 0444, rcudir,
|
|
NULL, &rcu_pending_fops);
|
|
if (!retval)
|
|
goto free_out;
|
|
|
|
retval = debugfs_create_file("rcutorture", 0444, rcudir,
|
|
NULL, &rcutorture_fops);
|
|
if (!retval)
|
|
goto free_out;
|
|
return 0;
|
|
free_out:
|
|
debugfs_remove_recursive(rcudir);
|
|
return 1;
|
|
}
|
|
|
|
static void __exit rcutree_trace_cleanup(void)
|
|
{
|
|
debugfs_remove_recursive(rcudir);
|
|
}
|
|
|
|
|
|
module_init(rcutree_trace_init);
|
|
module_exit(rcutree_trace_cleanup);
|
|
|
|
MODULE_AUTHOR("Paul E. McKenney");
|
|
MODULE_DESCRIPTION("Read-Copy Update tracing for hierarchical implementation");
|
|
MODULE_LICENSE("GPL");
|