[PATCH 3.16 090/306] ubi: Fix races around ubi_refill_pools()

From: Ben Hutchings
Date: Wed Feb 15 2017 - 18:16:52 EST


3.16.40-rc1 review patch. If anyone has any objections, please let me know.

------------------

From: Richard Weinberger <richard@xxxxxx>

commit 2e8f08deabbc7eefe4c5838aaa6aa9a23a8acf2e upstream.

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.

Fixes: dbb7d2a88d ("UBI: Add fastmap core")
Signed-off-by: Richard Weinberger <richard@xxxxxx>
[bwh: Backported to 3.16:
- Adjust filename, context, indentation
- s/ubi->fm_eba_sem/ubi->fm_sem/]
Signed-off-by: Ben Hutchings <ben@xxxxxxxxxxxxxxx>
---
--- a/drivers/mtd/ubi/eba.c
+++ b/drivers/mtd/ubi/eba.c
@@ -1021,6 +1021,8 @@ int ubi_eba_copy_leb(struct ubi_device *
struct ubi_volume *vol;
uint32_t crc;

+ ubi_assert(rwsem_is_locked(&ubi->fm_sem));
+
vol_id = be32_to_cpu(vid_hdr->vol_id);
lnum = be32_to_cpu(vid_hdr->lnum);

@@ -1189,9 +1191,7 @@ int ubi_eba_copy_leb(struct ubi_device *
}

ubi_assert(vol->eba_tbl[lnum] == from);
- down_read(&ubi->fm_sem);
vol->eba_tbl[lnum] = to;
- up_read(&ubi->fm_sem);

out_unlock_buf:
mutex_unlock(&ubi->buf_mutex);
--- a/drivers/mtd/ubi/fastmap.c
+++ b/drivers/mtd/ubi/fastmap.c
@@ -1413,22 +1413,30 @@ int ubi_update_fastmap(struct ubi_device
struct ubi_wl_entry *tmp_e;

mutex_lock(&ubi->fm_mutex);
+ down_write(&ubi->work_sem);
+ down_write(&ubi->fm_sem);

ubi_refill_pools(ubi);

if (ubi->ro_mode || ubi->fm_disabled) {
+ up_write(&ubi->fm_sem);
+ up_write(&ubi->work_sem);
mutex_unlock(&ubi->fm_mutex);
return 0;
}

ret = ubi_ensure_anchor_pebs(ubi);
if (ret) {
+ up_write(&ubi->fm_sem);
+ up_write(&ubi->work_sem);
mutex_unlock(&ubi->fm_mutex);
return ret;
}

new_fm = kzalloc(sizeof(*new_fm), GFP_KERNEL);
if (!new_fm) {
+ up_write(&ubi->fm_sem);
+ up_write(&ubi->work_sem);
mutex_unlock(&ubi->fm_mutex);
return -ENOMEM;
}
@@ -1539,16 +1547,14 @@ int ubi_update_fastmap(struct ubi_device
new_fm->e[0]->ec = tmp_e->ec;
}

- down_write(&ubi->work_sem);
- down_write(&ubi->fm_sem);
ret = ubi_write_fastmap(ubi, new_fm);
- up_write(&ubi->fm_sem);
- up_write(&ubi->work_sem);

if (ret)
goto err;

out_unlock:
+ up_write(&ubi->fm_sem);
+ up_write(&ubi->work_sem);
mutex_unlock(&ubi->fm_mutex);
kfree(old_fm);
return ret;
--- a/drivers/mtd/ubi/wl.c
+++ b/drivers/mtd/ubi/wl.c
@@ -653,6 +653,8 @@ static struct ubi_wl_entry *get_peb_for_
struct ubi_fm_pool *pool = &ubi->fm_wl_pool;
int pnum;

+ ubi_assert(rwsem_is_locked(&ubi->fm_sem));
+
if (pool->used == pool->size || !pool->size) {
/* We cannot update the fastmap here because this
* function is called in atomic context.
@@ -889,7 +891,7 @@ int ubi_is_erase_work(struct ubi_work *w
* 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;

@@ -909,7 +911,10 @@ static int schedule_erase(struct ubi_dev
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;
}

@@ -982,7 +987,7 @@ int ubi_wl_put_fm_peb(struct ubi_device
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);
}
#endif

@@ -1015,6 +1020,7 @@ static int wear_leveling_worker(struct u
if (!vid_hdr)
return -ENOMEM;

+ down_read(&ubi->fm_sem);
mutex_lock(&ubi->move_mutex);
spin_lock(&ubi->wl_lock);
ubi_assert(!ubi->move_from && !ubi->move_to);
@@ -1241,6 +1247,7 @@ static int wear_leveling_worker(struct u

dbg_wl("done");
mutex_unlock(&ubi->move_mutex);
+ up_read(&ubi->fm_sem);
return 0;

/*
@@ -1282,6 +1289,7 @@ out_not_moved:
}

mutex_unlock(&ubi->move_mutex);
+ up_read(&ubi->fm_sem);
return 0;

out_error:
@@ -1303,6 +1311,7 @@ out_error:
out_ro:
ubi_ro_mode(ubi);
mutex_unlock(&ubi->move_mutex);
+ up_read(&ubi->fm_sem);
ubi_assert(err != 0);
return err < 0 ? err : -EIO;

@@ -1310,6 +1319,7 @@ out_cancel:
ubi->wl_scheduled = 0;
spin_unlock(&ubi->wl_lock);
mutex_unlock(&ubi->move_mutex);
+ up_read(&ubi->fm_sem);
ubi_free_vid_hdr(ubi, vid_hdr);
return 0;
}
@@ -1411,7 +1421,7 @@ int ubi_ensure_anchor_pebs(struct ubi_de

wrk->anchor = 1;
wrk->func = &wear_leveling_worker;
- schedule_ubi_work(ubi, wrk);
+ __schedule_ubi_work(ubi, wrk);
return 0;
}
#endif
@@ -1477,7 +1487,7 @@ static int erase_worker(struct ubi_devic
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) {
err = err1;
goto out_ro;
@@ -1633,7 +1643,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);
@@ -1922,7 +1932,7 @@ int ubi_wl_init(struct ubi_device *ubi,
e->ec = aeb->ec;
ubi_assert(!ubi_is_fm_block(ubi, 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)) {
kmem_cache_free(ubi_wl_entry_slab, e);
goto out_free;
}