mirror of
https://github.com/torvalds/linux.git
synced 2025-08-15 14:11:42 +02:00
genirq: Add kunit tests for depth counts
There have been a few bugs and/or misunderstandings about the reference
counting, and startup/shutdown behaviors in the IRQ core and related CPU
hotplug code. These 4 test cases try to capture a few interesting cases.
* irq_disable_depth_test: basic request/disable/enable sequence
* irq_free_disabled_test: request/disable/free/re-request sequence -
this catches errors on previous revisions of my work
* irq_cpuhotplug_test: exercises managed-affinity IRQ + CPU hotplug.
This captures a problematic test case which was fixed recently.
This test requires CONFIG_SMP and a hotpluggable CPU#1.
* irq_shutdown_depth_test: exercises similar behavior from
irq_cpuhotplug_test, but directly using irq_*() APIs instead of going
through CPU hotplug. This still requires CONFIG_SMP, because
managed-affinity is stubbed out (and not all APIs are even present)
without it.
Note the use of 'imply SMP': ARCH=um doesn't support SMP, and kunit is
often exercised there. Thus, 'imply' will force SMP on where possible
(such as ARCH=x86_64), but leave it off where it's not.
Behavior on various SMP and ARCH configurations:
$ tools/testing/kunit/kunit.py run 'irq_test_cases*' --arch x86_64 --qemu_args '-smp 2'
[...]
[11:12:24] Testing complete. Ran 4 tests: passed: 4
$ tools/testing/kunit/kunit.py run 'irq_test_cases*' --arch x86_64
[...]
[11:13:27] [SKIPPED] irq_cpuhotplug_test
[11:13:27] ================= [PASSED] irq_test_cases ==================
[11:13:27] ============================================================
[11:13:27] Testing complete. Ran 4 tests: passed: 3, skipped: 1
# default: ARCH=um
$ tools/testing/kunit/kunit.py run 'irq_test_cases*'
[11:14:26] [SKIPPED] irq_shutdown_depth_test
[11:14:26] [SKIPPED] irq_cpuhotplug_test
[11:14:26] ================= [PASSED] irq_test_cases ==================
[11:14:26] ============================================================
[11:14:26] Testing complete. Ran 4 tests: passed: 2, skipped: 2
Without commit 788019eb55
("genirq: Retain disable depth for managed
interrupts across CPU hotplug"), this fails as follows:
[11:18:55] =============== irq_test_cases (4 subtests) ================
[11:18:55] [PASSED] irq_disable_depth_test
[11:18:55] [PASSED] irq_free_disabled_test
[11:18:55] # irq_shutdown_depth_test: EXPECTATION FAILED at kernel/irq/irq_test.c:147
[11:18:55] Expected desc->depth == 1, but
[11:18:55] desc->depth == 0 (0x0)
[11:18:55] ------------[ cut here ]------------
[11:18:55] Unbalanced enable for IRQ 26
[11:18:55] WARNING: CPU: 1 PID: 36 at kernel/irq/manage.c:792 __enable_irq+0x36/0x60
...
[11:18:55] [FAILED] irq_shutdown_depth_test
[11:18:55] #1
[11:18:55] # irq_cpuhotplug_test: EXPECTATION FAILED at kernel/irq/irq_test.c:202
[11:18:55] Expected irqd_is_activated(data) to be false, but is true
[11:18:55] # irq_cpuhotplug_test: EXPECTATION FAILED at kernel/irq/irq_test.c:203
[11:18:55] Expected irqd_is_started(data) to be false, but is true
[11:18:55] # irq_cpuhotplug_test: EXPECTATION FAILED at kernel/irq/irq_test.c:204
[11:18:55] Expected desc->depth == 1, but
[11:18:55] desc->depth == 0 (0x0)
[11:18:55] ------------[ cut here ]------------
[11:18:55] Unbalanced enable for IRQ 27
[11:18:55] WARNING: CPU: 0 PID: 38 at kernel/irq/manage.c:792 __enable_irq+0x36/0x60
...
[11:18:55] [FAILED] irq_cpuhotplug_test
[11:18:55] # module: irq_test
[11:18:55] # irq_test_cases: pass:2 fail:2 skip:0 total:4
[11:18:55] # Totals: pass:2 fail:2 skip:0 total:4
[11:18:55] ================= [FAILED] irq_test_cases ==================
[11:18:55] ============================================================
[11:18:55] Testing complete. Ran 4 tests: passed: 2, failed: 2
Signed-off-by: Brian Norris <briannorris@chromium.org>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Link: https://lore.kernel.org/all/20250522210837.4135244-1-briannorris@chromium.org
This commit is contained in:
parent
72218d74c9
commit
66067c3c8a
3 changed files with 241 additions and 0 deletions
|
@ -144,6 +144,17 @@ config GENERIC_IRQ_DEBUGFS
|
|||
config GENERIC_IRQ_KEXEC_CLEAR_VM_FORWARD
|
||||
bool
|
||||
|
||||
config IRQ_KUNIT_TEST
|
||||
bool "KUnit tests for IRQ management APIs" if !KUNIT_ALL_TESTS
|
||||
depends on KUNIT=y
|
||||
default KUNIT_ALL_TESTS
|
||||
imply SMP
|
||||
help
|
||||
This option enables KUnit tests for the IRQ subsystem API. These are
|
||||
only for development and testing, not for regular kernel use cases.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
endmenu
|
||||
|
||||
config GENERIC_IRQ_MULTI_HANDLER
|
||||
|
|
|
@ -19,3 +19,4 @@ obj-$(CONFIG_GENERIC_IRQ_IPI_MUX) += ipi-mux.o
|
|||
obj-$(CONFIG_SMP) += affinity.o
|
||||
obj-$(CONFIG_GENERIC_IRQ_DEBUGFS) += debugfs.o
|
||||
obj-$(CONFIG_GENERIC_IRQ_MATRIX_ALLOCATOR) += matrix.o
|
||||
obj-$(CONFIG_IRQ_KUNIT_TEST) += irq_test.o
|
||||
|
|
229
kernel/irq/irq_test.c
Normal file
229
kernel/irq/irq_test.c
Normal file
|
@ -0,0 +1,229 @@
|
|||
// SPDX-License-Identifier: LGPL-2.1+
|
||||
|
||||
#include <linux/cpu.h>
|
||||
#include <linux/cpumask.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqdesc.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/nodemask.h>
|
||||
#include <kunit/test.h>
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
static irqreturn_t noop_handler(int irq, void *data)
|
||||
{
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void noop(struct irq_data *data) { }
|
||||
static unsigned int noop_ret(struct irq_data *data) { return 0; }
|
||||
|
||||
static int noop_affinity(struct irq_data *data, const struct cpumask *dest,
|
||||
bool force)
|
||||
{
|
||||
irq_data_update_effective_affinity(data, dest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct irq_chip fake_irq_chip = {
|
||||
.name = "fake",
|
||||
.irq_startup = noop_ret,
|
||||
.irq_shutdown = noop,
|
||||
.irq_enable = noop,
|
||||
.irq_disable = noop,
|
||||
.irq_ack = noop,
|
||||
.irq_mask = noop,
|
||||
.irq_unmask = noop,
|
||||
.irq_set_affinity = noop_affinity,
|
||||
.flags = IRQCHIP_SKIP_SET_WAKE,
|
||||
};
|
||||
|
||||
static void irq_disable_depth_test(struct kunit *test)
|
||||
{
|
||||
struct irq_desc *desc;
|
||||
int virq, ret;
|
||||
|
||||
virq = irq_domain_alloc_descs(-1, 1, 0, NUMA_NO_NODE, NULL);
|
||||
KUNIT_ASSERT_GE(test, virq, 0);
|
||||
|
||||
irq_set_chip_and_handler(virq, &dummy_irq_chip, handle_simple_irq);
|
||||
|
||||
desc = irq_to_desc(virq);
|
||||
KUNIT_ASSERT_PTR_NE(test, desc, NULL);
|
||||
|
||||
ret = request_irq(virq, noop_handler, 0, "test_irq", NULL);
|
||||
KUNIT_EXPECT_EQ(test, ret, 0);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, desc->depth, 0);
|
||||
|
||||
disable_irq(virq);
|
||||
KUNIT_EXPECT_EQ(test, desc->depth, 1);
|
||||
|
||||
enable_irq(virq);
|
||||
KUNIT_EXPECT_EQ(test, desc->depth, 0);
|
||||
|
||||
free_irq(virq, NULL);
|
||||
}
|
||||
|
||||
static void irq_free_disabled_test(struct kunit *test)
|
||||
{
|
||||
struct irq_desc *desc;
|
||||
int virq, ret;
|
||||
|
||||
virq = irq_domain_alloc_descs(-1, 1, 0, NUMA_NO_NODE, NULL);
|
||||
KUNIT_ASSERT_GE(test, virq, 0);
|
||||
|
||||
irq_set_chip_and_handler(virq, &dummy_irq_chip, handle_simple_irq);
|
||||
|
||||
desc = irq_to_desc(virq);
|
||||
KUNIT_ASSERT_PTR_NE(test, desc, NULL);
|
||||
|
||||
ret = request_irq(virq, noop_handler, 0, "test_irq", NULL);
|
||||
KUNIT_EXPECT_EQ(test, ret, 0);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, desc->depth, 0);
|
||||
|
||||
disable_irq(virq);
|
||||
KUNIT_EXPECT_EQ(test, desc->depth, 1);
|
||||
|
||||
free_irq(virq, NULL);
|
||||
KUNIT_EXPECT_GE(test, desc->depth, 1);
|
||||
|
||||
ret = request_irq(virq, noop_handler, 0, "test_irq", NULL);
|
||||
KUNIT_EXPECT_EQ(test, ret, 0);
|
||||
KUNIT_EXPECT_EQ(test, desc->depth, 0);
|
||||
|
||||
free_irq(virq, NULL);
|
||||
}
|
||||
|
||||
static void irq_shutdown_depth_test(struct kunit *test)
|
||||
{
|
||||
struct irq_desc *desc;
|
||||
struct irq_data *data;
|
||||
int virq, ret;
|
||||
struct irq_affinity_desc affinity = {
|
||||
.is_managed = 1,
|
||||
.mask = CPU_MASK_ALL,
|
||||
};
|
||||
|
||||
if (!IS_ENABLED(CONFIG_SMP))
|
||||
kunit_skip(test, "requires CONFIG_SMP for managed shutdown");
|
||||
|
||||
virq = irq_domain_alloc_descs(-1, 1, 0, NUMA_NO_NODE, &affinity);
|
||||
KUNIT_ASSERT_GE(test, virq, 0);
|
||||
|
||||
irq_set_chip_and_handler(virq, &dummy_irq_chip, handle_simple_irq);
|
||||
|
||||
desc = irq_to_desc(virq);
|
||||
KUNIT_ASSERT_PTR_NE(test, desc, NULL);
|
||||
|
||||
data = irq_desc_get_irq_data(desc);
|
||||
KUNIT_ASSERT_PTR_NE(test, data, NULL);
|
||||
|
||||
ret = request_irq(virq, noop_handler, 0, "test_irq", NULL);
|
||||
KUNIT_EXPECT_EQ(test, ret, 0);
|
||||
|
||||
KUNIT_EXPECT_TRUE(test, irqd_is_activated(data));
|
||||
KUNIT_EXPECT_TRUE(test, irqd_is_started(data));
|
||||
KUNIT_EXPECT_TRUE(test, irqd_affinity_is_managed(data));
|
||||
|
||||
KUNIT_EXPECT_EQ(test, desc->depth, 0);
|
||||
|
||||
disable_irq(virq);
|
||||
KUNIT_EXPECT_EQ(test, desc->depth, 1);
|
||||
|
||||
irq_shutdown_and_deactivate(desc);
|
||||
|
||||
KUNIT_EXPECT_FALSE(test, irqd_is_activated(data));
|
||||
KUNIT_EXPECT_FALSE(test, irqd_is_started(data));
|
||||
|
||||
KUNIT_EXPECT_EQ(test, irq_activate(desc), 0);
|
||||
#ifdef CONFIG_SMP
|
||||
irq_startup_managed(desc);
|
||||
#endif
|
||||
|
||||
KUNIT_EXPECT_EQ(test, desc->depth, 1);
|
||||
|
||||
enable_irq(virq);
|
||||
KUNIT_EXPECT_EQ(test, desc->depth, 0);
|
||||
|
||||
free_irq(virq, NULL);
|
||||
}
|
||||
|
||||
static void irq_cpuhotplug_test(struct kunit *test)
|
||||
{
|
||||
struct irq_desc *desc;
|
||||
struct irq_data *data;
|
||||
int virq, ret;
|
||||
struct irq_affinity_desc affinity = {
|
||||
.is_managed = 1,
|
||||
};
|
||||
|
||||
if (!IS_ENABLED(CONFIG_SMP))
|
||||
kunit_skip(test, "requires CONFIG_SMP for CPU hotplug");
|
||||
if (!get_cpu_device(1))
|
||||
kunit_skip(test, "requires more than 1 CPU for CPU hotplug");
|
||||
if (!cpu_is_hotpluggable(1))
|
||||
kunit_skip(test, "CPU 1 must be hotpluggable");
|
||||
|
||||
cpumask_copy(&affinity.mask, cpumask_of(1));
|
||||
|
||||
virq = irq_domain_alloc_descs(-1, 1, 0, NUMA_NO_NODE, &affinity);
|
||||
KUNIT_ASSERT_GE(test, virq, 0);
|
||||
|
||||
irq_set_chip_and_handler(virq, &fake_irq_chip, handle_simple_irq);
|
||||
|
||||
desc = irq_to_desc(virq);
|
||||
KUNIT_ASSERT_PTR_NE(test, desc, NULL);
|
||||
|
||||
data = irq_desc_get_irq_data(desc);
|
||||
KUNIT_ASSERT_PTR_NE(test, data, NULL);
|
||||
|
||||
ret = request_irq(virq, noop_handler, 0, "test_irq", NULL);
|
||||
KUNIT_EXPECT_EQ(test, ret, 0);
|
||||
|
||||
KUNIT_EXPECT_TRUE(test, irqd_is_activated(data));
|
||||
KUNIT_EXPECT_TRUE(test, irqd_is_started(data));
|
||||
KUNIT_EXPECT_TRUE(test, irqd_affinity_is_managed(data));
|
||||
|
||||
KUNIT_EXPECT_EQ(test, desc->depth, 0);
|
||||
|
||||
disable_irq(virq);
|
||||
KUNIT_EXPECT_EQ(test, desc->depth, 1);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, remove_cpu(1), 0);
|
||||
KUNIT_EXPECT_FALSE(test, irqd_is_activated(data));
|
||||
KUNIT_EXPECT_FALSE(test, irqd_is_started(data));
|
||||
KUNIT_EXPECT_GE(test, desc->depth, 1);
|
||||
KUNIT_EXPECT_EQ(test, add_cpu(1), 0);
|
||||
|
||||
KUNIT_EXPECT_FALSE(test, irqd_is_activated(data));
|
||||
KUNIT_EXPECT_FALSE(test, irqd_is_started(data));
|
||||
KUNIT_EXPECT_EQ(test, desc->depth, 1);
|
||||
|
||||
enable_irq(virq);
|
||||
KUNIT_EXPECT_TRUE(test, irqd_is_activated(data));
|
||||
KUNIT_EXPECT_TRUE(test, irqd_is_started(data));
|
||||
KUNIT_EXPECT_EQ(test, desc->depth, 0);
|
||||
|
||||
free_irq(virq, NULL);
|
||||
}
|
||||
|
||||
static struct kunit_case irq_test_cases[] = {
|
||||
KUNIT_CASE(irq_disable_depth_test),
|
||||
KUNIT_CASE(irq_free_disabled_test),
|
||||
KUNIT_CASE(irq_shutdown_depth_test),
|
||||
KUNIT_CASE(irq_cpuhotplug_test),
|
||||
{}
|
||||
};
|
||||
|
||||
static struct kunit_suite irq_test_suite = {
|
||||
.name = "irq_test_cases",
|
||||
.test_cases = irq_test_cases,
|
||||
};
|
||||
|
||||
kunit_test_suite(irq_test_suite);
|
||||
MODULE_DESCRIPTION("IRQ unit test suite");
|
||||
MODULE_LICENSE("GPL");
|
Loading…
Add table
Add a link
Reference in a new issue