linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] mtd: brcmnand: Workaround false ECC uncorrectable errors
@ 2015-11-28 19:23 Simon Arlott
  2015-12-01 10:41 ` Jonas Gorski
  0 siblings, 1 reply; 9+ messages in thread
From: Simon Arlott @ 2015-11-28 19:23 UTC (permalink / raw)
  To: Brian Norris, Kamal Dasu, David Woodhouse, MTD Maling List,
	bcm-kernel-feedback-list, Linux Kernel Mailing List
  Cc: Florian Fainelli, Jonas Gorski

Workaround false ECC uncorrectable errors by checking if the data
has been erased and the OOB data indicates that the data has been
erased. The v4.0 controller on the BCM63168 incorrectly handles
these as uncorrectable errors.

I don't know which version of the controller handles this scenario
correctly. I'm only able to test this in Hamming mode, so the BCH
version needs to be checked.

This code is quite complicated because the fact that either the data
buffer or the OOB data may not have been read from the controller, and
both of them are required to determine if the error is incorrect.

Signed-off-by: Simon Arlott <simon@fire.lp0.eu>
---
 drivers/mtd/nand/brcmnand/brcmnand.c | 107 ++++++++++++++++++++++++++++++++++-
 1 file changed, 106 insertions(+), 1 deletion(-)

diff --git a/drivers/mtd/nand/brcmnand/brcmnand.c b/drivers/mtd/nand/brcmnand/brcmnand.c
index 5f26b8a..0857af7 100644
--- a/drivers/mtd/nand/brcmnand/brcmnand.c
+++ b/drivers/mtd/nand/brcmnand/brcmnand.c
@@ -1387,6 +1387,102 @@ static int brcmnand_dma_trans(struct brcmnand_host *host, u64 addr, u32 *buf,
 }
 
 /*
+ * Workaround false ECC uncorrectable errors by checking if the data
+ * has been erased and the OOB data indicates that the data has been
+ * erased. The controller incorrectly handles these as uncorrectable
+ * errors.
+ */
+static void brcmnand_read_ecc_unc_err(struct mtd_info *mtd,
+				struct nand_chip *chip,
+				int idx, unsigned int trans,
+				u32 *buf, u8 *oob_begin, u8 *oob_end,
+				u64 *err_addr)
+{
+	struct brcmnand_host *host = chip->priv;
+	struct brcmnand_controller *ctrl = host->ctrl;
+	u32 *buf_tmp = NULL;
+	u8 *oob_tmp = NULL;
+	bool buf_erased = false;
+	bool oob_erased = false;
+	int j;
+
+	/* Assume this is fixed in v5.0+ */
+	if (ctrl->nand_version >= 0x0500)
+		return;
+
+	/* Read OOB data if not already read */
+	if (!oob_begin) {
+		oob_tmp = kmalloc(ctrl->max_oob, GFP_KERNEL);
+		if (!oob_tmp)
+			goto out_free;
+
+		oob_begin = oob_tmp;
+		oob_end = oob_tmp;
+
+		oob_end += read_oob_from_regs(ctrl, idx, oob_tmp,
+				mtd->oobsize / trans,
+				host->hwcfg.sector_size_1k);
+	}
+
+	if (is_hamming_ecc(&host->hwcfg)) {
+		u8 *oob_offset = oob_begin + 6;
+
+		if (oob_offset + 3 < oob_end)
+			/* Erased if ECC bytes are all 0xFF, or the data bytes
+			 * are all 0xFF which should have Hamming codes of 0x00
+			 */
+			oob_erased = memchr_inv(oob_offset, 0xFF, 3) == NULL ||
+				memchr_inv(oob_offset, 0x00, 3) == NULL;
+	} else { /* BCH */
+		u8 *oob_offset = oob_end - ctrl->max_oob;
+
+		if (oob_offset >= oob_begin)
+			/* Erased if ECC bytes are all 0xFF */
+			oob_erased = memchr_inv(oob_offset, 0xFF,
+						oob_end - oob_offset) == NULL;
+	}
+
+	if (!oob_erased)
+		goto out_free;
+
+	/* Read data buffer if not already read */
+	if (!buf) {
+		buf_tmp = kmalloc_array(FC_WORDS, sizeof(*buf_tmp), GFP_KERNEL);
+		if (!buf_tmp)
+			goto out_free;
+
+		buf = buf_tmp;
+
+		brcmnand_soc_data_bus_prepare(ctrl->soc);
+
+		for (j = 0; j < FC_WORDS; j++, buf++)
+			*buf = brcmnand_read_fc(ctrl, j);
+
+		brcmnand_soc_data_bus_unprepare(ctrl->soc);
+	}
+
+	/* Go to start of buffer */
+	buf -= FC_WORDS;
+
+	/* Erased if all data bytes are 0xFF */
+	buf_erased = memchr_inv(buf, 0xFF, FC_WORDS) == NULL;
+
+	if (!buf_erased)
+		goto out_free;
+
+	/* Clear error addresses */
+	brcmnand_write_reg(ctrl, BRCMNAND_UNCORR_ADDR, 0);
+	brcmnand_write_reg(ctrl, BRCMNAND_CORR_ADDR, 0);
+	brcmnand_write_reg(ctrl, BRCMNAND_UNCORR_EXT_ADDR, 0);
+	brcmnand_write_reg(ctrl, BRCMNAND_CORR_EXT_ADDR, 0);
+	*err_addr = 0;
+
+out_free:
+	kfree(buf_tmp);
+	kfree(oob_tmp);
+}
+
+/*
  * Assumes proper CS is already set
  */
 static int brcmnand_read_by_pio(struct mtd_info *mtd, struct nand_chip *chip,
@@ -1396,6 +1492,7 @@ static int brcmnand_read_by_pio(struct mtd_info *mtd, struct nand_chip *chip,
 	struct brcmnand_host *host = chip->priv;
 	struct brcmnand_controller *ctrl = host->ctrl;
 	int i, j, ret = 0;
+	u8 *prev_oob = NULL;
 
 	/* Clear error addresses */
 	brcmnand_write_reg(ctrl, BRCMNAND_UNCORR_ADDR, 0);
@@ -1424,10 +1521,12 @@ static int brcmnand_read_by_pio(struct mtd_info *mtd, struct nand_chip *chip,
 			brcmnand_soc_data_bus_unprepare(ctrl->soc);
 		}
 
-		if (oob)
+		if (oob) {
+			prev_oob = oob;
 			oob += read_oob_from_regs(ctrl, i, oob,
 					mtd->oobsize / trans,
 					host->hwcfg.sector_size_1k);
+		}
 
 		if (!ret) {
 			*err_addr = brcmnand_read_reg(ctrl,
@@ -1435,6 +1534,12 @@ static int brcmnand_read_by_pio(struct mtd_info *mtd, struct nand_chip *chip,
 				((u64)(brcmnand_read_reg(ctrl,
 						BRCMNAND_UNCORR_EXT_ADDR)
 					& 0xffff) << 32);
+
+			if (*err_addr)
+				brcmnand_read_ecc_unc_err(mtd, chip,
+					i, trans, buf, prev_oob, oob,
+					err_addr);
+
 			if (*err_addr)
 				ret = -EBADMSG;
 		}
-- 
2.1.4

-- 
Simon Arlott

^ permalink raw reply related	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2015-12-02 22:23 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-11-28 19:23 [PATCH] mtd: brcmnand: Workaround false ECC uncorrectable errors Simon Arlott
2015-12-01 10:41 ` Jonas Gorski
2015-12-02 20:17   ` Simon Arlott
2015-12-02 20:44     ` Jonas Gorski
2015-12-02 20:54       ` Brian Norris
2015-12-02 21:06         ` Brian Norris
2015-12-02 21:08         ` Jonas Gorski
2015-12-02 21:29           ` Simon Arlott
2015-12-02 22:22           ` Brian Norris

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).