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 @@ -553,6 +553,102 @@ int ubi_eba_read_leb_sg(struct ubi_device *ubi, struct ubi_volume *vol, return ret; } +/** + * try_recover_peb - try to recover from write failure. + * @vol: volume description object + * @pnum: the physical eraseblock to recover + * @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 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 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) +{ + struct ubi_device *ubi = vol->ubi; + int new_pnum, err, vol_id = vol->vol_id, data_size; + uint32_t crc; + + *retry = false; + + new_pnum = ubi_wl_get_peb(ubi); + if (new_pnum < 0) { + err = new_pnum; + goto out_put; + } + + ubi_msg(ubi, "recover PEB %d, move data to PEB %d", + pnum, new_pnum); + + err = ubi_io_read_vid_hdr(ubi, pnum, vid_hdr, 1); + if (err && err != UBI_IO_BITFLIPS) { + if (err > 0) + err = -EIO; + goto out_put; + } + + ubi_assert(vid_hdr->vol_type == UBI_VID_DYNAMIC); + + mutex_lock(&ubi->buf_mutex); + memset(ubi->peb_buf + offset, 0xFF, len); + + /* 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) + goto out_unlock; + } + + *retry = true; + + memcpy(ubi->peb_buf + offset, buf, len); + + data_size = offset + len; + crc = crc32(UBI_CRC32_INIT, ubi->peb_buf, data_size); + vid_hdr->sqnum = cpu_to_be64(ubi_next_sqnum(ubi)); + vid_hdr->copy_flag = 1; + 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) + goto out_unlock; + + err = ubi_io_write_data(ubi, ubi->peb_buf, new_pnum, 0, data_size); + +out_unlock: + mutex_unlock(&ubi->buf_mutex); + + if (!err) + vol->eba_tbl[lnum] = new_pnum; + +out_put: + up_read(&ubi->fm_eba_sem); + + 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); + } + + return err; +} + /** * recover_peb - recover from write failure. * @ubi: UBI device description object @@ -566,106 +662,34 @@ int ubi_eba_read_leb_sg(struct ubi_device *ubi, struct ubi_volume *vol, * 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. + * 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), new_pnum, data_size, tries = 0; + int err, idx = vol_id2idx(ubi, vol_id), tries; struct ubi_volume *vol = ubi->volumes[idx]; struct ubi_vid_hdr *vid_hdr; - uint32_t crc; vid_hdr = ubi_zalloc_vid_hdr(ubi, GFP_NOFS); if (!vid_hdr) return -ENOMEM; -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; + 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, "recover PEB %d, move data to PEB %d", - pnum, new_pnum); - - err = ubi_io_read_vid_hdr(ubi, pnum, vid_hdr, 1); - if (err && err != UBI_IO_BITFLIPS) { - if (err > 0) - err = -EIO; - up_read(&ubi->fm_eba_sem); - goto out_put; - } - - ubi_assert(vid_hdr->vol_type == UBI_VID_DYNAMIC); - - mutex_lock(&ubi->buf_mutex); - memset(ubi->peb_buf + offset, 0xFF, len); - - /* 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); - goto out_unlock; - } - } - - memcpy(ubi->peb_buf + offset, buf, len); - - data_size = offset + len; - crc = crc32(UBI_CRC32_INIT, ubi->peb_buf, data_size); - vid_hdr->sqnum = cpu_to_be64(ubi_next_sqnum(ubi)); - vid_hdr->copy_flag = 1; - 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; - } - - 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; - } - - mutex_unlock(&ubi->buf_mutex); ubi_free_vid_hdr(ubi, vid_hdr); - vol->eba_tbl[lnum] = new_pnum; - 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; - -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; - } - ubi_msg(ubi, "try again"); - goto retry; } /**