UBI: Fastmap: Rework fastmap error paths

If UBI is unable to write the fastmap to the device
we have make sure that upon next attach UBI will fall
back to scanning mode.
In case we cannot ensure that they only thing we can do
is falling back to read-only mode.

The current error handling code is not powercut proof.
It could happen that a powercut while invalidating would
lead to a state where an too old fastmap could be used upon
attach.
This patch addresses the issue by writing a fake fastmap
super block to a fresh PEB instead of reerasing the existing one.
The fake fastmap super block will UBI case to do a full scan.

Signed-off-by: Richard Weinberger <richard@nod.at>
This commit is contained in:
Richard Weinberger 2014-11-10 16:11:40 +01:00
parent 61de74ce2f
commit 5ca97ad838
1 changed files with 112 additions and 46 deletions

View File

@ -1300,31 +1300,87 @@ static int erase_block(struct ubi_device *ubi, int pnum)
/**
* invalidate_fastmap - destroys a fastmap.
* @ubi: UBI device object
* @fm: the fastmap to be destroyed
*
* This function ensures that upon next UBI attach a full scan
* is issued. We need this if UBI is about to write a new fastmap
* but is unable to do so. In this case we have two options:
* a) Make sure that the current fastmap will not be usued upon
* attach time and contine or b) fall back to RO mode to have the
* current fastmap in a valid state.
* Returns 0 on success, < 0 indicates an internal error.
*/
static int invalidate_fastmap(struct ubi_device *ubi,
struct ubi_fastmap_layout *fm)
static int invalidate_fastmap(struct ubi_device *ubi)
{
int ret;
struct ubi_vid_hdr *vh;
struct ubi_fastmap_layout *fm;
struct ubi_wl_entry *e;
struct ubi_vid_hdr *vh = NULL;
ret = erase_block(ubi, fm->e[0]->pnum);
if (ret < 0)
return ret;
if (!ubi->fm)
return 0;
ubi->fm = NULL;
ret = -ENOMEM;
fm = kzalloc(sizeof(*fm), GFP_KERNEL);
if (!fm)
goto out;
vh = new_fm_vhdr(ubi, UBI_FM_SB_VOLUME_ID);
if (!vh)
return -ENOMEM;
goto out_free_fm;
/* deleting the current fastmap SB is not enough, an old SB may exist,
* so create a (corrupted) SB such that fastmap will find it and fall
* back to scanning mode in any case */
ret = -ENOSPC;
e = ubi_wl_get_fm_peb(ubi, 1);
if (!e)
goto out_free_fm;
/*
* Create fake fastmap such that UBI will fall back
* to scanning mode.
*/
vh->sqnum = cpu_to_be64(ubi_next_sqnum(ubi));
ret = ubi_io_write_vid_hdr(ubi, fm->e[0]->pnum, vh);
ret = ubi_io_write_vid_hdr(ubi, e->pnum, vh);
if (ret < 0) {
ubi_wl_put_fm_peb(ubi, e, 0, 0);
goto out_free_fm;
}
fm->used_blocks = 1;
fm->e[0] = e;
ubi->fm = fm;
out:
ubi_free_vid_hdr(ubi, vh);
return ret;
out_free_fm:
kfree(fm);
goto out;
}
/**
* return_fm_pebs - returns all PEBs used by a fastmap back to the
* WL sub-system.
* @ubi: UBI device object
* @fm: fastmap layout object
*/
static void return_fm_pebs(struct ubi_device *ubi,
struct ubi_fastmap_layout *fm)
{
int i;
if (!fm)
return;
for (i = 0; i < fm->used_blocks; i++) {
if (fm->e[i]) {
ubi_wl_put_fm_peb(ubi, fm->e[i], i,
fm->to_be_tortured[i]);
fm->e[i] = NULL;
}
}
}
/**
@ -1336,7 +1392,7 @@ static int invalidate_fastmap(struct ubi_device *ubi,
*/
int ubi_update_fastmap(struct ubi_device *ubi)
{
int ret, i;
int ret, i, j;
struct ubi_fastmap_layout *new_fm, *old_fm;
struct ubi_wl_entry *tmp_e;
@ -1376,34 +1432,40 @@ int ubi_update_fastmap(struct ubi_device *ubi)
tmp_e = ubi_wl_get_fm_peb(ubi, 0);
spin_unlock(&ubi->wl_lock);
if (!tmp_e && !old_fm) {
int j;
ubi_err(ubi, "could not get any free erase block");
if (!tmp_e) {
if (old_fm && old_fm->e[i]) {
ret = erase_block(ubi, old_fm->e[i]->pnum);
if (ret < 0) {
ubi_err(ubi, "could not erase old fastmap PEB");
for (j = 1; j < i; j++)
ubi_wl_put_fm_peb(ubi, new_fm->e[j], j, 0);
for (j = 1; j < i; j++) {
ubi_wl_put_fm_peb(ubi, new_fm->e[j],
j, 0);
new_fm->e[j] = NULL;
}
goto err;
}
new_fm->e[i] = old_fm->e[i];
old_fm->e[i] = NULL;
} else {
ubi_err(ubi, "could not get any free erase block");
ret = -ENOSPC;
goto err;
} else if (!tmp_e && old_fm && old_fm->e[i]) {
ret = erase_block(ubi, old_fm->e[i]->pnum);
if (ret < 0) {
int j;
for (j = 1; j < i; j++) {
ubi_wl_put_fm_peb(ubi, new_fm->e[j], j, 0);
new_fm->e[j] = NULL;
}
for (j = 1; j < i; j++)
ubi_wl_put_fm_peb(ubi, new_fm->e[j],
j, 0);
ubi_err(ubi, "could not erase old fastmap PEB");
ret = -ENOSPC;
goto err;
}
new_fm->e[i] = old_fm->e[i];
} else {
new_fm->e[i] = tmp_e;
if (old_fm && old_fm->e[i])
if (old_fm && old_fm->e[i]) {
ubi_wl_put_fm_peb(ubi, old_fm->e[i], i,
old_fm->to_be_tortured[i]);
old_fm->e[i] = NULL;
}
}
}
@ -1412,6 +1474,7 @@ int ubi_update_fastmap(struct ubi_device *ubi)
for (i = new_fm->used_blocks; i < old_fm->used_blocks; i++) {
ubi_wl_put_fm_peb(ubi, old_fm->e[i], i,
old_fm->to_be_tortured[i]);
old_fm->e[i] = NULL;
}
}
@ -1424,29 +1487,33 @@ int ubi_update_fastmap(struct ubi_device *ubi)
if (!tmp_e) {
ret = erase_block(ubi, old_fm->e[0]->pnum);
if (ret < 0) {
int i;
ubi_err(ubi, "could not erase old anchor PEB");
for (i = 1; i < new_fm->used_blocks; i++)
for (i = 1; i < new_fm->used_blocks; i++) {
ubi_wl_put_fm_peb(ubi, new_fm->e[i],
i, 0);
new_fm->e[i] = NULL;
}
goto err;
}
new_fm->e[0] = old_fm->e[0];
new_fm->e[0]->ec = ret;
old_fm->e[0] = NULL;
} else {
/* we've got a new anchor PEB, return the old one */
ubi_wl_put_fm_peb(ubi, old_fm->e[0], 0,
old_fm->to_be_tortured[0]);
new_fm->e[0] = tmp_e;
old_fm->e[0] = NULL;
}
} else {
if (!tmp_e) {
int i;
ubi_err(ubi, "could not find any anchor PEB");
for (i = 1; i < new_fm->used_blocks; i++)
for (i = 1; i < new_fm->used_blocks; i++) {
ubi_wl_put_fm_peb(ubi, new_fm->e[i], i, 0);
new_fm->e[i] = NULL;
}
ret = -ENOSPC;
goto err;
@ -1469,19 +1536,18 @@ int ubi_update_fastmap(struct ubi_device *ubi)
return ret;
err:
kfree(new_fm);
ubi_warn(ubi, "Unable to write new fastmap, err=%i", ret);
ret = 0;
if (old_fm) {
ret = invalidate_fastmap(ubi, old_fm);
if (ret < 0) {
ubi_err(ubi, "Unable to invalidiate current fastmap!");
ubi_ro_mode(ubi);
}
else if (ret)
ret = 0;
ret = invalidate_fastmap(ubi);
if (ret < 0) {
ubi_err(ubi, "Unable to invalidiate current fastmap!");
ubi_ro_mode(ubi);
} else {
return_fm_pebs(ubi, old_fm);
return_fm_pebs(ubi, new_fm);
ret = 0;
}
kfree(new_fm);
goto out_unlock;
}