Merge remote-tracking branch 'remotes/tegra/android-tegra-2.6.36-honeycomb-mr1' into...
[firefly-linux-kernel-4.4.55.git] / drivers / mtd / mtd_blkdevs.c
index 4c1ded5a9ce727c14575dadfe618c8753c0bf141..121075fd84a04dbe57faa340bdbe352602f204aa 100755 (executable)
@@ -1,7 +1,21 @@
 /*
- * (C) 2003 David Woodhouse <dwmw2@infradead.org>
+ * Interface to Linux block layer for MTD 'translation layers'.
  *
- * Interface to Linux 2.5 block layer for MTD 'translation layers'.
+ * Copyright © 2003-2010 David Woodhouse <dwmw2@infradead.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  *
  */
 
@@ -14,8 +28,8 @@
 #include <linux/mtd/mtd.h>
 #include <linux/blkdev.h>
 #include <linux/blkpg.h>
-#include <linux/freezer.h>
 #include <linux/spinlock.h>
+#include <linux/smp_lock.h>
 #include <linux/hdreg.h>
 #include <linux/init.h>
 #include <linux/mutex.h>
 #include "mtdcore.h"
 
 static LIST_HEAD(blktrans_majors);
+static DEFINE_MUTEX(blktrans_ref_mutex);
+
+void blktrans_dev_release(struct kref *kref)
+{
+       struct mtd_blktrans_dev *dev =
+               container_of(kref, struct mtd_blktrans_dev, ref);
+
+       dev->disk->private_data = NULL;
+       blk_cleanup_queue(dev->rq);
+       put_disk(dev->disk);
+       list_del(&dev->list);
+       kfree(dev);
+}
+
+static struct mtd_blktrans_dev *blktrans_dev_get(struct gendisk *disk)
+{
+       struct mtd_blktrans_dev *dev;
+
+       mutex_lock(&blktrans_ref_mutex);
+       dev = disk->private_data;
+
+       if (!dev)
+               goto unlock;
+       kref_get(&dev->ref);
+unlock:
+       mutex_unlock(&blktrans_ref_mutex);
+       return dev;
+}
+
+void blktrans_dev_put(struct mtd_blktrans_dev *dev)
+{
+       mutex_lock(&blktrans_ref_mutex);
+       kref_put(&dev->ref, blktrans_dev_release);
+       mutex_unlock(&blktrans_ref_mutex);
+}
 
