mtd: nand: ecc: Add an I/O request tweaking mechanism
authorMiquel Raynal <miquel.raynal@bootlin.com>
Tue, 29 Sep 2020 23:01:05 +0000 (01:01 +0200)
committerMiquel Raynal <miquel.raynal@bootlin.com>
Mon, 30 Nov 2020 08:27:32 +0000 (09:27 +0100)
Currently, BCH and Hamming engine are sharing the same
tweaking/restoring I/O mechanism: they need the I/O request to fully
cover the main/OOB area. Let's make this code generic as sharing the
code between two drivers is already a win. Maybe other ECC engine
drivers will need it too.

Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Link: https://lore.kernel.org/linux-mtd/20200929230124.31491-2-miquel.raynal@bootlin.com
drivers/mtd/nand/ecc.c
include/linux/mtd/nand.h

index 4a56e6c..5411377 100644 (file)
@@ -95,6 +95,7 @@
 
 #include <linux/module.h>
 #include <linux/mtd/nand.h>
+#include <linux/slab.h>
 
 /**
  * nand_ecc_init_ctx - Init the ECC engine context
@@ -479,6 +480,111 @@ bool nand_ecc_is_strong_enough(struct nand_device *nand)
 }
 EXPORT_SYMBOL(nand_ecc_is_strong_enough);
 
+/* ECC engine driver internal helpers */
+int nand_ecc_init_req_tweaking(struct nand_ecc_req_tweak_ctx *ctx,
+                              struct nand_device *nand)
+{
+       unsigned int total_buffer_size;
+
+       ctx->nand = nand;
+
+       /* Let the user decide the exact length of each buffer */
+       if (!ctx->page_buffer_size)
+               ctx->page_buffer_size = nanddev_page_size(nand);
+       if (!ctx->oob_buffer_size)
+               ctx->oob_buffer_size = nanddev_per_page_oobsize(nand);
+
+       total_buffer_size = ctx->page_buffer_size + ctx->oob_buffer_size;
+
+       ctx->spare_databuf = kzalloc(total_buffer_size, GFP_KERNEL);
+       if (!ctx->spare_databuf)
+               return -ENOMEM;
+
+       ctx->spare_oobbuf = ctx->spare_databuf + ctx->page_buffer_size;
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(nand_ecc_init_req_tweaking);
+
+void nand_ecc_cleanup_req_tweaking(struct nand_ecc_req_tweak_ctx *ctx)
+{
+       kfree(ctx->spare_databuf);
+}
+EXPORT_SYMBOL_GPL(nand_ecc_cleanup_req_tweaking);
+
+/*
+ * Ensure data and OOB area is fully read/written otherwise the correction might
+ * not work as expected.
+ */
+void nand_ecc_tweak_req(struct nand_ecc_req_tweak_ctx *ctx,
+                       struct nand_page_io_req *req)
+{
+       struct nand_device *nand = ctx->nand;
+       struct nand_page_io_req *orig, *tweak;
+
+       /* Save the original request */
+       ctx->orig_req = *req;
+       ctx->bounce_data = false;
+       ctx->bounce_oob = false;
+       orig = &ctx->orig_req;
+       tweak = req;
+
+       /* Ensure the request covers the entire page */
+       if (orig->datalen < nanddev_page_size(nand)) {
+               ctx->bounce_data = true;
+               tweak->dataoffs = 0;
+               tweak->datalen = nanddev_page_size(nand);
+               tweak->databuf.in = ctx->spare_databuf;
+               memset(tweak->databuf.in, 0xFF, ctx->page_buffer_size);
+       }
+
+       if (orig->ooblen < nanddev_per_page_oobsize(nand)) {
+               ctx->bounce_oob = true;
+               tweak->ooboffs = 0;
+               tweak->ooblen = nanddev_per_page_oobsize(nand);
+               tweak->oobbuf.in = ctx->spare_oobbuf;
+               memset(tweak->oobbuf.in, 0xFF, ctx->oob_buffer_size);
+       }
+
+       /* Copy the data that must be writen in the bounce buffers, if needed */
+       if (orig->type == NAND_PAGE_WRITE) {
+               if (ctx->bounce_data)
+                       memcpy((void *)tweak->databuf.out + orig->dataoffs,
+                              orig->databuf.out, orig->datalen);
+
+               if (ctx->bounce_oob)
+                       memcpy((void *)tweak->oobbuf.out + orig->ooboffs,
+                              orig->oobbuf.out, orig->ooblen);
+       }
+}
+EXPORT_SYMBOL_GPL(nand_ecc_tweak_req);
+
+void nand_ecc_restore_req(struct nand_ecc_req_tweak_ctx *ctx,
+                         struct nand_page_io_req *req)
+{
+       struct nand_page_io_req *orig, *tweak;
+
+       orig = &ctx->orig_req;
+       tweak = req;
+
+       /* Restore the data read from the bounce buffers, if needed */
+       if (orig->type == NAND_PAGE_READ) {
+               if (ctx->bounce_data)
+                       memcpy(orig->databuf.in,
+                              tweak->databuf.in + orig->dataoffs,
+                              orig->datalen);
+
+               if (ctx->bounce_oob)
+                       memcpy(orig->oobbuf.in,
+                              tweak->oobbuf.in + orig->ooboffs,
+                              orig->ooblen);
+       }
+
+       /* Ensure the original request is restored */
+       *req = *orig;
+}
+EXPORT_SYMBOL_GPL(nand_ecc_restore_req);
+
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("Miquel Raynal <miquel.raynal@bootlin.com>");
 MODULE_DESCRIPTION("Generic ECC engine");
index 697ea24..36e4fe0 100644 (file)
@@ -279,6 +279,38 @@ int nand_ecc_finish_io_req(struct nand_device *nand,
 bool nand_ecc_is_strong_enough(struct nand_device *nand);
 
 /**
+ * struct nand_ecc_req_tweak_ctx - Help for automatically tweaking requests
+ * @orig_req: Pointer to the original IO request
+ * @nand: Related NAND device, to have access to its memory organization
+ * @page_buffer_size: Real size of the page buffer to use (can be set by the
+ *                    user before the tweaking mechanism initialization)
+ * @oob_buffer_size: Real size of the OOB buffer to use (can be set by the
+ *                   user before the tweaking mechanism initialization)
+ * @spare_databuf: Data bounce buffer
+ * @spare_oobbuf: OOB bounce buffer
+ * @bounce_data: Flag indicating a data bounce buffer is used
+ * @bounce_oob: Flag indicating an OOB bounce buffer is used
+ */
+struct nand_ecc_req_tweak_ctx {
+       struct nand_page_io_req orig_req;
+       struct nand_device *nand;
+       unsigned int page_buffer_size;
+       unsigned int oob_buffer_size;
+       void *spare_databuf;
+       void *spare_oobbuf;
+       bool bounce_data;
+       bool bounce_oob;
+};
+
+int nand_ecc_init_req_tweaking(struct nand_ecc_req_tweak_ctx *ctx,
+                              struct nand_device *nand);
+void nand_ecc_cleanup_req_tweaking(struct nand_ecc_req_tweak_ctx *ctx);
+void nand_ecc_tweak_req(struct nand_ecc_req_tweak_ctx *ctx,
+                       struct nand_page_io_req *req);
+void nand_ecc_restore_req(struct nand_ecc_req_tweak_ctx *ctx,
+                         struct nand_page_io_req *req);
+
+/**
  * struct nand_ecc - Information relative to the ECC
  * @defaults: Default values, depend on the underlying subsystem
  * @requirements: ECC requirements from the NAND chip perspective