The Linux Kernel Mailing List
 help / color / mirror / Atom feed
From: Breno Leitao <leitao@debian.org>
To: paulmck@kernel.org, Andrew Morton <akpm@linux-foundation.org>,
	 d@ilvokhin.com
Cc: linux-kernel@vger.kernel.org,
	Peter Zijlstra <peterz@infradead.org>,
	 Ingo Molnar <mingo@kernel.org>,
	 Sebastian Andrzej Siewior <bigeasy@linutronix.de>,
	 linux-kernel@vger.kernel.org, kernel-team@meta.com,
	 Thomas Gleixner <tglx@kernel.org>,
	Breno Leitao <leitao@debian.org>
Subject: [PATCH v2 3/3] lib/test_csd_lock: Add a module to stall a CPU on a CSD lock
Date: Mon, 10 Aug 2026 04:29:26 -0700	[thread overview]
Message-ID: <20260810-csd-stall-duration-v2-3-795083bf04a4@debian.org> (raw)
In-Reply-To: <20260810-csd-stall-duration-v2-0-795083bf04a4@debian.org>

Add test_csd_lock, a module that keeps one CPU from answering an IPI for
as long as its stall_ms parameter says, so that the CSD-lock debug code
has a stall to report.  With in_handler=1 the CPU stalls inside a CSD
handler instead, which is the case where the IPI is not re-sent.

The module needs CONFIG_CSD_LOCK_WAIT_DEBUG and csdlock_debug=1.  Loading
it runs one stall and then fails the load with -EAGAIN, the way
test_lockup does, so that nothing is left loaded afterwards.

Signed-off-by: Breno Leitao <leitao@debian.org>
---
 lib/Kconfig.debug   |  12 ++++
 lib/Makefile        |   1 +
 lib/test_csd_lock.c | 173 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 186 insertions(+)

diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 1244dcac2294a..d693caaeae873 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -1372,6 +1372,18 @@ config WQ_CPU_INTENSIVE_REPORT
 	  triggering likely indicates that the work item should be switched
 	  to use an unbound workqueue.
 
+config TEST_CSD_LOCK
+	tristate "Test module to stall a CPU on a CSD lock"
+	depends on m
+	depends on CSD_LOCK_WAIT_DEBUG
+	help
+	  This builds the "test_csd_lock" module, which keeps one CPU from
+	  answering an IPI for as long as its stall_ms parameter says, so
+	  that the CSD-lock debug code has a stall to report.  It needs
+	  csdlock_debug=1 to be of any use.
+
+	  If unsure, say N.
+
 config TEST_LOCKUP
 	tristate "Test module to generate lockups"
 	depends on m
diff --git a/lib/Makefile b/lib/Makefile
index 7f75cc6edf94a..92f0ab7b740b4 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -99,6 +99,7 @@ obj-$(CONFIG_TEST_DEBUG_VIRTUAL) += test_debug_virtual.o
 obj-$(CONFIG_TEST_MEMCAT_P) += test_memcat_p.o
 obj-$(CONFIG_TEST_OBJAGG) += test_objagg.o
 obj-$(CONFIG_TEST_MEMINIT) += test_meminit.o
+obj-$(CONFIG_TEST_CSD_LOCK) += test_csd_lock.o
 obj-$(CONFIG_TEST_LOCKUP) += test_lockup.o
 obj-$(CONFIG_TEST_HMM) += test_hmm.o
 obj-$(CONFIG_TEST_FREE_PAGES) += test_free_pages.o
