ubi: Fix races around ubi_refill_pools()
authorRichard Weinberger <richard@nod.at>
Wed, 24 Aug 2016 12:36:14 +0000 (14:36 +0200)
committerRichard Weinberger <richard@nod.at>
Sun, 2 Oct 2016 20:54:01 +0000 (22:54 +0200)
When writing a new Fastmap the first thing that happens
is refilling the pools in memory.
At this stage it is possible that new PEBs from the new pools
get already claimed and written with data.
If this happens before the new Fastmap data structure hits the
flash and we face power cut the freshly written PEB will not
scanned and unnoticed.

Solve the issue by locking the pools until Fastmap is written.

Cc: <stable@vger.kernel.org>
Fixes: dbb7d2a88d ("UBI: Add fastmap core")
Signed-off-by: Richard Weinberger <richard@nod.at>
drivers/mtd/ubi/eba.c
drivers/mtd/ubi/fastmap-wl.c
drivers/mtd/ubi/fastmap.c
drivers/mtd/ubi/wl.c

index ddf4e63eed767d88e051df7f9fd50a4e0a13efe9..95c4048a371e87b6f5517b01109b19db85cedc56 100644 (file)
@@ -1210,6 +1210,8 @@ int ubi_eba_copy_leb(struct ubi_device *ubi, int from, int to,
        struct ubi_volume *vol;
        uint32_t crc;
 
+       ubi_assert(rwsem_is_locked(&ubi->fm_eba_sem));
+
        vol_id = be32_to_cpu(vid_hdr->vol_id);
        lnum = be32_to_cpu(vid_hdr->lnum);
 
@@ -1352,9 +1354,7 @@ int ubi_eba_copy_leb(struct ubi_device *ubi, int from, int to,
        }
 
        ubi_assert(vol->eba_tbl->entries[lnum].pnum == from);
-       down_read(&ubi->fm_eba_sem);
        vol->eba_tbl->entries[lnum].pnum = to;
-       up_read(&ubi->fm_eba_sem);
 
 out_unlock_buf:
        mutex_unlock(&ubi->buf_mutex);
index 30d3999dddbae93a6fd362c3bcd8fa40e0e6c8f1..4f0bd6b4422adc9bfa5c7bfd4f1afdd8abcdb4fc 100644 (file)
@@ -262,6 +262,8 @@ static struct ubi_wl_entry *get_peb_for_wl(struct ubi_device *ubi)
        struct ubi_fm_pool *pool = &ubi->fm_wl_pool;
        int pnum;
 
+       ubi_assert(rwsem_is_locked(&ubi->fm_eba_sem));
+
        if (pool->used == pool->size) {
                /* We cannot update the fastmap here because this
                 * function is called in atomic context.
@@ -303,7 +305,7 @@ int ubi_ensure_anchor_pebs(struct ubi_device *ubi)
 
        wrk->anchor = 1;
        wrk->func = &wear_leveling_worker;
-       schedule_ubi_work(ubi, wrk);
+       __schedule_ubi_work(ubi, wrk);
        return 0;
 }
 
@@ -344,7 +346,7 @@ int ubi_wl_put_fm_peb(struct ubi_device *ubi, struct ubi_wl_entry *fm_e,
        spin_unlock(&ubi->wl_lock);
 
        vol_id = lnum ? UBI_FM_DATA_VOLUME_ID : UBI_FM_SB_VOLUME_ID;
-       return schedule_erase(ubi, e, vol_id, lnum, torture);
+       return schedule_erase(ubi, e, vol_id, lnum, torture, true);
 }
 
 /**
index 4adffb893376b38f6ef108b66cc8d7a7d01fd3cd..8bd468332ec0a8f24b0bc7f892015afc37f47101 100644 (file)
@@ -1492,22 +1492,30 @@ int ubi_update_fastmap(struct ubi_device *ubi)
        struct ubi_wl_entry *tmp_e;
 
        down_write(&ubi->fm_protect);
+       down_write(&ubi->work_sem);
+       down_write(&ubi->fm_eba_sem);
 
        ubi_refill_pools(ubi);
 
        if (ubi->ro_mode || ubi->fm_disabled) {
+               up_write(&ubi->fm_eba_sem);
+               up_write(&ubi->work_sem);
                up_write(&ubi->fm_protect);
                return 0;
        }
 
        ret = ubi_ensure_anchor_pebs(ubi);
        if (ret) {
+               up_write(&ubi->fm_eba_sem);
+               up_write(&ubi->work_sem);
                up_write(&ubi->fm_protect);
                return ret;
        }
 
        new_fm = kzalloc(sizeof(*new_fm), GFP_KERNEL);
        if (!new_fm) {
+               up_write(&ubi->fm_eba_sem);
+               up_write(&ubi->work_sem);
                up_write(&ubi->fm_protect);
                return -ENOMEM;
        }
@@ -1616,16 +1624,14 @@ int ubi_update_fastmap(struct ubi_device *ubi)
                new_fm->e[0] = tmp_e;
        }
 
-       down_write(&ubi->work_sem);
-       down_write(&ubi->fm_eba_sem);
        ret = ubi_write_fastmap(ubi, new_fm);
-       up_write(&ubi->fm_eba_sem);
-       up_write(&ubi->work_sem);
 
        if (ret)
                goto err;
 
 out_unlock:
+       up_write(&ubi->fm_eba_sem);
+       up_write(&ubi->work_sem);
        up_write(&ubi->fm_protect);
        kfree(old_fm);
        return ret;
index 19206ad677b20405cc546ea6727138d1e5bb3cd3..b5b8cd6f481c5434cc0442496db24317702542ad 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;
 }
 
@@ -663,6 +666,7 @@ static int wear_leveling_worker(struct ubi_device *ubi, struct ubi_work *wrk,
 
        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);
@@ -893,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;
 
        /*
@@ -943,6 +948,7 @@ out_not_moved:
        }
 
        mutex_unlock(&ubi->move_mutex);
+       up_read(&ubi->fm_eba_sem);
        return 0;
 
 out_error:
@@ -964,6 +970,7 @@ out_error:
 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;
 
@@ -971,6 +978,7 @@ out_cancel:
        ubi->wl_scheduled = 0;
        spin_unlock(&ubi->wl_lock);
        mutex_unlock(&ubi->move_mutex);
+       up_read(&ubi->fm_eba_sem);
        ubi_free_vid_buf(vidb);
        return 0;
 }
@@ -1093,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;
@@ -1274,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);
@@ -1565,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;
                }
@@ -1644,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;
                        }