diff options
Diffstat (limited to 'drivers/mtd')
-rw-r--r-- | drivers/mtd/ubi/eba.c | 130 |
1 files changed, 77 insertions, 53 deletions
diff --git a/drivers/mtd/ubi/eba.c b/drivers/mtd/ubi/eba.c index ebf517271d29..be59cfb81934 100644 --- a/drivers/mtd/ubi/eba.c +++ b/drivers/mtd/ubi/eba.c @@ -554,39 +554,37 @@ int ubi_eba_read_leb_sg(struct ubi_device *ubi, struct ubi_volume *vol, } /** - * recover_peb - recover from write failure. - * @ubi: UBI device description object + * try_recover_peb - try to recover from write failure. + * @vol: volume description object * @pnum: the physical eraseblock to recover - * @vol_id: volume ID * @lnum: logical eraseblock number * @buf: data which was not written because of the write failure * @offset: offset of the failed write * @len: how many bytes should have been written + * @vid: VID header + * @retry: whether the caller should retry in case of failure * * This function is called in case of a write failure and moves all good data * from the potentially bad physical eraseblock to a good physical eraseblock. * This function also writes the data which was not written due to the failure. - * Returns new physical eraseblock number in case of success, and a negative - * error code in case of failure. + * Returns 0 in case of success, and a negative error code in case of failure. + * In case of failure, the %retry parameter is set to false if this is a fatal + * error (retrying won't help), and true otherwise. */ -static int recover_peb(struct ubi_device *ubi, int pnum, int vol_id, int lnum, - const void *buf, int offset, int len) +static int try_recover_peb(struct ubi_volume *vol, int pnum, int lnum, + const void *buf, int offset, int len, + struct ubi_vid_hdr *vid_hdr, bool *retry) { - int err, idx = vol_id2idx(ubi, vol_id), new_pnum, data_size, tries = 0; - struct ubi_volume *vol = ubi->volumes[idx]; - struct ubi_vid_hdr *vid_hdr; + struct ubi_device *ubi = vol->ubi; + int new_pnum, err, vol_id = vol->vol_id, data_size; uint32_t crc; - vid_hdr = ubi_zalloc_vid_hdr(ubi, GFP_NOFS); - if (!vid_hdr) - return -ENOMEM; + *retry = false; -retry: new_pnum = ubi_wl_get_peb(ubi); if (new_pnum < 0) { - ubi_free_vid_hdr(ubi, vid_hdr); - up_read(&ubi->fm_eba_sem); - return new_pnum; + err = new_pnum; + goto out_put; } ubi_msg(ubi, "recover PEB %d, move data to PEB %d", @@ -596,7 +594,6 @@ retry: if (err && err != UBI_IO_BITFLIPS) { if (err > 0) err = -EIO; - up_read(&ubi->fm_eba_sem); goto out_put; } @@ -608,12 +605,12 @@ retry: /* 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) { - up_read(&ubi->fm_eba_sem); + if (err && err != UBI_IO_BITFLIPS) goto out_unlock; - } } + *retry = true; + memcpy(ubi->peb_buf + offset, buf, len); data_size = offset + len; @@ -623,49 +620,76 @@ retry: vid_hdr->data_size = cpu_to_be32(data_size); vid_hdr->data_crc = cpu_to_be32(crc); err = ubi_io_write_vid_hdr(ubi, new_pnum, vid_hdr); - if (err) { - mutex_unlock(&ubi->buf_mutex); - up_read(&ubi->fm_eba_sem); - goto write_error; - } + if (err) + goto out_unlock; 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_eba_sem); - goto write_error; - } +out_unlock: mutex_unlock(&ubi->buf_mutex); - ubi_free_vid_hdr(ubi, vid_hdr); - vol->eba_tbl[lnum] = new_pnum; + if (!err) + vol->eba_tbl[lnum] = new_pnum; + +out_put: up_read(&ubi->fm_eba_sem); - ubi_wl_put_peb(ubi, vol_id, lnum, pnum, 1); - ubi_msg(ubi, "data was successfully recovered"); - return 0; + if (!err) { + ubi_wl_put_peb(ubi, vol_id, lnum, pnum, 1); + ubi_msg(ubi, "data was successfully recovered"); + } else if (new_pnum >= 0) { + /* + * Bad luck? This physical eraseblock is bad too? Crud. Let's + * try to get another one. + */ + ubi_wl_put_peb(ubi, vol_id, lnum, new_pnum, 1); + ubi_warn(ubi, "failed to write to PEB %d", new_pnum); + } -out_unlock: - mutex_unlock(&ubi->buf_mutex); -out_put: - ubi_wl_put_peb(ubi, vol_id, lnum, new_pnum, 1); - ubi_free_vid_hdr(ubi, vid_hdr); return err; +} -write_error: - /* - * Bad luck? This physical eraseblock is bad too? Crud. Let's try to - * get another one. - */ - ubi_warn(ubi, "failed to write to PEB %d", new_pnum); - ubi_wl_put_peb(ubi, vol_id, lnum, new_pnum, 1); - if (++tries > UBI_IO_RETRIES) { - ubi_free_vid_hdr(ubi, vid_hdr); - return err; +/** + * recover_peb - recover from write failure. + * @ubi: UBI device description object + * @pnum: the physical eraseblock to recover + * @vol_id: volume ID + * @lnum: logical eraseblock number + * @buf: data which was not written because of the write failure + * @offset: offset of the failed write + * @len: how many bytes should have been written + * + * This function is called in case of a write failure and moves all good data + * from the potentially bad physical eraseblock to a good physical eraseblock. + * This function also writes the data which was not written due to the failure. + * Returns 0 in case of success, and a negative error code in case of failure. + * This function tries %UBI_IO_RETRIES before giving up. + */ +static int recover_peb(struct ubi_device *ubi, int pnum, int vol_id, int lnum, + const void *buf, int offset, int len) +{ + int err, idx = vol_id2idx(ubi, vol_id), tries; + struct ubi_volume *vol = ubi->volumes[idx]; + struct ubi_vid_hdr *vid_hdr; + + vid_hdr = ubi_zalloc_vid_hdr(ubi, GFP_NOFS); + if (!vid_hdr) + return -ENOMEM; + + for (tries = 0; tries <= UBI_IO_RETRIES; tries++) { + bool retry; + + err = try_recover_peb(vol, pnum, lnum, buf, offset, len, + vid_hdr, &retry); + if (!err || !retry) + break; + + ubi_msg(ubi, "try again"); } - ubi_msg(ubi, "try again"); - goto retry; + + ubi_free_vid_hdr(ubi, vid_hdr); + + return err; } /** |