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