1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Preempt / IRQ disable delay thread to test latency tracers |
4 | * |
5 | * Copyright (C) 2018 Joel Fernandes (Google) <joel@joelfernandes.org> |
6 | */ |
7 | |
8 | #include <linux/trace_clock.h> |
9 | #include <linux/delay.h> |
10 | #include <linux/interrupt.h> |
11 | #include <linux/irq.h> |
12 | #include <linux/kernel.h> |
13 | #include <linux/kobject.h> |
14 | #include <linux/kthread.h> |
15 | #include <linux/module.h> |
16 | #include <linux/printk.h> |
17 | #include <linux/string.h> |
18 | #include <linux/sysfs.h> |
19 | #include <linux/completion.h> |
20 | |
21 | static ulong delay = 100; |
22 | static char test_mode[12] = "irq" ; |
23 | static uint burst_size = 1; |
24 | static int cpu_affinity = -1; |
25 | |
26 | module_param_named(delay, delay, ulong, 0444); |
27 | module_param_string(test_mode, test_mode, 12, 0444); |
28 | module_param_named(burst_size, burst_size, uint, 0444); |
29 | module_param_named(cpu_affinity, cpu_affinity, int, 0444); |
30 | MODULE_PARM_DESC(delay, "Period in microseconds (100 us default)" ); |
31 | MODULE_PARM_DESC(test_mode, "Mode of the test such as preempt, irq, or alternate (default irq)" ); |
32 | MODULE_PARM_DESC(burst_size, "The size of a burst (default 1)" ); |
33 | MODULE_PARM_DESC(cpu_affinity, "Cpu num test is running on" ); |
34 | |
35 | static struct completion done; |
36 | |
37 | #define MIN(x, y) ((x) < (y) ? (x) : (y)) |
38 | |
39 | static void busy_wait(ulong time) |
40 | { |
41 | u64 start, end; |
42 | |
43 | start = trace_clock_local(); |
44 | |
45 | do { |
46 | end = trace_clock_local(); |
47 | if (kthread_should_stop()) |
48 | break; |
49 | } while ((end - start) < (time * 1000)); |
50 | } |
51 | |
52 | static __always_inline void irqoff_test(void) |
53 | { |
54 | unsigned long flags; |
55 | |
56 | local_irq_save(flags); |
57 | busy_wait(time: delay); |
58 | local_irq_restore(flags); |
59 | } |
60 | |
61 | static __always_inline void preemptoff_test(void) |
62 | { |
63 | preempt_disable(); |
64 | busy_wait(time: delay); |
65 | preempt_enable(); |
66 | } |
67 | |
68 | static void execute_preemptirqtest(int idx) |
69 | { |
70 | if (!strcmp(test_mode, "irq" )) |
71 | irqoff_test(); |
72 | else if (!strcmp(test_mode, "preempt" )) |
73 | preemptoff_test(); |
74 | else if (!strcmp(test_mode, "alternate" )) { |
75 | if (idx % 2 == 0) |
76 | irqoff_test(); |
77 | else |
78 | preemptoff_test(); |
79 | } |
80 | } |
81 | |
82 | #define DECLARE_TESTFN(POSTFIX) \ |
83 | static void preemptirqtest_##POSTFIX(int idx) \ |
84 | { \ |
85 | execute_preemptirqtest(idx); \ |
86 | } \ |
87 | |
88 | /* |
89 | * We create 10 different functions, so that we can get 10 different |
90 | * backtraces. |
91 | */ |
92 | DECLARE_TESTFN(0) |
93 | DECLARE_TESTFN(1) |
94 | DECLARE_TESTFN(2) |
95 | DECLARE_TESTFN(3) |
96 | DECLARE_TESTFN(4) |
97 | DECLARE_TESTFN(5) |
98 | DECLARE_TESTFN(6) |
99 | DECLARE_TESTFN(7) |
100 | DECLARE_TESTFN(8) |
101 | DECLARE_TESTFN(9) |
102 | |
103 | static void (*testfuncs[])(int) = { |
104 | preemptirqtest_0, |
105 | preemptirqtest_1, |
106 | preemptirqtest_2, |
107 | preemptirqtest_3, |
108 | preemptirqtest_4, |
109 | preemptirqtest_5, |
110 | preemptirqtest_6, |
111 | preemptirqtest_7, |
112 | preemptirqtest_8, |
113 | preemptirqtest_9, |
114 | }; |
115 | |
116 | #define NR_TEST_FUNCS ARRAY_SIZE(testfuncs) |
117 | |
118 | static int preemptirq_delay_run(void *data) |
119 | { |
120 | int i; |
121 | int s = MIN(burst_size, NR_TEST_FUNCS); |
122 | struct cpumask cpu_mask; |
123 | |
124 | if (cpu_affinity > -1) { |
125 | cpumask_clear(dstp: &cpu_mask); |
126 | cpumask_set_cpu(cpu: cpu_affinity, dstp: &cpu_mask); |
127 | if (set_cpus_allowed_ptr(current, new_mask: &cpu_mask)) |
128 | pr_err("cpu_affinity:%d, failed\n" , cpu_affinity); |
129 | } |
130 | |
131 | for (i = 0; i < s; i++) |
132 | (testfuncs[i])(i); |
133 | |
134 | complete(&done); |
135 | |
136 | set_current_state(TASK_INTERRUPTIBLE); |
137 | while (!kthread_should_stop()) { |
138 | schedule(); |
139 | set_current_state(TASK_INTERRUPTIBLE); |
140 | } |
141 | |
142 | __set_current_state(TASK_RUNNING); |
143 | |
144 | return 0; |
145 | } |
146 | |
147 | static int preemptirq_run_test(void) |
148 | { |
149 | struct task_struct *task; |
150 | char task_name[50]; |
151 | |
152 | init_completion(x: &done); |
153 | |
154 | snprintf(buf: task_name, size: sizeof(task_name), fmt: "%s_test" , test_mode); |
155 | task = kthread_run(preemptirq_delay_run, NULL, task_name); |
156 | if (IS_ERR(ptr: task)) |
157 | return PTR_ERR(ptr: task); |
158 | if (task) { |
159 | wait_for_completion(&done); |
160 | kthread_stop(k: task); |
161 | } |
162 | return 0; |
163 | } |
164 | |
165 | |
166 | static ssize_t trigger_store(struct kobject *kobj, struct kobj_attribute *attr, |
167 | const char *buf, size_t count) |
168 | { |
169 | ssize_t ret; |
170 | |
171 | ret = preemptirq_run_test(); |
172 | if (ret) |
173 | return ret; |
174 | return count; |
175 | } |
176 | |
177 | static struct kobj_attribute trigger_attribute = |
178 | __ATTR(trigger, 0200, NULL, trigger_store); |
179 | |
180 | static struct attribute *attrs[] = { |
181 | &trigger_attribute.attr, |
182 | NULL, |
183 | }; |
184 | |
185 | static struct attribute_group attr_group = { |
186 | .attrs = attrs, |
187 | }; |
188 | |
189 | static struct kobject *preemptirq_delay_kobj; |
190 | |
191 | static int __init preemptirq_delay_init(void) |
192 | { |
193 | int retval; |
194 | |
195 | retval = preemptirq_run_test(); |
196 | if (retval != 0) |
197 | return retval; |
198 | |
199 | preemptirq_delay_kobj = kobject_create_and_add(name: "preemptirq_delay_test" , |
200 | parent: kernel_kobj); |
201 | if (!preemptirq_delay_kobj) |
202 | return -ENOMEM; |
203 | |
204 | retval = sysfs_create_group(kobj: preemptirq_delay_kobj, grp: &attr_group); |
205 | if (retval) |
206 | kobject_put(kobj: preemptirq_delay_kobj); |
207 | |
208 | return retval; |
209 | } |
210 | |
211 | static void __exit preemptirq_delay_exit(void) |
212 | { |
213 | kobject_put(kobj: preemptirq_delay_kobj); |
214 | } |
215 | |
216 | module_init(preemptirq_delay_init) |
217 | module_exit(preemptirq_delay_exit) |
218 | MODULE_LICENSE("GPL v2" ); |
219 | |