// SPDX-License-Identifier: GPL-2.0-or-later/* * Copyright 2002-2007 H. Peter Anvin - All Rights Reserved * * Test RAID-6 recovery algorithms. */ #include <kunit/test.h>#include <linux/prandom.h>#include <linux/vmalloc.h>#include <linux/raid/pq.h>#include "../algos.h" MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); #define RAID6_KUNIT_SEED 42#define RAID6_KUNIT_NUM_TEST_ITERS 10#define RAID6_KUNIT_MAX_BUFFERS 64 /* Including P and Q */#define RAID6_KUNIT_MAX_FAILURES 2#define RAID6_KUNIT_MAX_BYTES PAGE_SIZE#define RAID6_KUNIT_ALLOC_BYTES SZ_16K static struct rnd_state rng;static void *test_buffers[RAID6_KUNIT_MAX_BUFFERS];static void *aligned_buffers[RAID6_KUNIT_MAX_BUFFERS];static void *test_recov_buffers[RAID6_KUNIT_MAX_FAILURES];static size_t test_buflen; struct test_args { unsigned int recov_idx; const struct raid6_recov_calls *recov; unsigned int gen_idx; const struct raid6_calls *gen;}; static struct test_args args; static u32 rand32(void){ return prandom_u32_state(&rng);} /* 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);} static unsigned int random_nr_buffers(void){ return (rand32() % (RAID6_KUNIT_MAX_BUFFERS - (RAID6_MIN_DISKS - 1))) + RAID6_MIN_DISKS;} /* Generate a random alignment that is a multiple of 64. */static unsigned int random_alignment(unsigned int max_alignment){ if (max_alignment == 0) return 0; return (rand32() % (max_alignment + 1)) & ~63;} static void makedata(int start, int stop){ int i; for (i = start; i <= stop; i++) prandom_bytes_state(&rng, test_buffers[i], test_buflen);} static char member_type(unsigned int nr_buffers, int d){ if (d == nr_buffers - 2) return 'P'; if (d == nr_buffers - 1) return 'Q'; return 'D';} static void test_recover_one(struct kunit *test, unsigned int nr_buffers, unsigned int len, int faila, int failb){ const struct test_args *ta = test->param_value; void *dataptrs[RAID6_KUNIT_MAX_BUFFERS]; int i; if (faila > failb) swap(faila, failb); for (i = 0; i < RAID6_KUNIT_MAX_FAILURES; i++) memset(test_recov_buffers[i], 0xf0, test_buflen); memcpy(dataptrs, aligned_buffers, sizeof(dataptrs)); dataptrs[faila] = test_recov_buffers[0]; dataptrs[failb] = test_recov_buffers[1]; if (failb == nr_buffers - 1) { /* * We don't implement the data+Q failure scenario, since it * is equivalent to a RAID-5 failure (XOR, then recompute Q). */ if (WARN_ON_ONCE(faila != nr_buffers - 2)) return; /* P+Q failure. Just rebuild the syndrome. */ ta->gen->gen_syndrome(nr_buffers, len, dataptrs); } else if (failb == nr_buffers - 2) { /* data+P failure. */ ta->recov->datap(nr_buffers, len, faila, dataptrs); } else { /* data+data failure. */ ta->recov->data2(nr_buffers, len, faila, failb, dataptrs); } KUNIT_EXPECT_MEMEQ_MSG(test, aligned_buffers[faila], dataptrs[faila], len, "faila miscompared: %3d[%c] buffers %u len %u (failb=%3d[%c])\n", faila, member_type(nr_buffers, faila), nr_buffers, len, failb, member_type(nr_buffers, failb)); KUNIT_EXPECT_MEMEQ_MSG(test, aligned_buffers[failb], dataptrs[failb], len, "failb miscompared: %3d[%c] buffers %u len %u (faila=%3d[%c])\n", failb, member_type(nr_buffers, failb), nr_buffers, len, faila, member_type(nr_buffers, faila));} static void test_recover(struct kunit *test, unsigned int nr_buffers, unsigned int len){ unsigned int nr_data = nr_buffers - 2; int iterations, i; /* Test P+Q recovery */ test_recover_one(test, nr_buffers, len, nr_data, nr_buffers - 1); /* Test data+P recovery */ for (i = 0; i < nr_buffers - 2; i++) test_recover_one(test, nr_buffers, len, i, nr_data); /* Double data failure is impossible with a single data disk */ if (nr_data == 1) return; /* Test data+data recovery using random sampling */ iterations = nr_buffers * 2; /* should provide good enough coverage */ for (i = 0; i < iterations; i++) { int faila = rand32() % nr_data, failb; do { failb = rand32() % nr_data; } while (failb == faila); test_recover_one(test, nr_buffers, len, faila, failb); }} /* Simulate rmw run */static void test_rmw_one(struct kunit *test, unsigned int nr_buffers, unsigned int len, int p1, int p2){ const struct test_args *ta = test->param_value; ta->gen->xor_syndrome(nr_buffers, p1, p2, len, aligned_buffers); makedata(p1, p2); ta->gen->xor_syndrome(nr_buffers, p1, p2, len, aligned_buffers); test_recover(test, nr_buffers, len);} static void test_rmw(struct kunit *test, unsigned int nr_buffers, unsigned int len){ int iterations = nr_buffers / 2, i; for (i = 0; i < iterations; i++) { int p1 = rand32() % (nr_buffers - 2); int p2 = rand32() % (nr_buffers - 2); if (p2 < p1) swap(p1, p2); test_rmw_one(test, nr_buffers, len, p1, p2); }} static void raid6_test_one(struct kunit *test){ const struct test_args *ta = test->param_value; unsigned int nr_buffers = random_nr_buffers(); unsigned int len = random_length(RAID6_KUNIT_MAX_BYTES); unsigned int max_alignment; int i; /* Nuke syndromes */ memset(test_buffers[nr_buffers - 2], 0xee, test_buflen); memset(test_buffers[nr_buffers - 1], 0xee, test_buflen); /* * If we're not using the entire buffer size, inject randomize alignment * into the buffer. */ max_alignment = RAID6_KUNIT_MAX_BYTES - len; if (rand32() % 2 == 0) { /* Use random alignments mod 64 */ for (i = 0; i < nr_buffers; i++) aligned_buffers[i] = test_buffers[i] + random_alignment(max_alignment); } else { /* Go up to the guard page, to catch buffer overreads */ unsigned int align = test_buflen - len; for (i = 0; i < nr_buffers; i++) aligned_buffers[i] = test_buffers[i] + align; } /* Generate assumed good syndrome */ ta->gen->gen_syndrome(nr_buffers, len, aligned_buffers); test_recover(test, nr_buffers, len); if (ta->gen->xor_syndrome) test_rmw(test, nr_buffers, len);} static void raid6_test(struct kunit *test){ int i; for (i = 0; i < RAID6_KUNIT_NUM_TEST_ITERS; i++) raid6_test_one(test);} static void raid6_benchmark(struct kunit *test){ static const unsigned int nr_to_test[] = { 4, 5, 6, 7, 8, 10, 12, 15, 16, 32, }; static const unsigned int len_to_test[] = { SZ_4K, SZ_16K, }; unsigned int i, j, l; u64 t; if (!IS_ENABLED(CONFIG_RAID6_PQ_KUNIT_BENCHMARK)) kunit_skip(test, "not enabled"); /* warm-up */ for (i = 0; i < ARRAY_SIZE(nr_to_test); i++) { for (j = 0; j < ARRAY_SIZE(len_to_test); j++) { for (l = 0; l < 10; l++) { raid6_gen_syndrome(nr_to_test[i], len_to_test[j], test_buffers); } } } /* * Preferably this would be a loop over len_to_test, but the kunit * logging always adds a newline to each logged format string. */ static_assert(ARRAY_SIZE(len_to_test) == 2); kunit_info(test, " \t%5u bytes\t%5u bytes\n", len_to_test[0], len_to_test[1]); for (i = 0; i < ARRAY_SIZE(nr_to_test); i++) { unsigned int nr = nr_to_test[i]; u64 speed[ARRAY_SIZE(len_to_test)]; KUNIT_ASSERT_LE(test, nr, RAID6_KUNIT_MAX_BUFFERS); for (j = 0; j < ARRAY_SIZE(len_to_test); j++) { unsigned int len = len_to_test[j]; const unsigned long num_iters = 1000; KUNIT_ASSERT_GT(test, len, 0); KUNIT_ASSERT_LE(test, len, RAID6_KUNIT_ALLOC_BYTES); preempt_disable(); t = ktime_get_ns(); for (l = 0; l < num_iters; l++) raid6_gen_syndrome(nr_to_test[i], len_to_test[j], test_buffers); t = max(ktime_get_ns() - t, 1); preempt_enable(); speed[j] = div64_u64((u64)len * num_iters * nr, t); } static_assert(ARRAY_SIZE(len_to_test) == 2); kunit_info(test, "%3u disks:\t%5llu GB/s\t%5llu GB/s\n", nr, speed[0], speed[1]); }} static const void *raid6_gen_params(struct kunit *test, const void *prev, char *desc){ if (!prev) { memset(&args, 0, sizeof(args));next_algo: args.recov_idx = 0; args.gen = raid6_algo_find(args.gen_idx); if (!args.gen) return NULL; } if (args.recov) args.recov_idx++; args.recov = raid6_recov_algo_find(args.recov_idx); if (!args.recov) { args.gen_idx++; goto next_algo; } snprintf(desc, KUNIT_PARAM_DESC_SIZE, "gen=%s recov=%s", args.gen->name, args.recov->name); return &args;} static struct kunit_case raid6_test_cases[] = { KUNIT_CASE_PARAM(raid6_test, raid6_gen_params), KUNIT_CASE(raid6_benchmark), {},}; static int raid6_suite_init(struct kunit_suite *suite){ int i; prandom_seed_state(&rng, RAID6_KUNIT_SEED); /* * 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(RAID6_KUNIT_ALLOC_BYTES, PAGE_SIZE); for (i = 0; i < RAID6_KUNIT_MAX_FAILURES; i++) { test_recov_buffers[i] = vmalloc(test_buflen); if (!test_recov_buffers[i]) goto out_free_recov_buffers; } for (i = 0; i < RAID6_KUNIT_MAX_BUFFERS; i++) { test_buffers[i] = vmalloc(test_buflen); if (!test_buffers[i]) goto out_free_buffers; } makedata(0, RAID6_KUNIT_MAX_BUFFERS - 1); return 0; out_free_buffers: for (i = 0; i < RAID6_KUNIT_MAX_BUFFERS; i++) vfree(test_buffers[i]); memset(test_buffers, 0, sizeof(test_buffers));out_free_recov_buffers: for (i = 0; i < RAID6_KUNIT_MAX_FAILURES; i++) vfree(test_recov_buffers[i]); memset(test_recov_buffers, 0, sizeof(test_recov_buffers)); return -ENOMEM;} static void raid6_suite_exit(struct kunit_suite *suite){ int i; for (i = 0; i < RAID6_KUNIT_MAX_BUFFERS; i++) vfree(test_buffers[i]); memset(test_buffers, 0, sizeof(test_buffers)); for (i = 0; i < RAID6_KUNIT_MAX_FAILURES; i++) vfree(test_recov_buffers[i]); memset(test_recov_buffers, 0, sizeof(test_recov_buffers));} static struct kunit_suite raid6_test_suite = { .name = "raid6", .test_cases = raid6_test_cases, .suite_init = raid6_suite_init, .suite_exit = raid6_suite_exit,};kunit_test_suite(raid6_test_suite); MODULE_DESCRIPTION("Unit test for the RAID P/Q library functions");MODULE_LICENSE("GPL");
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright 2002-2007 H. Peter Anvin - All Rights Reserved
*
* Test RAID-6 recovery algorithms.
*/
#include <kunit/test.h>
#include <linux/prandom.h>
#include <linux/vmalloc.h>
#include <linux/raid/pq.h>
#include "../algos.h"
MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
#define RAID6_KUNIT_SEED 42
#define RAID6_KUNIT_NUM_TEST_ITERS 10
#define RAID6_KUNIT_MAX_BUFFERS 64 /* Including P and Q */
#define RAID6_KUNIT_MAX_FAILURES 2
#define RAID6_KUNIT_MAX_BYTES PAGE_SIZE
#define RAID6_KUNIT_ALLOC_BYTES SZ_16K
static struct rnd_state rng;
static void *test_buffers[RAID6_KUNIT_MAX_BUFFERS];
static void *aligned_buffers[RAID6_KUNIT_MAX_BUFFERS];
static void *test_recov_buffers[RAID6_KUNIT_MAX_FAILURES];
static size_t test_buflen;
struct test_args {
unsigned int recov_idx;
const struct raid6_recov_calls *recov;
unsigned int gen_idx;
const struct raid6_calls *gen;
};
static struct test_args args;
static u32 rand32(void)
{
return prandom_u32_state(&rng);
}
/* 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);
}
static unsigned int random_nr_buffers(void)
{
return (rand32() % (RAID6_KUNIT_MAX_BUFFERS - (RAID6_MIN_DISKS - 1))) +
RAID6_MIN_DISKS;
}
/* Generate a random alignment that is a multiple of 64. */
static unsigned int random_alignment(unsigned int max_alignment)
{
if (max_alignment == 0)
return 0;
return (rand32() % (max_alignment + 1)) & ~63;
}
static void makedata(int start, int stop)
{
int i;
for (i = start; i <= stop; i++)
prandom_bytes_state(&rng, test_buffers[i], test_buflen);
}
static char member_type(unsigned int nr_buffers, int d)
{
if (d == nr_buffers - 2)
return 'P';
if (d == nr_buffers - 1)
return 'Q';
return 'D';
}
static void test_recover_one(struct kunit *test, unsigned int nr_buffers,
unsigned int len, int faila, int failb)
{
const struct test_args *ta = test->param_value;
void *dataptrs[RAID6_KUNIT_MAX_BUFFERS];
int i;
if (faila > failb)
swap(faila, failb);
for (i = 0; i < RAID6_KUNIT_MAX_FAILURES; i++)
memset(test_recov_buffers[i], 0xf0, test_buflen);
memcpy(dataptrs, aligned_buffers, sizeof(dataptrs));
dataptrs[faila] = test_recov_buffers[0];
dataptrs[failb] = test_recov_buffers[1];
if (failb == nr_buffers - 1) {
/*
* We don't implement the data+Q failure scenario, since it
* is equivalent to a RAID-5 failure (XOR, then recompute Q).
*/
if (WARN_ON_ONCE(faila != nr_buffers - 2))
return;
/* P+Q failure. Just rebuild the syndrome. */
ta->gen->gen_syndrome(nr_buffers, len, dataptrs);
} else if (failb == nr_buffers - 2) {
/* data+P failure. */
ta->recov->datap(nr_buffers, len, faila, dataptrs);
} else {
/* data+data failure. */
ta->recov->data2(nr_buffers, len, faila, failb, dataptrs);
}
KUNIT_EXPECT_MEMEQ_MSG(test, aligned_buffers[faila], dataptrs[faila],
len,
"faila miscompared: %3d[%c] buffers %u len %u (failb=%3d[%c])\n",
faila, member_type(nr_buffers, faila),
nr_buffers, len,
failb, member_type(nr_buffers, failb));
KUNIT_EXPECT_MEMEQ_MSG(test, aligned_buffers[failb], dataptrs[failb],
len,
"failb miscompared: %3d[%c] buffers %u len %u (faila=%3d[%c])\n",
failb, member_type(nr_buffers, failb),
nr_buffers, len,
faila, member_type(nr_buffers, faila));
}
static void test_recover(struct kunit *test, unsigned int nr_buffers,
unsigned int len)
{
unsigned int nr_data = nr_buffers - 2;
int iterations, i;
/* Test P+Q recovery */
test_recover_one(test, nr_buffers, len, nr_data, nr_buffers - 1);
/* Test data+P recovery */
for (i = 0; i < nr_buffers - 2; i++)
test_recover_one(test, nr_buffers, len, i, nr_data);
/* Double data failure is impossible with a single data disk */
if (nr_data == 1)
return;
/* Test data+data recovery using random sampling */
iterations = nr_buffers * 2; /* should provide good enough coverage */
for (i = 0; i < iterations; i++) {
int faila = rand32() % nr_data, failb;
do {
failb = rand32() % nr_data;
} while (failb == faila);
test_recover_one(test, nr_buffers, len, faila, failb);
}
}
/* Simulate rmw run */
static void test_rmw_one(struct kunit *test, unsigned int nr_buffers,
unsigned int len, int p1, int p2)
{
const struct test_args *ta = test->param_value;
ta->gen->xor_syndrome(nr_buffers, p1, p2, len, aligned_buffers);
makedata(p1, p2);
ta->gen->xor_syndrome(nr_buffers, p1, p2, len, aligned_buffers);
test_recover(test, nr_buffers, len);
}
static void test_rmw(struct kunit *test, unsigned int nr_buffers,
unsigned int len)
{
int iterations = nr_buffers / 2, i;
for (i = 0; i < iterations; i++) {
int p1 = rand32() % (nr_buffers - 2);
int p2 = rand32() % (nr_buffers - 2);
if (p2 < p1)
swap(p1, p2);
test_rmw_one(test, nr_buffers, len, p1, p2);
}
}
static void raid6_test_one(struct kunit *test)
{
const struct test_args *ta = test->param_value;
unsigned int nr_buffers = random_nr_buffers();
unsigned int len = random_length(RAID6_KUNIT_MAX_BYTES);
unsigned int max_alignment;
int i;
/* Nuke syndromes */
memset(test_buffers[nr_buffers - 2], 0xee, test_buflen);
memset(test_buffers[nr_buffers - 1], 0xee, test_buflen);
/*
* If we're not using the entire buffer size, inject randomize alignment
* into the buffer.
*/
max_alignment = RAID6_KUNIT_MAX_BYTES - len;
if (rand32() % 2 == 0) {
/* Use random alignments mod 64 */
for (i = 0; i < nr_buffers; i++)
aligned_buffers[i] = test_buffers[i] +
random_alignment(max_alignment);
} else {
/* Go up to the guard page, to catch buffer overreads */
unsigned int align = test_buflen - len;
for (i = 0; i < nr_buffers; i++)
aligned_buffers[i] = test_buffers[i] + align;
}
/* Generate assumed good syndrome */
ta->gen->gen_syndrome(nr_buffers, len, aligned_buffers);
test_recover(test, nr_buffers, len);
if (ta->gen->xor_syndrome)
test_rmw(test, nr_buffers, len);
}
static void raid6_test(struct kunit *test)
{
int i;
for (i = 0; i < RAID6_KUNIT_NUM_TEST_ITERS; i++)
raid6_test_one(test);
}
static void raid6_benchmark(struct kunit *test)
{
static const unsigned int nr_to_test[] = {
4, 5, 6, 7, 8, 10, 12, 15, 16, 32,
};
static const unsigned int len_to_test[] = {
SZ_4K, SZ_16K,
};
unsigned int i, j, l;
u64 t;
if (!IS_ENABLED(CONFIG_RAID6_PQ_KUNIT_BENCHMARK))
kunit_skip(test, "not enabled");
/* warm-up */
for (i = 0; i < ARRAY_SIZE(nr_to_test); i++) {
for (j = 0; j < ARRAY_SIZE(len_to_test); j++) {
for (l = 0; l < 10; l++) {
raid6_gen_syndrome(nr_to_test[i],
len_to_test[j], test_buffers);
}
}
}
/*
* Preferably this would be a loop over len_to_test, but the kunit
* logging always adds a newline to each logged format string.
*/
static_assert(ARRAY_SIZE(len_to_test) == 2);
kunit_info(test, " \t%5u bytes\t%5u bytes\n",
len_to_test[0], len_to_test[1]);
for (i = 0; i < ARRAY_SIZE(nr_to_test); i++) {
unsigned int nr = nr_to_test[i];
u64 speed[ARRAY_SIZE(len_to_test)];
KUNIT_ASSERT_LE(test, nr, RAID6_KUNIT_MAX_BUFFERS);
for (j = 0; j < ARRAY_SIZE(len_to_test); j++) {
unsigned int len = len_to_test[j];
const unsigned long num_iters = 1000;
KUNIT_ASSERT_GT(test, len, 0);
KUNIT_ASSERT_LE(test, len, RAID6_KUNIT_ALLOC_BYTES);
preempt_disable();
t = ktime_get_ns();
for (l = 0; l < num_iters; l++)
raid6_gen_syndrome(nr_to_test[i],
len_to_test[j], test_buffers);
t = max(ktime_get_ns() - t, 1);
preempt_enable();
speed[j] = div64_u64((u64)len * num_iters * nr, t);
}
static_assert(ARRAY_SIZE(len_to_test) == 2);
kunit_info(test, "%3u disks:\t%5llu GB/s\t%5llu GB/s\n",
nr, speed[0], speed[1]);
}
}
static const void *raid6_gen_params(struct kunit *test, const void *prev,
char *desc)
{
if (!prev) {
memset(&args, 0, sizeof(args));
next_algo:
args.recov_idx = 0;
args.gen = raid6_algo_find(args.gen_idx);
if (!args.gen)
return NULL;
}
if (args.recov)
args.recov_idx++;
args.recov = raid6_recov_algo_find(args.recov_idx);
if (!args.recov) {
args.gen_idx++;
goto next_algo;
}
snprintf(desc, KUNIT_PARAM_DESC_SIZE, "gen=%s recov=%s",
args.gen->name, args.recov->name);
return &args;
}
static struct kunit_case raid6_test_cases[] = {
KUNIT_CASE_PARAM(raid6_test, raid6_gen_params),
KUNIT_CASE(raid6_benchmark),
{},
};
static int raid6_suite_init(struct kunit_suite *suite)
{
int i;
prandom_seed_state(&rng, RAID6_KUNIT_SEED);
/*
* 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(RAID6_KUNIT_ALLOC_BYTES, PAGE_SIZE);
for (i = 0; i < RAID6_KUNIT_MAX_FAILURES; i++) {
test_recov_buffers[i] = vmalloc(test_buflen);
if (!test_recov_buffers[i])
goto out_free_recov_buffers;
}
for (i = 0; i < RAID6_KUNIT_MAX_BUFFERS; i++) {
test_buffers[i] = vmalloc(test_buflen);
if (!test_buffers[i])
goto out_free_buffers;
}
makedata(0, RAID6_KUNIT_MAX_BUFFERS - 1);
return 0;
out_free_buffers:
for (i = 0; i < RAID6_KUNIT_MAX_BUFFERS; i++)
vfree(test_buffers[i]);
memset(test_buffers, 0, sizeof(test_buffers));
out_free_recov_buffers:
for (i = 0; i < RAID6_KUNIT_MAX_FAILURES; i++)
vfree(test_recov_buffers[i]);
memset(test_recov_buffers, 0, sizeof(test_recov_buffers));
return -ENOMEM;
}
static void raid6_suite_exit(struct kunit_suite *suite)
{
int i;
for (i = 0; i < RAID6_KUNIT_MAX_BUFFERS; i++)
vfree(test_buffers[i]);
memset(test_buffers, 0, sizeof(test_buffers));
for (i = 0; i < RAID6_KUNIT_MAX_FAILURES; i++)
vfree(test_recov_buffers[i]);
memset(test_recov_buffers, 0, sizeof(test_recov_buffers));
}
static struct kunit_suite raid6_test_suite = {
.name = "raid6",
.test_cases = raid6_test_cases,
.suite_init = raid6_suite_init,
.suite_exit = raid6_suite_exit,
};
kunit_test_suite(raid6_test_suite);
MODULE_DESCRIPTION("Unit test for the RAID P/Q library functions");
MODULE_LICENSE("GPL");