diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig index ea1be55..db9c287 100644 --- a/drivers/mtd/Kconfig +++ b/drivers/mtd/Kconfig @@ -30,6 +30,15 @@ needs driver support, currently only the cfi-flash driver supports concatenating MTD devices. +comment "MTD debug options" + +config MTD_PEB_DEBUG + bool "MTD PEB debug" + help + When enabled the MTD PEB API will emulate bitflips. Random read + operations will return that bits are flipped. The MTD PEB API + is used by UBI and ubiformat + source "drivers/mtd/devices/Kconfig" source "drivers/mtd/nor/Kconfig" source "drivers/mtd/nand/Kconfig" diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile index d3ae7fc..bfac8eb 100644 --- a/drivers/mtd/Makefile +++ b/drivers/mtd/Makefile @@ -3,7 +3,7 @@ obj-$(CONFIG_MTD_SPI_NOR) += spi-nor/ obj-$(CONFIG_MTD_UBI) += ubi/ obj-y += devices/ -obj-$(CONFIG_MTD) += core.o partition.o +obj-$(CONFIG_MTD) += core.o partition.o peb.o obj-$(CONFIG_MTD_OOB_DEVICE) += mtdoob.o obj-$(CONFIG_MTD_RAW_DEVICE) += mtdraw.o obj-$(CONFIG_MTD_CONCAT) += mtdconcat.o diff --git a/drivers/mtd/peb.c b/drivers/mtd/peb.c new file mode 100644 index 0000000..5cf2907 --- /dev/null +++ b/drivers/mtd/peb.c @@ -0,0 +1,537 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; version 2. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MTD_IO_RETRIES 3 + +static int __mtd_peb_chk_io; + +static int mtd_peb_chk_io(void) +{ + if (!IS_ENABLED(CONFIG_MTD_PEB_DEBUG)) + return 0; + + if (!__mtd_peb_chk_io) + return 0; + + return 1; +} + +static u32 __mtd_peb_emulate_bitflip; + +static int mtd_peb_emulate_bitflip(void) +{ + if (!IS_ENABLED(CONFIG_MTD_PEB_DEBUG)) + return 0; + + if (!__mtd_peb_emulate_bitflip) + return 0; + + return !(random32() % __mtd_peb_emulate_bitflip); +} + +static u32 __mtd_peb_emulate_write_failure; + +static int mtd_peb_emulate_write_failure(void) +{ + if (!IS_ENABLED(CONFIG_MTD_PEB_DEBUG)) + return 0; + + if (!__mtd_peb_emulate_write_failure) + return 0; + + return !(random32() % __mtd_peb_emulate_write_failure); +} + +static u32 __mtd_peb_emulate_erase_failures; + +static int mtd_peb_emulate_erase_failure(void) +{ + if (!IS_ENABLED(CONFIG_MTD_PEB_DEBUG)) + return 0; + + if (!__mtd_peb_emulate_erase_failures) + return 0; + + return !(random32() % __mtd_peb_emulate_erase_failures); +} + +#ifdef CONFIG_MTD_PEB_DEBUG +static int mtd_peb_debug_init(void) +{ + globalvar_add_simple_int("mtd_peb.mtd_peb_emulate_bitflip", + &__mtd_peb_emulate_bitflip, "%u"); + globalvar_add_simple_int("mtd_peb.mtd_peb_emulate_write_failure", + &__mtd_peb_emulate_write_failure, "%u"); + globalvar_add_simple_int("mtd_peb.mtd_peb_emulate_erase_failures", + &__mtd_peb_emulate_erase_failures, "%u"); + globalvar_add_simple_bool("mtd_peb.mtd_peb_chk_io", + &__mtd_peb_chk_io); + return 0; +} +device_initcall(mtd_peb_debug_init); + +BAREBOX_MAGICVAR_NAMED(global_mtd_peb_emulate_bitflip, + global.mtd_peb.emulate_bitflip, + "random bitflips, on average every #nth access returns -EUCLEAN"); +BAREBOX_MAGICVAR_NAMED(global_mtd_peb_emulate_write_failure, + global.mtd_peb.emulate_write_failure, + "random write failures, on average every #nth access returns write failure"); +BAREBOX_MAGICVAR_NAMED(global_mtd_peb_emulate_erase_failures, + global.mtd_peb.emulate_erase_failures, + "random erase failures, on average every #nth access returns erase failure"); +BAREBOX_MAGICVAR_NAMED(global_mtd_peb_chk_io, + global.mtd_peb.chk_io, + "If true, written data will be verified"); + +#endif + +static int mtd_peb_valid(struct mtd_info *mtd, int pnum) +{ + if (pnum < 0) + return false; + + if ((uint64_t)pnum * mtd->erasesize >= mtd->size) + return false; + + if (mtd->numeraseregions) + return false; + + return true; +} + +/** + * mtd_num_pebs - return number of PEBs for this device + * @mtd: mtd device + * + * This function returns the number of physical erase blocks this device + * has. + */ +int mtd_num_pebs(struct mtd_info *mtd) +{ + return mtd_div_by_eb(mtd->size, mtd); +} + +/** + * mtd_peb_mark_bad - mark a physical eraseblock as bad + * @mtd: mtd device + * @pnum: The number of the block + * + * This function marks a physical eraseblock as bad. + */ +int mtd_peb_mark_bad(struct mtd_info *mtd, int pnum) +{ + return mtd_block_markbad(mtd, (loff_t)pnum * mtd->erasesize); +} + +/** + * mtd_peb_is_bad - test if a physical eraseblock is bad + * @mtd: mtd device + * @pnum: The number of the block + * + * This function tests if a physical eraseblock is bad. Returns + * 0 if it is good, 1 if it is bad or a negative error value if the + * block is invalid + */ +int mtd_peb_is_bad(struct mtd_info *mtd, int pnum) +{ + if (!mtd_peb_valid(mtd, pnum)) + return -EINVAL; + + return mtd_block_isbad(mtd, (loff_t)pnum * mtd->erasesize); +} + +/** + * mtd_peb_read - read data from a physical eraseblock. + * @mtd: mtd device + * @buf: buffer where to store the read data + * @pnum: physical eraseblock number to read from + * @offset: offset within the physical eraseblock from where to read + * @len: how many bytes to read + * + * This function reads data from offset @offset of physical eraseblock @pnum + * and stores the read data in the @buf buffer. The following return codes are + * possible: + * + * o %0 if all the requested data were successfully read; + * o %-EUCLEAN if all the requested data were successfully read, but + * correctable bit-flips were detected; this is harmless but may indicate + * that this eraseblock may become bad soon (but do not have to); + * o %-EBADMSG if the MTD subsystem reported about data integrity problems, for + * example it can be an ECC error in case of NAND; this most probably means + * that the data is corrupted; + * o %-EIO if some I/O error occurred; + * o other negative error codes in case of other errors. + */ +int mtd_peb_read(struct mtd_info *mtd, void *buf, int pnum, int offset, + int len) +{ + int err, retries = 0; + size_t read; + loff_t addr; + + if (!mtd_peb_valid(mtd, pnum)) + return -EINVAL; + if (offset < 0 || offset + len > mtd->erasesize) + return -EINVAL; + if (len <= 0) + return -EINVAL; + if (mtd_peb_is_bad(mtd, pnum)) + return -EINVAL; + + /* Deliberately corrupt the buffer */ + *((uint8_t *)buf) ^= 0xFF; + + addr = (loff_t)pnum * mtd->erasesize + offset; +retry: + err = mtd_read(mtd, addr, len, &read, buf); + if (err) { + const char *errstr = mtd_is_eccerr(err) ? " (ECC error)" : ""; + + if (mtd_is_bitflip(err)) { + /* + * -EUCLEAN is reported if there was a bit-flip which + * was corrected, so this is harmless. + * + * We do not report about it here unless debugging is + * enabled. A corresponding message will be printed + * later, when it is has been scrubbed. + */ + dev_dbg(&mtd->class_dev, "fixable bit-flip detected at PEB %d\n", pnum); + if (len != read) + return -EIO; + return -EUCLEAN; + } + + if (mtd_is_eccerr(err) && retries++ < MTD_IO_RETRIES) + goto retry; + + dev_err(&mtd->class_dev, "error %d%s while reading %d bytes from PEB %d:%d\n", + err, errstr, len, pnum, offset); + return err; + } + + if (len != read) + return -EIO; + + if (mtd_peb_emulate_bitflip()) + return -EUCLEAN; + + return 0; +} + +/** + * mtd_peb_check_all_ff - check that a region of flash is empty. + * @mtd: mtd device + * @pnum: the physical eraseblock number to check + * @offset: the starting offset within the physical eraseblock to check + * @len: the length of the region to check + * + * This function returns zero if only 0xFF bytes are present at offset + * @offset of the physical eraseblock @pnum, -EBADMSG if the buffer does + * not contain all 0xFF or other negative error codes when other errors + * occured + */ +int mtd_peb_check_all_ff(struct mtd_info *mtd, int pnum, int offset, int len, + int warn) +{ + size_t read; + int err; + void *buf; + + buf = malloc(len); + if (!buf) + return -ENOMEM; + + err = mtd_peb_read(mtd, buf, pnum, offset, len); + if (err && !mtd_is_bitflip(err)) { + dev_err(&mtd->class_dev, + "error %d while reading %d bytes from PEB %d:%d, read %zd bytes\n", + err, len, pnum, offset, read); + goto out; + } + + err = mtd_buf_all_ff(buf, len); + if (err == 0) { + if (warn) + dev_err(&mtd->class_dev, "all-ff check failed for PEB %d\n", + pnum); + err = -EBADMSG; + goto out; + } + + err = 0; + +out: + free(buf); + + return err; +} + +/** + * mtd_peb_verify - make sure write succeeded. + * @mtd: mtd device + * @buf: buffer with data which were written + * @pnum: physical eraseblock number the data were written to + * @offset: offset within the physical eraseblock the data were written to + * @len: how many bytes were written + * + * This functions reads data which were recently written and compares it with + * the original data buffer - the data have to match. Returns zero if the data + * match and a negative error code if not or in case of failure. + */ +int mtd_peb_verify(struct mtd_info *mtd, const void *buf, int pnum, + int offset, int len) +{ + int err, i; + size_t read; + void *buf1; + loff_t addr = (loff_t)pnum * mtd->erasesize + offset; + + buf1 = malloc(len); + if (!buf1) + return 0; + + err = mtd_read(mtd, addr, len, &read, buf1); + if (err && !mtd_is_bitflip(err)) + goto out_free; + + for (i = 0; i < len; i++) { + uint8_t c = ((uint8_t *)buf)[i]; + uint8_t c1 = ((uint8_t *)buf1)[i]; + int dump_len; + + if (c == c1) + continue; + + dev_err(&mtd->class_dev, "self-check failed for PEB %d:%d, len %d\n", + pnum, offset, len); + dev_info(&mtd->class_dev, "data differs at position %d\n", i); + dump_len = max_t(int, 128, len - i); +#ifdef DEBUG + dev_info(&mtd->class_dev, "hex dump of the original buffer from %d to %d\n", + i, i + dump_len); + memory_display(buf + i, i, dump_len, 4, 0); + dev_info(&mtd->class_dev, "hex dump of the read buffer from %d to %d\n", + i, i + dump_len); + memory_display(buf1 + i, i, dump_len, 4, 0); + dump_stack(); +#endif + err = -EBADMSG; + goto out_free; + } + + err = 0; + +out_free: + free(buf1); + return err; +} + +/** + * mtd_peb_write - write data to a physical eraseblock. + * @mtd: mtd device + * @buf: buffer with the data to write + * @pnum: physical eraseblock number to write to + * @offset: offset within the physical eraseblock where to write + * @len: how many bytes to write + * + * This function writes @len bytes of data from buffer @buf to offset @offset + * of physical eraseblock @pnum. If all the data was successfully written, + * zero is returned. If an error occurred, this function returns a negative + * error code. If %-EBADMSG is returned, the physical eraseblock most probably + * went bad. + * + * Note, in case of an error, it is possible that something was still written + * to the flash media, but may be some garbage. + */ +int mtd_peb_write(struct mtd_info *mtd, const void *buf, int pnum, int offset, + int len) +{ + int err; + size_t written; + loff_t addr; + + dev_dbg(&mtd->class_dev, "write %d bytes to PEB %d:%d\n", len, pnum, offset); + + if (!mtd_peb_valid(mtd, pnum)) + return -EINVAL; + if (offset < 0 || offset + len > mtd->erasesize) + return -EINVAL; + if (len <= 0) + return -EINVAL; + if (len % (mtd->writesize >> mtd->subpage_sft)) + return -EINVAL; + if (mtd_peb_is_bad(mtd, pnum)) + return -EINVAL; + + if (mtd_peb_emulate_write_failure()) { + dev_err(&mtd->class_dev, "Cannot write %d bytes to PEB %d:%d (emulated)\n", + len, pnum, offset); + return -EIO; + } + + if (mtd_peb_chk_io()) { + /* The area we are writing to has to contain all 0xFF bytes */ + err = mtd_peb_check_all_ff(mtd, pnum, offset, len, 1); + if (err) + return err; + } + + addr = (loff_t)pnum * mtd->erasesize + offset; + err = mtd_write(mtd, addr, len, &written, buf); + if (err) { + dev_err(&mtd->class_dev, "error %d while writing %d bytes to PEB %d:%d, written %zd bytes\n", + err, len, pnum, offset, written); + } else { + if (written != len) + err = -EIO; + } + + if (!err && mtd_peb_chk_io()) + err = mtd_peb_verify(mtd, buf, pnum, offset, len); + + return err; +} + +/** + * mtd_peb_erase - erase a physical eraseblock. + * @mtd: mtd device + * @pnum: physical eraseblock number to erase + * + * This function erases physical eraseblock @pnum. + * + * This function returns 0 in case of success, %-EIO if the erasure failed, + * and other negative error codes in case of other errors. Note, %-EIO means + * that the physical eraseblock is bad. + */ +int mtd_peb_erase(struct mtd_info *mtd, int pnum) +{ + int ret; + struct erase_info ei = {}; + + dev_dbg(&mtd->class_dev, "erase PEB %d\n", pnum); + + if (!mtd_peb_valid(mtd, pnum)) + return -EINVAL; + + ei.mtd = mtd; + ei.addr = (loff_t)pnum * mtd->erasesize; + ei.len = mtd->erasesize; + + ret = mtd_erase(mtd, &ei); + if (ret) + return ret; + + if (mtd_peb_chk_io()) { + ret = mtd_peb_check_all_ff(mtd, pnum, 0, mtd->erasesize, 1); + if (ret == -EBADMSG) + ret = -EIO; + } + + if (mtd_peb_emulate_erase_failure()) { + dev_err(&mtd->class_dev, "cannot erase PEB %d (emulated)", pnum); + return -EIO; + } + + return 0; +} + +/* Patterns to write to a physical eraseblock when torturing it */ +static uint8_t patterns[] = {0xa5, 0x5a, 0x0}; + +/** + * mtd_peb_torture - test a supposedly bad physical eraseblock. + * @mtd: mtd device + * @pnum: the physical eraseblock number to test + * + * This function is a last resort when an eraseblock is assumed to be + * bad. It will write and check some patterns to the block. If the test + * is passed then this function will with the block freshly erased and + * the positive number returned indicaties how often the block has been + * erased during this test. + * If the block does not pass the test the block is marked as bad and + * -EIO is returned. + * Other negative errors are returned in case of other errors. + */ +int mtd_peb_torture(struct mtd_info *mtd, int pnum) +{ + int err, i, patt_count; + void *peb_buf; + + if (!mtd_peb_valid(mtd, pnum)) + return -EINVAL; + + peb_buf = malloc(mtd->erasesize); + if (!peb_buf) + return -ENOMEM; + + dev_dbg(&mtd->class_dev, "run torture test for PEB %d\n", pnum); + + patt_count = ARRAY_SIZE(patterns); + + for (i = 0; i < patt_count; i++) { + err = mtd_peb_erase(mtd, pnum); + if (err) + goto out; + + /* Make sure the PEB contains only 0xFF bytes after erasing */ + err = mtd_peb_check_all_ff(mtd, pnum, 0, mtd->writesize, 0); + if (err) + goto out; + + /* Write a pattern and check it */ + memset(peb_buf, patterns[i], mtd->erasesize); + err = mtd_peb_write(mtd, peb_buf, pnum, 0, mtd->erasesize); + if (err) + goto out; + + err = mtd_peb_verify(mtd, peb_buf, pnum, 0, mtd->erasesize); + if (err) + goto out; + } + + err = mtd_peb_erase(mtd, pnum); + if (err) + goto out; + + err = patt_count + 1; + dev_dbg(&mtd->class_dev, "PEB %d passed torture test, do not mark it as bad\n", + pnum); + +out: + if (err == -EUCLEAN || mtd_is_eccerr(err)) { + /* + * If a bit-flip or data integrity error was detected, the test + * has not passed because it happened on a freshly erased + * physical eraseblock which means something is wrong with it. + */ + dev_err(&mtd->class_dev, "read problems on freshly erased PEB %d, marking it bad\n", + pnum); + + mtd_peb_mark_bad(mtd, pnum); + + err = -EIO; + } + + free(peb_buf); + + return err; +} diff --git a/include/mtd/mtd-peb.h b/include/mtd/mtd-peb.h new file mode 100644 index 0000000..50ac9a5 --- /dev/null +++ b/include/mtd/mtd-peb.h @@ -0,0 +1,21 @@ +#ifndef __LINUX_MTD_MTDPEB_H +#define __LINUX_MTD_MTDPEB_H + +#include + +int mtd_peb_read(struct mtd_info *mtd, void *buf, int pnum, int offset, + int len); +int mtd_peb_write(struct mtd_info *mtd, const void *buf, int pnum, int offset, + int len); + +int mtd_peb_torture(struct mtd_info *mtd, int pnum); +int mtd_peb_erase(struct mtd_info *mtd, int pnum); +int mtd_peb_mark_bad(struct mtd_info *mtd, int pnum); +int mtd_peb_is_bad(struct mtd_info *mtd, int pnum); +int mtd_peb_check_all_ff(struct mtd_info *mtd, int pnum, int offset, int len, + int warn); +int mtd_peb_verify(struct mtd_info *mtd, const void *buf, int pnum, + int offset, int len); +int mtd_num_pebs(struct mtd_info *mtd); + +#endif /* __LINUX_MTD_MTDPEB_H */