diff --git a/lib/test_csd_lock.c b/lib/test_csd_lock.c
new file mode 100644
index 0000000000000..30c6c3332c3fc
--- /dev/null
+++ b/lib/test_csd_lock.c
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Keep one CPU from answering an IPI, so that the CSD-lock debug code in
+ * kernel/smp.c has a stall to report.
+ *
+ * Copyright (c) 2026 Meta Platforms, Inc. and affiliates
+ * Copyright (c) 2026 Breno Leitao <leitao@debian.org>
+ *
+ * The target either spins with interrupts disabled, which leaves it idle as
+ * far as the debug code can tell and gets the IPI re-sent, or spins inside a
+ * CSD handler, which does not.  The recovery message differs between the two.
+ *
+ * Loading the module runs one stall, then fails the load with -EAGAIN so
+ * that nothing is left loaded afterwards:
+ *
+ *	echo 500 > /sys/module/smp/parameters/csd_lock_timeout
+ *	modprobe test_csd_lock stall_ms=1000 in_handler=0
+ *
+ * csd_lock_timeout has to be below stall_ms for the stall to be reported at
+ * all, and the report has to come out before the CPU answers, so leave it
+ * some room.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/completion.h>
+#include <linux/cpu.h>
+#include <linux/cpumask.h>
+#include <linux/ktime.h>
+#include <linux/module.h>
+#include <linux/smp.h>
+#include <linux/workqueue.h>
+
+#define STALL_MS_MAX	10000
+
+static unsigned int stall_ms = 1000;
+module_param(stall_ms, uint, 0444);
+MODULE_PARM_DESC(stall_ms, "Time the target CPU ignores the IPI, in milliseconds.");
+
+static int stall_cpu = -1;
+module_param(stall_cpu, int, 0444);
+MODULE_PARM_DESC(stall_cpu, "CPU to stall, or -1 for the first online one.");
+
+static bool in_handler;
+module_param(in_handler, bool, 0444);
+MODULE_PARM_DESC(in_handler, "Stall inside a CSD handler instead of with interrupts disabled.");
+
+static int target_cpu;
+static bool target_stalling;
+static bool hog_launched;
+static struct work_struct irqoff_work;
+static struct work_struct sender_work;
+static call_single_data_t hog_csd;
+static DECLARE_COMPLETION(hog_done);
+
+static void csd_test_nop(void *unused)
+{
+}
+
+static void csd_test_spin(void)
+{
+	u64 end = ktime_get_mono_fast_ns() + (u64)stall_ms * NSEC_PER_MSEC;
+
+	while (ktime_get_mono_fast_ns() < end)
+		cpu_relax();
+}
+
+/* Nothing is running for the target while interrupts are off, so it gets a new IPI. */
+static void csd_test_irqoff_fn(struct work_struct *work)
+{
+	local_irq_disable();
+	/* Pairs with the load in csd_test_sender_fn(), which waits for this. */
+	smp_store_release(&target_stalling, true);
+	csd_test_spin();
+	local_irq_enable();
+}
+
+/* Here cur_csd stays set on the target, which suppresses the re-send. */
+static void csd_test_hog_fn(void *unused)
+{
+	/* Pairs with the load in csd_test_sender_fn(), which waits for this. */
+	smp_store_release(&target_stalling, true);
+	csd_test_spin();
+	complete(&hog_done);
+}
+
+/*
+ * Start the stall from here rather than from module init, so that however
+ * long this work item waits to be scheduled comes off before the target
+ * stops answering, not out of the middle of the stall.
+ */
+static void csd_test_sender_fn(struct work_struct *work)
+{
+	u64 deadline, ts;
+	int err;
+
+	if (in_handler) {
+		hog_csd.func = csd_test_hog_fn;
+		err = smp_call_function_single_async(target_cpu, &hog_csd);
+		if (err) {
+			pr_err("cannot queue the CSD handler on CPU%d: %d\n", target_cpu, err);
+			return;
+		}
+	} else {
+		queue_work_on(target_cpu, system_highpri_wq, &irqoff_work);
+	}
+	WRITE_ONCE(hog_launched, true);
+
+	deadline = ktime_get_mono_fast_ns() + (u64)STALL_MS_MAX * NSEC_PER_MSEC;
+	/* Pairs with the store in the stall functions: send once it is stuck. */
+	while (!smp_load_acquire(&target_stalling)) {
+		if (ktime_get_mono_fast_ns() > deadline) {
+			pr_err("CPU%d never stopped answering\n", target_cpu);
+			return;
+		}
+		cpu_relax();
+	}
+
+	ts = ktime_get_mono_fast_ns();
+	smp_call_function_single(target_cpu, csd_test_nop, NULL, 1);
+	pr_info("CPU%d answered after %llu ns\n", target_cpu,
+		ktime_get_mono_fast_ns() - ts);
+}
+
+static int __init test_csd_lock_init(void)
+{
+	int sender_cpu;
+	int ret = 0;
+
+	if (!stall_ms || stall_ms > STALL_MS_MAX) {
+		pr_err("stall_ms must be between 1 and %d\n", STALL_MS_MAX);
+		return -EINVAL;
+	}
+
+	INIT_WORK(&irqoff_work, csd_test_irqoff_fn);
+	INIT_WORK(&sender_work, csd_test_sender_fn);
+
+	cpus_read_lock();
+
+	target_cpu = stall_cpu < 0 ? cpumask_first(cpu_online_mask) : stall_cpu;
+	sender_cpu = nr_cpu_ids;
+	if (target_cpu < nr_cpu_ids && cpu_online(target_cpu))
+		sender_cpu = cpumask_any_but(cpu_online_mask, target_cpu);
+	if (sender_cpu >= nr_cpu_ids) {
+		pr_err("need CPU%d and one other CPU online\n", target_cpu);
+		ret = -EINVAL;
+		goto unlock;
+	}
+
+	pr_info("stalling CPU%d for %u ms %s, IPI from CPU%d\n", target_cpu, stall_ms,
+		in_handler ? "inside a CSD handler" : "with interrupts disabled", sender_cpu);
+
+	queue_work_on(sender_cpu, system_highpri_wq, &sender_work);
+	flush_work(&sender_work);
+	flush_work(&irqoff_work);
+
+	/* The CSD has to be idle again before this module goes away. */
+	if (in_handler && READ_ONCE(hog_launched) &&
+	    !wait_for_completion_timeout(&hog_done, msecs_to_jiffies(2 * STALL_MS_MAX)))
+		pr_err("CSD handler on CPU%d never finished\n", target_cpu);
+
+	/* The stall is over and there is nothing left to hold, so go away. */
+	ret = -EAGAIN;
+unlock:
+	cpus_read_unlock();
+
+	return ret;
+}
+module_init(test_csd_lock_init);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Breno Leitao <leitao@debian.org>");
+MODULE_DESCRIPTION("Test module to stall a CPU on a CSD lock");

-- 
2.53.0-Meta


      parent reply	other threads:[~2026-08-10 11:31 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-10 11:29 [PATCH v2 0/3] locking/csd-lock: Report how long a CSD stall lasted Breno Leitao
2026-08-10 11:29 ` [PATCH v2 1/3] locking/csd-lock: Pack csd_lock_wait_toolong() state into a struct Breno Leitao
2026-08-10 14:32   ` Dmitry Ilvokhin
2026-08-10 11:29 ` [PATCH v2 2/3] locking/csd-lock: Report how long a stuck CSD lock took to recover Breno Leitao
2026-08-10 11:29 ` Breno Leitao [this message]

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=20260810-csd-stall-duration-v2-3-795083bf04a4@debian.org \
    --to=leitao@debian.org \
    --cc=akpm@linux-foundation.org \
    --cc=bigeasy@linutronix.de \
    --cc=d@ilvokhin.com \
    --cc=kernel-team@meta.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mingo@kernel.org \
    --cc=paulmck@kernel.org \
    --cc=peterz@infradead.org \
    --cc=tglx@kernel.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