From 9ec9e1b5dea426d96e8465b708aee03fe79f0044 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Fri, 11 Mar 2016 11:03:38 +0100 Subject: 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(+) (limited to 'common/imx-bbu-nand-fcb.c') diff --git a/common/imx-bbu-nand-fcb.c b/common/imx-bbu-nand-fcb.c index 3dc9274a44..0c7f75c1d1 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); -- cgit v1.2.3