From b32b78f89221ee26ae60f9a59e71b5ec28ba5fe2 Mon Sep 17 00:00:00 2001 From: Richard Weinberger Date: Wed, 7 Nov 2018 23:16:18 +0100 Subject: ubi: Introduce in_pq() This function works like in_wl_tree() but checks whether an ubi_wl_entry is currently in the protection queue. We need this function to query the current state of an ubi_wl_entry. Signed-off-by: Richard Weinberger --- drivers/mtd/ubi/wl.c | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/ubi/wl.c b/drivers/mtd/ubi/wl.c index 6f2ac865ff05..ca1b31385eb5 100644 --- a/drivers/mtd/ubi/wl.c +++ b/drivers/mtd/ubi/wl.c @@ -277,6 +277,27 @@ static int in_wl_tree(struct ubi_wl_entry *e, struct rb_root *root) return 0; } +/** + * in_pq - check if a wear-leveling entry is present in the protection queue. + * @ubi: UBI device description object + * @e: the wear-leveling entry to check + * + * This function returns non-zero if @e is in the protection queue and zero + * if it is not. + */ +static inline int in_pq(const struct ubi_device *ubi, struct ubi_wl_entry *e) +{ + struct ubi_wl_entry *p; + int i; + + for (i = 0; i < UBI_PROT_QUEUE_LEN; ++i) + list_for_each_entry(p, &ubi->pq[i], u.list) + if (p == e) + return 1; + + return 0; +} + /** * prot_queue_add - add physical eraseblock to the protection queue. * @ubi: UBI device description object @@ -1848,16 +1869,11 @@ static int self_check_in_wl_tree(const struct ubi_device *ubi, static int self_check_in_pq(const struct ubi_device *ubi, struct ubi_wl_entry *e) { - struct ubi_wl_entry *p; - int i; - if (!ubi_dbg_chk_gen(ubi)) return 0; - for (i = 0; i < UBI_PROT_QUEUE_LEN; ++i) - list_for_each_entry(p, &ubi->pq[i], u.list) - if (p == e) - return 0; + if (in_pq(ubi, e)) + return 0; ubi_err(ubi, "self-check failed for PEB %d, EC %d, Protect queue", e->pnum, e->ec); -- cgit From 663586c0a8929db81e617c775823efb9d65f2bc2 Mon Sep 17 00:00:00 2001 From: Richard Weinberger Date: Wed, 7 Nov 2018 23:16:19 +0100 Subject: ubi: Expose the bitrot interface Using UBI_IOCRPEB and UBI_IOCSPEB userspace can force reading and scrubbing of PEBs. In case of bitflips UBI will automatically take action and move data to a different PEB. This interface allows a daemon to foster your NAND. Signed-off-by: Richard Weinberger --- drivers/mtd/ubi/cdev.c | 30 +++++++++++ drivers/mtd/ubi/ubi.h | 1 + drivers/mtd/ubi/wl.c | 144 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 175 insertions(+) (limited to 'drivers/mtd') diff --git a/drivers/mtd/ubi/cdev.c b/drivers/mtd/ubi/cdev.c index 22547d7a84ea..947a8adbc799 100644 --- a/drivers/mtd/ubi/cdev.c +++ b/drivers/mtd/ubi/cdev.c @@ -974,6 +974,36 @@ static long ubi_cdev_ioctl(struct file *file, unsigned int cmd, break; } + /* Check a specific PEB for bitflips and scrub it if needed */ + case UBI_IOCRPEB: + { + int pnum; + + err = get_user(pnum, (__user int32_t *)argp); + if (err) { + err = -EFAULT; + break; + } + + err = ubi_bitflip_check(ubi, pnum, 0); + break; + } + + /* Force scrubbing for a specific PEB */ + case UBI_IOCSPEB: + { + int pnum; + + err = get_user(pnum, (__user int32_t *)argp); + if (err) { + err = -EFAULT; + break; + } + + err = ubi_bitflip_check(ubi, pnum, 1); + break; + } + default: err = -ENOTTY; break; diff --git a/drivers/mtd/ubi/ubi.h b/drivers/mtd/ubi/ubi.h index d47b9e436e67..a1b9e764d489 100644 --- a/drivers/mtd/ubi/ubi.h +++ b/drivers/mtd/ubi/ubi.h @@ -929,6 +929,7 @@ int ubi_wl_put_fm_peb(struct ubi_device *ubi, struct ubi_wl_entry *used_e, int ubi_is_erase_work(struct ubi_work *wrk); void ubi_refill_pools(struct ubi_device *ubi); int ubi_ensure_anchor_pebs(struct ubi_device *ubi); +int ubi_bitflip_check(struct ubi_device *ubi, int pnum, int force_scrub); /* io.c */ int ubi_io_read(const struct ubi_device *ubi, void *buf, int pnum, int offset, diff --git a/drivers/mtd/ubi/wl.c b/drivers/mtd/ubi/wl.c index ca1b31385eb5..40f838d54b0f 100644 --- a/drivers/mtd/ubi/wl.c +++ b/drivers/mtd/ubi/wl.c @@ -1440,6 +1440,150 @@ int ubi_wl_flush(struct ubi_device *ubi, int vol_id, int lnum) return err; } +static bool scrub_possible(struct ubi_device *ubi, struct ubi_wl_entry *e) +{ + if (in_wl_tree(e, &ubi->scrub)) + return false; + else if (in_wl_tree(e, &ubi->erroneous)) + return false; + else if (ubi->move_from == e) + return false; + else if (ubi->move_to == e) + return false; + + return true; +} + +/** + * ubi_bitflip_check - Check an eraseblock for bitflips and scrub it if needed. + * @ubi: UBI device description object + * @pnum: the physical eraseblock to schedule + * @force: dont't read the block, assume bitflips happened and take action. + * + * This function reads the given eraseblock and checks if bitflips occured. + * In case of bitflips, the eraseblock is scheduled for scrubbing. + * If scrubbing is forced with @force, the eraseblock is not read, + * but scheduled for scrubbing right away. + * + * Returns: + * %EINVAL, PEB is out of range + * %ENOENT, PEB is no longer used by UBI + * %EBUSY, PEB cannot be checked now or a check is currently running on it + * %EAGAIN, bit flips happened but scrubbing is currently not possible + * %EUCLEAN, bit flips happened and PEB is scheduled for scrubbing + * %0, no bit flips detected + */ +int ubi_bitflip_check(struct ubi_device *ubi, int pnum, int force) +{ + int err; + struct ubi_wl_entry *e; + + if (pnum < 0 || pnum >= ubi->peb_count) { + err = -EINVAL; + goto out; + } + + /* + * Pause all parallel work, otherwise it can happen that the + * erase worker frees a wl entry under us. + */ + down_write(&ubi->work_sem); + + /* + * Make sure that the wl entry does not change state while + * inspecting it. + */ + spin_lock(&ubi->wl_lock); + e = ubi->lookuptbl[pnum]; + if (!e) { + spin_unlock(&ubi->wl_lock); + err = -ENOENT; + goto out_resume; + } + + /* + * Does it make sense to check this PEB? + */ + if (!scrub_possible(ubi, e)) { + spin_unlock(&ubi->wl_lock); + err = -EBUSY; + goto out_resume; + } + spin_unlock(&ubi->wl_lock); + + if (!force) { + mutex_lock(&ubi->buf_mutex); + err = ubi_io_read(ubi, ubi->peb_buf, pnum, 0, ubi->peb_size); + mutex_unlock(&ubi->buf_mutex); + } + + if (err == UBI_IO_BITFLIPS || force) { + /* + * Okay, bit flip happened, let's figure out what we can do. + */ + spin_lock(&ubi->wl_lock); + + /* + * Recheck. We released wl_lock, UBI might have killed the + * wl entry under us. + */ + e = ubi->lookuptbl[pnum]; + if (!e) { + spin_unlock(&ubi->wl_lock); + err = -ENOENT; + goto out_resume; + } + + /* + * Need to re-check state + */ + if (!scrub_possible(ubi, e)) { + spin_unlock(&ubi->wl_lock); + err = -EBUSY; + goto out_resume; + } + + if (in_pq(ubi, e)) { + prot_queue_del(ubi, e->pnum); + wl_tree_add(e, &ubi->scrub); + spin_unlock(&ubi->wl_lock); + + err = ensure_wear_leveling(ubi, 1); + } else if (in_wl_tree(e, &ubi->used)) { + rb_erase(&e->u.rb, &ubi->used); + wl_tree_add(e, &ubi->scrub); + spin_unlock(&ubi->wl_lock); + + err = ensure_wear_leveling(ubi, 1); + } else if (in_wl_tree(e, &ubi->free)) { + rb_erase(&e->u.rb, &ubi->free); + ubi->free_count--; + spin_unlock(&ubi->wl_lock); + + /* + * This PEB is empty we can schedule it for + * erasure right away. No wear leveling needed. + */ + err = schedule_erase(ubi, e, UBI_UNKNOWN, UBI_UNKNOWN, + force ? 0 : 1, true); + } else { + spin_unlock(&ubi->wl_lock); + err = -EAGAIN; + } + + if (!err && !force) + err = -EUCLEAN; + } else { + err = 0; + } + +out_resume: + up_write(&ubi->work_sem); +out: + + return err; +} + /** * tree_destroy - destroy an RB-tree. * @ubi: UBI device description object -- cgit From 5578e48e5c0bf4684e68ae08caa2293bfb3f5307 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 28 Feb 2019 08:35:51 +0300 Subject: ubi: wl: Silence uninitialized variable warning This condition needs to be fipped around because "err" is uninitialized when "force" is set. The Smatch static analysis tool complains and UBsan will also complain at runtime. Fixes: 663586c0a892 ("ubi: Expose the bitrot interface") Signed-off-by: Dan Carpenter Reviewed-by: Nathan Chancellor Tested-by: Nathan Chancellor Signed-off-by: Richard Weinberger --- drivers/mtd/ubi/wl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/mtd') diff --git a/drivers/mtd/ubi/wl.c b/drivers/mtd/ubi/wl.c index 40f838d54b0f..2709dc02fc24 100644 --- a/drivers/mtd/ubi/wl.c +++ b/drivers/mtd/ubi/wl.c @@ -1517,7 +1517,7 @@ int ubi_bitflip_check(struct ubi_device *ubi, int pnum, int force) mutex_unlock(&ubi->buf_mutex); } - if (err == UBI_IO_BITFLIPS || force) { + if (force || err == UBI_IO_BITFLIPS) { /* * Okay, bit flip happened, let's figure out what we can do. */ -- cgit