summaryrefslogtreecommitdiffstats
path: root/drivers/mtd/ubi/attach.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mtd/ubi/attach.c')
-rw-r--r--drivers/mtd/ubi/attach.c304
1 files changed, 201 insertions, 103 deletions
diff --git a/drivers/mtd/ubi/attach.c b/drivers/mtd/ubi/attach.c
index 903becd31410..93ceea4f27d5 100644
--- a/drivers/mtd/ubi/attach.c
+++ b/drivers/mtd/ubi/attach.c
@@ -91,9 +91,132 @@
static int self_check_ai(struct ubi_device *ubi, struct ubi_attach_info *ai);
-/* Temporary variables used during scanning */
-static struct ubi_ec_hdr *ech;
-static struct ubi_vid_hdr *vidh;
+#define AV_FIND BIT(0)
+#define AV_ADD BIT(1)
+#define AV_FIND_OR_ADD (AV_FIND | AV_ADD)
+
+/**
+ * find_or_add_av - internal function to find a volume, add a volume or do
+ * both (find and add if missing).
+ * @ai: attaching information
+ * @vol_id: the requested volume ID
+ * @flags: a combination of the %AV_FIND and %AV_ADD flags describing the
+ * expected operation. If only %AV_ADD is set, -EEXIST is returned
+ * if the volume already exists. If only %AV_FIND is set, NULL is
+ * returned if the volume does not exist. And if both flags are
+ * set, the helper first tries to find an existing volume, and if
+ * it does not exist it creates a new one.
+ * @created: in value used to inform the caller whether it"s a newly created
+ * volume or not.
+ *
+ * This function returns a pointer to a volume description or an ERR_PTR if
+ * the operation failed. It can also return NULL if only %AV_FIND is set and
+ * the volume does not exist.
+ */
+static struct ubi_ainf_volume *find_or_add_av(struct ubi_attach_info *ai,
+ int vol_id, unsigned int flags,
+ bool *created)
+{
+ struct ubi_ainf_volume *av;
+ struct rb_node **p = &ai->volumes.rb_node, *parent = NULL;
+
+ /* Walk the volume RB-tree to look if this volume is already present */
+ while (*p) {
+ parent = *p;
+ av = rb_entry(parent, struct ubi_ainf_volume, rb);
+
+ if (vol_id == av->vol_id) {
+ *created = false;
+
+ if (!(flags & AV_FIND))
+ return ERR_PTR(-EEXIST);
+
+ return av;
+ }
+
+ if (vol_id > av->vol_id)
+ p = &(*p)->rb_left;
+ else
+ p = &(*p)->rb_right;
+ }
+
+ if (!(flags & AV_ADD))
+ return NULL;
+
+ /* The volume is absent - add it */
+ av = kzalloc(sizeof(*av), GFP_KERNEL);
+ if (!av)
+ return ERR_PTR(-ENOMEM);
+
+ av->vol_id = vol_id;
+
+ if (vol_id > ai->highest_vol_id)
+ ai->highest_vol_id = vol_id;
+
+ rb_link_node(&av->rb, parent, p);
+ rb_insert_color(&av->rb, &ai->volumes);
+ ai->vols_found += 1;
+ *created = true;
+ dbg_bld("added volume %d", vol_id);
+ return av;
+}
+
+/**
+ * ubi_find_or_add_av - search for a volume in the attaching information and
+ * add one if it does not exist.
+ * @ai: attaching information
+ * @vol_id: the requested volume ID
+ * @created: whether the volume has been created or not
+ *
+ * This function returns a pointer to the new volume description or an
+ * ERR_PTR if the operation failed.
+ */
+static struct ubi_ainf_volume *ubi_find_or_add_av(struct ubi_attach_info *ai,
+ int vol_id, bool *created)
+{
+ return find_or_add_av(ai, vol_id, AV_FIND_OR_ADD, created);
+}
+
+/**
+ * ubi_alloc_aeb - allocate an aeb element
+ * @ai: attaching information
+ * @pnum: physical eraseblock number
+ * @ec: erase counter of the physical eraseblock
+ *
+ * Allocate an aeb object and initialize the pnum and ec information.
+ * vol_id and lnum are set to UBI_UNKNOWN, and the other fields are
+ * initialized to zero.
+ * Note that the element is not added in any list or RB tree.
+ */
+struct ubi_ainf_peb *ubi_alloc_aeb(struct ubi_attach_info *ai, int pnum,
+ int ec)
+{
+ struct ubi_ainf_peb *aeb;
+
+ aeb = kmem_cache_zalloc(ai->aeb_slab_cache, GFP_KERNEL);
+ if (!aeb)
+ return NULL;
+
+ aeb->pnum = pnum;
+ aeb->ec = ec;
+ aeb->vol_id = UBI_UNKNOWN;
+ aeb->lnum = UBI_UNKNOWN;
+
+ return aeb;
+}
+
+/**
+ * ubi_free_aeb - free an aeb element
+ * @ai: attaching information
+ * @aeb: the element to free
+ *
+ * Free an aeb object. The caller must have removed the element from any list
+ * or RB tree.
+ */
+void ubi_free_aeb(struct ubi_attach_info *ai, struct ubi_ainf_peb *aeb)
+{
+ kmem_cache_free(ai->aeb_slab_cache, aeb);
+}
/**
* add_to_list - add physical eraseblock to a list.
@@ -131,14 +254,12 @@ static int add_to_list(struct ubi_attach_info *ai, int pnum, int vol_id,
} else
BUG();
- aeb = kmem_cache_alloc(ai->aeb_slab_cache, GFP_KERNEL);
+ aeb = ubi_alloc_aeb(ai, pnum, ec);
if (!aeb)
return -ENOMEM;
- aeb->pnum = pnum;
aeb->vol_id = vol_id;
aeb->lnum = lnum;
- aeb->ec = ec;
if (to_head)
list_add(&aeb->u.list, list);
else
@@ -163,13 +284,11 @@ static int add_corrupted(struct ubi_attach_info *ai, int pnum, int ec)
dbg_bld("add to corrupted: PEB %d, EC %d", pnum, ec);
- aeb = kmem_cache_alloc(ai->aeb_slab_cache, GFP_KERNEL);
+ aeb = ubi_alloc_aeb(ai, pnum, ec);
if (!aeb)
return -ENOMEM;
ai->corr_peb_count += 1;
- aeb->pnum = pnum;
- aeb->ec = ec;
list_add(&aeb->u.list, &ai->corr);
return 0;
}
@@ -192,14 +311,12 @@ static int add_fastmap(struct ubi_attach_info *ai, int pnum,
{
struct ubi_ainf_peb *aeb;
- aeb = kmem_cache_alloc(ai->aeb_slab_cache, GFP_KERNEL);
+ aeb = ubi_alloc_aeb(ai, pnum, ec);
if (!aeb)
return -ENOMEM;
- aeb->pnum = pnum;
- aeb->vol_id = be32_to_cpu(vidh->vol_id);
- aeb->sqnum = be64_to_cpu(vidh->sqnum);
- aeb->ec = ec;
+ aeb->vol_id = be32_to_cpu(vid_hdr->vol_id);
+ aeb->sqnum = be64_to_cpu(vid_hdr->sqnum);
list_add(&aeb->u.list, &ai->fastmap);
dbg_bld("add to fastmap list: PEB %d, vol_id %d, sqnum: %llu", pnum,
@@ -294,44 +411,20 @@ static struct ubi_ainf_volume *add_volume(struct ubi_attach_info *ai,
const struct ubi_vid_hdr *vid_hdr)
{
struct ubi_ainf_volume *av;
- struct rb_node **p = &ai->volumes.rb_node, *parent = NULL;
+ bool created;
ubi_assert(vol_id == be32_to_cpu(vid_hdr->vol_id));
- /* Walk the volume RB-tree to look if this volume is already present */
- while (*p) {
- parent = *p;
- av = rb_entry(parent, struct ubi_ainf_volume, rb);
-
- if (vol_id == av->vol_id)
- return av;
-
- if (vol_id > av->vol_id)
- p = &(*p)->rb_left;
- else
- p = &(*p)->rb_right;
- }
-
- /* The volume is absent - add it */
- av = kmalloc(sizeof(struct ubi_ainf_volume), GFP_KERNEL);
- if (!av)
- return ERR_PTR(-ENOMEM);
+ av = ubi_find_or_add_av(ai, vol_id, &created);
+ if (IS_ERR(av) || !created)
+ return av;
- av->highest_lnum = av->leb_count = 0;
- av->vol_id = vol_id;
- av->root = RB_ROOT;
av->used_ebs = be32_to_cpu(vid_hdr->used_ebs);
av->data_pad = be32_to_cpu(vid_hdr->data_pad);
av->compat = vid_hdr->compat;
av->vol_type = vid_hdr->vol_type == UBI_VID_DYNAMIC ? UBI_DYNAMIC_VOLUME
: UBI_STATIC_VOLUME;
- if (vol_id > ai->highest_vol_id)
- ai->highest_vol_id = vol_id;
- rb_link_node(&av->rb, parent, p);
- rb_insert_color(&av->rb, &ai->volumes);
- ai->vols_found += 1;
- dbg_bld("added volume %d", vol_id);
return av;
}
@@ -360,7 +453,7 @@ int ubi_compare_lebs(struct ubi_device *ubi, const struct ubi_ainf_peb *aeb,
{
int len, err, second_is_newer, bitflips = 0, corrupted = 0;
uint32_t data_crc, crc;
- struct ubi_vid_hdr *vh = NULL;
+ struct ubi_vid_io_buf *vidb = NULL;
unsigned long long sqnum2 = be64_to_cpu(vid_hdr->sqnum);
if (sqnum2 == aeb->sqnum) {
@@ -403,12 +496,12 @@ int ubi_compare_lebs(struct ubi_device *ubi, const struct ubi_ainf_peb *aeb,
return bitflips << 1;
}
- vh = ubi_zalloc_vid_hdr(ubi, GFP_KERNEL);
- if (!vh)
+ vidb = ubi_alloc_vid_buf(ubi, GFP_KERNEL);
+ if (!vidb)
return -ENOMEM;
pnum = aeb->pnum;
- err = ubi_io_read_vid_hdr(ubi, pnum, vh, 0);
+ err = ubi_io_read_vid_hdr(ubi, pnum, vidb, 0);
if (err) {
if (err == UBI_IO_BITFLIPS)
bitflips = 1;
@@ -422,7 +515,7 @@ int ubi_compare_lebs(struct ubi_device *ubi, const struct ubi_ainf_peb *aeb,
}
}
- vid_hdr = vh;
+ vid_hdr = ubi_get_vid_hdr(vidb);
}
/* Read the data of the copy and check the CRC */
@@ -448,7 +541,7 @@ int ubi_compare_lebs(struct ubi_device *ubi, const struct ubi_ainf_peb *aeb,
}
mutex_unlock(&ubi->buf_mutex);
- ubi_free_vid_hdr(ubi, vh);
+ ubi_free_vid_buf(vidb);
if (second_is_newer)
dbg_bld("second PEB %d is newer, copy_flag is set", pnum);
@@ -460,7 +553,7 @@ int ubi_compare_lebs(struct ubi_device *ubi, const struct ubi_ainf_peb *aeb,
out_unlock:
mutex_unlock(&ubi->buf_mutex);
out_free_vidh:
- ubi_free_vid_hdr(ubi, vh);
+ ubi_free_vid_buf(vidb);
return err;
}
@@ -605,12 +698,10 @@ int ubi_add_to_av(struct ubi_device *ubi, struct ubi_attach_info *ai, int pnum,
if (err)
return err;
- aeb = kmem_cache_alloc(ai->aeb_slab_cache, GFP_KERNEL);
+ aeb = ubi_alloc_aeb(ai, pnum, ec);
if (!aeb)
return -ENOMEM;
- aeb->ec = ec;
- aeb->pnum = pnum;
aeb->vol_id = vol_id;
aeb->lnum = lnum;
aeb->scrub = bitflips;
@@ -629,6 +720,21 @@ int ubi_add_to_av(struct ubi_device *ubi, struct ubi_attach_info *ai, int pnum,
}
/**
+ * ubi_add_av - add volume to the attaching information.
+ * @ai: attaching information
+ * @vol_id: the requested volume ID
+ *
+ * This function returns a pointer to the new volume description or an
+ * ERR_PTR if the operation failed.
+ */
+struct ubi_ainf_volume *ubi_add_av(struct ubi_attach_info *ai, int vol_id)
+{
+ bool created;
+
+ return find_or_add_av(ai, vol_id, AV_ADD, &created);
+}
+
+/**
* ubi_find_av - find volume in the attaching information.
* @ai: attaching information
* @vol_id: the requested volume ID
@@ -639,24 +745,15 @@ int ubi_add_to_av(struct ubi_device *ubi, struct ubi_attach_info *ai, int pnum,
struct ubi_ainf_volume *ubi_find_av(const struct ubi_attach_info *ai,
int vol_id)
{
- struct ubi_ainf_volume *av;
- struct rb_node *p = ai->volumes.rb_node;
-
- while (p) {
- av = rb_entry(p, struct ubi_ainf_volume, rb);
-
- if (vol_id == av->vol_id)
- return av;
-
- if (vol_id > av->vol_id)
- p = p->rb_left;
- else
- p = p->rb_right;
- }
+ bool created;
- return NULL;
+ return find_or_add_av((struct ubi_attach_info *)ai, vol_id, AV_FIND,
+ &created);
}
+static void destroy_av(struct ubi_attach_info *ai, struct ubi_ainf_volume *av,
+ struct list_head *list);
+
/**
* ubi_remove_av - delete attaching information about a volume.
* @ai: attaching information
@@ -664,19 +761,10 @@ struct ubi_ainf_volume *ubi_find_av(const struct ubi_attach_info *ai,
*/
void ubi_remove_av(struct ubi_attach_info *ai, struct ubi_ainf_volume *av)
{
- struct rb_node *rb;
- struct ubi_ainf_peb *aeb;
-
dbg_bld("remove attaching information about volume %d", av->vol_id);
- while ((rb = rb_first(&av->root))) {
- aeb = rb_entry(rb, struct ubi_ainf_peb, u.rb);
- rb_erase(&aeb->u.rb, &av->root);
- list_add_tail(&aeb->u.list, &ai->erase);
- }
-
rb_erase(&av->rb, &ai->volumes);
- kfree(av);
+ destroy_av(ai, av, &ai->erase);
ai->vols_found -= 1;
}
@@ -866,6 +954,9 @@ static bool vol_ignored(int vol_id)
static int scan_peb(struct ubi_device *ubi, struct ubi_attach_info *ai,
int pnum, bool fast)
{
+ struct ubi_ec_hdr *ech = ai->ech;
+ struct ubi_vid_io_buf *vidb = ai->vidb;
+ struct ubi_vid_hdr *vidh = ubi_get_vid_hdr(vidb);
long long ec;
int err, bitflips = 0, vol_id = -1, ec_err = 0;
@@ -963,7 +1054,7 @@ static int scan_peb(struct ubi_device *ubi, struct ubi_attach_info *ai,
/* OK, we've done with the EC header, let's look at the VID header */
- err = ubi_io_read_vid_hdr(ubi, pnum, vidh, 0);
+ err = ubi_io_read_vid_hdr(ubi, pnum, vidb, 0);
if (err < 0)
return err;
switch (err) {
@@ -1191,10 +1282,12 @@ static int late_analysis(struct ubi_device *ubi, struct ubi_attach_info *ai)
* destroy_av - free volume attaching information.
* @av: volume attaching information
* @ai: attaching information
+ * @list: put the aeb elements in there if !NULL, otherwise free them
*
* This function destroys the volume attaching information.
*/
-static void destroy_av(struct ubi_attach_info *ai, struct ubi_ainf_volume *av)
+static void destroy_av(struct ubi_attach_info *ai, struct ubi_ainf_volume *av,
+ struct list_head *list)
{
struct ubi_ainf_peb *aeb;
struct rb_node *this = av->root.rb_node;
@@ -1214,7 +1307,10 @@ static void destroy_av(struct ubi_attach_info *ai, struct ubi_ainf_volume *av)
this->rb_right = NULL;
}
- kmem_cache_free(ai->aeb_slab_cache, aeb);
+ if (list)
+ list_add_tail(&aeb->u.list, list);
+ else
+ ubi_free_aeb(ai, aeb);
}
}
kfree(av);
@@ -1232,23 +1328,23 @@ static void destroy_ai(struct ubi_attach_info *ai)
list_for_each_entry_safe(aeb, aeb_tmp, &ai->alien, u.list) {
list_del(&aeb->u.list);
- kmem_cache_free(ai->aeb_slab_cache, aeb);
+ ubi_free_aeb(ai, aeb);
}
list_for_each_entry_safe(aeb, aeb_tmp, &ai->erase, u.list) {
list_del(&aeb->u.list);
- kmem_cache_free(ai->aeb_slab_cache, aeb);
+ ubi_free_aeb(ai, aeb);
}
list_for_each_entry_safe(aeb, aeb_tmp, &ai->corr, u.list) {
list_del(&aeb->u.list);
- kmem_cache_free(ai->aeb_slab_cache, aeb);
+ ubi_free_aeb(ai, aeb);
}
list_for_each_entry_safe(aeb, aeb_tmp, &ai->free, u.list) {
list_del(&aeb->u.list);
- kmem_cache_free(ai->aeb_slab_cache, aeb);
+ ubi_free_aeb(ai, aeb);
}
list_for_each_entry_safe(aeb, aeb_tmp, &ai->fastmap, u.list) {
list_del(&aeb->u.list);
- kmem_cache_free(ai->aeb_slab_cache, aeb);
+ ubi_free_aeb(ai, aeb);
}
/* Destroy the volume RB-tree */
@@ -1269,7 +1365,7 @@ static void destroy_ai(struct ubi_attach_info *ai)
rb->rb_right = NULL;
}
- destroy_av(ai, av);
+ destroy_av(ai, av, NULL);
}
}
@@ -1297,12 +1393,12 @@ static int scan_all(struct ubi_device *ubi, struct ubi_attach_info *ai,
err = -ENOMEM;
- ech = kzalloc(ubi->ec_hdr_alsize, GFP_KERNEL);
- if (!ech)
+ ai->ech = kzalloc(ubi->ec_hdr_alsize, GFP_KERNEL);
+ if (!ai->ech)
return err;
- vidh = ubi_zalloc_vid_hdr(ubi, GFP_KERNEL);
- if (!vidh)
+ ai->vidb = ubi_alloc_vid_buf(ubi, GFP_KERNEL);
+ if (!ai->vidb)
goto out_ech;
for (pnum = start; pnum < ubi->peb_count; pnum++) {
@@ -1351,15 +1447,15 @@ static int scan_all(struct ubi_device *ubi, struct ubi_attach_info *ai,
if (err)
goto out_vidh;
- ubi_free_vid_hdr(ubi, vidh);
- kfree(ech);
+ ubi_free_vid_buf(ai->vidb);
+ kfree(ai->ech);
return 0;
out_vidh:
- ubi_free_vid_hdr(ubi, vidh);
+ ubi_free_vid_buf(ai->vidb);
out_ech:
- kfree(ech);
+ kfree(ai->ech);
return err;
}
@@ -1411,12 +1507,12 @@ static int scan_fast(struct ubi_device *ubi, struct ubi_attach_info **ai)
if (!scan_ai)
goto out;
- ech = kzalloc(ubi->ec_hdr_alsize, GFP_KERNEL);
- if (!ech)
+ scan_ai->ech = kzalloc(ubi->ec_hdr_alsize, GFP_KERNEL);
+ if (!scan_ai->ech)
goto out_ai;
- vidh = ubi_zalloc_vid_hdr(ubi, GFP_KERNEL);
- if (!vidh)
+ scan_ai->vidb = ubi_alloc_vid_buf(ubi, GFP_KERNEL);
+ if (!scan_ai->vidb)
goto out_ech;
for (pnum = 0; pnum < UBI_FM_MAX_START; pnum++) {
@@ -1428,8 +1524,8 @@ static int scan_fast(struct ubi_device *ubi, struct ubi_attach_info **ai)
goto out_vidh;
}
- ubi_free_vid_hdr(ubi, vidh);
- kfree(ech);
+ ubi_free_vid_buf(scan_ai->vidb);
+ kfree(scan_ai->ech);
if (scan_ai->force_full_scan)
err = UBI_NO_FASTMAP;
@@ -1449,9 +1545,9 @@ static int scan_fast(struct ubi_device *ubi, struct ubi_attach_info **ai)
return err;
out_vidh:
- ubi_free_vid_hdr(ubi, vidh);
+ ubi_free_vid_buf(scan_ai->vidb);
out_ech:
- kfree(ech);
+ kfree(scan_ai->ech);
out_ai:
destroy_ai(scan_ai);
out:
@@ -1573,6 +1669,8 @@ out_ai:
*/
static int self_check_ai(struct ubi_device *ubi, struct ubi_attach_info *ai)
{
+ struct ubi_vid_io_buf *vidb = ai->vidb;
+ struct ubi_vid_hdr *vidh = ubi_get_vid_hdr(vidb);
int pnum, err, vols_found = 0;
struct rb_node *rb1, *rb2;
struct ubi_ainf_volume *av;
@@ -1708,7 +1806,7 @@ static int self_check_ai(struct ubi_device *ubi, struct ubi_attach_info *ai)
last_aeb = aeb;
- err = ubi_io_read_vid_hdr(ubi, aeb->pnum, vidh, 1);
+ err = ubi_io_read_vid_hdr(ubi, aeb->pnum, vidb, 1);
if (err && err != UBI_IO_BITFLIPS) {
ubi_err(ubi, "VID header is not OK (%d)",
err);