Merge tag 'upstream-4.9-rc1' of git://git.infradead.org/linux-ubifs
[cascardo/linux.git] / drivers / mtd / ubi / wl.c
index f453326..b5b8cd6 100644 (file)
@@ -580,7 +580,7 @@ static int erase_worker(struct ubi_device *ubi, struct ubi_work *wl_wrk,
  * failure.
  */
 static int schedule_erase(struct ubi_device *ubi, struct ubi_wl_entry *e,
-                         int vol_id, int lnum, int torture)
+                         int vol_id, int lnum, int torture, bool nested)
 {
        struct ubi_work *wl_wrk;
 
@@ -599,7 +599,10 @@ static int schedule_erase(struct ubi_device *ubi, struct ubi_wl_entry *e,
        wl_wrk->lnum = lnum;
        wl_wrk->torture = torture;
 
-       schedule_ubi_work(ubi, wl_wrk);
+       if (nested)
+               __schedule_ubi_work(ubi, wl_wrk);
+       else
+               schedule_ubi_work(ubi, wl_wrk);
        return 0;
 }
 
@@ -644,11 +647,12 @@ static int wear_leveling_worker(struct ubi_device *ubi, struct ubi_work *wrk,
                                int shutdown)
 {
        int err, scrubbing = 0, torture = 0, protect = 0, erroneous = 0;
-       int vol_id = -1, lnum = -1;
+       int erase = 0, keep = 0, vol_id = -1, lnum = -1;
 #ifdef CONFIG_MTD_UBI_FASTMAP
        int anchor = wrk->anchor;
 #endif
        struct ubi_wl_entry *e1, *e2;
+       struct ubi_vid_io_buf *vidb;
        struct ubi_vid_hdr *vid_hdr;
        int dst_leb_clean = 0;
 
@@ -656,10 +660,13 @@ static int wear_leveling_worker(struct ubi_device *ubi, struct ubi_work *wrk,
        if (shutdown)
                return 0;
 
-       vid_hdr = ubi_zalloc_vid_hdr(ubi, GFP_NOFS);
-       if (!vid_hdr)
+       vidb = ubi_alloc_vid_buf(ubi, GFP_NOFS);
+       if (!vidb)
                return -ENOMEM;
 
+       vid_hdr = ubi_get_vid_hdr(vidb);
+
+       down_read(&ubi->fm_eba_sem);
        mutex_lock(&ubi->move_mutex);
        spin_lock(&ubi->wl_lock);
        ubi_assert(!ubi->move_from && !ubi->move_to);
@@ -753,7 +760,7 @@ static int wear_leveling_worker(struct ubi_device *ubi, struct ubi_work *wrk,
         * which is being moved was unmapped.
         */
 
-       err = ubi_io_read_vid_hdr(ubi, e1->pnum, vid_hdr, 0);
+       err = ubi_io_read_vid_hdr(ubi, e1->pnum, vidb, 0);
        if (err && err != UBI_IO_BITFLIPS) {
                dst_leb_clean = 1;
                if (err == UBI_IO_FF) {
@@ -780,6 +787,16 @@ static int wear_leveling_worker(struct ubi_device *ubi, struct ubi_work *wrk,
                               e1->pnum);
                        scrubbing = 1;
                        goto out_not_moved;
+               } else if (ubi->fast_attach && err == UBI_IO_BAD_HDR_EBADMSG) {
+                       /*
+                        * While a full scan would detect interrupted erasures
+                        * at attach time we can face them here when attached from
+                        * Fastmap.
+                        */
+                       dbg_wl("PEB %d has ECC errors, maybe from an interrupted erasure",
+                              e1->pnum);
+                       erase = 1;
+                       goto out_not_moved;
                }
 
                ubi_err(ubi, "error %d while reading VID header from PEB %d",
@@ -790,7 +807,7 @@ static int wear_leveling_worker(struct ubi_device *ubi, struct ubi_work *wrk,
        vol_id = be32_to_cpu(vid_hdr->vol_id);
        lnum = be32_to_cpu(vid_hdr->lnum);
 
-       err = ubi_eba_copy_leb(ubi, e1->pnum, e2->pnum, vid_hdr);
+       err = ubi_eba_copy_leb(ubi, e1->pnum, e2->pnum, vidb);
        if (err) {
                if (err == MOVE_CANCEL_RACE) {
                        /*
@@ -815,6 +832,7 @@ static int wear_leveling_worker(struct ubi_device *ubi, struct ubi_work *wrk,
                         * Target PEB had bit-flips or write error - torture it.
                         */
                        torture = 1;
+                       keep = 1;
                        goto out_not_moved;
                }
 
@@ -847,7 +865,7 @@ static int wear_leveling_worker(struct ubi_device *ubi, struct ubi_work *wrk,
        if (scrubbing)
                ubi_msg(ubi, "scrubbed PEB %d (LEB %d:%d), data moved to PEB %d",
                        e1->pnum, vol_id, lnum, e2->pnum);
-       ubi_free_vid_hdr(ubi, vid_hdr);
+       ubi_free_vid_buf(vidb);
 
        spin_lock(&ubi->wl_lock);
        if (!ubi->move_to_put) {
@@ -879,6 +897,7 @@ static int wear_leveling_worker(struct ubi_device *ubi, struct ubi_work *wrk,
 
        dbg_wl("done");
        mutex_unlock(&ubi->move_mutex);
+       up_read(&ubi->fm_eba_sem);
        return 0;
 
        /*
@@ -901,7 +920,7 @@ out_not_moved:
                ubi->erroneous_peb_count += 1;
        } else if (scrubbing)
                wl_tree_add(e1, &ubi->scrub);
-       else
+       else if (keep)
                wl_tree_add(e1, &ubi->used);
        if (dst_leb_clean) {
                wl_tree_add(e2, &ubi->free);
@@ -913,7 +932,7 @@ out_not_moved:
        ubi->wl_scheduled = 0;
        spin_unlock(&ubi->wl_lock);
 
-       ubi_free_vid_hdr(ubi, vid_hdr);
+       ubi_free_vid_buf(vidb);
        if (dst_leb_clean) {
                ensure_wear_leveling(ubi, 1);
        } else {
@@ -922,7 +941,14 @@ out_not_moved:
                        goto out_ro;
        }
 
+       if (erase) {
+               err = do_sync_erase(ubi, e1, vol_id, lnum, 1);
+               if (err)
+                       goto out_ro;
+       }
+
        mutex_unlock(&ubi->move_mutex);
+       up_read(&ubi->fm_eba_sem);
        return 0;
 
 out_error:
@@ -937,13 +963,14 @@ out_error:
        ubi->move_to_put = ubi->wl_scheduled = 0;
        spin_unlock(&ubi->wl_lock);
 
-       ubi_free_vid_hdr(ubi, vid_hdr);
+       ubi_free_vid_buf(vidb);
        wl_entry_destroy(ubi, e1);
        wl_entry_destroy(ubi, e2);
 
 out_ro:
        ubi_ro_mode(ubi);
        mutex_unlock(&ubi->move_mutex);
+       up_read(&ubi->fm_eba_sem);
        ubi_assert(err != 0);
        return err < 0 ? err : -EIO;
 
@@ -951,7 +978,8 @@ out_cancel:
        ubi->wl_scheduled = 0;
        spin_unlock(&ubi->wl_lock);
        mutex_unlock(&ubi->move_mutex);
-       ubi_free_vid_hdr(ubi, vid_hdr);
+       up_read(&ubi->fm_eba_sem);
+       ubi_free_vid_buf(vidb);
        return 0;
 }
 
@@ -1073,7 +1101,7 @@ static int __erase_worker(struct ubi_device *ubi, struct ubi_work *wl_wrk)
                int err1;
 
                /* Re-schedule the LEB for erasure */
-               err1 = schedule_erase(ubi, e, vol_id, lnum, 0);
+               err1 = schedule_erase(ubi, e, vol_id, lnum, 0, false);
                if (err1) {
                        wl_entry_destroy(ubi, e);
                        err = err1;
@@ -1254,7 +1282,7 @@ retry:
        }
        spin_unlock(&ubi->wl_lock);
 
-       err = schedule_erase(ubi, e, vol_id, lnum, torture);
+       err = schedule_erase(ubi, e, vol_id, lnum, torture, false);
        if (err) {
                spin_lock(&ubi->wl_lock);
                wl_tree_add(e, &ubi->used);
@@ -1545,7 +1573,7 @@ int ubi_wl_init(struct ubi_device *ubi, struct ubi_attach_info *ai)
                e->pnum = aeb->pnum;
                e->ec = aeb->ec;
                ubi->lookuptbl[e->pnum] = e;
-               if (schedule_erase(ubi, e, aeb->vol_id, aeb->lnum, 0)) {
+               if (schedule_erase(ubi, e, aeb->vol_id, aeb->lnum, 0, false)) {
                        wl_entry_destroy(ubi, e);
                        goto out_free;
                }
@@ -1624,7 +1652,7 @@ int ubi_wl_init(struct ubi_device *ubi, struct ubi_attach_info *ai)
                        e->ec = aeb->ec;
                        ubi_assert(!ubi->lookuptbl[e->pnum]);
                        ubi->lookuptbl[e->pnum] = e;
-                       if (schedule_erase(ubi, e, aeb->vol_id, aeb->lnum, 0)) {
+                       if (schedule_erase(ubi, e, aeb->vol_id, aeb->lnum, 0, false)) {
                                wl_entry_destroy(ubi, e);
                                goto out_free;
                        }