block: simplify the block device claiming interface
[linux-2.6-microblaze.git] / fs / block_dev.c
index 6016777..0569f5e 100644 (file)
@@ -110,24 +110,20 @@ EXPORT_SYMBOL(invalidate_bdev);
 int truncate_bdev_range(struct block_device *bdev, fmode_t mode,
                        loff_t lstart, loff_t lend)
 {
-       struct block_device *claimed_bdev = NULL;
-       int err;
-
        /*
         * If we don't hold exclusive handle for the device, upgrade to it
         * while we discard the buffer cache to avoid discarding buffers
         * under live filesystem.
         */
        if (!(mode & FMODE_EXCL)) {
-               claimed_bdev = bdev->bd_contains;
-               err = bd_prepare_to_claim(bdev, claimed_bdev,
-                                         truncate_bdev_range);
+               int err = bd_prepare_to_claim(bdev, truncate_bdev_range);
                if (err)
                        return err;
        }
+
        truncate_inode_pages_range(bdev->bd_inode->i_mapping, lstart, lend);
-       if (claimed_bdev)
-               bd_abort_claiming(bdev, claimed_bdev, truncate_bdev_range);
+       if (!(mode & FMODE_EXCL))
+               bd_abort_claiming(bdev, truncate_bdev_range);
        return 0;
 }
 EXPORT_SYMBOL(truncate_bdev_range);
@@ -863,34 +859,47 @@ void __init bdev_cache_init(void)
        blockdev_superblock = bd_mnt->mnt_sb;   /* For writeback */
 }
 
-static struct block_device *bdget(dev_t dev)
+struct block_device *bdev_alloc(struct gendisk *disk, u8 partno)
 {
        struct block_device *bdev;
        struct inode *inode;
 
-       inode = iget_locked(blockdev_superblock, dev);
+       inode = new_inode(blockdev_superblock);
        if (!inode)
                return NULL;
-
-       bdev = &BDEV_I(inode)->bdev;
-
-       if (inode->i_state & I_NEW) {
-               spin_lock_init(&bdev->bd_size_lock);
-               bdev->bd_contains = NULL;
-               bdev->bd_super = NULL;
-               bdev->bd_inode = inode;
-               bdev->bd_part_count = 0;
-               bdev->bd_dev = dev;
-               inode->i_mode = S_IFBLK;
-               inode->i_rdev = dev;
-               inode->i_bdev = bdev;
-               inode->i_data.a_ops = &def_blk_aops;
-               mapping_set_gfp_mask(&inode->i_data, GFP_USER);
-               unlock_new_inode(inode);
-       }
+       inode->i_mode = S_IFBLK;
+       inode->i_rdev = 0;
+       inode->i_data.a_ops = &def_blk_aops;
+       mapping_set_gfp_mask(&inode->i_data, GFP_USER);
+
+       bdev = I_BDEV(inode);
+       spin_lock_init(&bdev->bd_size_lock);
+       bdev->bd_disk = disk;
+       bdev->bd_partno = partno;
+       bdev->bd_super = NULL;
+       bdev->bd_inode = inode;
+       bdev->bd_part_count = 0;
        return bdev;
 }
 
