From: "Paul E. McKenney" <paulmck@kernel.org>
To: rcu@vger.kernel.org
Cc: linux-kernel@vger.kernel.org, kernel-team@meta.com,
rostedt@goodmis.org,
Mathieu Desnoyers <mathieu.desnoyers@efficios.com>,
"Paul E. McKenney" <paulmck@kernel.org>
Subject: [PATCH RFC 1/4] torture: Add a hazptrtorture.c torture test
Date: Thu, 7 May 2026 09:51:10 -0700 [thread overview]
Message-ID: <20260507165113.2039524-1-paulmck@kernel.org> (raw)
In-Reply-To: <5bfe6ac9-432e-43b8-8290-59ca249b0f80@paulmck-laptop>
This commit adds a torture test for hazard pointers.
[ paulmck: Apply kernel test robot feedback. ]
Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
---
include/linux/torture.h | 2 +-
kernel/rcu/Kconfig.debug | 12 +
kernel/rcu/Makefile | 1 +
kernel/rcu/hazptrtorture.c | 684 ++++++++++++++++++
kernel/rcu/update.c | 3 +-
tools/testing/selftests/rcutorture/bin/kvm.sh | 6 +-
.../rcutorture/configs/hazptr/CFLIST | 2 +
.../rcutorture/configs/hazptr/CFcommon | 2 +
.../rcutorture/configs/hazptr/NOPREEMPT | 17 +
.../rcutorture/configs/hazptr/PREEMPT | 14 +
.../configs/hazptr/ver_functions.sh | 40 +
11 files changed, 778 insertions(+), 5 deletions(-)
create mode 100644 kernel/rcu/hazptrtorture.c
create mode 100644 tools/testing/selftests/rcutorture/configs/hazptr/CFLIST
create mode 100644 tools/testing/selftests/rcutorture/configs/hazptr/CFcommon
create mode 100644 tools/testing/selftests/rcutorture/configs/hazptr/NOPREEMPT
create mode 100644 tools/testing/selftests/rcutorture/configs/hazptr/PREEMPT
create mode 100644 tools/testing/selftests/rcutorture/configs/hazptr/ver_functions.sh
diff --git a/include/linux/torture.h b/include/linux/torture.h
index 1b59056c3b1822..d80f24ff69e3e1 100644
--- a/include/linux/torture.h
+++ b/include/linux/torture.h
@@ -130,7 +130,7 @@ void _torture_stop_kthread(char *m, struct task_struct **tp);
#define torture_preempt_schedule() do { } while (0)
#endif
-#if IS_ENABLED(CONFIG_RCU_TORTURE_TEST) || IS_MODULE(CONFIG_RCU_TORTURE_TEST) || IS_ENABLED(CONFIG_LOCK_TORTURE_TEST) || IS_MODULE(CONFIG_LOCK_TORTURE_TEST)
+#if IS_ENABLED(CONFIG_RCU_TORTURE_TEST) || IS_ENABLED(CONFIG_LOCK_TORTURE_TEST) || IS_ENABLED(CONFIG_HAZPTR_TORTURE_TEST)
long torture_sched_setaffinity(pid_t pid, const struct cpumask *in_mask, bool dowarn);
#endif
diff --git a/kernel/rcu/Kconfig.debug b/kernel/rcu/Kconfig.debug
index 83ac4e82cad7ee..7629c345b0b68f 100644
--- a/kernel/rcu/Kconfig.debug
+++ b/kernel/rcu/Kconfig.debug
@@ -113,6 +113,18 @@ config RCU_REF_SCALE_TEST
Say M if you want to build it as a module instead.
Say N if you are unsure.
+config HAZPTR_TORTURE_TEST
+ tristate "Torture tests for hazard pointers"
+ depends on DEBUG_KERNEL
+ select TORTURE_TEST
+ default n
+ help
+ This option provides in-kernel hazard-pointer stress tests.
+
+ Say Y here if you want hazard-pointer testing built into the kernel.
+ Say M if you want to build them as a module instead.
+ Say N if you are unsure.
+
config REPRO_TEST
tristate "Bug-reproducibility kernel code"
depends on DEBUG_KERNEL
diff --git a/kernel/rcu/Makefile b/kernel/rcu/Makefile
index c97351ec679adc..12bddae9dd266e 100644
--- a/kernel/rcu/Makefile
+++ b/kernel/rcu/Makefile
@@ -10,6 +10,7 @@ endif
obj-y += update.o sync.o
obj-$(CONFIG_TREE_SRCU) += srcutree.o
obj-$(CONFIG_TINY_SRCU) += srcutiny.o
+obj-$(CONFIG_HAZPTR_TORTURE_TEST) += hazptrtorture.o
obj-$(CONFIG_RCU_TORTURE_TEST) += rcutorture.o
obj-$(CONFIG_RCU_SCALE_TEST) += rcuscale.o
obj-$(CONFIG_RCU_REF_SCALE_TEST) += refscale.o
diff --git a/kernel/rcu/hazptrtorture.c b/kernel/rcu/hazptrtorture.c
new file mode 100644
index 00000000000000..1949a8da4f8c9d
--- /dev/null
+++ b/kernel/rcu/hazptrtorture.c
@@ -0,0 +1,684 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Hazard-pointer module-based torture test facility
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates.
+ *
+ * Author: Paul E. McKenney <paulmck@kernel.org>
+ */
+
+#define pr_fmt(fmt) fmt
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched/debug.h>
+#include <linux/delay.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/reboot.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/torture.h>
+#include <linux/hazptr.h>
+#include <linux/rcupdate.h>
+
+#include "rcu.h"
+
+MODULE_DESCRIPTION("Hazard-pointer module-based torture test facility");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Paul E. McKenney <paulmckrcu@meta.com>");
+
+torture_param(int, irqreader, 1, "Allow hazard-pointer readers from irq handlers");
+// @@@ torture_param(int, leakpointer, 0, "Leak pointer dereferences from readers");
+torture_param(int, nreaders, -1, "Number of hazard-pointer reader threads");
+torture_param(int, onoff_holdoff, 0, "Time after boot before CPU hotplugs (s)");
+torture_param(int, onoff_interval, 0, "Time between CPU hotplugs (jiffies), 0=disable");
+// @@@ Move the rcu_torture_preempt() function and friends to kernel/torture.c.
+torture_param(int, preempt_duration, 0, "Preemption duration (ms), zero to disable");
+torture_param(int, preempt_interval, MSEC_PER_SEC, "Interval between preemptions (ms)");
+torture_param(int, shuffle_interval, 3, "Number of seconds between shuffles");
+torture_param(int, shutdown_secs, 0, "Shutdown time (s), <= zero to disable.");
+torture_param(int, stat_interval, 60, "Number of seconds between stats printk()s");
+torture_param(int, stutter, 5, "Number of seconds to run/halt test");
+torture_param(int, verbose, 1, "Enable verbose debugging printk()s");
+
+static char *torture_type = "hazptr";
+module_param(torture_type, charp, 0444);
+MODULE_PARM_DESC(torture_type, "Type of hazard pointers to torture (hazptr, ...)");
+
+static int nrealreaders;
+static struct task_struct *writer_task;
+static struct task_struct *preempt_task;
+static struct task_struct **reader_tasks;
+static struct task_struct *stats_task;
+
+#define HAZPTR_TORTURE_PIPE_LEN 10
+
+// Update-side data structure used to check RCU readers.
+struct hazptr_torture {
+ void *obj_hazptr;
+ int htort_pipe_count;
+ struct list_head htort_free;
+};
+
+static LIST_HEAD(hazptr_torture_freelist);
+static struct hazptr_torture /* __hazptr @@@ */ *hazptr_torture_current;
+static unsigned long hazptr_torture_current_version;
+static struct hazptr_torture hazptr_tortures[10 * HAZPTR_TORTURE_PIPE_LEN];
+static DEFINE_SPINLOCK(hazptr_torture_lock);
+static DEFINE_PER_CPU(long [HAZPTR_TORTURE_PIPE_LEN + 1], hazptr_torture_count);
+static atomic_t hazptr_torture_wcount[HAZPTR_TORTURE_PIPE_LEN + 1];
+static atomic_t n_hazptr_torture_alloc;
+static atomic_t n_hazptr_torture_alloc_fail;
+static atomic_t n_hazptr_torture_free;
+static atomic_t n_hazptr_torture_error;
+static struct list_head hazptr_torture_removed;
+
+/* @@@ */ static int hazptr_torture_writer_state;
+#define HTWS_FIXED_DELAY 0
+#define HTWS_DELAY 1
+#define HTWS_REPLACE 2
+#define HTWS_SYNC 3
+#define HTWS_STUTTER 4
+#define HTWS_STOPPING 5
+static const char * const hazptr_torture_writer_state_names[] = {
+ "HTWS_FIXED_DELAY",
+ "HTWS_DELAY",
+ "HTWS_REPLACE",
+ "HTWS_SYNC",
+ "HTWS_STUTTER",
+ "HTWS_STOPPING",
+};
+
+static const char *hazptr_torture_writer_state_getname(void)
+{
+ unsigned int i = READ_ONCE(hazptr_torture_writer_state);
+
+ if (i >= ARRAY_SIZE(hazptr_torture_writer_state_names))
+ return "???";
+ return hazptr_torture_writer_state_names[i];
+}
+
+/*
+ * Allocate an element from the hazptr_tortures pool.
+ */
+static struct hazptr_torture *hazptr_torture_alloc(void)
+{
+ struct list_head *p;
+
+ spin_lock_bh(&hazptr_torture_lock);
+ if (list_empty(&hazptr_torture_freelist)) {
+ atomic_inc(&n_hazptr_torture_alloc_fail);
+ spin_unlock_bh(&hazptr_torture_lock);
+ return NULL;
+ }
+ atomic_inc(&n_hazptr_torture_alloc);
+ p = hazptr_torture_freelist.next;
+ list_del_init(p);
+ spin_unlock_bh(&hazptr_torture_lock);
+ return container_of(p, struct hazptr_torture, htort_free);
+}
+
+/*
+ * Free an element to the hazptr_tortures pool.
+ */
+static void
+hazptr_torture_free(struct hazptr_torture *p)
+{
+ atomic_inc(&n_hazptr_torture_free);
+ spin_lock_bh(&hazptr_torture_lock);
+ list_add_tail(&p->htort_free, &hazptr_torture_freelist);
+ spin_unlock_bh(&hazptr_torture_lock);
+}
+
+/*
+ * Update object in the pipe. This should be invoked after a suitable time.
+ */
+static bool
+hazptr_torture_pipe_update_one(struct hazptr_torture *rp)
+{
+ int i;
+
+ i = rp->htort_pipe_count;
+ if (i > HAZPTR_TORTURE_PIPE_LEN)
+ i = HAZPTR_TORTURE_PIPE_LEN;
+ atomic_inc(&hazptr_torture_wcount[i]);
+ WRITE_ONCE(rp->htort_pipe_count, i + 1);
+ ASSERT_EXCLUSIVE_WRITER(rp->htort_pipe_count);
+ if (i + 1 >= HAZPTR_TORTURE_PIPE_LEN)
+ return true;
+ return false;
+}
+
+/*
+ * Update all callbacks in the pipe each time period.
+ */
+static void
+hazptr_torture_pipe_update(struct hazptr_torture *old_rp)
+{
+ struct hazptr_torture *rp;
+ struct hazptr_torture *rp1;
+
+ if (old_rp)
+ list_add(&old_rp->htort_free, &hazptr_torture_removed);
+ list_for_each_entry_safe(rp, rp1, &hazptr_torture_removed, htort_free) {
+ if (hazptr_torture_pipe_update_one(rp)) {
+ list_del(&rp->htort_free);
+ hazptr_torture_free(rp);
+ }
+ }
+}
+
+/*
+ * Operations vector for selecting different types of tests.
+ */
+
+struct hazptr_torture_ops {
+ void (*init)(void);
+ void (*cleanup)(void);
+ struct hazptr_torture *((*readlock)(struct hazptr_ctx **hcpp));
+ void (*read_delay)(struct torture_random_state *rrsp);
+ void (*readunlock)(struct hazptr_ctx *hcp, struct hazptr_torture *htp);
+ // @@@ int (*readlock_held)(void); // lockdep.
+ // @@@ int (*readlock_nesting)(void); // actual nesting, if available, -1 if not.
+ // @@@ void (*deferred_free)(struct rcu_torture *p); @@@ call_hazptr()
+ void (*sync)(void *htp);
+ // @@@ void (*stats)(void); If statistics must be extracted from hazptr.c.
+ int irq_capable;
+ int must_free_ctx;
+ const char *name;
+};
+
+static struct hazptr_torture_ops *cur_ops;
+
+/*
+ * Definitions for hazard-pointer torture testing.
+ */
+
+static struct hazptr_torture *hazptr_torture_read_lock(struct hazptr_ctx **hcpp)
+{
+ struct hazptr_ctx *hcp = kmalloc(sizeof(*hcp), GFP_KERNEL);
+
+ *hcpp = hcp;
+ if (!hcp)
+ return NULL;
+ return (struct hazptr_torture *)hazptr_acquire(hcp, (void *)&hazptr_torture_current);
+}
+
+static void hazptr_read_delay(struct torture_random_state *rrsp)
+{
+ const unsigned long shortdelay_us = 200;
+ unsigned long longdelay_ms = 300;
+
+ /* We want a short delay sometimes to make a reader delay the grace
+ * period, and we want a long delay occasionally to trigger
+ * force_quiescent_state. */
+
+ if (!(torture_random(rrsp) % (nrealreaders * 2000 * longdelay_ms))) {
+ if ((preempt_count() & HARDIRQ_MASK) || softirq_count())
+ longdelay_ms = 5; /* Avoid triggering BH limits. */
+ mdelay(longdelay_ms);
+ }
+ if (!(torture_random(rrsp) % (nrealreaders * 2 * shortdelay_us)))
+ udelay(shortdelay_us);
+ if (!preempt_count() && !(torture_random(rrsp) % (nrealreaders * 500)))
+ torture_preempt_schedule(); /* QS only if preemptible. */
+}
+
+static void hazptr_torture_read_unlock(struct hazptr_ctx *hcp, struct hazptr_torture *htp)
+{
+ if (hcp) {
+ hazptr_release(hcp, htp);
+ if (cur_ops->must_free_ctx)
+ kfree(hcp);
+ }
+}
+
+static void hazptr_sync_torture_init(void)
+{
+ INIT_LIST_HEAD(&hazptr_torture_removed);
+}
+
+static struct hazptr_torture_ops hazptr_ops = {
+ .init = hazptr_sync_torture_init,
+ .readlock = hazptr_torture_read_lock,
+ .read_delay = hazptr_read_delay,
+ .readunlock = hazptr_torture_read_unlock,
+ .sync = hazptr_synchronize,
+ .irq_capable = 1,
+ .must_free_ctx = 1,
+ .name = "hazptr"
+};
+
+/*
+ * Hazard-pointer torture writer kthread. Repeatedly substitutes a new
+ * structure for that pointed to by hazptr_torture_current, freeing the
+ * old structure after a series of timeouts (the "pipeline").
+ */
+static int
+hazptr_torture_writer(void *arg)
+{
+ bool booting_still = false;
+ int i;
+ unsigned long j;
+ int oldnice = task_nice(current);
+ struct hazptr_torture *rp;
+ struct hazptr_torture *old_rp;
+ static DEFINE_TORTURE_RANDOM(rand);
+ bool stutter_waited;
+
+ VERBOSE_TOROUT_STRING("hazptr_torture_writer task started");
+ // If the system is still booting, let it finish.
+ j = jiffies;
+ while (!torture_must_stop() && !rcu_inkernel_boot_has_ended()) {
+ booting_still = true;
+ schedule_timeout_interruptible(HZ);
+ }
+ if (booting_still)
+ pr_alert("%s" TORTURE_FLAG " Waited %lu jiffies for boot to complete.\n",
+ torture_type, jiffies - j);
+
+ do {
+ hazptr_torture_writer_state = HTWS_FIXED_DELAY;
+ torture_hrtimeout_us(500, 1000, &rand);
+ rp = hazptr_torture_alloc();
+ if (rp == NULL)
+ continue;
+ rp->htort_pipe_count = 0;
+ ASSERT_EXCLUSIVE_WRITER(rp->htort_pipe_count);
+ hazptr_torture_writer_state = HTWS_DELAY;
+ udelay(torture_random(&rand) & 0x3ff);
+ hazptr_torture_writer_state = HTWS_REPLACE;
+ old_rp = READ_ONCE(hazptr_torture_current);
+ smp_store_release(&hazptr_torture_current, rp);
+ smp_wmb(); /* Mods to old_rp must follow smp_store_release() */
+ if (old_rp) {
+ i = old_rp->htort_pipe_count;
+ if (i > HAZPTR_TORTURE_PIPE_LEN)
+ i = HAZPTR_TORTURE_PIPE_LEN;
+ atomic_inc(&hazptr_torture_wcount[i]);
+ WRITE_ONCE(old_rp->htort_pipe_count,
+ old_rp->htort_pipe_count + 1);
+ ASSERT_EXCLUSIVE_WRITER(old_rp->htort_pipe_count);
+
+ hazptr_torture_writer_state = HTWS_SYNC;
+ cur_ops->sync((void *)old_rp);
+ hazptr_torture_pipe_update(old_rp);
+ }
+
+ WRITE_ONCE(hazptr_torture_current_version, hazptr_torture_current_version + 1);
+ hazptr_torture_writer_state = HTWS_STUTTER;
+ stutter_waited = stutter_wait("hazptr_torture_writer");
+ if (stutter_waited && !torture_must_stop())
+ for (i = 0; i < ARRAY_SIZE(hazptr_tortures); i++)
+ if (list_empty(&hazptr_tortures[i].htort_free) &&
+ READ_ONCE(hazptr_torture_current) != &hazptr_tortures[i]) {
+ tracing_off();
+ WARN(1, "%s: htort_pipe_count: %d\n", __func__, hazptr_tortures[i].htort_pipe_count);
+ rcu_ftrace_dump(DUMP_ALL);
+ break;
+ }
+ if (stutter_waited)
+ sched_set_normal(current, oldnice);
+ } while (!torture_must_stop());
+ hazptr_torture_current = NULL; // Let stats task know that we are done.
+ hazptr_torture_writer_state = HTWS_STOPPING;
+ torture_kthread_stopping("hazptr_torture_writer");
+ return 0;
+}
+
+/*
+ * Hazard-pointer torture reader kthread. Repeatedly dereferences
+ * hazptr_torture_current, incrementing the corresponding element of the
+ * pipeline array. The counter in the element should never be greater
+ * than 1, otherwise, the hazard-pointer implementation is broken.
+ */
+static int hazptr_torture_reader(void *arg)
+{
+ struct hazptr_ctx *hcp;
+ struct hazptr_torture *htp;
+ unsigned long lastsleep = jiffies;
+ long myid = (long)arg;
+ int mynumonline = myid;
+ int pipe_count;
+ DEFINE_TORTURE_RANDOM(rand);
+
+ VERBOSE_TOROUT_STRING("hazptr_torture_reader task started");
+ set_user_nice(current, MAX_NICE);
+ do {
+ htp = cur_ops->readlock(&hcp);
+ if (!htp) {
+ schedule_timeout_interruptible(HZ / 10);
+ continue;
+ }
+ if (time_after(jiffies, lastsleep) && !torture_must_stop()) {
+ torture_hrtimeout_us(500, 1000, &rand);
+ lastsleep = jiffies + 10;
+ }
+ cur_ops->read_delay(&rand);
+ preempt_disable();
+ pipe_count = READ_ONCE(htp->htort_pipe_count);
+ if (pipe_count > HAZPTR_TORTURE_PIPE_LEN) {
+ // Should not happen in a correct RCU implementation,
+ // happens quite often for torture_type=busted.
+ pipe_count = HAZPTR_TORTURE_PIPE_LEN;
+ }
+ if (pipe_count > 1)
+ rcu_ftrace_dump(DUMP_ALL);
+ __this_cpu_inc(hazptr_torture_count[pipe_count]);
+ preempt_enable();
+ cur_ops->readunlock(hcp, htp);
+ while (!torture_must_stop() &&
+ (torture_num_online_cpus() < mynumonline || !rcu_inkernel_boot_has_ended()))
+ schedule_timeout_interruptible(HZ / 5);
+ stutter_wait("hazptr_torture_reader");
+ } while (!torture_must_stop());
+ torture_kthread_stopping("hazptr_torture_reader");
+ return 0;
+}
+
+/*
+ * Print torture statistics. Caller must ensure that there is only one
+ * call to this function at a given time!!! This is normally accomplished
+ * by relying on the module system to only have one copy of the module
+ * loaded, and then by giving the hazptr_torture_stats kthread full control
+ * (or the init/cleanup functions when hazptr_torture_stats thread is
+ * not running).
+ */
+static void
+hazptr_torture_stats_print(void)
+{
+ const char *cp = hazptr_torture_writer_state_getname();;
+ int cpu;
+ int i;
+ long pipesummary[HAZPTR_TORTURE_PIPE_LEN + 1] = { 0 };
+ long batchsummary[HAZPTR_TORTURE_PIPE_LEN + 1] = { 0 };
+ struct hazptr_torture *rtcp;
+ static unsigned long rtcv_snap = ULONG_MAX;
+ static bool splatted;
+ struct task_struct *wtp;
+
+ for_each_possible_cpu(cpu)
+ for (i = 0; i < HAZPTR_TORTURE_PIPE_LEN + 1; i++)
+ pipesummary[i] += READ_ONCE(per_cpu(hazptr_torture_count, cpu)[i]);
+ for (i = HAZPTR_TORTURE_PIPE_LEN; i >= 0; i--) {
+ if (pipesummary[i] != 0)
+ break;
+ } // The value of variable "i" is used later, so don't clobber it!
+
+ pr_alert("%s%s ", torture_type, TORTURE_FLAG);
+ rtcp = READ_ONCE(hazptr_torture_current);
+ pr_cont("rtc: %p %s: %lu %s tfle: %d rta: %d rtaf: %d rtf: %d ",
+ rtcp,
+ rtcp && !rcu_stall_is_suppressed_at_boot() ? "ver" : "VER",
+ hazptr_torture_current_version,
+ cp,
+ list_empty(&hazptr_torture_freelist),
+ atomic_read(&n_hazptr_torture_alloc),
+ atomic_read(&n_hazptr_torture_alloc_fail),
+ atomic_read(&n_hazptr_torture_free));
+ torture_onoff_stats();
+
+ pr_alert("%s%s ", torture_type, TORTURE_FLAG);
+ if (i > 1) {
+ pr_cont("%s", "!!! ");
+ atomic_inc(&n_hazptr_torture_error);
+ WARN_ON_ONCE(i > 1); // Too-short grace period
+ }
+ pr_cont("Reader Pipe: ");
+ for (i = 0; i < HAZPTR_TORTURE_PIPE_LEN + 1; i++)
+ pr_cont(" %ld", pipesummary[i]);
+ pr_cont("\n");
+
+ pr_alert("%s%s ", torture_type, TORTURE_FLAG);
+ pr_cont("Reader Batch: ");
+ for (i = 0; i < HAZPTR_TORTURE_PIPE_LEN + 1; i++)
+ pr_cont(" %ld", batchsummary[i]);
+ pr_cont("\n");
+
+ pr_alert("%s%s ", torture_type, TORTURE_FLAG);
+ pr_cont("Free-Block Circulation: ");
+ for (i = 0; i < HAZPTR_TORTURE_PIPE_LEN + 1; i++) {
+ pr_cont(" %d", atomic_read(&hazptr_torture_wcount[i]));
+ }
+ pr_cont("\n");
+
+ if (rtcv_snap == hazptr_torture_current_version &&
+ READ_ONCE(hazptr_torture_current) &&
+ rcu_inkernel_boot_has_ended()) {
+ int __maybe_unused flags = 0;
+ unsigned long __maybe_unused gp_seq = 0;
+
+ wtp = READ_ONCE(writer_task);
+ pr_alert("??? Writer stall state %s(%d) g%lu f%#x ->state %#x cpu %d\n",
+ hazptr_torture_writer_state_getname(),
+ hazptr_torture_writer_state, gp_seq, flags,
+ wtp == NULL ? ~0U : wtp->__state,
+ wtp == NULL ? -1 : (int)task_cpu(wtp));
+ if (!splatted && wtp) {
+ sched_show_task(wtp);
+ splatted = true;
+ }
+ rcu_ftrace_dump(DUMP_ALL);
+ }
+ rtcv_snap = hazptr_torture_current_version;
+}
+
+/*
+ * Periodically prints torture statistics, if periodic statistics printing
+ * was specified via the stat_interval module parameter.
+ */
+static int
+hazptr_torture_stats(void *arg)
+{
+ VERBOSE_TOROUT_STRING("hazptr_torture_stats task started");
+ do {
+ schedule_timeout_interruptible(stat_interval * HZ);
+ hazptr_torture_stats_print();
+ torture_shutdown_absorb("hazptr_torture_stats");
+ } while (!torture_must_stop());
+ torture_kthread_stopping("hazptr_torture_stats");
+ return 0;
+}
+
+static void
+hazptr_torture_print_module_parms(struct hazptr_torture_ops *cur_ops, const char *tag)
+{
+ pr_alert("%s" TORTURE_FLAG
+ "--- %s: nreaders=%d "
+ "stat_interval=%d verbose=%d "
+ "shuffle_interval=%d stutter=%d irqreader=%d "
+ "onoff_interval=%d onoff_holdoff=%d\n",
+ torture_type, tag, nrealreaders,
+ stat_interval, verbose,
+ shuffle_interval, stutter, irqreader,
+ onoff_interval, onoff_holdoff);
+}
+
+// Randomly preempt online CPUs.
+static int hazptr_torture_preempt(void *unused)
+{
+ int cpu = -1;
+ DEFINE_TORTURE_RANDOM(rand);
+
+ schedule_timeout_idle(onoff_holdoff * HZ);
+ do {
+ // Wait for preempt_interval ms with up to 100us fuzz.
+ torture_hrtimeout_ms(preempt_interval, 100, &rand);
+ // Select online CPU.
+ cpu = cpumask_next(cpu, cpu_online_mask);
+ if (cpu >= nr_cpu_ids)
+ cpu = cpumask_next(-1, cpu_online_mask);
+ WARN_ON_ONCE(cpu >= nr_cpu_ids);
+ // Move to that CPU, if can't do so, retry later.
+ if (torture_sched_setaffinity(current->pid, cpumask_of(cpu), false))
+ continue;
+ // Preempt at high-ish priority, then reset to normal.
+ sched_set_fifo(current);
+ torture_sched_setaffinity(current->pid, cpu_present_mask, true);
+ mdelay(preempt_duration);
+ sched_set_normal(current, 0);
+ stutter_wait("hazptr_torture_preempt");
+ } while (!torture_must_stop());
+ torture_kthread_stopping("hazptr_torture_preempt");
+ return 0;
+}
+
+static void
+hazptr_torture_cleanup(void)
+{
+ int i;
+
+ if (torture_cleanup_begin())
+ return;
+ if (!cur_ops) {
+ torture_cleanup_end();
+ return;
+ }
+
+ torture_stop_kthread(hazptr_torture_preempt, preempt_task);
+ torture_stop_kthread(hazptr_torture_writer, writer_task);
+
+ if (reader_tasks) {
+ for (i = 0; i < nrealreaders; i++)
+ torture_stop_kthread(hazptr_torture_reader,
+ reader_tasks[i]);
+ kfree(reader_tasks);
+ reader_tasks = NULL;
+ }
+
+ torture_stop_kthread(hazptr_torture_stats, stats_task);
+
+ /* Do torture-type-specific cleanup operations. */
+ if (cur_ops->cleanup != NULL)
+ cur_ops->cleanup();
+
+ hazptr_torture_stats_print(); /* -After- the stats thread is stopped! */
+ if (atomic_read(&n_hazptr_torture_error))
+ hazptr_torture_print_module_parms(cur_ops, "End of test: FAILURE");
+ else if (torture_onoff_failures())
+ hazptr_torture_print_module_parms(cur_ops, "End of test: HAZPTR_HOTPLUG");
+ else
+ hazptr_torture_print_module_parms(cur_ops, "End of test: SUCCESS");
+ torture_cleanup_end();
+}
+
+static int __init hazptr_torture_init(void)
+{
+ long i;
+ int cpu;
+ int firsterr = 0;
+ static struct hazptr_torture_ops *torture_ops[] = { &hazptr_ops, };
+
+ if (!torture_init_begin(torture_type, verbose))
+ return -EBUSY;
+
+ /* Process args and tell the world that the torturer is on the job. */
+ for (i = 0; i < ARRAY_SIZE(torture_ops); i++) {
+ cur_ops = torture_ops[i];
+ if (strcmp(torture_type, cur_ops->name) == 0)
+ break;
+ }
+ if (i == ARRAY_SIZE(torture_ops)) {
+ pr_alert("hazptr-torture: invalid torture type: \"%s\"\n", torture_type);
+ pr_alert("hazptr-torture types:");
+ for (i = 0; i < ARRAY_SIZE(torture_ops); i++)
+ pr_cont(" %s", torture_ops[i]->name);
+ pr_cont("\n");
+ firsterr = -EINVAL;
+ cur_ops = NULL;
+ goto unwind;
+ }
+
+ if (cur_ops->init)
+ cur_ops->init();
+
+ if (nreaders >= 0) {
+ nrealreaders = nreaders;
+ } else {
+ nrealreaders = num_online_cpus() - 2 - nreaders;
+ if (nrealreaders <= 0)
+ nrealreaders = 1;
+ }
+ hazptr_torture_print_module_parms(cur_ops, "Start of test");
+
+ /* Set up the freelist. */
+ INIT_LIST_HEAD(&hazptr_torture_freelist);
+ for (i = 0; i < ARRAY_SIZE(hazptr_tortures); i++)
+ list_add_tail(&hazptr_tortures[i].htort_free, &hazptr_torture_freelist);
+
+ /* Initialize the statistics so that each run gets its own numbers. */
+
+ hazptr_torture_current = NULL;
+ hazptr_torture_current_version = 0;
+ atomic_set(&n_hazptr_torture_alloc, 0);
+ atomic_set(&n_hazptr_torture_alloc_fail, 0);
+ atomic_set(&n_hazptr_torture_free, 0);
+ atomic_set(&n_hazptr_torture_error, 0);
+ for (i = 0; i < HAZPTR_TORTURE_PIPE_LEN + 1; i++)
+ atomic_set(&hazptr_torture_wcount[i], 0);
+ for_each_possible_cpu(cpu) {
+ for (i = 0; i < HAZPTR_TORTURE_PIPE_LEN + 1; i++)
+ per_cpu(hazptr_torture_count, cpu)[i] = 0;
+ }
+
+ /* Start up the kthreads. */
+
+ reader_tasks = kzalloc_objs(reader_tasks[0], nrealreaders);
+ for (i = 0; i < nrealreaders; i++) {
+ firsterr = torture_create_kthread(hazptr_torture_reader, (void *)i,
+ reader_tasks[i]);
+ if (torture_init_error(firsterr))
+ goto unwind;
+ }
+
+ firsterr = torture_create_kthread(hazptr_torture_writer, NULL, writer_task);
+ if (torture_init_error(firsterr))
+ goto unwind;
+
+ if (stat_interval > 0) {
+ firsterr = torture_create_kthread(hazptr_torture_stats, NULL, stats_task);
+ if (torture_init_error(firsterr))
+ goto unwind;
+ }
+ if (shuffle_interval > 0) {
+ firsterr = torture_shuffle_init(shuffle_interval * HZ);
+ if (torture_init_error(firsterr))
+ goto unwind;
+ }
+ if (stutter < 0)
+ stutter = 0;
+ if (stutter) {
+ int t;
+
+ t = stutter * HZ;
+ firsterr = torture_stutter_init(stutter * HZ, t);
+ if (torture_init_error(firsterr))
+ goto unwind;
+ }
+ firsterr = torture_shutdown_init(shutdown_secs, hazptr_torture_cleanup);
+ if (torture_init_error(firsterr))
+ goto unwind;
+ if (preempt_duration > 0) {
+ firsterr = torture_create_kthread(hazptr_torture_preempt, NULL, preempt_task);
+ if (torture_init_error(firsterr))
+ goto unwind;
+ }
+
+ torture_init_end();
+ return 0;
+
+unwind:
+ torture_init_end();
+ hazptr_torture_cleanup();
+ if (shutdown_secs) {
+ WARN_ON(!IS_MODULE(CONFIG_HAZPTR_TORTURE_TEST));
+ kernel_power_off();
+ }
+ return firsterr;
+}
+
+module_init(hazptr_torture_init);
+module_exit(hazptr_torture_cleanup);
diff --git a/kernel/rcu/update.c b/kernel/rcu/update.c
index b62735a6788423..2a778b8ab4ad78 100644
--- a/kernel/rcu/update.c
+++ b/kernel/rcu/update.c
@@ -44,6 +44,7 @@
#include <linux/slab.h>
#include <linux/irq_work.h>
#include <linux/rcupdate_trace.h>
+#include <linux/torture.h>
#define CREATE_TRACE_POINTS
@@ -525,7 +526,7 @@ EXPORT_SYMBOL_GPL(do_trace_rcu_torture_read);
do { } while (0)
#endif
-#if IS_ENABLED(CONFIG_RCU_TORTURE_TEST) || IS_MODULE(CONFIG_RCU_TORTURE_TEST) || IS_ENABLED(CONFIG_LOCK_TORTURE_TEST) || IS_MODULE(CONFIG_LOCK_TORTURE_TEST)
+#if IS_ENABLED(CONFIG_RCU_TORTURE_TEST) || IS_ENABLED(CONFIG_LOCK_TORTURE_TEST) || IS_ENABLED(CONFIG_HAZPTR_TORTURE_TEST)
/* Get rcutorture access to sched_setaffinity(). */
long torture_sched_setaffinity(pid_t pid, const struct cpumask *in_mask, bool dowarn)
{
diff --git a/tools/testing/selftests/rcutorture/bin/kvm.sh b/tools/testing/selftests/rcutorture/bin/kvm.sh
index dfb73a3461a6df..14570f5e3ce17b 100755
--- a/tools/testing/selftests/rcutorture/bin/kvm.sh
+++ b/tools/testing/selftests/rcutorture/bin/kvm.sh
@@ -91,7 +91,7 @@ usage () {
echo " --remote"
echo " --results absolute-pathname"
echo " --shutdown-grace seconds"
- echo " --torture lock|rcu|rcuscale|refscale|repro|scf|X*"
+ echo " --torture hazptr|lock|rcu|rcuscale|refscale|repro|scf|X*"
echo " --trust-make"
exit 1
}
@@ -256,9 +256,9 @@ do
shift
;;
--torture)
- checkarg --torture "(suite name)" "$#" "$2" '^\(lock\|rcu\|rcuscale\|refscale\|repro\|scf\|X.*\)$' '^--'
+ checkarg --torture "(suite name)" "$#" "$2" '^\(hazptr\|lock\|rcu\|rcuscale\|refscale\|repro\|scf\|X.*\)$' '^--'
TORTURE_SUITE=$2
- TORTURE_MOD="`echo $TORTURE_SUITE | sed -e 's/^\(lock\|rcu\|scf\)$/\1torture/'`"
+ TORTURE_MOD="`echo $TORTURE_SUITE | sed -e 's/^\(hazptr\|lock\|rcu\|scf\)$/\1torture/'`"
shift
if test "$TORTURE_SUITE" = rcuscale || test "$TORTURE_SUITE" = refscale
then
diff --git a/tools/testing/selftests/rcutorture/configs/hazptr/CFLIST b/tools/testing/selftests/rcutorture/configs/hazptr/CFLIST
new file mode 100644
index 00000000000000..4d62eb4a39f999
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/hazptr/CFLIST
@@ -0,0 +1,2 @@
+NOPREEMPT
+PREEMPT
diff --git a/tools/testing/selftests/rcutorture/configs/hazptr/CFcommon b/tools/testing/selftests/rcutorture/configs/hazptr/CFcommon
new file mode 100644
index 00000000000000..c440d227007dce
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/hazptr/CFcommon
@@ -0,0 +1,2 @@
+CONFIG_HAZPTR_TORTURE_TEST=y
+CONFIG_PRINTK_TIME=y
diff --git a/tools/testing/selftests/rcutorture/configs/hazptr/NOPREEMPT b/tools/testing/selftests/rcutorture/configs/hazptr/NOPREEMPT
new file mode 100644
index 00000000000000..e2da430abe4d70
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/hazptr/NOPREEMPT
@@ -0,0 +1,17 @@
+CONFIG_SMP=y
+CONFIG_NR_CPUS=16
+CONFIG_PREEMPT_LAZY=y
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+CONFIG_PREEMPT_DYNAMIC=n
+CONFIG_HZ_PERIODIC=n
+CONFIG_NO_HZ_IDLE=y
+CONFIG_NO_HZ_FULL=n
+CONFIG_HOTPLUG_CPU=y
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_DEBUG_LOCK_ALLOC=n
+CONFIG_PROVE_LOCKING=n
+CONFIG_KPROBES=n
+CONFIG_FTRACE=n
diff --git a/tools/testing/selftests/rcutorture/configs/hazptr/PREEMPT b/tools/testing/selftests/rcutorture/configs/hazptr/PREEMPT
new file mode 100644
index 00000000000000..b8ea4364b20b7b
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/hazptr/PREEMPT
@@ -0,0 +1,14 @@
+CONFIG_SMP=y
+CONFIG_NR_CPUS=16
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+CONFIG_HZ_PERIODIC=n
+CONFIG_NO_HZ_IDLE=y
+CONFIG_NO_HZ_FULL=n
+CONFIG_HOTPLUG_CPU=y
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_DEBUG_LOCK_ALLOC=n
+CONFIG_PROVE_LOCKING=n
+CONFIG_DEBUG_OBJECTS_RCU_HEAD=n
diff --git a/tools/testing/selftests/rcutorture/configs/hazptr/ver_functions.sh b/tools/testing/selftests/rcutorture/configs/hazptr/ver_functions.sh
new file mode 100644
index 00000000000000..a28ea2f292e453
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/hazptr/ver_functions.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0+
+#
+# Kernel-version-dependent shell functions for the rest of the scripts.
+#
+# Claude created this file, and I quote:
+#
+# "I created [this file] modeled on the lock torture
+# version. It defines per_version_boot_params to pass
+# hazptrtorture.shutdown_secs=$3, hazptrtorture.stat_interval=15,
+# hazptrtorture.verbose=1, and optional CPU-hotplug parameters to
+# the kernel command line."
+#
+# I therefore kept locktorture's ver_functions.sh copyright notice:
+#
+# Copyright (C) Meta Platforms, Inc. and affiliates.
+#
+# Authors: Paul E. McKenney <paulmck@kernel.org>
+
+# hazptrtorture_param_onoff bootparam-string config-file
+#
+# Adds onoff hazptrtorture module parameters to kernels having it.
+hazptrtorture_param_onoff () {
+ if ! bootparam_hotplug_cpu "$1" && configfrag_hotplug_cpu "$2"
+ then
+ echo CPU-hotplug kernel, adding hazptrtorture onoff. 1>&2
+ echo hazptrtorture.onoff_interval=3 hazptrtorture.onoff_holdoff=30
+ fi
+}
+
+# per_version_boot_params bootparam-string config-file seconds
+#
+# Adds per-version torture-module parameters to kernels supporting them.
+per_version_boot_params () {
+ echo `hazptrtorture_param_onoff "$1" "$2"` \
+ hazptrtorture.stat_interval=15 \
+ hazptrtorture.shutdown_secs=$3 \
+ hazptrtorture.verbose=1 \
+ $1
+}
--
2.40.1
next prev parent reply other threads:[~2026-05-07 16:51 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-07 16:50 [PATCH RFC 0/4] Hazard-pointer torture test Paul E. McKenney
2026-05-07 16:51 ` Paul E. McKenney [this message]
2026-05-07 16:51 ` [PATCH RFC 2/4] hazptrtorture: Add testing of on-stack hazptr_ctx structures Paul E. McKenney
2026-05-07 16:51 ` [PATCH RFC 3/4] hazptrtorture: Add microsecond-scale sleep in readers Paul E. McKenney
2026-05-07 16:51 ` [PATCH RFC 4/4] hazptrtorture: Enable system-independent CPU overcommit Paul E. McKenney
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260507165113.2039524-1-paulmck@kernel.org \
--to=paulmck@kernel.org \
--cc=kernel-team@meta.com \
--cc=linux-kernel@vger.kernel.org \
--cc=mathieu.desnoyers@efficios.com \
--cc=rcu@vger.kernel.org \
--cc=rostedt@goodmis.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox