UBI: Fastmap: Rework fastmap error paths
[deliverable/linux.git] / drivers / mtd / ubi / fastmap.c
index 8b95c48a002d651cf0c63ce89c499260268f638a..db2625d3ecd9e8342ec1583304f40d7e8a3934c7 100644 (file)
@@ -136,14 +136,15 @@ static struct ubi_ainf_volume *add_vol(struct ubi_attach_info *ai, int vol_id,
        if (!av)
                goto out;
 
-       av->highest_lnum = av->leb_count = 0;
+       av->highest_lnum = av->leb_count = av->used_ebs = 0;
        av->vol_id = vol_id;
-       av->used_ebs = used_ebs;
        av->data_pad = data_pad;
        av->last_data_size = last_eb_bytes;
        av->compat = 0;
        av->vol_type = vol_type;
        av->root = RB_ROOT;
+       if (av->vol_type == UBI_STATIC_VOLUME)
+               av->used_ebs = used_ebs;
 
        dbg_bld("found volume (ID %i)", vol_id);
 
@@ -362,6 +363,7 @@ static void unmap_peb(struct ubi_attach_info *ai, int pnum)
                        aeb = rb_entry(node2, struct ubi_ainf_peb, u.rb);
                        if (aeb->pnum == pnum) {
                                rb_erase(&aeb->u.rb, &av->root);
+                               av->leb_count--;
                                kmem_cache_free(ai->aeb_slab_cache, aeb);
                                return;
                        }
@@ -798,7 +800,7 @@ int ubi_scan_fastmap(struct ubi_device *ubi, struct ubi_attach_info *ai,
        __be32 crc, tmp_crc;
        unsigned long long sqnum = 0;
 
-       mutex_lock(&ubi->fm_mutex);
+       down_write(&ubi->fm_protect);
        memset(ubi->fm_buf, 0, ubi->fm_size);
 
        fmsb = kmalloc(sizeof(*fmsb), GFP_KERNEL);
@@ -989,7 +991,7 @@ int ubi_scan_fastmap(struct ubi_device *ubi, struct ubi_attach_info *ai,
        ubi_free_vid_hdr(ubi, vh);
        kfree(ech);
 out:
-       mutex_unlock(&ubi->fm_mutex);
+       up_write(&ubi->fm_protect);
        if (ret == UBI_BAD_FASTMAP)
                ubi_err(ubi, "Attach by fastmap failed, doing a full scan!");
        return ret;
@@ -1298,31 +1300,87 @@ out:
 /**
  * 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;
+
+       ret = -ENOSPC;
+       e = ubi_wl_get_fm_peb(ubi, 1);
+       if (!e)
+               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 */
+       /*
+        * 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;
+               }
+       }
 }
 
 /**
@@ -1334,28 +1392,28 @@ 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;
 
-       mutex_lock(&ubi->fm_mutex);
+       down_write(&ubi->fm_protect);
 
        ubi_refill_pools(ubi);
 
        if (ubi->ro_mode || ubi->fm_disabled) {
-               mutex_unlock(&ubi->fm_mutex);
+               up_write(&ubi->fm_protect);
                return 0;
        }
 
        ret = ubi_ensure_anchor_pebs(ubi);
        if (ret) {
-               mutex_unlock(&ubi->fm_mutex);
+               up_write(&ubi->fm_protect);
                return ret;
        }
 
        new_fm = kzalloc(sizeof(*new_fm), GFP_KERNEL);
        if (!new_fm) {
-               mutex_unlock(&ubi->fm_mutex);
+               up_write(&ubi->fm_protect);
                return -ENOMEM;
        }
 
@@ -1374,34 +1432,49 @@ 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");
-
-                       for (j = 1; j < i; j++)
-                               ubi_wl_put_fm_peb(ubi, new_fm->e[j], j, 0);
-
-                       ret = -ENOSPC;
-                       goto err;
-               } else if (!tmp_e && old_fm) {
-                       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);
+               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);
+                                               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");
+
+                               for (j = 1; j < i; j++) {
+                                       ubi_wl_put_fm_peb(ubi, new_fm->e[j], j, 0);
+                                       new_fm->e[j] = NULL;
+                               }
 
-                               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)
+                       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;
+                       }
+               }
+       }
+
+       /* Old fastmap is larger than the new one */
+       if (old_fm && new_fm->used_blocks < old_fm->used_blocks) {
+               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;
                }
        }
 
@@ -1414,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;
@@ -1445,31 +1522,32 @@ int ubi_update_fastmap(struct ubi_device *ubi)
        }
 
        down_write(&ubi->work_sem);
-       down_write(&ubi->fm_sem);
+       down_write(&ubi->fm_eba_sem);
        ret = ubi_write_fastmap(ubi, new_fm);
-       up_write(&ubi->fm_sem);
+       up_write(&ubi->fm_eba_sem);
        up_write(&ubi->work_sem);
 
        if (ret)
                goto err;
 
 out_unlock:
-       mutex_unlock(&ubi->fm_mutex);
+       up_write(&ubi->fm_protect);
        kfree(old_fm);
        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!");
-               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;
 }
This page took 0.034755 seconds and 5 git commands to generate.