328 lines
9.3 KiB
C
328 lines
9.3 KiB
C
// 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");
|