From ec242d217db573db8ed83742806b09ea7c8121e5 Mon Sep 17 00:00:00 2001 From: Pradeep P V K Date: Tue, 11 May 2021 10:55:40 +0530 Subject: [PATCH] ubi: Add scrub_all support to UBI The data on NAND parts degrades overtime. This is known as data decay. This decay is accelarated by extreme temperatures. When data on nand is read over and over again this can also cause decay of data. This is known as read disturb and can degrade the data in the page/block that is read and the adjacent cells as well. This data degrade can be corrected to a certain degree by the nand driver/controller using the ECC but this is not sufficient specially in products designed to last years. The only way to combat the decay is to "refresh" the data by moving it to a new location. Add scrub_all support to UBI to facilitate the data refresh. Change-Id: Ifafb82fe4ddb7120277dcfbbff79b3e087ca344d Signed-off-by: Nikhilesh Reddy Signed-off-by: Pradeep P V K --- drivers/mtd/ubi/attach.c | 17 ++- drivers/mtd/ubi/block.c | 9 +- drivers/mtd/ubi/build.c | 111 +++++++++++++--- drivers/mtd/ubi/io.c | 11 ++ drivers/mtd/ubi/ubi.h | 14 +- drivers/mtd/ubi/vmt.c | 31 +++-- drivers/mtd/ubi/wl.c | 267 ++++++++++++++++++++++++++++++++++++++- 7 files changed, 422 insertions(+), 38 deletions(-) diff --git a/drivers/mtd/ubi/attach.c b/drivers/mtd/ubi/attach.c index 10b2459f8951..d1f036393798 100644 --- a/drivers/mtd/ubi/attach.c +++ b/drivers/mtd/ubi/attach.c @@ -821,9 +821,22 @@ struct ubi_ainf_peb *ubi_early_get_peb(struct ubi_device *ubi, int err = 0; struct ubi_ainf_peb *aeb, *tmp_aeb; - if (!list_empty(&ai->free)) { - aeb = list_entry(ai->free.next, struct ubi_ainf_peb, u.list); + list_for_each_entry_safe(aeb, tmp_aeb, &ai->free, u.list) { + list_del(&aeb->u.list); + if (aeb->ec == UBI_UNKNOWN) { + ubi_err(ubi, "PEB %d in freelist has unknown EC", + aeb->pnum); + aeb->ec = ai->mean_ec; + } + err = early_erase_peb(ubi, ai, aeb->pnum, aeb->ec+1); + if (err) { + ubi_err(ubi, "Erase failed for PEB %d in freelist", + aeb->pnum); + list_add(&aeb->u.list, &ai->erase); + continue; + } + aeb->ec += 1; dbg_bld("return free PEB %d, EC %d", aeb->pnum, aeb->ec); return aeb; } diff --git a/drivers/mtd/ubi/block.c b/drivers/mtd/ubi/block.c index e1a2ae21dfd3..ec476a5d0e37 100644 --- a/drivers/mtd/ubi/block.c +++ b/drivers/mtd/ubi/block.c @@ -121,7 +121,7 @@ static int __init ubiblock_set_param(const char *val, return -EINVAL; } - strcpy(buf, val); + strlcpy(buf, val, sizeof(buf)); /* Get rid of the final newline */ if (buf[len - 1] == '\n') @@ -141,12 +141,12 @@ static int __init ubiblock_set_param(const char *val, ret = kstrtoint(tokens[1], 10, ¶m->vol_id); if (ret < 0) { param->vol_id = -1; - strcpy(param->name, tokens[1]); + strlcpy(param->name, tokens[1], UBIBLOCK_PARAM_LEN+1); } } else { /* One parameter: must be device path */ - strcpy(param->name, tokens[0]); + strlcpy(param->name, tokens[0], UBIBLOCK_PARAM_LEN+1); param->ubi_num = -1; param->vol_id = -1; } @@ -412,7 +412,8 @@ int ubiblock_create(struct ubi_volume_info *vi) goto out_put_disk; } gd->private_data = dev; - sprintf(gd->disk_name, "ubiblock%d_%d", dev->ubi_num, dev->vol_id); + scnprintf(gd->disk_name, DISK_NAME_LEN, "ubiblock%d_%d", + dev->ubi_num, dev->vol_id); set_capacity(gd, disk_capacity); dev->gd = gd; diff --git a/drivers/mtd/ubi/build.c b/drivers/mtd/ubi/build.c index d636bbe214cb..971c3b7a51c3 100644 --- a/drivers/mtd/ubi/build.c +++ b/drivers/mtd/ubi/build.c @@ -95,7 +95,7 @@ static DEFINE_SPINLOCK(ubi_devices_lock); static ssize_t version_show(struct class *class, struct class_attribute *attr, char *buf) { - return sprintf(buf, "%d\n", UBI_VERSION); + return scnprintf(buf, sizeof(int), "%d\n", UBI_VERSION); } static CLASS_ATTR_RO(version); @@ -114,6 +114,9 @@ struct class ubi_class = { static ssize_t dev_attribute_show(struct device *dev, struct device_attribute *attr, char *buf); +static ssize_t dev_attribute_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count); /* UBI device attributes (correspond to files in '//class/ubi/ubiX') */ static struct device_attribute dev_eraseblock_size = @@ -140,6 +143,13 @@ static struct device_attribute dev_mtd_num = __ATTR(mtd_num, S_IRUGO, dev_attribute_show, NULL); static struct device_attribute dev_ro_mode = __ATTR(ro_mode, S_IRUGO, dev_attribute_show, NULL); +static struct device_attribute dev_mtd_trigger_scrub = + __ATTR(scrub_all, 0644, + dev_attribute_show, dev_attribute_store); +static struct device_attribute dev_mtd_max_scrub_sqnum = + __ATTR(scrub_max_sqnum, 0444, dev_attribute_show, NULL); +static struct device_attribute dev_mtd_min_scrub_sqnum = + __ATTR(scrub_min_sqnum, 0444, dev_attribute_show, NULL); /** * ubi_volume_notify - send a volume change notification. @@ -332,6 +342,17 @@ int ubi_major2num(int major) return ubi_num; } +static unsigned long long get_max_sqnum(struct ubi_device *ubi) +{ + unsigned long long max_sqnum; + + spin_lock(&ubi->ltree_lock); + max_sqnum = ubi->global_sqnum - 1; + spin_unlock(&ubi->ltree_lock); + + return max_sqnum; +} + /* "Show" method for files in '//class/ubi/ubiX/' */ static ssize_t dev_attribute_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -355,29 +376,50 @@ static ssize_t dev_attribute_show(struct device *dev, return -ENODEV; if (attr == &dev_eraseblock_size) - ret = sprintf(buf, "%d\n", ubi->leb_size); + ret = scnprintf(buf, sizeof(int), "%d\n", + ubi->leb_size); else if (attr == &dev_avail_eraseblocks) - ret = sprintf(buf, "%d\n", ubi->avail_pebs); + ret = scnprintf(buf, sizeof(int), "%d\n", + ubi->avail_pebs); else if (attr == &dev_total_eraseblocks) - ret = sprintf(buf, "%d\n", ubi->good_peb_count); + ret = scnprintf(buf, sizeof(int), "%d\n", + ubi->good_peb_count); else if (attr == &dev_volumes_count) - ret = sprintf(buf, "%d\n", ubi->vol_count - UBI_INT_VOL_COUNT); + ret = scnprintf(buf, sizeof(int), "%d\n", + ubi->vol_count - UBI_INT_VOL_COUNT); else if (attr == &dev_max_ec) - ret = sprintf(buf, "%d\n", ubi->max_ec); + ret = scnprintf(buf, sizeof(int), "%d\n", + ubi->max_ec); else if (attr == &dev_reserved_for_bad) - ret = sprintf(buf, "%d\n", ubi->beb_rsvd_pebs); + ret = scnprintf(buf, sizeof(int), "%d\n", + ubi->beb_rsvd_pebs); else if (attr == &dev_bad_peb_count) - ret = sprintf(buf, "%d\n", ubi->bad_peb_count); + ret = scnprintf(buf, sizeof(int), "%d\n", + ubi->bad_peb_count); else if (attr == &dev_max_vol_count) - ret = sprintf(buf, "%d\n", ubi->vtbl_slots); + ret = scnprintf(buf, sizeof(int), "%d\n", + ubi->vtbl_slots); else if (attr == &dev_min_io_size) - ret = sprintf(buf, "%d\n", ubi->min_io_size); + ret = scnprintf(buf, sizeof(int), "%d\n", + ubi->min_io_size); else if (attr == &dev_bgt_enabled) - ret = sprintf(buf, "%d\n", ubi->thread_enabled); + ret = scnprintf(buf, sizeof(int), "%d\n", + ubi->thread_enabled); else if (attr == &dev_mtd_num) - ret = sprintf(buf, "%d\n", ubi->mtd->index); + ret = scnprintf(buf, sizeof(int), "%d\n", + ubi->mtd->index); else if (attr == &dev_ro_mode) - ret = sprintf(buf, "%d\n", ubi->ro_mode); + ret = scnprintf(buf, sizeof(int), "%d\n", + ubi->ro_mode); + else if (attr == &dev_mtd_trigger_scrub) + ret = scnprintf(buf, sizeof(int), "%d\n", + atomic_read(&ubi->scrub_work_count)); + else if (attr == &dev_mtd_max_scrub_sqnum) + ret = scnprintf(buf, sizeof(unsigned long long), "%llu\n", + get_max_sqnum(ubi)); + else if (attr == &dev_mtd_min_scrub_sqnum) + ret = scnprintf(buf, sizeof(unsigned long long), "%llu\n", + ubi_wl_scrub_get_min_sqnum(ubi)); else ret = -EINVAL; @@ -398,10 +440,46 @@ static struct attribute *ubi_dev_attrs[] = { &dev_bgt_enabled.attr, &dev_mtd_num.attr, &dev_ro_mode.attr, + &dev_mtd_trigger_scrub.attr, + &dev_mtd_max_scrub_sqnum.attr, + &dev_mtd_min_scrub_sqnum.attr, NULL }; ATTRIBUTE_GROUPS(ubi_dev); +static ssize_t dev_attribute_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret = 0; + struct ubi_device *ubi; + unsigned long long scrub_sqnum; + + ubi = container_of(dev, struct ubi_device, dev); + ubi = ubi_get_device(ubi->ubi_num); + if (!ubi) + return -ENODEV; + + if (attr == &dev_mtd_trigger_scrub) { + if (kstrtoull(buf, 10, &scrub_sqnum)) { + ret = -EINVAL; + goto out; + } + if (!ubi->lookuptbl) { + pr_err("lookuptbl is null\n"); + ret = -ENOENT; + goto out; + } + ret = ubi_wl_scrub_all(ubi, scrub_sqnum); + if (ret == 0) + ret = count; + } + +out: + ubi_put_device(ubi); + return ret; +} + static void dev_release(struct device *dev) { struct ubi_device *ubi = container_of(dev, struct ubi_device, dev); @@ -438,7 +516,8 @@ static int uif_init(struct ubi_device *ubi) int i, err; dev_t dev; - sprintf(ubi->ubi_name, UBI_NAME_STR "%d", ubi->ubi_num); + scnprintf(ubi->ubi_name, sizeof(UBI_NAME_STR) + 5, + UBI_NAME_STR "%d", ubi->ubi_num); /* * Major numbers for the UBI character devices are allocated @@ -1376,7 +1455,7 @@ static int ubi_mtd_param_parse(const char *val, const struct kernel_param *kp) return 0; } - strcpy(buf, val); + strlcpy(buf, val, sizeof(buf)); /* Get rid of the final newline */ if (buf[len - 1] == '\n') @@ -1391,7 +1470,7 @@ static int ubi_mtd_param_parse(const char *val, const struct kernel_param *kp) } p = &mtd_dev_param[mtd_devs]; - strcpy(&p->name[0], tokens[0]); + strlcpy(&p->name[0], tokens[0], MTD_PARAM_LEN_MAX); token = tokens[1]; if (token) { diff --git a/drivers/mtd/ubi/io.c b/drivers/mtd/ubi/io.c index b57b84fb97d0..851d26e6f0e1 100644 --- a/drivers/mtd/ubi/io.c +++ b/drivers/mtd/ubi/io.c @@ -1056,6 +1056,15 @@ int ubi_io_write_vid_hdr(struct ubi_device *ubi, int pnum, dbg_io("write VID header to PEB %d", pnum); ubi_assert(pnum >= 0 && pnum < ubi->peb_count); + /* + * Re-erase the PEB before using it. This should minimize any issues + * from decay of charge in this block. + */ + if (ubi->wl_is_inited) { + err = ubi_wl_re_erase_peb(ubi, pnum); + if (err) + return err; + } err = self_check_peb_ec_hdr(ubi, pnum); if (err) return err; @@ -1074,6 +1083,8 @@ int ubi_io_write_vid_hdr(struct ubi_device *ubi, int pnum, err = ubi_io_write(ubi, p, pnum, ubi->vid_hdr_aloffset, ubi->vid_hdr_alsize); + if (!err && ubi->wl_is_inited) + ubi_wl_update_peb_sqnum(ubi, pnum, vid_hdr); return err; } diff --git a/drivers/mtd/ubi/ubi.h b/drivers/mtd/ubi/ubi.h index a173eb707bdd..911fd3987537 100644 --- a/drivers/mtd/ubi/ubi.h +++ b/drivers/mtd/ubi/ubi.h @@ -170,6 +170,8 @@ struct ubi_vid_io_buf { * @u.list: link in the protection queue * @ec: erase counter * @pnum: physical eraseblock number + * @tagged_scrub_all: if the entry is tagged for scrub all + * @sqnum: The sequence number of the vol header. * * This data structure is used in the WL sub-system. Each physical eraseblock * has a corresponding &struct wl_entry object which may be kept in different @@ -182,6 +184,8 @@ struct ubi_wl_entry { } u; int ec; int pnum; + unsigned int tagged_scrub_all:1; + unsigned long long sqnum; }; /** @@ -625,6 +629,9 @@ struct ubi_device { struct task_struct *bgt_thread; int thread_enabled; char bgt_name[sizeof(UBI_BGT_NAME_PATTERN)+2]; + bool scrub_in_progress; + atomic_t scrub_work_count; + int wl_is_inited; /* I/O sub-system's stuff */ long long flash_size; @@ -919,7 +926,12 @@ 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); - +ssize_t ubi_wl_scrub_all(struct ubi_device *ubi, + unsigned long long scrub_sqnum); +void ubi_wl_update_peb_sqnum(struct ubi_device *ubi, int pnum, + struct ubi_vid_hdr *vid_hdr); +unsigned long long ubi_wl_scrub_get_min_sqnum(struct ubi_device *ubi); +int ubi_wl_re_erase_peb(struct ubi_device *ubi, int pnum); /* io.c */ int ubi_io_read(const struct ubi_device *ubi, void *buf, int pnum, int offset, int len); diff --git a/drivers/mtd/ubi/vmt.c b/drivers/mtd/ubi/vmt.c index 139ee132bfbc..327f369d9fd0 100644 --- a/drivers/mtd/ubi/vmt.c +++ b/drivers/mtd/ubi/vmt.c @@ -54,7 +54,7 @@ static struct device_attribute attr_vol_upd_marker = static ssize_t vol_attribute_show(struct device *dev, struct device_attribute *attr, char *buf) { - int ret; + int ret, size; struct ubi_volume *vol = container_of(dev, struct ubi_volume, dev); struct ubi_device *ubi; @@ -73,27 +73,36 @@ static ssize_t vol_attribute_show(struct device *dev, spin_unlock(&ubi->volumes_lock); if (attr == &attr_vol_reserved_ebs) - ret = sprintf(buf, "%d\n", vol->reserved_pebs); + ret = scnprintf(buf, sizeof(int), "%d\n", vol->reserved_pebs); else if (attr == &attr_vol_type) { const char *tp; - if (vol->vol_type == UBI_DYNAMIC_VOLUME) + if (vol->vol_type == UBI_DYNAMIC_VOLUME) { tp = "dynamic"; - else + size = 8; + } else { tp = "static"; - ret = sprintf(buf, "%s\n", tp); + size = 7; + } + ret = scnprintf(buf, size, "%s\n", tp); } else if (attr == &attr_vol_name) - ret = sprintf(buf, "%s\n", vol->name); + ret = scnprintf(buf, vol->name_len + 1, "%s\n", + vol->name); else if (attr == &attr_vol_corrupted) - ret = sprintf(buf, "%d\n", vol->corrupted); + ret = scnprintf(buf, sizeof(int), "%d\n", + vol->corrupted); else if (attr == &attr_vol_alignment) - ret = sprintf(buf, "%d\n", vol->alignment); + ret = scnprintf(buf, sizeof(int), "%d\n", + vol->alignment); else if (attr == &attr_vol_usable_eb_size) - ret = sprintf(buf, "%d\n", vol->usable_leb_size); + ret = scnprintf(buf, sizeof(int), "%d\n", + vol->usable_leb_size); else if (attr == &attr_vol_data_bytes) - ret = sprintf(buf, "%lld\n", vol->used_bytes); + ret = scnprintf(buf, sizeof(unsigned long long), "%lld\n", + vol->used_bytes); else if (attr == &attr_vol_upd_marker) - ret = sprintf(buf, "%d\n", vol->upd_marker); + ret = scnprintf(buf, sizeof(int), "%d\n", + vol->upd_marker); else /* This must be a bug */ ret = -EINVAL; diff --git a/drivers/mtd/ubi/wl.c b/drivers/mtd/ubi/wl.c index 7def041bbe48..bc0b9ff9512e 100644 --- a/drivers/mtd/ubi/wl.c +++ b/drivers/mtd/ubi/wl.c @@ -89,6 +89,7 @@ #include #include #include +#include #include "ubi.h" #include "wl.h" @@ -488,6 +489,42 @@ out_free: return err; } +int ubi_wl_re_erase_peb(struct ubi_device *ubi, int pnum) +{ + int err; + struct ubi_wl_entry *e; + struct ubi_ec_hdr *ec_hdr; + + spin_lock(&ubi->wl_lock); + e = ubi->lookuptbl[pnum]; + spin_unlock(&ubi->wl_lock); + + dbg_wl("Re-erase PEB %d, EC %u", e->pnum, e->ec); + + err = self_check_ec(ubi, e->pnum, e->ec); + if (err) + return -EINVAL; + + ec_hdr = kzalloc(ubi->ec_hdr_alsize, GFP_NOFS); + if (!ec_hdr) + return -ENOMEM; + + err = ubi_io_sync_erase(ubi, e->pnum, 0); + if (err < 0) + goto out_free; + + dbg_wl("re-erased PEB %d, EC %u", e->pnum, e->ec); + + ec_hdr->ec = cpu_to_be64(e->ec); + + err = ubi_io_write_ec_hdr(ubi, e->pnum, ec_hdr); + if (err) + goto out_free; +out_free: + kfree(ec_hdr); + return err; +} + /** * serve_prot_queue - check if it is time to stop protecting PEBs. * @ubi: UBI device description object @@ -863,9 +900,19 @@ static int wear_leveling_worker(struct ubi_device *ubi, struct ubi_work *wrk, } /* The PEB has been successfully moved */ - if (scrubbing) - ubi_msg(ubi, "scrubbed PEB %d (LEB %d:%d), data moved to PEB %d", - e1->pnum, vol_id, lnum, e2->pnum); + if (scrubbing) { + spin_lock(&ubi->wl_lock); + if (e1->tagged_scrub_all) { + WARN_ON(atomic_read(&ubi->scrub_work_count) <= 0); + atomic_dec(&ubi->scrub_work_count); + e1->tagged_scrub_all = 0; + e2->tagged_scrub_all = 0; + } else { + ubi_msg(ubi, "scrubbed PEB %d (LEB %d:%d), data moved to PEB %d", + e1->pnum, vol_id, lnum, e2->pnum); + } + spin_unlock(&ubi->wl_lock); + } ubi_free_vid_buf(vidb); spin_lock(&ubi->wl_lock); @@ -1233,6 +1280,7 @@ int ubi_wl_put_peb(struct ubi_device *ubi, int vol_id, int lnum, retry: spin_lock(&ubi->wl_lock); e = ubi->lookuptbl[pnum]; + e->sqnum = UBI_UNKNOWN; if (e == ubi->move_from) { /* * User is putting the physical eraseblock which was selected to @@ -1269,6 +1317,20 @@ retry: } else if (in_wl_tree(e, &ubi->scrub)) { self_check_in_wl_tree(ubi, e, &ubi->scrub); rb_erase(&e->u.rb, &ubi->scrub); + + /* + * Since this PEB has been put we dont need to worry + * about it anymore + */ + if (e->tagged_scrub_all) { + int wrk_count; + + wrk_count = atomic_read(&ubi->scrub_work_count); + WARN_ON(wrk_count <= 0); + + atomic_dec(&ubi->scrub_work_count); + e->tagged_scrub_all = 0; + } } else if (in_wl_tree(e, &ubi->erroneous)) { self_check_in_wl_tree(ubi, e, &ubi->erroneous); rb_erase(&e->u.rb, &ubi->erroneous); @@ -1300,6 +1362,197 @@ retry: return err; } +/** + * ubi_wl_scrub_get_min_sqnum - Return the minimum sqnum of the used/pq/scrub. + * @ubi: UBI device description object + * + * This function returns the minimum sqnum of the PEB that are currently in use. + * + * Return the min sqnum if there are any used PEB's otherwise return ~(0) + * + */ +unsigned long long ubi_wl_scrub_get_min_sqnum(struct ubi_device *ubi) +{ + int i; + struct ubi_wl_entry *e, *tmp; + struct rb_node *node; + unsigned long long min_sqnum = ~0; + + spin_lock(&ubi->wl_lock); + + /* Go through the pq list */ + for (i = 0; i < UBI_PROT_QUEUE_LEN; ++i) { + list_for_each_entry_safe(e, tmp, &ubi->pq[i], u.list) { + if (e->sqnum < min_sqnum) + min_sqnum = e->sqnum; + } + } + + /* Go through used PEB tree */ + for (node = rb_first(&ubi->used); node; node = rb_next(node)) { + e = rb_entry(node, struct ubi_wl_entry, u.rb); + self_check_in_wl_tree(ubi, e, &ubi->used); + if (e->sqnum < min_sqnum) + min_sqnum = e->sqnum; + } + /* Go through scrub PEB tree */ + for (node = rb_first(&ubi->scrub); node; node = rb_next(node)) { + e = rb_entry(node, struct ubi_wl_entry, u.rb); + self_check_in_wl_tree(ubi, e, &ubi->scrub); + if (e->sqnum < min_sqnum) + min_sqnum = e->sqnum; + } + spin_unlock(&ubi->wl_lock); + return min_sqnum; +} + +/** + * ubi_wl_update_peb_sqnum - Update the vol hdr sqnum of the PEB. + * @pnum: The PEB number. + * @vid_hdr: The vol hdr being written to the PEB. + * + */ +void ubi_wl_update_peb_sqnum(struct ubi_device *ubi, int pnum, + struct ubi_vid_hdr *vid_hdr) +{ + struct ubi_wl_entry *e; + + spin_lock(&ubi->wl_lock); + e = ubi->lookuptbl[pnum]; + e->sqnum = be64_to_cpu(vid_hdr->sqnum); + e->tagged_scrub_all = 0; + spin_unlock(&ubi->wl_lock); +} + +static int is_ubi_readonly(struct ubi_device *ubi) +{ + int is_readonly = 0; + + spin_lock(&ubi->wl_lock); + if (ubi->ro_mode || !ubi->thread_enabled || + ubi_dbg_is_bgt_disabled(ubi)) + is_readonly = 1; + spin_unlock(&ubi->wl_lock); + + return is_readonly; +} + +/** + * ubi_wl_scan_all - Scan all PEB's + * @ubi: UBI device description object + * @scrub_sqnum: The max seqnum of the PEB to scrub from the used/pq lists + * + * This function schedules all device PEBs for scrubbing if the sqnum of the + * vol hdr is less than the sqnum in the trigger. + * + * Return 0 in case of success, (negative) error code otherwise + * + */ +ssize_t ubi_wl_scrub_all(struct ubi_device *ubi, unsigned long long scrub_sqnum) +{ + struct rb_node *node; + struct ubi_wl_entry *e, *tmp; + int scrub_count = 0; + int total_scrub_count = 0; + int err, i; + + if (!ubi->lookuptbl) { + ubi_err(ubi, "lookuptbl is null"); + return -ENOENT; + } + + if (is_ubi_readonly(ubi)) { + ubi_err(ubi, "Cannot *Initiate* scrub:background thread disabled or readonly!"); + return -EROFS; + } + + /* Wait for all currently running work to be done! */ + down_write(&ubi->work_sem); + spin_lock(&ubi->wl_lock); + ubi_msg(ubi, "Scrub triggered sqnum = %llu!", scrub_sqnum); + + if (ubi->scrub_in_progress) { + ubi_err(ubi, "Scrub already in progress, ignoring the trigger"); + spin_unlock(&ubi->wl_lock); + up_write(&ubi->work_sem); /* Allow new work to start. */ + return -EBUSY; + } + ubi->scrub_in_progress = true; + + /* Go through scrub PEB tree and count pending */ + for (node = rb_first(&ubi->scrub); node; node = rb_next(node)) { + e = rb_entry(node, struct ubi_wl_entry, u.rb); + self_check_in_wl_tree(ubi, e, &ubi->scrub); + e->tagged_scrub_all = 1; + total_scrub_count++; + } + + /* Move all used pebs to scrub tree */ + node = rb_first(&ubi->used); + while (node != NULL) { + e = rb_entry(node, struct ubi_wl_entry, u.rb); + self_check_in_wl_tree(ubi, e, &ubi->used); + node = rb_next(node); + + if (e->sqnum > scrub_sqnum) + continue; + rb_erase(&e->u.rb, &ubi->used); + wl_tree_add(e, &ubi->scrub); + e->tagged_scrub_all = 1; + scrub_count++; + total_scrub_count++; + } + + /* Move all protected pebs to scrub tree */ + for (i = 0; i < UBI_PROT_QUEUE_LEN; ++i) { + list_for_each_entry_safe(e, tmp, &ubi->pq[i], u.list) { + + if (e->sqnum > scrub_sqnum) + continue; + + list_del(&e->u.list); + wl_tree_add(e, &ubi->scrub); + e->tagged_scrub_all = 1; + scrub_count++; + total_scrub_count++; + } + } + + atomic_set(&ubi->scrub_work_count, total_scrub_count); + spin_unlock(&ubi->wl_lock); + up_write(&ubi->work_sem); /* Allow new work to start. */ + + /* + * Technically scrubbing is the same as wear-leveling, so it is done + * by the WL worker. + */ + err = ensure_wear_leveling(ubi, 0); + if (err) { + ubi_err(ubi, "Failed to start the WL worker err =%d", err); + return err; + } + ubi_msg(ubi, "Scheduled %d PEB's for scrubbing!", scrub_count); + ubi_msg(ubi, "Total PEB's for scrub = %d", total_scrub_count); + + /* Wait for scrub to finish */ + while (atomic_read(&ubi->scrub_work_count) > 0) { + /* Poll every second to check if the scrub work is done */ + msleep(1000); + + if (is_ubi_readonly(ubi)) { + ubi_err(ubi, "Cannot *Complete* scrub:background thread disabled or readonly!"); + return -EROFS; + } + wake_up_process(ubi->bgt_thread); + } + + spin_lock(&ubi->wl_lock); + ubi->scrub_in_progress = false; + spin_unlock(&ubi->wl_lock); + ubi_msg(ubi, "Done scrubbing %d PEB's!", scrub_count); + return 0; +} + /** * ubi_wl_scrub_peb - schedule a physical eraseblock for scrubbing. * @ubi: UBI device description object @@ -1753,7 +2006,8 @@ int ubi_wl_init(struct ubi_device *ubi, struct ubi_attach_info *ai) ubi->max_ec = ai->max_ec; INIT_LIST_HEAD(&ubi->works); - sprintf(ubi->bgt_name, UBI_BGT_NAME_PATTERN, ubi->ubi_num); + scnprintf(ubi->bgt_name, sizeof(UBI_BGT_NAME_PATTERN)+2, + UBI_BGT_NAME_PATTERN, ubi->ubi_num); err = -ENOMEM; ubi->lookuptbl = kcalloc(ubi->peb_count, sizeof(void *), GFP_KERNEL); @@ -1786,6 +2040,8 @@ int ubi_wl_init(struct ubi_device *ubi, struct ubi_attach_info *ai) e->pnum = aeb->pnum; e->ec = aeb->ec; + e->tagged_scrub_all = 0; + e->sqnum = aeb->sqnum; ubi_assert(e->ec >= 0); wl_tree_add(e, &ubi->free); @@ -1808,6 +2064,8 @@ int ubi_wl_init(struct ubi_device *ubi, struct ubi_attach_info *ai) e->pnum = aeb->pnum; e->ec = aeb->ec; + e->tagged_scrub_all = 0; + e->sqnum = aeb->sqnum; ubi->lookuptbl[e->pnum] = e; if (!aeb->scrub) { @@ -1891,6 +2149,7 @@ int ubi_wl_init(struct ubi_device *ubi, struct ubi_attach_info *ai) #ifdef CONFIG_MTD_UBI_FASTMAP ubi_ensure_anchor_pebs(ubi); #endif + ubi->wl_is_inited = 1; return 0; out_free: