root/kernel/printk/printk_ringbuffer_kunit_test.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");