Skip to content

Commit 66067c3

Browse files
computersforpeaceKAGA-KOKO
authored andcommitted
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 788019e ("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 <[email protected]> Signed-off-by: Thomas Gleixner <[email protected]> Link: https://lore.kernel.org/all/[email protected]
1 parent 72218d7 commit 66067c3

File tree

3 files changed

+241
-0
lines changed

3 files changed

+241
-0
lines changed

kernel/irq/Kconfig

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,17 @@ config GENERIC_IRQ_DEBUGFS
144144
config GENERIC_IRQ_KEXEC_CLEAR_VM_FORWARD
145145
bool
146146

147+
config IRQ_KUNIT_TEST
148+
bool "KUnit tests for IRQ management APIs" if !KUNIT_ALL_TESTS
149+
depends on KUNIT=y
150+
default KUNIT_ALL_TESTS
151+
imply SMP
152+
help
153+
This option enables KUnit tests for the IRQ subsystem API. These are
154+
only for development and testing, not for regular kernel use cases.
155+
156+
If unsure, say N.
157+
147158
endmenu
148159

149160
config GENERIC_IRQ_MULTI_HANDLER

kernel/irq/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ obj-$(CONFIG_GENERIC_IRQ_IPI_MUX) += ipi-mux.o
1919
obj-$(CONFIG_SMP) += affinity.o
2020
obj-$(CONFIG_GENERIC_IRQ_DEBUGFS) += debugfs.o
2121
obj-$(CONFIG_GENERIC_IRQ_MATRIX_ALLOCATOR) += matrix.o
22+
obj-$(CONFIG_IRQ_KUNIT_TEST) += irq_test.o

kernel/irq/irq_test.c

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
// SPDX-License-Identifier: LGPL-2.1+
2+
3+
#include <linux/cpu.h>
4+
#include <linux/cpumask.h>
5+
#include <linux/interrupt.h>
6+
#include <linux/irq.h>
7+
#include <linux/irqdesc.h>
8+
#include <linux/irqdomain.h>
9+
#include <linux/nodemask.h>
10+
#include <kunit/test.h>
11+
12+
#include "internals.h"
13+
14+
static irqreturn_t noop_handler(int irq, void *data)
15+
{
16+
return IRQ_HANDLED;
17+
}
18+
19+
static void noop(struct irq_data *data) { }
20+
static unsigned int noop_ret(struct irq_data *data) { return 0; }
21+
22+
static int noop_affinity(struct irq_data *data, const struct cpumask *dest,
23+
bool force)
24+
{
25+
irq_data_update_effective_affinity(data, dest);
26+
27+
return 0;
28+
}
29+
30+
static struct irq_chip fake_irq_chip = {
31+
.name = "fake",
32+
.irq_startup = noop_ret,
33+
.irq_shutdown = noop,
34+
.irq_enable = noop,
35+
.irq_disable = noop,
36+
.irq_ack = noop,
37+
.irq_mask = noop,
38+
.irq_unmask = noop,
39+
.irq_set_affinity = noop_affinity,
40+
.flags = IRQCHIP_SKIP_SET_WAKE,
41+
};
42+
43+
static void irq_disable_depth_test(struct kunit *test)
44+
{
45+
struct irq_desc *desc;
46+
int virq, ret;
47+
48+
virq = irq_domain_alloc_descs(-1, 1, 0, NUMA_NO_NODE, NULL);
49+
KUNIT_ASSERT_GE(test, virq, 0);
50+
51+
irq_set_chip_and_handler(virq, &dummy_irq_chip, handle_simple_irq);
52+
53+
desc = irq_to_desc(virq);
54+
KUNIT_ASSERT_PTR_NE(test, desc, NULL);
55+
56+
ret = request_irq(virq, noop_handler, 0, "test_irq", NULL);
57+
KUNIT_EXPECT_EQ(test, ret, 0);
58+
59+
KUNIT_EXPECT_EQ(test, desc->depth, 0);
60+
61+
disable_irq(virq);
62+
KUNIT_EXPECT_EQ(test, desc->depth, 1);
63+
64+
enable_irq(virq);
65+
KUNIT_EXPECT_EQ(test, desc->depth, 0);
66+
67+
free_irq(virq, NULL);
68+
}
69+
70+
static void irq_free_disabled_test(struct kunit *test)
71+
{
72+
struct irq_desc *desc;
73+
int virq, ret;
74+
75+
virq = irq_domain_alloc_descs(-1, 1, 0, NUMA_NO_NODE, NULL);
76+
KUNIT_ASSERT_GE(test, virq, 0);
77+
78+
irq_set_chip_and_handler(virq, &dummy_irq_chip, handle_simple_irq);
79+
80+
desc = irq_to_desc(virq);
81+
KUNIT_ASSERT_PTR_NE(test, desc, NULL);
82+
83+
ret = request_irq(virq, noop_handler, 0, "test_irq", NULL);
84+
KUNIT_EXPECT_EQ(test, ret, 0);
85+
86+
KUNIT_EXPECT_EQ(test, desc->depth, 0);
87+
88+
disable_irq(virq);
89+
KUNIT_EXPECT_EQ(test, desc->depth, 1);
90+
91+
free_irq(virq, NULL);
92+
KUNIT_EXPECT_GE(test, desc->depth, 1);
93+
94+
ret = request_irq(virq, noop_handler, 0, "test_irq", NULL);
95+
KUNIT_EXPECT_EQ(test, ret, 0);
96+
KUNIT_EXPECT_EQ(test, desc->depth, 0);
97+
98+
free_irq(virq, NULL);
99+
}
100+
101+
static void irq_shutdown_depth_test(struct kunit *test)
102+
{
103+
struct irq_desc *desc;
104+
struct irq_data *data;
105+
int virq, ret;
106+
struct irq_affinity_desc affinity = {
107+
.is_managed = 1,
108+
.mask = CPU_MASK_ALL,
109+
};
110+
111+
if (!IS_ENABLED(CONFIG_SMP))
112+
kunit_skip(test, "requires CONFIG_SMP for managed shutdown");
113+
114+
virq = irq_domain_alloc_descs(-1, 1, 0, NUMA_NO_NODE, &affinity);
115+
KUNIT_ASSERT_GE(test, virq, 0);
116+
117+
irq_set_chip_and_handler(virq, &dummy_irq_chip, handle_simple_irq);
118+
119+
desc = irq_to_desc(virq);
120+
KUNIT_ASSERT_PTR_NE(test, desc, NULL);
121+
122+
data = irq_desc_get_irq_data(desc);
123+
KUNIT_ASSERT_PTR_NE(test, data, NULL);
124+
125+
ret = request_irq(virq, noop_handler, 0, "test_irq", NULL);
126+
KUNIT_EXPECT_EQ(test, ret, 0);
127+
128+
KUNIT_EXPECT_TRUE(test, irqd_is_activated(data));
129+
KUNIT_EXPECT_TRUE(test, irqd_is_started(data));
130+
KUNIT_EXPECT_TRUE(test, irqd_affinity_is_managed(data));
131+
132+
KUNIT_EXPECT_EQ(test, desc->depth, 0);
133+
134+
disable_irq(virq);
135+
KUNIT_EXPECT_EQ(test, desc->depth, 1);
136+
137+
irq_shutdown_and_deactivate(desc);
138+
139+
KUNIT_EXPECT_FALSE(test, irqd_is_activated(data));
140+
KUNIT_EXPECT_FALSE(test, irqd_is_started(data));
141+
142+
KUNIT_EXPECT_EQ(test, irq_activate(desc), 0);
143+
#ifdef CONFIG_SMP
144+
irq_startup_managed(desc);
145+
#endif
146+
147+
KUNIT_EXPECT_EQ(test, desc->depth, 1);
148+
149+
enable_irq(virq);
150+
KUNIT_EXPECT_EQ(test, desc->depth, 0);
151+
152+
free_irq(virq, NULL);
153+
}
154+
155+
static void irq_cpuhotplug_test(struct kunit *test)
156+
{
157+
struct irq_desc *desc;
158+
struct irq_data *data;
159+
int virq, ret;
160+
struct irq_affinity_desc affinity = {
161+
.is_managed = 1,
162+
};
163+
164+
if (!IS_ENABLED(CONFIG_SMP))
165+
kunit_skip(test, "requires CONFIG_SMP for CPU hotplug");
166+
if (!get_cpu_device(1))
167+
kunit_skip(test, "requires more than 1 CPU for CPU hotplug");
168+
if (!cpu_is_hotpluggable(1))
169+
kunit_skip(test, "CPU 1 must be hotpluggable");
170+
171+
cpumask_copy(&affinity.mask, cpumask_of(1));
172+
173+
virq = irq_domain_alloc_descs(-1, 1, 0, NUMA_NO_NODE, &affinity);
174+
KUNIT_ASSERT_GE(test, virq, 0);
175+
176+
irq_set_chip_and_handler(virq, &fake_irq_chip, handle_simple_irq);
177+
178+
desc = irq_to_desc(virq);
179+
KUNIT_ASSERT_PTR_NE(test, desc, NULL);
180+
181+
data = irq_desc_get_irq_data(desc);
182+
KUNIT_ASSERT_PTR_NE(test, data, NULL);
183+
184+
ret = request_irq(virq, noop_handler, 0, "test_irq", NULL);
185+
KUNIT_EXPECT_EQ(test, ret, 0);
186+
187+
KUNIT_EXPECT_TRUE(test, irqd_is_activated(data));
188+
KUNIT_EXPECT_TRUE(test, irqd_is_started(data));
189+
KUNIT_EXPECT_TRUE(test, irqd_affinity_is_managed(data));
190+
191+
KUNIT_EXPECT_EQ(test, desc->depth, 0);
192+
193+
disable_irq(virq);
194+
KUNIT_EXPECT_EQ(test, desc->depth, 1);
195+
196+
KUNIT_EXPECT_EQ(test, remove_cpu(1), 0);
197+
KUNIT_EXPECT_FALSE(test, irqd_is_activated(data));
198+
KUNIT_EXPECT_FALSE(test, irqd_is_started(data));
199+
KUNIT_EXPECT_GE(test, desc->depth, 1);
200+
KUNIT_EXPECT_EQ(test, add_cpu(1), 0);
201+
202+
KUNIT_EXPECT_FALSE(test, irqd_is_activated(data));
203+
KUNIT_EXPECT_FALSE(test, irqd_is_started(data));
204+
KUNIT_EXPECT_EQ(test, desc->depth, 1);
205+
206+
enable_irq(virq);
207+
KUNIT_EXPECT_TRUE(test, irqd_is_activated(data));
208+
KUNIT_EXPECT_TRUE(test, irqd_is_started(data));
209+
KUNIT_EXPECT_EQ(test, desc->depth, 0);
210+
211+
free_irq(virq, NULL);
212+
}
213+
214+
static struct kunit_case irq_test_cases[] = {
215+
KUNIT_CASE(irq_disable_depth_test),
216+
KUNIT_CASE(irq_free_disabled_test),
217+
KUNIT_CASE(irq_shutdown_depth_test),
218+
KUNIT_CASE(irq_cpuhotplug_test),
219+
{}
220+
};
221+
222+
static struct kunit_suite irq_test_suite = {
223+
.name = "irq_test_cases",
224+
.test_cases = irq_test_cases,
225+
};
226+
227+
kunit_test_suite(irq_test_suite);
228+
MODULE_DESCRIPTION("IRQ unit test suite");
229+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)