UBI: Fastmap: Fix race after ubi_wl_get_peb()

ubi_wl_get_peb() returns a fresh PEB which can be used by
user of UBI. Due to the pool logic fastmap will correctly
map this PEB upon attach time because it will be scanned.

If a new fastmap is written (due to heavy parallel io)
while the before the fresh PEB is assigned to the EBA table
it will not be scanned as it is no longer in the pool.
So, the race window exists between ubi_wl_get_peb()
and the EBA table assignment.
We have to make sure that no new fastmap can be written
while that.

To ensure that ubi_wl_get_peb() will grab ubi->fm_sem in read mode
and the user of ubi_wl_get_peb() has to release it after the PEB
got assigned.

Signed-off-by: Richard Weinberger <richard@nod.at>
This commit is contained in:
Richard Weinberger 2014-11-10 16:27:10 +01:00
parent ad3d6a05ee
commit 8fb2a51478
2 changed files with 25 additions and 6 deletions

View file

@ -567,6 +567,7 @@ static int recover_peb(struct ubi_device *ubi, int pnum, int vol_id, int lnum,
new_pnum = ubi_wl_get_peb(ubi);
if (new_pnum < 0) {
ubi_free_vid_hdr(ubi, vid_hdr);
up_read(&ubi->fm_sem);
return new_pnum;
}
@ -577,13 +578,16 @@ static int recover_peb(struct ubi_device *ubi, int pnum, int vol_id, int lnum,
if (err && err != UBI_IO_BITFLIPS) {
if (err > 0)
err = -EIO;
up_read(&ubi->fm_sem);
goto out_put;
}
vid_hdr->sqnum = cpu_to_be64(ubi_next_sqnum(ubi));
err = ubi_io_write_vid_hdr(ubi, new_pnum, vid_hdr);
if (err)
if (err) {
up_read(&ubi->fm_sem);
goto write_error;
}
data_size = offset + len;
mutex_lock(&ubi->buf_mutex);
@ -592,8 +596,10 @@ static int recover_peb(struct ubi_device *ubi, int pnum, int vol_id, int lnum,
/* Read everything before the area where the write failure happened */
if (offset > 0) {
err = ubi_io_read_data(ubi, ubi->peb_buf, pnum, 0, offset);
if (err && err != UBI_IO_BITFLIPS)
if (err && err != UBI_IO_BITFLIPS) {
up_read(&ubi->fm_sem);
goto out_unlock;
}
}
memcpy(ubi->peb_buf + offset, buf, len);
@ -601,13 +607,13 @@ static int recover_peb(struct ubi_device *ubi, int pnum, int vol_id, int lnum,
err = ubi_io_write_data(ubi, ubi->peb_buf, new_pnum, 0, data_size);
if (err) {
mutex_unlock(&ubi->buf_mutex);
up_read(&ubi->fm_sem);
goto write_error;
}
mutex_unlock(&ubi->buf_mutex);
ubi_free_vid_hdr(ubi, vid_hdr);
down_read(&ubi->fm_sem);
vol->eba_tbl[lnum] = new_pnum;
up_read(&ubi->fm_sem);
ubi_wl_put_peb(ubi, vol_id, lnum, pnum, 1);
@ -704,6 +710,7 @@ int ubi_eba_write_leb(struct ubi_device *ubi, struct ubi_volume *vol, int lnum,
if (pnum < 0) {
ubi_free_vid_hdr(ubi, vid_hdr);
leb_write_unlock(ubi, vol_id, lnum);
up_read(&ubi->fm_sem);
return pnum;
}
@ -714,6 +721,7 @@ int ubi_eba_write_leb(struct ubi_device *ubi, struct ubi_volume *vol, int lnum,
if (err) {
ubi_warn(ubi, "failed to write VID header to LEB %d:%d, PEB %d",
vol_id, lnum, pnum);
up_read(&ubi->fm_sem);
goto write_error;
}
@ -722,11 +730,11 @@ int ubi_eba_write_leb(struct ubi_device *ubi, struct ubi_volume *vol, int lnum,
if (err) {
ubi_warn(ubi, "failed to write %d bytes at offset %d of LEB %d:%d, PEB %d",
len, offset, vol_id, lnum, pnum);
up_read(&ubi->fm_sem);
goto write_error;
}
}
down_read(&ubi->fm_sem);
vol->eba_tbl[lnum] = pnum;
up_read(&ubi->fm_sem);
@ -825,6 +833,7 @@ int ubi_eba_write_leb_st(struct ubi_device *ubi, struct ubi_volume *vol,
if (pnum < 0) {
ubi_free_vid_hdr(ubi, vid_hdr);
leb_write_unlock(ubi, vol_id, lnum);
up_read(&ubi->fm_sem);
return pnum;
}
@ -835,6 +844,7 @@ int ubi_eba_write_leb_st(struct ubi_device *ubi, struct ubi_volume *vol,
if (err) {
ubi_warn(ubi, "failed to write VID header to LEB %d:%d, PEB %d",
vol_id, lnum, pnum);
up_read(&ubi->fm_sem);
goto write_error;
}
@ -842,11 +852,11 @@ int ubi_eba_write_leb_st(struct ubi_device *ubi, struct ubi_volume *vol,
if (err) {
ubi_warn(ubi, "failed to write %d bytes of data to PEB %d",
len, pnum);
up_read(&ubi->fm_sem);
goto write_error;
}
ubi_assert(vol->eba_tbl[lnum] < 0);
down_read(&ubi->fm_sem);
vol->eba_tbl[lnum] = pnum;
up_read(&ubi->fm_sem);
@ -943,6 +953,7 @@ int ubi_eba_atomic_leb_change(struct ubi_device *ubi, struct ubi_volume *vol,
pnum = ubi_wl_get_peb(ubi);
if (pnum < 0) {
err = pnum;
up_read(&ubi->fm_sem);
goto out_leb_unlock;
}
@ -953,6 +964,7 @@ int ubi_eba_atomic_leb_change(struct ubi_device *ubi, struct ubi_volume *vol,
if (err) {
ubi_warn(ubi, "failed to write VID header to LEB %d:%d, PEB %d",
vol_id, lnum, pnum);
up_read(&ubi->fm_sem);
goto write_error;
}
@ -960,10 +972,10 @@ int ubi_eba_atomic_leb_change(struct ubi_device *ubi, struct ubi_volume *vol,
if (err) {
ubi_warn(ubi, "failed to write %d bytes of data to PEB %d",
len, pnum);
up_read(&ubi->fm_sem);
goto write_error;
}
down_read(&ubi->fm_sem);
old_pnum = vol->eba_tbl[lnum];
vol->eba_tbl[lnum] = pnum;
up_read(&ubi->fm_sem);

View file

@ -641,6 +641,7 @@ void ubi_refill_pools(struct ubi_device *ubi)
/* ubi_wl_get_peb - works exaclty like __wl_get_peb but keeps track of
* the fastmap pool.
* Returns with ubi->fm_sem held in read mode!
*/
int ubi_wl_get_peb(struct ubi_device *ubi)
{
@ -649,16 +650,20 @@ int ubi_wl_get_peb(struct ubi_device *ubi)
struct ubi_fm_pool *wl_pool = &ubi->fm_wl_pool;
again:
down_read(&ubi->fm_sem);
spin_lock(&ubi->wl_lock);
/* We check here also for the WL pool because at this point we can
* refill the WL pool synchronous. */
if (pool->used == pool->size || wl_pool->used == wl_pool->size) {
spin_unlock(&ubi->wl_lock);
up_read(&ubi->fm_sem);
ret = ubi_update_fastmap(ubi);
if (ret) {
ubi_msg(ubi, "Unable to write a new fastmap: %i", ret);
down_read(&ubi->fm_sem);
return -ENOSPC;
}
down_read(&ubi->fm_sem);
spin_lock(&ubi->wl_lock);
}
@ -670,6 +675,7 @@ int ubi_wl_get_peb(struct ubi_device *ubi)
goto out;
}
retried = 1;
up_read(&ubi->fm_sem);
goto again;
}
@ -725,6 +731,7 @@ int ubi_wl_get_peb(struct ubi_device *ubi)
spin_lock(&ubi->wl_lock);
peb = wl_get_peb(ubi);
spin_unlock(&ubi->wl_lock);
down_read(&ubi->fm_sem);
if (peb < 0)
return peb;