Commit | Line | Data |
---|---|---|
7126bd8b AM |
1 | #include <linux/kernel.h> |
2 | #include <linux/module.h> | |
3 | #include <linux/list.h> | |
7126bd8b AM |
4 | #include <linux/random.h> |
5 | #include <linux/string.h> | |
6 | #include <linux/bitops.h> | |
1749c00f | 7 | #include <linux/slab.h> |
7126bd8b AM |
8 | #include <linux/mtd/nand_ecc.h> |
9 | ||
6060fb42 AM |
10 | /* |
11 | * Test the implementation for software ECC | |
12 | * | |
13 | * No actual MTD device is needed, So we don't need to warry about losing | |
14 | * important data by human error. | |
15 | * | |
16 | * This covers possible patterns of corruption which can be reliably corrected | |
17 | * or detected. | |
18 | */ | |
19 | ||
7126bd8b AM |
20 | #if defined(CONFIG_MTD_NAND) || defined(CONFIG_MTD_NAND_MODULE) |
21 | ||
6060fb42 AM |
22 | struct nand_ecc_test { |
23 | const char *name; | |
24 | void (*prepare)(void *, void *, void *, void *, const size_t); | |
25 | int (*verify)(void *, void *, void *, const size_t); | |
26 | }; | |
27 | ||
c092b439 AM |
28 | /* |
29 | * The reason for this __change_bit_le() instead of __change_bit() is to inject | |
30 | * bit error properly within the region which is not a multiple of | |
31 | * sizeof(unsigned long) on big-endian systems | |
32 | */ | |
33 | #ifdef __LITTLE_ENDIAN | |
34 | #define __change_bit_le(nr, addr) __change_bit(nr, addr) | |
35 | #elif defined(__BIG_ENDIAN) | |
36 | #define __change_bit_le(nr, addr) \ | |
37 | __change_bit((nr) ^ ((BITS_PER_LONG - 1) & ~0x7), addr) | |
38 | #else | |
39 | #error "Unknown byte order" | |
40 | #endif | |
41 | ||
6060fb42 AM |
42 | static void single_bit_error_data(void *error_data, void *correct_data, |
43 | size_t size) | |
7126bd8b | 44 | { |
c092b439 | 45 | unsigned int offset = random32() % (size * BITS_PER_BYTE); |
7126bd8b | 46 | |
6060fb42 AM |
47 | memcpy(error_data, correct_data, size); |
48 | __change_bit_le(offset, error_data); | |
49 | } | |
50 | ||
ccaa6795 AM |
51 | static void no_bit_error(void *error_data, void *error_ecc, |
52 | void *correct_data, void *correct_ecc, const size_t size) | |
53 | { | |
54 | memcpy(error_data, correct_data, size); | |
55 | memcpy(error_ecc, correct_ecc, 3); | |
56 | } | |
57 | ||
58 | static int no_bit_error_verify(void *error_data, void *error_ecc, | |
59 | void *correct_data, const size_t size) | |
60 | { | |
61 | unsigned char calc_ecc[3]; | |
62 | int ret; | |
63 | ||
64 | __nand_calculate_ecc(error_data, size, calc_ecc); | |
65 | ret = __nand_correct_data(error_data, error_ecc, calc_ecc, size); | |
66 | if (ret == 0 && !memcmp(correct_data, error_data, size)) | |
67 | return 0; | |
68 | ||
69 | return -EINVAL; | |
70 | } | |
71 | ||
6060fb42 AM |
72 | static void single_bit_error_in_data(void *error_data, void *error_ecc, |
73 | void *correct_data, void *correct_ecc, const size_t size) | |
74 | { | |
75 | single_bit_error_data(error_data, correct_data, size); | |
76 | memcpy(error_ecc, correct_ecc, 3); | |
77 | } | |
78 | ||
79 | static int single_bit_error_correct(void *error_data, void *error_ecc, | |
80 | void *correct_data, const size_t size) | |
81 | { | |
82 | unsigned char calc_ecc[3]; | |
83 | int ret; | |
84 | ||
85 | __nand_calculate_ecc(error_data, size, calc_ecc); | |
86 | ret = __nand_correct_data(error_data, error_ecc, calc_ecc, size); | |
87 | if (ret == 1 && !memcmp(correct_data, error_data, size)) | |
88 | return 0; | |
89 | ||
90 | return -EINVAL; | |
7126bd8b AM |
91 | } |
92 | ||
6060fb42 | 93 | static const struct nand_ecc_test nand_ecc_test[] = { |
ccaa6795 AM |
94 | { |
95 | .name = "no-bit-error", | |
96 | .prepare = no_bit_error, | |
97 | .verify = no_bit_error_verify, | |
98 | }, | |
6060fb42 AM |
99 | { |
100 | .name = "single-bit-error-in-data-correct", | |
101 | .prepare = single_bit_error_in_data, | |
102 | .verify = single_bit_error_correct, | |
103 | }, | |
104 | }; | |
105 | ||
c5b8384a AM |
106 | static void dump_data_ecc(void *error_data, void *error_ecc, void *correct_data, |
107 | void *correct_ecc, const size_t size) | |
108 | { | |
109 | pr_info("hexdump of error data:\n"); | |
110 | print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 4, | |
111 | error_data, size, false); | |
112 | print_hex_dump(KERN_INFO, "hexdump of error ecc: ", | |
113 | DUMP_PREFIX_NONE, 16, 1, error_ecc, 3, false); | |
114 | ||
115 | pr_info("hexdump of correct data:\n"); | |
116 | print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 4, | |
117 | correct_data, size, false); | |
118 | print_hex_dump(KERN_INFO, "hexdump of correct ecc: ", | |
119 | DUMP_PREFIX_NONE, 16, 1, correct_ecc, 3, false); | |
120 | } | |
121 | ||
6060fb42 | 122 | static int nand_ecc_test_run(const size_t size) |
7126bd8b | 123 | { |
6060fb42 | 124 | int i; |
1749c00f AM |
125 | int err = 0; |
126 | void *error_data; | |
127 | void *error_ecc; | |
128 | void *correct_data; | |
129 | void *correct_ecc; | |
7126bd8b | 130 | |
1749c00f AM |
131 | error_data = kmalloc(size, GFP_KERNEL); |
132 | error_ecc = kmalloc(3, GFP_KERNEL); | |
133 | correct_data = kmalloc(size, GFP_KERNEL); | |
134 | correct_ecc = kmalloc(3, GFP_KERNEL); | |
135 | ||
136 | if (!error_data || !error_ecc || !correct_data || !correct_ecc) { | |
137 | err = -ENOMEM; | |
138 | goto error; | |
139 | } | |
7126bd8b | 140 | |
c5b8384a | 141 | get_random_bytes(correct_data, size); |
c5b8384a | 142 | __nand_calculate_ecc(correct_data, size, correct_ecc); |
6060fb42 AM |
143 | |
144 | for (i = 0; i < ARRAY_SIZE(nand_ecc_test); i++) { | |
145 | nand_ecc_test[i].prepare(error_data, error_ecc, | |
146 | correct_data, correct_ecc, size); | |
147 | err = nand_ecc_test[i].verify(error_data, error_ecc, | |
148 | correct_data, size); | |
149 | ||
150 | if (err) { | |
151 | pr_err("mtd_nandecctest: not ok - %s-%zd\n", | |
152 | nand_ecc_test[i].name, size); | |
153 | dump_data_ecc(error_data, error_ecc, | |
154 | correct_data, correct_ecc, size); | |
155 | break; | |
156 | } | |
157 | pr_info("mtd_nandecctest: ok - %s-%zd\n", | |
158 | nand_ecc_test[i].name, size); | |
7126bd8b | 159 | } |
1749c00f AM |
160 | error: |
161 | kfree(error_data); | |
162 | kfree(error_ecc); | |
163 | kfree(correct_data); | |
164 | kfree(correct_ecc); | |
165 | ||
166 | return err; | |
7126bd8b AM |
167 | } |
168 | ||
169 | #else | |
170 | ||
6060fb42 | 171 | static int nand_ecc_test_run(const size_t size) |
7126bd8b AM |
172 | { |
173 | return 0; | |
174 | } | |
175 | ||
176 | #endif | |
177 | ||
178 | static int __init ecc_test_init(void) | |
179 | { | |
f45c2990 | 180 | int err; |
7126bd8b | 181 | |
6060fb42 | 182 | err = nand_ecc_test_run(256); |
f45c2990 AM |
183 | if (err) |
184 | return err; | |
185 | ||
6060fb42 | 186 | return nand_ecc_test_run(512); |
7126bd8b AM |
187 | } |
188 | ||
189 | static void __exit ecc_test_exit(void) | |
190 | { | |
191 | } | |
192 | ||
193 | module_init(ecc_test_init); | |
194 | module_exit(ecc_test_exit); | |
195 | ||
196 | MODULE_DESCRIPTION("NAND ECC function test module"); | |
197 | MODULE_AUTHOR("Akinobu Mita"); | |
198 | MODULE_LICENSE("GPL"); |