+void bdev_add(struct block_device *bdev, dev_t dev)
+{
+       bdev->bd_dev = dev;
+       bdev->bd_inode->i_rdev = dev;
+       bdev->bd_inode->i_ino = dev;
+       insert_inode_hash(bdev->bd_inode);
+}
+
+static struct block_device *bdget(dev_t dev)
+{
+       struct inode *inode;
+
+       inode = ilookup(blockdev_superblock, dev);
+       if (!inode)
+               return NULL;
+       return &BDEV_I(inode)->bdev;
+}
+
 /**
  * bdgrab -- Grab a reference to an already referenced block device
  * @bdev:      Block device to grab a reference to.
@@ -928,67 +937,8 @@ void bdput(struct block_device *bdev)
 {
        iput(bdev->bd_inode);
 }
-
 EXPORT_SYMBOL(bdput);
  
-static struct block_device *bd_acquire(struct inode *inode)
-{
-       struct block_device *bdev;
-
-       spin_lock(&bdev_lock);
-       bdev = inode->i_bdev;
-       if (bdev && !inode_unhashed(bdev->bd_inode)) {
-               bdgrab(bdev);
-               spin_unlock(&bdev_lock);
-               return bdev;
-       }
-       spin_unlock(&bdev_lock);
-
-       /*
-        * i_bdev references block device inode that was already shut down
-        * (corresponding device got removed).  Remove the reference and look
-        * up block device inode again just in case new device got
-        * reestablished under the same device number.
-        */
-       if (bdev)
-               bd_forget(inode);
-
-       bdev = bdget(inode->i_rdev);
-       if (bdev) {
-               spin_lock(&bdev_lock);
-               if (!inode->i_bdev) {
-                       /*
-                        * We take an additional reference to bd_inode,
-                        * and it's released in clear_inode() of inode.
-                        * So, we can access it via ->i_mapping always
-                        * without igrab().
-                        */
-                       bdgrab(bdev);
-                       inode->i_bdev = bdev;
-                       inode->i_mapping = bdev->bd_inode->i_mapping;
-               }
-               spin_unlock(&bdev_lock);
-       }
-       return bdev;
-}
-
-/* Call when you free inode */
-
-void bd_forget(struct inode *inode)
-{
-       struct block_device *bdev = NULL;
-
-       spin_lock(&bdev_lock);
-       if (!sb_is_blkdev_sb(inode->i_sb))
-               bdev = inode->i_bdev;
-       inode->i_bdev = NULL;
-       inode->i_mapping = &inode->i_data;
-       spin_unlock(&bdev_lock);
-
-       if (bdev)
-               bdput(bdev);
-}
-
 /**
  * bd_may_claim - test whether a block device can be claimed
  * @bdev: block device of interest
@@ -1024,7 +974,6 @@ static bool bd_may_claim(struct block_device *bdev, struct block_device *whole,
 /**
  * bd_prepare_to_claim - claim a block device
  * @bdev: block device of interest
- * @whole: the whole device containing @bdev, may equal @bdev
  * @holder: holder trying to claim @bdev
  *
  * Claim @bdev.  This function fails if @bdev is already claimed by another
@@ -1034,9 +983,12 @@ static bool bd_may_claim(struct block_device *bdev, struct block_device *whole,
  * RETURNS:
  * 0 if @bdev can be claimed, -EBUSY otherwise.
  */
-int bd_prepare_to_claim(struct block_device *bdev, struct block_device *whole,
-               void *holder)
+int bd_prepare_to_claim(struct block_device *bdev, void *holder)
 {
+       struct block_device *whole = bdev_whole(bdev);
+
+       if (WARN_ON_ONCE(!holder))
+               return -EINVAL;
 retry:
        spin_lock(&bdev_lock);
        /* if someone else claimed, fail */
@@ -1064,27 +1016,6 @@ retry:
 }
 EXPORT_SYMBOL_GPL(bd_prepare_to_claim); /* only for the loop driver */
 
