diff --git a/Documentation/user/updating.rst b/Documentation/user/updating.rst index 6a1a733..7aac0a9 100644 --- a/Documentation/user/updating.rst +++ b/Documentation/user/updating.rst @@ -30,3 +30,11 @@ **NOTE** barebox images can be enriched with metadata which can be used to check if a given image is suitable for updating barebox, see :ref:`imd`. + +Repairing existing boot images +------------------------------ + +Some SoCs allow to store multiple boot images on a device in order to +improve robustness. When an update handler supports it the handler can +repair and/or refresh an image from this redundant information. This is +done with the '-r' option to :ref:`command_barebox_update`. diff --git a/commands/Kconfig b/commands/Kconfig index 9519a44..875c5f4 100644 --- a/commands/Kconfig +++ b/commands/Kconfig @@ -694,7 +694,6 @@ config CMD_UBIFORMAT tristate depends on MTD_UBI - select LIBMTD select LIBSCAN select LIBUBIGEN prompt "ubiformat" @@ -1909,6 +1908,23 @@ -o OFFS start offset on flash -l LEN length of flash to test +config CMD_NAND_BITFLIP + tristate + depends on NAND + prompt "nand_bitflip" + help + nand_bitflip - Create bitflips on Nand pages. This command is useful for testing + purposes. + + Usage: nand_bitflip NANDDEV + + This command creates bitflips on Nand pages. + Options: + -b block to work on + -o offset in Nand + -r flip random bits + -n Specify maximum number of bitflips to generate + config CMD_POWEROFF tristate depends on HAS_POWEROFF diff --git a/commands/Makefile b/commands/Makefile index 8975d4b..f1b482f 100644 --- a/commands/Makefile +++ b/commands/Makefile @@ -116,3 +116,4 @@ obj-$(CONFIG_CMD_DHRYSTONE) += dhrystone.o obj-$(CONFIG_CMD_SPD_DECODE) += spd_decode.o obj-$(CONFIG_CMD_MMC_EXTCSD) += mmc_extcsd.o +obj-$(CONFIG_CMD_NAND_BITFLIP) += nand-bitflip.o diff --git a/commands/barebox-update.c b/commands/barebox-update.c index 92e0efa..c2f2b68 100644 --- a/commands/barebox-update.c +++ b/commands/barebox-update.c @@ -26,10 +26,10 @@ static int do_barebox_update(int argc, char *argv[]) { - int opt, ret; + int opt, ret, repair = 0; struct bbu_data data = {}; - while ((opt = getopt(argc, argv, "t:yf:ld:")) > 0) { + while ((opt = getopt(argc, argv, "t:yf:ld:r")) > 0) { switch (opt) { case 'd': data.devicefile = optarg; @@ -48,19 +48,24 @@ printf("registered update handlers:\n"); bbu_handlers_list(); return 0; + case 'r': + repair = 1; + break; default: return COMMAND_ERROR_USAGE; } } - if (!(argc - optind)) - return COMMAND_ERROR_USAGE; + if (argc - optind > 0) { + data.imagefile = argv[optind]; - data.imagefile = argv[optind]; - - data.image = read_file(data.imagefile, &data.len); - if (!data.image) - return -errno; + data.image = read_file(data.imagefile, &data.len); + if (!data.image) + return -errno; + } else { + if (!repair) + return COMMAND_ERROR_USAGE; + } ret = barebox_update(&data); @@ -74,6 +79,7 @@ BAREBOX_CMD_HELP_OPT("-l\t", "list registered targets") BAREBOX_CMD_HELP_OPT("-t TARGET", "specify data target handler name") BAREBOX_CMD_HELP_OPT("-d DEVICE", "write image to DEVICE") +BAREBOX_CMD_HELP_OPT("-r\t", "refresh or repair. Do not update, but repair an existing image") BAREBOX_CMD_HELP_OPT("-y\t", "autom. use 'yes' when asking confirmations") BAREBOX_CMD_HELP_OPT("-f LEVEL", "set force level") BAREBOX_CMD_HELP_END @@ -81,7 +87,7 @@ BAREBOX_CMD_START(barebox_update) .cmd = do_barebox_update, BAREBOX_CMD_DESC("update barebox to persistent media") - BAREBOX_CMD_OPTS("[-ltdyf] [IMAGE]") + BAREBOX_CMD_OPTS("[-ltdyfr] [IMAGE]") BAREBOX_CMD_GROUP(CMD_GRP_MISC) BAREBOX_CMD_HELP(cmd_barebox_update_help) BAREBOX_CMD_END diff --git a/commands/nand-bitflip.c b/commands/nand-bitflip.c new file mode 100644 index 0000000..fe56f22 --- /dev/null +++ b/commands/nand-bitflip.c @@ -0,0 +1,117 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int do_nand_bitflip(int argc, char *argv[]) +{ + int opt, ret, fd; + static struct mtd_info_user meminfo; + int block = 0; + int random = 0; + int num_bitflips = 1; + loff_t offset = 0, roffset; + int check = 0; + size_t r; + void *buf; + + while ((opt = getopt(argc, argv, "b:rn:o:c")) > 0) { + switch (opt) { + case 'r': + random = 1; + break; + case 'n': + num_bitflips = simple_strtoul(optarg, NULL, 0); + break; + case 'b': + block = simple_strtoul(optarg, NULL, 0); + break; + case 'o': + offset = simple_strtoull(optarg, NULL, 0); + break; + case 'c': + check = 1; + break; + default: + return COMMAND_ERROR_USAGE; + } + } + + if (optind >= argc) + return COMMAND_ERROR_USAGE; + + fd = open(argv[optind], O_RDWR); + if (fd < 0) + return fd; + + ret = ioctl(fd, MEMGETINFO, &meminfo); + + close(fd); + + if (ret) + return ret; + + block += mtd_div_by_eb(offset, meminfo.mtd); + offset = mtd_mod_by_eb(offset, meminfo.mtd); + + if (!check) { + ret = mtd_peb_create_bitflips(meminfo.mtd, block, offset, meminfo.writesize, + num_bitflips, random, 1); + if (ret) { + printf("Creating bitflips failed with: %s\n", strerror(-ret)); + return ret; + } + } + + buf = xzalloc(meminfo.writesize); + + roffset = (loff_t)block * meminfo.mtd->erasesize + offset; + ret = meminfo.mtd->read(meminfo.mtd, roffset, meminfo.writesize, &r, buf); + if (ret > 0) { + printf("page at block %d, offset 0x%08llx has %d bitflips%s\n", + block, offset, ret, + ret >= meminfo.mtd->bitflip_threshold ? ", needs cleanup" : ""); + } else if (!ret) { + printf("No bitflips found on block %d, offset 0x%08llx\n", block, offset); + } else { + printf("Reading block %d, offset 0x%08llx failed with: %s\n", block, offset, + strerror(-ret)); + } + + free(buf); + + return 0; +} + +BAREBOX_CMD_HELP_START(nand_bitflip) +BAREBOX_CMD_HELP_TEXT("This command creates bitflips on Nand pages.") +BAREBOX_CMD_HELP_TEXT("Options:") +BAREBOX_CMD_HELP_OPT ("-b ", "block to work on") +BAREBOX_CMD_HELP_OPT ("-o ", "offset in Nand") +BAREBOX_CMD_HELP_OPT ("-r\t", "flip random bits") +BAREBOX_CMD_HELP_OPT ("-n ", "Specify maximum number of bitflips to generate") +BAREBOX_CMD_HELP_END + +BAREBOX_CMD_START(nand_bitflip) + .cmd = do_nand_bitflip, + BAREBOX_CMD_DESC("Create bitflips on Nand pages") + BAREBOX_CMD_OPTS("NANDDEV") + BAREBOX_CMD_GROUP(CMD_GRP_HWMANIP) + BAREBOX_CMD_HELP(cmd_nand_bitflip_help) +BAREBOX_CMD_END diff --git a/commands/nand.c b/commands/nand.c index c330ad1..b065a66 100644 --- a/commands/nand.c +++ b/commands/nand.c @@ -32,14 +32,19 @@ #define NAND_ADD 1 #define NAND_DEL 2 #define NAND_MARKBAD 3 +#define NAND_MARKGOOD 4 +#define NAND_INFO 5 static int do_nand(int argc, char *argv[]) { int opt; int command = 0; loff_t badblock = 0; + int fd; + int ret; + struct mtd_info_user mtdinfo; - while((opt = getopt(argc, argv, "adb:")) > 0) { + while((opt = getopt(argc, argv, "adb:g:i")) > 0) { if (command) { printf("only one command may be given\n"); return 1; @@ -55,12 +60,27 @@ case 'b': command = NAND_MARKBAD; badblock = strtoull_suffix(optarg, NULL, 0); + break; + case 'g': + command = NAND_MARKGOOD; + badblock = strtoull_suffix(optarg, NULL, 0); + break; + case 'i': + command = NAND_INFO; + break; + default: + return COMMAND_ERROR_USAGE; } } if (optind >= argc) return COMMAND_ERROR_USAGE; + if (!command) { + printf("No action given\n"); + return COMMAND_ERROR_USAGE; + } + if (command == NAND_ADD) { while (optind < argc) { if (dev_add_bb_dev(basename(argv[optind]), NULL)) @@ -77,19 +97,32 @@ } } - if (command == NAND_MARKBAD) { - int ret = 0, fd; + fd = open(argv[optind], O_RDWR); + if (fd < 0) { + perror("open"); + return 1; + } - printf("marking block at 0x%08llx on %s as bad\n", - badblock, argv[optind]); + ret = ioctl(fd, MEMGETINFO, &mtdinfo); + if (ret) + goto out; - fd = open(argv[optind], O_RDWR); - if (fd < 0) { - perror("open"); - return 1; + if (command == NAND_MARKBAD || command == NAND_MARKGOOD) { + const char *str; + int ctl; + + if (command == NAND_MARKBAD) { + str = "bad"; + ctl = MEMSETBADBLOCK; + } else { + str = "good"; + ctl = MEMSETGOODBLOCK; } - ret = ioctl(fd, MEMSETBADBLOCK, &badblock); + printf("marking block at 0x%08llx on %s as %s\n", + badblock, argv[optind], str); + + ret = ioctl(fd, ctl, &badblock); if (ret) { if (ret == -EINVAL) printf("Maybe offset %lld is out of range.\n", @@ -98,11 +131,29 @@ perror("ioctl"); } - close(fd); - return ret; + goto out; } - return 0; + if (command == NAND_INFO) { + loff_t ofs; + int bad = 0; + + for (ofs = 0; ofs < mtdinfo.size; ofs += mtdinfo.erasesize) { + if (ioctl(fd, MEMGETBADBLOCK, &ofs)) { + printf("Block at 0x%08llx is bad\n", ofs); + bad = 1; + } + } + + if (!bad) + printf("No bad blocks\n"); + } + + ret = 0; +out: + close(fd); + + return ret; } BAREBOX_CMD_HELP_START(nand) @@ -110,6 +161,8 @@ BAREBOX_CMD_HELP_OPT ("-a", "register a bad block aware device ontop of a normal NAND device") BAREBOX_CMD_HELP_OPT ("-d", "deregister a bad block aware device") BAREBOX_CMD_HELP_OPT ("-b OFFS", "mark block at OFFSet as bad") +BAREBOX_CMD_HELP_OPT ("-g OFFS", "mark block at OFFSet as good") +BAREBOX_CMD_HELP_OPT ("-i", "info. Show information about bad blocks") BAREBOX_CMD_HELP_END BAREBOX_CMD_START(nand) diff --git a/commands/ubiformat.c b/commands/ubiformat.c index f9c50b7..0172654 100644 --- a/commands/ubiformat.c +++ b/commands/ubiformat.c @@ -46,12 +46,12 @@ #include #include #include -#include #include #include #include #include #include +#include /* The variables below are set by command line arguments */ struct args { @@ -68,7 +68,6 @@ long long ec; const char *image; const char *node; - int node_fd; }; static struct args args; @@ -166,16 +165,18 @@ return 0; } -static void print_bad_eraseblocks(const struct mtd_dev_info *mtd, +static void print_bad_eraseblocks(struct mtd_info *mtd, const struct ubi_scan_info *si) { - int first = 1, eb; + int first = 1, eb, eb_cnt; + + eb_cnt = mtd_div_by_eb(mtd->size, mtd); if (si->bad_cnt == 0) return; normsg_cont("%d bad eraseblocks found, numbers: ", si->bad_cnt); - for (eb = 0; eb < mtd->eb_cnt; eb++) { + for (eb = 0; eb < eb_cnt; eb++) { if (si->ec[eb] != EB_BAD) continue; if (first) { @@ -210,7 +211,7 @@ return 0; } -static int drop_ffs(const struct mtd_dev_info *mtd, const void *buf, int len) +static int drop_ffs(struct mtd_info *mtd, const void *buf, int len) { int i; @@ -220,8 +221,8 @@ /* The resulting length must be aligned to the minimum flash I/O size */ len = i + 1; - len = (len + mtd->min_io_size - 1) / mtd->min_io_size; - len *= mtd->min_io_size; + len = (len + mtd->writesize - 1) / mtd->writesize; + len *= mtd->writesize; return len; } @@ -271,20 +272,20 @@ } /* TODO: we should actually torture the PEB before marking it as bad */ -static int mark_bad(const struct mtd_dev_info *mtd, struct ubi_scan_info *si, int eb) +static int mark_bad(struct mtd_info *mtd, struct ubi_scan_info *si, int eb) { int err; if (!args.quiet) normsg_cont("marking block %d bad\n", eb); - if (!mtd->bb_allowed) { + if (!mtd_can_have_bb(mtd)) { if (!args.quiet) printf("\n"); return errmsg("bad blocks not supported by this flash"); } - err = mtd_mark_bad(mtd, args.node_fd, eb); + err = mtd_peb_mark_bad(mtd, eb); if (err) return err; @@ -294,24 +295,26 @@ return consecutive_bad_check(eb); } -static int flash_image(const struct mtd_dev_info *mtd, +static int flash_image(struct mtd_info *mtd, const struct ubigen_info *ui, struct ubi_scan_info *si) { - int fd, img_ebs, eb, written_ebs = 0, ret = -1; + int fd, img_ebs, eb, written_ebs = 0, ret = -1, eb_cnt; off_t st_size; char *buf = NULL; + eb_cnt = mtd_num_pebs(mtd); + fd = open_file(&st_size); if (fd < 0) return fd; - buf = malloc(mtd->eb_size); + buf = malloc(mtd->erasesize); if (!buf) { - sys_errmsg("cannot allocate %d bytes of memory", mtd->eb_size); + sys_errmsg("cannot allocate %d bytes of memory", mtd->erasesize); goto out_close; } - img_ebs = st_size / mtd->eb_size; + img_ebs = st_size / mtd->erasesize; if (img_ebs > si->good_cnt) { sys_errmsg("file \"%s\" is too large (%lld bytes)", @@ -319,10 +322,10 @@ goto out_close; } - if (st_size % mtd->eb_size) { + if (st_size % mtd->erasesize) { sys_errmsg("file \"%s\" (size %lld bytes) is not multiple of " "eraseblock size (%d bytes)", - args.image, (long long)st_size, mtd->eb_size); + args.image, (long long)st_size, mtd->erasesize); goto out_close; } @@ -332,13 +335,13 @@ } verbose(args.verbose, "will write %d eraseblocks", img_ebs); - for (eb = 0; eb < mtd->eb_cnt; eb++) { + for (eb = 0; eb < eb_cnt; eb++) { int err, new_len; long long ec; if (!args.quiet && !args.verbose) { printf("\rubiformat: flashing eraseblock %d -- %2u %% complete ", - eb, (eb + 1) * 100 / mtd->eb_cnt); + eb, (eb + 1) * 100 / eb_cnt); } if (si->ec[eb] == EB_BAD) @@ -348,13 +351,13 @@ normsg_cont("eraseblock %d: erase", eb); } - err = libmtd_erase(mtd, args.node_fd, eb); + err = mtd_peb_erase(mtd, eb); if (err) { if (!args.quiet) printf("\n"); sys_errmsg("failed to erase eraseblock %d", eb); - if (errno != EIO) + if (err != EIO) goto out_close; if (mark_bad(mtd, si, eb)) @@ -363,7 +366,7 @@ continue; } - err = read_full(fd, buf, mtd->eb_size); + err = read_full(fd, buf, mtd->erasesize); if (err < 0) { sys_errmsg("failed to read eraseblock %d from \"%s\"", written_ebs, args.image); @@ -392,20 +395,21 @@ printf(", write data\n"); } - new_len = drop_ffs(mtd, buf, mtd->eb_size); + new_len = drop_ffs(mtd, buf, mtd->erasesize); - err = libmtd_write(mtd, args.node_fd, eb, 0, buf, new_len); + err = mtd_peb_write(mtd, buf, eb, 0, new_len); if (err) { sys_errmsg("cannot write eraseblock %d", eb); - if (errno != EIO) + if (err != EIO) goto out_close; - err = mtd_torture(mtd, args.node_fd, eb); - if (err) { - if (mark_bad(mtd, si, eb)) - goto out_close; - } + err = mtd_peb_torture(mtd, eb); + if (err < 0 && err != -EIO) + goto out_close; + if (err == -EIO && consecutive_bad_check(eb)) + goto out_close; + continue; } if (++written_ebs >= img_ebs) @@ -423,30 +427,32 @@ return ret; } -static int format(const struct mtd_dev_info *mtd, +static int format(struct mtd_info *mtd, const struct ubigen_info *ui, struct ubi_scan_info *si, - int start_eb, int novtbl) + int start_eb, int novtbl, int subpage_size) { - int eb, err, write_size; + int eb, err, write_size, eb_cnt; struct ubi_ec_hdr *hdr; struct ubi_vtbl_record *vtbl; int eb1 = -1, eb2 = -1; long long ec1 = -1, ec2 = -1; - write_size = UBI_EC_HDR_SIZE + mtd->subpage_size - 1; - write_size /= mtd->subpage_size; - write_size *= mtd->subpage_size; + eb_cnt = mtd_num_pebs(mtd); + + write_size = UBI_EC_HDR_SIZE + subpage_size - 1; + write_size /= subpage_size; + write_size *= subpage_size; hdr = malloc(write_size); if (!hdr) return sys_errmsg("cannot allocate %d bytes of memory", write_size); memset(hdr, 0xFF, write_size); - for (eb = start_eb; eb < mtd->eb_cnt; eb++) { + for (eb = start_eb; eb < eb_cnt; eb++) { long long ec; if (!args.quiet && !args.verbose) { printf("\rubiformat: formatting eraseblock %d -- %2u %% complete ", - eb, (eb + 1 - start_eb) * 100 / (mtd->eb_cnt - start_eb)); + eb, (eb + 1 - start_eb) * 100 / (eb_cnt - start_eb)); } if (si->ec[eb] == EB_BAD) @@ -464,13 +470,13 @@ normsg_cont("eraseblock %d: erase", eb); } - err = libmtd_erase(mtd, args.node_fd, eb); + err = mtd_peb_erase(mtd, eb); if (err) { if (!args.quiet) printf("\n"); sys_errmsg("failed to erase eraseblock %d", eb); - if (errno != EIO) + if (err != EIO) goto out_free; if (mark_bad(mtd, si, eb)) @@ -495,7 +501,7 @@ printf(", write EC %lld\n", ec); } - err = libmtd_write(mtd, args.node_fd, eb, 0, hdr, write_size); + err = mtd_peb_write(mtd, hdr, eb, 0, write_size); if (err) { if (!args.quiet && !args.verbose) printf("\n"); @@ -503,16 +509,17 @@ write_size, eb); if (errno != EIO) { - if (args.subpage_size != mtd->min_io_size) + if (args.subpage_size != mtd->writesize) normsg("may be sub-page size is incorrect?"); goto out_free; } - err = mtd_torture(mtd, args.node_fd, eb); - if (err) { - if (mark_bad(mtd, si, eb)) - goto out_free; - } + err = mtd_peb_torture(mtd, eb); + if (err < 0 && err != -EIO) + goto out_free; + if (err == -EIO && consecutive_bad_check(eb)) + goto out_free; + continue; } @@ -532,8 +539,7 @@ if (!vtbl) goto out_free; - err = ubigen_write_layout_vol(ui, eb1, eb2, ec1, ec2, vtbl, - args.node_fd); + err = ubigen_write_layout_vol(ui, eb1, eb2, ec1, ec2, vtbl, mtd); free(vtbl); if (err) { errmsg("cannot write layout volume"); @@ -551,78 +557,82 @@ int do_ubiformat(int argc, char *argv[]) { - int err, verbose; - struct mtd_dev_info mtd; + int err, verbose, fd, eb_cnt; + struct mtd_info *mtd; struct ubigen_info ui; struct ubi_scan_info *si; + struct mtd_info_user mtd_user; err = parse_opt(argc, argv); if (err) return err; - err = mtd_get_dev_info(args.node, &mtd); - if (err) { - sys_errmsg("cannot get information about \"%s\"", args.node); - goto out_close_mtd; + fd = open(args.node, O_RDWR); + if (fd < 0) + return sys_errmsg("cannot open \"%s\"", args.node); + + if (ioctl(fd, MEMGETINFO, &mtd_user)) { + sys_errmsg("MEMGETINFO ioctl request failed"); + goto out_close; } - if (!is_power_of_2(mtd.min_io_size)) { + mtd = mtd_user.mtd; + + if (!is_power_of_2(mtd->writesize)) { errmsg("min. I/O size is %d, but should be power of 2", - mtd.min_io_size); - goto out_close_mtd; + mtd->writesize); + goto out_close; } - if (args.subpage_size && args.subpage_size != mtd.subpage_size) { - mtd.subpage_size = args.subpage_size; - args.manual_subpage = 1; + if (args.subpage_size) { + if (args.subpage_size != mtd->writesize >> mtd->subpage_sft) + args.manual_subpage = 1; + } else { + args.subpage_size = mtd->writesize >> mtd->subpage_sft; } if (args.manual_subpage) { /* Do some sanity check */ - if (args.subpage_size > mtd.min_io_size) { + if (args.subpage_size > mtd->writesize) { errmsg("sub-page cannot be larger than min. I/O unit"); - goto out_close_mtd; + goto out_close; } - if (mtd.min_io_size % args.subpage_size) { + if (mtd->writesize % args.subpage_size) { errmsg("min. I/O unit size should be multiple of " "sub-page size"); - goto out_close_mtd; + goto out_close; } } - args.node_fd = open(args.node, O_RDWR); - if (args.node_fd < 0) { - sys_errmsg("cannot open \"%s\"", args.node); - goto out_close_mtd; - } - /* Validate VID header offset if it was specified */ if (args.vid_hdr_offs != 0) { if (args.vid_hdr_offs % 8) { errmsg("VID header offset has to be multiple of min. I/O unit size"); goto out_close; } - if (args.vid_hdr_offs + (int)UBI_VID_HDR_SIZE > mtd.eb_size) { + if (args.vid_hdr_offs + (int)UBI_VID_HDR_SIZE > mtd->erasesize) { errmsg("bad VID header offset"); goto out_close; } } - if (!mtd.writable) { - errmsg("%s (%s) is a read-only device", mtd.node, args.node); + if (!(mtd->flags & MTD_WRITEABLE)) { + errmsg("%s is a read-only device", args.node); goto out_close; } /* Make sure this MTD device is not attached to UBI */ /* FIXME! Find a proper way to do this in barebox! */ + eb_cnt = mtd_div_by_eb(mtd->size, mtd); + if (!args.quiet) { - normsg_cont("%s (%s), size %lld bytes (%s)", mtd.node, mtd.type_str, - mtd.size, size_human_readable(mtd.size)); - printf(", %d eraseblocks of %d bytes (%s)", mtd.eb_cnt, - mtd.eb_size, size_human_readable(mtd.eb_size)); - printf(", min. I/O size %d bytes\n", mtd.min_io_size); + normsg_cont("%s (%s), size %lld bytes (%s)", args.node, mtd_type_str(mtd), + mtd->size, size_human_readable(mtd->size)); + printf(", %d eraseblocks of %d bytes (%s)", eb_cnt, + mtd->erasesize, size_human_readable(mtd->erasesize)); + printf(", min. I/O size %d bytes\n", mtd->writesize); } if (args.quiet) @@ -631,9 +641,9 @@ verbose = 2; else verbose = 1; - err = libscan_ubi_scan(&mtd, args.node_fd, &si, verbose); + err = libscan_ubi_scan(mtd, &si, verbose); if (err) { - errmsg("failed to scan %s (%s)", mtd.node, args.node); + errmsg("failed to scan %s", args.node); goto out_close; } @@ -644,7 +654,7 @@ if (si->good_cnt < 2 && (!args.novtbl || args.image)) { errmsg("too few non-bad eraseblocks (%d) on %s", - si->good_cnt, mtd.node); + si->good_cnt, args.node); goto out_free; } @@ -656,7 +666,7 @@ normsg("%d eraseblocks are supposedly empty", si->empty_cnt); if (si->corrupted_cnt) normsg("%d corrupted erase counters", si->corrupted_cnt); - print_bad_eraseblocks(&mtd, si); + print_bad_eraseblocks(mtd, si); } if (si->alien_cnt) { @@ -717,7 +727,7 @@ if (!args.quiet && args.override_ec) normsg("use erase counter %lld for all eraseblocks", args.ec); - ubigen_info_init(&ui, mtd.eb_size, mtd.min_io_size, mtd.subpage_size, + ubigen_info_init(&ui, mtd->erasesize, mtd->writesize, args.subpage_size, args.vid_hdr_offs, args.ubi_ver, args.image_seq); if (si->vid_hdr_offs != -1 && ui.vid_hdr_offs != si->vid_hdr_offs) { @@ -735,28 +745,27 @@ } if (args.image) { - err = flash_image(&mtd, &ui, si); + err = flash_image(mtd, &ui, si); if (err < 0) goto out_free; - err = format(&mtd, &ui, si, err, 1); + err = format(mtd, &ui, si, err, 1, args.subpage_size); if (err) goto out_free; } else { - err = format(&mtd, &ui, si, 0, args.novtbl); + err = format(mtd, &ui, si, 0, args.novtbl, args.subpage_size); if (err) goto out_free; } libscan_ubi_scan_free(si); - close(args.node_fd); return 0; out_free: libscan_ubi_scan_free(si); out_close: - close(args.node_fd); -out_close_mtd: + close(fd); + return 1; } diff --git a/common/bbu.c b/common/bbu.c index 5fbef6a..1b22139 100644 --- a/common/bbu.c +++ b/common/bbu.c @@ -66,9 +66,13 @@ if (data->flags & BBU_FLAG_YES) return 0; - printf("update barebox from %s using handler %s to %s (y/n)?\n", + if (data->imagefile) + printf("update barebox from %s using handler %s to %s (y/n)?\n", data->imagefile, data->handler_name, data->devicefile); + else + printf("Refresh barebox on %s using handler %s (y/n)?\n", + data->devicefile, data->handler_name); key = read_key(); @@ -215,6 +219,12 @@ if (!handler) return -ENODEV; + if (!data->image && !data->imagefile && + !(handler->flags & BBU_HANDLER_CAN_REFRESH)) { + pr_err("No Image file given\n"); + return -EINVAL; + } + if (!data->handler_name) data->handler_name = handler->name; diff --git a/common/imx-bbu-nand-fcb.c b/common/imx-bbu-nand-fcb.c index 3eb9e9b..04c6e60 100644 --- a/common/imx-bbu-nand-fcb.c +++ b/common/imx-bbu-nand-fcb.c @@ -31,8 +31,11 @@ #include #include #include +#include #include #include +#include +#include struct dbbt_block { uint32_t Checksum; @@ -107,8 +110,6 @@ void (*fcb_create)(struct imx_nand_fcb_bbu_handler *imx_handler, struct fcb_block *fcb, struct mtd_info *mtd); - void (*dbbt_create)(struct imx_nand_fcb_bbu_handler *imx_handler, - struct dbbt_block *dbbt, int num_bad_blocks); enum filetype filetype; }; @@ -137,6 +138,22 @@ ecc[i] = calculate_parity_13_8(src[i]); } +static int lookup_single_error_13_8(unsigned char syndrome) +{ + int i; + unsigned char syndrome_table[] = { + 0x1c, 0x16, 0x13, 0x19, + 0x1a, 0x07, 0x15, 0x0e, + 0x01, 0x02, 0x04, 0x08, + 0x10, + }; + + for (i = 0; i < 13; i ++) + if (syndrome_table[i] == syndrome) + return i; + return -1; +} + static uint32_t calc_chksum(void *buf, size_t size) { u32 chksum = 0; @@ -238,6 +255,66 @@ return ret; } +static int read_fcb(struct mtd_info *mtd, int num, struct fcb_block **retfcb) +{ + int i; + int bitflips = 0; + u8 parity, np, syndrome, bit_to_flip; + u8 *fcb, *ecc; + int ret; + void *rawpage; + + *retfcb = NULL; + + rawpage = xmalloc(mtd->writesize + mtd->oobsize); + + ret = raw_read_page(mtd, rawpage, mtd->erasesize * num); + if (ret) { + pr_err("Cannot read block %d\n", num); + goto err; + } + + fcb = rawpage + 12; + ecc = rawpage + 512 + 12; + + for (i = 0; i < 512; i++) { + parity = ecc[i]; + np = calculate_parity_13_8(fcb[i]); + + syndrome = np ^ parity; + if (syndrome == 0) + continue; + + if (!(hweight8(syndrome) & 1)) { + pr_err("Uncorrectable error at offset %d\n", i); + ret = -EIO; + goto err; + } + + bit_to_flip = lookup_single_error_13_8(syndrome); + if (bit_to_flip < 0) { + pr_err("Uncorrectable error at offset %d\n", i); + ret = -EIO; + goto err; + } + + bitflips++; + + if (bit_to_flip > 7) + ecc[i] ^= 1 << (bit_to_flip - 8); + else + fcb[i] ^= 1 << bit_to_flip; + } + + *retfcb = xmemdup(rawpage + 12, 512); + + ret = 0; +err: + free(rawpage); + + return ret; +} + static int fcb_create(struct imx_nand_fcb_bbu_handler *imx_handler, struct fcb_block *fcb, struct mtd_info *mtd) { @@ -272,77 +349,123 @@ return 0; } -static int imx_bbu_erase(struct mtd_info *mtd) +static int mtd_peb_write_block(struct mtd_info *mtd, void *buf, int block, int len) { - uint64_t offset = 0; - struct erase_info erase; int ret; + int retries = 0; - while (offset < mtd->size) { - pr_debug("erasing at 0x%08llx\n", offset); - if (mtd_block_isbad(mtd, offset)) { - pr_debug("erase skip block @ 0x%08llx\n", offset); - offset += mtd->erasesize; - continue; - } + if (mtd_peb_is_bad(mtd, block)) + return -EINVAL; +again: + ret = mtd_peb_write(mtd, buf, block, 0, len); + if (!ret) + return 0; - memset(&erase, 0, sizeof(erase)); - erase.addr = offset; - erase.len = mtd->erasesize; - - ret = mtd_erase(mtd, &erase); - if (ret) - return ret; - - offset += mtd->erasesize; + if (ret == -EBADMSG) { + ret = mtd_peb_torture(mtd, block); + if (!ret && retries++ < 3) + goto again; } - return 0; + return ret; } -static int imx_bbu_write_firmware(struct mtd_info *mtd, unsigned block, - unsigned num_blocks, void *buf, size_t len) +/** + * imx_bbu_firmware_max_blocks - get max number of blocks for firmware + * @mtd: The mtd device + * + * We use 4 blocks for FCB/DBBT, the rest of the partition is + * divided into two equally sized firmware slots. This function + * returns the number of blocks available for one firmware slot. + * The actually usable size may be smaller due to bad blocks. + */ +static int imx_bbu_firmware_max_blocks(struct mtd_info *mtd) { - uint64_t offset = block * mtd->erasesize; - int ret; - size_t written; + return (mtd_div_by_eb(mtd->size, mtd) - 4) / 2; +} + +/** + * imx_bbu_firmware_start_block - get start block for a firmware slot + * @mtd: The mtd device + * @num: The slot number (0 or 1) + * + * We use 4 blocks for FCB/DBBT, the rest of the partition is + * divided into two equally sized firmware slots. This function + * returns the start block for the given firmware slot. + */ +static int imx_bbu_firmware_start_block(struct mtd_info *mtd, int num) +{ + return 4 + num * imx_bbu_firmware_max_blocks(mtd); +} + +static int imx_bbu_write_firmware(struct mtd_info *mtd, unsigned num, void *buf, + size_t len) +{ + int ret, i, newbadblock = 0; + int num_blocks = imx_bbu_firmware_max_blocks(mtd); + int block = imx_bbu_firmware_start_block(mtd, num); + + pr_info("writing firmware %d to block %d (ofs 0x%08x)\n", + num, block, block * mtd->erasesize); + + for (i = 0; i < num_blocks; i++) { + if (mtd_peb_is_bad(mtd, block + i)) + continue; + + ret = mtd_peb_erase(mtd, block + i); + if (ret && ret != -EIO) + return ret; + } while (len > 0) { int now = min(len, mtd->erasesize); - if (!num_blocks) + if (!num_blocks) { + pr_err("Out of good eraseblocks, cannot write firmware\n"); return -ENOSPC; + } - pr_debug("writing %p at 0x%08llx, left 0x%08x\n", - buf, offset, len); + pr_debug("writing %p peb %d, left 0x%08x\n", + buf, block, len); - if (mtd_block_isbad(mtd, offset)) { - pr_debug("write skip block @ 0x%08llx\n", offset); - offset += mtd->erasesize; + if (mtd_peb_is_bad(mtd, block)) { + pr_debug("skipping block %d\n", block); + num_blocks--; block++; continue; } - ret = mtd_write(mtd, offset, now, &written, buf); - if (ret) - return ret; + ret = mtd_peb_write_block(mtd, buf, block, now); - offset += now; + if (ret == -EIO) { + block++; + num_blocks--; + newbadblock = 1; + continue; + } + + if (ret) { + pr_err("Writing block %d failed with: %s\n", block, strerror(-ret)); + return ret; + } + len -= now; buf += now; block++; num_blocks--; } - return block; + return newbadblock; } -static int dbbt_data_create(struct mtd_info *mtd, void *buf, int num_blocks) +static void *dbbt_data_create(struct mtd_info *mtd) { int n; int n_bad_blocks = 0; - uint32_t *bb = buf + 0x8; - uint32_t *n_bad_blocksp = buf + 0x4; + void *dbbt = xzalloc(mtd->writesize); + uint32_t *bb = dbbt + 0x8; + uint32_t *n_bad_blocksp = dbbt + 0x4; + int num_blocks = mtd_div_by_eb(mtd->size, mtd); for (n = 0; n < num_blocks; n++) { loff_t offset = n * mtd->erasesize; @@ -353,104 +476,289 @@ } } - *n_bad_blocksp = n_bad_blocks; - - return n_bad_blocks; -} - -static int imx_bbu_nand_update(struct bbu_handler *handler, struct bbu_data *data) -{ - struct imx_nand_fcb_bbu_handler *imx_handler = - container_of(handler, struct imx_nand_fcb_bbu_handler, handler); - struct cdev *bcb_cdev; - struct mtd_info *mtd; - int ret, block_fw1, block_fw2; - struct fcb_block *fcb; - struct dbbt_block *dbbt; - void *fcb_raw_page, *dbbt_page, *dbbt_data_page; - void *ecc; - int written; - void *fw; - unsigned fw_size, partition_size; - int i; - enum filetype filetype; - unsigned num_blocks_fcb_dbbt, num_blocks, num_blocks_fw; - - filetype = file_detect_type(data->image, data->len); - - if (filetype != imx_handler->filetype && - !bbu_force(data, "Image is not of type %s but of type %s", - file_type_to_string(imx_handler->filetype), - file_type_to_string(filetype))) - return -EINVAL; - - bcb_cdev = cdev_by_name(handler->devicefile); - if (!bcb_cdev) { - pr_err("%s: No FCB device!\n", __func__); - return -ENODEV; + if (!n_bad_blocks) { + free(dbbt); + return NULL; } - mtd = bcb_cdev->mtd; - partition_size = mtd->size; + *n_bad_blocksp = n_bad_blocks; + + return dbbt; +} + +static void imx28_dbbt_create(struct dbbt_block *dbbt, int num_bad_blocks) +{ + uint32_t a = 0; + uint8_t *p = (void *)dbbt; + int i; + + dbbt->numberBB = num_bad_blocks; + + for (i = 4; i < 512; i++) + a += p[i]; + + a ^= 0xffffffff; + + dbbt->Checksum = a; +} + +/** + * imx_bbu_write_fcb - Write FCB and DBBT raw data to the device + * @mtd: The mtd Nand device + * @block: The block to write to + * @fcb_raw_page: The raw FCB data + * @dbbt_data_page: The DBBT data + * + * This function writes the FCB/DBBT data to the block given in @block + * to the Nand device. The FCB data has to be given in the raw flash + * layout, already with ecc data supplied. + * + * return: 0 on success or a negative error code otherwise. + */ +static int imx_bbu_write_fcb(struct mtd_info *mtd, int block, void *fcb_raw_page, + void *dbbt_data_page) +{ + struct dbbt_block *dbbt; + int ret; + int retries = 0; + uint32_t *n_bad_blocksp = dbbt_data_page + 0x4; +again: + dbbt = xzalloc(mtd->writesize); + + dbbt->Checksum = 0; + dbbt->FingerPrint = 0x54424244; + dbbt->Version = 0x01000000; + if (dbbt_data_page) + dbbt->DBBTNumOfPages = 1; + if (cpu_is_mx28()) + imx28_dbbt_create(dbbt, *n_bad_blocksp); + + ret = mtd_peb_erase(mtd, block); + if (ret) + return ret; + + ret = raw_write_page(mtd, fcb_raw_page, block * mtd->erasesize); + if (ret) { + pr_err("Writing FCB on block %d failed with %s\n", + block, strerror(-ret)); + goto out; + } + + ret = mtd_peb_write(mtd, (void *)dbbt, block, mtd->writesize, + mtd->writesize); + if (ret < 0) { + pr_err("Writing DBBT header on block %d failed with %s\n", + block, strerror(-ret)); + goto out; + } + + if (dbbt_data_page) { + ret = mtd_peb_write(mtd, dbbt_data_page, block, mtd->writesize * 5, + mtd->writesize); + if (ret < 0) { + pr_err("Writing DBBT on block %d failed with %s\n", + block, strerror(-ret)); + goto out; + } + } + + ret = 0; +out: + free(dbbt); + + if (ret == -EBADMSG) { + ret = mtd_peb_torture(mtd, block); + + if (!ret && retries++ < 3) + goto again; + } + + return ret; +} + +/** + * dbbt_block_is_bad - Check if according to the given DBBT a block is bad + * @dbbt: The DBBT data page + * @block: The block to test + * + * This function checks if a block is marked as bad in the given DBBT. + * + * return: true if the block is bad, false otherwise. + */ +static int dbbt_block_is_bad(void *_dbbt, int block) +{ + int i; + u32 *dbbt = _dbbt; + int num_bad_blocks; + + if (!_dbbt) + return false; + + dbbt++; /* reserved */ + + num_bad_blocks = *dbbt++; + + for (i = 0; i < num_bad_blocks; i++) { + if (*dbbt == block) + return true; + dbbt++; + } + + return false; +} + +/** + * dbbt_check - Check if DBBT is readable and consistent to the mtd BBT + * @mtd: The mtd Nand device + * @dbbt: The page where the DBBT is found + * + * This function checks if the DBBT is readable and consistent to the mtd + * layers idea of bad blocks. + * + * return: 0 if the DBBT is readable and consistent to the mtd BBT, a + * negative error code otherwise. + */ +static int dbbt_check(struct mtd_info *mtd, int page) +{ + int ret, needs_cleanup = 0; + size_t r; + void *dbbt_header; + void *dbbt_entries = NULL; + struct dbbt_block *dbbt; + int num_blocks = mtd_div_by_eb(mtd->size, mtd); + int n; + + dbbt_header = xmalloc(mtd->writesize); + + ret = mtd_read(mtd, page * mtd->writesize, mtd->writesize, &r, dbbt_header); + if (ret == -EUCLEAN) { + pr_warn("page %d needs cleaning\n", page); + needs_cleanup = 1; + } else if (ret < 0) { + pr_err("Cannot read page %d: %s\n", page, strerror(-ret)); + goto out; + } + + dbbt = dbbt_header; + + if (dbbt->FingerPrint != 0x54424244) { + pr_err("dbbt at page %d is readable but does not contain a valid DBBT\n", + page); + ret = -EINVAL; + goto out; + } + + if (dbbt->DBBTNumOfPages) { + dbbt_entries = xmalloc(mtd->writesize); + + ret = mtd_read(mtd, (page + 4) * mtd->writesize, mtd->writesize, &r, dbbt_entries); + if (ret == -EUCLEAN) { + pr_warn("page %d needs cleaning\n", page); + needs_cleanup = 1; + } else if (ret < 0) { + pr_err("Cannot read page %d: %s\n", page, strerror(-ret)); + free(dbbt_entries); + goto out; + } + } else { + dbbt_entries = NULL; + } + + for (n = 0; n < num_blocks; n++) { + if (mtd_peb_is_bad(mtd, n) != dbbt_block_is_bad(dbbt_entries, n)) { + ret = -EINVAL; + goto out; + } + } + + ret = 0; +out: + free(dbbt_header); + free(dbbt_entries); + + if (ret < 0) + return ret; + if (needs_cleanup) + return -EUCLEAN; + return 0; +} + +/** + * fcb_dbbt_check - Check if a FCB/DBBT is valid + * @mtd: The mtd Nand device + * @num: The number of the FCB, corresponds to the eraseblock number + * @fcb: The FCB to check against + * + * This function checks if FCB/DBBT found on a device are valid. This + * means: + * - the FCB is readable on the device + * - the FCB is the same as the reference passed in @fcb + * - the DBBT is consistent to the mtd BBT + * + * return: 0 if the FCB/DBBT are valid, a negative error code otherwise + */ +static int fcb_dbbt_check(struct mtd_info *mtd, int num, struct fcb_block *fcb) +{ + int ret; + struct fcb_block *f; + int pages_per_block = mtd->erasesize / mtd->writesize; + + ret = read_fcb(mtd, num, &f); + if (ret) + return ret; + + if (memcmp(fcb, f, sizeof(*fcb))) { + ret = -EINVAL; + goto out; + } + + ret = dbbt_check(mtd, num * pages_per_block + 1); + if (ret) + goto out; + + ret = 0; + +out: + free(f); + + return ret; +} + +/** + * imx_bbu_write_fcbs_dbbts - Write FCBs/DBBTs to first four blocks + * @mtd: The mtd device to write the FCBs/DBBTs to + * @fcb: The FCB block to write + * + * This creates the FCBs/DBBTs and writes them to the first four blocks + * of the Nand device. The raw FCB data is created from the input FCB + * block, the DBBTs are created from the barebox mtd Nand Bad Block + * Table. The DBBTs are written in the second page same of each FCB block. + * Data will actually only be written if it differs from the data found + * on the device or if a return value of -EUCLEAN while reading + * indicates that a refresh is necessary. + * + * return: 0 for success or a negative error code otherwise. + */ +static int imx_bbu_write_fcbs_dbbts(struct mtd_info *mtd, struct fcb_block *fcb) +{ + void *dbbt = NULL; + int i, ret, valid = 0; + void *fcb_raw_page; + + /* + * The DBBT search start page is configurable in the FCB block. + * This function writes the DBBTs in the pages directly behind + * the FCBs, so everything else is invalid here. + */ + if (fcb->DBBTSearchAreaStartAddress != 1) + return -EINVAL; fcb_raw_page = xzalloc(mtd->writesize + mtd->oobsize); - fcb = fcb_raw_page + 12; - ecc = fcb_raw_page + 512 + 12; + memcpy(fcb_raw_page + 12, fcb, sizeof(struct fcb_block)); + encode_hamming_13_8(fcb_raw_page + 12, fcb_raw_page + 12 + 512, 512); - dbbt_page = xzalloc(mtd->writesize); - dbbt_data_page = xzalloc(mtd->writesize); - dbbt = dbbt_page; - - /* - * We have to write one additional page to make the ROM happy. - * Maybe the PagesInFirmwarex fields are really the number of pages - 1. - * kobs-ng has the same. - */ - fw_size = ALIGN(data->len + mtd->writesize, mtd->writesize); - fw = xzalloc(fw_size); - memcpy(fw, data->image, data->len); - - num_blocks_fcb_dbbt = 4; - num_blocks = partition_size / mtd->erasesize; - num_blocks_fw = (num_blocks - num_blocks_fcb_dbbt) / 2; - - block_fw1 = num_blocks_fcb_dbbt; - block_fw2 = num_blocks_fcb_dbbt + num_blocks_fw; - - pr_info("writing first firmware to block %d (ofs 0x%08x)\n", - block_fw1, block_fw1 * mtd->erasesize); - pr_info("writing second firmware to block %d (ofs 0x%08x)\n", - block_fw2, block_fw2 * mtd->erasesize); - pr_info("maximum size per firmware: 0x%08x bytes\n", - num_blocks_fw * mtd->erasesize); - - if (num_blocks_fw * mtd->erasesize < fw_size) - return -ENOSPC; - - ret = bbu_confirm(data); - if (ret) - goto out; - - ret = imx_bbu_erase(mtd); - if (ret) - goto out; - - ret = imx_bbu_write_firmware(mtd, block_fw1, num_blocks_fw, fw, fw_size); - if (ret < 0) - goto out; - - ret = imx_bbu_write_firmware(mtd, block_fw2, num_blocks_fw, fw, fw_size); - if (ret < 0) - goto out; - - fcb->Firmware1_startingPage = block_fw1 * mtd->erasesize / mtd->writesize; - fcb->Firmware2_startingPage = block_fw2 * mtd->erasesize / mtd->writesize; - fcb->PagesInFirmware1 = ALIGN(data->len, mtd->writesize) / mtd->writesize; - fcb->PagesInFirmware2 = fcb->PagesInFirmware1; - - fcb_create(imx_handler, fcb, mtd); - encode_hamming_13_8(fcb, ecc, 512); + dbbt = dbbt_data_create(mtd); /* * Set the first and second byte of OOB data to 0xFF, not 0x00. These @@ -461,43 +769,417 @@ */ memset(fcb_raw_page + mtd->writesize, 0xFF, 2); - dbbt->Checksum = 0; - dbbt->FingerPrint = 0x54424244; - dbbt->Version = 0x01000000; + for (i = 0; i < 4; i++) { + if (mtd_peb_is_bad(mtd, i)) + continue; - ret = dbbt_data_create(mtd, dbbt_data_page, block_fw2 + num_blocks_fw); + if (!fcb_dbbt_check(mtd, i, fcb)) { + valid++; + pr_info("FCB/DBBT on block %d still valid\n", i); + continue; + } + + pr_info("Writing FCB/DBBT on block %d\n", i); + + ret = imx_bbu_write_fcb(mtd, i, fcb_raw_page, dbbt); + if (ret) + pr_err("Writing FCB/DBBT %d failed with: %s\n", i, strerror(-ret)); + else + valid++; + } + + free(fcb_raw_page); + free(dbbt); + + if (!valid) + pr_err("No FCBs/DBBTs could be written. System won't boot from Nand\n"); + + return valid > 0 ? 0 : -EIO; +} + +static int block_is_empty(struct mtd_info *mtd, int block) +{ + int rawsize = mtd->writesize + mtd->oobsize; + u8 *rawpage = xmalloc(rawsize); + int ret; + loff_t offset = (loff_t)block * mtd->erasesize; + + ret = raw_read_page(mtd, rawpage, offset); + if (ret) + goto err; + + ret = nand_check_erased_buf(rawpage, rawsize, 4 * 13); + + if (ret == -EBADMSG) + ret = 0; + else if (ret >= 0) + ret = 1; + +err: + free(rawpage); + return ret; +} + +static int read_firmware(struct mtd_info *mtd, int first_page, int num_pages, + void **firmware) +{ + void *buf, *pos; + int pages_per_block = mtd->erasesize / mtd->writesize; + int now, size, block, ret, need_cleaning = 0; + + pr_debug("%s: reading %d pages from page %d\n", __func__, num_pages, first_page); + + buf = pos = malloc(num_pages * mtd->writesize); + if (!buf) + return -ENOMEM; + + if (first_page % pages_per_block) { + pr_err("Firmware does not begin on eraseblock boundary\n"); + ret = -EINVAL; + goto err; + } + + block = first_page / pages_per_block; + size = num_pages * mtd->writesize; + + while (size) { + if (block >= mtd_num_pebs(mtd)) { + ret = -EIO; + goto err; + } + + if (mtd_peb_is_bad(mtd, block)) { + block++; + continue; + } + + now = min_t(unsigned int , size, mtd->erasesize); + + ret = mtd_peb_read(mtd, pos, block, 0, now); + if (ret == -EUCLEAN) { + pr_info("Block %d needs cleaning\n", block); + need_cleaning = 1; + } else if (ret < 0) { + pr_err("Reading PEB %d failed with %d\n", block, ret); + goto err; + } + + if (mtd_buf_all_ff(pos, now)) { + /* + * At this point we do not know if this is a + * block that contains only 0xff or if it is + * really empty. We test this by reading a raw + * page and check if it's empty + */ + ret = block_is_empty(mtd, block); + if (ret < 0) + goto err; + if (ret) { + ret = -EINVAL; + goto err; + } + } + + pos += now; + size -= now; + block++; + } + + ret = 0; + + *firmware = buf; + + pr_info("Firmware @ page %d, size %d pages has crc32: 0x%08x\n", + first_page, num_pages, crc32(0, buf, num_pages * mtd->writesize)); + +err: + if (ret < 0) { + free(buf); + pr_warn("Firmware at page %d is not readable\n", first_page); + return ret; + } + + if (need_cleaning) { + pr_warn("Firmware at page %d needs cleanup\n", first_page); + return -EUCLEAN; + } + + return 0; +} + +static void read_firmware_all(struct mtd_info *mtd, struct fcb_block *fcb, void **data, int *len, + int *used_refresh, int *unused_refresh, int *used) +{ + void *primary = NULL, *secondary = NULL; + int pages_per_block = mtd->erasesize / mtd->writesize; + int fw0 = imx_bbu_firmware_start_block(mtd, 0) * pages_per_block; + int fw1 = imx_bbu_firmware_start_block(mtd, 1) * pages_per_block; + int first, ret, primary_refresh = 0, secondary_refresh = 0; + + *used_refresh = 0; + *unused_refresh = 0; + + if (fcb->Firmware1_startingPage == fw0 && + fcb->Firmware2_startingPage == fw1) { + first = 0; + } else if (fcb->Firmware1_startingPage == fw1 && + fcb->Firmware2_startingPage == fw0) { + first = 1; + } else { + pr_warn("FCB is not what we expect. Update will not be robust"); + *used = 0; + return; + } + + if (fcb->PagesInFirmware1 != fcb->PagesInFirmware2) { + pr_warn("FCB is not what we expect. Update will not be robust"); + return; + } + + *len = fcb->PagesInFirmware1 * mtd->writesize; + + ret = read_firmware(mtd, fcb->Firmware1_startingPage, fcb->PagesInFirmware1, &primary); + if (ret > 0) + primary_refresh = 1; + + ret = read_firmware(mtd, fcb->Firmware2_startingPage, fcb->PagesInFirmware2, &secondary); + if (ret > 0) + secondary_refresh = 1; + + if (!primary && !secondary) { + *unused_refresh = 1; + *used_refresh = 1; + *used = 0; + *data = NULL; + } else if (primary && !secondary) { + *used_refresh = primary_refresh; + *unused_refresh = 1; + *used = first; + *data = primary; + return; + } else if (secondary && !primary) { + *used_refresh = secondary_refresh; + *unused_refresh = 1; + *used = !first; + *data = secondary; + } else { + if (memcmp(primary, secondary, fcb->PagesInFirmware1 * mtd->writesize)) + *unused_refresh = 1; + + *used_refresh = primary_refresh; + *used = first; + *data = primary; + free(secondary); + } + + pr_info("Primary firmware is on pages %d-%d, %svalid, %s\n", fcb->Firmware1_startingPage, + fcb->Firmware1_startingPage + fcb->PagesInFirmware1, primary ? "" : "in", + primary_refresh ? "needs cleanup" : "clean"); + + pr_info("secondary firmware is on pages %d-%d, %svalid, %s\n", fcb->Firmware2_startingPage, + fcb->Firmware2_startingPage + fcb->PagesInFirmware2, secondary ? "" : "in", + secondary_refresh ? "needs cleanup" : "clean"); + + pr_info("ROM uses slot %d\n", *used); +} + +static int imx_bbu_nand_update(struct bbu_handler *handler, struct bbu_data *data) +{ + struct imx_nand_fcb_bbu_handler *imx_handler = + container_of(handler, struct imx_nand_fcb_bbu_handler, handler); + struct cdev *bcb_cdev; + struct mtd_info *mtd; + int ret, i; + struct fcb_block *fcb = NULL; + void *fw = NULL, *fw_orig = NULL; + unsigned fw_size, partition_size; + enum filetype filetype; + unsigned num_blocks_fw; + int pages_per_block; + int used = 0; + int fw_orig_len; + int used_refresh = 0, unused_refresh = 0; + + if (data->image) { + filetype = file_detect_type(data->image, data->len); + + if (filetype != imx_handler->filetype && + !bbu_force(data, "Image is not of type %s but of type %s", + file_type_to_string(imx_handler->filetype), + file_type_to_string(filetype))) + return -EINVAL; + } + + bcb_cdev = cdev_by_name(handler->devicefile); + if (!bcb_cdev) { + pr_err("%s: No FCB device!\n", __func__); + return -ENODEV; + } + + mtd = bcb_cdev->mtd; + partition_size = mtd->size; + pages_per_block = mtd->erasesize / mtd->writesize; + + for (i = 0; i < 4; i++) { + read_fcb(mtd, i, &fcb); + if (fcb) + break; + } + + /* + * This code uses the following layout in the Nand flash: + * + * fwmaxsize = (n_blocks - 4) / 2 + * + * block + * + * 0 ---------------------- + * | FCB/DBBT 0 | + * 1 ---------------------- + * | FCB/DBBT 1 | + * 2 ---------------------- + * | FCB/DBBT 2 | + * 3 ---------------------- + * | FCB/DBBT 3 | + * 4 ---------------------- + * | Firmware slot 0 | + * 4 + fwmaxsize ---------------------- + * | Firmware slot 1 | + * ---------------------- + * + * We want a robust update in which a power failure may occur + * everytime without bricking the board, so here's the strategy: + * + * The FCBs contain pointers to the firmware slots in the + * Firmware1_startingPage and Firmware2_startingPage fields. Note that + * Firmware1_startingPage doesn't necessarily point to slot 0. We + * exchange the pointers during update to atomically switch between the + * old and the new firmware. + * + * - We read the first valid FCB and the firmware slots. + * - We check which firmware slot is currently used by the ROM: + * - if no FCB is found or its layout differs from the above layout, + * continue without robust update + * - if only one firmware slot is readable, the ROM uses it + * - if both slots are readable, the ROM will use slot 0 + * - Step 1: erase/update the slot currently unused by the ROM + * - Step 2: Update FCBs/DBBTs, thereby letting Firmware1_startingPage + * point to the slot we just updated. From this moment + * on the new firmware will be used and running a + * refresh/repair after a power failure after this + * step will complete the update. + * - Step 3: erase/update the other firmwre slot + * - Step 4: Eventually write FCBs/DBBTs again. This may become + * necessary when step 3 revealed new bad blocks. + * + * This robust update only works when the original FCBs on the device + * uses the same layout as this code does. In other cases update will + * also work, but it won't be robust against power failures. + * + * Refreshing the firmware which is needed when blocks become unreadable + * due to read disturbance works the same way, only that the new firmware + * is the same as the old firmware and that it will only be written when + * reading from the device returns -EUCLEAN indicating that a block needs + * to be rewritten. + */ + if (fcb) + read_firmware_all(mtd, fcb, &fw_orig, &fw_orig_len, + &used_refresh, &unused_refresh, &used); + + if (data->image) { + /* + * We have to write one additional page to make the ROM happy. + * Maybe the PagesInFirmwarex fields are really the number of pages - 1. + * kobs-ng has the same. + */ + fw_size = ALIGN(data->len + mtd->writesize, mtd->writesize); + fw = xzalloc(fw_size); + memcpy(fw, data->image, data->len); + free(fw_orig); + used_refresh = 1; + unused_refresh = 1; + + free(fcb); + fcb = xzalloc(sizeof(*fcb)); + fcb->Firmware1_startingPage = imx_bbu_firmware_start_block(mtd, !used) * pages_per_block; + fcb->Firmware2_startingPage = imx_bbu_firmware_start_block(mtd, used) * pages_per_block; + fcb->PagesInFirmware1 = fw_size / mtd->writesize; + fcb->PagesInFirmware2 = fcb->PagesInFirmware1; + + fcb_create(imx_handler, fcb, mtd); + } else { + if (!fcb) { + pr_err("No FCB found on device, cannot refresh\n"); + ret = -EINVAL; + goto out; + } + + if (!fw_orig) { + pr_err("No firmware found on device, cannot refresh\n"); + ret = -EINVAL; + goto out; + } + + fw = fw_orig; + fw_size = fw_orig_len; + pr_info("Refreshing existing firmware\n"); + } + + num_blocks_fw = imx_bbu_firmware_max_blocks(mtd); + + if (num_blocks_fw * mtd->erasesize < fw_size) { + pr_err("Not enough space for update\n"); + return -ENOSPC; + } + + ret = bbu_confirm(data); + if (ret) + goto out; + + /* Step 1: write firmware which is currently unused by the ROM */ + if (unused_refresh) { + pr_info("%sing slot %d\n", data->image ? "updat" : "refresh", !used); + ret = imx_bbu_write_firmware(mtd, !used, fw, fw_size); + if (ret < 0) + goto out; + } else { + pr_info("firmware slot %d still ok, nothing to do\n", !used); + } + + /* + * Step 2: Write FCBs/DBBTs. This will use the firmware we have + * just written as primary firmware. From now on the new + * firmware will be booted. + */ + ret = imx_bbu_write_fcbs_dbbts(mtd, fcb); if (ret < 0) goto out; - if (ret > 0) { - dbbt->DBBTNumOfPages = 1; - if (imx_handler->dbbt_create) - imx_handler->dbbt_create(imx_handler, dbbt, ret); + /* Step 3: Write the secondary firmware */ + if (used_refresh) { + pr_info("%sing slot %d\n", data->image ? "updat" : "refresh", used); + ret = imx_bbu_write_firmware(mtd, used, fw, fw_size); + if (ret < 0) + goto out; + } else { + pr_info("firmware slot %d still ok, nothing to do\n", used); } - for (i = 0; i < 4; i++) { - ret = raw_write_page(mtd, fcb_raw_page, mtd->erasesize * i); - if (ret) + /* + * Step 4: If writing the secondary firmware discovered new bad + * blocks, write the FCBs/DBBTs again with updated bad block + * information. + */ + if (ret > 0) { + pr_info("New bad blocks detected, writing FCBs/DBBTs again\n"); + ret = imx_bbu_write_fcbs_dbbts(mtd, fcb); + if (ret < 0) goto out; - - ret = mtd_write(mtd, mtd->erasesize * i + mtd->writesize, - mtd->writesize, &written, dbbt_page); - if (ret) - goto out; - - if (dbbt->DBBTNumOfPages > 0) { - ret = mtd_write(mtd, mtd->erasesize * i + mtd->writesize * 5, - mtd->writesize, &written, dbbt_data_page); - if (ret) - goto out; - } } out: - free(dbbt_page); - free(dbbt_data_page); - free(fcb_raw_page); free(fw); + free(fcb); return ret; } @@ -526,7 +1208,7 @@ handler = &imx_handler->handler; handler->devicefile = "nand0.barebox"; handler->name = name; - handler->flags = flags; + handler->flags = flags | BBU_HANDLER_CAN_REFRESH; handler->handler = imx_bbu_nand_update; ret = bbu_register_handler(handler); @@ -589,23 +1271,6 @@ fcb->EraseThreshold = readl(bch_regs + BCH_MODE); } -static void imx28_dbbt_create(struct imx_nand_fcb_bbu_handler *imx_handler, - struct dbbt_block *dbbt, int num_bad_blocks) -{ - uint32_t a = 0; - uint8_t *p = (void *)dbbt; - int i; - - dbbt->numberBB = num_bad_blocks; - - for (i = 4; i < 512; i++) - a += p[i]; - - a ^= 0xffffffff; - - dbbt->Checksum = a; -} - int imx28_bbu_nand_register_handler(const char *name, unsigned long flags) { struct imx_nand_fcb_bbu_handler *imx_handler; @@ -614,14 +1279,13 @@ imx_handler = xzalloc(sizeof(*imx_handler)); imx_handler->fcb_create = imx28_fcb_create; - imx_handler->dbbt_create = imx28_dbbt_create; imx_handler->filetype = filetype_mxs_bootstream; handler = &imx_handler->handler; handler->devicefile = "nand0.barebox"; handler->name = name; - handler->flags = flags; + handler->flags = flags | BBU_HANDLER_CAN_REFRESH; handler->handler = imx_bbu_nand_update; ret = bbu_register_handler(handler); diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig index ea1be55..db9c287 100644 --- a/drivers/mtd/Kconfig +++ b/drivers/mtd/Kconfig @@ -30,6 +30,15 @@ needs driver support, currently only the cfi-flash driver supports concatenating MTD devices. +comment "MTD debug options" + +config MTD_PEB_DEBUG + bool "MTD PEB debug" + help + When enabled the MTD PEB API will emulate bitflips. Random read + operations will return that bits are flipped. The MTD PEB API + is used by UBI and ubiformat + source "drivers/mtd/devices/Kconfig" source "drivers/mtd/nor/Kconfig" source "drivers/mtd/nand/Kconfig" diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile index d3ae7fc..bfac8eb 100644 --- a/drivers/mtd/Makefile +++ b/drivers/mtd/Makefile @@ -3,7 +3,7 @@ obj-$(CONFIG_MTD_SPI_NOR) += spi-nor/ obj-$(CONFIG_MTD_UBI) += ubi/ obj-y += devices/ -obj-$(CONFIG_MTD) += core.o partition.o +obj-$(CONFIG_MTD) += core.o partition.o peb.o obj-$(CONFIG_MTD_OOB_DEVICE) += mtdoob.o obj-$(CONFIG_MTD_RAW_DEVICE) += mtdraw.o obj-$(CONFIG_MTD_CONCAT) += mtdconcat.o diff --git a/drivers/mtd/core.c b/drivers/mtd/core.c index 161c6ad..dcf94bf 100644 --- a/drivers/mtd/core.c +++ b/drivers/mtd/core.c @@ -34,7 +34,15 @@ static LIST_HEAD(mtd_register_hooks); -int mtd_all_ff(const void *buf, unsigned int len) +/** + * mtd_buf_all_ff - check if buffer contains only 0xff + * @buf: buffer to check + * @size: buffer size in bytes + * + * This function returns %1 if there are only 0xff bytes in @buf, and %0 if + * something else was also found. + */ +int mtd_buf_all_ff(const void *buf, unsigned int len) { while ((unsigned long)buf & 0x3) { if (*(const uint8_t *)buf != 0xff) @@ -66,6 +74,25 @@ return 1; } +/** + * mtd_buf_check_pattern - check if buffer contains only a certain byte pattern. + * @buf: buffer to check + * @patt: the pattern to check + * @size: buffer size in bytes + * + * This function returns %1 in there are only @patt bytes in @buf, and %0 if + * something else was also found. + */ +int mtd_buf_check_pattern(const void *buf, uint8_t patt, int size) +{ + int i; + + for (i = 0; i < size; i++) + if (((const uint8_t *)buf)[i] != patt) + return 0; + return 1; +} + static ssize_t mtd_op_read(struct cdev *cdev, void* buf, size_t count, loff_t offset, ulong flags) { @@ -231,6 +258,10 @@ dev_dbg(cdev->dev, "MEMSETBADBLOCK: 0x%08llx\n", *offset); ret = mtd_block_markbad(mtd, *offset); break; + case MEMSETGOODBLOCK: + dev_dbg(cdev->dev, "MEMSETGOODBLOCK: 0x%08llx\n", *offset); + ret = mtd_block_markgood(mtd, *offset); + break; case MEMERASE: ret = mtd_op_erase(cdev, ei->length, ei->start + cdev->offset); break; @@ -320,6 +351,18 @@ return ret; } +int mtd_block_markgood(struct mtd_info *mtd, loff_t ofs) +{ + int ret; + + if (mtd->block_markgood) + ret = mtd->block_markgood(mtd, ofs); + else + ret = -ENOSYS; + + return ret; +} + int mtd_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { @@ -717,3 +760,25 @@ { list_add(&hook->hook, &mtd_register_hooks); } + +const char *mtd_type_str(struct mtd_info *mtd) +{ + switch (mtd->type) { + case MTD_ABSENT: + return "absent"; + case MTD_RAM: + return "ram"; + case MTD_ROM: + return "rom"; + case MTD_NORFLASH: + return "nor"; + case MTD_NANDFLASH: + return "nand"; + case MTD_DATAFLASH: + return"dataflash"; + case MTD_UBIVOLUME: + return "ubi"; + default: + return "unknown"; + } +} diff --git a/drivers/mtd/mtdconcat.c b/drivers/mtd/mtdconcat.c index 6395b2f..fa43071 100644 --- a/drivers/mtd/mtdconcat.c +++ b/drivers/mtd/mtdconcat.c @@ -506,6 +506,28 @@ return err; } +static int concat_block_markgood(struct mtd_info *mtd, loff_t ofs) +{ + struct mtd_concat *concat = CONCAT(mtd); + int i, err = -EINVAL; + + for (i = 0; i < concat->num_subdev; i++) { + struct mtd_info *subdev = concat->subdev[i]; + + if (ofs >= subdev->size) { + ofs -= subdev->size; + continue; + } + + err = mtd_block_markgood(subdev, ofs); + if (!err) + mtd->ecc_stats.badblocks--; + break; + } + + return err; +} + /* * This function constructs a virtual MTD device by concatenating * num_devs MTD devices. A pointer to the new device object is @@ -565,6 +587,8 @@ concat->mtd.block_isbad = concat_block_isbad; if (subdev[0]->block_markbad) concat->mtd.block_markbad = concat_block_markbad; + if (subdev[0]->block_markgood) + concat->mtd.block_markgood = concat_block_markgood; concat->mtd.ecc_stats.badblocks = subdev[0]->ecc_stats.badblocks; diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index ec5a8b7..79c1edd 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -456,6 +456,38 @@ return chip->block_bad(mtd, ofs, getchip); } +/** + * nand_default_block_markgood - [DEFAULT] mark a block good + * @mtd: MTD device structure + * @ofs: offset from device start + * + * This is the default implementation, which can be overridden by + * a hardware specific driver. +*/ +static __maybe_unused int nand_default_block_markgood(struct mtd_info *mtd, loff_t ofs) +{ + struct nand_chip *chip = mtd->priv; + int block, res, ret = 0; + + /* Get block number */ + block = (int)(ofs >> chip->bbt_erase_shift); + /* Mark block good in memory-based BBT */ + if (chip->bbt) + chip->bbt[block >> 2] &= ~(0x01 << ((block & 0x03) << 1)); + + /* Update flash-based bad block table */ + if (IS_ENABLED(CONFIG_NAND_BBT) && chip->bbt_options & NAND_BBT_USE_FLASH) { + res = nand_update_bbt(mtd, ofs); + if (!ret) + ret = res; + } + + if (!ret) + mtd->ecc_stats.badblocks++; + + return ret; +} + /* Wait for the ready pin, after a command. The timeout is caught later. */ void nand_wait_ready(struct mtd_info *mtd) { @@ -925,7 +957,7 @@ * bitflips_threshold, or -ERROR_CODE for bitflips in excess of the * threshold. */ -static int nand_check_erased_buf(void *buf, int len, int bitflips_threshold) +int nand_check_erased_buf(void *buf, int len, int bitflips_threshold) { const unsigned char *bitmap = buf; int bitflips = 0; @@ -2371,7 +2403,7 @@ memset(chip->oob_poi, 0xff, mtd->oobsize); } - if (oob || !mtd_all_ff(wbuf, mtd->writesize)) { + if (oob || !mtd_buf_all_ff(wbuf, mtd->writesize)) { ret = chip->write_page(mtd, chip, column, bytes, wbuf, oob_required, page, cached, (ops->mode == MTD_OPS_RAW)); @@ -2775,6 +2807,30 @@ } /** + * nand_block_markgood - [MTD Interface] Mark block at the given offset as bad + * @mtd: MTD device structure + * @ofs: offset relative to mtd start + */ +static int nand_block_markgood(struct mtd_info *mtd, loff_t ofs) +{ + struct nand_chip *chip = mtd->priv; + int ret; + + if (!IS_ENABLED(CONFIG_MTD_WRITE)) + return -ENOTSUPP; + + ret = nand_block_isbad(mtd, ofs); + if (ret < 0) + return ret; + + /* If it was good already, return success and do nothing */ + if (!ret) + return 0; + + return chip->block_markgood(mtd, ofs); +} + +/** * nand_onfi_set_features- [REPLACEABLE] set features for ONFI nand * @mtd: MTD device structure * @chip: nand chip info structure @@ -2844,6 +2900,8 @@ #ifdef CONFIG_MTD_WRITE if (!chip->block_markbad) chip->block_markbad = nand_default_block_markbad; + if (!chip->block_markgood) + chip->block_markgood = nand_default_block_markgood; if (!chip->write_buf) chip->write_buf = busw ? nand_write_buf16 : nand_write_buf; #endif @@ -3707,6 +3765,7 @@ mtd->unlock = NULL; mtd->block_isbad = nand_block_isbad; mtd->block_markbad = nand_block_markbad; + mtd->block_markgood = nand_block_markgood; mtd->writebufsize = mtd->writesize; /* propagate ecc info to mtd_info */ diff --git a/drivers/mtd/partition.c b/drivers/mtd/partition.c index c11a3db..777cb75 100644 --- a/drivers/mtd/partition.c +++ b/drivers/mtd/partition.c @@ -18,6 +18,26 @@ return res; } +static int mtd_part_read_oob(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops) +{ + int res; + + if (from >= mtd->size) + return -EINVAL; + if (ops->datbuf && from + ops->len > mtd->size) + return -EINVAL; + + res = mtd->master->read_oob(mtd->master, from + mtd->master_offset, ops); + if (unlikely(res)) { + if (mtd_is_bitflip(res)) + mtd->ecc_stats.corrected++; + if (mtd_is_eccerr(res)) + mtd->ecc_stats.failed++; + } + return res; +} + static int mtd_part_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf) { @@ -114,6 +134,21 @@ return res; } +static int mtd_part_block_markgood(struct mtd_info *mtd, loff_t ofs) +{ + int res; + + if (!(mtd->flags & MTD_WRITEABLE)) + return -EROFS; + if (ofs >= mtd->size) + return -EINVAL; + ofs += mtd->master_offset; + res = mtd->master->block_markgood(mtd->master, ofs); + if (!res) + mtd->ecc_stats.badblocks--; + return res; +} + struct mtd_info *mtd_add_partition(struct mtd_info *mtd, off_t offset, uint64_t size, unsigned long flags, const char *name) { @@ -168,10 +203,13 @@ part->lock = mtd_part_lock; part->unlock = mtd_part_unlock; part->block_markbad = mtd->block_markbad ? mtd_part_block_markbad : NULL; + part->block_markgood = mtd->block_markgood ? mtd_part_block_markgood : NULL; } if (mtd->write_oob) part->write_oob = mtd_part_write_oob; + if (mtd->read_oob) + part->read_oob = mtd_part_read_oob; part->block_isbad = mtd->block_isbad ? mtd_part_block_isbad : NULL; part->size = size; diff --git a/drivers/mtd/peb.c b/drivers/mtd/peb.c new file mode 100644 index 0000000..639dc0e --- /dev/null +++ b/drivers/mtd/peb.c @@ -0,0 +1,658 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MTD_IO_RETRIES 3 + +static int __mtd_peb_chk_io; + +static int mtd_peb_chk_io(void) +{ + if (!IS_ENABLED(CONFIG_MTD_PEB_DEBUG)) + return 0; + + if (!__mtd_peb_chk_io) + return 0; + + return 1; +} + +static u32 __mtd_peb_emulate_bitflip; + +static int mtd_peb_emulate_bitflip(void) +{ + if (!IS_ENABLED(CONFIG_MTD_PEB_DEBUG)) + return 0; + + if (!__mtd_peb_emulate_bitflip) + return 0; + + return !(random32() % __mtd_peb_emulate_bitflip); +} + +static u32 __mtd_peb_emulate_write_failure; + +static int mtd_peb_emulate_write_failure(void) +{ + if (!IS_ENABLED(CONFIG_MTD_PEB_DEBUG)) + return 0; + + if (!__mtd_peb_emulate_write_failure) + return 0; + + return !(random32() % __mtd_peb_emulate_write_failure); +} + +static u32 __mtd_peb_emulate_erase_failures; + +static int mtd_peb_emulate_erase_failure(void) +{ + if (!IS_ENABLED(CONFIG_MTD_PEB_DEBUG)) + return 0; + + if (!__mtd_peb_emulate_erase_failures) + return 0; + + return !(random32() % __mtd_peb_emulate_erase_failures); +} + +#ifdef CONFIG_MTD_PEB_DEBUG +static int mtd_peb_debug_init(void) +{ + globalvar_add_simple_int("mtd_peb.mtd_peb_emulate_bitflip", + &__mtd_peb_emulate_bitflip, "%u"); + globalvar_add_simple_int("mtd_peb.mtd_peb_emulate_write_failure", + &__mtd_peb_emulate_write_failure, "%u"); + globalvar_add_simple_int("mtd_peb.mtd_peb_emulate_erase_failures", + &__mtd_peb_emulate_erase_failures, "%u"); + globalvar_add_simple_bool("mtd_peb.mtd_peb_chk_io", + &__mtd_peb_chk_io); + return 0; +} +device_initcall(mtd_peb_debug_init); + +BAREBOX_MAGICVAR_NAMED(global_mtd_peb_emulate_bitflip, + global.mtd_peb.emulate_bitflip, + "random bitflips, on average every #nth access returns -EUCLEAN"); +BAREBOX_MAGICVAR_NAMED(global_mtd_peb_emulate_write_failure, + global.mtd_peb.emulate_write_failure, + "random write failures, on average every #nth access returns write failure"); +BAREBOX_MAGICVAR_NAMED(global_mtd_peb_emulate_erase_failures, + global.mtd_peb.emulate_erase_failures, + "random erase failures, on average every #nth access returns erase failure"); +BAREBOX_MAGICVAR_NAMED(global_mtd_peb_chk_io, + global.mtd_peb.chk_io, + "If true, written data will be verified"); + +#endif + +static int mtd_peb_valid(struct mtd_info *mtd, int pnum) +{ + if (pnum < 0) + return false; + + if ((uint64_t)pnum * mtd->erasesize >= mtd->size) + return false; + + if (mtd->numeraseregions) + return false; + + return true; +} + +/** + * mtd_num_pebs - return number of PEBs for this device + * @mtd: mtd device + * + * This function returns the number of physical erase blocks this device + * has. + */ +int mtd_num_pebs(struct mtd_info *mtd) +{ + return mtd_div_by_eb(mtd->size, mtd); +} + +/** + * mtd_peb_mark_bad - mark a physical eraseblock as bad + * @mtd: mtd device + * @pnum: The number of the block + * + * This function marks a physical eraseblock as bad. + */ +int mtd_peb_mark_bad(struct mtd_info *mtd, int pnum) +{ + return mtd_block_markbad(mtd, (loff_t)pnum * mtd->erasesize); +} + +/** + * mtd_peb_is_bad - test if a physical eraseblock is bad + * @mtd: mtd device + * @pnum: The number of the block + * + * This function tests if a physical eraseblock is bad. Returns + * 0 if it is good, 1 if it is bad or a negative error value if the + * block is invalid + */ +int mtd_peb_is_bad(struct mtd_info *mtd, int pnum) +{ + if (!mtd_peb_valid(mtd, pnum)) + return -EINVAL; + + return mtd_block_isbad(mtd, (loff_t)pnum * mtd->erasesize); +} + +/** + * mtd_peb_read - read data from a physical eraseblock. + * @mtd: mtd device + * @buf: buffer where to store the read data + * @pnum: physical eraseblock number to read from + * @offset: offset within the physical eraseblock from where to read + * @len: how many bytes to read + * + * This function reads data from offset @offset of physical eraseblock @pnum + * and stores the read data in the @buf buffer. The following return codes are + * possible: + * + * o %0 if all the requested data were successfully read; + * o %-EUCLEAN if all the requested data were successfully read, but + * correctable bit-flips were detected; this is harmless but may indicate + * that this eraseblock may become bad soon (but do not have to); + * o %-EBADMSG if the MTD subsystem reported about data integrity problems, for + * example it can be an ECC error in case of NAND; this most probably means + * that the data is corrupted; + * o %-EIO if some I/O error occurred; + * o other negative error codes in case of other errors. + */ +int mtd_peb_read(struct mtd_info *mtd, void *buf, int pnum, int offset, + int len) +{ + int err, retries = 0; + size_t read; + loff_t addr; + + if (!mtd_peb_valid(mtd, pnum)) + return -EINVAL; + if (offset < 0 || offset + len > mtd->erasesize) + return -EINVAL; + if (len <= 0) + return -EINVAL; + if (mtd_peb_is_bad(mtd, pnum)) + return -EINVAL; + + /* Deliberately corrupt the buffer */ + *((uint8_t *)buf) ^= 0xFF; + + addr = (loff_t)pnum * mtd->erasesize + offset; +retry: + err = mtd_read(mtd, addr, len, &read, buf); + if (err) { + const char *errstr = mtd_is_eccerr(err) ? " (ECC error)" : ""; + + if (mtd_is_bitflip(err)) { + /* + * -EUCLEAN is reported if there was a bit-flip which + * was corrected, so this is harmless. + * + * We do not report about it here unless debugging is + * enabled. A corresponding message will be printed + * later, when it is has been scrubbed. + */ + dev_dbg(&mtd->class_dev, "fixable bit-flip detected at PEB %d\n", pnum); + if (len != read) + return -EIO; + return -EUCLEAN; + } + + if (mtd_is_eccerr(err) && retries++ < MTD_IO_RETRIES) + goto retry; + + dev_err(&mtd->class_dev, "error %d%s while reading %d bytes from PEB %d:%d\n", + err, errstr, len, pnum, offset); + return err; + } + + if (len != read) + return -EIO; + + if (mtd_peb_emulate_bitflip()) + return -EUCLEAN; + + return 0; +} + +/** + * mtd_peb_check_all_ff - check that a region of flash is empty. + * @mtd: mtd device + * @pnum: the physical eraseblock number to check + * @offset: the starting offset within the physical eraseblock to check + * @len: the length of the region to check + * + * This function returns zero if only 0xFF bytes are present at offset + * @offset of the physical eraseblock @pnum, -EBADMSG if the buffer does + * not contain all 0xFF or other negative error codes when other errors + * occured + */ +int mtd_peb_check_all_ff(struct mtd_info *mtd, int pnum, int offset, int len, + int warn) +{ + size_t read; + int err; + void *buf; + + buf = malloc(len); + if (!buf) + return -ENOMEM; + + err = mtd_peb_read(mtd, buf, pnum, offset, len); + if (err && !mtd_is_bitflip(err)) { + dev_err(&mtd->class_dev, + "error %d while reading %d bytes from PEB %d:%d, read %zd bytes\n", + err, len, pnum, offset, read); + goto out; + } + + err = mtd_buf_all_ff(buf, len); + if (err == 0) { + if (warn) + dev_err(&mtd->class_dev, "all-ff check failed for PEB %d\n", + pnum); + err = -EBADMSG; + goto out; + } + + err = 0; + +out: + free(buf); + + return err; +} + +/** + * mtd_peb_verify - make sure write succeeded. + * @mtd: mtd device + * @buf: buffer with data which were written + * @pnum: physical eraseblock number the data were written to + * @offset: offset within the physical eraseblock the data were written to + * @len: how many bytes were written + * + * This functions reads data which were recently written and compares it with + * the original data buffer - the data have to match. Returns zero if the data + * match and a negative error code if not or in case of failure. + */ +int mtd_peb_verify(struct mtd_info *mtd, const void *buf, int pnum, + int offset, int len) +{ + int err, i; + size_t read; + void *buf1; + loff_t addr = (loff_t)pnum * mtd->erasesize + offset; + + buf1 = malloc(len); + if (!buf1) + return 0; + + err = mtd_read(mtd, addr, len, &read, buf1); + if (err && !mtd_is_bitflip(err)) + goto out_free; + + for (i = 0; i < len; i++) { + uint8_t c = ((uint8_t *)buf)[i]; + uint8_t c1 = ((uint8_t *)buf1)[i]; + int dump_len; + + if (c == c1) + continue; + + dev_err(&mtd->class_dev, "self-check failed for PEB %d:%d, len %d\n", + pnum, offset, len); + dev_info(&mtd->class_dev, "data differs at position %d\n", i); + dump_len = max_t(int, 128, len - i); +#ifdef DEBUG + dev_info(&mtd->class_dev, "hex dump of the original buffer from %d to %d\n", + i, i + dump_len); + memory_display(buf + i, i, dump_len, 4, 0); + dev_info(&mtd->class_dev, "hex dump of the read buffer from %d to %d\n", + i, i + dump_len); + memory_display(buf1 + i, i, dump_len, 4, 0); + dump_stack(); +#endif + err = -EBADMSG; + goto out_free; + } + + err = 0; + +out_free: + free(buf1); + return err; +} + +/** + * mtd_peb_write - write data to a physical eraseblock. + * @mtd: mtd device + * @buf: buffer with the data to write + * @pnum: physical eraseblock number to write to + * @offset: offset within the physical eraseblock where to write + * @len: how many bytes to write + * + * This function writes @len bytes of data from buffer @buf to offset @offset + * of physical eraseblock @pnum. If all the data was successfully written, + * zero is returned. If an error occurred, this function returns a negative + * error code. If %-EBADMSG is returned, the physical eraseblock most probably + * went bad. + * + * Note, in case of an error, it is possible that something was still written + * to the flash media, but may be some garbage. + */ +int mtd_peb_write(struct mtd_info *mtd, const void *buf, int pnum, int offset, + int len) +{ + int err; + size_t written; + loff_t addr; + + dev_dbg(&mtd->class_dev, "write %d bytes to PEB %d:%d\n", len, pnum, offset); + + if (!mtd_peb_valid(mtd, pnum)) + return -EINVAL; + if (offset < 0 || offset + len > mtd->erasesize) + return -EINVAL; + if (len <= 0) + return -EINVAL; + if (len % (mtd->writesize >> mtd->subpage_sft)) + return -EINVAL; + if (mtd_peb_is_bad(mtd, pnum)) + return -EINVAL; + + if (mtd_peb_emulate_write_failure()) { + dev_err(&mtd->class_dev, "Cannot write %d bytes to PEB %d:%d (emulated)\n", + len, pnum, offset); + return -EIO; + } + + if (mtd_peb_chk_io()) { + /* The area we are writing to has to contain all 0xFF bytes */ + err = mtd_peb_check_all_ff(mtd, pnum, offset, len, 1); + if (err) + return err; + } + + addr = (loff_t)pnum * mtd->erasesize + offset; + err = mtd_write(mtd, addr, len, &written, buf); + if (err) { + dev_err(&mtd->class_dev, "error %d while writing %d bytes to PEB %d:%d, written %zd bytes\n", + err, len, pnum, offset, written); + } else { + if (written != len) + err = -EIO; + } + + if (!err && mtd_peb_chk_io()) + err = mtd_peb_verify(mtd, buf, pnum, offset, len); + + return err; +} + +/** + * mtd_peb_erase - erase a physical eraseblock. + * @mtd: mtd device + * @pnum: physical eraseblock number to erase + * + * This function erases physical eraseblock @pnum. + * + * This function returns 0 in case of success, %-EIO if the erasure failed, + * and other negative error codes in case of other errors. Note, %-EIO means + * that the physical eraseblock is bad. + */ +int mtd_peb_erase(struct mtd_info *mtd, int pnum) +{ + int ret; + struct erase_info ei = {}; + + dev_dbg(&mtd->class_dev, "erase PEB %d\n", pnum); + + if (!mtd_peb_valid(mtd, pnum)) + return -EINVAL; + + ei.mtd = mtd; + ei.addr = (loff_t)pnum * mtd->erasesize; + ei.len = mtd->erasesize; + + ret = mtd_erase(mtd, &ei); + if (ret) + return ret; + + if (mtd_peb_chk_io()) { + ret = mtd_peb_check_all_ff(mtd, pnum, 0, mtd->erasesize, 1); + if (ret == -EBADMSG) + ret = -EIO; + } + + if (mtd_peb_emulate_erase_failure()) { + dev_err(&mtd->class_dev, "cannot erase PEB %d (emulated)", pnum); + return -EIO; + } + + return 0; +} + +/* Patterns to write to a physical eraseblock when torturing it */ +static uint8_t patterns[] = {0xa5, 0x5a, 0x0}; + +/** + * mtd_peb_torture - test a supposedly bad physical eraseblock. + * @mtd: mtd device + * @pnum: the physical eraseblock number to test + * + * This function is a last resort when an eraseblock is assumed to be + * bad. It will write and check some patterns to the block. If the test + * is passed then this function will with the block freshly erased and + * the positive number returned indicaties how often the block has been + * erased during this test. + * If the block does not pass the test the block is marked as bad and + * -EIO is returned. + * Other negative errors are returned in case of other errors. + */ +int mtd_peb_torture(struct mtd_info *mtd, int pnum) +{ + int err, i, patt_count; + void *peb_buf; + + if (!mtd_peb_valid(mtd, pnum)) + return -EINVAL; + + peb_buf = malloc(mtd->erasesize); + if (!peb_buf) + return -ENOMEM; + + dev_dbg(&mtd->class_dev, "run torture test for PEB %d\n", pnum); + + patt_count = ARRAY_SIZE(patterns); + + for (i = 0; i < patt_count; i++) { + err = mtd_peb_erase(mtd, pnum); + if (err) + goto out; + + /* Make sure the PEB contains only 0xFF bytes after erasing */ + err = mtd_peb_check_all_ff(mtd, pnum, 0, mtd->writesize, 0); + if (err) + goto out; + + /* Write a pattern and check it */ + memset(peb_buf, patterns[i], mtd->erasesize); + err = mtd_peb_write(mtd, peb_buf, pnum, 0, mtd->erasesize); + if (err) + goto out; + + err = mtd_peb_verify(mtd, peb_buf, pnum, 0, mtd->erasesize); + if (err) + goto out; + } + + err = mtd_peb_erase(mtd, pnum); + if (err) + goto out; + + err = patt_count + 1; + dev_dbg(&mtd->class_dev, "PEB %d passed torture test, do not mark it as bad\n", + pnum); + +out: + if (err == -EUCLEAN || mtd_is_eccerr(err)) { + /* + * If a bit-flip or data integrity error was detected, the test + * has not passed because it happened on a freshly erased + * physical eraseblock which means something is wrong with it. + */ + dev_err(&mtd->class_dev, "read problems on freshly erased PEB %d, marking it bad\n", + pnum); + + mtd_peb_mark_bad(mtd, pnum); + + err = -EIO; + } + + free(peb_buf); + + return err; +} + +/** + * mtd_peb_create_bitflips - create bitflips on Nand pages + * @mtd: mtd device + * @pnum: Physical erase block number + * @offset: offset within erase block + * @len: The length of the area to create bitflips in + * @num_bitflips: The number of bitflips to create + * @random: If true, create bitflips at random offsets + * @info: If true, print information where bitflips are created + * + * This uses the mtd raw ops to create bitflips on a Nand page for + * testing purposes. If %random is false then the positions to flip are + * reproducible (thus, a second call with the same arguments reverts the + * bitflips). + * + * Return: 0 for success, otherwise a negative error code is returned + */ +int mtd_peb_create_bitflips(struct mtd_info *mtd, int pnum, int offset, + int len, int num_bitflips, int random, + int info) +{ + struct mtd_oob_ops ops; + int pages_per_block = mtd->erasesize / mtd->writesize; + int i; + int ret; + void *buf = NULL, *oobbuf = NULL; + int step; + + if (offset < 0 || offset + len > mtd->erasesize) + return -EINVAL; + if (len <= 0) + return -EINVAL; + if (num_bitflips <= 0) + return -EINVAL; + if (mtd_peb_is_bad(mtd, pnum)) + return -EINVAL; + + buf = malloc(mtd->writesize * pages_per_block); + if (!buf) { + ret = -ENOMEM; + goto err; + } + + oobbuf = malloc(mtd->oobsize * pages_per_block); + if (!oobbuf) { + ret = -ENOMEM; + goto err; + } + + ops.mode = MTD_OPS_RAW; + ops.ooboffs = 0; + ops.len = mtd->writesize; + ops.ooblen = mtd->oobsize; + + for (i = 0; i < pages_per_block; i++) { + loff_t offs = (loff_t)pnum * mtd->erasesize + i * mtd->writesize; + + ops.datbuf = buf + i * mtd->writesize; + ops.oobbuf = oobbuf + i * mtd->oobsize; + + ret = mtd_read_oob(mtd, offs, &ops); + if (ret) { + dev_err(&mtd->class_dev, "Cannot read raw data at 0x%08llx\n", offs); + goto err; + } + } + + if (random) + step = random32() % num_bitflips; + else + step = len / num_bitflips; + + for (i = 0; i < num_bitflips; i++) { + int offs; + int bit; + u8 *pos = buf; + + if (random) { + offs = random32() % len; + bit = random32() % 8; + } else { + offs = i * len / num_bitflips; + bit = i % 8; + } + + pos[offs] ^= 1 << bit; + + if (info) + dev_info(&mtd->class_dev, "Flipping bit %d @ %d\n", bit, offs); + } + + ret = mtd_peb_erase(mtd, pnum); + if (ret < 0) { + dev_err(&mtd->class_dev, "Cannot erase PEB %d\n", pnum); + goto err; + } + + for (i = 0; i < pages_per_block; i++) { + loff_t offs = (loff_t)pnum * mtd->erasesize + i * mtd->writesize; + + ops.datbuf = buf + i * mtd->writesize; + ops.oobbuf = oobbuf + i * mtd->oobsize; + + ret = mtd_write_oob(mtd, offs, &ops); + if (ret) { + dev_err(&mtd->class_dev, "Cannot write page at 0x%08llx\n", offs); + goto err; + } + } + + ret = 0; +err: + if (ret) + dev_err(&mtd->class_dev, "Failed to create bitflips: %s\n", strerror(-ret)); + + free(buf); + free(oobbuf); + + return ret; +} diff --git a/drivers/mtd/ubi/Kconfig b/drivers/mtd/ubi/Kconfig index ccd547d..4c49793 100644 --- a/drivers/mtd/ubi/Kconfig +++ b/drivers/mtd/ubi/Kconfig @@ -77,4 +77,17 @@ If in doubt, say "N". +comment "UBI debugging options" + +config MTD_UBI_CHECK_IO + bool "Check IO operations" + help + When enabled UBI will check if erased blocks are really erased and if areas + written to are empty before writing. + +config MTD_UBI_GENERAL_EXTRA_CHECKS + bool "general extra checks" + help + This enables some general extra checks in UBI + endif # MTD_UBI diff --git a/drivers/mtd/ubi/attach.c b/drivers/mtd/ubi/attach.c index d6fe43b..88370f4 100644 --- a/drivers/mtd/ubi/attach.c +++ b/drivers/mtd/ubi/attach.c @@ -772,7 +772,7 @@ if (err) goto out_unlock; - if (ubi_check_pattern(ubi->peb_buf, 0xFF, ubi->leb_size)) + if (mtd_buf_all_ff(ubi->peb_buf, ubi->leb_size)) goto out_unlock; ubi_err("PEB %d contains corrupted VID header, and the data does not contain all 0xFF", diff --git a/drivers/mtd/ubi/debug.h b/drivers/mtd/ubi/debug.h index ebf961b..511e454 100644 --- a/drivers/mtd/ubi/debug.h +++ b/drivers/mtd/ubi/debug.h @@ -72,54 +72,19 @@ return ubi->dbg.disable_bgt; } -/** - * ubi_dbg_is_bitflip - if it is time to emulate a bit-flip. - * @ubi: UBI device description object - * - * Returns non-zero if a bit-flip should be emulated, otherwise returns zero. - */ -static inline int ubi_dbg_is_bitflip(const struct ubi_device *ubi) -{ - if (ubi->dbg.emulate_bitflips) - return !(random32() % 200); - return 0; -} - -/** - * ubi_dbg_is_write_failure - if it is time to emulate a write failure. - * @ubi: UBI device description object - * - * Returns non-zero if a write failure should be emulated, otherwise returns - * zero. - */ -static inline int ubi_dbg_is_write_failure(const struct ubi_device *ubi) -{ - if (ubi->dbg.emulate_io_failures) - return !(random32() % 500); - return 0; -} - -/** - * ubi_dbg_is_erase_failure - if its time to emulate an erase failure. - * @ubi: UBI device description object - * - * Returns non-zero if an erase failure should be emulated, otherwise returns - * zero. - */ -static inline int ubi_dbg_is_erase_failure(const struct ubi_device *ubi) -{ - if (ubi->dbg.emulate_io_failures) - return !(random32() % 400); - return 0; -} - static inline int ubi_dbg_chk_io(const struct ubi_device *ubi) { - return ubi->dbg.chk_io; + if (IS_ENABLED(CONFIG_MTD_UBI_CHECK_IO)) + return 1; + else + return 0; } static inline int ubi_dbg_chk_gen(const struct ubi_device *ubi) { - return ubi->dbg.chk_gen; + if (IS_ENABLED(CONFIG_MTD_UBI_GENERAL_EXTRA_CHECKS)) + return 1; + else + return 0; } #endif /* !__UBI_DEBUG_H__ */ diff --git a/drivers/mtd/ubi/io.c b/drivers/mtd/ubi/io.c index e55dfc5..b3cb4f2 100644 --- a/drivers/mtd/ubi/io.c +++ b/drivers/mtd/ubi/io.c @@ -83,6 +83,7 @@ */ #include +#include #include "ubi.h" static int self_check_not_bad(const struct ubi_device *ubi, int pnum); @@ -92,8 +93,6 @@ static int self_check_peb_vid_hdr(const struct ubi_device *ubi, int pnum); static int self_check_vid_hdr(const struct ubi_device *ubi, int pnum, const struct ubi_vid_hdr *vid_hdr); -static int self_check_write(struct ubi_device *ubi, const void *buf, int pnum, - int offset, int len); /** * ubi_io_read - read data from a physical eraseblock. @@ -120,91 +119,12 @@ int ubi_io_read(const struct ubi_device *ubi, void *buf, int pnum, int offset, int len) { - int err, retries = 0; - size_t read; - loff_t addr; + int ret; - dbg_io("read %d bytes from PEB %d:%d", len, pnum, offset); - - ubi_assert(pnum >= 0 && pnum < ubi->peb_count); - ubi_assert(offset >= 0 && offset + len <= ubi->peb_size); - ubi_assert(len > 0); - - err = self_check_not_bad(ubi, pnum); - if (err) - return err; - - /* - * Deliberately corrupt the buffer to improve robustness. Indeed, if we - * do not do this, the following may happen: - * 1. The buffer contains data from previous operation, e.g., read from - * another PEB previously. The data looks like expected, e.g., if we - * just do not read anything and return - the caller would not - * notice this. E.g., if we are reading a VID header, the buffer may - * contain a valid VID header from another PEB. - * 2. The driver is buggy and returns us success or -EBADMSG or - * -EUCLEAN, but it does not actually put any data to the buffer. - * - * This may confuse UBI or upper layers - they may think the buffer - * contains valid data while in fact it is just old data. This is - * especially possible because UBI (and UBIFS) relies on CRC, and - * treats data as correct even in case of ECC errors if the CRC is - * correct. - * - * Try to prevent this situation by changing the first byte of the - * buffer. - */ - *((uint8_t *)buf) ^= 0xFF; - - addr = (loff_t)pnum * ubi->peb_size + offset; -retry: - err = mtd_read(ubi->mtd, addr, len, &read, buf); - if (err) { - const char *errstr = mtd_is_eccerr(err) ? " (ECC error)" : ""; - - if (mtd_is_bitflip(err)) { - /* - * -EUCLEAN is reported if there was a bit-flip which - * was corrected, so this is harmless. - * - * We do not report about it here unless debugging is - * enabled. A corresponding message will be printed - * later, when it is has been scrubbed. - */ - ubi_msg("fixable bit-flip detected at PEB %d", pnum); - ubi_assert(len == read); - return UBI_IO_BITFLIPS; - } - - if (retries++ < UBI_IO_RETRIES) { - ubi_warn("error %d%s while reading %d bytes from PEB %d:%d, read only %zd bytes, retry", - err, errstr, len, pnum, offset, read); - goto retry; - } - - ubi_err("error %d%s while reading %d bytes from PEB %d:%d, read %zd bytes", - err, errstr, len, pnum, offset, read); - dump_stack(); - - /* - * The driver should never return -EBADMSG if it failed to read - * all the requested data. But some buggy drivers might do - * this, so we change it to -EIO. - */ - if (read != len && mtd_is_eccerr(err)) { - ubi_assert(0); - err = -EIO; - } - } else { - ubi_assert(len == read); - - if (ubi_dbg_is_bitflip(ubi)) { - dbg_gen("bit-flip (emulated)"); - err = UBI_IO_BITFLIPS; - } - } - - return err; + ret = mtd_peb_read(ubi->mtd, buf, pnum, offset, len); + if (mtd_is_bitflip(ret)) + return UBI_IO_BITFLIPS; + return ret; } /** @@ -228,8 +148,6 @@ int len) { int err; - size_t written; - loff_t addr; dbg_io("write %d bytes to PEB %d:%d", len, pnum, offset); @@ -243,15 +161,6 @@ return -EROFS; } - err = self_check_not_bad(ubi, pnum); - if (err) - return err; - - /* The area we are writing to has to contain all 0xFF bytes */ - err = ubi_self_check_all_ff(ubi, pnum, offset, len); - if (err) - return err; - if (offset >= ubi->leb_start) { /* * We write to the data area of the physical eraseblock. Make @@ -265,50 +174,7 @@ return err; } - if (ubi_dbg_is_write_failure(ubi)) { - ubi_err("cannot write %d bytes to PEB %d:%d (emulated)", - len, pnum, offset); - dump_stack(); - return -EIO; - } - - addr = (loff_t)pnum * ubi->peb_size + offset; - err = mtd_write(ubi->mtd, addr, len, &written, buf); - if (err) { - ubi_err("error %d while writing %d bytes to PEB %d:%d, written %zd bytes", - err, len, pnum, offset, written); - dump_stack(); - ubi_dump_flash(ubi, pnum, offset, len); - } else - ubi_assert(written == len); - - if (!err) { - err = self_check_write(ubi, buf, pnum, offset, len); - if (err) - return err; - - /* - * Since we always write sequentially, the rest of the PEB has - * to contain only 0xFF bytes. - */ - offset += len; - len = ubi->peb_size - offset; - if (len) - err = ubi_self_check_all_ff(ubi, pnum, offset, len); - } - - return err; -} - -/** - * erase_callback - MTD erasure call-back. - * @ei: MTD erase information object. - * - * Note, even though MTD erase interface is asynchronous, all the current - * implementations are synchronous anyway. - */ -static void erase_callback(struct erase_info *ei) -{ + return mtd_peb_write(ubi->mtd, buf, pnum, offset, len); } /** @@ -322,9 +188,6 @@ */ static int do_sync_erase(struct ubi_device *ubi, int pnum) { - int err, retries = 0; - struct erase_info ei; - dbg_io("erase PEB %d", pnum); ubi_assert(pnum >= 0 && pnum < ubi->peb_count); @@ -333,122 +196,7 @@ return -EROFS; } -retry: - memset(&ei, 0, sizeof(struct erase_info)); - - ei.mtd = ubi->mtd; - ei.addr = (loff_t)pnum * ubi->peb_size; - ei.len = ubi->peb_size; - ei.callback = erase_callback; - - err = mtd_erase(ubi->mtd, &ei); - if (err) { - if (retries++ < UBI_IO_RETRIES) { - ubi_warn("error %d while erasing PEB %d, retry", - err, pnum); - goto retry; - } - ubi_err("cannot erase PEB %d, error %d", pnum, err); - dump_stack(); - return err; - } - - if (ei.state == MTD_ERASE_FAILED) { - if (retries++ < UBI_IO_RETRIES) { - ubi_warn("error while erasing PEB %d, retry", pnum); - goto retry; - } - ubi_err("cannot erase PEB %d", pnum); - dump_stack(); - return -EIO; - } - - err = ubi_self_check_all_ff(ubi, pnum, 0, ubi->peb_size); - if (err) - return err; - - if (ubi_dbg_is_erase_failure(ubi)) { - ubi_err("cannot erase PEB %d (emulated)", pnum); - return -EIO; - } - - return 0; -} - -/* Patterns to write to a physical eraseblock when torturing it */ -static uint8_t patterns[] = {0xa5, 0x5a, 0x0}; - -/** - * torture_peb - test a supposedly bad physical eraseblock. - * @ubi: UBI device description object - * @pnum: the physical eraseblock number to test - * - * This function returns %-EIO if the physical eraseblock did not pass the - * test, a positive number of erase operations done if the test was - * successfully passed, and other negative error codes in case of other errors. - */ -static int torture_peb(struct ubi_device *ubi, int pnum) -{ - int err, i, patt_count; - - ubi_msg("run torture test for PEB %d", pnum); - patt_count = ARRAY_SIZE(patterns); - ubi_assert(patt_count > 0); - - for (i = 0; i < patt_count; i++) { - err = do_sync_erase(ubi, pnum); - if (err) - goto out; - - /* Make sure the PEB contains only 0xFF bytes */ - err = ubi_io_read(ubi, ubi->peb_buf, pnum, 0, ubi->peb_size); - if (err) - goto out; - - err = ubi_check_pattern(ubi->peb_buf, 0xFF, ubi->peb_size); - if (err == 0) { - ubi_err("erased PEB %d, but a non-0xFF byte found", - pnum); - err = -EIO; - goto out; - } - - /* Write a pattern and check it */ - memset(ubi->peb_buf, patterns[i], ubi->peb_size); - err = ubi_io_write(ubi, ubi->peb_buf, pnum, 0, ubi->peb_size); - if (err) - goto out; - - memset(ubi->peb_buf, ~patterns[i], ubi->peb_size); - err = ubi_io_read(ubi, ubi->peb_buf, pnum, 0, ubi->peb_size); - if (err) - goto out; - - err = ubi_check_pattern(ubi->peb_buf, patterns[i], - ubi->peb_size); - if (err == 0) { - ubi_err("pattern %x checking failed for PEB %d", - patterns[i], pnum); - err = -EIO; - goto out; - } - } - - err = patt_count; - ubi_msg("PEB %d passed torture test, do not mark it as bad", pnum); - -out: - if (err == UBI_IO_BITFLIPS || mtd_is_eccerr(err)) { - /* - * If a bit-flip or data integrity error was detected, the test - * has not passed because it happened on a freshly erased - * physical eraseblock which means something is wrong with it. - */ - ubi_err("read problems on freshly erased PEB %d, must be bad", - pnum); - err = -EIO; - } - return err; + return mtd_peb_erase(ubi->mtd, pnum); } /** @@ -563,15 +311,15 @@ } if (torture) { - ret = torture_peb(ubi, pnum); + ret = mtd_peb_torture(ubi->mtd, pnum); if (ret < 0) return ret; + } else { + err = do_sync_erase(ubi, pnum); + if (err) + return err; } - err = do_sync_erase(ubi, pnum); - if (err) - return err; - return ret + 1; } @@ -605,35 +353,6 @@ } /** - * ubi_io_mark_bad - mark a physical eraseblock as bad. - * @ubi: UBI device description object - * @pnum: the physical eraseblock number to mark - * - * This function returns zero in case of success and a negative error code in - * case of failure. - */ -int ubi_io_mark_bad(const struct ubi_device *ubi, int pnum) -{ - int err; - struct mtd_info *mtd = ubi->mtd; - - ubi_assert(pnum >= 0 && pnum < ubi->peb_count); - - if (ubi->ro_mode) { - ubi_err("read-only mode"); - return -EROFS; - } - - if (!ubi->bad_allowed) - return 0; - - err = mtd_block_markbad(mtd, (loff_t)pnum * ubi->peb_size); - if (err) - ubi_err("cannot mark PEB %d bad, error %d", pnum, err); - return err; -} - -/** * validate_ec_hdr - validate an erase counter header. * @ubi: UBI device description object * @ec_hdr: the erase counter header to check @@ -740,7 +459,7 @@ * 0xFF. If yes, this physical eraseblock is assumed to be * empty. */ - if (ubi_check_pattern(ec_hdr, 0xFF, UBI_EC_HDR_SIZE)) { + if (mtd_buf_all_ff(ec_hdr, UBI_EC_HDR_SIZE)) { /* The physical eraseblock is supposedly empty */ if (verbose) ubi_warn("no EC header found at PEB %d, only 0xFF bytes", @@ -996,7 +715,7 @@ if (mtd_is_eccerr(read_err)) return UBI_IO_BAD_HDR_EBADMSG; - if (ubi_check_pattern(vid_hdr, 0xFF, UBI_VID_HDR_SIZE)) { + if (mtd_buf_all_ff(vid_hdr, UBI_VID_HDR_SIZE)) { if (verbose) ubi_warn("no VID header found at PEB %d, only 0xFF bytes", pnum); @@ -1283,72 +1002,6 @@ } /** - * self_check_write - make sure write succeeded. - * @ubi: UBI device description object - * @buf: buffer with data which were written - * @pnum: physical eraseblock number the data were written to - * @offset: offset within the physical eraseblock the data were written to - * @len: how many bytes were written - * - * This functions reads data which were recently written and compares it with - * the original data buffer - the data have to match. Returns zero if the data - * match and a negative error code if not or in case of failure. - */ -static int self_check_write(struct ubi_device *ubi, const void *buf, int pnum, - int offset, int len) -{ - int err, i; - size_t read; - void *buf1; - loff_t addr = (loff_t)pnum * ubi->peb_size + offset; - - if (!ubi_dbg_chk_io(ubi)) - return 0; - - buf1 = kmalloc(len, GFP_KERNEL); - if (!buf1) { - ubi_err("cannot allocate memory to check writes"); - return 0; - } - - err = mtd_read(ubi->mtd, addr, len, &read, buf1); - if (err && !mtd_is_bitflip(err)) - goto out_free; - - for (i = 0; i < len; i++) { - uint8_t c = ((uint8_t *)buf)[i]; - uint8_t c1 = ((uint8_t *)buf1)[i]; - int dump_len; - - if (c == c1) - continue; - - ubi_err("self-check failed for PEB %d:%d, len %d", - pnum, offset, len); - ubi_msg("data differ at position %d", i); - dump_len = max_t(int, 128, len - i); - ubi_msg("hex dump of the original buffer from %d to %d", - i, i + dump_len); - print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_OFFSET, 32, 1, - buf + i, dump_len, 1); - ubi_msg("hex dump of the read buffer from %d to %d", - i, i + dump_len); - print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_OFFSET, 32, 1, - buf1 + i, dump_len, 1); - dump_stack(); - err = -EINVAL; - goto out_free; - } - - vfree(buf1); - return 0; - -out_free: - vfree(buf1); - return err; -} - -/** * ubi_self_check_all_ff - check that a region of flash is empty. * @ubi: UBI device description object * @pnum: the physical eraseblock number to check @@ -1361,44 +1014,8 @@ */ int ubi_self_check_all_ff(struct ubi_device *ubi, int pnum, int offset, int len) { - size_t read; - int err; - void *buf; - loff_t addr = (loff_t)pnum * ubi->peb_size + offset; - if (!ubi_dbg_chk_io(ubi)) return 0; - buf = kmalloc(len, GFP_KERNEL); - if (!buf) { - ubi_err("cannot allocate memory to check for 0xFFs"); - return 0; - } - - err = mtd_read(ubi->mtd, addr, len, &read, buf); - if (err && !mtd_is_bitflip(err)) { - ubi_err("error %d while reading %d bytes from PEB %d:%d, read %zd bytes", - err, len, pnum, offset, read); - goto error; - } - - err = ubi_check_pattern(buf, 0xFF, len); - if (err == 0) { - ubi_err("flash region at PEB %d:%d, length %d does not contain all 0xFF bytes", - pnum, offset, len); - goto fail; - } - - vfree(buf); - return 0; - -fail: - ubi_err("self-check failed for PEB %d", pnum); - ubi_msg("hex dump of the %d-%d region", offset, offset + len); - print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_OFFSET, 32, 1, buf, len, 1); - err = -EINVAL; -error: - dump_stack(); - vfree(buf); - return err; + return mtd_peb_check_all_ff(ubi->mtd, pnum, offset, len, 1); } diff --git a/drivers/mtd/ubi/misc.c b/drivers/mtd/ubi/misc.c index b5c6efe..9633466 100644 --- a/drivers/mtd/ubi/misc.c +++ b/drivers/mtd/ubi/misc.c @@ -128,22 +128,3 @@ ubi->bad_peb_count, ubi->bad_peb_limit); } } - -/** - * ubi_check_pattern - check if buffer contains only a certain byte pattern. - * @buf: buffer to check - * @patt: the pattern to check - * @size: buffer size in bytes - * - * This function returns %1 in there are only @patt bytes in @buf, and %0 if - * something else was also found. - */ -int ubi_check_pattern(const void *buf, uint8_t patt, int size) -{ - int i; - - for (i = 0; i < size; i++) - if (((const uint8_t *)buf)[i] != patt) - return 0; - return 1; -} diff --git a/drivers/mtd/ubi/ubi.h b/drivers/mtd/ubi/ubi.h index 61ae738..03a36d2 100644 --- a/drivers/mtd/ubi/ubi.h +++ b/drivers/mtd/ubi/ubi.h @@ -757,7 +757,6 @@ int ubi_check_volume(struct ubi_device *ubi, int vol_id); void ubi_update_reserved(struct ubi_device *ubi); void ubi_calculate_reserved(struct ubi_device *ubi); -int ubi_check_pattern(const void *buf, uint8_t patt, int size); /* eba.c */ int ubi_eba_unmap_leb(struct ubi_device *ubi, struct ubi_volume *vol, @@ -800,7 +799,6 @@ int len); int ubi_io_sync_erase(struct ubi_device *ubi, int pnum, int torture); int ubi_io_is_bad(const struct ubi_device *ubi, int pnum); -int ubi_io_mark_bad(const struct ubi_device *ubi, int pnum); int ubi_io_read_ec_hdr(struct ubi_device *ubi, int pnum, struct ubi_ec_hdr *ec_hdr, int verbose); int ubi_io_write_ec_hdr(struct ubi_device *ubi, int pnum, diff --git a/drivers/mtd/ubi/wl.c b/drivers/mtd/ubi/wl.c index 4c20e90..cb2f9d7 100644 --- a/drivers/mtd/ubi/wl.c +++ b/drivers/mtd/ubi/wl.c @@ -1421,11 +1421,6 @@ available_consumed = 1; } - ubi_msg("mark PEB %d as bad", pnum); - err = ubi_io_mark_bad(ubi, pnum); - if (err) - goto out_ro; - if (ubi->beb_rsvd_pebs > 0) { if (available_consumed) { /* diff --git a/fs/devfs-core.c b/fs/devfs-core.c index deacaaa..75ed3b0 100644 --- a/fs/devfs-core.c +++ b/fs/devfs-core.c @@ -192,6 +192,7 @@ switch (request) { case MEMSETBADBLOCK: + case MEMSETGOODBLOCK: case MEMGETBADBLOCK: offset = *_buf; offset += cdev->offset; diff --git a/include/bbu.h b/include/bbu.h index 701d7f6..9d24ffc 100644 --- a/include/bbu.h +++ b/include/bbu.h @@ -24,6 +24,7 @@ const char *name; struct list_head list; #define BBU_HANDLER_FLAG_DEFAULT (1 << 0) +#define BBU_HANDLER_CAN_REFRESH (1 << 1) unsigned long flags; /* default device file, can be overwritten on the command line */ diff --git a/include/linux/mtd/mtd-abi.h b/include/linux/mtd/mtd-abi.h index 8e778df..9bca9b5 100644 --- a/include/linux/mtd/mtd-abi.h +++ b/include/linux/mtd/mtd-abi.h @@ -118,6 +118,7 @@ #define ECCGETLAYOUT _IOR('M', 17, struct nand_ecclayout) #define ECCGETSTATS _IOR('M', 18, struct mtd_ecc_stats) #define MTDFILEMODE _IO('M', 19) +#define MEMSETGOODBLOCK _IOW('M', 20, loff_t) /* * Obsolete legacy interface. Keep it in order not to break userspace diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h index 421a941..18d8866 100644 --- a/include/linux/mtd/mtd.h +++ b/include/linux/mtd/mtd.h @@ -189,6 +189,7 @@ /* Bad block management functions */ int (*block_isbad) (struct mtd_info *mtd, loff_t ofs); int (*block_markbad) (struct mtd_info *mtd, loff_t ofs); + int (*block_markgood) (struct mtd_info *mtd, loff_t ofs); /* ECC status information */ struct mtd_ecc_stats ecc_stats; @@ -280,6 +281,7 @@ extern void put_mtd_device(struct mtd_info *mtd); +const char *mtd_type_str(struct mtd_info *mtd); struct mtd_notifier { void (*add)(struct mtd_info *mtd); @@ -308,8 +310,10 @@ int mtd_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len); int mtd_block_isbad(struct mtd_info *mtd, loff_t ofs); int mtd_block_markbad(struct mtd_info *mtd, loff_t ofs); +int mtd_block_markgood(struct mtd_info *mtd, loff_t ofs); -int mtd_all_ff(const void *buf, unsigned int len); +int mtd_buf_all_ff(const void *buf, unsigned int len); +int mtd_buf_check_pattern(const void *buf, uint8_t patt, int size); /* * Debugging macro and defines diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index 83d664e..5beec39 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -50,6 +50,7 @@ void *ecc, int ecclen, void *extraoob, int extraooblen, int bitflips_threshold); +int nand_check_erased_buf(void *buf, int len, int bitflips_threshold); /* The maximum number of NAND chips in an array */ #define NAND_MAX_CHIPS 8 @@ -394,6 +395,7 @@ * @select_chip: [REPLACEABLE] select chip nr * @block_bad: [REPLACEABLE] check, if the block is bad * @block_markbad: [REPLACEABLE] mark the block bad + * @block_markgood: [REPLACEABLE] mark the block good * @cmd_ctrl: [BOARDSPECIFIC] hardwarespecific function for controlling * ALE/CLE/nCE. Also used to write command and address * @init_size: [BOARDSPECIFIC] hardwarespecific function for setting @@ -479,6 +481,7 @@ void (*select_chip)(struct mtd_info *mtd, int chip); int (*block_bad)(struct mtd_info *mtd, loff_t ofs, int getchip); int (*block_markbad)(struct mtd_info *mtd, loff_t ofs); + int (*block_markgood)(struct mtd_info *mtd, loff_t ofs); void (*cmd_ctrl)(struct mtd_info *mtd, int dat, unsigned int ctrl); int (*init_size)(struct mtd_info *mtd, struct nand_chip *this, u8 *id_data); diff --git a/include/mtd/libmtd.h b/include/mtd/libmtd.h deleted file mode 100644 index 65c390a..0000000 --- a/include/mtd/libmtd.h +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (C) 2008, 2009 Nokia Corporation - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See - * the GNU General Public License for more details. - * Author: Artem Bityutskiy - * - * MTD library. - */ - -#ifndef __LIBMTD_H__ -#define __LIBMTD_H__ - -/* Maximum MTD device name length */ -#define MTD_NAME_MAX 127 -/* Maximum MTD device type string length */ -#define MTD_TYPE_MAX 64 - -/** - * struct mtd_dev_info - information about an MTD device. - * @node: node pointing to device - * @type: flash type (constants like %MTD_NANDFLASH defined in mtd-abi.h) - * @type_str: static R/O flash type string - * @name: device name - * @size: device size in bytes - * @eb_cnt: count of eraseblocks - * @eb_size: eraseblock size - * @min_io_size: minimum input/output unit size - * @subpage_size: sub-page size - * @oob_size: OOB size (zero if the device does not have OOB area) - * @region_cnt: count of additional erase regions - * @writable: zero if the device is read-only - * @bb_allowed: non-zero if the MTD device may have bad eraseblocks - */ -struct mtd_dev_info -{ - const char *node; - int type; - const char type_str[MTD_TYPE_MAX + 1]; - long long size; - int eb_cnt; - int eb_size; - int min_io_size; - int subpage_size; - int oob_size; - int region_cnt; - unsigned int writable:1; - unsigned int bb_allowed:1; -}; - -/** - * mtd_get_dev_info - get information about an MTD device. - * @desc: MTD library descriptor - * @node: name of the MTD device node - * @mtd: the MTD device information is returned here - * - * This function gets information about MTD device defined by the @node device - * node file and saves this information in the @mtd object. Returns %0 in case - * of success and %-1 in case of failure. If MTD subsystem is not present in the - * system, or the MTD device does not exist, errno is set to @ENODEV. - */ -int mtd_get_dev_info(const char *node, struct mtd_dev_info *mtd); - -/** - * libmtd_erase - erase an eraseblock. - * @desc: MTD library descriptor - * @mtd: MTD device description object - * @fd: MTD device node file descriptor - * @eb: eraseblock to erase - * - * This function erases eraseblock @eb of MTD device described by @fd. Returns - * %0 in case of success and %-1 in case of failure. - */ -int libmtd_erase(const struct mtd_dev_info *mtd, int fd, int eb); - -/** - * mtd_torture - torture an eraseblock. - * @desc: MTD library descriptor - * @mtd: MTD device description object - * @fd: MTD device node file descriptor - * @eb: eraseblock to torture - * - * This function tortures eraseblock @eb. Returns %0 in case of success and %-1 - * in case of failure. - */ -int mtd_torture(const struct mtd_dev_info *mtd, int fd, int eb); - -/** - * mtd_is_bad - check if eraseblock is bad. - * @mtd: MTD device description object - * @fd: MTD device node file descriptor - * @eb: eraseblock to check - * - * This function checks if eraseblock @eb is bad. Returns %0 if not, %1 if yes, - * and %-1 in case of failure. - */ -int mtd_is_bad(const struct mtd_dev_info *mtd, int fd, int eb); - -/** - * mtd_mark_bad - mark an eraseblock as bad. - * @mtd: MTD device description object - * @fd: MTD device node file descriptor - * @eb: eraseblock to mark as bad - * - * This function marks eraseblock @eb as bad. Returns %0 in case of success and - * %-1 in case of failure. - */ -int mtd_mark_bad(const struct mtd_dev_info *mtd, int fd, int eb); - -/** - * mtd_read - read data from an MTD device. - * @mtd: MTD device description object - * @fd: MTD device node file descriptor - * @eb: eraseblock to read from - * @offs: offset withing the eraseblock to read from - * @buf: buffer to read data to - * @len: how many bytes to read - * - * This function reads @len bytes of data from eraseblock @eb and offset @offs - * of the MTD device defined by @mtd and stores the read data at buffer @buf. - * Returns %0 in case of success and %-1 in case of failure. - */ -int libmtd_read(const struct mtd_dev_info *mtd, int fd, int eb, int offs, - void *buf, int len); - -/** - * mtd_write - write data to an MTD device. - * @mtd: MTD device description object - * @fd: MTD device node file descriptor - * @eb: eraseblock to write to - * @offs: offset withing the eraseblock to write to - * @buf: buffer to write - * @len: how many bytes to write - * - * This function writes @len bytes of data to eraseblock @eb and offset @offs - * of the MTD device defined by @mtd. Returns %0 in case of success and %-1 in - * case of failure. - */ -int libmtd_write(const struct mtd_dev_info *mtd, int fd, int eb, int offs, - void *buf, int len); - -#endif /* __LIBMTD_H__ */ diff --git a/include/mtd/libscan.h b/include/mtd/libscan.h index bb01482..e925a26 100644 --- a/include/mtd/libscan.h +++ b/include/mtd/libscan.h @@ -84,12 +84,11 @@ /** * ubi_scan - scan an MTD device. * @mtd: information about the MTD device to scan - * @fd: MTD device node file descriptor * @info: the result of the scanning is returned here * @verbose: verbose mode: %0 - be silent, %1 - output progress information, * 2 - debugging output mode */ -int libscan_ubi_scan(struct mtd_dev_info *mtd, int fd, struct ubi_scan_info **info, +int libscan_ubi_scan(struct mtd_info *mtd, struct ubi_scan_info **info, int verbose); /** diff --git a/include/mtd/libubigen.h b/include/mtd/libubigen.h index f05978b..266f393 100644 --- a/include/mtd/libubigen.h +++ b/include/mtd/libubigen.h @@ -170,13 +170,13 @@ * @ec1: erase counter value for @peb1 * @ec2: erase counter value for @peb1 * @vtbl: volume table - * @fd: output file descriptor seeked to the proper position + * @mtd: The mtd device * * This function creates the UBI layout volume which contains 2 copies of the * volume table. Returns zero in case of success and %-1 in case of failure. */ int ubigen_write_layout_vol(const struct ubigen_info *ui, int peb1, int peb2, long long ec1, long long ec2, - struct ubi_vtbl_record *vtbl, int fd); + struct ubi_vtbl_record *vtbl, struct mtd_info *mtd); #endif /* !__LIBUBIGEN_H__ */ diff --git a/include/mtd/mtd-peb.h b/include/mtd/mtd-peb.h new file mode 100644 index 0000000..e4fd01d --- /dev/null +++ b/include/mtd/mtd-peb.h @@ -0,0 +1,24 @@ +#ifndef __LINUX_MTD_MTDPEB_H +#define __LINUX_MTD_MTDPEB_H + +#include + +int mtd_peb_read(struct mtd_info *mtd, void *buf, int pnum, int offset, + int len); +int mtd_peb_write(struct mtd_info *mtd, const void *buf, int pnum, int offset, + int len); + +int mtd_peb_torture(struct mtd_info *mtd, int pnum); +int mtd_peb_erase(struct mtd_info *mtd, int pnum); +int mtd_peb_mark_bad(struct mtd_info *mtd, int pnum); +int mtd_peb_is_bad(struct mtd_info *mtd, int pnum); +int mtd_peb_check_all_ff(struct mtd_info *mtd, int pnum, int offset, int len, + int warn); +int mtd_peb_verify(struct mtd_info *mtd, const void *buf, int pnum, + int offset, int len); +int mtd_num_pebs(struct mtd_info *mtd); +int mtd_peb_create_bitflips(struct mtd_info *mtd, int pnum, int offset, + int len, int num_bitflips, int random, + int info); + +#endif /* __LINUX_MTD_MTDPEB_H */ diff --git a/lib/Kconfig b/lib/Kconfig index f051751..d5f99ae 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -52,9 +52,6 @@ config LIBUBIGEN bool -config LIBMTD - bool - config STMP_DEVICE bool diff --git a/lib/Makefile b/lib/Makefile index 44ba25b..b6da848 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -44,7 +44,6 @@ obj-$(CONFIG_QSORT) += qsort.o obj-$(CONFIG_LIBSCAN) += libscan.o obj-$(CONFIG_LIBUBIGEN) += libubigen.o -obj-$(CONFIG_LIBMTD) += libmtd.o obj-y += gui/ obj-$(CONFIG_XYMODEM) += xymodem.o obj-y += unlink-recursive.o diff --git a/lib/libmtd.c b/lib/libmtd.c deleted file mode 100644 index 56672bd..0000000 --- a/lib/libmtd.c +++ /dev/null @@ -1,368 +0,0 @@ -/* - * Copyright (C) 2009 Nokia Corporation - * Copyright (C) 2012 Wolfram Sang, Pengutronix e.K. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See - * the GNU General Public License for more details. - * - * Author: Artem Bityutskiy - * Author: Wolfram Sang - * - * This file is part of the MTD library. Based on pre-2.6.30 kernels support, - * now adapted to barebox. - * - * NOTE: No support for 64 bit sizes yet! - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define PROGRAM_NAME "libmtd" - -static inline int mtd_ioctl_error(const struct mtd_dev_info *mtd, int eb, - const char *sreq) -{ - return sys_errmsg("%s ioctl failed for eraseblock %d (%s)", - sreq, eb, mtd->node); -} - -static int mtd_valid_erase_block(const struct mtd_dev_info *mtd, int eb) -{ - if (eb < 0 || eb >= mtd->eb_cnt) { - errmsg("bad eraseblock number %d, %s has %d eraseblocks", - eb, mtd->node, mtd->eb_cnt); - errno = EINVAL; - return -1; - } - return 0; -} - -int libmtd_erase(const struct mtd_dev_info *mtd, int fd, int eb) -{ - int ret; - struct erase_info_user ei; - - ret = mtd_valid_erase_block(mtd, eb); - if (ret) - return ret; - - ei.start = (__u64)eb * mtd->eb_size; - ei.length = mtd->eb_size; - - ret = ioctl(fd, MEMERASE, &ei); - if (ret < 0) - return mtd_ioctl_error(mtd, eb, "MEMERASE"); - return 0; -} - -/* Patterns to write to a physical eraseblock when torturing it */ -static uint8_t patterns[] = {0xa5, 0x5a, 0x0}; - -/** - * check_pattern - check if buffer contains only a certain byte pattern. - * @buf: buffer to check - * @patt: the pattern to check - * @size: buffer size in bytes - * - * This function returns %1 in there are only @patt bytes in @buf, and %0 if - * something else was also found. - */ -static int check_pattern(const void *buf, uint8_t patt, int size) -{ - int i; - - for (i = 0; i < size; i++) - if (((const uint8_t *)buf)[i] != patt) - return 0; - return 1; -} - -int mtd_torture(const struct mtd_dev_info *mtd, int fd, int eb) -{ - int err, i, patt_count; - void *buf; - - normsg("run torture test for PEB %d", eb); - patt_count = ARRAY_SIZE(patterns); - - buf = xmalloc(mtd->eb_size); - - for (i = 0; i < patt_count; i++) { - err = libmtd_erase(mtd, fd, eb); - if (err) - goto out; - - /* Make sure the PEB contains only 0xFF bytes */ - err = libmtd_read(mtd, fd, eb, 0, buf, mtd->eb_size); - if (err) - goto out; - - err = check_pattern(buf, 0xFF, mtd->eb_size); - if (err == 0) { - errmsg("erased PEB %d, but a non-0xFF byte found", eb); - errno = EIO; - goto out; - } - - /* Write a pattern and check it */ - memset(buf, patterns[i], mtd->eb_size); - err = libmtd_write(mtd, fd, eb, 0, buf, mtd->eb_size); - if (err) - goto out; - - memset(buf, ~patterns[i], mtd->eb_size); - err = libmtd_read(mtd, fd, eb, 0, buf, mtd->eb_size); - if (err) - goto out; - - err = check_pattern(buf, patterns[i], mtd->eb_size); - if (err == 0) { - errmsg("pattern %x checking failed for PEB %d", - patterns[i], eb); - errno = EIO; - goto out; - } - } - - err = 0; - normsg("PEB %d passed torture test, do not mark it a bad", eb); - -out: - free(buf); - return -1; -} - -int mtd_is_bad(const struct mtd_dev_info *mtd, int fd, int eb) -{ - int ret; - loff_t seek; - - ret = mtd_valid_erase_block(mtd, eb); - if (ret) - return ret; - - if (!mtd->bb_allowed) - return 0; - - seek = (loff_t)eb * mtd->eb_size; - ret = ioctl(fd, MEMGETBADBLOCK, &seek); - if (ret == -1) - return mtd_ioctl_error(mtd, eb, "MEMGETBADBLOCK"); - return ret; -} - -int mtd_mark_bad(const struct mtd_dev_info *mtd, int fd, int eb) -{ - int ret; - loff_t seek; - - if (!mtd->bb_allowed) { - errno = EINVAL; - return -1; - } - - ret = mtd_valid_erase_block(mtd, eb); - if (ret) - return ret; - - seek = (loff_t)eb * mtd->eb_size; - ret = ioctl(fd, MEMSETBADBLOCK, &seek); - if (ret == -1) - return mtd_ioctl_error(mtd, eb, "MEMSETBADBLOCK"); - return 0; -} - -int libmtd_read(const struct mtd_dev_info *mtd, int fd, int eb, int offs, - void *buf, int len) -{ - int ret, rd = 0; - loff_t seek; - - ret = mtd_valid_erase_block(mtd, eb); - if (ret) - return ret; - - if (offs < 0 || offs + len > mtd->eb_size) { - errmsg("bad offset %d or length %d, %s eraseblock size is %d", - offs, len, mtd->node, mtd->eb_size); - errno = EINVAL; - return -1; - } - - /* Seek to the beginning of the eraseblock */ - seek = (loff_t)eb * mtd->eb_size + offs; - if (lseek(fd, seek, SEEK_SET) != seek) - return sys_errmsg("cannot seek %s to offset %llu", - mtd->node, (unsigned long long)seek); - - while (rd < len) { - ret = read(fd, buf, len); - if (ret < 0) - return sys_errmsg("cannot read %d bytes from %s (eraseblock %d, offset %d)", - len, mtd->node, eb, offs); - rd += ret; - } - - return 0; -} - -int libmtd_write(const struct mtd_dev_info *mtd, int fd, int eb, int offs, - void *buf, int len) -{ - int ret; - loff_t seek; - - ret = mtd_valid_erase_block(mtd, eb); - if (ret) - return ret; - - if (offs < 0 || offs + len > mtd->eb_size) { - errmsg("bad offset %d or length %d, %s eraseblock size is %d", - offs, len, mtd->node, mtd->eb_size); - errno = EINVAL; - return -1; - } - if (offs % mtd->subpage_size) { - errmsg("write offset %d is not aligned to %s min. I/O size %d", - offs, mtd->node, mtd->subpage_size); - errno = EINVAL; - return -1; - } - if (len % mtd->subpage_size) { - errmsg("write length %d is not aligned to %s min. I/O size %d", - len, mtd->node, mtd->subpage_size); - errno = EINVAL; - return -1; - } - - /* Seek to the beginning of the eraseblock */ - seek = (loff_t)eb * mtd->eb_size + offs; - if (lseek(fd, seek, SEEK_SET) != seek) - return sys_errmsg("cannot seek %s to offset %llu", - mtd->node, (unsigned long long)seek); - - ret = write(fd, buf, len); - if (ret != len) - return sys_errmsg("cannot write %d bytes to %s (eraseblock %d, offset %d)", - len, mtd->node, eb, offs); - - return 0; -} - -/** - * mtd_get_dev_info - fill the mtd_dev_info structure - * @node: name of the MTD device node - * @mtd: the MTD device information is returned here - */ -int mtd_get_dev_info(const char *node, struct mtd_dev_info *mtd) -{ - struct mtd_info_user ui; - int fd, ret; - loff_t offs = 0; - - memset(mtd, '\0', sizeof(struct mtd_dev_info)); - - mtd->node = node; - - fd = open(node, O_RDWR); - if (fd < 0) - return sys_errmsg("cannot open \"%s\"", node); - - if (ioctl(fd, MEMGETINFO, &ui)) { - sys_errmsg("MEMGETINFO ioctl request failed"); - goto out_close; - } - - ret = ioctl(fd, MEMGETBADBLOCK, &offs); - if (ret == -1) { - if (errno != EOPNOTSUPP) { - sys_errmsg("MEMGETBADBLOCK ioctl failed"); - goto out_close; - } - errno = 0; - mtd->bb_allowed = 0; - } else - mtd->bb_allowed = 1; - - mtd->type = ui.type; - mtd->size = ui.size; - mtd->eb_size = ui.erasesize; - mtd->min_io_size = ui.writesize; - mtd->oob_size = ui.oobsize; - mtd->subpage_size = ui.subpagesize; - - if (mtd->min_io_size <= 0) { - errmsg("%s has insane min. I/O unit size %d", - node, mtd->min_io_size); - goto out_close; - } - if (mtd->eb_size <= 0 || mtd->eb_size < mtd->min_io_size) { - errmsg("%s has insane eraseblock size %d", - node, mtd->eb_size); - goto out_close; - } - if (mtd->size <= 0 || mtd->size < mtd->eb_size) { - errmsg("%s has insane size %lld", - node, mtd->size); - goto out_close; - } - - mtd->eb_cnt = mtd_user_div_by_eb(ui.size, &ui); - - switch(mtd->type) { - case MTD_ABSENT: - errmsg("%s (%s) is removable and is not present", - mtd->node, node); - goto out_close; - case MTD_RAM: - strcpy((char *)mtd->type_str, "ram"); - break; - case MTD_ROM: - strcpy((char *)mtd->type_str, "rom"); - break; - case MTD_NORFLASH: - strcpy((char *)mtd->type_str, "nor"); - break; - case MTD_NANDFLASH: - strcpy((char *)mtd->type_str, "nand"); - break; - case MTD_DATAFLASH: - strcpy((char *)mtd->type_str, "dataflash"); - break; - case MTD_UBIVOLUME: - strcpy((char *)mtd->type_str, "ubi"); - break; - default: - goto out_close; - } - - if (ui.flags & MTD_WRITEABLE) - mtd->writable = 1; - - close(fd); - - return 0; - -out_close: - close(fd); - return -1; -} diff --git a/lib/libscan.c b/lib/libscan.c index 0c850ae..bf298a7 100644 --- a/lib/libscan.c +++ b/lib/libscan.c @@ -26,26 +26,28 @@ #include #include #include -#include +#include #include #include #include #include #include -int libscan_ubi_scan(struct mtd_dev_info *mtd, int fd, struct ubi_scan_info **info, +int libscan_ubi_scan(struct mtd_info *mtd, struct ubi_scan_info **info, int verbose) { - int eb, v = (verbose == 2), pr = (verbose == 1); + int eb, v = (verbose == 2), pr = (verbose == 1), eb_cnt; struct ubi_scan_info *si; unsigned long long sum = 0; + eb_cnt = mtd_div_by_eb(mtd->size, mtd); + si = calloc(1, sizeof(struct ubi_scan_info)); if (!si) return sys_errmsg("cannot allocate %zd bytes of memory", sizeof(struct ubi_scan_info)); - si->ec = calloc(mtd->eb_cnt, sizeof(uint32_t)); + si->ec = calloc(eb_cnt, sizeof(uint32_t)); if (!si->ec) { sys_errmsg("cannot allocate %zd bytes of memory", sizeof(struct ubi_scan_info)); @@ -54,8 +56,8 @@ si->vid_hdr_offs = si->data_offs = -1; - verbose(v, "start scanning eraseblocks 0-%d", mtd->eb_cnt); - for (eb = 0; eb < mtd->eb_cnt; eb++) { + verbose(v, "start scanning eraseblocks 0-%d", eb_cnt); + for (eb = 0; eb < eb_cnt; eb++) { int ret; uint32_t crc; struct ubi_ec_hdr ech; @@ -65,10 +67,10 @@ normsg_cont("scanning eraseblock %d", eb); if (pr) { printf("\r" PROGRAM_NAME ": scanning eraseblock %d -- %2u %% complete ", - eb, (eb + 1) * 100 / mtd->eb_cnt); + eb, (eb + 1) * 100 / eb_cnt); } - ret = mtd_is_bad(mtd, fd, eb); + ret = mtd_peb_is_bad(mtd, eb); if (ret == -1) goto out_ec; if (ret) { @@ -79,12 +81,12 @@ continue; } - ret = libmtd_read(mtd, fd, eb, 0, &ech, sizeof(struct ubi_ec_hdr)); + ret = mtd_peb_read(mtd, &ech, eb, 0, sizeof(struct ubi_ec_hdr)); if (ret < 0) goto out_ec; if (be32_to_cpu(ech.magic) != UBI_EC_HDR_MAGIC) { - if (mtd_all_ff(&ech, sizeof(struct ubi_ec_hdr))) { + if (mtd_buf_all_ff(&ech, sizeof(struct ubi_ec_hdr))) { si->empty_cnt += 1; si->ec[eb] = EB_EMPTY; if (v) @@ -121,14 +123,14 @@ if (si->vid_hdr_offs == -1) { si->vid_hdr_offs = be32_to_cpu(ech.vid_hdr_offset); si->data_offs = be32_to_cpu(ech.data_offset); - if (si->data_offs % mtd->min_io_size) { + if (si->data_offs % mtd->writesize) { if (pr) printf("\n"); if (v) printf(": corrupted because of the below\n"); warnmsg("bad data offset %d at eraseblock %d (n" "of multiple of min. I/O unit size %d)", - si->data_offs, eb, mtd->min_io_size); + si->data_offs, eb, mtd->writesize); warnmsg("treat eraseblock %d as corrupted", eb); si->corrupted_cnt += 1; si->ec[eb] = EB_CORRUPTED; @@ -174,7 +176,7 @@ if (si->ok_cnt != 0) { /* Calculate mean erase counter */ - for (eb = 0; eb < mtd->eb_cnt; eb++) { + for (eb = 0; eb < eb_cnt; eb++) { if (si->ec[eb] > EC_MAX) continue; sum += si->ec[eb]; @@ -183,7 +185,7 @@ si->mean_ec = sum; } - si->good_cnt = mtd->eb_cnt - si->bad_cnt; + si->good_cnt = eb_cnt - si->bad_cnt; verbose(v, "finished, mean EC %lld, %d OK, %d corrupted, %d empty, %d " "alien, bad %d", si->mean_ec, si->ok_cnt, si->corrupted_cnt, si->empty_cnt, si->alien_cnt, si->bad_cnt); diff --git a/lib/libubigen.c b/lib/libubigen.c index 9006329..77ebb05 100644 --- a/lib/libubigen.c +++ b/lib/libubigen.c @@ -34,6 +34,7 @@ #include #include #include +#include void ubigen_info_init(struct ubigen_info *ui, int peb_size, int min_io_size, int subpage_size, int vid_hdr_offs, int ubi_ver, @@ -247,13 +248,12 @@ int ubigen_write_layout_vol(const struct ubigen_info *ui, int peb1, int peb2, long long ec1, long long ec2, - struct ubi_vtbl_record *vtbl, int fd) + struct ubi_vtbl_record *vtbl, struct mtd_info *mtd) { int ret; struct ubigen_vol_info vi; char *outbuf; struct ubi_vid_hdr *vid_hdr; - off_t seek; vi.bytes = ui->leb_size * UBI_LAYOUT_VOLUME_EBS; vi.id = UBI_LAYOUT_VOLUME_ID; @@ -277,29 +277,18 @@ memset(outbuf + ui->data_offs + ui->vtbl_size, 0xFF, ui->peb_size - ui->data_offs - ui->vtbl_size); - seek = (off_t) peb1 * ui->peb_size; - if (lseek(fd, seek, SEEK_SET) != seek) { - sys_errmsg("cannot seek output file"); - goto out_free; - } - ubigen_init_ec_hdr(ui, (struct ubi_ec_hdr *)outbuf, ec1); ubigen_init_vid_hdr(ui, &vi, vid_hdr, 0, NULL, 0); - ret = write(fd, outbuf, ui->peb_size); - if (ret != ui->peb_size) { + ret = mtd_peb_write(mtd, outbuf, peb1, 0, ui->peb_size); + if (ret < 0) { sys_errmsg("cannot write %d bytes", ui->peb_size); goto out_free; } - seek = (off_t) peb2 * ui->peb_size; - if (lseek(fd, seek, SEEK_SET) != seek) { - sys_errmsg("cannot seek output file"); - goto out_free; - } ubigen_init_ec_hdr(ui, (struct ubi_ec_hdr *)outbuf, ec2); ubigen_init_vid_hdr(ui, &vi, vid_hdr, 1, NULL, 0); - ret = write(fd, outbuf, ui->peb_size); - if (ret != ui->peb_size) { + ret = mtd_peb_write(mtd, outbuf, peb2, 0, ui->peb_size); + if (ret < 0) { sys_errmsg("cannot write %d bytes", ui->peb_size); goto out_free; }