nfsd4: reshuffle lease-setting code to allow reuse
[safe/jmp/linux-2.6] / drivers / mtd / mtdconcat.c
index 699fce7..db6de74 100644 (file)
@@ -6,8 +6,6 @@
  * NAND support by Christian Gan <cgan@iders.ca>
  *
  * This code is GPL
- *
- * $Id: mtdconcat.c,v 1.11 2005/11/07 11:14:20 gleixner Exp $
  */
 
 #include <linux/kernel.h>
@@ -15,6 +13,7 @@
 #include <linux/slab.h>
 #include <linux/sched.h>
 #include <linux/types.h>
+#include <linux/backing-dev.h>
 
 #include <linux/mtd/mtd.h>
 #include <linux/mtd/concat.h>
@@ -56,7 +55,7 @@ concat_read(struct mtd_info *mtd, loff_t from, size_t len,
            size_t * retlen, u_char * buf)
 {
        struct mtd_concat *concat = CONCAT(mtd);
-       int err = -EINVAL;
+       int ret = 0, err;
        int i;
 
        *retlen = 0;
@@ -80,19 +79,29 @@ concat_read(struct mtd_info *mtd, loff_t from, size_t len,
 
                err = subdev->read(subdev, from, size, &retsize, buf);
 
-               if (err)
-                       break;
+               /* Save information about bitflips! */
+               if (unlikely(err)) {
+                       if (err == -EBADMSG) {
+                               mtd->ecc_stats.failed++;
+                               ret = err;
+                       } else if (err == -EUCLEAN) {
+                               mtd->ecc_stats.corrected++;
+                               /* Do not overwrite -EBADMSG !! */
+                               if (!ret)
+                                       ret = err;
+                       } else
+                               return err;
+               }
 
                *retlen += retsize;
                len -= size;
                if (len == 0)
-                       break;
+                       return ret;
 
-               err = -EINVAL;
                buf += size;
                from = 0;
        }
-       return err;
+       return -EINVAL;
 }
 
 static int
@@ -168,7 +177,7 @@ concat_writev(struct mtd_info *mtd, const struct kvec *vecs,
 
        /* Check alignment */
        if (mtd->writesize > 1) {
-               loff_t __to = to;
+               uint64_t __to = to;
                if (do_div(__to, mtd->writesize) || (total_len % mtd->writesize))
                        return -EINVAL;
        }
@@ -189,7 +198,7 @@ concat_writev(struct mtd_info *mtd, const struct kvec *vecs,
                        continue;
                }
 
-               size = min(total_len, (size_t)(subdev->size - to));
+               size = min_t(uint64_t, total_len, subdev->size - to);
                wsize = size; /* store for future use */
 
                entry_high = entry_low;
@@ -231,101 +240,106 @@ concat_writev(struct mtd_info *mtd, const struct kvec *vecs,
 }
 
 static int
-concat_read_oob(struct mtd_info *mtd, loff_t from, size_t len,
-               size_t * retlen, u_char * buf)
+concat_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops)
 {
        struct mtd_concat *concat = CONCAT(mtd);
-       int err = -EINVAL;
-       int i;
+       struct mtd_oob_ops devops = *ops;
+       int i, err, ret = 0;
 
-       *retlen = 0;
+       ops->retlen = ops->oobretlen = 0;
 
        for (i = 0; i < concat->num_subdev; i++) {
                struct mtd_info *subdev = concat->subdev[i];
-               size_t size, retsize;
 
                if (from >= subdev->size) {
-                       /* Not destined for this subdev */
-                       size = 0;
                        from -= subdev->size;
                        continue;
                }
-               if (from + len > subdev->size)
-                       /* First part goes into this subdev */
-                       size = subdev->size - from;
-               else
-                       /* Entire transaction goes into this subdev */
-                       size = len;
 
-               if (subdev->read_oob)
-                       err = subdev->read_oob(subdev, from, size,
-                                              &retsize, buf);
-               else
-                       err = -EINVAL;
-
-               if (err)
-                       break;
+               /* partial read ? */
+               if (from + devops.len > subdev->size)
+                       devops.len = subdev->size - from;
+
+               err = subdev->read_oob(subdev, from, &devops);
+               ops->retlen += devops.retlen;
+               ops->oobretlen += devops.oobretlen;
+
+               /* Save information about bitflips! */
+               if (unlikely(err)) {
+                       if (err == -EBADMSG) {
+                               mtd->ecc_stats.failed++;
+                               ret = err;
+                       } else if (err == -EUCLEAN) {
+                               mtd->ecc_stats.corrected++;
+                               /* Do not overwrite -EBADMSG !! */
+                               if (!ret)
+                                       ret = err;
+                       } else
+                               return err;
+               }
 
-               *retlen += retsize;
-               len -= size;
-               if (len == 0)
-                       break;
+               if (devops.datbuf) {
+                       devops.len = ops->len - ops->retlen;
+                       if (!devops.len)
+                               return ret;
+                       devops.datbuf += devops.retlen;
+               }
+               if (devops.oobbuf) {
+                       devops.ooblen = ops->ooblen - ops->oobretlen;
+                       if (!devops.ooblen)
+                               return ret;
+                       devops.oobbuf += ops->oobretlen;
+               }
 
-               err = -EINVAL;
-               buf += size;
                from = 0;
        }
-       return err;
+       return -EINVAL;
 }
 
 static int
