Subject: blkfront: fixes for 'xm block-detach ... --force' Prevent prematurely freeing 'struct blkfront_info' instances (when the xenbus data structures are gone, but the Linux ones are still needed). Prevent adding a disk with the same (major, minor) [and hence the same name and sysfs entries, which leads to oopses] when the previous instance wasn't fully de-allocated yet. This still doesn't address all issues resulting from forced detach: I/O submitted after the detach still blocks forever, likely preventing subsequent un-mounting from completing. It's not clear to me (not knowing much about the block layer) how this can be avoided. Signed-off-by: Jan Beulich --- drivers/block/xen-blkfront.c | 83 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 78 insertions(+), 5 deletions(-) --- linux-2.6.33-rc4/drivers/block/xen-blkfront.c 2010-01-18 10:43:21.000000000 +0100 +++ 2.6.33-rc4-xen-blkfront-forced-detach/drivers/block/xen-blkfront.c 2010-01-18 11:14:20.000000000 +0100 @@ -103,6 +103,10 @@ struct blkfront_info static DEFINE_SPINLOCK(blkif_io_lock); +static unsigned int nr_minors; +static unsigned long *minors; +static DEFINE_SPINLOCK(minor_lock); + #define MAXIMUM_OUTSTANDING_BLOCK_REQS \ (BLKIF_MAX_SEGMENTS_PER_REQUEST * BLK_RING_SIZE) #define GRANT_INVALID_REF 0 @@ -137,6 +141,55 @@ static void add_id_to_freelist(struct bl info->shadow_free = id; } +static int xlbd_reserve_minors(unsigned int minor, unsigned int nr) +{ + unsigned int end = minor + nr; + int rc; + + if (end > nr_minors) { + unsigned long *bitmap, *old; + + bitmap = kzalloc(BITS_TO_LONGS(end) * sizeof(*bitmap), + GFP_KERNEL); + if (bitmap == NULL) + return -ENOMEM; + + spin_lock(&minor_lock); + if (end > nr_minors) { + old = minors; + memcpy(bitmap, minors, + BITS_TO_LONGS(nr_minors) * sizeof(*bitmap)); + minors = bitmap; + nr_minors = BITS_TO_LONGS(end) * BITS_PER_LONG; + } else + old = bitmap; + spin_unlock(&minor_lock); + kfree(old); + } + + spin_lock(&minor_lock); + if (find_next_bit(minors, end, minor) >= end) { + for (; minor < end; ++minor) + __set_bit(minor, minors); + rc = 0; + } else + rc = -EBUSY; + spin_unlock(&minor_lock); + + return rc; +} + +static void xlbd_release_minors(unsigned int minor, unsigned int nr) +{ + unsigned int end = minor + nr; + + BUG_ON(end > nr_minors); + spin_lock(&minor_lock); + for (; minor < end; ++minor) + __clear_bit(minor, minors); + spin_unlock(&minor_lock); +} + static void blkif_restart_queue_callback(void *arg) { struct blkfront_info *info = (struct blkfront_info *)arg; @@ -417,9 +470,14 @@ static int xlvbd_alloc_gendisk(blkif_sec if ((minor % nr_parts) == 0) nr_minors = nr_parts; + err = xlbd_reserve_minors(minor, nr_minors); + if (err) + goto out; + err = -ENODEV; + gd = alloc_disk(nr_minors); if (gd == NULL) - goto out; + goto release; offset = minor / nr_parts; @@ -450,7 +508,7 @@ static int xlvbd_alloc_gendisk(blkif_sec if (xlvbd_init_blk_queue(gd, sector_size)) { del_gendisk(gd); - goto out; + goto release; } info->rq = gd->queue; @@ -470,6 +528,8 @@ static int xlvbd_alloc_gendisk(blkif_sec return 0; + release: + xlbd_release_minors(minor, nr_minors); out: return err; } @@ -924,6 +984,7 @@ static void blkfront_connect(struct blkf static void blkfront_closing(struct xenbus_device *dev) { struct blkfront_info *info = dev_get_drvdata(&dev->dev); + unsigned int minor, nr_minors; unsigned long flags; dev_dbg(&dev->dev, "blkfront_closing: %s removed\n", dev->nodename); @@ -946,7 +1007,10 @@ static void blkfront_closing(struct xenb blk_cleanup_queue(info->rq); info->rq = NULL; + minor = info->gd->first_minor; + nr_minors = info->gd->minors; del_gendisk(info->gd); + xlbd_release_minors(minor, nr_minors); out: xenbus_frontend_closed(dev); @@ -1004,7 +1068,10 @@ static int blkfront_remove(struct xenbus blkif_free(info, 0); - kfree(info); + if(info->users == 0) + kfree(info); + else + info->is_ready = -1; return 0; } @@ -1013,12 +1080,15 @@ static int blkfront_is_ready(struct xenb { struct blkfront_info *info = dev_get_drvdata(&dev->dev); - return info->is_ready; + return info->is_ready > 0; } static int blkif_open(struct block_device *bdev, fmode_t mode) { struct blkfront_info *info = bdev->bd_disk->private_data; + + if(info->is_ready < 0) + return -ENODEV; info->users++; return 0; } @@ -1034,7 +1104,10 @@ static int blkif_release(struct gendisk struct xenbus_device *dev = info->xbdev; enum xenbus_state state = xenbus_read_driver_state(dev->otherend); - if (state == XenbusStateClosing && info->is_ready) + if(info->is_ready < 0) { + blkfront_closing(dev); + kfree(info); + } else if (state == XenbusStateClosing && info->is_ready) blkfront_closing(dev); } return 0;