From 172af2a30c4fbd71087103e4cf9b6c9f585d8602 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Tue, 23 Feb 2016 14:41:48 +0100 Subject: [PATCH 01/28] mtd: Add support for marking blocks as good Signed-off-by: Sascha Hauer --- commands/nand.c | 34 ++++++++++++++++++--- drivers/mtd/core.c | 16 ++++++++++ drivers/mtd/mtdconcat.c | 24 +++++++++++++++ drivers/mtd/nand/nand_base.c | 59 ++++++++++++++++++++++++++++++++++++ drivers/mtd/partition.c | 16 ++++++++++ fs/devfs-core.c | 1 + include/linux/mtd/mtd-abi.h | 1 + include/linux/mtd/mtd.h | 2 ++ include/linux/mtd/nand.h | 2 ++ 9 files changed, 150 insertions(+), 5 deletions(-) diff --git a/commands/nand.c b/commands/nand.c index c330ad1dc..1807c9231 100644 --- a/commands/nand.c +++ b/commands/nand.c @@ -32,6 +32,7 @@ #define NAND_ADD 1 #define NAND_DEL 2 #define NAND_MARKBAD 3 +#define NAND_MARKGOOD 4 static int do_nand(int argc, char *argv[]) { @@ -39,7 +40,7 @@ static int do_nand(int argc, char *argv[]) int command = 0; loff_t badblock = 0; - while((opt = getopt(argc, argv, "adb:")) > 0) { + while((opt = getopt(argc, argv, "adb:g:")) > 0) { if (command) { printf("only one command may be given\n"); return 1; @@ -55,12 +56,24 @@ static int do_nand(int argc, char *argv[]) case 'b': command = NAND_MARKBAD; badblock = strtoull_suffix(optarg, NULL, 0); + break; + case 'g': + command = NAND_MARKGOOD; + badblock = strtoull_suffix(optarg, NULL, 0); + 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,11 +90,21 @@ static int do_nand(int argc, char *argv[]) } } - if (command == NAND_MARKBAD) { + if (command == NAND_MARKBAD || command == NAND_MARKGOOD) { int ret = 0, fd; + const char *str; + int ctl; - printf("marking block at 0x%08llx on %s as bad\n", - badblock, argv[optind]); + if (command == NAND_MARKBAD) { + str = "bad"; + ctl = MEMSETBADBLOCK; + } else { + str = "good"; + ctl = MEMSETGOODBLOCK; + } + + printf("marking block at 0x%08llx on %s as %s\n", + badblock, argv[optind], str); fd = open(argv[optind], O_RDWR); if (fd < 0) { @@ -89,7 +112,7 @@ static int do_nand(int argc, char *argv[]) return 1; } - ret = ioctl(fd, MEMSETBADBLOCK, &badblock); + ret = ioctl(fd, ctl, &badblock); if (ret) { if (ret == -EINVAL) printf("Maybe offset %lld is out of range.\n", @@ -110,6 +133,7 @@ BAREBOX_CMD_HELP_TEXT("Options:") 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_END BAREBOX_CMD_START(nand) diff --git a/drivers/mtd/core.c b/drivers/mtd/core.c index 161c6ad87..3143b07ca 100644 --- a/drivers/mtd/core.c +++ b/drivers/mtd/core.c @@ -231,6 +231,10 @@ int mtd_ioctl(struct cdev *cdev, int request, void *buf) 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 +324,18 @@ int mtd_block_markbad(struct mtd_info *mtd, loff_t ofs) 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) { diff --git a/drivers/mtd/mtdconcat.c b/drivers/mtd/mtdconcat.c index 6395b2fd5..fa430712d 100644 --- a/drivers/mtd/mtdconcat.c +++ b/drivers/mtd/mtdconcat.c @@ -506,6 +506,28 @@ static int concat_block_markbad(struct mtd_info *mtd, loff_t ofs) 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 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c 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 ec5a8b757..ffbf82927 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -456,6 +456,38 @@ static int nand_block_checkbad(struct mtd_info *mtd, loff_t ofs, int getchip, 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) { @@ -2774,6 +2806,30 @@ static int nand_block_markbad(struct mtd_info *mtd, loff_t ofs) return chip->block_markbad(mtd, ofs); } +/** + * 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 @@ -2844,6 +2900,8 @@ static void nand_set_defaults(struct nand_chip *chip, int busw) #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 @@ int nand_scan_tail(struct mtd_info *mtd) 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 c11a3db1c..261e35c3f 100644 --- a/drivers/mtd/partition.c +++ b/drivers/mtd/partition.c @@ -114,6 +114,21 @@ static int mtd_part_block_markbad(struct mtd_info *mtd, loff_t ofs) 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,6 +183,7 @@ struct mtd_info *mtd_add_partition(struct mtd_info *mtd, off_t offset, 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) diff --git a/fs/devfs-core.c b/fs/devfs-core.c index deacaaad3..75ed3b0e6 100644 --- a/fs/devfs-core.c +++ b/fs/devfs-core.c @@ -192,6 +192,7 @@ static int partition_ioctl(struct cdev *cdev, int request, void *buf) switch (request) { case MEMSETBADBLOCK: + case MEMSETGOODBLOCK: case MEMGETBADBLOCK: offset = *_buf; offset += cdev->offset; diff --git a/include/linux/mtd/mtd-abi.h b/include/linux/mtd/mtd-abi.h index 8e778df17..9bca9b5e0 100644 --- a/include/linux/mtd/mtd-abi.h +++ b/include/linux/mtd/mtd-abi.h @@ -118,6 +118,7 @@ struct otp_info { #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 421a941aa..efb08b12b 100644 --- a/include/linux/mtd/mtd.h +++ b/include/linux/mtd/mtd.h @@ -189,6 +189,7 @@ struct mtd_info { /* 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; @@ -308,6 +309,7 @@ int mtd_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len); 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); diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index 83d664e7e..b787842db 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -394,6 +394,7 @@ struct nand_buffers { * @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 +480,7 @@ struct nand_chip { 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); From 40ee3002bba2be046e3b96b0665436a080ba0c25 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Tue, 23 Feb 2016 15:05:44 +0100 Subject: [PATCH 02/28] mtd: nand: Add option to print bbt in nand command Signed-off-by: Sascha Hauer --- commands/nand.c | 51 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/commands/nand.c b/commands/nand.c index 1807c9231..b065a661c 100644 --- a/commands/nand.c +++ b/commands/nand.c @@ -33,14 +33,18 @@ #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:g:")) > 0) { + while((opt = getopt(argc, argv, "adb:g:i")) > 0) { if (command) { printf("only one command may be given\n"); return 1; @@ -61,6 +65,9 @@ static int do_nand(int argc, char *argv[]) command = NAND_MARKGOOD; badblock = strtoull_suffix(optarg, NULL, 0); break; + case 'i': + command = NAND_INFO; + break; default: return COMMAND_ERROR_USAGE; } @@ -90,8 +97,17 @@ static int do_nand(int argc, char *argv[]) } } + fd = open(argv[optind], O_RDWR); + if (fd < 0) { + perror("open"); + return 1; + } + + ret = ioctl(fd, MEMGETINFO, &mtdinfo); + if (ret) + goto out; + if (command == NAND_MARKBAD || command == NAND_MARKGOOD) { - int ret = 0, fd; const char *str; int ctl; @@ -106,12 +122,6 @@ static int do_nand(int argc, char *argv[]) printf("marking block at 0x%08llx on %s as %s\n", badblock, argv[optind], str); - fd = open(argv[optind], O_RDWR); - if (fd < 0) { - perror("open"); - return 1; - } - ret = ioctl(fd, ctl, &badblock); if (ret) { if (ret == -EINVAL) @@ -121,11 +131,29 @@ static int do_nand(int argc, char *argv[]) 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) @@ -134,6 +162,7 @@ BAREBOX_CMD_HELP_OPT ("-a", "register a bad block aware device ontop of a norma 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) From ca6aceb8073163479812754797b204394c97ecde Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 4 Mar 2016 08:39:54 +0100 Subject: [PATCH 03/28] mtd: mtdpart: Add oob_read function Signed-off-by: Sascha Hauer --- drivers/mtd/partition.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/drivers/mtd/partition.c b/drivers/mtd/partition.c index 261e35c3f..777cb758c 100644 --- a/drivers/mtd/partition.c +++ b/drivers/mtd/partition.c @@ -18,6 +18,26 @@ static int mtd_part_read(struct mtd_info *mtd, loff_t from, size_t len, 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) { @@ -188,6 +208,8 @@ struct mtd_info *mtd_add_partition(struct mtd_info *mtd, off_t offset, 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; From 1eee78b58073ff5637e699d4fa61f117440f79a2 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 26 Feb 2016 10:27:09 +0100 Subject: [PATCH 04/28] mtd: Introduce function to get mtd type string Signed-off-by: Sascha Hauer --- drivers/mtd/core.c | 22 ++++++++++++++++++++++ include/linux/mtd/mtd.h | 1 + 2 files changed, 23 insertions(+) diff --git a/drivers/mtd/core.c b/drivers/mtd/core.c index 3143b07ca..0a3f7ed33 100644 --- a/drivers/mtd/core.c +++ b/drivers/mtd/core.c @@ -733,3 +733,25 @@ void mtdcore_add_hook(struct mtddev_hook *hook) { 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/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h index efb08b12b..98dd82c40 100644 --- a/include/linux/mtd/mtd.h +++ b/include/linux/mtd/mtd.h @@ -281,6 +281,7 @@ extern struct mtd_info *get_mtd_device_nm(const char *name); 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); From 30750ef9903470e01ac864bafe366d5c724c545b Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 26 Feb 2016 11:45:39 +0100 Subject: [PATCH 05/28] mtd: rename mtd_all_ff -> mtd_buf_all_ff To make clear this function checks a given buffer and not data on a mtd device. Signed-off-by: Sascha Hauer --- drivers/mtd/core.c | 10 +++++++++- drivers/mtd/nand/nand_base.c | 2 +- include/linux/mtd/mtd.h | 2 +- lib/libscan.c | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/mtd/core.c b/drivers/mtd/core.c index 0a3f7ed33..ff53a4a1b 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) diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index ffbf82927..7eef287e5 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -2403,7 +2403,7 @@ static int nand_do_write_ops(struct mtd_info *mtd, loff_t to, 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)); diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h index 98dd82c40..437488228 100644 --- a/include/linux/mtd/mtd.h +++ b/include/linux/mtd/mtd.h @@ -312,7 +312,7 @@ 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); /* * Debugging macro and defines diff --git a/lib/libscan.c b/lib/libscan.c index 0c850ae6e..29d1cdbec 100644 --- a/lib/libscan.c +++ b/lib/libscan.c @@ -84,7 +84,7 @@ int libscan_ubi_scan(struct mtd_dev_info *mtd, int fd, struct ubi_scan_info **in 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) From fdac29be6259e75ded9b322949ed56236b30d47d Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 26 Feb 2016 11:35:38 +0100 Subject: [PATCH 06/28] mtd: Introduce mtd_check_pattern Signed-off-by: Sascha Hauer --- drivers/mtd/core.c | 19 +++++++++++++++++++ include/linux/mtd/mtd.h | 1 + 2 files changed, 20 insertions(+) diff --git a/drivers/mtd/core.c b/drivers/mtd/core.c index ff53a4a1b..dcf94bfc0 100644 --- a/drivers/mtd/core.c +++ b/drivers/mtd/core.c @@ -74,6 +74,25 @@ int mtd_buf_all_ff(const void *buf, unsigned int len) 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) { diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h index 437488228..18d886649 100644 --- a/include/linux/mtd/mtd.h +++ b/include/linux/mtd/mtd.h @@ -313,6 +313,7 @@ int mtd_block_markbad(struct mtd_info *mtd, loff_t ofs); int mtd_block_markgood(struct mtd_info *mtd, loff_t ofs); 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 From eef520a32b72606793cd02b2af57e559c64f8620 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 26 Feb 2016 10:29:16 +0100 Subject: [PATCH 07/28] mtd: Introduce mtd-peb API Code which properly wants to handle Nand flash has to work in a block based way since blocks are the entities that are erased or may become bad. The regular mtd API works based on offsets in the device which introduces unhandy 64bit arithmetics and the requirement to align buffers to blocks. This introduces the mtd peb API (PEB for physical Erase Block) which allows the users to work in a block oriented way. The API is heavily inspired by the UBI IO layer and in fact can replace parts thereof later. Signed-off-by: Sascha Hauer --- drivers/mtd/Kconfig | 9 + drivers/mtd/Makefile | 2 +- drivers/mtd/peb.c | 537 ++++++++++++++++++++++++++++++++++++++++++ include/mtd/mtd-peb.h | 21 ++ 4 files changed, 568 insertions(+), 1 deletion(-) create mode 100644 drivers/mtd/peb.c create mode 100644 include/mtd/mtd-peb.h diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig index ea1be556b..db9c287b4 100644 --- a/drivers/mtd/Kconfig +++ b/drivers/mtd/Kconfig @@ -30,6 +30,15 @@ config MTD_CONCAT 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 d3ae7fca3..bfac8ebe6 100644 --- a/drivers/mtd/Makefile +++ b/drivers/mtd/Makefile @@ -3,7 +3,7 @@ obj-$(CONFIG_DRIVER_CFI) += nor/ 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/peb.c b/drivers/mtd/peb.c new file mode 100644 index 000000000..5cf2907cc --- /dev/null +++ b/drivers/mtd/peb.c @@ -0,0 +1,537 @@ +/* + * 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; +} diff --git a/include/mtd/mtd-peb.h b/include/mtd/mtd-peb.h new file mode 100644 index 000000000..50ac9a5cc --- /dev/null +++ b/include/mtd/mtd-peb.h @@ -0,0 +1,21 @@ +#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); + +#endif /* __LINUX_MTD_MTDPEB_H */ From 1d88c6697722806d6c16bfadffb7c3c4d666e8e5 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 26 Feb 2016 10:31:03 +0100 Subject: [PATCH 08/28] ubiformat: Use mtd-peb API This changes ubiformat from the libmtd API to the mtd-peb API. This makes the libmtd API unnecessary and it can be removed in the next step. Signed-off-by: Sascha Hauer --- commands/ubiformat.c | 191 +++++++++++++++++++++------------------- include/mtd/libscan.h | 3 +- include/mtd/libubigen.h | 4 +- lib/libscan.c | 28 +++--- lib/libubigen.c | 23 ++--- 5 files changed, 124 insertions(+), 125 deletions(-) diff --git a/commands/ubiformat.c b/commands/ubiformat.c index f9c50b793..017265423 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 @@ struct args { long long ec; const char *image; const char *node; - int node_fd; }; static struct args args; @@ -166,16 +165,18 @@ static int parse_opt(int argc, char *argv[]) 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 @@ static int change_ech(struct ubi_ec_hdr *hdr, uint32_t image_seq, 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 @@ static int drop_ffs(const struct mtd_dev_info *mtd, const void *buf, int len) /* 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 @@ static int consecutive_bad_check(int eb) } /* 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 @@ static int mark_bad(const struct mtd_dev_info *mtd, struct ubi_scan_info *si, in 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 @@ static int flash_image(const struct mtd_dev_info *mtd, 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 @@ static int flash_image(const struct mtd_dev_info *mtd, } 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 @@ static int flash_image(const struct mtd_dev_info *mtd, 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 @@ static int flash_image(const struct mtd_dev_info *mtd, 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 @@ static int flash_image(const struct mtd_dev_info *mtd, 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_peb_torture(mtd, eb); + if (err < 0 && err != -EIO) + goto out_close; + if (err == -EIO && consecutive_bad_check(eb)) goto out_close; - err = mtd_torture(mtd, args.node_fd, eb); - if (err) { - if (mark_bad(mtd, si, eb)) - goto out_close; - } continue; } if (++written_ebs >= img_ebs) @@ -423,30 +427,32 @@ out_close: 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 @@ static int format(const struct mtd_dev_info *mtd, 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 @@ static int format(const struct mtd_dev_info *mtd, 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 @@ static int format(const struct mtd_dev_info *mtd, 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 @@ static int format(const struct mtd_dev_info *mtd, 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 @@ out_free: 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 @@ int do_ubiformat(int argc, char *argv[]) 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 @@ int do_ubiformat(int argc, char *argv[]) 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 @@ int do_ubiformat(int argc, char *argv[]) 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 @@ int do_ubiformat(int argc, char *argv[]) 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 @@ int do_ubiformat(int argc, char *argv[]) } 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/include/mtd/libscan.h b/include/mtd/libscan.h index bb0148203..e925a26ad 100644 --- a/include/mtd/libscan.h +++ b/include/mtd/libscan.h @@ -84,12 +84,11 @@ struct mtd_dev_info; /** * 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 f05978b32..266f393a9 100644 --- a/include/mtd/libubigen.h +++ b/include/mtd/libubigen.h @@ -170,13 +170,13 @@ int ubigen_write_volume(const struct ubigen_info *ui, * @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/lib/libscan.c b/lib/libscan.c index 29d1cdbec..bf298a70b 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 @@ int libscan_ubi_scan(struct mtd_dev_info *mtd, int fd, struct ubi_scan_info **in 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 @@ int libscan_ubi_scan(struct mtd_dev_info *mtd, int fd, struct ubi_scan_info **in 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,7 +81,7 @@ int libscan_ubi_scan(struct mtd_dev_info *mtd, int fd, struct ubi_scan_info **in 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; @@ -121,14 +123,14 @@ int libscan_ubi_scan(struct mtd_dev_info *mtd, int fd, struct ubi_scan_info **in 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 @@ int libscan_ubi_scan(struct mtd_dev_info *mtd, int fd, struct ubi_scan_info **in 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 @@ int libscan_ubi_scan(struct mtd_dev_info *mtd, int fd, struct ubi_scan_info **in 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 900632905..77ebb053c 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 @@ out_free: 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 @@ int ubigen_write_layout_vol(const struct ubigen_info *ui, int peb1, int peb2, 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; } From 81b52d93cb03ddd6e4921a2f93897c43f82d192a Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 26 Feb 2016 12:17:27 +0100 Subject: [PATCH 09/28] remove now unused libmtd The only user of libmtd was ubiformat which now uses the mtd-peb API, so remove the now unused libmtd. Signed-off-by: Sascha Hauer --- commands/Kconfig | 1 - include/mtd/libmtd.h | 149 ------------------ lib/Kconfig | 3 - lib/Makefile | 1 - lib/libmtd.c | 368 ------------------------------------------- 5 files changed, 522 deletions(-) delete mode 100644 include/mtd/libmtd.h delete mode 100644 lib/libmtd.c diff --git a/commands/Kconfig b/commands/Kconfig index 9519a44cf..172439154 100644 --- a/commands/Kconfig +++ b/commands/Kconfig @@ -694,7 +694,6 @@ config CMD_UBI config CMD_UBIFORMAT tristate depends on MTD_UBI - select LIBMTD select LIBSCAN select LIBUBIGEN prompt "ubiformat" diff --git a/include/mtd/libmtd.h b/include/mtd/libmtd.h deleted file mode 100644 index 65c390aff..000000000 --- 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/lib/Kconfig b/lib/Kconfig index f05175103..d5f99ae2e 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -52,9 +52,6 @@ config LIBSCAN config LIBUBIGEN bool -config LIBMTD - bool - config STMP_DEVICE bool diff --git a/lib/Makefile b/lib/Makefile index 44ba25b76..b6da848d1 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -44,7 +44,6 @@ obj-$(CONFIG_BITREV) += bitrev.o 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 56672bd95..000000000 --- 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; -} From 51be064a39a00376bf76d8259761b1eadfcbb635 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 26 Feb 2016 11:36:38 +0100 Subject: [PATCH 10/28] mtd: ubi: Use mtd_all_ff/mtd_check_pattern Signed-off-by: Sascha Hauer --- drivers/mtd/ubi/attach.c | 2 +- drivers/mtd/ubi/io.c | 10 +++++----- drivers/mtd/ubi/misc.c | 19 ------------------- drivers/mtd/ubi/ubi.h | 1 - 4 files changed, 6 insertions(+), 26 deletions(-) diff --git a/drivers/mtd/ubi/attach.c b/drivers/mtd/ubi/attach.c index d6fe43b83..88370f465 100644 --- a/drivers/mtd/ubi/attach.c +++ b/drivers/mtd/ubi/attach.c @@ -772,7 +772,7 @@ static int check_corruption(struct ubi_device *ubi, struct ubi_vid_hdr *vid_hdr, 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/io.c b/drivers/mtd/ubi/io.c index e55dfc5bc..801c0ebcb 100644 --- a/drivers/mtd/ubi/io.c +++ b/drivers/mtd/ubi/io.c @@ -405,7 +405,7 @@ static int torture_peb(struct ubi_device *ubi, int pnum) if (err) goto out; - err = ubi_check_pattern(ubi->peb_buf, 0xFF, ubi->peb_size); + err = mtd_buf_all_ff(ubi->peb_buf, ubi->peb_size); if (err == 0) { ubi_err("erased PEB %d, but a non-0xFF byte found", pnum); @@ -424,7 +424,7 @@ static int torture_peb(struct ubi_device *ubi, int pnum) if (err) goto out; - err = ubi_check_pattern(ubi->peb_buf, patterns[i], + err = mtd_buf_check_pattern(ubi->peb_buf, patterns[i], ubi->peb_size); if (err == 0) { ubi_err("pattern %x checking failed for PEB %d", @@ -740,7 +740,7 @@ int ubi_io_read_ec_hdr(struct ubi_device *ubi, int pnum, * 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 +996,7 @@ int ubi_io_read_vid_hdr(struct ubi_device *ubi, int pnum, 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); @@ -1382,7 +1382,7 @@ int ubi_self_check_all_ff(struct ubi_device *ubi, int pnum, int offset, int len) goto error; } - err = ubi_check_pattern(buf, 0xFF, len); + err = mtd_buf_all_ff(buf, len); if (err == 0) { ubi_err("flash region at PEB %d:%d, length %d does not contain all 0xFF bytes", pnum, offset, len); diff --git a/drivers/mtd/ubi/misc.c b/drivers/mtd/ubi/misc.c index b5c6efe89..963346646 100644 --- a/drivers/mtd/ubi/misc.c +++ b/drivers/mtd/ubi/misc.c @@ -128,22 +128,3 @@ void ubi_calculate_reserved(struct ubi_device *ubi) 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 61ae73809..577b35d79 100644 --- a/drivers/mtd/ubi/ubi.h +++ b/drivers/mtd/ubi/ubi.h @@ -757,7 +757,6 @@ int ubi_calc_data_len(const struct ubi_device *ubi, const void *buf, 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, From 4ac7569943d26bdd8e559f38addd7dba6a75db36 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 26 Feb 2016 12:12:42 +0100 Subject: [PATCH 11/28] mtd: ubi: Use mtd_peb_check_all_ff Use mtd-peb function for checking for a all-ff buffer. Signed-off-by: Sascha Hauer --- drivers/mtd/ubi/io.c | 39 ++------------------------------------- 1 file changed, 2 insertions(+), 37 deletions(-) diff --git a/drivers/mtd/ubi/io.c b/drivers/mtd/ubi/io.c index 801c0ebcb..449599657 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); @@ -1361,44 +1362,8 @@ out_free: */ 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 = mtd_buf_all_ff(buf, 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); } From 70542a9c65ebe636d99f6e59adf8e984e6eddef2 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 26 Feb 2016 12:20:58 +0100 Subject: [PATCH 12/28] mtd: ubi: Use mtd_peb_torture The mtd-peb API provides a torture test derived from the UBI torture test. Use it. Since the mtd-peb variant of the torture test will also mark a block as bad when the test fails this also makes a separate ubi_io_mark_bad unnecessary. Signed-off-by: Sascha Hauer --- drivers/mtd/ubi/io.c | 115 ++---------------------------------------- drivers/mtd/ubi/ubi.h | 1 - drivers/mtd/ubi/wl.c | 5 -- 3 files changed, 5 insertions(+), 116 deletions(-) diff --git a/drivers/mtd/ubi/io.c b/drivers/mtd/ubi/io.c index 449599657..403125323 100644 --- a/drivers/mtd/ubi/io.c +++ b/drivers/mtd/ubi/io.c @@ -376,82 +376,6 @@ retry: 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 = mtd_buf_all_ff(ubi->peb_buf, 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 = mtd_buf_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; -} - /** * nor_erase_prepare - prepare a NOR flash PEB for erasure. * @ubi: UBI device description object @@ -564,15 +488,15 @@ int ubi_io_sync_erase(struct ubi_device *ubi, int pnum, int torture) } 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 +529,6 @@ int ubi_io_is_bad(const struct ubi_device *ubi, int pnum) return 0; } -/** - * 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 diff --git a/drivers/mtd/ubi/ubi.h b/drivers/mtd/ubi/ubi.h index 577b35d79..03a36d2e7 100644 --- a/drivers/mtd/ubi/ubi.h +++ b/drivers/mtd/ubi/ubi.h @@ -799,7 +799,6 @@ int ubi_io_write(struct ubi_device *ubi, const void *buf, int pnum, int offset, 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 4c20e908e..cb2f9d733 100644 --- a/drivers/mtd/ubi/wl.c +++ b/drivers/mtd/ubi/wl.c @@ -1421,11 +1421,6 @@ static int erase_worker(struct ubi_device *ubi, struct ubi_work *wl_wrk, 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) { /* From 2316255dfa920d8f62f3ed550b5cf7b1d1330f79 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 11 Mar 2016 10:09:36 +0100 Subject: [PATCH 13/28] mtd: ubi: Use mtd_peb_read mtd_peb_read provides the same functionality as ubi_io_read. Use it. Signed-off-by: Sascha Hauer --- drivers/mtd/ubi/debug.h | 13 ------ drivers/mtd/ubi/io.c | 89 +++-------------------------------------- 2 files changed, 5 insertions(+), 97 deletions(-) diff --git a/drivers/mtd/ubi/debug.h b/drivers/mtd/ubi/debug.h index ebf961b9f..f177e6b3f 100644 --- a/drivers/mtd/ubi/debug.h +++ b/drivers/mtd/ubi/debug.h @@ -72,19 +72,6 @@ static inline int ubi_dbg_is_bgt_disabled(const struct ubi_device *ubi) 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 diff --git a/drivers/mtd/ubi/io.c b/drivers/mtd/ubi/io.c index 403125323..4fa8e5d0a 100644 --- a/drivers/mtd/ubi/io.c +++ b/drivers/mtd/ubi/io.c @@ -121,91 +121,12 @@ static int self_check_write(struct ubi_device *ubi, const void *buf, int pnum, 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; } /** From ecab3b1c21196a13851310d91e42f0b6d40e5e7e Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 11 Mar 2016 10:10:42 +0100 Subject: [PATCH 14/28] mtd: ubi: Use mtd_peb_write mtd_peb_write provides the same functionality as ubi_io_write. Use it. Signed-off-by: Sascha Hauer --- drivers/mtd/ubi/debug.h | 14 ----- drivers/mtd/ubi/io.c | 113 +--------------------------------------- 2 files changed, 1 insertion(+), 126 deletions(-) diff --git a/drivers/mtd/ubi/debug.h b/drivers/mtd/ubi/debug.h index f177e6b3f..9d0542c89 100644 --- a/drivers/mtd/ubi/debug.h +++ b/drivers/mtd/ubi/debug.h @@ -72,20 +72,6 @@ static inline int ubi_dbg_is_bgt_disabled(const struct ubi_device *ubi) return ubi->dbg.disable_bgt; } -/** - * 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 diff --git a/drivers/mtd/ubi/io.c b/drivers/mtd/ubi/io.c index 4fa8e5d0a..ed0cd1c09 100644 --- a/drivers/mtd/ubi/io.c +++ b/drivers/mtd/ubi/io.c @@ -93,8 +93,6 @@ static int self_check_ec_hdr(const struct ubi_device *ubi, int pnum, 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. @@ -150,8 +148,6 @@ int ubi_io_write(struct ubi_device *ubi, const void *buf, int pnum, int offset, int len) { int err; - size_t written; - loff_t addr; dbg_io("write %d bytes to PEB %d:%d", len, pnum, offset); @@ -165,15 +161,6 @@ int ubi_io_write(struct ubi_device *ubi, const void *buf, int pnum, int offset, 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 @@ -187,39 +174,7 @@ int ubi_io_write(struct ubi_device *ubi, const void *buf, int pnum, int offset, 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; + return mtd_peb_write(ubi->mtd, buf, pnum, offset, len); } /** @@ -1099,72 +1054,6 @@ exit: return err; } -/** - * 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 From e4715ba031ef75ea2fa286aed153c7e722a2fa23 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 11 Mar 2016 10:10:52 +0100 Subject: [PATCH 15/28] mtd: ubi: Use mtd_peb_erase mtd_peb_erase provides the same functionality as do_sync_erase. Use it. Signed-off-by: Sascha Hauer --- drivers/mtd/ubi/debug.h | 14 ----------- drivers/mtd/ubi/io.c | 55 +---------------------------------------- 2 files changed, 1 insertion(+), 68 deletions(-) diff --git a/drivers/mtd/ubi/debug.h b/drivers/mtd/ubi/debug.h index 9d0542c89..c1b41b4d7 100644 --- a/drivers/mtd/ubi/debug.h +++ b/drivers/mtd/ubi/debug.h @@ -72,20 +72,6 @@ static inline int ubi_dbg_is_bgt_disabled(const struct ubi_device *ubi) return ubi->dbg.disable_bgt; } -/** - * 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; diff --git a/drivers/mtd/ubi/io.c b/drivers/mtd/ubi/io.c index ed0cd1c09..b3cb4f2dc 100644 --- a/drivers/mtd/ubi/io.c +++ b/drivers/mtd/ubi/io.c @@ -177,17 +177,6 @@ int ubi_io_write(struct ubi_device *ubi, const void *buf, int pnum, int offset, return mtd_peb_write(ubi->mtd, buf, pnum, offset, len); } -/** - * 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) -{ -} - /** * do_sync_erase - synchronously erase a physical eraseblock. * @ubi: UBI device description object @@ -199,9 +188,6 @@ static void erase_callback(struct erase_info *ei) */ 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); @@ -210,46 +196,7 @@ static int do_sync_erase(struct ubi_device *ubi, int pnum) 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; + return mtd_peb_erase(ubi->mtd, pnum); } /** From eea6d75d1bfa91ff5da7992ecbc1ff828542c342 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 11 Mar 2016 09:15:23 +0100 Subject: [PATCH 16/28] mtd: ubi: Make debug options configurable This makes the UBI debug options configurable. This make the debug options actually available to the user and also allows the compiler to optimize away the debug code when the options are disabled. Signed-off-by: Sascha Hauer --- drivers/mtd/ubi/Kconfig | 13 +++++++++++++ drivers/mtd/ubi/debug.h | 10 ++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/drivers/mtd/ubi/Kconfig b/drivers/mtd/ubi/Kconfig index ccd547dd7..4c497931f 100644 --- a/drivers/mtd/ubi/Kconfig +++ b/drivers/mtd/ubi/Kconfig @@ -77,4 +77,17 @@ config MTD_UBI_FASTMAP 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/debug.h b/drivers/mtd/ubi/debug.h index c1b41b4d7..511e45436 100644 --- a/drivers/mtd/ubi/debug.h +++ b/drivers/mtd/ubi/debug.h @@ -74,11 +74,17 @@ static inline int ubi_dbg_is_bgt_disabled(const struct ubi_device *ubi) 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__ */ From 1e6955fdb815906bdd2ecbf2c50e5059852f2400 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Wed, 2 Mar 2016 10:16:50 +0100 Subject: [PATCH 17/28] commands: Create nand_bitflip command This adds a command to flip bits in a Nand flash. This is useful for testing purposes to check if flipped bits are corrected and if the driver returns the correct number of bitflips. The command writes a configurable number of bitflips to a single Nand page. If the -r option is not given the results are reproducible, so calling the same command twice will revert the bitflips. The command uses the raw read/write Nand operations which are probably less tested than the regular read/write operations, so the command may produce surprising results. As of writing the command has been tested with the GPMI Nand driver and the imx-nand driver with fixes posted. Signed-off-by: Sascha Hauer --- commands/Kconfig | 17 ++++++ commands/Makefile | 1 + commands/nand-bitflip.c | 117 ++++++++++++++++++++++++++++++++++++++ drivers/mtd/peb.c | 121 ++++++++++++++++++++++++++++++++++++++++ include/mtd/mtd-peb.h | 3 + 5 files changed, 259 insertions(+) create mode 100644 commands/nand-bitflip.c diff --git a/commands/Kconfig b/commands/Kconfig index 172439154..875c5f4f0 100644 --- a/commands/Kconfig +++ b/commands/Kconfig @@ -1908,6 +1908,23 @@ config CMD_NANDTEST -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 8975d4bd4..f1b482f04 100644 --- a/commands/Makefile +++ b/commands/Makefile @@ -116,3 +116,4 @@ obj-$(CONFIG_CMD_DHCP) += dhcp.o 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/nand-bitflip.c b/commands/nand-bitflip.c new file mode 100644 index 000000000..fe56f222c --- /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/drivers/mtd/peb.c b/drivers/mtd/peb.c index 5cf2907cc..639dc0ee6 100644 --- a/drivers/mtd/peb.c +++ b/drivers/mtd/peb.c @@ -535,3 +535,124 @@ out: 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/include/mtd/mtd-peb.h b/include/mtd/mtd-peb.h index 50ac9a5cc..e4fd01df9 100644 --- a/include/mtd/mtd-peb.h +++ b/include/mtd/mtd-peb.h @@ -17,5 +17,8 @@ int mtd_peb_check_all_ff(struct mtd_info *mtd, int pnum, int offset, int len, 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 */ From fa61152d972a47edcad1a3da0a6500db194c1a77 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 10 Mar 2016 16:24:10 +0100 Subject: [PATCH 18/28] bbu: Allow to refresh/repair images Some SoCs allow to store multiple boot images on a device in order to improve robustness. This adds a -r option to barebox_update to indicate we do not want to make an update but instead repair/refresh an existing image. Handlers which want to support this feature must set the BBU_HANDLER_CAN_REFRESH flag during registration. Signed-off-by: Sascha Hauer --- Documentation/user/updating.rst | 8 ++++++++ commands/barebox-update.c | 26 ++++++++++++++++---------- common/bbu.c | 12 +++++++++++- include/bbu.h | 1 + 4 files changed, 36 insertions(+), 11 deletions(-) diff --git a/Documentation/user/updating.rst b/Documentation/user/updating.rst index 6a1a73348..7aac0a99d 100644 --- a/Documentation/user/updating.rst +++ b/Documentation/user/updating.rst @@ -30,3 +30,11 @@ commands. The exact commands are board specific. **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/barebox-update.c b/commands/barebox-update.c index 92e0efab6..c2f2b68e0 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 @@ static int do_barebox_update(int argc, char *argv[]) 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_TEXT("Options:") 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_HELP_END 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/common/bbu.c b/common/bbu.c index 68812a733..09c96bbdb 100644 --- a/common/bbu.c +++ b/common/bbu.c @@ -65,9 +65,13 @@ int bbu_confirm(struct bbu_data *data) 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(); @@ -141,6 +145,12 @@ int barebox_update(struct bbu_data *data) 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/include/bbu.h b/include/bbu.h index 0fe7a1a9b..971fc1405 100644 --- a/include/bbu.h +++ b/include/bbu.h @@ -23,6 +23,7 @@ struct bbu_handler { 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 */ From 3abbbbd7c3fb4f5aa376800b11fcf0d2975225a7 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Wed, 2 Mar 2016 10:17:10 +0100 Subject: [PATCH 19/28] mtd: nand: export nand_check_erased_buf Signed-off-by: Sascha Hauer --- drivers/mtd/nand/nand_base.c | 2 +- include/linux/mtd/nand.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index 7eef287e5..79c1edd5d 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -957,7 +957,7 @@ EXPORT_SYMBOL(nand_lock); * 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; diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index b787842db..5beec39b7 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -50,6 +50,7 @@ extern int nand_check_erased_ecc_chunk(void *data, int datalen, 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 From c5532a2d36f326577b1bd2743c79fc00b6a41402 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 4 Mar 2016 12:04:24 +0100 Subject: [PATCH 20/28] imx-bbu-nand-fcb: factor out layout functions Signed-off-by: Sascha Hauer --- common/imx-bbu-nand-fcb.c | 66 +++++++++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 20 deletions(-) diff --git a/common/imx-bbu-nand-fcb.c b/common/imx-bbu-nand-fcb.c index 3eb9e9b16..26b0b294a 100644 --- a/common/imx-bbu-nand-fcb.c +++ b/common/imx-bbu-nand-fcb.c @@ -300,12 +300,45 @@ static int imx_bbu_erase(struct mtd_info *mtd) return 0; } -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) +{ + 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) { - uint64_t offset = block * mtd->erasesize; int ret; size_t written; + int num_blocks = imx_bbu_firmware_max_blocks(mtd); + int block = imx_bbu_firmware_start_block(mtd, num); + uint64_t offset = block * mtd->erasesize; + + pr_info("writing firmware %d to block %d (ofs 0x%08x)\n", + num, block, block * mtd->erasesize); while (len > 0) { int now = min(len, mtd->erasesize); @@ -364,7 +397,7 @@ static int imx_bbu_nand_update(struct bbu_handler *handler, struct bbu_data *dat container_of(handler, struct imx_nand_fcb_bbu_handler, handler); struct cdev *bcb_cdev; struct mtd_info *mtd; - int ret, block_fw1, block_fw2; + int ret; struct fcb_block *fcb; struct dbbt_block *dbbt; void *fcb_raw_page, *dbbt_page, *dbbt_data_page; @@ -374,7 +407,8 @@ static int imx_bbu_nand_update(struct bbu_handler *handler, struct bbu_data *dat unsigned fw_size, partition_size; int i; enum filetype filetype; - unsigned num_blocks_fcb_dbbt, num_blocks, num_blocks_fw; + unsigned num_blocks_fw; + int pages_per_block; filetype = file_detect_type(data->image, data->len); @@ -392,6 +426,7 @@ static int imx_bbu_nand_update(struct bbu_handler *handler, struct bbu_data *dat mtd = bcb_cdev->mtd; partition_size = mtd->size; + pages_per_block = mtd->erasesize / mtd->writesize; fcb_raw_page = xzalloc(mtd->writesize + mtd->oobsize); @@ -411,17 +446,8 @@ static int imx_bbu_nand_update(struct bbu_handler *handler, struct bbu_data *dat 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; + num_blocks_fw = imx_bbu_firmware_max_blocks(mtd); - 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); @@ -436,16 +462,16 @@ static int imx_bbu_nand_update(struct bbu_handler *handler, struct bbu_data *dat if (ret) goto out; - ret = imx_bbu_write_firmware(mtd, block_fw1, num_blocks_fw, fw, fw_size); + ret = imx_bbu_write_firmware(mtd, 0, fw, fw_size); if (ret < 0) goto out; - ret = imx_bbu_write_firmware(mtd, block_fw2, num_blocks_fw, fw, fw_size); + ret = imx_bbu_write_firmware(mtd, 1, 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->Firmware1_startingPage = imx_bbu_firmware_start_block(mtd, 0) * pages_per_block; + fcb->Firmware2_startingPage = imx_bbu_firmware_start_block(mtd, 1) * pages_per_block; fcb->PagesInFirmware1 = ALIGN(data->len, mtd->writesize) / mtd->writesize; fcb->PagesInFirmware2 = fcb->PagesInFirmware1; @@ -465,7 +491,7 @@ static int imx_bbu_nand_update(struct bbu_handler *handler, struct bbu_data *dat dbbt->FingerPrint = 0x54424244; dbbt->Version = 0x01000000; - ret = dbbt_data_create(mtd, dbbt_data_page, block_fw2 + num_blocks_fw); + ret = dbbt_data_create(mtd, dbbt_data_page, partition_size / mtd->erasesize); if (ret < 0) goto out; From f6f7bccaa1f79d8e79621c9658f1b1c9fc417dc3 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 4 Mar 2016 12:19:05 +0100 Subject: [PATCH 21/28] imx-bbu-nand-fcb: Use mtd-peb API to write firmware With this patch we verify the firmware written to the NAND and thus can react on write failures. We torture the block and if it went bad we mark it as bad. Signed-off-by: Sascha Hauer --- common/imx-bbu-nand-fcb.c | 44 +++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/common/imx-bbu-nand-fcb.c b/common/imx-bbu-nand-fcb.c index 26b0b294a..f1fc40401 100644 --- a/common/imx-bbu-nand-fcb.c +++ b/common/imx-bbu-nand-fcb.c @@ -33,6 +33,7 @@ #include #include #include +#include struct dbbt_block { uint32_t Checksum; @@ -300,6 +301,27 @@ static int imx_bbu_erase(struct mtd_info *mtd) return 0; } +static int mtd_peb_write_block(struct mtd_info *mtd, void *buf, int block, int len) +{ + int ret; + int retries = 0; + + if (mtd_peb_is_bad(mtd, block)) + return -EINVAL; +again: + ret = mtd_peb_write(mtd, buf, block, 0, len); + if (!ret) + return 0; + + if (ret == -EBADMSG) { + ret = mtd_peb_torture(mtd, block); + if (!ret && retries++ < 3) + goto again; + } + + return ret; +} + /** * imx_bbu_firmware_max_blocks - get max number of blocks for firmware * @mtd: The mtd device @@ -332,10 +354,8 @@ static int imx_bbu_write_firmware(struct mtd_info *mtd, unsigned num, void *buf, size_t len) { int ret; - size_t written; int num_blocks = imx_bbu_firmware_max_blocks(mtd); int block = imx_bbu_firmware_start_block(mtd, num); - uint64_t offset = block * mtd->erasesize; pr_info("writing firmware %d to block %d (ofs 0x%08x)\n", num, block, block * mtd->erasesize); @@ -346,21 +366,27 @@ static int imx_bbu_write_firmware(struct mtd_info *mtd, unsigned num, void *buf, if (!num_blocks) 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); + ret = mtd_peb_write_block(mtd, buf, block, now); + + if (ret == -EIO) { + block++; + num_blocks--; + continue; + } + if (ret) return ret; - offset += now; len -= now; buf += now; block++; From 14fd7e9478e53be751bdd568c294623e908b90c1 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 4 Mar 2016 12:50:31 +0100 Subject: [PATCH 22/28] imx-bbu-nand-fcb: factor out a fcb write function Signed-off-by: Sascha Hauer --- common/imx-bbu-nand-fcb.c | 258 +++++++++++++++++++++++++------------- 1 file changed, 172 insertions(+), 86 deletions(-) diff --git a/common/imx-bbu-nand-fcb.c b/common/imx-bbu-nand-fcb.c index f1fc40401..492dd92b6 100644 --- a/common/imx-bbu-nand-fcb.c +++ b/common/imx-bbu-nand-fcb.c @@ -33,6 +33,7 @@ #include #include #include +#include #include struct dbbt_block { @@ -108,8 +109,6 @@ struct imx_nand_fcb_bbu_handler { 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; }; @@ -396,12 +395,14 @@ static int imx_bbu_write_firmware(struct mtd_info *mtd, unsigned num, void *buf, return block; } -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; @@ -412,9 +413,167 @@ static int dbbt_data_create(struct mtd_info *mtd, void *buf, int num_blocks) } } + if (!n_bad_blocks) { + free(dbbt); + return NULL; + } + *n_bad_blocksp = n_bad_blocks; - return 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 = 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; +} + +/** + * 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); + + 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 = dbbt_data_create(mtd); + + /* + * Set the first and second byte of OOB data to 0xFF, not 0x00. These + * bytes are used as the Manufacturers Bad Block Marker (MBBM). Since + * the FCB is mostly written to the first page in a block, a scan for + * factory bad blocks will detect these blocks as bad, e.g. when + * function nand_scan_bbt() is executed to build a new bad block table. + */ + memset(fcb_raw_page + mtd->writesize, 0xFF, 2); + + for (i = 0; i < 4; i++) { + if (mtd_peb_is_bad(mtd, 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 imx_bbu_nand_update(struct bbu_handler *handler, struct bbu_data *data) @@ -424,14 +583,9 @@ static int imx_bbu_nand_update(struct bbu_handler *handler, struct bbu_data *dat struct cdev *bcb_cdev; struct mtd_info *mtd; int ret; - struct fcb_block *fcb; - struct dbbt_block *dbbt; - void *fcb_raw_page, *dbbt_page, *dbbt_data_page; - void *ecc; - int written; + struct fcb_block fcb = {}; void *fw; unsigned fw_size, partition_size; - int i; enum filetype filetype; unsigned num_blocks_fw; int pages_per_block; @@ -454,15 +608,6 @@ static int imx_bbu_nand_update(struct bbu_handler *handler, struct bbu_data *dat partition_size = mtd->size; pages_per_block = mtd->erasesize / mtd->writesize; - fcb_raw_page = xzalloc(mtd->writesize + mtd->oobsize); - - fcb = fcb_raw_page + 12; - ecc = fcb_raw_page + 512 + 12; - - 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. @@ -496,59 +641,18 @@ static int imx_bbu_nand_update(struct bbu_handler *handler, struct bbu_data *dat if (ret < 0) goto out; - fcb->Firmware1_startingPage = imx_bbu_firmware_start_block(mtd, 0) * pages_per_block; - fcb->Firmware2_startingPage = imx_bbu_firmware_start_block(mtd, 1) * pages_per_block; - fcb->PagesInFirmware1 = ALIGN(data->len, mtd->writesize) / mtd->writesize; - fcb->PagesInFirmware2 = fcb->PagesInFirmware1; + fcb.Firmware1_startingPage = imx_bbu_firmware_start_block(mtd, 0) * pages_per_block; + fcb.Firmware2_startingPage = imx_bbu_firmware_start_block(mtd, 1) * pages_per_block; + 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); + fcb_create(imx_handler, &fcb, mtd); - /* - * Set the first and second byte of OOB data to 0xFF, not 0x00. These - * bytes are used as the Manufacturers Bad Block Marker (MBBM). Since - * the FCB is mostly written to the first page in a block, a scan for - * factory bad blocks will detect these blocks as bad, e.g. when - * function nand_scan_bbt() is executed to build a new bad block table. - */ - memset(fcb_raw_page + mtd->writesize, 0xFF, 2); - - dbbt->Checksum = 0; - dbbt->FingerPrint = 0x54424244; - dbbt->Version = 0x01000000; - - ret = dbbt_data_create(mtd, dbbt_data_page, partition_size / mtd->erasesize); + 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); - } - - for (i = 0; i < 4; i++) { - ret = raw_write_page(mtd, fcb_raw_page, mtd->erasesize * i); - if (ret) - 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); return ret; @@ -641,23 +745,6 @@ static void imx28_fcb_create(struct imx_nand_fcb_bbu_handler *imx_handler, 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; @@ -666,7 +753,6 @@ int imx28_bbu_nand_register_handler(const char *name, unsigned long flags) 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; From 678617ecaf30d04a7ce15295e0816122320ba498 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 4 Mar 2016 12:53:48 +0100 Subject: [PATCH 23/28] imx-bbu-nand-fcb: erase on demand Instead of erasing the whole partition on update entry, erase the areas separately when we actually want to write them. This is done as a step towards robust update. Signed-off-by: Sascha Hauer --- common/imx-bbu-nand-fcb.c | 47 ++++++++++++--------------------------- 1 file changed, 14 insertions(+), 33 deletions(-) diff --git a/common/imx-bbu-nand-fcb.c b/common/imx-bbu-nand-fcb.c index 492dd92b6..3dc9274a4 100644 --- a/common/imx-bbu-nand-fcb.c +++ b/common/imx-bbu-nand-fcb.c @@ -272,34 +272,6 @@ static int fcb_create(struct imx_nand_fcb_bbu_handler *imx_handler, return 0; } -static int imx_bbu_erase(struct mtd_info *mtd) -{ - uint64_t offset = 0; - struct erase_info erase; - int ret; - - 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; - } - - memset(&erase, 0, sizeof(erase)); - erase.addr = offset; - erase.len = mtd->erasesize; - - ret = mtd_erase(mtd, &erase); - if (ret) - return ret; - - offset += mtd->erasesize; - } - - return 0; -} - static int mtd_peb_write_block(struct mtd_info *mtd, void *buf, int block, int len) { int ret; @@ -352,13 +324,22 @@ static int imx_bbu_firmware_start_block(struct mtd_info *mtd, int num) static int imx_bbu_write_firmware(struct mtd_info *mtd, unsigned num, void *buf, size_t len) { - int ret; + int ret, i; 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); @@ -470,6 +451,10 @@ again: 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", @@ -629,10 +614,6 @@ static int imx_bbu_nand_update(struct bbu_handler *handler, struct bbu_data *dat if (ret) goto out; - ret = imx_bbu_erase(mtd); - if (ret) - goto out; - ret = imx_bbu_write_firmware(mtd, 0, fw, fw_size); if (ret < 0) goto out; From 9ec9e1b5dea426d96e8465b708aee03fe79f0044 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 11 Mar 2016 11:03:38 +0100 Subject: [PATCH 24/28] imx-bbu-nand-fcb: Only write FCBs/DBBTs when necessary Instead of writing the FCBs/DBBTs on every update write them only if they have changed or if a block needs cleanup (returns -EUCLEAN) Signed-off-by: Sascha Hauer --- common/imx-bbu-nand-fcb.c | 230 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 230 insertions(+) diff --git a/common/imx-bbu-nand-fcb.c b/common/imx-bbu-nand-fcb.c index 3dc9274a4..0c7f75c1d 100644 --- a/common/imx-bbu-nand-fcb.c +++ b/common/imx-bbu-nand-fcb.c @@ -137,6 +137,22 @@ static void encode_hamming_13_8(void *_src, void *_ecc, size_t size) 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 +254,66 @@ static ssize_t raw_write_page(struct mtd_info *mtd, void *buf, loff_t offset) 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) { @@ -494,6 +570,154 @@ out: 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 @@ -543,6 +767,12 @@ static int imx_bbu_write_fcbs_dbbts(struct mtd_info *mtd, struct fcb_block *fcb) if (mtd_peb_is_bad(mtd, i)) continue; + 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); From 5c9b26378e5b80cc876ec2515047d8b2759ff47c Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 11 Mar 2016 11:08:09 +0100 Subject: [PATCH 25/28] imx-bbu-nand-fcb: When writing firmware return new bad blocks Positive return values of imx_bbu_write_firmware() so far indicate the last block that has been written to. This value is unused, so return values > 0 to indicate if there are new bad blocks. This information can be used in the next step to know if the DBBT has to be rewritten. Signed-off-by: Sascha Hauer --- common/imx-bbu-nand-fcb.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/common/imx-bbu-nand-fcb.c b/common/imx-bbu-nand-fcb.c index 0c7f75c1d..3e1f35fbe 100644 --- a/common/imx-bbu-nand-fcb.c +++ b/common/imx-bbu-nand-fcb.c @@ -400,7 +400,7 @@ static int imx_bbu_firmware_start_block(struct mtd_info *mtd, int num) static int imx_bbu_write_firmware(struct mtd_info *mtd, unsigned num, void *buf, size_t len) { - int ret, i; + int ret, i, newbadblock = 0; int num_blocks = imx_bbu_firmware_max_blocks(mtd); int block = imx_bbu_firmware_start_block(mtd, num); @@ -437,6 +437,7 @@ static int imx_bbu_write_firmware(struct mtd_info *mtd, unsigned num, void *buf, if (ret == -EIO) { block++; num_blocks--; + newbadblock = 1; continue; } @@ -449,7 +450,7 @@ static int imx_bbu_write_firmware(struct mtd_info *mtd, unsigned num, void *buf, num_blocks--; } - return block; + return newbadblock; } static void *dbbt_data_create(struct mtd_info *mtd) From ab603a5e30142a13a9fd85abcccae3bedf6ba079 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 11 Mar 2016 11:11:02 +0100 Subject: [PATCH 26/28] imx-bbu-nand-fcb: Print error when writing blocks fails When writing to a block fails the update handler fails relatively silent. Print an error message in this case. Signed-off-by: Sascha Hauer --- common/imx-bbu-nand-fcb.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/common/imx-bbu-nand-fcb.c b/common/imx-bbu-nand-fcb.c index 3e1f35fbe..5ded45ae5 100644 --- a/common/imx-bbu-nand-fcb.c +++ b/common/imx-bbu-nand-fcb.c @@ -441,8 +441,10 @@ static int imx_bbu_write_firmware(struct mtd_info *mtd, unsigned num, void *buf, continue; } - if (ret) + if (ret) { + pr_err("Writing block %d failed with: %s\n", block, strerror(-ret)); return ret; + } len -= now; buf += now; From b52c174b2d850992482b26977e4a67d4a63f3e97 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 11 Mar 2016 11:13:27 +0100 Subject: [PATCH 27/28] imx-bbu-nand-fcb: Make robust against power cuts This patch makes the update to Nand robust against power failures. With this we make sure that during every step of the update at least one of the two images on Nand is readable and valid. Also this patch makes it possible to refresh/repair the boot images on Nand. This may become necessary when a previous update has been interrupted due to a power cut, or when the number of bitflips is near to the number we can correct. This is also done in a way that allow power cuts at every step. We assume 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 | ---------------------- When the layout found on the device differs from the above the update won't be robust, but nevertheless works. Since the layout is changed to the above during the update, the next update will be robust. Here's the strategy we use to implement a robust update: 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. Refreshing the firmware which is needed when 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. Signed-off-by: Sascha Hauer --- common/imx-bbu-nand-fcb.c | 374 ++++++++++++++++++++++++++++++++++---- 1 file changed, 342 insertions(+), 32 deletions(-) diff --git a/common/imx-bbu-nand-fcb.c b/common/imx-bbu-nand-fcb.c index 5ded45ae5..b3dea3706 100644 --- a/common/imx-bbu-nand-fcb.c +++ b/common/imx-bbu-nand-fcb.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -794,27 +795,218 @@ static int imx_bbu_write_fcbs_dbbts(struct mtd_info *mtd, struct fcb_block *fcb) 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; - struct fcb_block fcb = {}; - void *fw; + 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; - filetype = file_detect_type(data->image, data->len); + if (data->image) { + filetype = file_detect_type(data->image, data->len); - if (filetype != imx_handler->filetype && + 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; + return -EINVAL; + } bcb_cdev = cdev_by_name(handler->devicefile); if (!bcb_cdev) { @@ -826,48 +1018,166 @@ static int imx_bbu_nand_update(struct bbu_handler *handler, struct bbu_data *dat 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; + } + /* - * 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. + * 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. */ - fw_size = ALIGN(data->len + mtd->writesize, mtd->writesize); - fw = xzalloc(fw_size); - memcpy(fw, data->image, data->len); + 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); - pr_info("maximum size per firmware: 0x%08x bytes\n", - num_blocks_fw * mtd->erasesize); - - if (num_blocks_fw * mtd->erasesize < fw_size) + 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; - ret = imx_bbu_write_firmware(mtd, 0, fw, fw_size); + /* 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; - ret = imx_bbu_write_firmware(mtd, 1, fw, fw_size); - if (ret < 0) - goto out; + /* 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); + } - fcb.Firmware1_startingPage = imx_bbu_firmware_start_block(mtd, 0) * pages_per_block; - fcb.Firmware2_startingPage = imx_bbu_firmware_start_block(mtd, 1) * pages_per_block; - fcb.PagesInFirmware1 = ALIGN(data->len, mtd->writesize) / mtd->writesize; - fcb.PagesInFirmware2 = fcb.PagesInFirmware1; - - fcb_create(imx_handler, &fcb, mtd); - - ret = imx_bbu_write_fcbs_dbbts(mtd, &fcb); - if (ret < 0) - goto out; + /* + * 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; + } out: free(fw); + free(fcb); return ret; } @@ -896,7 +1206,7 @@ int imx6_bbu_nand_register_handler(const char *name, unsigned long flags) 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); @@ -973,7 +1283,7 @@ int imx28_bbu_nand_register_handler(const char *name, unsigned long flags) 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); From 60f2c23684797173169a940abf9f1985537c156e Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Tue, 8 Mar 2016 13:05:47 +0100 Subject: [PATCH 28/28] imx-bbu-nand-fcb: Print error message when out of pebs Signed-off-by: Sascha Hauer --- common/imx-bbu-nand-fcb.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/common/imx-bbu-nand-fcb.c b/common/imx-bbu-nand-fcb.c index b3dea3706..04c6e6050 100644 --- a/common/imx-bbu-nand-fcb.c +++ b/common/imx-bbu-nand-fcb.c @@ -420,8 +420,10 @@ static int imx_bbu_write_firmware(struct mtd_info *mtd, unsigned num, void *buf, 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 peb %d, left 0x%08x\n", buf, block, len);