1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
|
// SPDX-License-Identifier: GPL-2.0
#include <linux/cpuhplock.h>
#include <linux/cpumask.h>
#include <linux/init.h>
#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/random.h>
#include <linux/slab.h>
#include <linux/timer.h>
#include <linux/wait.h>
#include <kunit/resource.h>
#include <kunit/test.h>
#include "printk_ringbuffer.h"
/*
* This KUnit tests the data integrity of the lockless printk_ringbuffer.
* From multiple CPUs it writes messages of varying length and content while
* a reader validates the correctness of the messages.
*
* IMPORTANT: The more CPUs you can use for this KUnit, the better!
*
* The test works by starting "num_online_cpus() - 1" writer threads, each
* pinned to their own CPU. Each writer thread loops, writing data of varying
* length into a printk_ringbuffer as fast as possible. The data content is
* an embedded data struct followed by string content repeating the byte:
*
* 'A' + CPUID
*
* The reader is running on the remaining online CPU, or if there is only one
* CPU on the same as the writer.
* It ensures that the embedded struct content is consistent with the string
* and that the string * is terminated and is composed of the same repeating
* byte as its first byte.
*
* Because the threads are running in such tight loops, they will call
* cond_resched() from time to time so the system stays functional.
*
* If the reader encounters an error, the test is aborted and some
* information about the error is reported.
* The runtime of the test can be configured with the runtime_ms module parameter.
*
* Note that the test is performed on a separate printk_ringbuffer instance
* and not the instance used by printk().
*/
static unsigned long runtime_ms = 10 * MSEC_PER_SEC;
module_param(runtime_ms, ulong, 0400);
/* test data structure */
struct prbtest_rbdata {
unsigned int size;
char text[] __counted_by(size);
};
#define MAX_RBDATA_TEXT_SIZE 0x80
#define MAX_PRB_RECORD_SIZE (sizeof(struct prbtest_rbdata) + MAX_RBDATA_TEXT_SIZE)
struct prbtest_data {
struct kunit *test;
struct printk_ringbuffer *ringbuffer;
/* used by writers to signal reader of new records */
wait_queue_head_t new_record_wait;
};
struct prbtest_thread_data {
unsigned long num;
struct prbtest_data *test_data;
};
static void prbtest_fail_record(struct kunit *test, const struct prbtest_rbdata *dat, u64 seq)
{
unsigned int len;
len = dat->size - 1;
KUNIT_FAIL(test, "BAD RECORD: seq=%llu size=%u text=%.*s\n",
seq, dat->size,
len < MAX_RBDATA_TEXT_SIZE ? len : -1,
len < MAX_RBDATA_TEXT_SIZE ? dat->text : "<invalid>");
}
static bool prbtest_check_data(const struct prbtest_rbdata *dat)
{
unsigned int len;
/* Sane size? At least one character + trailing '\0' */
if (dat->size < 2 || dat->size > MAX_RBDATA_TEXT_SIZE)
return false;
len = dat->size - 1;
if (dat->text[len] != '\0')
return false;
/* String repeats with the same character? */
while (len--) {
if (dat->text[len] != dat->text[0])
return false;
}
return true;
}
static int prbtest_writer(void *data)
{
struct prbtest_thread_data *tr = data;
char text_id = 'A' + tr->num;
struct prb_reserved_entry e;
struct prbtest_rbdata *dat;
u32 record_size, text_size;
unsigned long count = 0;
struct printk_record r;
kunit_info(tr->test_data->test, "start thread %03lu (writer)\n", tr->num);
for (;;) {
/* ensure at least 1 character + trailing '\0' */
text_size = get_random_u32_inclusive(2, MAX_RBDATA_TEXT_SIZE);
if (WARN_ON_ONCE(text_size < 2))
text_size = 2;
if (WARN_ON_ONCE(text_size > MAX_RBDATA_TEXT_SIZE))
text_size = MAX_RBDATA_TEXT_SIZE;
record_size = sizeof(struct prbtest_rbdata) + text_size;
WARN_ON_ONCE(record_size > MAX_PRB_RECORD_SIZE);
/* specify the text sizes for reservation */
prb_rec_init_wr(&r, record_size);
/*
* Reservation can fail if:
*
* - No free descriptor is available.
* - The buffer is full, and the oldest record is reserved
* but not yet committed.
*
* It actually happens in this test because all CPUs are trying
* to write an unbounded number of messages in a tight loop.
* These failures are intentionally ignored because this test
* focuses on races, ringbuffer consistency, and pushing system
* usability limits.
*/
if (prb_reserve(&e, tr->test_data->ringbuffer, &r)) {
r.info->text_len = record_size;
dat = (struct prbtest_rbdata *)r.text_buf;
dat->size = text_size;
memset(dat->text, text_id, text_size - 1);
dat->text[text_size - 1] = '\0';
prb_commit(&e);
wake_up_interruptible(&tr->test_data->new_record_wait);
}
if ((count++ & 0x3fff) == 0)
cond_resched();
if (kthread_should_stop())
break;
}
kunit_info(tr->test_data->test, "end thread %03lu: wrote=%lu\n", tr->num, count);
return 0;
}
struct prbtest_wakeup_timer {
struct timer_list timer;
struct task_struct *task;
};
static void prbtest_wakeup_callback(struct timer_list *timer)
{
struct prbtest_wakeup_timer *wakeup = timer_container_of(wakeup, timer, timer);
set_tsk_thread_flag(wakeup->task, TIF_NOTIFY_SIGNAL);
wake_up_process(wakeup->task);
}
static int prbtest_reader(struct prbtest_data *test_data, unsigned long timeout_ms)
{
struct prbtest_wakeup_timer wakeup;
char text_buf[MAX_PRB_RECORD_SIZE];
unsigned long count = 0;
struct printk_info info;
struct printk_record r;
u64 seq = 0;
wakeup.task = current;
timer_setup_on_stack(&wakeup.timer, prbtest_wakeup_callback, 0);
mod_timer(&wakeup.timer, jiffies + msecs_to_jiffies(timeout_ms));
prb_rec_init_rd(&r, &info, text_buf, sizeof(text_buf));
kunit_info(test_data->test, "start reader\n");
while (!wait_event_interruptible(test_data->new_record_wait,
prb_read_valid(test_data->ringbuffer, seq, &r))) {
/* check/track the sequence */
if (info.seq < seq)
KUNIT_FAIL(test_data->test, "BAD SEQ READ: request=%llu read=%llu\n",
seq, info.seq);
if (!prbtest_check_data((struct prbtest_rbdata *)r.text_buf))
prbtest_fail_record(test_data->test,
(struct prbtest_rbdata *)r.text_buf, info.seq);
if ((count++ & 0x3fff) == 0)
cond_resched();
seq = info.seq + 1;
}
timer_delete_sync(&wakeup.timer);
timer_destroy_on_stack(&wakeup.timer);
kunit_info(test_data->test, "end reader: read=%lu seq=%llu\n", count, info.seq);
return 0;
}
KUNIT_DEFINE_ACTION_WRAPPER(prbtest_cpumask_cleanup, free_cpumask_var, struct cpumask *);
KUNIT_DEFINE_ACTION_WRAPPER(prbtest_kthread_cleanup, kthread_stop, struct task_struct *);
static void prbtest_add_cpumask_cleanup(struct kunit *test, cpumask_var_t mask)
{
int err;
err = kunit_add_action_or_reset(test, prbtest_cpumask_cleanup, mask);
KUNIT_ASSERT_EQ(test, err, 0);
}
static void prbtest_add_kthread_cleanup(struct kunit *test, struct task_struct *kthread)
{
int err;
err = kunit_add_action_or_reset(test, prbtest_kthread_cleanup, kthread);
KUNIT_ASSERT_EQ(test, err, 0);
}
static inline void prbtest_prb_reinit(struct printk_ringbuffer *rb)
{
prb_init(rb, rb->text_data_ring.data, rb->text_data_ring.size_bits, rb->desc_ring.descs,
rb->desc_ring.count_bits, rb->desc_ring.infos);
}
static void test_readerwriter(struct kunit *test)
{
/* Equivalent to CONFIG_LOG_BUF_SHIFT=13 */
DEFINE_PRINTKRB(test_rb, 8, 5);
struct prbtest_thread_data *thread_data;
struct prbtest_data *test_data;
struct task_struct *thread;
cpumask_var_t test_cpus;
int cpu, reader_cpu;
KUNIT_ASSERT_TRUE(test, alloc_cpumask_var(&test_cpus, GFP_KERNEL));
prbtest_add_cpumask_cleanup(test, test_cpus);
cpus_read_lock();
/*
* Failure of KUNIT_ASSERT() kills the current task
* so it can not be called while the CPU hotplug lock is held.
* Instead use a snapshot of the online CPUs.
* If they change during test execution it is unfortunate but not a grave error.
*/
cpumask_copy(test_cpus, cpu_online_mask);
cpus_read_unlock();
/* One CPU is for the reader, all others are writers */
reader_cpu = cpumask_first(test_cpus);
if (cpumask_weight(test_cpus) == 1)
kunit_warn(test, "more than one CPU is recommended");
else
cpumask_clear_cpu(reader_cpu, test_cpus);
/* KUnit test can get restarted more times. */
prbtest_prb_reinit(&test_rb);
test_data = kunit_kmalloc(test, sizeof(*test_data), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, test_data);
test_data->test = test;
test_data->ringbuffer = &test_rb;
init_waitqueue_head(&test_data->new_record_wait);
kunit_info(test, "running for %lu ms\n", runtime_ms);
for_each_cpu(cpu, test_cpus) {
thread_data = kunit_kmalloc(test, sizeof(*thread_data), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, thread_data);
thread_data->test_data = test_data;
thread_data->num = cpu;
thread = kthread_run_on_cpu(prbtest_writer, thread_data, cpu,
"prbtest writer %u");
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, thread);
prbtest_add_kthread_cleanup(test, thread);
}
kunit_info(test, "starting test\n");
set_cpus_allowed_ptr(current, cpumask_of(reader_cpu));
prbtest_reader(test_data, runtime_ms);
kunit_info(test, "completed test\n");
}
static struct kunit_case prb_test_cases[] = {
KUNIT_CASE_SLOW(test_readerwriter),
{}
};
static struct kunit_suite prb_test_suite = {
.name = "printk-ringbuffer",
.test_cases = prb_test_cases,
};
kunit_test_suite(prb_test_suite);
MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
MODULE_AUTHOR("John Ogness <john.ogness@linutronix.de>");
MODULE_DESCRIPTION("printk_ringbuffer KUnit test");
MODULE_LICENSE("GPL");
|