root/lib/raid/xor/tests/xor_kunit.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Unit test the XOR library functions.
 *
 * Copyright 2024 Google LLC
 * Copyright 2026 Christoph Hellwig
 *
 * Based on the CRC tests by Eric Biggers <ebiggers@google.com>.
 */
#include <kunit/test.h>
#include <linux/prandom.h>
#include <linux/string_choices.h>
#include <linux/vmalloc.h>
#include <linux/raid/xor.h>

#define XOR_KUNIT_SEED                  42
#define XOR_KUNIT_MAX_BYTES             16384
#define XOR_KUNIT_MAX_BUFFERS           64
#define XOR_KUNIT_NUM_TEST_ITERS        1000

static struct rnd_state rng;
static void *test_buffers[XOR_KUNIT_MAX_BUFFERS];
static void *test_dest;
static void *test_ref;
static size_t test_buflen;

static u32 rand32(void)
{
        return prandom_u32_state(&rng);
}

/* Reference implementation using dumb byte-wise XOR */
static void xor_ref(void *dest, void **srcs, unsigned int src_cnt,
                unsigned int bytes)
{
        unsigned int off, idx;
        u8 *d = dest;

        for (off = 0; off < bytes; off++) {
                for (idx = 0; idx < src_cnt; idx++) {
                        u8 *src = srcs[idx];

                        d[off] ^= src[off];
                }
        }
}

/* Generate a random length that is a multiple of 512. */
static unsigned int random_length(unsigned int max_length)
{
        return round_up((rand32() % max_length) + 1, 512);
}

/* Generate a random alignment that is a multiple of 64. */
static unsigned int random_alignment(unsigned int max_alignment)
{
        return ((rand32() % max_alignment) + 1) & ~63;
}

static void xor_generate_random_data(void)
{
        int i;

        prandom_bytes_state(&rng, test_dest, test_buflen);
        memcpy(test_ref, test_dest, test_buflen);
        for (i = 0; i < XOR_KUNIT_MAX_BUFFERS; i++)
                prandom_bytes_state(&rng, test_buffers[i], test_buflen);
}

/* Test that xor_gen gives the same result as a reference implementation. */
static void xor_test(struct kunit *test)
{
        void *aligned_buffers[XOR_KUNIT_MAX_BUFFERS];
        size_t i;

        for (i = 0; i < XOR_KUNIT_NUM_TEST_ITERS; i++) {
                unsigned int nr_buffers =
                        (rand32() % XOR_KUNIT_MAX_BUFFERS) + 1;
                unsigned int len = random_length(XOR_KUNIT_MAX_BYTES);
                unsigned int max_alignment, align = 0;
                void *buffers;

                if (rand32() % 8 == 0)
                        /* Refresh the data occasionally. */
                        xor_generate_random_data();

                /*
                 * If we're not using the entire buffer size, inject randomize
                 * alignment into the buffer.
                 */
                max_alignment = XOR_KUNIT_MAX_BYTES - len;
                if (max_alignment == 0) {
                        buffers = test_buffers;
                } else if (rand32() % 2 == 0) {
                        /* Use random alignments mod 64 */
                        int j;

                        for (j = 0; j < nr_buffers; j++)
                                aligned_buffers[j] = test_buffers[j] +
                                        random_alignment(max_alignment);
                        buffers = aligned_buffers;
                        align = random_alignment(max_alignment);
                } else {
                        /* Go up to the guard page, to catch buffer overreads */
                        int j;

                        align = test_buflen - len;
                        for (j = 0; j < nr_buffers; j++)
                                aligned_buffers[j] = test_buffers[j] + align;
                        buffers = aligned_buffers;
                }

                /*
                 * Compute the XOR, and verify that it equals the XOR computed
                 * by a simple byte-at-a-time reference implementation.
                 */
                xor_ref(test_ref + align, buffers, nr_buffers, len);
                xor_gen(test_dest + align, buffers, nr_buffers, len);
                KUNIT_EXPECT_MEMEQ_MSG(test, test_ref + align,
                                test_dest + align, len,
                                "Wrong result with buffers=%u, len=%u, unaligned=%s, at_end=%s",
                                nr_buffers, len,
                                str_yes_no(max_alignment),
                                str_yes_no(align + len == test_buflen));
        }
}

static struct kunit_case xor_test_cases[] = {
        KUNIT_CASE(xor_test),
        {},
};

static int xor_suite_init(struct kunit_suite *suite)
{
        int i;

        /*
         * Allocate the test buffer using vmalloc() with a page-aligned length
         * so that it is immediately followed by a guard page.  This allows
         * buffer overreads to be detected, even in assembly code.
         */
        test_buflen = round_up(XOR_KUNIT_MAX_BYTES, PAGE_SIZE);
        test_ref = vmalloc(test_buflen);
        if (!test_ref)
                return -ENOMEM;
        test_dest = vmalloc(test_buflen);
        if (!test_dest)
                goto out_free_ref;
        for (i = 0; i < XOR_KUNIT_MAX_BUFFERS; i++) {
                test_buffers[i] = vmalloc(test_buflen);
                if (!test_buffers[i])
                        goto out_free_buffers;
        }

        prandom_seed_state(&rng, XOR_KUNIT_SEED);
        xor_generate_random_data();
        return 0;

out_free_buffers:
        while (--i >= 0)
                vfree(test_buffers[i]);
        vfree(test_dest);
out_free_ref:
        vfree(test_ref);
        return -ENOMEM;
}

static void xor_suite_exit(struct kunit_suite *suite)
{
        int i;

        vfree(test_ref);
        vfree(test_dest);
        for (i = 0; i < XOR_KUNIT_MAX_BUFFERS; i++)
                vfree(test_buffers[i]);
}

static struct kunit_suite xor_test_suite = {
        .name           = "xor",
        .test_cases     = xor_test_cases,
        .suite_init     = xor_suite_init,
        .suite_exit     = xor_suite_exit,
};
kunit_test_suite(xor_test_suite);

MODULE_DESCRIPTION("Unit test for the XOR library functions");
MODULE_LICENSE("GPL");