mtd: blktrans: Hotplug fixes

* Add locking where it was missing.

* Don't do a get_mtd_device in blktrans_open because it would lead to a
  deadlock; instead do that in add_mtd_blktrans_dev.

* Only free the mtd_blktrans_dev structure when the last user exits.

* Flush request queue on device removal.

* Track users, and call tr->release in del_mtd_blktrans_dev
  Due to that ->open and release aren't called more that once.

Now it is safe to call del_mtd_blktrans_dev while the device is still in use.

Signed-off-by: Maxim Levitsky <maximlevitsky@gmail.com>
Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
This commit is contained in:
Maxim Levitsky 2010-02-22 20:39:30 +02:00 committed by David Woodhouse
parent a863862257
commit 048d871995
9 changed files with 148 additions and 59 deletions

View File

@ -1082,7 +1082,6 @@ static void ftl_remove_dev(struct mtd_blktrans_dev *dev)
{ {
del_mtd_blktrans_dev(dev); del_mtd_blktrans_dev(dev);
ftl_freepart((partition_t *)dev); ftl_freepart((partition_t *)dev);
kfree(dev);
} }
static struct mtd_blktrans_ops ftl_tr = { static struct mtd_blktrans_ops ftl_tr = {

View File

@ -139,7 +139,6 @@ static void inftl_remove_dev(struct mtd_blktrans_dev *dev)
kfree(inftl->PUtable); kfree(inftl->PUtable);
kfree(inftl->VUtable); kfree(inftl->VUtable);
kfree(inftl);
} }
/* /*

View File

@ -24,6 +24,40 @@
#include "mtdcore.h" #include "mtdcore.h"
static LIST_HEAD(blktrans_majors); 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;
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);
}
static int do_blktrans_request(struct mtd_blktrans_ops *tr, static int do_blktrans_request(struct mtd_blktrans_ops *tr,
@ -111,81 +145,112 @@ static int mtd_blktrans_thread(void *arg)
static void mtd_blktrans_request(struct request_queue *rq) static void mtd_blktrans_request(struct request_queue *rq)
{ {
struct mtd_blktrans_dev *dev = rq->queuedata; struct mtd_blktrans_dev *dev;
wake_up_process(dev->thread); 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) static int blktrans_open(struct block_device *bdev, fmode_t mode)
{ {
struct mtd_blktrans_dev *dev = bdev->bd_disk->private_data; struct mtd_blktrans_dev *dev = blktrans_dev_get(bdev->bd_disk);
struct mtd_blktrans_ops *tr = dev->tr; int ret;
int ret = -ENODEV;
if (!get_mtd_device(NULL, dev->mtd->index)) if (!dev)
goto out; return -ERESTARTSYS;
if (!try_module_get(tr->owner)) mutex_lock(&dev->lock);
goto out_tr;
/* FIXME: Locking. A hot pluggable device can go away if (!dev->mtd) {
(del_mtd_device can be called for it) without its module ret = -ENXIO;
being unloaded. */ goto unlock;
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);
} }
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);
return ret; return ret;
} }
static int blktrans_release(struct gendisk *disk, fmode_t mode) static int blktrans_release(struct gendisk *disk, fmode_t mode)
{ {
struct mtd_blktrans_dev *dev = disk->private_data; struct mtd_blktrans_dev *dev = blktrans_dev_get(disk);
struct mtd_blktrans_ops *tr = dev->tr; int ret = -ENXIO;
int ret = 0;
if (tr->release) if (!dev)
ret = tr->release(dev); return ret;
if (!ret) { mutex_lock(&dev->lock);
dev->mtd->usecount--;
put_mtd_device(dev->mtd);
module_put(tr->owner);
}
/* 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);
return ret; return ret;
} }
static int blktrans_getgeo(struct block_device *bdev, struct hd_geometry *geo) 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) if (!dev)
return dev->tr->getgeo(dev, geo); return ret;
return -ENOTTY;
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, static int blktrans_ioctl(struct block_device *bdev, fmode_t mode,
unsigned int cmd, unsigned long arg) unsigned int cmd, unsigned long arg)
{ {
struct mtd_blktrans_dev *dev = bdev->bd_disk->private_data; struct mtd_blktrans_dev *dev = blktrans_dev_get(bdev->bd_disk);
struct mtd_blktrans_ops *tr = dev->tr; int ret = -ENXIO;
if (!dev)
return ret;
mutex_lock(&dev->lock);
if (!dev->mtd)
goto unlock;
switch (cmd) { switch (cmd) {
case BLKFLSBUF: case BLKFLSBUF:
if (tr->flush) ret = dev->tr->flush ? dev->tr->flush(dev) : 0;
return tr->flush(dev);
/* The core code did the work, we had nothing to do. */
return 0;
default: default:
return -ENOTTY; ret = -ENOTTY;
} }
unlock:
mutex_unlock(&dev->lock);
blktrans_dev_put(dev);
return ret;
} }
static const struct block_device_operations mtd_blktrans_ops = { static const struct block_device_operations mtd_blktrans_ops = {
@ -209,6 +274,7 @@ int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new)
BUG(); BUG();
} }
mutex_lock(&blktrans_ref_mutex);
list_for_each_entry(d, &tr->devs, list) { list_for_each_entry(d, &tr->devs, list) {
if (new->devnum == -1) { if (new->devnum == -1) {
/* Use first free number */ /* Use first free number */
@ -220,6 +286,7 @@ int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new)
} }
} else if (d->devnum == new->devnum) { } else if (d->devnum == new->devnum) {
/* Required number taken */ /* Required number taken */
mutex_unlock(&blktrans_ref_mutex);
return -EBUSY; return -EBUSY;
} else if (d->devnum > new->devnum) { } else if (d->devnum > new->devnum) {
/* Required number was free */ /* Required number was free */
@ -237,16 +304,20 @@ int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new)
* minor numbers and that the disk naming code below can cope * minor numbers and that the disk naming code below can cope
* with this number. */ * with this number. */
if (new->devnum > (MINORMASK >> tr->part_bits) || if (new->devnum > (MINORMASK >> tr->part_bits) ||
(tr->part_bits && new->devnum >= 27 * 26)) (tr->part_bits && new->devnum >= 27 * 26)) {
mutex_unlock(&blktrans_ref_mutex);
goto error1; goto error1;
}
list_add_tail(&new->list, &tr->devs); list_add_tail(&new->list, &tr->devs);
added: added:
mutex_unlock(&blktrans_ref_mutex);
mutex_init(&new->lock); mutex_init(&new->lock);
kref_init(&new->ref);
if (!tr->writesect) if (!tr->writesect)
new->readonly = 1; new->readonly = 1;
/* Create gendisk */ /* Create gendisk */
ret = -ENOMEM; ret = -ENOMEM;
gd = alloc_disk(1 << tr->part_bits); gd = alloc_disk(1 << tr->part_bits);
@ -275,7 +346,6 @@ int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new)
set_capacity(gd, (new->size * tr->blksize) >> 9); set_capacity(gd, (new->size * tr->blksize) >> 9);
/* Create the request queue */ /* Create the request queue */
spin_lock_init(&new->queue_lock); spin_lock_init(&new->queue_lock);
new->rq = blk_init_queue(mtd_blktrans_request, &new->queue_lock); new->rq = blk_init_queue(mtd_blktrans_request, &new->queue_lock);
@ -292,6 +362,9 @@ int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new)
gd->queue = new->rq; gd->queue = new->rq;
__get_mtd_device(new->mtd);
__module_get(tr->owner);
/* Create processing thread */ /* Create processing thread */
/* TODO: workqueue ? */ /* TODO: workqueue ? */
new->thread = kthread_run(mtd_blktrans_thread, new, new->thread = kthread_run(mtd_blktrans_thread, new,
@ -308,6 +381,8 @@ int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new)
add_disk(gd); add_disk(gd);
return 0; return 0;
error4: error4:
module_put(tr->owner);
__put_mtd_device(new->mtd);
blk_cleanup_queue(new->rq); blk_cleanup_queue(new->rq);
error3: error3:
put_disk(new->disk); put_disk(new->disk);
@ -320,20 +395,41 @@ error1:
int del_mtd_blktrans_dev(struct mtd_blktrans_dev *old) int del_mtd_blktrans_dev(struct mtd_blktrans_dev *old)
{ {
unsigned long flags;
if (mutex_trylock(&mtd_table_mutex)) { if (mutex_trylock(&mtd_table_mutex)) {
mutex_unlock(&mtd_table_mutex); mutex_unlock(&mtd_table_mutex);
BUG(); BUG();
} }
list_del(&old->list); /* Stop new requests to arrive */
/* stop new requests to arrive */
del_gendisk(old->disk); del_gendisk(old->disk);
/* Stop the thread */ /* Stop the thread */
kthread_stop(old->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);
blk_cleanup_queue(old->rq); blk_cleanup_queue(old->rq);
/* 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);
/* At that point, we don't touch the mtd anymore */
old->mtd = NULL;
mutex_unlock(&old->lock);
blktrans_dev_put(old);
return 0; return 0;
} }
@ -396,7 +492,6 @@ int register_mtd_blktrans(struct mtd_blktrans_ops *tr)
tr->add_mtd(tr, mtd); tr->add_mtd(tr, mtd);
mutex_unlock(&mtd_table_mutex); mutex_unlock(&mtd_table_mutex);
return 0; return 0;
} }
@ -406,7 +501,6 @@ int deregister_mtd_blktrans(struct mtd_blktrans_ops *tr)
mutex_lock(&mtd_table_mutex); mutex_lock(&mtd_table_mutex);
/* Remove it from the list of active majors */ /* Remove it from the list of active majors */
list_del(&tr->list); list_del(&tr->list);

