blob: f5c5697936142b2658ea323445d47166fb5090fa [file] [log] [blame]
rjw1f884582022-01-06 17:20:42 +08001/*
2 * Copyright (c) 2009-2014 Travis Geiselbrecht
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files
6 * (the "Software"), to deal in the Software without restriction,
7 * including without limitation the rights to use, copy, modify, merge,
8 * publish, distribute, sublicense, and/or sell copies of the Software,
9 * and to permit persons to whom the Software is furnished to do so,
10 * subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 */
23#include <assert.h>
24#include <debug.h>
25#include <stdio.h>
26#include <stdlib.h>
27#include <string.h>
28#include <err.h>
29#include <lib/console.h>
30#include <lib/bio.h>
31#include <lib/partition.h>
32#include <platform.h>
33#include <kernel/thread.h>
34
35#if WITH_LIB_CKSUM
36#include <lib/cksum.h>
37#endif
38
39#define DMA_ALIGNMENT (CACHE_LINE)
40#define THREE_BYTE_ADDR_BOUNDARY (16777216)
41#define SUB_ERASE_TEST_SAMPLES (32)
42
43#if defined(WITH_LIB_CONSOLE)
44
45#if LK_DEBUGLEVEL > 0
46static int cmd_bio(int argc, const cmd_args *argv);
47static int bio_test_device(bdev_t* device);
48
49STATIC_COMMAND_START
50STATIC_COMMAND("bio", "block io debug commands", &cmd_bio)
51STATIC_COMMAND_END(bio);
52
53static int cmd_bio(int argc, const cmd_args *argv)
54{
55 int rc = 0;
56
57 if (argc < 2) {
58notenoughargs:
59 printf("not enough arguments:\n");
60usage:
61 printf("%s list\n", argv[0].str);
62 printf("%s read <device> <address> <offset> <len>\n", argv[0].str);
63 printf("%s write <device> <address> <offset> <len>\n", argv[0].str);
64 printf("%s dump <device> <offset> <len>\n", argv[0].str);
65 printf("%s erase <device> <offset> <len>\n", argv[0].str);
66 printf("%s ioctl <device> <request> <arg>\n", argv[0].str);
67 printf("%s remove <device>\n", argv[0].str);
68 printf("%s test <device>\n", argv[0].str);
69#if WITH_LIB_PARTITION
70 printf("%s partscan <device> [offset]\n", argv[0].str);
71#endif
72#if WITH_LIB_CKSUM
73 printf("%s crc32 <device> <offset> <len> [repeat]\n", argv[0].str);
74#endif
75 return -1;
76 }
77
78 if (!strcmp(argv[1].str, "list")) {
79 bio_dump_devices();
80 } else if (!strcmp(argv[1].str, "read")) {
81 if (argc < 6) goto notenoughargs;
82
83 addr_t address = argv[3].u;
84 off_t offset = argv[4].u; // XXX use long
85 size_t len = argv[5].u;
86
87 bdev_t *dev = bio_open(argv[2].str);
88 if (!dev) {
89 printf("error opening block device\n");
90 return -1;
91 }
92
93 lk_time_t t = current_time();
94 ssize_t err = bio_read(dev, (void *)address, offset, len);
95 t = current_time() - t;
96 dprintf(INFO, "bio_read returns %d, took %u msecs (%d bytes/sec)\n", (int)err, (uint)t, (uint32_t)((uint64_t)err * 1000 / t));
97
98 bio_close(dev);
99
100 rc = err;
101 } else if (!strcmp(argv[1].str, "write")) {
102 if (argc < 6) goto notenoughargs;
103
104 addr_t address = argv[3].u;
105 off_t offset = argv[4].u; // XXX use long
106 size_t len = argv[5].u;
107
108 bdev_t *dev = bio_open(argv[2].str);
109 if (!dev) {
110 printf("error opening block device\n");
111 return -1;
112 }
113
114 lk_time_t t = current_time();
115 ssize_t err = bio_write(dev, (void *)address, offset, len);
116 t = current_time() - t;
117 dprintf(INFO, "bio_write returns %d, took %u msecs (%d bytes/sec)\n", (int)err, (uint)t, (uint32_t)((uint64_t)err * 1000 / t));
118
119 bio_close(dev);
120
121 rc = err;
122 } else if (!strcmp(argv[1].str, "dump")) {
123 if (argc < 5) {
124 printf("not enough arguments:\n");
125 goto usage;
126 }
127
128 off_t offset = argv[3].u; // XXX use long
129 size_t len = argv[4].u;
130
131 bdev_t *dev = bio_open(argv[2].str);
132 if (!dev) {
133 printf("error opening block device\n");
134 return -1;
135 }
136
137 uint8_t* buf = memalign(CACHE_LINE, 256);
138 ssize_t err = 0;
139 while (len > 0) {
140 size_t amt = MIN(256, len);
141 ssize_t err = bio_read(dev, buf, offset, amt);
142
143 if (err < 0) {
144 dprintf(ALWAYS, "read error %s %zu@%zu (err %d)\n",
145 argv[2].str, amt, (size_t)offset, (int)err);
146 break;
147 }
148
149 DEBUG_ASSERT((size_t)err <= amt);
150 hexdump8_ex(buf, err, offset);
151
152 if ((size_t)err != amt) {
153 dprintf(ALWAYS, "short read from %s @%zu (wanted %zu, got %zu)\n",
154 argv[2].str, (size_t)offset, amt, (size_t)err);
155 break;
156 }
157
158 offset += amt;
159 len -= amt;
160 }
161
162 bio_close(dev);
163 rc = err;
164 } else if (!strcmp(argv[1].str, "erase")) {
165 if (argc < 5) goto notenoughargs;
166
167 off_t offset = argv[3].u; // XXX use long
168 size_t len = argv[4].u;
169
170 bdev_t *dev = bio_open(argv[2].str);
171 if (!dev) {
172 printf("error opening block device\n");
173 return -1;
174 }
175
176 lk_time_t t = current_time();
177 ssize_t err = bio_erase(dev, offset, len);
178 t = current_time() - t;
179 dprintf(INFO, "bio_erase returns %d, took %u msecs (%d bytes/sec)\n", (int)err, (uint)t, (uint32_t)((uint64_t)err * 1000 / t));
180
181 bio_close(dev);
182
183 rc = err;
184 } else if (!strcmp(argv[1].str, "ioctl")) {
185 if (argc < 4) goto notenoughargs;
186
187 int request = argv[3].u;
188 int arg = (argc == 5) ? argv[4].u : 0;
189
190 bdev_t *dev = bio_open(argv[2].str);
191 if (!dev) {
192 printf("error opening block device\n");
193 return -1;
194 }
195
196 int err = bio_ioctl(dev, request, (void *)(uintptr_t)arg);
197 dprintf(INFO, "bio_ioctl returns %d\n", err);
198
199 bio_close(dev);
200
201 rc = err;
202 } else if (!strcmp(argv[1].str, "remove")) {
203 if (argc < 3) goto notenoughargs;
204
205 bdev_t *dev = bio_open(argv[2].str);
206 if (!dev) {
207 printf("error opening block device\n");
208 return -1;
209 }
210
211 bio_unregister_device(dev);
212 bio_close(dev);
213 } else if (!strcmp(argv[1].str, "test")) {
214 if (argc < 3) goto notenoughargs;
215
216 bdev_t *dev = bio_open(argv[2].str);
217 if (!dev) {
218 printf("error opening block device\n");
219 return -1;
220 }
221
222 int err = bio_test_device(dev);
223 bio_close(dev);
224
225 rc = err;
226#if WITH_LIB_PARTITION
227 } else if (!strcmp(argv[1].str, "partscan")) {
228 if (argc < 3) goto notenoughargs;
229
230 off_t offset = 0;
231 if (argc > 3)
232 offset = argv[3].u;
233
234 rc = partition_publish(argv[2].str, offset);
235 dprintf(INFO, "partition_publish returns %d\n", rc);
236#endif
237#if WITH_LIB_CKSUM
238 } else if (!strcmp(argv[1].str, "crc32")) {
239 if (argc < 5) goto notenoughargs;
240
241 off_t offset = argv[3].u; // XXX use long
242 size_t len = argv[4].u;
243
244 bdev_t *dev = bio_open(argv[2].str);
245 if (!dev) {
246 printf("error opening block device\n");
247 return -1;
248 }
249
250 void *buf = malloc(dev->block_size);
251
252 bool repeat = false;
253 if (argc >= 6 && !strcmp(argv[5].str, "repeat")) {
254 repeat = true;
255 }
256
257 do {
258 ulong crc = 0;
259 off_t pos = offset;
260 while (pos < offset + (off_t)len) {
261 ssize_t err = bio_read(dev, buf, pos, MIN(len - (pos - offset), dev->block_size));
262
263 if (err <= 0) {
264 printf("error reading at offset 0x%llx\n", offset + pos);
265 break;
266 }
267
268 crc = crc32(crc, buf, err);
269
270 pos += err;
271 }
272
273 printf("crc 0x%08lx\n", crc);
274 } while (repeat);
275
276 bio_close(dev);
277 free(buf);
278
279#endif
280 } else {
281 printf("unrecognized subcommand\n");
282 goto usage;
283 }
284
285 return rc;
286}
287
288#endif
289
290#endif
291
292// Returns the number of blocks that do not match the reference pattern.
293static bool is_valid_block(bdev_t *device, bnum_t block_num, uint8_t* pattern,
294 size_t pattern_length)
295{
296 uint8_t *block_contents = memalign(DMA_ALIGNMENT, device->block_size);
297 if (!block_contents) {
298 return false;
299 }
300
301 ssize_t n_bytes = device->read_block(device, block_contents, block_num, 1);
302 if (n_bytes < 0 || n_bytes != (ssize_t)device->block_size) {
303 free(block_contents);
304 return false;
305 }
306
307 for (size_t i = 0; i < device->block_size; i++) {
308 if (block_contents[i] != pattern[i % pattern_length]) {
309 free(block_contents);
310 block_contents = NULL;
311 return false;
312 }
313 }
314
315 free(block_contents);
316 block_contents = NULL;
317
318 return true;
319}
320
321static ssize_t erase_test(bdev_t *device)
322{
323 printf("erasing device...\n");
324
325 ssize_t err = bio_erase(device, 0, device->total_size);
326 if (err < 0) {
327 return err;
328 } else if (err != device->total_size) {
329 return ERR_IO;
330 }
331
332 printf("validating erase...\n");
333 size_t num_invalid_blocks = 0;
334 for (bnum_t bnum = 0; bnum < device->block_count; bnum++) {
335 if (!is_valid_block(device, bnum, &device->erase_byte, sizeof(device->erase_byte))) {
336 num_invalid_blocks++;
337 }
338 }
339 return num_invalid_blocks;
340}
341
342static bool test_erase_block(bdev_t* device, uint32_t block_addr)
343{
344 bool success = false;
345 uint8_t valid_byte[1];
346
347 uint8_t *block_contents = memalign(DMA_ALIGNMENT, device->block_size);
348 if (!block_contents)
349 return success;
350 memset(block_contents, ~(device->erase_byte), device->block_size);
351
352 ssize_t err = bio_write_block(device, block_contents, block_addr, 1);
353 if (err != (ssize_t)device->block_size) {
354 goto finish;
355 }
356
357 valid_byte[0] = ~(device->erase_byte);
358 if (!is_valid_block(device, block_addr, valid_byte, 1)) {
359 goto finish;
360 }
361
362 err = bio_erase(device, block_addr * device->block_size, 1);
363 if (err <= 0) {
364 goto finish;
365 }
366
367 valid_byte[0] = device->erase_byte;
368 if (is_valid_block(device, block_addr, valid_byte, 1)) {
369 success = true;
370 }
371
372finish:
373 free(block_contents);
374 return success;
375}
376
377// Ensure that (sub)sector erase work.
378static bool sub_erase_test(bdev_t* device, uint32_t n_samples)
379{
380 printf("Sampling the device %d times.\n", n_samples);
381 for (uint32_t i = 0; i < n_samples; i++) {
382 bnum_t block_addr = rand() % device->block_count;
383 if (!test_erase_block(device, block_addr)) {
384 return false;
385 }
386 }
387 return true;
388}
389
390static uint8_t get_signature(uint32_t word)
391{
392 uint8_t* sigptr = (uint8_t*)(&word);
393 return sigptr[0] ^ sigptr[1] ^ sigptr[2] ^ sigptr[3];
394}
395
396// returns the number of blocks where the write was not successful.
397static ssize_t write_test(bdev_t *device)
398{
399 uint8_t *test_buffer = memalign(DMA_ALIGNMENT, device->block_size);
400
401 if (!test_buffer)
402 return -1;
403
404 for (bnum_t bnum = 0; bnum < device->block_count; bnum++) {
405 memset(test_buffer, get_signature(bnum), device->block_size);
406 ssize_t err = bio_write_block(device, test_buffer, bnum, 1);
407 if (err < 0) {
408 free(test_buffer);
409 return err;
410 }
411 }
412
413 size_t num_errors = 0;
414 uint8_t expected_pattern[1];
415 for (bnum_t bnum = 0; bnum < device->block_count; bnum++) {
416 expected_pattern[0] = get_signature(bnum);
417 if (!is_valid_block(device, bnum, expected_pattern, sizeof(expected_pattern))) {
418 num_errors++;
419 }
420 }
421
422 free(test_buffer);
423
424 return num_errors;
425}
426
427static int bio_test_device(bdev_t* device)
428{
429 ssize_t num_errors = erase_test(device);
430 if (num_errors < 0) {
431 printf("error %ld performing erase test\n", num_errors);
432 return -1;
433 }
434 printf("discovered %ld error(s) while testing erase.\n", num_errors);
435 if (num_errors) {
436 // No point in continuing the tests if we couldn't erase the device.
437 printf("not continuing to test writes.\n");
438 return -1;
439 }
440
441 num_errors = write_test(device);
442 printf("Discovered %ld error(s) while testing write.\n", num_errors);
443 if (num_errors) {
444 return -1;
445 }
446
447 printf ("Testing sub-erase...\n");
448 bool success = sub_erase_test(device, SUB_ERASE_TEST_SAMPLES);
449 if (!success) {
450 printf("Discovered errors while testing sub-erase.\n");
451 return -1;
452 } else {
453 printf("No errors while testing sub-erase.\n");
454 }
455
456 return 0;
457}