-concat_write_oob(struct mtd_info *mtd, loff_t to, size_t len,
-                size_t * retlen, const u_char * buf)
+concat_write_oob(struct mtd_info *mtd, loff_t to, struct mtd_oob_ops *ops)
 {
        struct mtd_concat *concat = CONCAT(mtd);
-       int err = -EINVAL;
-       int i;
+       struct mtd_oob_ops devops = *ops;
+       int i, err;
 
        if (!(mtd->flags & MTD_WRITEABLE))
                return -EROFS;
 
-       *retlen = 0;
+       ops->retlen = 0;
 
        for (i = 0; i < concat->num_subdev; i++) {
                struct mtd_info *subdev = concat->subdev[i];
-               size_t size, retsize;
 
                if (to >= subdev->size) {
-                       size = 0;
                        to -= subdev->size;
                        continue;
                }
-               if (to + len > subdev->size)
-                       size = subdev->size - to;
-               else
-                       size = len;
 
-               if (!(subdev->flags & MTD_WRITEABLE))
-                       err = -EROFS;
-               else if (subdev->write_oob)
-                       err = subdev->write_oob(subdev, to, size, &retsize,
-                                               buf);
-               else
-                       err = -EINVAL;
+               /* partial write ? */
+               if (to + devops.len > subdev->size)
+                       devops.len = subdev->size - to;
 
+               err = subdev->write_oob(subdev, to, &devops);
+               ops->retlen += devops.retlen;
                if (err)
-                       break;
+                       return err;
 
-               *retlen += retsize;
-               len -= size;
-               if (len == 0)
-                       break;
-
-               err = -EINVAL;
-               buf += size;
+               if (devops.datbuf) {
+                       devops.len = ops->len - ops->retlen;
+                       if (!devops.len)
+                               return 0;
+                       devops.datbuf += devops.retlen;
+               }
+               if (devops.oobbuf) {
+                       devops.ooblen = ops->ooblen - ops->oobretlen;
+                       if (!devops.ooblen)
+                               return 0;
+                       devops.oobbuf += devops.oobretlen;
+               }
                to = 0;
        }
-       return err;
+       return -EINVAL;
 }
 
 static void concat_erase_callback(struct erase_info *instr)
@@ -372,7 +386,7 @@ static int concat_erase(struct mtd_info *mtd, struct erase_info *instr)
        struct mtd_concat *concat = CONCAT(mtd);
        struct mtd_info *subdev;
        int i, err;
-       u_int32_t length, offset = 0;
+       uint64_t length, offset = 0;
        struct erase_info *erase;
 
        if (!(mtd->flags & MTD_WRITEABLE))
@@ -413,7 +427,7 @@ static int concat_erase(struct mtd_info *mtd, struct erase_info *instr)
                 * to-be-erased area begins. Verify that the starting
                 * offset is aligned to this region's erase size:
                 */
-               if (instr->addr & (erase_regions[i].erasesize - 1))
+               if (i < 0 || instr->addr & (erase_regions[i].erasesize - 1))
                        return -EINVAL;
 
                /*
@@ -426,12 +440,12 @@ static int concat_erase(struct mtd_info *mtd, struct erase_info *instr)
                /*
                 * check if the ending offset is aligned to this region's erase size
                 */
-               if ((instr->addr + instr->len) & (erase_regions[i].erasesize -
-                                                 1))
+               if (i < 0 || ((instr->addr + instr->len) &
+                                       (erase_regions[i].erasesize - 1)))
                        return -EINVAL;
        }
 
-       instr->fail_addr = 0xffffffff;
+       instr->fail_addr = MTD_FAIL_ADDR_UNKNOWN;
 
        /* make a local copy of instr to avoid modifying the caller's struct */
        erase = kmalloc(sizeof (struct erase_info), GFP_KERNEL);
@@ -480,7 +494,7 @@ static int concat_erase(struct mtd_info *mtd, struct erase_info *instr)
                        /* sanity check: should never happen since
                         * block alignment has been checked above */
                        BUG_ON(err == -EINVAL);
-                       if (erase->fail_addr != 0xffffffff)
+                       if (erase->fail_addr != MTD_FAIL_ADDR_UNKNOWN)
                                instr->fail_addr = erase->fail_addr + offset;
                        break;
                }
