Let's continue introducing the generic ECC engine abstraction in the NAND subsystem by instantiating a second ECC engine: the software Hamming one. Signed-off-by: Miquel Raynal <miquel.raynal@xxxxxxxxxxx> --- drivers/mtd/nand/ecc/sw-hamming-engine.c | 201 +++++++++++++++++++++ drivers/mtd/nand/raw/nand_base.c | 28 ++- include/linux/mtd/nand-sw-hamming-engine.h | 15 ++ 3 files changed, 227 insertions(+), 17 deletions(-) diff --git a/drivers/mtd/nand/ecc/sw-hamming-engine.c b/drivers/mtd/nand/ecc/sw-hamming-engine.c index 3918daf5547b..58c81c812709 100644 --- a/drivers/mtd/nand/ecc/sw-hamming-engine.c +++ b/drivers/mtd/nand/ecc/sw-hamming-engine.c @@ -465,6 +465,207 @@ int ecc_sw_hamming_correct(struct nand_device *nand, unsigned char *buf, } EXPORT_SYMBOL(ecc_sw_hamming_correct); +int ecc_sw_hamming_init_ctx(struct nand_device *nand) +{ + struct nand_ecc_conf *conf = &nand->ecc.ctx.conf; + struct mtd_info *mtd = nanddev_to_mtd(nand); + struct ecc_sw_hamming_conf *engine_conf; + + if (!mtd->ooblayout) { + switch (mtd->oobsize) { + case 8: + case 16: + mtd_set_ooblayout(mtd, &nand_ooblayout_sp_ops); + break; + case 64: + case 128: + mtd_set_ooblayout(mtd, &nand_ooblayout_lp_hamming_ops); + break; + default: + return -ENOTSUPP; + } + } + + conf->step_size = nand->ecc.user_conf.step_size; + conf->strength = 1; + + /* Use the strongest configuration by default */ + if (conf->step_size != 256 && conf->step_size != 512) + conf->step_size = 256; + + engine_conf = kzalloc(sizeof(*engine_conf), GFP_KERNEL); + if (!engine_conf) + return -ENOMEM; + + engine_conf->code_size = 3; + engine_conf->nsteps = mtd->writesize / conf->step_size; + engine_conf->calc_buf = kzalloc(sizeof(mtd->oobsize), GFP_KERNEL); + engine_conf->code_buf = kzalloc(sizeof(mtd->oobsize), GFP_KERNEL); + if (!engine_conf->calc_buf || !engine_conf->code_buf) { + kfree(engine_conf); + return -ENOMEM; + } + + nand->ecc.ctx.priv = engine_conf; + + return 0; +} + +void ecc_sw_hamming_cleanup_ctx(struct nand_device *nand) +{ + struct ecc_sw_hamming_conf *engine_conf = nand->ecc.ctx.priv; + + if (engine_conf) { + kfree(engine_conf->calc_buf); + kfree(engine_conf->code_buf); + } + + kfree(engine_conf); +} + +static int ecc_sw_hamming_prepare_io_req(struct nand_device *nand, + struct nand_page_io_req *req, + void *oobbuf) +{ + struct ecc_sw_hamming_conf *engine_conf = nand->ecc.ctx.priv; + struct mtd_info *mtd = nanddev_to_mtd(nand); + int eccsize = nand->ecc.ctx.conf.step_size; + int eccbytes = engine_conf->code_size; + int eccsteps = engine_conf->nsteps; + int total = nand->ecc.ctx.conf.total; + u8 *ecccalc = engine_conf->calc_buf; + const u8 *data = req->databuf.out; + int i, ret; + + /* Ensure the OOB buffer is empty before using it */ + if (req->oobbuf.in) + memset(req->oobbuf.in, 0xff, nanddev_per_page_oobsize(nand)); + + if (req->mode == MTD_OPS_RAW) + return 0; + + /* This engine does not provide BBM/free OOB bytes protection */ + if (!req->datalen) + return 0; + + /* + * Ensure OOB area is fully read/written otherwise the software + * correction cannot not apply. + */ + engine_conf->reqooblen = req->ooblen; + req->ooblen = nanddev_per_page_oobsize(nand); + + /* No preparation for page read */ + if (req->type == NAND_PAGE_READ) + return 0; + + /* Preparation for page write: derive the ECC bytes and place them */ + for (i = 0; eccsteps; eccsteps--, i += eccbytes, data += eccsize) + ecc_sw_hamming_calculate(nand, data, &ecccalc[i]); + + ret = mtd_ooblayout_set_eccbytes(mtd, ecccalc, oobbuf, 0, total); + + /* Also place user data OOB bytes in the free area, if any */ + if (engine_conf->reqooblen) { + if (req->mode == MTD_OPS_AUTO_OOB) + mtd_ooblayout_set_databytes(mtd, req->oobbuf.out, + oobbuf, + req->ooboffs, + req->ooblen); + else + memcpy(oobbuf + req->ooboffs, req->oobbuf.out, + req->ooblen); + } + + return ret; +} + +static int ecc_sw_hamming_finish_io_req(struct nand_device *nand, + struct nand_page_io_req *req, + void *oobbuf) +{ + struct ecc_sw_hamming_conf *engine_conf = nand->ecc.ctx.priv; + struct mtd_info *mtd = nanddev_to_mtd(nand); + int eccsize = nand->ecc.ctx.conf.step_size; + int total = nand->ecc.ctx.conf.total; + int eccbytes = engine_conf->code_size; + int eccsteps = engine_conf->nsteps; + u8 *ecccalc = engine_conf->calc_buf; + u8 *ecccode = engine_conf->code_buf; + unsigned int max_bitflips = 0; + u8 *data = req->databuf.in; + int i, ret; + + if (req->mode == MTD_OPS_RAW) + return 0; + + /* This engine does not provide BBM/free OOB bytes protection */ + if (!req->datalen) + return 0; + + /* Don't mess up with the upper layer: restore the request OOB length */ + req->ooblen = engine_conf->reqooblen; + + /* Nothing more to do for page write */ + if (req->type == NAND_PAGE_WRITE) + return 0; + + /* Finish a page read: retrieve the (raw) ECC bytes*/ + ret = mtd_ooblayout_get_eccbytes(mtd, ecccode, oobbuf, 0, total); + if (ret) + return ret; + + /* Calculate the ECC bytes */ + for (i = 0; eccsteps; eccsteps--, i += eccbytes, data += eccsize) + ecc_sw_hamming_calculate(nand, data, &ecccalc[i]); + + eccsteps = engine_conf->nsteps; + + /* Finish a page read: compare and correct */ + for (eccsteps = engine_conf->nsteps, i = 0, data = req->databuf.in; + eccsteps; + eccsteps--, i += eccbytes, data += eccsize) { + int stat = ecc_sw_hamming_correct(nand, data, + &ecccode[i], + &ecccalc[i]); + if (stat < 0) { + mtd->ecc_stats.failed++; + } else { + mtd->ecc_stats.corrected += stat; + max_bitflips = max_t(unsigned int, max_bitflips, stat); + } + } + + /* Format the OOB buffer that will be returned to the user */ + if (req->ooblen) { + if (req->mode == MTD_OPS_AUTO_OOB) + mtd_ooblayout_get_databytes(mtd, oobbuf, + req->oobbuf.in, + req->ooboffs, req->ooblen); + else + memcpy(req->oobbuf.in, oobbuf + req->ooboffs, + req->ooblen); + } + + return max_bitflips; +} + +static struct nand_ecc_engine_ops sw_hamming_engine_ops = { + .init_ctx = ecc_sw_hamming_init_ctx, + .cleanup_ctx = ecc_sw_hamming_cleanup_ctx, + .prepare_io_req = ecc_sw_hamming_prepare_io_req, + .finish_io_req = ecc_sw_hamming_finish_io_req, +}; + +static struct nand_ecc_engine sw_hamming_engine = { + .ops = &sw_hamming_engine_ops, +}; + +struct nand_ecc_engine *ecc_sw_hamming_get_engine(void) +{ + return &sw_hamming_engine; +} + MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Frans Meulenbroeks <fransmeulenbroeks@xxxxxxxxx>"); MODULE_DESCRIPTION("NAND software Hamming ECC support"); diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c index bfefa6009b04..1ed90fd512d7 100644 --- a/drivers/mtd/nand/raw/nand_base.c +++ b/drivers/mtd/nand/raw/nand_base.c @@ -4845,34 +4845,27 @@ static void nand_scan_ident_cleanup(struct nand_chip *chip) int rawnand_sw_hamming_init(struct nand_chip *chip) { - struct mtd_info *mtd = nand_to_mtd(chip); struct ecc_sw_hamming_conf *engine_conf; struct nand_device *base = &chip->base; + int ret; base->ecc.user_conf.mode = NAND_ECC_SOFT; base->ecc.user_conf.algo = NAND_ECC_HAMMING; base->ecc.user_conf.strength = chip->ecc.strength; base->ecc.user_conf.step_size = chip->ecc.size; - if (base->ecc.user_conf.strength != 1 || - (base->ecc.user_conf.step_size != 256 && - base->ecc.user_conf.step_size != 512)) { - pr_err("%s: unsupported strength or step size\n", __func__); - return -EINVAL; - } + ret = ecc_sw_hamming_init_ctx(base); + if (ret) + return ret; - engine_conf = kzalloc(sizeof(*engine_conf), GFP_KERNEL); - if (!engine_conf) - return -ENOMEM; - - engine_conf->code_size = 3; - engine_conf->nsteps = mtd->writesize / base->ecc.user_conf.step_size; + engine_conf = base->ecc.ctx.priv; if (chip->ecc.options & NAND_ECC_SOFT_HAMMING_SM_ORDER) engine_conf->sm_order = true; - base->ecc.ctx.priv = engine_conf; - + chip->ecc.size = base->ecc.ctx.conf.step_size; + chip->ecc.strength = base->ecc.ctx.conf.strength; + chip->ecc.total = base->ecc.ctx.conf.total; chip->ecc.steps = engine_conf->nsteps; chip->ecc.bytes = engine_conf->code_size; @@ -4905,7 +4898,7 @@ void rawnand_sw_hamming_cleanup(struct nand_chip *chip) { struct nand_device *base = &chip->base; - kfree(base->ecc.ctx.priv); + ecc_sw_hamming_cleanup_ctx(base); } EXPORT_SYMBOL(rawnand_sw_hamming_cleanup); @@ -5369,7 +5362,8 @@ static int nand_scan_tail(struct nand_chip *chip) * If no default placement scheme is given, select an appropriate one. */ if (!mtd->ooblayout && - !(ecc->mode == NAND_ECC_SOFT && ecc->algo == NAND_ECC_BCH)) { + !(ecc->mode == NAND_ECC_SOFT && ecc->algo == NAND_ECC_BCH) && + !(ecc->mode == NAND_ECC_SOFT && ecc->algo == NAND_ECC_HAMMING)) { switch (mtd->oobsize) { case 8: case 16: diff --git a/include/linux/mtd/nand-sw-hamming-engine.h b/include/linux/mtd/nand-sw-hamming-engine.h index 378ba46f7e1d..8df36d189482 100644 --- a/include/linux/mtd/nand-sw-hamming-engine.h +++ b/include/linux/mtd/nand-sw-hamming-engine.h @@ -32,6 +32,8 @@ struct ecc_sw_hamming_conf { #if IS_ENABLED(CONFIG_MTD_NAND_ECC_SW_HAMMING) +int ecc_sw_hamming_init_ctx(struct nand_device *nand); +void ecc_sw_hamming_cleanup_ctx(struct nand_device *nand); int __ecc_sw_hamming_calculate(const unsigned char *buf, unsigned int step_size, unsigned char *code, bool sm_order); @@ -43,9 +45,17 @@ int __ecc_sw_hamming_correct(unsigned char *buf, unsigned char *read_ecc, bool sm_order); int ecc_sw_hamming_correct(struct nand_device *nand, unsigned char *buf, unsigned char *read_ecc, unsigned char *calc_ecc); +struct nand_ecc_engine *ecc_sw_hamming_get_engine(void); #else /* !CONFIG_MTD_NAND_ECC_SW_HAMMING */ +static inline int ecc_sw_hamming_init_ctx(struct nand_device *nand) +{ + return -ENOTSUPP; +} + +static inline void ecc_sw_hamming_cleanup_ctx(struct nand_device *nand) {} + static inline int __ecc_sw_hamming_calculate(const unsigned char *buf, unsigned int step_size, unsigned char *code, @@ -78,6 +88,11 @@ static inline int ecc_sw_hamming_correct(struct nand_device *nand, return -ENOTSUPP; } +static inline struct nand_ecc_engine *ecc_sw_hamming_get_engine(void) +{ + return NULL; +} + #endif /* CONFIG_MTD_NAND_ECC_SW_HAMMING */ #endif /* __MTD_NAND_ECC_SW_HAMMING_H__ */ -- 2.19.1 ______________________________________________________ Linux MTD discussion mailing list http://lists.infradead.org/mailman/listinfo/linux-mtd/