Re: [PATCH 21/28] hazptrtorture: Add HOTADDR and LONGHOLD scenarios
From: Paul E. McKenney
Date: Tue Nov 10 2026 - 16:53:35 EST
On Thu, Nov 05, 2026 at 12:54:39AM +0800, Kunwu Chan wrote:
> HOTADDR: stages the sticky-address forward-progress scenario.
> LONGHOLD: validates legitimate long-held references.
>
> Signed-off-by: Kunwu Chan <kunwu.chan@xxxxxxxxx>
Continuing on the register_trace_*() point, one approach would be to make
the module parameters needing this to be under "#ifdef CONFIG_TRACING"
and provide no-op functions as needed.
Thanx, Paul
> ---
> kernel/rcu/hazptrtorture.c | 384 +++++++++++++++++-
> .../rcutorture/configs/hazptr/CFLIST | 2 +
> .../rcutorture/configs/hazptr/HOTADDR | 18 +
> .../rcutorture/configs/hazptr/HOTADDR.boot | 1 +
> .../rcutorture/configs/hazptr/LONGHOLD | 18 +
> .../rcutorture/configs/hazptr/LONGHOLD.boot | 1 +
> 6 files changed, 419 insertions(+), 5 deletions(-)
> create mode 100644 tools/testing/selftests/rcutorture/configs/hazptr/HOTADDR
> create mode 100644 tools/testing/selftests/rcutorture/configs/hazptr/HOTADDR.boot
> create mode 100644 tools/testing/selftests/rcutorture/configs/hazptr/LONGHOLD
> create mode 100644 tools/testing/selftests/rcutorture/configs/hazptr/LONGHOLD.boot
>
> diff --git a/kernel/rcu/hazptrtorture.c b/kernel/rcu/hazptrtorture.c
> index fe09280d063c..5dbe04af7515 100644
> --- a/kernel/rcu/hazptrtorture.c
> +++ b/kernel/rcu/hazptrtorture.c
> @@ -24,6 +24,7 @@
> #include <linux/hazptr.h>
> #include <linux/rcupdate.h>
> #include <linux/workqueue.h>
> +#include <trace/events/hazptr.h>
>
> #include "rcu.h"
>
> @@ -54,6 +55,15 @@ torture_param(int, stutter, 5, "Number of seconds to run/halt test");
> torture_param(int, verbose, 1, "Enable verbose debugging printk()s");
> torture_param(bool, wq_churn, false,
> "Churn workqueues to exercise lockdep dynamic keys");
> +torture_param(int, nhotreaders, 0,
> + "Hot-addr hammer readers, 0 disables Lai-scenario");
> +torture_param(int, hot_hold_us, 20000,
> + "Hammer reader hold (us)");
> +torture_param(int, hot_hammer_ms, 3000, "Hammer window (ms)");
> +torture_param(int, hot_longhold_ms, 0,
> + "Long-hold round duration (ms), 0 disables; R0 holds past the stall timeout, no hammer");
> +torture_param(bool, starve_expected, false,
> + "Starvation episodes are expected (documenting mode)");
>
> static char *torture_type = "hazptr";
> module_param(torture_type, charp, 0444);
> @@ -66,6 +76,22 @@ static struct task_struct **reader_tasks;
> static struct task_struct *do_pending_task;
> static struct task_struct *stats_task;
> static struct task_struct *wq_churn_task;
> +static struct task_struct *hot_stage_task;
> +static struct task_struct *hot_r0_task;
> +static struct task_struct **hot_reader_tasks;
> +static int hot_target_storage;
> +static void *hot_addr = &hot_target_storage;
> +static atomic_t n_hot_syncs;
> +static atomic_t n_hot_starves;
> +static atomic_t n_hot_in_seq;
> +static atomic_t hot_live_syncs; /* transient sync kthreads alive */
> +static atomic_t hot_hammer_holding; /* hammer readers currently holding */
> +static atomic_t hot_r0_gate; /* 1: R0 may acquire for this round */
> +static DECLARE_COMPLETION(hot_enqueued);
> +static DECLARE_COMPLETION(hot_sync_done);
> +static DECLARE_COMPLETION(r0_held);
> +static DECLARE_COMPLETION(r0_release);
> +static bool hot_hammer_active;
>
> #define HAZPTR_TORTURE_PIPE_LEN 10
>
> @@ -87,13 +113,13 @@ 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 atomic_t n_hazptr_torture_wq_churn;
> static DEFINE_PER_CPU(atomic_long_t, hazptr_torture_acquires);
> static DEFINE_PER_CPU(atomic_long_t, hazptr_torture_releases);
> static DEFINE_PER_CPU(atomic_long_t, hazptr_torture_acquires_irq);
> static DEFINE_PER_CPU(atomic_long_t, hazptr_torture_releases_irq);
> static DEFINE_PER_CPU(atomic_long_t, hazptr_torture_releases_defer);
> static DEFINE_PER_CPU(atomic_long_t, hazptr_torture_releases_undefer);
> +static atomic_t n_hazptr_torture_wq_churn;
> static struct list_head hazptr_torture_removed;
>
> // State for a deferred (AKA pending) hazard pointer
> @@ -681,6 +707,10 @@ hazptr_torture_stats_print(void)
> torture_sum_pcpu_atomic_long(&hazptr_torture_releases_defer),
> torture_sum_pcpu_atomic_long(&hazptr_torture_releases_undefer),
> atomic_read(&n_hazptr_torture_wq_churn));
> + pr_cont(" hotsync: %d hotstarve: %d hotseq: %d",
> + atomic_read(&n_hot_syncs),
> + atomic_read(&n_hot_starves),
> + atomic_read(&n_hot_in_seq));
>
> pr_alert("%s%s ", torture_type, TORTURE_FLAG);
> if (i > 1) {
> @@ -752,7 +782,8 @@ hazptr_torture_print_module_parms(struct hazptr_torture_ops *cur_ops, const char
> "nest_acquire=%d "
> "onoff_interval=%d onoff_holdoff=%d "
> "preempt_duration=%d preempt_interval=%d "
> - "reader_sleep_us=%d "
> + "reader_sleep_us=%d wq_churn=%d "
> + "nhotreaders=%d hot_hold_us=%d hot_hammer_ms=%d "
> "shuffle_interval=%d shutdown_secs=%d stat_interval=%d stutter=%d "
> "verbose=%d\n",
> torture_type, tag, nrealreaders, nwriters,
> @@ -760,7 +791,8 @@ hazptr_torture_print_module_parms(struct hazptr_torture_ops *cur_ops, const char
> nest_acquire,
> onoff_interval, onoff_holdoff,
> preempt_duration, preempt_interval,
> - reader_sleep_us,
> + reader_sleep_us, wq_churn,
> + nhotreaders, hot_hold_us, hot_hammer_ms,
> shuffle_interval, shutdown_secs, stat_interval, stutter,
> verbose);
> }
> @@ -801,10 +833,14 @@ static void hazptr_torture_wq_churn_fn(struct work_struct *work)
> /*
> * Repeatedly create and destroy workqueues. Each workqueue creation
> * registers a lockdep dynamic key and each destruction unregisters it,
> - * which exercises both is_dynamic_key() and lockdep_unregister_key().
> + * which exercises both is_dynamic_key() and lockdep_unregister_key(),
> + * the two lockdep paths that use hazptr. Most useful in kernels with
> + * CONFIG_PROVE_LOCKING=y and CONFIG_HAZPTR_ACQUIRE_FORCE_SLOWPATH=y.
> */
> static int hazptr_torture_wq_churn(void *unused)
> {
> + DEFINE_TORTURE_RANDOM(rand);
> +
> VERBOSE_TOROUT_STRING("hazptr_torture_wq_churn task started");
> do {
> struct workqueue_struct *wq;
> @@ -812,7 +848,7 @@ static int hazptr_torture_wq_churn(void *unused)
>
> wq = alloc_workqueue("hazptr-torture-churn", WQ_PERCPU, 1);
> if (!wq) {
> - torture_hrtimeout_ms(10, 10, NULL);
> + torture_hrtimeout_ms(10, 10, &rand);
> continue;
> }
> INIT_WORK(&churn_work, hazptr_torture_wq_churn_fn);
> @@ -826,6 +862,271 @@ static int hazptr_torture_wq_churn(void *unused)
> return 0;
> }
>
> +/*
> + * Hot-addr staged scenario --- Lai's forward-progress test.
> + *
> + * Three persistent kthreads plus a per-round transient sync kthread:
> + * hot_r0_task old reader: acquires hot_addr, signals, waits
> + * for release command, then releases.
> + * hot_reader_tasks hammer readers: tight acquire/release loops on
> + * hot_addr, long hold, near-zero gap, no stutter,
> + * kthread_bind'd to distinct CPUs.
> + * hot_stage_task orchestrator: drives the staged sequence round
> + * by round, recording starvation episodes.
> + * hot_do_sync transient per-round: hazptr_synchronize(hot_addr).
> + *
> + * A tracepoint on waiter-enqueue confirms the waiter is on
> + * hazptr_scan.pending before the orchestrator verifies the negative
> + * control (sync must NOT have completed while R0 holds).
> + */
> +
> +static void hot_probe_enqueue(void *__data, void *addr)
> +{
> + if (addr == hot_addr)
> + complete(&hot_enqueued);
> +}
> +
> +/* Per-round transient sync kthread. hot_live_syncs lets the
> + * orchestrator and cleanup wait for exit, so no completion event
> + * from one round can leak into the next.
> + */
> +static int hazptr_torture_hot_do_sync(void *unused)
> +{
> + hazptr_synchronize(hot_addr);
> + complete(&hot_sync_done);
> + atomic_dec(&hot_live_syncs);
> + return 0;
> +}
> +
> +/* R0: acquires hot_addr, signals orchestrator, waits for release cmd.
> + * The release wait is timeout-bounded so kthread_stop() at cleanup can
> + * always join; the hazard pointer is always released before exit.
> + * The one-shot gate ensures R0 does not re-acquire immediately after
> + * releasing: re-acquisition is reserved for a new round, so a draining
> + * waiter is never blocked by R0 itself.
> + */
> +static int hazptr_torture_hot_r0(void *unused)
> +{
> + struct hazptr_ctx ctx;
> + void *p;
> +
> + VERBOSE_TOROUT_STRING("hot_r0 task started");
> + do {
> + while (!atomic_read(&hot_r0_gate) && !torture_must_stop())
> + schedule_timeout_interruptible(1);
> + if (torture_must_stop())
> + break;
> + atomic_set(&hot_r0_gate, 0);
> + p = hot_addr;
> + (void)hazptr_acquire(&ctx, &p);
> + complete(&r0_held);
> + while (!wait_for_completion_timeout(&r0_release, HZ)) {
> + if (torture_must_stop())
> + break;
> + }
> + hazptr_release(&ctx, p);
> + } while (!torture_must_stop());
> + torture_kthread_stopping("hot_r0");
> + return 0;
> +}
> +
> +/* Hammer reader: parks while not hammering (hot_hammer_active false),
> + * so it persists across rounds. hot_hammer_holding counts live holds
> + * and lets the orchestrator hand off R0 only once a hammer reader
> + * actually holds the address -- no coverage gap at the seam.
> + */
> +static int hazptr_torture_hot_reader(void *arg)
> +{
> + int cpu = (long)arg;
> + struct hazptr_ctx ctx;
> + void *p;
> + DEFINE_TORTURE_RANDOM(rand);
> +
> + set_cpus_allowed_ptr(current, cpumask_of(cpu));
> + for (;;) {
> + while (!READ_ONCE(hot_hammer_active) && !torture_must_stop())
> + schedule_timeout_interruptible(1);
> + if (torture_must_stop())
> + break;
> + p = hot_addr;
> + p = hazptr_acquire(&ctx, &p);
> + atomic_inc(&hot_hammer_holding);
> + torture_hrtimeout_us(hot_hold_us, 100, &rand);
> + hazptr_release(&ctx, p);
> + atomic_dec(&hot_hammer_holding);
> + torture_hrtimeout_us(1, 1, &rand);
> + }
> + torture_kthread_stopping("hot_reader");
> + return 0;
> +}
> +
> +/*
> + * Orchestrator: one round per loop iteration. All waits are
> + * timeout-bounded so cleanup can always join this task.
> + *
> + * Phase Action
> + * boot Wait for R0's initial hold.
> + * P2 Launch transient sync kthread; wait for tracepoint
> + * confirmation (hot_enqueued).
> + * P3 Negative control (non-consuming completion_done()): sync
> + * must NOT be done while R0 still holds.
> + * P4 Handoff: start hammer, wait until a hammer reader holds
> + * (hot_hammer_holding > 0), then release R0 -- no gap.
> + * P5 Hammer window: poll sync for hot_hammer_ms.
> + * -> completes in window: n_hot_in_seq++.
> + * -> still pending: n_hot_starves++ (episode; counts
> + * as test error only when !starve_expected).
> + * P6 Positive control: stop hammer, sync must drain within 5 s
> + * (skipped when P5 already observed completion).
> + * bottom Drain this round's sync kthread (hot_live_syncs), wait for
> + * R0's next hold; jitter; loop unless must_stop.
> + */
> +static int hazptr_torture_hot_stage(void *unused)
> +{
> + unsigned long round = 0;
> + DEFINE_TORTURE_RANDOM(rand);
> +
> + VERBOSE_TOROUT_STRING("hot_stage task started");
> +
> + /* If the system is still booting, let it finish (same as the
> + * classic writer): a busy boot would make staged rounds record
> + * false starvation episodes and false stall splats. */
> + while (!torture_must_stop() && !rcu_inkernel_boot_has_ended())
> + schedule_timeout_interruptible(HZ);
> +
> + do {
> + u64 t_sync_start;
> + int done_in_window = 0, positive_done;
> + struct task_struct *sync_task;
> +
> + reinit_completion(&r0_held);
> + reinit_completion(&r0_release);
> + reinit_completion(&hot_sync_done);
> + reinit_completion(&hot_enqueued);
> + WRITE_ONCE(hot_hammer_active, false);
> +
> + /* Gate R0 into a fresh hold and wait for it. */
> + atomic_set(&hot_r0_gate, 1);
> + while (!wait_for_completion_timeout(&r0_held, HZ)) {
> + if (torture_must_stop())
> + goto out;
> + }
> +
> + /* P2: launch sync; confirm waiter enqueued. */
> + t_sync_start = ktime_get_ns();
> + atomic_inc(&hot_live_syncs);
> + sync_task = kthread_run(hazptr_torture_hot_do_sync, NULL,
> + "hot_do_sync");
> + if (IS_ERR(sync_task)) {
> + atomic_dec(&hot_live_syncs);
> + pr_alert("%shot: sync kthread alloc failed r%lu\n",
> + TORTURE_FLAG, round);
> + goto skip_round;
> + }
> + if (!wait_for_completion_timeout(&hot_enqueued, HZ)) {
> + pr_alert("%shot: enqueue not confirmed r%lu\n",
> + TORTURE_FLAG, round);
> + goto skip_round;
> + }
> +
> + /* P3 NEGATIVE CONTROL: sync must still be pending. */
> + if (completion_done(&hot_sync_done)) {
> + pr_alert("%shot: completed before R0 released r%lu\n",
> + TORTURE_FLAG, round);
> + atomic_inc(&n_hazptr_torture_error);
> + }
> +
> + /*
> + * LONGHOLD variant: no hammer. R0 legitimately holds
> + * past the stall timeout; the detector should report
> + * (observation, not interruption), and after release the
> + * sync must drain normally. Validates that long-lived
> + * references -- an hazptr feature -- do not break the
> + * machinery and are not treated as errors.
> + */
> + if (hot_longhold_ms) {
> + torture_hrtimeout_ms(hot_longhold_ms, 100, &rand);
> + complete(&r0_release);
> + done_in_window = wait_for_completion_timeout(
> + &hot_sync_done, 5 * HZ);
> + if (!done_in_window) {
> + pr_alert("%shot: longhold: sync did not drain after release r%lu\n",
> + TORTURE_FLAG, round);
> + atomic_inc(&n_hazptr_torture_error);
> + }
> + atomic_inc(done_in_window ? &n_hot_in_seq
> + : &n_hot_starves);
> + positive_done = done_in_window;
> + {
> + u64 dt = ktime_get_ns() - t_sync_start;
> +
> + atomic_inc(&n_hot_syncs);
> + pr_alert("%shot r%lu: longhold drain=%d dt_ns=%llu\n",
> + TORTURE_FLAG, round,
> + !!done_in_window, dt);
> + }
> + goto skip_round;
> + }
> +
> + /* P4: handoff -- hammer holds before R0 releases. */
> + WRITE_ONCE(hot_hammer_active, true);
> + while (!atomic_read(&hot_hammer_holding) &&
> + !torture_must_stop())
> + schedule_timeout_interruptible(1);
> + complete(&r0_release);
> +
> + /* P5 HAMMER WINDOW. */
> + done_in_window = wait_for_completion_timeout(
> + &hot_sync_done, msecs_to_jiffies(hot_hammer_ms));
> + if (done_in_window) {
> + atomic_inc(&n_hot_in_seq);
> + } else {
> + atomic_inc(&n_hot_starves);
> + if (!starve_expected)
> + atomic_inc(&n_hazptr_torture_error);
> + }
> +
> + /* P6 POSITIVE CONTROL. */
> + WRITE_ONCE(hot_hammer_active, false);
> + if (done_in_window) {
> + positive_done = 1;
> + } else {
> + positive_done = wait_for_completion_timeout(
> + &hot_sync_done, 5 * HZ);
> + if (!positive_done) {
> + pr_alert("%shot: positive control timed out r%lu (machinery fault?)\n",
> + TORTURE_FLAG, round);
> + atomic_inc(&n_hazptr_torture_error);
> + }
> + }
> +
> + {
> + u64 dt = ktime_get_ns() - t_sync_start;
> +
> + atomic_inc(&n_hot_syncs);
> + pr_alert("%shot r%lu: starve=%d in_seq=%d pos=%d dt_ns=%llu\n",
> + TORTURE_FLAG, round, !done_in_window,
> + !!done_in_window, positive_done, dt);
> + }
> +
> + skip_round:
> + /* Release R0 (harmless if already released) so an
> + * orphan sync from a skipped round can drain. */
> + complete(&r0_release);
> + /* Drain this round's sync kthread: no completion event
> + * may cross the round boundary. */
> + while (atomic_read(&hot_live_syncs) && !torture_must_stop())
> + schedule_timeout_interruptible(1);
> + /* R0 parks on the gate after its release; the next round
> + * opens the gate and waits for its fresh hold above. */
> + round++;
> + torture_hrtimeout_ms(500, 200, &rand);
> + } while (!torture_must_stop());
> +out:
> + torture_kthread_stopping("hot_stage");
> + return 0;
> +}
> +
> static void
> hazptr_torture_cleanup(void)
> {
> @@ -840,6 +1141,34 @@ hazptr_torture_cleanup(void)
>
> torture_stop_kthread(hazptr_torture_do_pending, do_pending_task);
> torture_stop_kthread(hazptr_torture_preempt, preempt_task);
> + torture_stop_kthread(hazptr_torture_wq_churn, wq_churn_task);
> +
> + /* Hot scenario: stop hammer readers first so pending syncs drain. */
> + if (nhotreaders > 0 || hot_longhold_ms > 0) {
> + int wait_ms = 5000;
> +
> + WRITE_ONCE(hot_hammer_active, false);
> + if (hot_reader_tasks) {
> + for (i = 0; i < nhotreaders; i++)
> + torture_stop_kthread(hazptr_torture_hot_reader,
> + hot_reader_tasks[i]);
> + kfree(hot_reader_tasks);
> + hot_reader_tasks = NULL;
> + }
> + torture_stop_kthread(hazptr_torture_hot_r0, hot_r0_task);
> + torture_stop_kthread(hazptr_torture_hot_stage, hot_stage_task);
> + /* Wait for any transient sync kthreads to exit so no
> + * module code is executing at unload. */
> + while (atomic_read(&hot_live_syncs) && wait_ms > 0) {
> + schedule_timeout_uninterruptible(msecs_to_jiffies(50));
> + wait_ms -= 50;
> + }
> + if (atomic_read(&hot_live_syncs))
> + pr_alert("%shot: %d sync kthread(s) still alive at exit\n",
> + TORTURE_FLAG, atomic_read(&hot_live_syncs));
> + unregister_trace_hazptr_waiter_enqueue(hot_probe_enqueue, NULL);
> + }
> +
> torture_stop_kthread(hazptr_torture_writer, writer_task);
>
> if (reader_tasks) {
> @@ -873,6 +1202,8 @@ static int __init hazptr_torture_init(void)
> int firsterr = 0;
> static struct hazptr_torture_ops *torture_ops[] = { &hazptr_ops, &hazptr_stack_ops, };
>
> + if (!strcmp(torture_type, "none"))
> + return 0;
> if (!torture_init_begin(torture_type, verbose))
> return -EBUSY;
>
> @@ -919,6 +1250,10 @@ static int __init hazptr_torture_init(void)
> atomic_set(&n_hazptr_torture_free, 0);
> atomic_set(&n_hazptr_torture_error, 0);
> atomic_set(&n_hazptr_torture_wq_churn, 0);
> + atomic_set(&n_hot_syncs, 0);
> + atomic_set(&n_hot_starves, 0);
> + atomic_set(&n_hot_in_seq, 0);
> + atomic_set(&hot_r0_gate, 0);
> for (i = 0; i < HAZPTR_TORTURE_PIPE_LEN + 1; i++)
> atomic_set(&hazptr_torture_wcount[i], 0);
> for_each_possible_cpu(cpu) {
> @@ -1004,6 +1339,45 @@ static int __init hazptr_torture_init(void)
> if (torture_init_error(firsterr))
> goto unwind;
> }
> + if (nhotreaders > 0 || hot_longhold_ms > 0) {
> + int cpu = 0, j;
> +
> + firsterr = register_trace_hazptr_waiter_enqueue(hot_probe_enqueue, NULL);
> + if (firsterr) {
> + pr_alert("%shot: tracepoint registration failed: %d\n",
> + TORTURE_FLAG, firsterr);
> + goto unwind;
> + }
> +
> + if (nhotreaders > 0) {
> + hot_reader_tasks = kcalloc(nhotreaders,
> + sizeof(hot_reader_tasks[0]),
> + GFP_KERNEL);
> + if (!hot_reader_tasks) {
> + firsterr = -ENOMEM;
> + goto unwind;
> + }
> + for (j = 0; j < nhotreaders; j++) {
> + cpu = cpumask_next(cpu % num_online_cpus(),
> + cpu_online_mask);
> + if (cpu >= nr_cpu_ids)
> + cpu = cpumask_first(cpu_online_mask);
> + firsterr = torture_create_kthread(
> + hazptr_torture_hot_reader,
> + (void *)(long)cpu, hot_reader_tasks[j]);
> + if (torture_init_error(firsterr))
> + goto unwind;
> + }
> + }
> + firsterr = torture_create_kthread(hazptr_torture_hot_r0, NULL,
> + hot_r0_task);
> + if (torture_init_error(firsterr))
> + goto unwind;
> + firsterr = torture_create_kthread(hazptr_torture_hot_stage,
> + NULL, hot_stage_task);
> + if (torture_init_error(firsterr))
> + goto unwind;
> + }
>
> torture_init_end();
> return 0;
> diff --git a/tools/testing/selftests/rcutorture/configs/hazptr/CFLIST b/tools/testing/selftests/rcutorture/configs/hazptr/CFLIST
> index 90ef09b0e654..b3f6137ce8ab 100644
> --- a/tools/testing/selftests/rcutorture/configs/hazptr/CFLIST
> +++ b/tools/testing/selftests/rcutorture/configs/hazptr/CFLIST
> @@ -6,3 +6,5 @@ SLOWPATH
> LOCKDEP
> CPU128
> CPU256
> +HOTADDR
> +LONGHOLD
> diff --git a/tools/testing/selftests/rcutorture/configs/hazptr/HOTADDR b/tools/testing/selftests/rcutorture/configs/hazptr/HOTADDR
> new file mode 100644
> index 000000000000..6f4d6746b63c
> --- /dev/null
> +++ b/tools/testing/selftests/rcutorture/configs/hazptr/HOTADDR
> @@ -0,0 +1,18 @@
> +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_PROVE_LOCKING=n
> +CONFIG_DEBUG_LOCK_ALLOC=n
> +CONFIG_DEBUG_OBJECTS_RCU_HEAD=n
> +CONFIG_DEBUG_KERNEL=y
> +CONFIG_HAZPTR_DEBUG=y
> +CONFIG_HAZPTR_STALL_DETECTOR=y
> +CONFIG_HAZPTR_STALL_TIMEOUT=1000
> diff --git a/tools/testing/selftests/rcutorture/configs/hazptr/HOTADDR.boot b/tools/testing/selftests/rcutorture/configs/hazptr/HOTADDR.boot
> new file mode 100644
> index 000000000000..878b588e2e39
> --- /dev/null
> +++ b/tools/testing/selftests/rcutorture/configs/hazptr/HOTADDR.boot
> @@ -0,0 +1 @@
> +hazptrtorture.nreaders=0 hazptrtorture.nwriters=0 hazptrtorture.nhotreaders=4 hazptrtorture.hot_hold_us=20000 hazptrtorture.starve_expected=1 hazptrtorture.shutdown_secs=20
> diff --git a/tools/testing/selftests/rcutorture/configs/hazptr/LONGHOLD b/tools/testing/selftests/rcutorture/configs/hazptr/LONGHOLD
> new file mode 100644
> index 000000000000..fe293638bbe2
> --- /dev/null
> +++ b/tools/testing/selftests/rcutorture/configs/hazptr/LONGHOLD
> @@ -0,0 +1,18 @@
> +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_PROVE_LOCKING=n
> +CONFIG_DEBUG_LOCK_ALLOC=n
> +CONFIG_DEBUG_OBJECTS_RCU_HEAD=n
> +CONFIG_DEBUG_KERNEL=y
> +CONFIG_HAZPTR_DEBUG=y
> +CONFIG_HAZPTR_STALL_DETECTOR=y
> +CONFIG_HAZPTR_STALL_TIMEOUT=2000
> diff --git a/tools/testing/selftests/rcutorture/configs/hazptr/LONGHOLD.boot b/tools/testing/selftests/rcutorture/configs/hazptr/LONGHOLD.boot
> new file mode 100644
> index 000000000000..cbe30b6a09cc
> --- /dev/null
> +++ b/tools/testing/selftests/rcutorture/configs/hazptr/LONGHOLD.boot
> @@ -0,0 +1 @@
> +hazptrtorture.nreaders=0 hazptrtorture.nwriters=0 hazptrtorture.hot_longhold_ms=8000 hazptrtorture.shutdown_secs=40
> --
> 2.43.0
>