diff --git a/commands/nand.c b/commands/nand.c index c330ad1..1807c92 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 @@ 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 @@ 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 @@ } } - 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 @@ 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_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 161c6ad..3143b07 100644 --- a/drivers/mtd/core.c +++ b/drivers/mtd/core.c @@ -231,6 +231,10 @@ dev_dbg(cdev->dev, "MEMSETBADBLOCK: 0x%08llx\n", *offset); ret = mtd_block_markbad(mtd, *offset); break; + case MEMSETGOODBLOCK: + dev_dbg(cdev->dev, "MEMSETGOODBLOCK: 0x%08llx\n", *offset); + ret = mtd_block_markgood(mtd, *offset); + break; case MEMERASE: ret = mtd_op_erase(cdev, ei->length, ei->start + cdev->offset); break; @@ -320,6 +324,18 @@ return ret; } +int mtd_block_markgood(struct mtd_info *mtd, loff_t ofs) +{ + int ret; + + if (mtd->block_markgood) + ret = mtd->block_markgood(mtd, ofs); + else + ret = -ENOSYS; + + return ret; +} + int mtd_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) { diff --git a/drivers/mtd/mtdconcat.c b/drivers/mtd/mtdconcat.c index 6395b2f..fa43071 100644 --- a/drivers/mtd/mtdconcat.c +++ b/drivers/mtd/mtdconcat.c @@ -506,6 +506,28 @@ return err; } +static int concat_block_markgood(struct mtd_info *mtd, loff_t ofs) +{ + struct mtd_concat *concat = CONCAT(mtd); + int i, err = -EINVAL; + + for (i = 0; i < concat->num_subdev; i++) { + struct mtd_info *subdev = concat->subdev[i]; + + if (ofs >= subdev->size) { + ofs -= subdev->size; + continue; + } + + err = mtd_block_markgood(subdev, ofs); + if (!err) + mtd->ecc_stats.badblocks--; + break; + } + + return err; +} + /* * This function constructs a virtual MTD device by concatenating * num_devs MTD devices. A pointer to the new device object is @@ -565,6 +587,8 @@ concat->mtd.block_isbad = concat_block_isbad; if (subdev[0]->block_markbad) concat->mtd.block_markbad = concat_block_markbad; + if (subdev[0]->block_markgood) + concat->mtd.block_markgood = concat_block_markgood; concat->mtd.ecc_stats.badblocks = subdev[0]->ecc_stats.badblocks; diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index ec5a8b7..ffbf829 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -456,6 +456,38 @@ return chip->block_bad(mtd, ofs, getchip); } +/** + * nand_default_block_markgood - [DEFAULT] mark a block good + * @mtd: MTD device structure + * @ofs: offset from device start + * + * This is the default implementation, which can be overridden by + * a hardware specific driver. +*/ +static __maybe_unused int nand_default_block_markgood(struct mtd_info *mtd, loff_t ofs) +{ + struct nand_chip *chip = mtd->priv; + int block, res, ret = 0; + + /* Get block number */ + block = (int)(ofs >> chip->bbt_erase_shift); + /* Mark block good in memory-based BBT */ + if (chip->bbt) + chip->bbt[block >> 2] &= ~(0x01 << ((block & 0x03) << 1)); + + /* Update flash-based bad block table */ + if (IS_ENABLED(CONFIG_NAND_BBT) && chip->bbt_options & NAND_BBT_USE_FLASH) { + res = nand_update_bbt(mtd, ofs); + if (!ret) + ret = res; + } + + if (!ret) + mtd->ecc_stats.badblocks++; + + return ret; +} + /* Wait for the ready pin, after a command. The timeout is caught later. */ void nand_wait_ready(struct mtd_info *mtd) { @@ -2775,6 +2807,30 @@ } /** + * nand_block_markgood - [MTD Interface] Mark block at the given offset as bad + * @mtd: MTD device structure + * @ofs: offset relative to mtd start + */ +static int nand_block_markgood(struct mtd_info *mtd, loff_t ofs) +{ + struct nand_chip *chip = mtd->priv; + int ret; + + if (!IS_ENABLED(CONFIG_MTD_WRITE)) + return -ENOTSUPP; + + ret = nand_block_isbad(mtd, ofs); + if (ret < 0) + return ret; + + /* If it was good already, return success and do nothing */ + if (!ret) + return 0; + + return chip->block_markgood(mtd, ofs); +} + +/** * nand_onfi_set_features- [REPLACEABLE] set features for ONFI nand * @mtd: MTD device structure * @chip: nand chip info structure @@ -2844,6 +2900,8 @@ #ifdef CONFIG_MTD_WRITE if (!chip->block_markbad) chip->block_markbad = nand_default_block_markbad; + if (!chip->block_markgood) + chip->block_markgood = nand_default_block_markgood; if (!chip->write_buf) chip->write_buf = busw ? nand_write_buf16 : nand_write_buf; #endif @@ -3707,6 +3765,7 @@ mtd->unlock = NULL; mtd->block_isbad = nand_block_isbad; mtd->block_markbad = nand_block_markbad; + mtd->block_markgood = nand_block_markgood; mtd->writebufsize = mtd->writesize; /* propagate ecc info to mtd_info */ diff --git a/drivers/mtd/partition.c b/drivers/mtd/partition.c index c11a3db..261e35c 100644 --- a/drivers/mtd/partition.c +++ b/drivers/mtd/partition.c @@ -114,6 +114,21 @@ return res; } +static int mtd_part_block_markgood(struct mtd_info *mtd, loff_t ofs) +{ + int res; + + if (!(mtd->flags & MTD_WRITEABLE)) + return -EROFS; + if (ofs >= mtd->size) + return -EINVAL; + ofs += mtd->master_offset; + res = mtd->master->block_markgood(mtd->master, ofs); + if (!res) + mtd->ecc_stats.badblocks--; + return res; +} + struct mtd_info *mtd_add_partition(struct mtd_info *mtd, off_t offset, uint64_t size, unsigned long flags, const char *name) { @@ -168,6 +183,7 @@ 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 deacaaa..75ed3b0 100644 --- a/fs/devfs-core.c +++ b/fs/devfs-core.c @@ -192,6 +192,7 @@ switch (request) { case MEMSETBADBLOCK: + case MEMSETGOODBLOCK: case MEMGETBADBLOCK: offset = *_buf; offset += cdev->offset; diff --git a/include/linux/mtd/mtd-abi.h b/include/linux/mtd/mtd-abi.h index 8e778df..9bca9b5 100644 --- a/include/linux/mtd/mtd-abi.h +++ b/include/linux/mtd/mtd-abi.h @@ -118,6 +118,7 @@ #define ECCGETLAYOUT _IOR('M', 17, struct nand_ecclayout) #define ECCGETSTATS _IOR('M', 18, struct mtd_ecc_stats) #define MTDFILEMODE _IO('M', 19) +#define MEMSETGOODBLOCK _IOW('M', 20, loff_t) /* * Obsolete legacy interface. Keep it in order not to break userspace diff --git a/include/linux/mtd/mtd.h b/include/linux/mtd/mtd.h index 421a941..efb08b1 100644 --- a/include/linux/mtd/mtd.h +++ b/include/linux/mtd/mtd.h @@ -189,6 +189,7 @@ /* Bad block management functions */ int (*block_isbad) (struct mtd_info *mtd, loff_t ofs); int (*block_markbad) (struct mtd_info *mtd, loff_t ofs); + int (*block_markgood) (struct mtd_info *mtd, loff_t ofs); /* ECC status information */ struct mtd_ecc_stats ecc_stats; @@ -308,6 +309,7 @@ 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 83d664e..b787842 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -394,6 +394,7 @@ * @select_chip: [REPLACEABLE] select chip nr * @block_bad: [REPLACEABLE] check, if the block is bad * @block_markbad: [REPLACEABLE] mark the block bad + * @block_markgood: [REPLACEABLE] mark the block good * @cmd_ctrl: [BOARDSPECIFIC] hardwarespecific function for controlling * ALE/CLE/nCE. Also used to write command and address * @init_size: [BOARDSPECIFIC] hardwarespecific function for setting @@ -479,6 +480,7 @@ void (*select_chip)(struct mtd_info *mtd, int chip); int (*block_bad)(struct mtd_info *mtd, loff_t ofs, int getchip); int (*block_markbad)(struct mtd_info *mtd, loff_t ofs); + int (*block_markgood)(struct mtd_info *mtd, loff_t ofs); void (*cmd_ctrl)(struct mtd_info *mtd, int dat, unsigned int ctrl); int (*init_size)(struct mtd_info *mtd, struct nand_chip *this, u8 *id_data);