-struct mtd_blkcore_priv {
-       struct task_struct *thread;
-       struct request_queue *rq;
-       spinlock_t queue_lock;
-};
 
 static int do_blktrans_request(struct mtd_blktrans_ops *tr,
                               struct mtd_blktrans_dev *dev,
@@ -59,14 +103,14 @@ static int do_blktrans_request(struct mtd_blktrans_ops *tr,
 
        buf = req->buffer;
 
-       if (!blk_fs_request(req))
+       if (req->cmd_type != REQ_TYPE_FS)
                return -EIO;
 
        if (blk_rq_pos(req) + blk_rq_cur_sectors(req) >
            get_capacity(req->rq_disk))
                return -EIO;
 
-       if (blk_discard_rq(req))
+       if (req->cmd_flags & REQ_DISCARD)
                return tr->discard(dev, block, nsect);
 
        switch(rq_data_dir(req)) {
@@ -74,17 +118,17 @@ static int do_blktrans_request(struct mtd_blktrans_ops *tr,
                //for (; nsect > 0; nsect--, block++, buf += tr->blksize)
                        if (tr->readsect(dev, block,nsect, buf))
                                return -EIO;
+               rq_flush_dcache_pages(req);
                return 0;
-
        case WRITE:
                if (!tr->writesect)
                        return -EIO;
 
+               rq_flush_dcache_pages(req);
                //for (; nsect > 0; nsect--, block++, buf += tr->blksize)
                        if (tr->writesect(dev, block,nsect, buf))
                                return -EIO;
                return 0;
-
        default:
                printk(KERN_NOTICE "Unknown request %u\n", rq_data_dir(req));
                return -EIO;
@@ -93,17 +137,13 @@ static int do_blktrans_request(struct mtd_blktrans_ops *tr,
 
 static int mtd_blktrans_thread(void *arg)
 {
-       struct mtd_blktrans_ops *tr = arg;
-       struct request_queue *rq = tr->blkcore_priv->rq;
+       struct mtd_blktrans_dev *dev = arg;
+       struct request_queue *rq = dev->rq;
        struct request *req = NULL;
 
-       /* we might get involved when memory gets low, so use PF_MEMALLOC */
-       current->flags |= PF_MEMALLOC;
-
        spin_lock_irq(rq->queue_lock);
 
        while (!kthread_should_stop()) {
-               struct mtd_blktrans_dev *dev;
                int res;
 
                if (!req && !(req = blk_fetch_request(rq))) {
@@ -114,13 +154,10 @@ static int mtd_blktrans_thread(void *arg)
                        continue;
                }
 
-               dev = req->rq_disk->private_data;
-               tr = dev->tr;
-
                spin_unlock_irq(rq->queue_lock);
 
                mutex_lock(&dev->lock);
-               res = do_blktrans_request(tr, dev, req);
+               res = do_blktrans_request(dev->tr, dev, req);
                mutex_unlock(&dev->lock);
 
                spin_lock_irq(rq->queue_lock);
@@ -139,88 +176,126 @@ static int mtd_blktrans_thread(void *arg)
 
 static void mtd_blktrans_request(struct request_queue *rq)
 {
-       struct mtd_blktrans_ops *tr = rq->queuedata;
-       wake_up_process(tr->blkcore_priv->thread);
-}
+       struct mtd_blktrans_dev *dev;
+       struct request *req = NULL;
 
+       dev = rq->queuedata;
+
+       if (!dev)
+               while ((req = blk_fetch_request(rq)) != NULL)
+                       __blk_end_request_all(req, -ENODEV);
+       else
+               wake_up_process(dev->thread);
+}
 
 static int blktrans_open(struct block_device *bdev, fmode_t mode)
 {
-       struct mtd_blktrans_dev *dev = bdev->bd_disk->private_data;
-       struct mtd_blktrans_ops *tr = dev->tr;
-       int ret = -ENODEV;
-
-       if (!get_mtd_device(NULL, dev->mtd->index))
-               goto out;
-
-       if (!try_module_get(tr->owner))
-               goto out_tr;
-
-       /* FIXME: Locking. A hot pluggable device can go away
-          (del_mtd_device can be called for it) without its module
-          being unloaded. */
-       dev->mtd->usecount++;
-
-       ret = 0;
-       if (tr->open && (ret = tr->open(dev))) {
-               dev->mtd->usecount--;
-               put_mtd_device(dev->mtd);
-       out_tr:
-               module_put(tr->owner);
+       struct mtd_blktrans_dev *dev = blktrans_dev_get(bdev->bd_disk);
+       int ret;
+
+       if (!dev)
+               return -ERESTARTSYS; /* FIXME: busy loop! -arnd*/
+
+       lock_kernel();
+       mutex_lock(&dev->lock);
+
+       if (!dev->mtd) {
+               ret = -ENXIO;
+               goto unlock;
        }
- out:
+
+       ret = !dev->open++ && dev->tr->open ? dev->tr->open(dev) : 0;
+
+       /* Take another reference on the device so it won't go away till
+               last release */
+       if (!ret)
+               kref_get(&dev->ref);
+unlock:
+       mutex_unlock(&dev->lock);
+       blktrans_dev_put(dev);
+       unlock_kernel();
        return ret;
 }
 
 static int blktrans_release(struct gendisk *disk, fmode_t mode)
 {
-       struct mtd_blktrans_dev *dev = disk->private_data;
-       struct mtd_blktrans_ops *tr = dev->tr;
-       int ret = 0;
+       struct mtd_blktrans_dev *dev = blktrans_dev_get(disk);
+       int ret = -ENXIO;
 
-       if (tr->release)
-               ret = tr->release(dev);
+       if (!dev)
+               return ret;
 
-       if (!ret) {
-               dev->mtd->usecount--;
-               put_mtd_device(dev->mtd);
-               module_put(tr->owner);
-       }
+       lock_kernel();
+       mutex_lock(&dev->lock);
+
+       /* Release one reference, we sure its not the last one here*/
+       kref_put(&dev->ref, blktrans_dev_release);
+
+       if (!dev->mtd)
+               goto unlock;
 
+       ret = !--dev->open && dev->tr->release ? dev->tr->release(dev) : 0;
+unlock:
+       mutex_unlock(&dev->lock);
+       blktrans_dev_put(dev);
+       unlock_kernel();
        return ret;
 }
 
 static int blktrans_getgeo(struct block_device *bdev, struct hd_geometry *geo)
 {
-       struct mtd_blktrans_dev *dev = bdev->bd_disk->private_data;
+       struct mtd_blktrans_dev *dev = blktrans_dev_get(bdev->bd_disk);
+       int ret = -ENXIO;
 
-       if (dev->tr->getgeo)
-               return dev->tr->getgeo(dev, geo);
-       return -ENOTTY;
+       if (!dev)
+               return ret;
+
+       mutex_lock(&dev->lock);
+
+       if (!dev->mtd)
+               goto unlock;
+
+       ret = dev->tr->getgeo ? dev->tr->getgeo(dev, geo) : 0;
+unlock:
+       mutex_unlock(&dev->lock);
+       blktrans_dev_put(dev);
+       return ret;
 }
 
 static int blktrans_ioctl(struct block_device *bdev, fmode_t mode,
                              unsigned int cmd, unsigned long arg)
 {
-       struct mtd_blktrans_dev *dev = bdev->bd_disk->private_data;
-       struct mtd_blktrans_ops *tr = dev->tr;
+       struct mtd_blktrans_dev *dev = blktrans_dev_get(bdev->bd_disk);
+       int ret = -ENXIO;
+
+       if (!dev)
+               return ret;
+
+       lock_kernel();
+       mutex_lock(&dev->lock);
+
+       if (!dev->mtd)
+               goto unlock;
 
        switch (cmd) {
        case BLKFLSBUF:
-               if (tr->flush)
-                       return tr->flush(dev);
-               /* The core code did the work, we had nothing to do. */
-               return 0;
+               ret = dev->tr->flush ? dev->tr->flush(dev) : 0;
+               break;
        default:
-               return -ENOTTY;
+               ret = -ENOTTY;
        }
+unlock:
+       mutex_unlock(&dev->lock);
+       unlock_kernel();
+       blktrans_dev_put(dev);
+       return ret;
 }
 
 static const struct block_device_operations mtd_blktrans_ops = {
        .owner          = THIS_MODULE,
        .open           = blktrans_open,
        .release        = blktrans_release,
-       .locked_ioctl   = blktrans_ioctl,
+       .ioctl          = blktrans_ioctl,
        .getgeo         = blktrans_getgeo,
 };
 
@@ -230,12 +305,14 @@ int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new)
        struct mtd_blktrans_dev *d;
        int last_devnum = -1;
        struct gendisk *gd;
+       int ret;
 
        if (mutex_trylock(&mtd_table_mutex)) {
                mutex_unlock(&mtd_table_mutex);
                BUG();
        }
 
+       mutex_lock(&blktrans_ref_mutex);
        list_for_each_entry(d, &tr->devs, list) {
                if (new->devnum == -1) {
                        /* Use first free number */
@@ -247,6 +324,7 @@ int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new)
                        }
                } else if (d->devnum == new->devnum) {
                        /* Required number taken */
+                       mutex_unlock(&blktrans_ref_mutex);
                        return -EBUSY;
                } else if (d->devnum > new->devnum) {
                        /* Required number was free */
@@ -255,24 +333,38 @@ int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new)
                }
                last_devnum = d->devnum;
        }
+
+       ret = -EBUSY;
        if (new->devnum == -1)
                new->devnum = last_devnum+1;
 
-       if ((new->devnum << tr->part_bits) > 256) {
-               return -EBUSY;
+       /* Check that the device and any partitions will get valid
+        * minor numbers and that the disk naming code below can cope
+        * with this number. */
+       if (new->devnum > (MINORMASK >> tr->part_bits) ||
+           (tr->part_bits && new->devnum >= 27 * 26)) {
+               mutex_unlock(&blktrans_ref_mutex);
+               goto error1;
        }
 
        list_add_tail(&new->list, &tr->devs);
  added:
+       mutex_unlock(&blktrans_ref_mutex);
+
        mutex_init(&new->lock);
+       kref_init(&new->ref);
        if (!tr->writesect)
                new->readonly = 1;
 
+       /* Create gendisk */
+       ret = -ENOMEM;
        gd = alloc_disk(1 << tr->part_bits);
-       if (!gd) {
-               list_del(&new->list);
-               return -ENOMEM;
-       }
+
+       if (!gd)
+               goto error2;
+
+       new->disk = gd;
+       gd->private_data = new;
        gd->major = tr->major;
        gd->first_minor = (new->devnum) << tr->part_bits;
        gd->fops = &mtd_blktrans_ops;
@@ -295,9 +387,33 @@ int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new)
        //set_capacity(gd, (new->size * tr->blksize) >> 9);
        set_capacity(gd, (new->size >> 9) * tr->blksize);   //modify by zyf for cap>=4GB 20110120
 
-       gd->private_data = new;
-       new->blkcore_priv = gd;
-       gd->queue = tr->blkcore_priv->rq;
+       /* Create the request queue */
+       spin_lock_init(&new->queue_lock);
+       new->rq = blk_init_queue(mtd_blktrans_request, &new->queue_lock);
+
+       if (!new->rq)
+               goto error3;
+
+       new->rq->queuedata = new;
+       blk_queue_logical_block_size(new->rq, tr->blksize);
+
+       if (tr->discard)
+               queue_flag_set_unlocked(QUEUE_FLAG_DISCARD,
+                                       new->rq);
+
+       gd->queue = new->rq;
+
+       __get_mtd_device(new->mtd);
+       __module_get(tr->owner);
+
+       /* Create processing thread */
+       /* TODO: workqueue ? */
+       new->thread = kthread_run(mtd_blktrans_thread, new,
+                       "%s%d", tr->name, new->mtd->index);
+       if (IS_ERR(new->thread)) {
+               ret = PTR_ERR(new->thread);
+               goto error4;
+       }
        gd->driverfs_dev = &new->mtd->dev;
 
        if (new->readonly)
@@ -305,21 +421,66 @@ int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new)
 
        add_disk(gd);
 
+       if (new->disk_attributes) {
+               ret = sysfs_create_group(&disk_to_dev(gd)->kobj,
+                                       new->disk_attributes);
+               WARN_ON(ret);
+       }
        return 0;
+error4:
+       module_put(tr->owner);
+       __put_mtd_device(new->mtd);
+       blk_cleanup_queue(new->rq);
+error3:
+       put_disk(new->disk);
+error2:
+       list_del(&new->list);
+error1:
+       kfree(new);
+       return ret;
 }
 
 int del_mtd_blktrans_dev(struct mtd_blktrans_dev *old)
 {
+       unsigned long flags;
+
        if (mutex_trylock(&mtd_table_mutex)) {
                mutex_unlock(&mtd_table_mutex);
                BUG();
        }
 
-       list_del(&old->list);
+       if (old->disk_attributes)
+               sysfs_remove_group(&disk_to_dev(old->disk)->kobj,
+                                               old->disk_attributes);
+
+       /* Stop new requests to arrive */
+       del_gendisk(old->disk);
+
+
+       /* Stop the thread */
+       kthread_stop(old->thread);
+
+       /* Kill current requests */
+       spin_lock_irqsave(&old->queue_lock, flags);
+       old->rq->queuedata = NULL;
+       blk_start_queue(old->rq);
+       spin_unlock_irqrestore(&old->queue_lock, flags);
+
+       /* Ask trans driver for release to the mtd device */
+       mutex_lock(&old->lock);
+       if (old->open && old->tr->release) {
+               old->tr->release(old);
+               old->open = 0;
+       }
+
+       __put_mtd_device(old->mtd);
+       module_put(old->tr->owner);
 
-       del_gendisk(old->blkcore_priv);
-       put_disk(old->blkcore_priv);
+       /* At that point, we don't touch the mtd anymore */
+       old->mtd = NULL;
 
+       mutex_unlock(&old->lock);
+       blktrans_dev_put(old);
        return 0;
 }
 
@@ -352,7 +513,8 @@ static struct mtd_notifier blktrans_notifier = {
 
 int register_mtd_blktrans(struct mtd_blktrans_ops *tr)
 {
-       int ret, i;
+       struct mtd_info *mtd;
+       int ret;
 
        /* Register the notifier if/when the first device type is
           registered, to prevent the link/init ordering from fucking
@@ -360,9 +522,6 @@ int register_mtd_blktrans(struct mtd_blktrans_ops *tr)
        if (!blktrans_notifier.list.next)
                register_mtd_user(&blktrans_notifier);
 
-       tr->blkcore_priv = kzalloc(sizeof(*tr->blkcore_priv), GFP_KERNEL);
-       if (!tr->blkcore_priv)
-               return -ENOMEM;
 
        mutex_lock(&mtd_table_mutex);
 
@@ -370,49 +529,20 @@ int register_mtd_blktrans(struct mtd_blktrans_ops *tr)
        if (ret) {
                printk(KERN_WARNING "Unable to register %s block device on major %d: %d\n",
                       tr->name, tr->major, ret);
-               kfree(tr->blkcore_priv);
                mutex_unlock(&mtd_table_mutex);
                return ret;
        }
-       spin_lock_init(&tr->blkcore_priv->queue_lock);
-
-       tr->blkcore_priv->rq = blk_init_queue(mtd_blktrans_request, &tr->blkcore_priv->queue_lock);
-       if (!tr->blkcore_priv->rq) {
-               unregister_blkdev(tr->major, tr->name);
-               kfree(tr->blkcore_priv);
-               mutex_unlock(&mtd_table_mutex);
-               return -ENOMEM;
-       }
-
-       tr->blkcore_priv->rq->queuedata = tr;
-       blk_queue_logical_block_size(tr->blkcore_priv->rq, tr->blksize);
-       if (tr->discard)
-               queue_flag_set_unlocked(QUEUE_FLAG_DISCARD,
-                                       tr->blkcore_priv->rq);
 
        tr->blkshift = ffs(tr->blksize) - 1;
 
-       tr->blkcore_priv->thread = kthread_run(mtd_blktrans_thread, tr,
-                       "%sd", tr->name);
-       if (IS_ERR(tr->blkcore_priv->thread)) {
-               int ret = PTR_ERR(tr->blkcore_priv->thread);
-               blk_cleanup_queue(tr->blkcore_priv->rq);
-               unregister_blkdev(tr->major, tr->name);
-               kfree(tr->blkcore_priv);
-               mutex_unlock(&mtd_table_mutex);
-               return ret;
-       }
-
        INIT_LIST_HEAD(&tr->devs);
        list_add(&tr->list, &blktrans_majors);
 
-       for (i=0; i<MAX_MTD_DEVICES; i++) {
-               if (mtd_table[i] && mtd_table[i]->type != MTD_ABSENT)
-                       tr->add_mtd(tr, mtd_table[i]);
-       }
+       mtd_for_each_device(mtd)
+               if (mtd->type != MTD_ABSENT)
+                       tr->add_mtd(tr, mtd);
 
        mutex_unlock(&mtd_table_mutex);
-
        return 0;
 }
 
@@ -422,22 +552,15 @@ int deregister_mtd_blktrans(struct mtd_blktrans_ops *tr)
 
        mutex_lock(&mtd_table_mutex);
 
-       /* Clean up the kernel thread */
-       kthread_stop(tr->blkcore_priv->thread);
-
        /* Remove it from the list of active majors */
        list_del(&tr->list);
 
        list_for_each_entry_safe(dev, next, &tr->devs, list)
                tr->remove_dev(dev);
 
-       blk_cleanup_queue(tr->blkcore_priv->rq);
        unregister_blkdev(tr->major, tr->name);
-
        mutex_unlock(&mtd_table_mutex);
 
-       kfree(tr->blkcore_priv);
-
        BUG_ON(!list_empty(&tr->devs));
        return 0;
 }