327 lines · 9.3 KB · C
RawDownload
// 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");