@@ -505,7 +519,7 @@ static int concat_erase(struct mtd_info *mtd, struct erase_info *instr)
        return 0;
 }
 
-static int concat_lock(struct mtd_info *mtd, loff_t ofs, size_t len)
+static int concat_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
 {
        struct mtd_concat *concat = CONCAT(mtd);
        int i, err = -EINVAL;
@@ -515,7 +529,7 @@ static int concat_lock(struct mtd_info *mtd, loff_t ofs, size_t len)
 
        for (i = 0; i < concat->num_subdev; i++) {
                struct mtd_info *subdev = concat->subdev[i];
-               size_t size;
+               uint64_t size;
 
                if (ofs >= subdev->size) {
                        size = 0;
@@ -543,7 +557,7 @@ static int concat_lock(struct mtd_info *mtd, loff_t ofs, size_t len)
        return err;
 }
 
-static int concat_unlock(struct mtd_info *mtd, loff_t ofs, size_t len)
+static int concat_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
 {
        struct mtd_concat *concat = CONCAT(mtd);
        int i, err = 0;
@@ -553,7 +567,7 @@ static int concat_unlock(struct mtd_info *mtd, loff_t ofs, size_t len)
 
        for (i = 0; i < concat->num_subdev; i++) {
                struct mtd_info *subdev = concat->subdev[i];
-               size_t size;
+               uint64_t size;
 
                if (ofs >= subdev->size) {
                        size = 0;
@@ -662,6 +676,8 @@ static int concat_block_markbad(struct mtd_info *mtd, loff_t ofs)
                }
 
                err = subdev->block_markbad(subdev, ofs);
+               if (!err)
+                       mtd->ecc_stats.badblocks++;
                break;
        }
 
@@ -669,6 +685,40 @@ static int concat_block_markbad(struct mtd_info *mtd, loff_t ofs)
 }
 
 /*
+ * try to support NOMMU mmaps on concatenated devices
+ * - we don't support subdev spanning as we can't guarantee it'll work
+ */
+static unsigned long concat_get_unmapped_area(struct mtd_info *mtd,
+                                             unsigned long len,
+                                             unsigned long offset,
+                                             unsigned long flags)
+{
+       struct mtd_concat *concat = CONCAT(mtd);
+       int i;
+
+       for (i = 0; i < concat->num_subdev; i++) {
+               struct mtd_info *subdev = concat->subdev[i];
+
+               if (offset >= subdev->size) {
+                       offset -= subdev->size;
+                       continue;
+               }
+
+               /* we've found the subdev over which the mapping will reside */
+               if (offset + len > subdev->size)
+                       return (unsigned long) -EINVAL;
+
+               if (subdev->get_unmapped_area)
+                       return subdev->get_unmapped_area(subdev, len, offset,
+                                                        flags);
+
+               break;
+       }
+
+       return (unsigned long) -ENOSYS;
+}
+
+/*
  * This function constructs a virtual MTD device by concatenating
  * num_devs MTD devices. A pointer to the new device object is
  * stored to *new_dev upon success. This function does _not_
@@ -676,12 +726,12 @@ static int concat_block_markbad(struct mtd_info *mtd, loff_t ofs)
  */
 struct mtd_info *mtd_concat_create(struct mtd_info *subdev[],  /* subdevices to concatenate */
                                   int num_devs,        /* number of subdevices      */
-                                  char *name)
+                                  const char *name)
 {                              /* name for the new device   */
        int i;
        size_t size;
        struct mtd_concat *concat;
-       u_int32_t max_erasesize, curr_erasesize;
+       uint32_t max_erasesize, curr_erasesize;
        int num_erase_region;
 
        printk(KERN_NOTICE "Concatenating MTD devices:\n");
@@ -691,14 +741,13 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[],     /* subdevices to c
 
        /* allocate the device structure */
        size = SIZEOF_STRUCT_MTD_CONCAT(num_devs);
-       concat = kmalloc(size, GFP_KERNEL);
+       concat = kzalloc(size, GFP_KERNEL);
        if (!concat) {
                printk
                    ("memory allocation error while creating concatenated device \"%s\"\n",
                     name);
                return NULL;
        }
-       memset(concat, 0, size);
        concat->subdev = (struct mtd_info **) (concat + 1);
 
        /*
@@ -710,9 +759,9 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[],       /* subdevices to c
        concat->mtd.size = subdev[0]->size;
        concat->mtd.erasesize = subdev[0]->erasesize;
        concat->mtd.writesize = subdev[0]->writesize;
+       concat->mtd.subpage_sft = subdev[0]->subpage_sft;
        concat->mtd.oobsize = subdev[0]->oobsize;
-       concat->mtd.ecctype = subdev[0]->ecctype;
-       concat->mtd.eccsize = subdev[0]->eccsize;
+       concat->mtd.oobavail = subdev[0]->oobavail;
        if (subdev[0]->writev)
                concat->mtd.writev = concat_writev;
        if (subdev[0]->read_oob)
@@ -724,6 +773,10 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[],      /* subdevices to c
        if (subdev[0]->block_markbad)
                concat->mtd.block_markbad = concat_block_markbad;
 
+       concat->mtd.ecc_stats.badblocks = subdev[0]->ecc_stats.badblocks;
+
+       concat->mtd.backing_dev_info = subdev[0]->backing_dev_info;
+
        concat->subdev[0] = subdev[0];
 
        for (i = 1; i < num_devs; i++) {
@@ -750,11 +803,21 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[],     /* subdevices to c
                                concat->mtd.flags |=
                                    subdev[i]->flags & MTD_WRITEABLE;
                }
+
+               /* only permit direct mapping if the BDIs are all the same
+                * - copy-mapping is still permitted
+                */
+               if (concat->mtd.backing_dev_info !=
+                   subdev[i]->backing_dev_info)
+                       concat->mtd.backing_dev_info =
+                               &default_backing_dev_info;
+
                concat->mtd.size += subdev[i]->size;
+               concat->mtd.ecc_stats.badblocks +=
+                       subdev[i]->ecc_stats.badblocks;
                if (concat->mtd.writesize   !=  subdev[i]->writesize ||
+                   concat->mtd.subpage_sft != subdev[i]->subpage_sft ||
                    concat->mtd.oobsize    !=  subdev[i]->oobsize ||
-                   concat->mtd.ecctype    !=  subdev[i]->ecctype ||
-                   concat->mtd.eccsize    !=  subdev[i]->eccsize ||
                    !concat->mtd.read_oob  != !subdev[i]->read_oob ||
                    !concat->mtd.write_oob != !subdev[i]->write_oob) {
                        kfree(concat);
@@ -766,7 +829,7 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[],       /* subdevices to c
 
        }
 
-       concat->mtd.oobinfo = subdev[0]->oobinfo;
+       concat->mtd.ecclayout = subdev[0]->ecclayout;
 
        concat->num_subdev = num_devs;
        concat->mtd.name = name;
@@ -779,6 +842,7 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[],       /* subdevices to c
        concat->mtd.unlock = concat_unlock;
        concat->mtd.suspend = concat_suspend;
        concat->mtd.resume = concat_resume;
+       concat->mtd.get_unmapped_area = concat_get_unmapped_area;
 
        /*
         * Combine the erase block size info of the subdevices:
@@ -825,12 +889,14 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[],     /* subdevices to c
                concat->mtd.erasesize = curr_erasesize;
                concat->mtd.numeraseregions = 0;
        } else {
+               uint64_t tmp64;
+
                /*
                 * erase block size varies across the subdevices: allocate
                 * space to store the data describing the variable erase regions
                 */
                struct mtd_erase_region_info *erase_region_p;
-               u_int32_t begin, position;
+               uint64_t begin, position;
 
                concat->mtd.erasesize = max_erasesize;
                concat->mtd.numeraseregions = num_erase_region;
@@ -862,8 +928,9 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[],       /* subdevices to c
                                        erase_region_p->offset = begin;
                                        erase_region_p->erasesize =
                                            curr_erasesize;
-                                       erase_region_p->numblocks =
-                                           (position - begin) / curr_erasesize;
+                                       tmp64 = position - begin;
+                                       do_div(tmp64, curr_erasesize);
+                                       erase_region_p->numblocks = tmp64;
                                        begin = position;
 
                                        curr_erasesize = subdev[i]->erasesize;
@@ -880,9 +947,9 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[],       /* subdevices to c
                                                erase_region_p->offset = begin;
                                                erase_region_p->erasesize =
                                                    curr_erasesize;
-                                               erase_region_p->numblocks =
-                                                   (position -
-                                                    begin) / curr_erasesize;
+                                               tmp64 = position - begin;
+                                               do_div(tmp64, curr_erasesize);
+                                               erase_region_p->numblocks = tmp64;
                                                begin = position;
 
                                                curr_erasesize =
@@ -892,14 +959,16 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[],     /* subdevices to c
                                        }
                                        position +=
                                            subdev[i]->eraseregions[j].
-                                           numblocks * curr_erasesize;
+                                           numblocks * (uint64_t)curr_erasesize;
                                }
                        }
                }
                /* Now write the final entry */
                erase_region_p->offset = begin;
                erase_region_p->erasesize = curr_erasesize;
-               erase_region_p->numblocks = (position - begin) / curr_erasesize;
+               tmp64 = position - begin;
+               do_div(tmp64, curr_erasesize);
+               erase_region_p->numblocks = tmp64;
        }
 
        return &concat->mtd;