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: