162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * asynchronous raid6 recovery self test
462306a36Sopenharmony_ci * Copyright (c) 2009, Intel Corporation.
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * based on drivers/md/raid6test/test.c:
762306a36Sopenharmony_ci * 	Copyright 2002-2007 H. Peter Anvin
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci#include <linux/async_tx.h>
1062306a36Sopenharmony_ci#include <linux/gfp.h>
1162306a36Sopenharmony_ci#include <linux/mm.h>
1262306a36Sopenharmony_ci#include <linux/random.h>
1362306a36Sopenharmony_ci#include <linux/module.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#undef pr
1662306a36Sopenharmony_ci#define pr(fmt, args...) pr_info("raid6test: " fmt, ##args)
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#define NDISKS 64 /* Including P and Q */
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_cistatic struct page *dataptrs[NDISKS];
2162306a36Sopenharmony_ciunsigned int dataoffs[NDISKS];
2262306a36Sopenharmony_cistatic addr_conv_t addr_conv[NDISKS];
2362306a36Sopenharmony_cistatic struct page *data[NDISKS+3];
2462306a36Sopenharmony_cistatic struct page *spare;
2562306a36Sopenharmony_cistatic struct page *recovi;
2662306a36Sopenharmony_cistatic struct page *recovj;
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_cistatic void callback(void *param)
2962306a36Sopenharmony_ci{
3062306a36Sopenharmony_ci	struct completion *cmp = param;
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	complete(cmp);
3362306a36Sopenharmony_ci}
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cistatic void makedata(int disks)
3662306a36Sopenharmony_ci{
3762306a36Sopenharmony_ci	int i;
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	for (i = 0; i < disks; i++) {
4062306a36Sopenharmony_ci		get_random_bytes(page_address(data[i]), PAGE_SIZE);
4162306a36Sopenharmony_ci		dataptrs[i] = data[i];
4262306a36Sopenharmony_ci		dataoffs[i] = 0;
4362306a36Sopenharmony_ci	}
4462306a36Sopenharmony_ci}
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_cistatic char disk_type(int d, int disks)
4762306a36Sopenharmony_ci{
4862306a36Sopenharmony_ci	if (d == disks - 2)
4962306a36Sopenharmony_ci		return 'P';
5062306a36Sopenharmony_ci	else if (d == disks - 1)
5162306a36Sopenharmony_ci		return 'Q';
5262306a36Sopenharmony_ci	else
5362306a36Sopenharmony_ci		return 'D';
5462306a36Sopenharmony_ci}
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci/* Recover two failed blocks. */
5762306a36Sopenharmony_cistatic void raid6_dual_recov(int disks, size_t bytes, int faila, int failb,
5862306a36Sopenharmony_ci		struct page **ptrs, unsigned int *offs)
5962306a36Sopenharmony_ci{
6062306a36Sopenharmony_ci	struct async_submit_ctl submit;
6162306a36Sopenharmony_ci	struct completion cmp;
6262306a36Sopenharmony_ci	struct dma_async_tx_descriptor *tx = NULL;
6362306a36Sopenharmony_ci	enum sum_check_flags result = ~0;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	if (faila > failb)
6662306a36Sopenharmony_ci		swap(faila, failb);
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	if (failb == disks-1) {
6962306a36Sopenharmony_ci		if (faila == disks-2) {
7062306a36Sopenharmony_ci			/* P+Q failure.  Just rebuild the syndrome. */
7162306a36Sopenharmony_ci			init_async_submit(&submit, 0, NULL, NULL, NULL, addr_conv);
7262306a36Sopenharmony_ci			tx = async_gen_syndrome(ptrs, offs,
7362306a36Sopenharmony_ci					disks, bytes, &submit);
7462306a36Sopenharmony_ci		} else {
7562306a36Sopenharmony_ci			struct page *blocks[NDISKS];
7662306a36Sopenharmony_ci			struct page *dest;
7762306a36Sopenharmony_ci			int count = 0;
7862306a36Sopenharmony_ci			int i;
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci			BUG_ON(disks > NDISKS);
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci			/* data+Q failure.  Reconstruct data from P,
8362306a36Sopenharmony_ci			 * then rebuild syndrome
8462306a36Sopenharmony_ci			 */
8562306a36Sopenharmony_ci			for (i = disks; i-- ; ) {
8662306a36Sopenharmony_ci				if (i == faila || i == failb)
8762306a36Sopenharmony_ci					continue;
8862306a36Sopenharmony_ci				blocks[count++] = ptrs[i];
8962306a36Sopenharmony_ci			}
9062306a36Sopenharmony_ci			dest = ptrs[faila];
9162306a36Sopenharmony_ci			init_async_submit(&submit, ASYNC_TX_XOR_ZERO_DST, NULL,
9262306a36Sopenharmony_ci					  NULL, NULL, addr_conv);
9362306a36Sopenharmony_ci			tx = async_xor(dest, blocks, 0, count, bytes, &submit);
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci			init_async_submit(&submit, 0, tx, NULL, NULL, addr_conv);
9662306a36Sopenharmony_ci			tx = async_gen_syndrome(ptrs, offs,
9762306a36Sopenharmony_ci					disks, bytes, &submit);
9862306a36Sopenharmony_ci		}
9962306a36Sopenharmony_ci	} else {
10062306a36Sopenharmony_ci		if (failb == disks-2) {
10162306a36Sopenharmony_ci			/* data+P failure. */
10262306a36Sopenharmony_ci			init_async_submit(&submit, 0, NULL, NULL, NULL, addr_conv);
10362306a36Sopenharmony_ci			tx = async_raid6_datap_recov(disks, bytes,
10462306a36Sopenharmony_ci					faila, ptrs, offs, &submit);
10562306a36Sopenharmony_ci		} else {
10662306a36Sopenharmony_ci			/* data+data failure. */
10762306a36Sopenharmony_ci			init_async_submit(&submit, 0, NULL, NULL, NULL, addr_conv);
10862306a36Sopenharmony_ci			tx = async_raid6_2data_recov(disks, bytes,
10962306a36Sopenharmony_ci					faila, failb, ptrs, offs, &submit);
11062306a36Sopenharmony_ci		}
11162306a36Sopenharmony_ci	}
11262306a36Sopenharmony_ci	init_completion(&cmp);
11362306a36Sopenharmony_ci	init_async_submit(&submit, ASYNC_TX_ACK, tx, callback, &cmp, addr_conv);
11462306a36Sopenharmony_ci	tx = async_syndrome_val(ptrs, offs,
11562306a36Sopenharmony_ci			disks, bytes, &result, spare, 0, &submit);
11662306a36Sopenharmony_ci	async_tx_issue_pending(tx);
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	if (wait_for_completion_timeout(&cmp, msecs_to_jiffies(3000)) == 0)
11962306a36Sopenharmony_ci		pr("%s: timeout! (faila: %d failb: %d disks: %d)\n",
12062306a36Sopenharmony_ci		   __func__, faila, failb, disks);
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	if (result != 0)
12362306a36Sopenharmony_ci		pr("%s: validation failure! faila: %d failb: %d sum_check_flags: %x\n",
12462306a36Sopenharmony_ci		   __func__, faila, failb, result);
12562306a36Sopenharmony_ci}
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_cistatic int test_disks(int i, int j, int disks)
12862306a36Sopenharmony_ci{
12962306a36Sopenharmony_ci	int erra, errb;
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	memset(page_address(recovi), 0xf0, PAGE_SIZE);
13262306a36Sopenharmony_ci	memset(page_address(recovj), 0xba, PAGE_SIZE);
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	dataptrs[i] = recovi;
13562306a36Sopenharmony_ci	dataptrs[j] = recovj;
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	raid6_dual_recov(disks, PAGE_SIZE, i, j, dataptrs, dataoffs);
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	erra = memcmp(page_address(data[i]), page_address(recovi), PAGE_SIZE);
14062306a36Sopenharmony_ci	errb = memcmp(page_address(data[j]), page_address(recovj), PAGE_SIZE);
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	pr("%s(%d, %d): faila=%3d(%c)  failb=%3d(%c)  %s\n",
14362306a36Sopenharmony_ci	   __func__, i, j, i, disk_type(i, disks), j, disk_type(j, disks),
14462306a36Sopenharmony_ci	   (!erra && !errb) ? "OK" : !erra ? "ERRB" : !errb ? "ERRA" : "ERRAB");
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	dataptrs[i] = data[i];
14762306a36Sopenharmony_ci	dataptrs[j] = data[j];
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	return erra || errb;
15062306a36Sopenharmony_ci}
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_cistatic int test(int disks, int *tests)
15362306a36Sopenharmony_ci{
15462306a36Sopenharmony_ci	struct dma_async_tx_descriptor *tx;
15562306a36Sopenharmony_ci	struct async_submit_ctl submit;
15662306a36Sopenharmony_ci	struct completion cmp;
15762306a36Sopenharmony_ci	int err = 0;
15862306a36Sopenharmony_ci	int i, j;
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	recovi = data[disks];
16162306a36Sopenharmony_ci	recovj = data[disks+1];
16262306a36Sopenharmony_ci	spare  = data[disks+2];
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	makedata(disks);
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	/* Nuke syndromes */
16762306a36Sopenharmony_ci	memset(page_address(data[disks-2]), 0xee, PAGE_SIZE);
16862306a36Sopenharmony_ci	memset(page_address(data[disks-1]), 0xee, PAGE_SIZE);
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	/* Generate assumed good syndrome */
17162306a36Sopenharmony_ci	init_completion(&cmp);
17262306a36Sopenharmony_ci	init_async_submit(&submit, ASYNC_TX_ACK, NULL, callback, &cmp, addr_conv);
17362306a36Sopenharmony_ci	tx = async_gen_syndrome(dataptrs, dataoffs, disks, PAGE_SIZE, &submit);
17462306a36Sopenharmony_ci	async_tx_issue_pending(tx);
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	if (wait_for_completion_timeout(&cmp, msecs_to_jiffies(3000)) == 0) {
17762306a36Sopenharmony_ci		pr("error: initial gen_syndrome(%d) timed out\n", disks);
17862306a36Sopenharmony_ci		return 1;
17962306a36Sopenharmony_ci	}
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	pr("testing the %d-disk case...\n", disks);
18262306a36Sopenharmony_ci	for (i = 0; i < disks-1; i++)
18362306a36Sopenharmony_ci		for (j = i+1; j < disks; j++) {
18462306a36Sopenharmony_ci			(*tests)++;
18562306a36Sopenharmony_ci			err += test_disks(i, j, disks);
18662306a36Sopenharmony_ci		}
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	return err;
18962306a36Sopenharmony_ci}
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_cistatic int __init raid6_test(void)
19362306a36Sopenharmony_ci{
19462306a36Sopenharmony_ci	int err = 0;
19562306a36Sopenharmony_ci	int tests = 0;
19662306a36Sopenharmony_ci	int i;
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	for (i = 0; i < NDISKS+3; i++) {
19962306a36Sopenharmony_ci		data[i] = alloc_page(GFP_KERNEL);
20062306a36Sopenharmony_ci		if (!data[i]) {
20162306a36Sopenharmony_ci			while (i--)
20262306a36Sopenharmony_ci				put_page(data[i]);
20362306a36Sopenharmony_ci			return -ENOMEM;
20462306a36Sopenharmony_ci		}
20562306a36Sopenharmony_ci	}
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	/* the 4-disk and 5-disk cases are special for the recovery code */
20862306a36Sopenharmony_ci	if (NDISKS > 4)
20962306a36Sopenharmony_ci		err += test(4, &tests);
21062306a36Sopenharmony_ci	if (NDISKS > 5)
21162306a36Sopenharmony_ci		err += test(5, &tests);
21262306a36Sopenharmony_ci	/* the 11 and 12 disk cases are special for ioatdma (p-disabled
21362306a36Sopenharmony_ci	 * q-continuation without extended descriptor)
21462306a36Sopenharmony_ci	 */
21562306a36Sopenharmony_ci	if (NDISKS > 12) {
21662306a36Sopenharmony_ci		err += test(11, &tests);
21762306a36Sopenharmony_ci		err += test(12, &tests);
21862306a36Sopenharmony_ci	}
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	/* the 24 disk case is special for ioatdma as it is the boundary point
22162306a36Sopenharmony_ci	 * at which it needs to switch from 8-source ops to 16-source
22262306a36Sopenharmony_ci	 * ops for continuation (assumes DMA_HAS_PQ_CONTINUE is not set)
22362306a36Sopenharmony_ci	 */
22462306a36Sopenharmony_ci	if (NDISKS > 24)
22562306a36Sopenharmony_ci		err += test(24, &tests);
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	err += test(NDISKS, &tests);
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci	pr("\n");
23062306a36Sopenharmony_ci	pr("complete (%d tests, %d failure%s)\n",
23162306a36Sopenharmony_ci	   tests, err, err == 1 ? "" : "s");
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	for (i = 0; i < NDISKS+3; i++)
23462306a36Sopenharmony_ci		put_page(data[i]);
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	return 0;
23762306a36Sopenharmony_ci}
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_cistatic void __exit raid6_test_exit(void)
24062306a36Sopenharmony_ci{
24162306a36Sopenharmony_ci}
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci/* when compiled-in wait for drivers to load first (assumes dma drivers
24462306a36Sopenharmony_ci * are also compiled-in)
24562306a36Sopenharmony_ci */
24662306a36Sopenharmony_cilate_initcall(raid6_test);
24762306a36Sopenharmony_cimodule_exit(raid6_test_exit);
24862306a36Sopenharmony_ciMODULE_AUTHOR("Dan Williams <dan.j.williams@intel.com>");
24962306a36Sopenharmony_ciMODULE_DESCRIPTION("asynchronous RAID-6 recovery self tests");
25062306a36Sopenharmony_ciMODULE_LICENSE("GPL");
251