Contributors: 4
Author Tokens Token Proportion Commits Commit Proportion
Thomas Weißschuh 1175 90.80% 1 20.00%
Petr Mladek 67 5.18% 2 40.00%
Arnd Bergmann 39 3.01% 1 20.00%
John Ogness 13 1.00% 1 20.00%
Total 1294 5


// 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");