-static struct gendisk *bdev_get_gendisk(struct block_device *bdev, int *partno)
-{
-       struct gendisk *disk = get_gendisk(bdev->bd_dev, partno);
-
-       if (!disk)
-               return NULL;
-       /*
-        * Now that we hold gendisk reference we make sure bdev we looked up is
-        * not stale. If it is, it means device got removed and created before
-        * we looked up gendisk and we fail open in such case. Associating
-        * unhashed bdev with newly created gendisk could lead to two bdevs
-        * (and thus two independent caches) being associated with one device
-        * which is bad.
-        */
-       if (inode_unhashed(bdev->bd_inode)) {
-               put_disk_and_module(disk);
-               return NULL;
-       }
-       return disk;
-}
-
 static void bd_clear_claiming(struct block_device *whole, void *holder)
 {
        lockdep_assert_held(&bdev_lock);
@@ -1097,15 +1028,15 @@ static void bd_clear_claiming(struct block_device *whole, void *holder)
 /**
  * bd_finish_claiming - finish claiming of a block device
  * @bdev: block device of interest
- * @whole: whole block device
  * @holder: holder that has claimed @bdev
  *
  * Finish exclusive open of a block device. Mark the device as exlusively
  * open by the holder and wake up all waiters for exclusive open to finish.
  */
-static void bd_finish_claiming(struct block_device *bdev,
-               struct block_device *whole, void *holder)
+static void bd_finish_claiming(struct block_device *bdev, void *holder)
 {
+       struct block_device *whole = bdev_whole(bdev);
+
        spin_lock(&bdev_lock);
        BUG_ON(!bd_may_claim(bdev, whole, holder));
        /*
@@ -1130,11 +1061,10 @@ static void bd_finish_claiming(struct block_device *bdev,
  * also used when exclusive open is not actually desired and we just needed
  * to block other exclusive openers for a while.
  */
-void bd_abort_claiming(struct block_device *bdev, struct block_device *whole,
-                      void *holder)
+void bd_abort_claiming(struct block_device *bdev, void *holder)
 {
        spin_lock(&bdev_lock);
-       bd_clear_claiming(whole, holder);
+       bd_clear_claiming(bdev_whole(bdev), holder);
        spin_unlock(&bdev_lock);
 }
 EXPORT_SYMBOL(bd_abort_claiming);
@@ -1407,66 +1337,21 @@ EXPORT_SYMBOL_GPL(bdev_disk_changed);
  *  mutex_lock(part->bd_mutex)
  *    mutex_lock_nested(whole->bd_mutex, 1)
  */
-
-static int __blkdev_get(struct block_device *bdev, fmode_t mode, void *holder,
-               int for_part)
+static int __blkdev_get(struct block_device *bdev, fmode_t mode)
 {
-       struct block_device *whole = NULL, *claiming = NULL;
-       struct gendisk *disk;
+       struct gendisk *disk = bdev->bd_disk;
        int ret;
-       int partno;
-       bool first_open = false, unblock_events = true, need_restart;
-
- restart:
-       need_restart = false;
-       ret = -ENXIO;
-       disk = bdev_get_gendisk(bdev, &partno);
-       if (!disk)
-               goto out;
 
-       if (partno) {
-               whole = bdget_disk(disk, 0);
-               if (!whole) {
-                       ret = -ENOMEM;
-                       goto out_put_disk;
-               }
-       }
-
-       if (!for_part && (mode & FMODE_EXCL)) {
-               WARN_ON_ONCE(!holder);
-               if (whole)
-                       claiming = whole;
-               else
-                       claiming = bdev;
-               ret = bd_prepare_to_claim(bdev, claiming, holder);
-               if (ret)
-                       goto out_put_whole;
-       }
-
-       disk_block_events(disk);
-       mutex_lock_nested(&bdev->bd_mutex, for_part);
        if (!bdev->bd_openers) {
-               first_open = true;
-               bdev->bd_disk = disk;
-               bdev->bd_contains = bdev;
-               bdev->bd_partno = partno;
-
-               if (!partno) {
+               if (!bdev_is_partition(bdev)) {
                        ret = -ENXIO;
-                       bdev->bd_part = disk_get_part(disk, partno);
+                       bdev->bd_part = disk_get_part(disk, 0);
                        if (!bdev->bd_part)
                                goto out_clear;
 
                        ret = 0;
-                       if (disk->fops->open) {
+                       if (disk->fops->open)
                                ret = disk->fops->open(bdev, mode);
-                               /*
-                                * If we lost a race with 'disk' being deleted,
-                                * try again.  See md.c
-                                */
-                               if (ret == -ERESTARTSYS)
-                                       need_restart = true;
-                       }
 
                        if (!ret) {
                                bd_set_nr_sectors(bdev, get_capacity(disk));
@@ -1486,14 +1371,23 @@ static int __blkdev_get(struct block_device *bdev, fmode_t mode, void *holder,
                        if (ret)
                                goto out_clear;
                } else {
-                       BUG_ON(for_part);
-                       ret = __blkdev_get(whole, mode, NULL, 1);
-                       if (ret)
+                       struct block_device *whole = bdget_disk(disk, 0);
+
+                       mutex_lock_nested(&whole->bd_mutex, 1);
+                       ret = __blkdev_get(whole, mode);
+                       if (ret) {
+                               mutex_unlock(&whole->bd_mutex);
+                               bdput(whole);
                                goto out_clear;
-                       bdev->bd_contains = bdgrab(whole);
-                       bdev->bd_part = disk_get_part(disk, partno);
+                       }
+                       whole->bd_part_count++;
+                       mutex_unlock(&whole->bd_mutex);
+
+                       bdev->bd_part = disk_get_part(disk, bdev->bd_partno);
                        if (!(disk->flags & GENHD_FL_UP) ||
                            !bdev->bd_part || !bdev->bd_part->nr_sects) {
+                               __blkdev_put(whole, mode, 1);
+                               bdput(whole);
                                ret = -ENXIO;
                                goto out_clear;
                        }
@@ -1504,7 +1398,7 @@ static int __blkdev_get(struct block_device *bdev, fmode_t mode, void *holder,
                if (bdev->bd_bdi == &noop_backing_dev_info)
                        bdev->bd_bdi = bdi_get(disk->queue->backing_dev_info);
        } else {
-               if (bdev->bd_contains == bdev) {
+               if (!bdev_is_partition(bdev)) {
                        ret = 0;
                        if (bdev->bd_disk->fops->open)
                                ret = bdev->bd_disk->fops->open(bdev, mode);
@@ -1513,180 +1407,188 @@ static int __blkdev_get(struct block_device *bdev, fmode_t mode, void *holder,
                            (!ret || ret == -ENOMEDIUM))
                                bdev_disk_changed(bdev, ret == -ENOMEDIUM);
                        if (ret)
-                               goto out_unlock_bdev;
+                               return ret;
                }
        }
        bdev->bd_openers++;
-       if (for_part)
-               bdev->bd_part_count++;
-       if (claiming)
-               bd_finish_claiming(bdev, claiming, holder);
-
-       /*
-        * Block event polling for write claims if requested.  Any write holder
-        * makes the write_holder state stick until all are released.  This is
-        * good enough and tracking individual writeable reference is too
-        * fragile given the way @mode is used in blkdev_get/put().
-        */
-       if (claiming && (mode & FMODE_WRITE) && !bdev->bd_write_holder &&
-           (disk->flags & GENHD_FL_BLOCK_EVENTS_ON_EXCL_WRITE)) {
-               bdev->bd_write_holder = true;
-               unblock_events = false;
-       }
-       mutex_unlock(&bdev->bd_mutex);
-
-       if (unblock_events)
-               disk_unblock_events(disk);
-
-       /* only one opener holds refs to the module and disk */
-       if (!first_open)
-               put_disk_and_module(disk);
-       if (whole)
-               bdput(whole);
        return 0;
 
  out_clear:
        disk_put_part(bdev->bd_part);
-       bdev->bd_disk = NULL;
        bdev->bd_part = NULL;
-       if (bdev != bdev->bd_contains)
-               __blkdev_put(bdev->bd_contains, mode, 1);
-       bdev->bd_contains = NULL;
- out_unlock_bdev:
-       if (claiming)
-               bd_abort_claiming(bdev, claiming, holder);
-       mutex_unlock(&bdev->bd_mutex);
-       disk_unblock_events(disk);
- out_put_whole:
-       if (whole)
-               bdput(whole);
- out_put_disk:
-       put_disk_and_module(disk);
-       if (need_restart)
-               goto restart;
- out:
        return ret;
 }
 
-/**
- * blkdev_get - open a block device
- * @bdev: block_device to open
- * @mode: FMODE_* mask
- * @holder: exclusive holder identifier
- *
- * Open @bdev with @mode.  If @mode includes %FMODE_EXCL, @bdev is
- * open with exclusive access.  Specifying %FMODE_EXCL with %NULL
- * @holder is invalid.  Exclusive opens may nest for the same @holder.
- *
- * On success, the reference count of @bdev is unchanged.  On failure,
- * @bdev is put.
- *
- * CONTEXT:
- * Might sleep.
- *
- * RETURNS:
- * 0 on success, -errno on failure.
- */
-static int blkdev_get(struct block_device *bdev, fmode_t mode, void *holder)
+struct block_device *blkdev_get_no_open(dev_t dev)
 {
-       int ret, perm = 0;
+       struct block_device *bdev;
+       struct gendisk *disk;
 
-       if (mode & FMODE_READ)
-               perm |= MAY_READ;
-       if (mode & FMODE_WRITE)
-               perm |= MAY_WRITE;
-       ret = devcgroup_inode_permission(bdev->bd_inode, perm);
-       if (ret)
-               goto bdput;
+       down_read(&bdev_lookup_sem);
+       bdev = bdget(dev);
+       if (!bdev) {
+               up_read(&bdev_lookup_sem);
+               blk_request_module(dev);
+               down_read(&bdev_lookup_sem);
+
+               bdev = bdget(dev);
+               if (!bdev)
+                       goto unlock;
+       }
 
-       ret =__blkdev_get(bdev, mode, holder, 0);
-       if (ret)
+       disk = bdev->bd_disk;
+       if (!kobject_get_unless_zero(&disk_to_dev(disk)->kobj))
                goto bdput;
-       return 0;
-
+       if ((disk->flags & (GENHD_FL_UP | GENHD_FL_HIDDEN)) != GENHD_FL_UP)
+               goto put_disk;
+       if (!try_module_get(bdev->bd_disk->fops->owner))
+               goto put_disk;
+       up_read(&bdev_lookup_sem);
+       return bdev;
+put_disk:
+       put_disk(disk);
 bdput:
        bdput(bdev);
-       return ret;
+unlock:
+       up_read(&bdev_lookup_sem);
+       return NULL;
+}
+
+void blkdev_put_no_open(struct block_device *bdev)
+{
+       module_put(bdev->bd_disk->fops->owner);
+       put_disk(bdev->bd_disk);
+       bdput(bdev);
 }
 
 /**
- * blkdev_get_by_path - open a block device by name
- * @path: path to the block device to open
+ * blkdev_get_by_dev - open a block device by device number
+ * @dev: device number of block device to open
  * @mode: FMODE_* mask
  * @holder: exclusive holder identifier
  *
- * Open the blockdevice described by the device file at @path.  @mode
- * and @holder are identical to blkdev_get().
+ * Open the block device described by device number @dev. If @mode includes
+ * %FMODE_EXCL, the block device is opened with exclusive access.  Specifying
+ * %FMODE_EXCL with a %NULL @holder is invalid.  Exclusive opens may nest for
+ * the same @holder.
  *
- * On success, the returned block_device has reference count of one.
+ * Use this interface ONLY if you really do not have anything better - i.e. when
+ * you are behind a truly sucky interface and all you are given is a device
+ * number.  Everything else should use blkdev_get_by_path().
  *
  * CONTEXT:
  * Might sleep.
  *
  * RETURNS:
- * Pointer to block_device on success, ERR_PTR(-errno) on failure.
+ * Reference to the block_device on success, ERR_PTR(-errno) on failure.
  */
-struct block_device *blkdev_get_by_path(const char *path, fmode_t mode,
-                                       void *holder)
+struct block_device *blkdev_get_by_dev(dev_t dev, fmode_t mode, void *holder)
 {
+       bool unblock_events = true;
        struct block_device *bdev;
-       int err;
+       struct gendisk *disk;
+       int ret;
 
-       bdev = lookup_bdev(path);
-       if (IS_ERR(bdev))
-               return bdev;
+       ret = devcgroup_check_permission(DEVCG_DEV_BLOCK,
+                       MAJOR(dev), MINOR(dev),
+                       ((mode & FMODE_READ) ? DEVCG_ACC_READ : 0) |
+                       ((mode & FMODE_WRITE) ? DEVCG_ACC_WRITE : 0));
+       if (ret)
+               return ERR_PTR(ret);
 
-       err = blkdev_get(bdev, mode, holder);
-       if (err)
-               return ERR_PTR(err);
+       /*
+        * If we lost a race with 'disk' being deleted, try again.  See md.c.
+        */
+retry:
+       bdev = blkdev_get_no_open(dev);
+       if (!bdev)
+               return ERR_PTR(-ENXIO);
+       disk = bdev->bd_disk;
 
-       if ((mode & FMODE_WRITE) && bdev_read_only(bdev)) {
-               blkdev_put(bdev, mode);
-               return ERR_PTR(-EACCES);
+       if (mode & FMODE_EXCL) {
+               ret = bd_prepare_to_claim(bdev, holder);
+               if (ret)
+                       goto put_blkdev;
+       }
+
+       disk_block_events(disk);
+
+       mutex_lock(&bdev->bd_mutex);
+       ret =__blkdev_get(bdev, mode);
+       if (ret)
+               goto abort_claiming;
+       if (mode & FMODE_EXCL) {
+               bd_finish_claiming(bdev, holder);
+
+               /*
+                * Block event polling for write claims if requested.  Any write
+                * holder makes the write_holder state stick until all are
+                * released.  This is good enough and tracking individual
+                * writeable reference is too fragile given the way @mode is
+                * used in blkdev_get/put().
+                */
+               if ((mode & FMODE_WRITE) && !bdev->bd_write_holder &&
+                   (disk->flags & GENHD_FL_BLOCK_EVENTS_ON_EXCL_WRITE)) {
+                       bdev->bd_write_holder = true;
+                       unblock_events = false;
+               }
        }
+       mutex_unlock(&bdev->bd_mutex);
 
+       if (unblock_events)
+               disk_unblock_events(disk);
        return bdev;
+
+abort_claiming:
+       if (mode & FMODE_EXCL)
+               bd_abort_claiming(bdev, holder);
+       mutex_unlock(&bdev->bd_mutex);
+       disk_unblock_events(disk);
+put_blkdev:
+       blkdev_put_no_open(bdev);
+       if (ret == -ERESTARTSYS)
+               goto retry;
+       return ERR_PTR(ret);
 }
-EXPORT_SYMBOL(blkdev_get_by_path);
+EXPORT_SYMBOL(blkdev_get_by_dev);
 
 /**
- * blkdev_get_by_dev - open a block device by device number
- * @dev: device number of block device to open
+ * blkdev_get_by_path - open a block device by name
+ * @path: path to the block device to open
  * @mode: FMODE_* mask
  * @holder: exclusive holder identifier
  *
- * Open the blockdevice described by device number @dev.  @mode and
- * @holder are identical to blkdev_get().
- *
- * Use it ONLY if you really do not have anything better - i.e. when
- * you are behind a truly sucky interface and all you are given is a
- * device number.  _Never_ to be used for internal purposes.  If you
- * ever need it - reconsider your API.
- *
- * On success, the returned block_device has reference count of one.
+ * Open the block device described by the device file at @path.  If @mode
+ * includes %FMODE_EXCL, the block device is opened with exclusive access.
+ * Specifying %FMODE_EXCL with a %NULL @holder is invalid.  Exclusive opens may
+ * nest for the same @holder.
  *
  * CONTEXT:
  * Might sleep.
  *
  * RETURNS:
- * Pointer to block_device on success, ERR_PTR(-errno) on failure.
+ * Reference to the block_device on success, ERR_PTR(-errno) on failure.
  */
-struct block_device *blkdev_get_by_dev(dev_t dev, fmode_t mode, void *holder)
+struct block_device *blkdev_get_by_path(const char *path, fmode_t mode,
+                                       void *holder)
 {
        struct block_device *bdev;
-       int err;
+       dev_t dev;
+       int error;
 
-       bdev = bdget(dev);
-       if (!bdev)
-               return ERR_PTR(-ENOMEM);
+       error = lookup_bdev(path, &dev);
+       if (error)
+               return ERR_PTR(error);
 
-       err = blkdev_get(bdev, mode, holder);
-       if (err)
-               return ERR_PTR(err);
+       bdev = blkdev_get_by_dev(dev, mode, holder);
+       if (!IS_ERR(bdev) && (mode & FMODE_WRITE) && bdev_read_only(bdev)) {
+               blkdev_put(bdev, mode);
+               return ERR_PTR(-EACCES);
+       }
 
        return bdev;
 }
-EXPORT_SYMBOL(blkdev_get_by_dev);
+EXPORT_SYMBOL(blkdev_get_by_path);
 
 static int blkdev_open(struct inode * inode, struct file * filp)
 {
@@ -1709,14 +1611,12 @@ static int blkdev_open(struct inode * inode, struct file * filp)
        if ((filp->f_flags & O_ACCMODE) == 3)
                filp->f_mode |= FMODE_WRITE_IOCTL;
 
-       bdev = bd_acquire(inode);
-       if (bdev == NULL)
-               return -ENOMEM;
-
+       bdev = blkdev_get_by_dev(inode->i_rdev, filp->f_mode, filp);
+       if (IS_ERR(bdev))
+               return PTR_ERR(bdev);
        filp->f_mapping = bdev->bd_inode->i_mapping;
        filp->f_wb_err = filemap_sample_wb_err(filp->f_mapping);
-
-       return blkdev_get(bdev, filp->f_mode, filp);
+       return 0;
 }
 
 static void __blkdev_put(struct block_device *bdev, fmode_t mode, int for_part)
@@ -1749,27 +1649,27 @@ static void __blkdev_put(struct block_device *bdev, fmode_t mode, int for_part)
 
                disk_put_part(bdev->bd_part);
                bdev->bd_part = NULL;
-               bdev->bd_disk = NULL;
                if (bdev_is_partition(bdev))
-                       victim = bdev->bd_contains;
-               bdev->bd_contains = NULL;
-
-               put_disk_and_module(disk);
+                       victim = bdev_whole(bdev);
        } else {
                if (!bdev_is_partition(bdev) && disk->fops->release)
                        disk->fops->release(disk, mode);
        }
        mutex_unlock(&bdev->bd_mutex);
-       bdput(bdev);
-       if (victim)
+       if (victim) {
                __blkdev_put(victim, mode, 1);
+               bdput(victim);
+       }
 }
 
 void blkdev_put(struct block_device *bdev, fmode_t mode)
 {
+       struct gendisk *disk = bdev->bd_disk;
+
        mutex_lock(&bdev->bd_mutex);
 
        if (mode & FMODE_EXCL) {
+               struct block_device *whole = bdev_whole(bdev);
                bool bdev_free;
 
                /*
@@ -1780,13 +1680,12 @@ void blkdev_put(struct block_device *bdev, fmode_t mode)
                spin_lock(&bdev_lock);
 
                WARN_ON_ONCE(--bdev->bd_holders < 0);
-               WARN_ON_ONCE(--bdev->bd_contains->bd_holders < 0);
+               WARN_ON_ONCE(--whole->bd_holders < 0);
 
-               /* bd_contains might point to self, check in a separate step */
                if ((bdev_free = !bdev->bd_holders))
                        bdev->bd_holder = NULL;
-               if (!bdev->bd_contains->bd_holders)
-                       bdev->bd_contains->bd_holder = NULL;
+               if (!whole->bd_holders)
+                       whole->bd_holder = NULL;
 
                spin_unlock(&bdev_lock);
 
@@ -1795,7 +1694,7 @@ void blkdev_put(struct block_device *bdev, fmode_t mode)
                 * unblock evpoll if it was a write holder.
                 */
                if (bdev_free && bdev->bd_write_holder) {
-                       disk_unblock_events(bdev->bd_disk);
+                       disk_unblock_events(disk);
                        bdev->bd_write_holder = false;
                }
        }
@@ -1805,11 +1704,11 @@ void blkdev_put(struct block_device *bdev, fmode_t mode)
         * event.  This is to ensure detection of media removal commanded
         * from userland - e.g. eject(1).
         */
-       disk_flush_events(bdev->bd_disk, DISK_EVENT_MEDIA_CHANGE);
-
+       disk_flush_events(disk, DISK_EVENT_MEDIA_CHANGE);
        mutex_unlock(&bdev->bd_mutex);
 
        __blkdev_put(bdev, mode, 0);
+       blkdev_put_no_open(bdev);
 }
 EXPORT_SYMBOL(blkdev_put);
 
@@ -2022,37 +1921,32 @@ const struct file_operations def_blk_fops = {
  * namespace if possible and return it.  Return ERR_PTR(error)
  * otherwise.
  */
-struct block_device *lookup_bdev(const char *pathname)
+int lookup_bdev(const char *pathname, dev_t *dev)
 {
-       struct block_device *bdev;
        struct inode *inode;
        struct path path;
        int error;
 
        if (!pathname || !*pathname)
-               return ERR_PTR(-EINVAL);
+               return -EINVAL;
 
        error = kern_path(pathname, LOOKUP_FOLLOW, &path);
        if (error)
-               return ERR_PTR(error);
+               return error;
 
        inode = d_backing_inode(path.dentry);
        error = -ENOTBLK;
        if (!S_ISBLK(inode->i_mode))
-               goto fail;
+               goto out_path_put;
        error = -EACCES;
        if (!may_open_dev(&path))
-               goto fail;
-       error = -ENOMEM;
-       bdev = bd_acquire(inode);
-       if (!bdev)
-               goto fail;
-out:
+               goto out_path_put;
+
+       *dev = inode->i_rdev;
+       error = 0;
+out_path_put:
        path_put(&path);
-       return bdev;
-fail:
-       bdev = ERR_PTR(error);
-       goto out;
+       return error;
 }
 EXPORT_SYMBOL(lookup_bdev);