From patchwork Sun Mar 29 12:13:17 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Richard Weinberger X-Patchwork-Id: 455753 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from bombadil.infradead.org (bombadil.infradead.org [IPv6:2001:1868:205::9]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id A7BA614012F for ; Sun, 29 Mar 2015 23:15:45 +1100 (AEDT) Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1YcC6T-0003YB-UF; Sun, 29 Mar 2015 12:14:09 +0000 Received: from mail.sigma-star.at ([95.130.255.111]) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1YcC69-0003RZ-KC for linux-mtd@lists.infradead.org; Sun, 29 Mar 2015 12:13:52 +0000 Received: from localhost (localhost.localdomain [127.0.0.1]) by mail.sigma-star.at (Postfix) with ESMTP id 8DE6024F8002; Sun, 29 Mar 2015 14:13:24 +0200 (CEST) X-Virus-Scanned: amavisd-new at mail.sigma-star.at Received: from azrael.upc.at (chello213047235169.tirol.surfer.at [213.47.235.169]) by mail.sigma-star.at (Postfix) with ESMTPSA id 3064324F8001; Sun, 29 Mar 2015 14:13:23 +0200 (CEST) From: Richard Weinberger To: linux-mtd@lists.infradead.org Subject: [PATCH 4/4] UBI: Implement bitrot checking Date: Sun, 29 Mar 2015 14:13:17 +0200 Message-Id: <1427631197-23610-5-git-send-email-richard@nod.at> X-Mailer: git-send-email 1.8.4.5 In-Reply-To: <1427631197-23610-1-git-send-email-richard@nod.at> References: <1427631197-23610-1-git-send-email-richard@nod.at> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20150329_051350_142217_21AF162B X-CRM114-Status: GOOD ( 22.82 ) X-Spam-Score: -0.0 (/) X-Spam-Report: SpamAssassin version 3.4.0 on bombadil.infradead.org summary: Content analysis details: (-0.0 points) pts rule name description ---- ---------------------- -------------------------------------------------- -0.0 SPF_PASS SPF: sender matches SPF record Cc: Richard Weinberger , boris.brezillon@free-electrons.com, linux-kernel@vger.kernel.org, dedekind1@gmail.com X-BeenThere: linux-mtd@lists.infradead.org X-Mailman-Version: 2.1.18-1 Precedence: list List-Id: Linux MTD discussion mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: "linux-mtd" Errors-To: linux-mtd-bounces+incoming=patchwork.ozlabs.org@lists.infradead.org This patch implements bitrot checking for UBI. ubi_wl_trigger_bitrot_check() triggers a re-read of every PEB. If a bitflip is detected PEBs in use will get scrubbed and free ones erased. Signed-off-by: Richard Weinberger Reviewed-by: David Oberhollenzer Reviewed-by: Andrea Scian --- drivers/mtd/ubi/build.c | 39 +++++++++++++ drivers/mtd/ubi/ubi.h | 4 ++ drivers/mtd/ubi/wl.c | 146 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 189 insertions(+) diff --git a/drivers/mtd/ubi/build.c b/drivers/mtd/ubi/build.c index 9690cf9..f58330b 100644 --- a/drivers/mtd/ubi/build.c +++ b/drivers/mtd/ubi/build.c @@ -118,6 +118,9 @@ static struct class_attribute ubi_version = static ssize_t dev_attribute_show(struct device *dev, struct device_attribute *attr, char *buf); +static ssize_t trigger_bitrot_check(struct device *dev, + struct device_attribute *mattr, + const char *data, size_t count); /* UBI device attributes (correspond to files in '//class/ubi/ubiX') */ static struct device_attribute dev_eraseblock_size = @@ -142,6 +145,8 @@ static struct device_attribute dev_bgt_enabled = __ATTR(bgt_enabled, S_IRUGO, dev_attribute_show, NULL); static struct device_attribute dev_mtd_num = __ATTR(mtd_num, S_IRUGO, dev_attribute_show, NULL); +static struct device_attribute dev_trigger_bitrot_check = + __ATTR(trigger_bitrot_check, S_IWUSR, NULL, trigger_bitrot_check); /** * ubi_volume_notify - send a volume change notification. @@ -334,6 +339,36 @@ int ubi_major2num(int major) return ubi_num; } +/* "Store" method for file '//class/ubi/ubiX/trigger_bitrot_check' */ +static ssize_t trigger_bitrot_check(struct device *dev, + struct device_attribute *mattr, + const char *data, size_t count) +{ + struct ubi_device *ubi; + int ret; + + ubi = container_of(dev, struct ubi_device, dev); + ubi = ubi_get_device(ubi->ubi_num); + if (!ubi) { + ret = -ENODEV; + goto out; + } + + if (atomic_inc_return(&ubi->bit_rot_work) != 1) { + ret = -EBUSY; + goto out_dec; + } + + ubi_wl_trigger_bitrot_check(ubi); + ret = count; + +out_dec: + atomic_dec(&ubi->bit_rot_work); +out: + ubi_put_device(ubi); + return ret; +} + /* "Show" method for files in '//class/ubi/ubiX/' */ static ssize_t dev_attribute_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -445,6 +480,9 @@ static int ubi_sysfs_init(struct ubi_device *ubi, int *ref) if (err) return err; err = device_create_file(&ubi->dev, &dev_mtd_num); + if (err) + return err; + err = device_create_file(&ubi->dev, &dev_trigger_bitrot_check); return err; } @@ -465,6 +503,7 @@ static void ubi_sysfs_close(struct ubi_device *ubi) device_remove_file(&ubi->dev, &dev_total_eraseblocks); device_remove_file(&ubi->dev, &dev_avail_eraseblocks); device_remove_file(&ubi->dev, &dev_eraseblock_size); + device_remove_file(&ubi->dev, &dev_trigger_bitrot_check); device_unregister(&ubi->dev); } diff --git a/drivers/mtd/ubi/ubi.h b/drivers/mtd/ubi/ubi.h index cb3dcd0..da88cd8 100644 --- a/drivers/mtd/ubi/ubi.h +++ b/drivers/mtd/ubi/ubi.h @@ -39,6 +39,7 @@ #include #include #include +#include #include #include "ubi-media.h" @@ -464,6 +465,7 @@ struct ubi_debug_info { * @bgt_thread: background thread description object * @thread_enabled: if the background thread is enabled * @bgt_name: background thread name + * @bit_rot_work: non-zero if a bit rot check is running * * @flash_size: underlying MTD device size (in bytes) * @peb_count: count of physical eraseblocks on the MTD device @@ -567,6 +569,7 @@ struct ubi_device { struct task_struct *bgt_thread; int thread_enabled; char bgt_name[sizeof(UBI_BGT_NAME_PATTERN)+2]; + atomic_t bit_rot_work; /* I/O sub-system's stuff */ long long flash_size; @@ -834,6 +837,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); +void ubi_wl_trigger_bitrot_check(struct ubi_device *ubi); /* 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 9b11db9..784bb52 100644 --- a/drivers/mtd/ubi/wl.c +++ b/drivers/mtd/ubi/wl.c @@ -1447,6 +1447,150 @@ static void tree_destroy(struct ubi_device *ubi, struct rb_root *root) } /** + * bitrot_check_worker - physical eraseblock bitrot check worker function. + * @ubi: UBI device description object + * @wl_wrk: the work object + * @shutdown: non-zero if the worker has to free memory and exit + * + * This function reads a physical eraseblock and schedules scrubbing if + * bit flips are detected. + */ +static int bitrot_check_worker(struct ubi_device *ubi, struct ubi_work *wl_wrk, + int shutdown) +{ + struct ubi_wl_entry *e = wl_wrk->e; + int err; + + kfree(wl_wrk); + if (shutdown) { + dbg_wl("cancel bitrot check of PEB %d", e->pnum); + wl_entry_destroy(ubi, e); + return 0; + } + + mutex_lock(&ubi->buf_mutex); + err = ubi_io_read(ubi, ubi->peb_buf, e->pnum, 0, ubi->peb_size); + mutex_unlock(&ubi->buf_mutex); + if (err == UBI_IO_BITFLIPS) { + dbg_wl("found bitflips in PEB %d", e->pnum); + spin_lock(&ubi->wl_lock); + + 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); + spin_unlock(&ubi->wl_lock); + + wl_wrk = prepare_erase_work(e, -1, -1, 1); + if (IS_ERR(wl_wrk)) { + err = PTR_ERR(wl_wrk); + goto out; + } + + __schedule_ubi_work(ubi, wl_wrk); + err = 0; + } + /* + * e is target of a move operation, all we can do is kicking + * wear leveling such that we can catch it later or wear + * leveling itself scrubbs the PEB. + */ + else if (ubi->move_to == e || ubi->move_from == e) { + spin_unlock(&ubi->wl_lock); + + err = ensure_wear_leveling(ubi, 1); + } + /* + * e is member of a fastmap pool. We are not allowed to + * remove it from that pool as the on-flash fastmap data + * structure refers to it. Let's schedule a new fastmap write + * such that the said PEB can get released. + */ + else { + ubi_schedule_fm_work(ubi); + spin_unlock(&ubi->wl_lock); + + err = 0; + } + } + else { + /* + * Ignore read errors as we return only work related errors. + * Read errors will be logged by ubi_io_read(). + */ + err = 0; + } + +out: + atomic_dec(&ubi->bit_rot_work); + ubi_assert(atomic_read(&ubi->bit_rot_work) >= 0); + return err; +} + +/** + * schedule_bitrot_check - schedule a bitrot check work. + * @ubi: UBI device description object + * @e: the WL entry of the physical eraseblock to check + * + * This function returns zero in case of success and a %-ENOMEM in case of + * failure. + */ +static int schedule_bitrot_check(struct ubi_device *ubi, + struct ubi_wl_entry *e) +{ + struct ubi_work *wl_wrk; + + ubi_assert(e); + + dbg_wl("schedule bitrot check of PEB %d", e->pnum); + + wl_wrk = kmalloc(sizeof(struct ubi_work), GFP_NOFS); + if (!wl_wrk) + return -ENOMEM; + + wl_wrk->func = &bitrot_check_worker; + wl_wrk->e = e; + + schedule_ubi_work(ubi, wl_wrk); + return 0; +} + +/** + * ubi_wl_trigger_bitrot_check - triggers a re-read of all physical erase + * blocks. + * @ubi: UBI device description object + */ +void ubi_wl_trigger_bitrot_check(struct ubi_device *ubi) +{ + int i; + struct ubi_wl_entry *e; + + ubi_msg(ubi, "Running a full read check"); + + for (i = 0; i < ubi->peb_count; i++) { + spin_lock(&ubi->wl_lock); + e = ubi->lookuptbl[i]; + spin_unlock(&ubi->wl_lock); + if (e) { + atomic_inc(&ubi->bit_rot_work); + schedule_bitrot_check(ubi, e); + } + } +} + +/** * ubi_thread - UBI background thread. * @u: the UBI device description object pointer */ @@ -1646,6 +1790,8 @@ int ubi_wl_init(struct ubi_device *ubi, struct ubi_attach_info *ai) ubi->avail_pebs -= reserved_pebs; ubi->rsvd_pebs += reserved_pebs; + atomic_set(&ubi->bit_rot_work, 0); + /* Schedule wear-leveling if needed */ err = ensure_wear_leveling(ubi, 0); if (err)