summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--common/imx-bbu-nand-fcb.c230
1 files changed, 230 insertions, 0 deletions
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)
{
@@ -495,6 +571,154 @@ out:
}
/**
+ * dbbt_block_is_bad - Check if according to the given DBBT a block is bad
+ * @dbbt: The DBBT data page
+ * @block: The block to test
+ *
+ * This function checks if a block is marked as bad in the given DBBT.
+ *
+ * return: true if the block is bad, false otherwise.
+ */
+static int dbbt_block_is_bad(void *_dbbt, int block)
+{
+ int i;
+ u32 *dbbt = _dbbt;
+ int num_bad_blocks;
+
+ if (!_dbbt)
+ return false;
+
+ dbbt++; /* reserved */
+
+ num_bad_blocks = *dbbt++;
+
+ for (i = 0; i < num_bad_blocks; i++) {
+ if (*dbbt == block)
+ return true;
+ dbbt++;
+ }
+
+ return false;
+}
+
+/**
+ * dbbt_check - Check if DBBT is readable and consistent to the mtd BBT
+ * @mtd: The mtd Nand device
+ * @dbbt: The page where the DBBT is found
+ *
+ * This function checks if the DBBT is readable and consistent to the mtd
+ * layers idea of bad blocks.
+ *
+ * return: 0 if the DBBT is readable and consistent to the mtd BBT, a
+ * negative error code otherwise.
+ */
+static int dbbt_check(struct mtd_info *mtd, int page)
+{
+ int ret, needs_cleanup = 0;
+ size_t r;
+ void *dbbt_header;
+ void *dbbt_entries = NULL;
+ struct dbbt_block *dbbt;
+ int num_blocks = mtd_div_by_eb(mtd->size, mtd);
+ int n;
+
+ dbbt_header = xmalloc(mtd->writesize);
+
+ ret = mtd_read(mtd, page * mtd->writesize, mtd->writesize, &r, dbbt_header);
+ if (ret == -EUCLEAN) {
+ pr_warn("page %d needs cleaning\n", page);
+ needs_cleanup = 1;
+ } else if (ret < 0) {
+ pr_err("Cannot read page %d: %s\n", page, strerror(-ret));
+ goto out;
+ }
+
+ dbbt = dbbt_header;
+
+ if (dbbt->FingerPrint != 0x54424244) {
+ pr_err("dbbt at page %d is readable but does not contain a valid DBBT\n",
+ page);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (dbbt->DBBTNumOfPages) {
+ dbbt_entries = xmalloc(mtd->writesize);
+
+ ret = mtd_read(mtd, (page + 4) * mtd->writesize, mtd->writesize, &r, dbbt_entries);
+ if (ret == -EUCLEAN) {
+ pr_warn("page %d needs cleaning\n", page);
+ needs_cleanup = 1;
+ } else if (ret < 0) {
+ pr_err("Cannot read page %d: %s\n", page, strerror(-ret));
+ free(dbbt_entries);
+ goto out;
+ }
+ } else {
+ dbbt_entries = NULL;
+ }
+
+ for (n = 0; n < num_blocks; n++) {
+ if (mtd_peb_is_bad(mtd, n) != dbbt_block_is_bad(dbbt_entries, n)) {
+ ret = -EINVAL;
+ goto out;
+ }
+ }
+
+ ret = 0;
+out:
+ free(dbbt_header);
+ free(dbbt_entries);
+
+ if (ret < 0)
+ return ret;
+ if (needs_cleanup)
+ return -EUCLEAN;
+ return 0;
+}
+
+/**
+ * fcb_dbbt_check - Check if a FCB/DBBT is valid
+ * @mtd: The mtd Nand device
+ * @num: The number of the FCB, corresponds to the eraseblock number
+ * @fcb: The FCB to check against
+ *
+ * This function checks if FCB/DBBT found on a device are valid. This
+ * means:
+ * - the FCB is readable on the device
+ * - the FCB is the same as the reference passed in @fcb
+ * - the DBBT is consistent to the mtd BBT
+ *
+ * return: 0 if the FCB/DBBT are valid, a negative error code otherwise
+ */
+static int fcb_dbbt_check(struct mtd_info *mtd, int num, struct fcb_block *fcb)
+{
+ int ret;
+ struct fcb_block *f;
+ int pages_per_block = mtd->erasesize / mtd->writesize;
+
+ ret = read_fcb(mtd, num, &f);
+ if (ret)
+ return ret;
+
+ if (memcmp(fcb, f, sizeof(*fcb))) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = dbbt_check(mtd, num * pages_per_block + 1);
+ if (ret)
+ goto out;
+
+ ret = 0;
+
+out:
+ free(f);
+
+ return ret;
+}
+
+/**
* imx_bbu_write_fcbs_dbbts - Write FCBs/DBBTs to first four blocks
* @mtd: The mtd device to write the FCBs/DBBTs to
* @fcb: The FCB block to write
@@ -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);