]>
Commit | Line | Data |
---|---|---|
5db66b3a MR |
1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* | |
3 | * mtd.c | |
4 | * | |
5 | * Generic command to handle basic operations on any memory device. | |
6 | * | |
7 | * Copyright: Bootlin, 2018 | |
8 | * Author: Miquèl Raynal <[email protected]> | |
9 | */ | |
10 | ||
11 | #include <command.h> | |
12 | #include <common.h> | |
13 | #include <console.h> | |
14 | #include <malloc.h> | |
15 | #include <mapmem.h> | |
16 | #include <mtd.h> | |
17 | ||
9671243e BB |
18 | #include <linux/ctype.h> |
19 | ||
20 | static struct mtd_info *get_mtd_by_name(const char *name) | |
21 | { | |
22 | struct mtd_info *mtd; | |
23 | ||
24 | mtd_probe_devices(); | |
25 | ||
26 | mtd = get_mtd_device_nm(name); | |
27 | if (IS_ERR_OR_NULL(mtd)) | |
28 | printf("MTD device %s not found, ret %ld\n", name, | |
29 | PTR_ERR(mtd)); | |
30 | ||
31 | return mtd; | |
32 | } | |
33 | ||
5db66b3a MR |
34 | static uint mtd_len_to_pages(struct mtd_info *mtd, u64 len) |
35 | { | |
36 | do_div(len, mtd->writesize); | |
37 | ||
38 | return len; | |
39 | } | |
40 | ||
41 | static bool mtd_is_aligned_with_min_io_size(struct mtd_info *mtd, u64 size) | |
42 | { | |
43 | return !do_div(size, mtd->writesize); | |
44 | } | |
45 | ||
46 | static bool mtd_is_aligned_with_block_size(struct mtd_info *mtd, u64 size) | |
47 | { | |
48 | return !do_div(size, mtd->erasesize); | |
49 | } | |
50 | ||
51 | static void mtd_dump_buf(const u8 *buf, uint len, uint offset) | |
52 | { | |
53 | int i, j; | |
54 | ||
55 | for (i = 0; i < len; ) { | |
56 | printf("0x%08x:\t", offset + i); | |
57 | for (j = 0; j < 8; j++) | |
58 | printf("%02x ", buf[i + j]); | |
59 | printf(" "); | |
60 | i += 8; | |
61 | for (j = 0; j < 8; j++) | |
62 | printf("%02x ", buf[i + j]); | |
63 | printf("\n"); | |
64 | i += 8; | |
65 | } | |
66 | } | |
67 | ||
68 | static void mtd_dump_device_buf(struct mtd_info *mtd, u64 start_off, | |
69 | const u8 *buf, u64 len, bool woob) | |
70 | { | |
71 | bool has_pages = mtd->type == MTD_NANDFLASH || | |
72 | mtd->type == MTD_MLCNANDFLASH; | |
73 | int npages = mtd_len_to_pages(mtd, len); | |
74 | uint page; | |
75 | ||
76 | if (has_pages) { | |
77 | for (page = 0; page < npages; page++) { | |
78 | u64 data_off = page * mtd->writesize; | |
79 | ||
80 | printf("\nDump %d data bytes from 0x%08llx:\n", | |
81 | mtd->writesize, start_off + data_off); | |
82 | mtd_dump_buf(&buf[data_off], | |
83 | mtd->writesize, start_off + data_off); | |
84 | ||
85 | if (woob) { | |
86 | u64 oob_off = page * mtd->oobsize; | |
87 | ||
88 | printf("Dump %d OOB bytes from page at 0x%08llx:\n", | |
89 | mtd->oobsize, start_off + data_off); | |
90 | mtd_dump_buf(&buf[len + oob_off], | |
91 | mtd->oobsize, 0); | |
92 | } | |
93 | } | |
94 | } else { | |
95 | printf("\nDump %lld data bytes from 0x%llx:\n", | |
96 | len, start_off); | |
97 | mtd_dump_buf(buf, len, start_off); | |
98 | } | |
99 | } | |
100 | ||
101 | static void mtd_show_parts(struct mtd_info *mtd, int level) | |
102 | { | |
103 | struct mtd_info *part; | |
104 | int i; | |
105 | ||
106 | list_for_each_entry(part, &mtd->partitions, node) { | |
107 | for (i = 0; i < level; i++) | |
108 | printf("\t"); | |
109 | printf(" - 0x%012llx-0x%012llx : \"%s\"\n", | |
110 | part->offset, part->offset + part->size, part->name); | |
111 | ||
112 | mtd_show_parts(part, level + 1); | |
113 | } | |
114 | } | |
115 | ||
116 | static void mtd_show_device(struct mtd_info *mtd) | |
117 | { | |
118 | /* Device */ | |
119 | printf("* %s\n", mtd->name); | |
120 | #if defined(CONFIG_DM) | |
121 | if (mtd->dev) { | |
122 | printf(" - device: %s\n", mtd->dev->name); | |
123 | printf(" - parent: %s\n", mtd->dev->parent->name); | |
124 | printf(" - driver: %s\n", mtd->dev->driver->name); | |
125 | } | |
126 | #endif | |
127 | ||
128 | /* MTD device information */ | |
129 | printf(" - type: "); | |
130 | switch (mtd->type) { | |
131 | case MTD_RAM: | |
132 | printf("RAM\n"); | |
133 | break; | |
134 | case MTD_ROM: | |
135 | printf("ROM\n"); | |
136 | break; | |
137 | case MTD_NORFLASH: | |
138 | printf("NOR flash\n"); | |
139 | break; | |
140 | case MTD_NANDFLASH: | |
141 | printf("NAND flash\n"); | |
142 | break; | |
143 | case MTD_DATAFLASH: | |
144 | printf("Data flash\n"); | |
145 | break; | |
146 | case MTD_UBIVOLUME: | |
147 | printf("UBI volume\n"); | |
148 | break; | |
149 | case MTD_MLCNANDFLASH: | |
150 | printf("MLC NAND flash\n"); | |
151 | break; | |
152 | case MTD_ABSENT: | |
153 | default: | |
154 | printf("Unknown\n"); | |
155 | break; | |
156 | } | |
157 | ||
158 | printf(" - block size: 0x%x bytes\n", mtd->erasesize); | |
159 | printf(" - min I/O: 0x%x bytes\n", mtd->writesize); | |
160 | ||
161 | if (mtd->oobsize) { | |
162 | printf(" - OOB size: %u bytes\n", mtd->oobsize); | |
163 | printf(" - OOB available: %u bytes\n", mtd->oobavail); | |
164 | } | |
165 | ||
166 | if (mtd->ecc_strength) { | |
167 | printf(" - ECC strength: %u bits\n", mtd->ecc_strength); | |
168 | printf(" - ECC step size: %u bytes\n", mtd->ecc_step_size); | |
169 | printf(" - bitflip threshold: %u bits\n", | |
170 | mtd->bitflip_threshold); | |
171 | } | |
172 | ||
173 | printf(" - 0x%012llx-0x%012llx : \"%s\"\n", | |
174 | mtd->offset, mtd->offset + mtd->size, mtd->name); | |
175 | ||
176 | /* MTD partitions, if any */ | |
177 | mtd_show_parts(mtd, 1); | |
178 | } | |
179 | ||
180 | /* Logic taken from fs/ubifs/recovery.c:is_empty() */ | |
181 | static bool mtd_oob_write_is_empty(struct mtd_oob_ops *op) | |
182 | { | |
183 | int i; | |
184 | ||
185 | for (i = 0; i < op->len; i++) | |
186 | if (op->datbuf[i] != 0xff) | |
187 | return false; | |
188 | ||
189 | for (i = 0; i < op->ooblen; i++) | |
190 | if (op->oobbuf[i] != 0xff) | |
191 | return false; | |
192 | ||
193 | return true; | |
194 | } | |
195 | ||
9671243e BB |
196 | static int do_mtd_list(cmd_tbl_t *cmdtp, int flag, int argc, |
197 | char * const argv[]) | |
5db66b3a MR |
198 | { |
199 | struct mtd_info *mtd; | |
200 | int dev_nb = 0; | |
201 | ||
202 | /* Ensure all devices (and their partitions) are probed */ | |
203 | mtd_probe_devices(); | |
204 | ||
205 | printf("List of MTD devices:\n"); | |
206 | mtd_for_each_device(mtd) { | |
207 | if (!mtd_is_partition(mtd)) | |
208 | mtd_show_device(mtd); | |
209 | ||
210 | dev_nb++; | |
211 | } | |
212 | ||
213 | if (!dev_nb) { | |
214 | printf("No MTD device found\n"); | |
215 | return CMD_RET_FAILURE; | |
216 | } | |
217 | ||
218 | return CMD_RET_SUCCESS; | |
219 | } | |
220 | ||
221 | static int mtd_special_write_oob(struct mtd_info *mtd, u64 off, | |
222 | struct mtd_oob_ops *io_op, | |
223 | bool write_empty_pages, bool woob) | |
224 | { | |
225 | int ret = 0; | |
226 | ||
227 | /* | |
228 | * By default, do not write an empty page. | |
229 | * Skip it by simulating a successful write. | |
230 | */ | |
231 | if (!write_empty_pages && mtd_oob_write_is_empty(io_op)) { | |
232 | io_op->retlen = mtd->writesize; | |
233 | io_op->oobretlen = woob ? mtd->oobsize : 0; | |
234 | } else { | |
235 | ret = mtd_write_oob(mtd, off, io_op); | |
236 | } | |
237 | ||
238 | return ret; | |
239 | } | |
240 | ||
9671243e | 241 | static int do_mtd_io(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) |
5db66b3a | 242 | { |
9671243e BB |
243 | bool dump, read, raw, woob, write_empty_pages, has_pages = false; |
244 | u64 start_off, off, len, remaining, default_len; | |
245 | struct mtd_oob_ops io_op = {}; | |
246 | uint user_addr = 0, npages; | |
247 | const char *cmd = argv[0]; | |
5db66b3a | 248 | struct mtd_info *mtd; |
9671243e BB |
249 | u32 oob_len; |
250 | u8 *buf; | |
251 | int ret; | |
5db66b3a | 252 | |
5db66b3a MR |
253 | if (argc < 2) |
254 | return CMD_RET_USAGE; | |
255 | ||
9671243e BB |
256 | mtd = get_mtd_by_name(argv[1]); |
257 | if (IS_ERR_OR_NULL(mtd)) | |
258 | return CMD_RET_FAILURE; | |
5db66b3a | 259 | |
9671243e BB |
260 | if (mtd->type == MTD_NANDFLASH || mtd->type == MTD_MLCNANDFLASH) |
261 | has_pages = true; | |
5db66b3a | 262 | |
9671243e BB |
263 | dump = !strncmp(cmd, "dump", 4); |
264 | read = dump || !strncmp(cmd, "read", 4); | |
265 | raw = strstr(cmd, ".raw"); | |
266 | woob = strstr(cmd, ".oob"); | |
267 | write_empty_pages = !has_pages || strstr(cmd, ".dontskipff"); | |
5db66b3a | 268 | |
9671243e BB |
269 | argc -= 2; |
270 | argv += 2; | |
5db66b3a | 271 | |
9671243e BB |
272 | if (!dump) { |
273 | if (!argc) { | |
274 | ret = CMD_RET_USAGE; | |
275 | goto out_put_mtd; | |
5db66b3a MR |
276 | } |
277 | ||
9671243e BB |
278 | user_addr = simple_strtoul(argv[0], NULL, 16); |
279 | argc--; | |
280 | argv++; | |
281 | } | |
5db66b3a | 282 | |
9671243e BB |
283 | start_off = argc > 0 ? simple_strtoul(argv[0], NULL, 16) : 0; |
284 | if (!mtd_is_aligned_with_min_io_size(mtd, start_off)) { | |
285 | printf("Offset not aligned with a page (0x%x)\n", | |
286 | mtd->writesize); | |
287 | ret = CMD_RET_FAILURE; | |
288 | goto out_put_mtd; | |
289 | } | |
5db66b3a | 290 | |
9671243e BB |
291 | default_len = dump ? mtd->writesize : mtd->size; |
292 | len = argc > 1 ? simple_strtoul(argv[1], NULL, 16) : default_len; | |
293 | if (!mtd_is_aligned_with_min_io_size(mtd, len)) { | |
294 | len = round_up(len, mtd->writesize); | |
295 | printf("Size not on a page boundary (0x%x), rounding to 0x%llx\n", | |
296 | mtd->writesize, len); | |
297 | } | |
5db66b3a | 298 | |
9671243e BB |
299 | remaining = len; |
300 | npages = mtd_len_to_pages(mtd, len); | |
301 | oob_len = woob ? npages * mtd->oobsize : 0; | |
5db66b3a | 302 | |
9671243e BB |
303 | if (dump) |
304 | buf = kmalloc(len + oob_len, GFP_KERNEL); | |
305 | else | |
306 | buf = map_sysmem(user_addr, 0); | |
307 | ||
308 | if (!buf) { | |
309 | printf("Could not map/allocate the user buffer\n"); | |
310 | ret = CMD_RET_FAILURE; | |
311 | goto out_put_mtd; | |
312 | } | |
313 | ||
314 | if (has_pages) | |
315 | printf("%s %lld byte(s) (%d page(s)) at offset 0x%08llx%s%s%s\n", | |
316 | read ? "Reading" : "Writing", len, npages, start_off, | |
317 | raw ? " [raw]" : "", woob ? " [oob]" : "", | |
318 | !read && write_empty_pages ? " [dontskipff]" : ""); | |
319 | else | |
320 | printf("%s %lld byte(s) at offset 0x%08llx\n", | |
321 | read ? "Reading" : "Writing", len, start_off); | |
322 | ||
323 | io_op.mode = raw ? MTD_OPS_RAW : MTD_OPS_AUTO_OOB; | |
324 | io_op.len = has_pages ? mtd->writesize : len; | |
325 | io_op.ooblen = woob ? mtd->oobsize : 0; | |
326 | io_op.datbuf = buf; | |
327 | io_op.oobbuf = woob ? &buf[len] : NULL; | |
328 | ||
329 | /* Search for the first good block after the given offset */ | |
330 | off = start_off; | |
331 | while (mtd_block_isbad(mtd, off)) | |
332 | off += mtd->erasesize; | |
333 | ||
334 | /* Loop over the pages to do the actual read/write */ | |
335 | while (remaining) { | |
336 | /* Skip the block if it is bad */ | |
337 | if (mtd_is_aligned_with_block_size(mtd, off) && | |
338 | mtd_block_isbad(mtd, off)) { | |
339 | off += mtd->erasesize; | |
340 | continue; | |
5db66b3a MR |
341 | } |
342 | ||
9671243e BB |
343 | if (read) |
344 | ret = mtd_read_oob(mtd, off, &io_op); | |
5db66b3a | 345 | else |
9671243e BB |
346 | ret = mtd_special_write_oob(mtd, off, &io_op, |
347 | write_empty_pages, woob); | |
5db66b3a | 348 | |
9671243e BB |
349 | if (ret) { |
350 | printf("Failure while %s at offset 0x%llx\n", | |
351 | read ? "reading" : "writing", off); | |
352 | break; | |
353 | } | |
5db66b3a | 354 | |
9671243e BB |
355 | off += io_op.retlen; |
356 | remaining -= io_op.retlen; | |
357 | io_op.datbuf += io_op.retlen; | |
358 | io_op.oobbuf += io_op.oobretlen; | |
359 | } | |
5db66b3a | 360 | |
9671243e BB |
361 | if (!ret && dump) |
362 | mtd_dump_device_buf(mtd, start_off, buf, len, woob); | |
5db66b3a | 363 | |
9671243e BB |
364 | if (dump) |
365 | kfree(buf); | |
366 | else | |
367 | unmap_sysmem(buf); | |
5db66b3a | 368 | |
9671243e BB |
369 | if (ret) { |
370 | printf("%s on %s failed with error %d\n", | |
371 | read ? "Read" : "Write", mtd->name, ret); | |
372 | ret = CMD_RET_FAILURE; | |
373 | } else { | |
374 | ret = CMD_RET_SUCCESS; | |
375 | } | |
5db66b3a | 376 | |
9671243e BB |
377 | out_put_mtd: |
378 | put_mtd_device(mtd); | |
5db66b3a | 379 | |
9671243e BB |
380 | return ret; |
381 | } | |
5db66b3a | 382 | |
9671243e BB |
383 | static int do_mtd_erase(cmd_tbl_t *cmdtp, int flag, int argc, |
384 | char * const argv[]) | |
385 | { | |
386 | struct erase_info erase_op = {}; | |
387 | struct mtd_info *mtd; | |
388 | u64 off, len; | |
389 | bool scrub; | |
390 | int ret; | |
5db66b3a | 391 | |
9671243e BB |
392 | if (argc < 2) |
393 | return CMD_RET_USAGE; | |
5db66b3a | 394 | |
9671243e BB |
395 | mtd = get_mtd_by_name(argv[1]); |
396 | if (IS_ERR_OR_NULL(mtd)) | |
397 | return CMD_RET_FAILURE; | |
5db66b3a | 398 | |
9671243e | 399 | scrub = strstr(argv[0], ".dontskipbad"); |
5db66b3a | 400 | |
9671243e BB |
401 | argc -= 2; |
402 | argv += 2; | |
5db66b3a | 403 | |
9671243e BB |
404 | off = argc > 0 ? simple_strtoul(argv[0], NULL, 16) : 0; |
405 | len = argc > 1 ? simple_strtoul(argv[1], NULL, 16) : mtd->size; | |
5db66b3a | 406 | |
9671243e BB |
407 | if (!mtd_is_aligned_with_block_size(mtd, off)) { |
408 | printf("Offset not aligned with a block (0x%x)\n", | |
409 | mtd->erasesize); | |
410 | ret = CMD_RET_FAILURE; | |
411 | goto out_put_mtd; | |
412 | } | |
5db66b3a | 413 | |
9671243e BB |
414 | if (!mtd_is_aligned_with_block_size(mtd, len)) { |
415 | printf("Size not a multiple of a block (0x%x)\n", | |
416 | mtd->erasesize); | |
417 | ret = CMD_RET_FAILURE; | |
418 | goto out_put_mtd; | |
419 | } | |
5db66b3a | 420 | |
9671243e BB |
421 | printf("Erasing 0x%08llx ... 0x%08llx (%d eraseblock(s))\n", |
422 | off, off + len - 1, mtd_div_by_eb(len, mtd)); | |
5db66b3a | 423 | |
9671243e BB |
424 | erase_op.mtd = mtd; |
425 | erase_op.addr = off; | |
426 | erase_op.len = len; | |
427 | erase_op.scrub = scrub; | |
5db66b3a | 428 | |
9671243e BB |
429 | while (erase_op.len) { |
430 | ret = mtd_erase(mtd, &erase_op); | |
5db66b3a | 431 | |
9671243e BB |
432 | /* Abort if its not a bad block error */ |
433 | if (ret != -EIO) | |
434 | break; | |
435 | ||
436 | printf("Skipping bad block at 0x%08llx\n", erase_op.fail_addr); | |
437 | ||
438 | /* Skip bad block and continue behind it */ | |
439 | erase_op.len -= erase_op.fail_addr - erase_op.addr; | |
440 | erase_op.len -= mtd->erasesize; | |
441 | erase_op.addr = erase_op.fail_addr + mtd->erasesize; | |
442 | } | |
443 | ||
444 | if (ret && ret != -EIO) | |
445 | ret = CMD_RET_FAILURE; | |
446 | else | |
447 | ret = CMD_RET_SUCCESS; | |
448 | ||
449 | out_put_mtd: | |
450 | put_mtd_device(mtd); | |
451 | ||
452 | return ret; | |
453 | } | |
454 | ||
455 | static int do_mtd_bad(cmd_tbl_t *cmdtp, int flag, int argc, | |
456 | char * const argv[]) | |
457 | { | |
458 | struct mtd_info *mtd; | |
459 | loff_t off; | |
460 | ||
461 | if (argc < 2) | |
5db66b3a | 462 | return CMD_RET_USAGE; |
9671243e BB |
463 | |
464 | mtd = get_mtd_by_name(argv[1]); | |
465 | if (IS_ERR_OR_NULL(mtd)) | |
466 | return CMD_RET_FAILURE; | |
467 | ||
468 | if (!mtd_can_have_bb(mtd)) { | |
469 | printf("Only NAND-based devices can have bad blocks\n"); | |
470 | goto out_put_mtd; | |
471 | } | |
472 | ||
473 | printf("MTD device %s bad blocks list:\n", mtd->name); | |
474 | for (off = 0; off < mtd->size; off += mtd->erasesize) { | |
475 | if (mtd_block_isbad(mtd, off)) | |
476 | printf("\t0x%08llx\n", off); | |
5db66b3a MR |
477 | } |
478 | ||
9671243e BB |
479 | out_put_mtd: |
480 | put_mtd_device(mtd); | |
481 | ||
5db66b3a MR |
482 | return CMD_RET_SUCCESS; |
483 | } | |
484 | ||
9671243e BB |
485 | #ifdef CONFIG_AUTO_COMPLETE |
486 | static int mtd_name_complete(int argc, char * const argv[], char last_char, | |
487 | int maxv, char *cmdv[]) | |
488 | { | |
489 | int len = 0, n_found = 0; | |
490 | struct mtd_info *mtd; | |
491 | ||
492 | argc--; | |
493 | argv++; | |
494 | ||
495 | if (argc > 1 || | |
496 | (argc == 1 && (last_char == '\0' || isblank(last_char)))) | |
497 | return 0; | |
498 | ||
499 | if (argc) | |
500 | len = strlen(argv[0]); | |
501 | ||
502 | mtd_for_each_device(mtd) { | |
503 | if (argc && | |
504 | (len > strlen(mtd->name) || | |
505 | strncmp(argv[0], mtd->name, len))) | |
506 | continue; | |
507 | ||
508 | if (n_found >= maxv - 2) { | |
509 | cmdv[n_found++] = "..."; | |
510 | break; | |
511 | } | |
512 | ||
513 | cmdv[n_found++] = mtd->name; | |
514 | } | |
515 | ||
516 | cmdv[n_found] = NULL; | |
517 | ||
518 | return n_found; | |
519 | } | |
520 | #endif /* CONFIG_AUTO_COMPLETE */ | |
521 | ||
5db66b3a | 522 | #ifdef CONFIG_SYS_LONGHELP |
a645831c | 523 | static char mtd_help_text[] = |
5db66b3a MR |
524 | "- generic operations on memory technology devices\n\n" |
525 | "mtd list\n" | |
526 | "mtd read[.raw][.oob] <name> <addr> [<off> [<size>]]\n" | |
527 | "mtd dump[.raw][.oob] <name> [<off> [<size>]]\n" | |
528 | "mtd write[.raw][.oob][.dontskipff] <name> <addr> [<off> [<size>]]\n" | |
529 | "mtd erase[.dontskipbad] <name> [<off> [<size>]]\n" | |
530 | "\n" | |
531 | "Specific functions:\n" | |
532 | "mtd bad <name>\n" | |
533 | "\n" | |
534 | "With:\n" | |
535 | "\t<name>: NAND partition/chip name\n" | |
536 | "\t<addr>: user address from/to which data will be retrieved/stored\n" | |
537 | "\t<off>: offset in <name> in bytes (default: start of the part)\n" | |
538 | "\t\t* must be block-aligned for erase\n" | |
539 | "\t\t* must be page-aligned otherwise\n" | |
540 | "\t<size>: length of the operation in bytes (default: the entire device)\n" | |
541 | "\t\t* must be a multiple of a block for erase\n" | |
542 | "\t\t* must be a multiple of a page otherwise (special case: default is a page with dump)\n" | |
543 | "\n" | |
a645831c | 544 | "The .dontskipff option forces writing empty pages, don't use it if unsure.\n"; |
5db66b3a | 545 | #endif |
5db66b3a | 546 | |
9671243e BB |
547 | U_BOOT_CMD_WITH_SUBCMDS(mtd, "MTD utils", mtd_help_text, |
548 | U_BOOT_SUBCMD_MKENT(list, 1, 1, do_mtd_list), | |
549 | U_BOOT_SUBCMD_MKENT_COMPLETE(read, 5, 0, do_mtd_io, | |
550 | mtd_name_complete), | |
551 | U_BOOT_SUBCMD_MKENT_COMPLETE(write, 5, 0, do_mtd_io, | |
552 | mtd_name_complete), | |
553 | U_BOOT_SUBCMD_MKENT_COMPLETE(dump, 4, 0, do_mtd_io, | |
554 | mtd_name_complete), | |
555 | U_BOOT_SUBCMD_MKENT_COMPLETE(erase, 4, 0, do_mtd_erase, | |
556 | mtd_name_complete), | |
557 | U_BOOT_SUBCMD_MKENT_COMPLETE(bad, 2, 1, do_mtd_bad, | |
558 | mtd_name_complete)); |