diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index 4a9aed4f0104..11139084b051 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -15,6 +15,14 @@ config MTD_NAND_ECC bool depends on MTD_NAND_CORE +config MTD_NAND_BBT_USING_FLASH + bool "Mtd Nand BBT using flash management strategy" + depends on MTD_NAND_CORE + help + This is a simple flash BBT management strategy for Nand devices. + + Say Y when you want to use BBT in flash. + endmenu endmenu diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile index 981372953b56..437aba822651 100644 --- a/drivers/mtd/nand/Makefile +++ b/drivers/mtd/nand/Makefile @@ -8,3 +8,4 @@ obj-y += raw/ obj-y += spi/ nandcore-$(CONFIG_MTD_NAND_ECC) += ecc.o +nandcore-$(CONFIG_MTD_NAND_BBT_USING_FLASH) += bbt_store.o diff --git a/drivers/mtd/nand/bbt_store.c b/drivers/mtd/nand/bbt_store.c new file mode 100644 index 000000000000..37b4708749d8 --- /dev/null +++ b/drivers/mtd/nand/bbt_store.c @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2021 Rockchip Electronics Co. Ltd. + * + */ + +#include +#include + +#ifdef BBT_DEBUG +#define BBT_DBG pr_err +#else +#define BBT_DBG(args...) +#endif + +struct nanddev_bbt_info { + u8 pattern[4]; + unsigned int version; +}; + +static u8 bbt_pattern[] = {'B', 'b', 't', '0' }; + +static int nanddev_read_bbt(struct nand_device *nand, u32 block, bool update) +{ + unsigned int bits_per_block = fls(NAND_BBT_BLOCK_NUM_STATUS); + unsigned int nblocks = nanddev_neraseblocks(nand); + unsigned int nbytes = DIV_ROUND_UP(nblocks * bits_per_block, + BITS_PER_LONG) * sizeof(*nand->bbt.cache); + struct mtd_info *mtd = nanddev_to_mtd(nand); + u8 *data_buf, *oob_buf; + struct nanddev_bbt_info *bbt_info; + struct mtd_oob_ops ops; + int bbt_page_num; + int ret = 0; + unsigned int version = 0; + + if (!nand->bbt.cache) + return -ENOMEM; + + if (block >= nblocks) + return -EINVAL; + + /* Aligned to page size, and even pages is better */ + bbt_page_num = (sizeof(struct nanddev_bbt_info) + nbytes + + mtd->writesize - 1) >> (ffs(mtd->writesize) - 1); + bbt_page_num = (bbt_page_num + 1) / 2 * 2; + data_buf = kzalloc(bbt_page_num * mtd->writesize, GFP_KERNEL); + if (!data_buf) + return -ENOMEM; + oob_buf = kzalloc(bbt_page_num * mtd->oobsize, GFP_KERNEL); + if (!oob_buf) { + kfree(data_buf); + + return -ENOMEM; + } + + bbt_info = (struct nanddev_bbt_info *)(data_buf + nbytes); + + memset(&ops, 0, sizeof(struct mtd_oob_ops)); + ops.mode = MTD_OPS_PLACE_OOB; + ops.datbuf = data_buf; + ops.len = bbt_page_num * mtd->writesize; + ops.oobbuf = oob_buf; + ops.ooblen = bbt_page_num * mtd->oobsize; + ops.ooboffs = 0; + + ret = mtd_read_oob(mtd, block * mtd->erasesize, &ops); + if (ret && ret != -EUCLEAN) { + pr_err("%s fail %d\n", __func__, ret); + ret = -EIO; + goto out; + } else { + ret = 0; + } + + if (oob_buf[0] != 0xff && !memcmp(bbt_pattern, bbt_info->pattern, 4)) + version = bbt_info->version; + + BBT_DBG("read_bbt from blk=%d tag=%d ver=%d\n", block, update, version); + if (update && version > nand->bbt.version) { + memcpy(nand->bbt.cache, data_buf, nbytes); + nand->bbt.version = version; + } + +out: + kfree(oob_buf); + kfree(data_buf); + + return ret < 0 ? -EIO : version; +} + +static int nanddev_write_bbt(struct nand_device *nand, u32 block) +{ + unsigned int bits_per_block = fls(NAND_BBT_BLOCK_NUM_STATUS); + unsigned int nblocks = nanddev_neraseblocks(nand); + unsigned int nbytes = DIV_ROUND_UP(nblocks * bits_per_block, + BITS_PER_LONG) * sizeof(*nand->bbt.cache); + struct mtd_info *mtd = nanddev_to_mtd(nand); + u8 *data_buf, *oob_buf; + struct nanddev_bbt_info *bbt_info; + struct mtd_oob_ops ops; + int bbt_page_num; + int ret = 0; + struct nand_pos pos; + + BBT_DBG("write_bbt to blk=%d ver=%d\n", block, nand->bbt.version); + if (!nand->bbt.cache) + return -ENOMEM; + + if (block >= nblocks) + return -EINVAL; + + /* Aligned to page size, and even pages is better */ + bbt_page_num = (sizeof(struct nanddev_bbt_info) + nbytes + + mtd->writesize - 1) >> (ffs(mtd->writesize) - 1); + bbt_page_num = (bbt_page_num + 1) / 2 * 2; + + data_buf = kzalloc(bbt_page_num * mtd->writesize, GFP_KERNEL); + if (!data_buf) + return -ENOMEM; + oob_buf = kzalloc(bbt_page_num * mtd->oobsize, GFP_KERNEL); + if (!oob_buf) { + kfree(data_buf); + + return -ENOMEM; + } + + bbt_info = (struct nanddev_bbt_info *)(data_buf + nbytes); + + memcpy(data_buf, nand->bbt.cache, nbytes); + memcpy(bbt_info, bbt_pattern, 4); + bbt_info->version = nand->bbt.version; + + nanddev_offs_to_pos(nand, block * mtd->erasesize, &pos); + ret = nand->ops->erase(nand, &pos); + if (ret) + goto out; + + memset(&ops, 0, sizeof(struct mtd_oob_ops)); + ops.mode = MTD_OPS_PLACE_OOB; + ops.datbuf = data_buf; + ops.len = bbt_page_num * mtd->writesize; + ops.oobbuf = oob_buf; + ops.ooblen = bbt_page_num * mtd->oobsize; + ops.ooboffs = 0; + ret = mtd_write_oob(mtd, block * mtd->erasesize, &ops); + +out: + kfree(oob_buf); + kfree(data_buf); + + return ret; +} + +static int nanddev_bbt_format(struct nand_device *nand) +{ + unsigned int nblocks = nanddev_neraseblocks(nand); + struct mtd_info *mtd = nanddev_to_mtd(nand); + struct nand_pos pos; + u32 start_block, block; + + start_block = nblocks - NANDDEV_BBT_SCAN_MAXBLOCKS; + + for (block = 0; block < nblocks; block++) { + nanddev_offs_to_pos(nand, block * mtd->erasesize, &pos); + if (nanddev_isbad(nand, &pos)) + nanddev_bbt_set_block_status(nand, block, + NAND_BBT_BLOCK_FACTORY_BAD); + } + + for (block = 0; block < NANDDEV_BBT_SCAN_MAXBLOCKS; block++) { + if (nanddev_bbt_get_block_status(nand, start_block + block) == + NAND_BBT_BLOCK_GOOD) + nanddev_bbt_set_block_status(nand, start_block + block, + NAND_BBT_BLOCK_WORN); + } + + return 0; +} + +/** + * nanddev_scan_bbt_in_flash() - Scan for a BBT in the flash + * @nand: nand device + * + * Scan a bbt in flash, if not exist, format one. + * + * Return: 0 in case of success, a negative error code otherwise. + */ +int nanddev_scan_bbt_in_flash(struct nand_device *nand) +{ + unsigned int nblocks = nanddev_neraseblocks(nand); + u32 start_block, block; + int ret = 0; + + nand->bbt.version = 0; + start_block = nblocks - NANDDEV_BBT_SCAN_MAXBLOCKS; + for (block = 0; block < NANDDEV_BBT_SCAN_MAXBLOCKS; block++) + nanddev_read_bbt(nand, start_block + block, true); + + if (nand->bbt.version == 0) { + nanddev_bbt_format(nand); + ret = nanddev_bbt_in_flash_update(nand); + if (ret) { + nand->bbt.option = 0; + pr_err("%s fail\n", __func__); + } + } + + nand->bbt.option |= NANDDEV_BBT_SCANNED; + + return ret; +} +EXPORT_SYMBOL_GPL(nanddev_scan_bbt_in_flash); + +/** + * nanddev_bbt_in_flash_update() - Update a BBT + * @nand: nand device + * + * Update the BBT to flash. + * + * Return: 0 in case of success, a negative error code otherwise. + */ +int nanddev_bbt_in_flash_update(struct nand_device *nand) +{ + if (nand->bbt.option & NANDDEV_BBT_SCANNED) { + unsigned int nblocks = nanddev_neraseblocks(nand); + u32 bbt_version[NANDDEV_BBT_SCAN_MAXBLOCKS]; + int start_block, block; + u32 min_version, block_des; + int ret, count = 0; + + start_block = nblocks - NANDDEV_BBT_SCAN_MAXBLOCKS; + for (block = 0; block < NANDDEV_BBT_SCAN_MAXBLOCKS; block++) { + ret = nanddev_bbt_get_block_status(nand, start_block + block); + if (ret == NAND_BBT_BLOCK_FACTORY_BAD) { + bbt_version[block] = 0xFFFFFFFF; + continue; + } + ret = nanddev_read_bbt(nand, start_block + block, + false); + if (ret < 0) + bbt_version[block] = 0xFFFFFFFF; + else if (ret == 0) + bbt_version[block] = 0; + else + bbt_version[block] = ret; + } +get_min_ver: + min_version = 0xFFFFFFFF; + block_des = 0; + for (block = 0; block < NANDDEV_BBT_SCAN_MAXBLOCKS; block++) { + if (bbt_version[block] < min_version) { + min_version = bbt_version[block]; + block_des = start_block + block; + } + } + + if (block_des > 0) { + nand->bbt.version++; + ret = nanddev_write_bbt(nand, block_des); + bbt_version[block_des - start_block] = 0xFFFFFFFF; + if (ret) { + pr_err("%s blk= %d ret= %d\n", __func__, + block_des, ret); + goto get_min_ver; + } else { + count++; + if (count < 2) + goto get_min_ver; + BBT_DBG("%s success\n", __func__); + } + } else { + pr_err("%s failed\n", __func__); + + return -EINVAL; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(nanddev_bbt_in_flash_update); diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index 0aea4a5e61a2..919081dc2177 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -638,6 +639,9 @@ static int spinand_mtd_block_markbad(struct mtd_info *mtd, loff_t offs) ret = nanddev_markbad(nand, &pos); mutex_unlock(&spinand->lock); + if (IS_ENABLED(CONFIG_MTD_NAND_BBT_USING_FLASH)) + nanddev_bbt_in_flash_update(nand); + return ret; } @@ -1095,6 +1099,9 @@ static int spinand_init(struct spinand_device *spinand) if (IS_ENABLED(CONFIG_SPI_ROCKCHIP_SFC)) mtd->name = "spi-nand0"; + if (IS_ENABLED(CONFIG_MTD_NAND_BBT_USING_FLASH)) + nanddev_scan_bbt_in_flash(nand); + return 0; err_cleanup_nanddev: diff --git a/include/linux/mtd/bbt_store.h b/include/linux/mtd/bbt_store.h new file mode 100644 index 000000000000..20373ef5c4a6 --- /dev/null +++ b/include/linux/mtd/bbt_store.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2021 Rockchip Electronics Co. Ltd. + * + */ + +#ifndef __BBT_STORE_H +#define __BBT_STORE_H + +#include + +#ifdef CONFIG_MTD_NAND_BBT_USING_FLASH +int nanddev_scan_bbt_in_flash(struct nand_device *nand); +int nanddev_bbt_in_flash_update(struct nand_device *nand); +#else +static inline int nanddev_scan_bbt_in_flash(struct nand_device *nand) +{ + return 0; +} +static inline int nanddev_bbt_in_flash_update(struct nand_device *nand) +{ + return 0; +} +#endif + +#endif diff --git a/include/linux/mtd/nand.h b/include/linux/mtd/nand.h index 697ea2474a7c..6d19d8c5b6e7 100644 --- a/include/linux/mtd/nand.h +++ b/include/linux/mtd/nand.h @@ -198,12 +198,24 @@ struct nand_ecc_props { /* NAND ECC misc flags */ #define NAND_ECC_MAXIMIZE_STRENGTH BIT(0) +/* nand_bbt option */ +#define NANDDEV_BBT_SCANNED BIT(0) + +/* The maximum number of blocks to scan for a bbt */ +#define NANDDEV_BBT_SCAN_MAXBLOCKS 4 + /** * struct nand_bbt - bad block table object * @cache: in memory BBT cache + * @option: the option of BBT + * @version: current memory BBT cache version */ struct nand_bbt { unsigned long *cache; +#ifdef CONFIG_MTD_NAND_BBT_USING_FLASH + unsigned int option; + unsigned int version; +#endif }; /**