View File

@ -354,9 +354,7 @@ static void mtdblock_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
static void mtdblock_remove_dev(struct mtd_blktrans_dev *dev) static void mtdblock_remove_dev(struct mtd_blktrans_dev *dev)
{ {
struct mtdblk_dev *mtdblk = container_of(dev, struct mtdblk_dev, mbd); struct mtdblk_dev *mtdblk = container_of(dev, struct mtdblk_dev, mbd);
del_mtd_blktrans_dev(dev); del_mtd_blktrans_dev(dev);
kfree(mtdblk);
} }
static struct mtd_blktrans_ops mtdblock_tr = { static struct mtd_blktrans_ops mtdblock_tr = {

View File

@ -49,7 +49,6 @@ static void mtdblock_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
static void mtdblock_remove_dev(struct mtd_blktrans_dev *dev) static void mtdblock_remove_dev(struct mtd_blktrans_dev *dev)
{ {
del_mtd_blktrans_dev(dev); del_mtd_blktrans_dev(dev);
kfree(dev);
} }
static struct mtd_blktrans_ops mtdblock_tr = { static struct mtd_blktrans_ops mtdblock_tr = {

View File

@ -126,7 +126,6 @@ static void nftl_remove_dev(struct mtd_blktrans_dev *dev)
del_mtd_blktrans_dev(dev); del_mtd_blktrans_dev(dev);
kfree(nftl->ReplUnitTable); kfree(nftl->ReplUnitTable);
kfree(nftl->EUNtable); kfree(nftl->EUNtable);
kfree(nftl);
} }
/* /*

View File

@ -817,7 +817,6 @@ static void rfd_ftl_remove_dev(struct mtd_blktrans_dev *dev)
vfree(part->sector_map); vfree(part->sector_map);
kfree(part->header_cache); kfree(part->header_cache);
kfree(part->blocks); kfree(part->blocks);
kfree(part);
} }
static struct mtd_blktrans_ops rfd_ftl_tr = { static struct mtd_blktrans_ops rfd_ftl_tr = {

View File

@ -375,7 +375,6 @@ static void ssfdcr_remove_dev(struct mtd_blktrans_dev *dev)
del_mtd_blktrans_dev(dev); del_mtd_blktrans_dev(dev);
kfree(ssfdc->logic_block_map); kfree(ssfdc->logic_block_map);
kfree(ssfdc);
} }
static int ssfdcr_readsect(struct mtd_blktrans_dev *dev, static int ssfdcr_readsect(struct mtd_blktrans_dev *dev,

View File

@ -9,6 +9,7 @@
#define __MTD_TRANS_H__ #define __MTD_TRANS_H__
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/kref.h>
struct hd_geometry; struct hd_geometry;
struct mtd_info; struct mtd_info;
@ -24,6 +25,8 @@ struct mtd_blktrans_dev {
int devnum; int devnum;
unsigned long size; unsigned long size;
int readonly; int readonly;
int open;
struct kref ref;
struct gendisk *disk; struct gendisk *disk;
struct task_struct *thread; struct task_struct *thread;
struct request_queue *rq; struct request_queue *rq;