1 /*
2 * Copyright (c) 2009-2014 Travis Geiselbrecht
3 *
4 * Use of this source code is governed by a MIT-style
5 * license that can be found in the LICENSE file or at
6 * https://opensource.org/licenses/MIT
7 */
8 #include <assert.h>
9 #include <lk/debug.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <lk/err.h>
14 #include <lk/console_cmd.h>
15 #include <lib/bio.h>
16 #include <platform.h>
17 #include <kernel/thread.h>
18
19 #if WITH_LIB_CKSUM
20 #include <lib/cksum.h>
21 #endif
22 #if WITH_LIB_PARTITION
23 #include <lib/partition.h>
24 #endif
25
26 #define DMA_ALIGNMENT (CACHE_LINE)
27 #define THREE_BYTE_ADDR_BOUNDARY (16777216)
28 #define SUB_ERASE_TEST_SAMPLES (32)
29
30 #if LK_DEBUGLEVEL > 0
31 static int cmd_bio(int argc, const console_cmd_args *argv);
32 static int bio_test_device(bdev_t *device);
33
34 STATIC_COMMAND_START
35 STATIC_COMMAND("bio", "block io debug commands", &cmd_bio)
36 STATIC_COMMAND_END(bio);
37
cmd_bio(int argc,const console_cmd_args * argv)38 static int cmd_bio(int argc, const console_cmd_args *argv) {
39 int rc = 0;
40
41 if (argc < 2) {
42 notenoughargs:
43 printf("not enough arguments:\n");
44 usage:
45 printf("%s list\n", argv[0].str);
46 printf("%s read <device> <address> <offset> <len>\n", argv[0].str);
47 printf("%s write <device> <address> <offset> <len>\n", argv[0].str);
48 printf("%s dump <device> <offset> <len>\n", argv[0].str);
49 printf("%s erase <device> <offset> <len>\n", argv[0].str);
50 printf("%s ioctl <device> <request> <arg>\n", argv[0].str);
51 printf("%s remove <device>\n", argv[0].str);
52 printf("%s test <device>\n", argv[0].str);
53 #if WITH_LIB_PARTITION
54 printf("%s partscan <device> [offset]\n", argv[0].str);
55 #endif
56 #if WITH_LIB_CKSUM
57 printf("%s crc32 <device> <offset> <len> [repeat]\n", argv[0].str);
58 #endif
59 return -1;
60 }
61
62 if (!strcmp(argv[1].str, "list")) {
63 bio_dump_devices();
64 } else if (!strcmp(argv[1].str, "read")) {
65 if (argc < 6) goto notenoughargs;
66
67 addr_t address = argv[3].u;
68 off_t offset = argv[4].u; // XXX use long
69 size_t len = argv[5].u;
70
71 bdev_t *dev = bio_open(argv[2].str);
72 if (!dev) {
73 printf("error opening block device\n");
74 return -1;
75 }
76
77 lk_time_t t = current_time();
78 ssize_t err = bio_read(dev, (void *)address, offset, len);
79 t = current_time() - t;
80 dprintf(INFO, "bio_read returns %d, took %u msecs (%d bytes/sec)\n", (int)err, (uint)t, (uint32_t)((uint64_t)err * 1000 / t));
81
82 bio_close(dev);
83
84 rc = err;
85 } else if (!strcmp(argv[1].str, "write")) {
86 if (argc < 6) goto notenoughargs;
87
88 addr_t address = argv[3].u;
89 off_t offset = argv[4].u; // XXX use long
90 size_t len = argv[5].u;
91
92 bdev_t *dev = bio_open(argv[2].str);
93 if (!dev) {
94 printf("error opening block device\n");
95 return -1;
96 }
97
98 lk_time_t t = current_time();
99 ssize_t err = bio_write(dev, (void *)address, offset, len);
100 t = current_time() - t;
101 dprintf(INFO, "bio_write returns %d, took %u msecs (%d bytes/sec)\n", (int)err, (uint)t, (uint32_t)((uint64_t)err * 1000 / t));
102
103 bio_close(dev);
104
105 rc = err;
106 } else if (!strcmp(argv[1].str, "dump")) {
107 if (argc < 5) {
108 printf("not enough arguments:\n");
109 goto usage;
110 }
111
112 off_t offset = argv[3].u; // XXX use long
113 size_t len = argv[4].u;
114
115 bdev_t *dev = bio_open(argv[2].str);
116 if (!dev) {
117 printf("error opening block device\n");
118 return -1;
119 }
120
121 uint8_t *buf = memalign(CACHE_LINE, 256);
122 ssize_t err = 0;
123 while (len > 0) {
124 size_t amt = MIN(256, len);
125 ssize_t err_len = bio_read(dev, buf, offset, amt);
126
127 if (err_len < 0) {
128 dprintf(ALWAYS, "read error %s %zu@%zu (err_len %ld)\n",
129 argv[2].str, amt, (size_t)offset, err_len);
130 break;
131 }
132
133 DEBUG_ASSERT((size_t)err_len <= amt);
134 hexdump8_ex(buf, err_len, offset);
135
136 if ((size_t)err_len != amt) {
137 dprintf(ALWAYS, "short read from %s @%zu (wanted %zu, got %zu)\n",
138 argv[2].str, (size_t)offset, amt, (size_t)err);
139 break;
140 }
141
142 offset += amt;
143 len -= amt;
144 }
145
146 bio_close(dev);
147 rc = err;
148 } else if (!strcmp(argv[1].str, "erase")) {
149 if (argc < 5) goto notenoughargs;
150
151 off_t offset = argv[3].u; // XXX use long
152 size_t len = argv[4].u;
153
154 bdev_t *dev = bio_open(argv[2].str);
155 if (!dev) {
156 printf("error opening block device\n");
157 return -1;
158 }
159
160 lk_time_t t = current_time();
161 ssize_t err = bio_erase(dev, offset, len);
162 t = current_time() - t;
163 dprintf(INFO, "bio_erase returns %d, took %u msecs (%d bytes/sec)\n", (int)err, (uint)t, (uint32_t)((uint64_t)err * 1000 / t));
164
165 bio_close(dev);
166
167 rc = err;
168 } else if (!strcmp(argv[1].str, "ioctl")) {
169 if (argc < 4) goto notenoughargs;
170
171 int request = argv[3].u;
172 unsigned long arg = (argc == 5) ? argv[4].u : 0;
173
174 bdev_t *dev = bio_open(argv[2].str);
175 if (!dev) {
176 printf("error opening block device\n");
177 return -1;
178 }
179
180 int err = bio_ioctl(dev, request, (void *)arg);
181 dprintf(INFO, "bio_ioctl returns %d\n", err);
182
183 bio_close(dev);
184
185 rc = err;
186 } else if (!strcmp(argv[1].str, "remove")) {
187 if (argc < 3) goto notenoughargs;
188
189 bdev_t *dev = bio_open(argv[2].str);
190 if (!dev) {
191 printf("error opening block device\n");
192 return -1;
193 }
194
195 bio_unregister_device(dev);
196 bio_close(dev);
197 } else if (!strcmp(argv[1].str, "test")) {
198 if (argc < 3) goto notenoughargs;
199
200 bdev_t *dev = bio_open(argv[2].str);
201 if (!dev) {
202 printf("error opening block device\n");
203 return -1;
204 }
205
206 int err = bio_test_device(dev);
207 bio_close(dev);
208
209 rc = err;
210 #if WITH_LIB_PARTITION
211 } else if (!strcmp(argv[1].str, "partscan")) {
212 if (argc < 3) goto notenoughargs;
213
214 off_t offset = 0;
215 if (argc > 3)
216 offset = argv[3].u;
217
218 rc = partition_publish(argv[2].str, offset);
219 dprintf(INFO, "partition_publish returns %d\n", rc);
220 #endif
221 #if WITH_LIB_CKSUM
222 } else if (!strcmp(argv[1].str, "crc32")) {
223 if (argc < 5) goto notenoughargs;
224
225 unsigned long offset = argv[3].u;
226 size_t len = argv[4].u;
227
228 bdev_t *dev = bio_open(argv[2].str);
229 if (!dev) {
230 printf("error opening block device\n");
231 return -1;
232 }
233
234 void *buf = malloc(dev->block_size);
235
236 bool repeat = false;
237 if (argc >= 6 && !strcmp(argv[5].str, "repeat")) {
238 repeat = true;
239 }
240
241 do {
242 ulong crc = 0;
243 unsigned long pos = offset;
244 while (pos < offset + len) {
245 ssize_t err = bio_read(dev, buf, pos, MIN(len - (pos - offset), dev->block_size));
246
247 if (err <= 0) {
248 printf("error reading at offset 0x%lx\n", offset + pos);
249 break;
250 }
251
252 crc = crc32(crc, buf, err);
253
254 pos += err;
255 }
256
257 printf("crc 0x%08lx\n", crc);
258 } while (repeat);
259
260 bio_close(dev);
261 free(buf);
262
263 #endif
264 } else {
265 printf("unrecognized subcommand\n");
266 goto usage;
267 }
268
269 return rc;
270 }
271
272 #endif
273
274 // Returns the number of blocks that do not match the reference pattern.
is_valid_block(bdev_t * device,bnum_t block_num,uint8_t * pattern,size_t pattern_length)275 static bool is_valid_block(bdev_t *device, bnum_t block_num, uint8_t *pattern,
276 size_t pattern_length) {
277 uint8_t *block_contents = memalign(DMA_ALIGNMENT, device->block_size);
278
279 ssize_t n_bytes = device->read_block(device, block_contents, block_num, 1);
280 if (n_bytes < 0 || n_bytes != (ssize_t)device->block_size) {
281 free(block_contents);
282 return false;
283 }
284
285 for (size_t i = 0; i < device->block_size; i++) {
286 if (block_contents[i] != pattern[i % pattern_length]) {
287 free(block_contents);
288 block_contents = NULL;
289 return false;
290 }
291 }
292
293 free(block_contents);
294 block_contents = NULL;
295
296 return true;
297 }
298
erase_test(bdev_t * device)299 static ssize_t erase_test(bdev_t *device) {
300 printf("erasing device...\n");
301
302 ssize_t err = bio_erase(device, 0, device->total_size);
303 if (err < 0) {
304 return err;
305 } else if (err != device->total_size) {
306 return ERR_IO;
307 }
308
309 printf("validating erase...\n");
310 size_t num_invalid_blocks = 0;
311 for (bnum_t bnum = 0; bnum < device->block_count; bnum++) {
312 if (!is_valid_block(device, bnum, &device->erase_byte, sizeof(device->erase_byte))) {
313 num_invalid_blocks++;
314 }
315 }
316 return num_invalid_blocks;
317 }
318
test_erase_block(bdev_t * device,uint32_t block_addr)319 static bool test_erase_block(bdev_t *device, uint32_t block_addr) {
320 bool success = false;
321 uint8_t valid_byte[1];
322
323 uint8_t *block_contents = memalign(DMA_ALIGNMENT, device->block_size);
324 memset(block_contents, ~(device->erase_byte), device->block_size);
325
326 ssize_t err = bio_write_block(device, block_contents, block_addr, 1);
327 if (err != (ssize_t)device->block_size) {
328 goto finish;
329 }
330
331 valid_byte[0] = ~(device->erase_byte);
332 if (!is_valid_block(device, block_addr, valid_byte, 1)) {
333 goto finish;
334 }
335
336 err = bio_erase(device, block_addr * device->block_size, 1);
337 if (err <= 0) {
338 goto finish;
339 }
340
341 valid_byte[0] = device->erase_byte;
342 if (is_valid_block(device, block_addr, valid_byte, 1)) {
343 success = true;
344 }
345
346 finish:
347 free(block_contents);
348 return success;
349 }
350
351 // Ensure that (sub)sector erase work.
sub_erase_test(bdev_t * device,uint32_t n_samples)352 static bool sub_erase_test(bdev_t *device, uint32_t n_samples) {
353 printf("Sampling the device %d times.\n", n_samples);
354 for (uint32_t i = 0; i < n_samples; i++) {
355 bnum_t block_addr = rand() % device->block_count;
356 if (!test_erase_block(device, block_addr)) {
357 return false;
358 }
359 }
360 return true;
361 }
362
get_signature(uint32_t word)363 static uint8_t get_signature(uint32_t word) {
364 uint8_t *sigptr = (uint8_t *)(&word);
365 return sigptr[0] ^ sigptr[1] ^ sigptr[2] ^ sigptr[3];
366 }
367
368 // returns the number of blocks where the write was not successful.
write_test(bdev_t * device)369 static ssize_t write_test(bdev_t *device) {
370 uint8_t *test_buffer = memalign(DMA_ALIGNMENT, device->block_size);
371
372 for (bnum_t bnum = 0; bnum < device->block_count; bnum++) {
373 memset(test_buffer, get_signature(bnum), device->block_size);
374 ssize_t err = bio_write_block(device, test_buffer, bnum, 1);
375 if (err < 0) {
376 free(test_buffer);
377 return err;
378 }
379 }
380
381 size_t num_errors = 0;
382 uint8_t expected_pattern[1];
383 for (bnum_t bnum = 0; bnum < device->block_count; bnum++) {
384 expected_pattern[0] = get_signature(bnum);
385 if (!is_valid_block(device, bnum, expected_pattern, sizeof(expected_pattern))) {
386 num_errors++;
387 }
388 }
389
390 free(test_buffer);
391
392 return num_errors;
393 }
394
memory_mapped_test(bdev_t * device)395 static status_t memory_mapped_test(bdev_t *device) {
396 status_t retcode = NO_ERROR;
397
398 uint8_t *test_buffer = memalign(DMA_ALIGNMENT, device->block_size);
399 if (!test_buffer) {
400 printf("Could not allocate %zu bytes for a temporary buffer. "
401 "Aborting.\n", device->block_size);
402 return ERR_NO_MEMORY;
403 }
404
405 uint8_t *reference_buffer = memalign(DMA_ALIGNMENT, device->block_size);
406 if (!reference_buffer) {
407 printf("Could not allocate %zu bytes for a temporary reference "
408 "buffer. Aborting.\n", device->block_size);
409 free(test_buffer);
410 return ERR_NO_MEMORY;
411 }
412
413 // Erase the first page of the Device.
414 ssize_t err = bio_erase(device, 0, device->block_size);
415 if (err < (ssize_t)device->block_size) {
416 printf("Expected to erase at least %zu bytes but only erased %ld. "
417 "Not continuing to test memory mapped mode.\n",
418 device->block_size, err);
419 retcode = ERR_IO;
420 goto finish;
421 }
422
423 // Write a pattern to the first page of the device.
424 uint8_t pattern_seed = (uint8_t)(rand() % 256);
425
426 for (size_t i = 0; i < device->block_size; i++) {
427 test_buffer[i] = (uint8_t)((pattern_seed + i) % 256);
428 }
429
430 err = bio_write_block(device, test_buffer, 0, 1);
431 if (err != (ssize_t)device->block_size) {
432 printf("Error while writing test pattern to device. Expected to write "
433 "%zu bytes but actually wrote %ld. Not continuing to test memory "
434 "mapped mode.\n", device->block_size, err);
435 retcode = ERR_IO;
436 goto finish;
437 }
438
439 // Put the device into linear mode if possible.
440 uint8_t *devaddr;
441 int ioctl_result = bio_ioctl(device, BIO_IOCTL_GET_MEM_MAP, (void *)&devaddr);
442 if (ioctl_result == ERR_NOT_SUPPORTED) {
443 printf("Device does not support linear mode. Aborting.\n");
444 retcode = ERR_NOT_SUPPORTED;
445 goto finish;
446 } else if (ioctl_result != NO_ERROR) {
447 printf("BIO_IOCTL_GET_MEM_MAP returned error %d. Aborting.\n",
448 ioctl_result);
449 retcode = ioctl_result;
450 goto finish;
451 }
452
453 uint8_t *testptr = test_buffer;
454 for (uint i = 0; i < device->block_size; i++) {
455 if (*testptr != *devaddr) {
456 printf("Data mismatch at position %d. Expected %d got %d. "
457 "Aborting.\n", i, *testptr, *devaddr);
458 goto finish;
459 }
460 testptr++;
461 devaddr++;
462 }
463
464 // Put the device back into command mode.
465 ioctl_result = bio_ioctl(device, BIO_IOCTL_PUT_MEM_MAP, NULL);
466 if (ioctl_result != NO_ERROR) {
467 printf("BIO_IOCTL_GET_MEM_MAP returned error %d. Aborting.\n",
468 ioctl_result);
469 retcode = ioctl_result;
470 goto finish;
471 }
472
473 // Read the first page into memory using command mode and compare it with
474 // what we wrote back earlier.
475 err = bio_read_block(device, reference_buffer, 0, 1);
476 if (err != (ssize_t)device->block_size) {
477 printf("Expected to read %zu bytes, actually read %ld. Aborting.\n",
478 device->block_size, err);
479 retcode = ERR_IO;
480 goto finish;
481 }
482
483 uint8_t *expected = test_buffer;
484 uint8_t *actual = reference_buffer;
485 for (uint i = 0; i < device->block_size; i++) {
486 if (*actual != *expected) {
487 printf("Data mismatch at position %d. Expected %d got %d. "
488 "Aborting.\n", i, *expected, *actual);
489 goto finish;
490 }
491 expected++;
492 actual++;
493 }
494
495 finish:
496 free(test_buffer);
497 free(reference_buffer);
498 return retcode;
499 }
500
bio_test_device(bdev_t * device)501 static int bio_test_device(bdev_t *device) {
502 ssize_t num_errors = erase_test(device);
503 if (num_errors < 0) {
504 printf("error %ld performing erase test\n", num_errors);
505 return -1;
506 }
507 printf("discovered %ld error(s) while testing erase.\n", num_errors);
508 if (num_errors) {
509 // No point in continuing the tests if we couldn't erase the device.
510 printf("not continuing to test writes.\n");
511 return -1;
512 }
513
514 num_errors = write_test(device);
515 printf("Discovered %ld error(s) while testing write.\n", num_errors);
516 if (num_errors) {
517 return -1;
518 }
519
520 printf ("Testing sub-erase...\n");
521 bool success = sub_erase_test(device, SUB_ERASE_TEST_SAMPLES);
522 if (!success) {
523 printf("Discovered errors while testing sub-erase.\n");
524 return -1;
525 } else {
526 printf("No errors while testing sub-erase.\n");
527 }
528
529 printf("Testing memory mapped mode...\n");
530 status_t test_result = memory_mapped_test(device);
531 if (test_result != NO_ERROR) {
532 printf("Memory mapped test returned error %d\n", test_result);
533 } else {
534 printf("Memory mapped mode tests returned successfully\n");
535 }
536
537 return 0;
538 }
539