From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from stravinsky.debian.org (stravinsky.debian.org [82.195.75.108]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 20A723C1D6E for ; Mon, 10 Aug 2026 11:31:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=82.195.75.108 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786361492; cv=none; b=Ww8bKSqXucALZ75IeYdX6wYFoWX8FQsTkqnYvnl5IBg0VdFXUNHAqjnQPL8sWrywIRfqcys3NpzGQFUgfM1OQYrt54ajaZR5N9gv5YwkclQr0p2gtGY/JDJloPs0g7C8dhMTGqBv/13jZbxSf2efNgUZXJcwqNQC85Tr+aOEXZA= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786361492; c=relaxed/simple; bh=oBQO/paOC3jfNxmP9QwllDlFbaL1M6c2lFtXhCl5NWE=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=L7piFTfuRVAA8+nIGG+BRnCFjMX67cLoCS712aSX8sn0m5hzTUjoM+M7QVxmv3FhOaJ8SbePV/7q6y8rYThQPxcy2OaSAZzrZGmwEtvOQ3O8e+bjA27to0GnJH9zJwKdEQ1erfE4XLPEwQCSq0bD+K6pgU0e/rfe6EPH/raPSVI= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=debian.org; spf=pass smtp.mailfrom=debian.org; dkim=pass (2048-bit key) header.d=debian.org header.i=@debian.org header.b=NWvzi6sA; arc=none smtp.client-ip=82.195.75.108 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=debian.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=debian.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=debian.org header.i=@debian.org header.b="NWvzi6sA" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=debian.org; s=smtpauto.stravinsky; h=X-Debian-User:Cc:To:In-Reply-To:References: Message-Id:Content-Transfer-Encoding:Content-Type:MIME-Version:Subject:Date: From:Reply-To:Content-ID:Content-Description; bh=zGnUzUyt/C6WSaBd30rtxolkfTV1jDrlJuBWJFZGKvA=; b=NWvzi6sArpfv/oVjTlWzn9Ufh0 A3E8VQEono3pO/7ZKNo3EgFx+hqKptBx6n5sSBWUS0ZmzKvNBg6ig/49ijYz2tDHX9IcEMHSu9IPQ A59qBgcs4jRFv3FaZVgCe4suVVkOLO9J5yX3M0rVwLaRD3MRAekZ0U90ENr5SNFNkg9hxsiYXXzds GXdNHk6opcAmCjs7syypvfjjfDw34l1CcOcuaIgEQR5Y59vteWPtyRRh0fduKohMl7Fch7REPvOMF 2QU9UPboOZz39XT01o6vMXxuFm2rxm040mvTLqRk3iNA+VckE5tYC4nBQC478PxRkYyIqog5COgps RFgLYnig==; Received: from authenticated-user by stravinsky.debian.org with esmtpsa (TLS1.3:ECDHE_X25519__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim 4.96) (envelope-from ) id 1wtOE2-002ifb-0Y; Mon, 10 Aug 2026 11:31:27 +0000 From: Breno Leitao Date: Mon, 10 Aug 2026 04:29:26 -0700 Subject: [PATCH v2 3/3] lib/test_csd_lock: Add a module to stall a CPU on a CSD lock Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Message-Id: <20260810-csd-stall-duration-v2-3-795083bf04a4@debian.org> References: <20260810-csd-stall-duration-v2-0-795083bf04a4@debian.org> In-Reply-To: <20260810-csd-stall-duration-v2-0-795083bf04a4@debian.org> To: paulmck@kernel.org, Andrew Morton , d@ilvokhin.com Cc: linux-kernel@vger.kernel.org, Peter Zijlstra , Ingo Molnar , Sebastian Andrzej Siewior , linux-kernel@vger.kernel.org, kernel-team@meta.com, Thomas Gleixner , Breno Leitao X-Mailer: b4 0.16-dev-f8e9d X-Developer-Signature: v=1; a=openpgp-sha256; l=7932; i=leitao@debian.org; h=from:subject:message-id; bh=oBQO/paOC3jfNxmP9QwllDlFbaL1M6c2lFtXhCl5NWE=; b=owEBbQKS/ZANAwAIATWjk5/8eHdtAcsmYgBqebZxdYDV1OyZZxOFaMpvBwHLh8MbbQoPZgFXc e9jgbg3WESJAjMEAAEIAB0WIQSshTmm6PRnAspKQ5s1o5Of/Hh3bQUCanm2cQAKCRA1o5Of/Hh3 bRH5D/9w0Ff1vB7zY5cgtI8g/rS0+rEAmvDR8bhil1OFCAPJ1s0X1Ppr5xhbUQmm1x+lL2NZzXi spOxJUqYG6PIXojEnjJmLZIDriCt+ZmiD9lcrCuS053p21lIaZvfVUCa0ip3us588lGP5bdrcOA XCmZuC3+tGIbVGAt0w73U8iVjf/E3JXHA+tFVQt20OpLh+8TH2+bEPvDqnAsNDnIdy6/hiyo8DM 6zYCHdDPQId4CPrHoFy4KK3L/dg38LRP+L2FKhmVxfat6THdkCEw+5idbYUtBAZ/O3dhQUAxeev DpyVeI0Dr0yUutlYTu0p1o+ICQ38QuG/s4TNsl+DpUoctfu1L8wtpE583cTE59JKGrtDl0gPVbY vMHxSM2tMKWOw4mAB3dw8no5Up10f31jj3VIxrwWqtswdcNqdxWWhDyqCgsnNczyqhB6Ey/LFAm hJQA0mT2JgTcLe6xclIPD9AxIRuaOXsd8QE+sYV6/Vbqt0W+BXrbi6ZIKTKFkYijJA2j16C99mq 0zncwnYpGHcdkMG+hEG/QKZpWs/bw67OZeRg/6iVabyveyBMw+8o9BkT0ZSYufqkqZp6W2wuBTi 3XQxYgTrFLpprT8Mhtu7vkyJkGSU59an/IBmxNO1ONssXFWhQZTXt78em4qK65cppe4+xfE2cNx HV8CTeXFC0mj0zQ== X-Developer-Key: i=leitao@debian.org; a=openpgp; fpr=AC8539A6E8F46702CA4A439B35A3939FFC78776D X-Debian-User: leitao 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 --- 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 + * + * 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 +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_DESCRIPTION("Test module to stall a CPU on a CSD lock"); -- 2.53.0-Meta