
From: Pete Zaitcev <zaitcev@redhat.com>



Signed-off-by: Andrew Morton <akpm@osdl.org>
---

 25-akpm/drivers/block/ub.c |   68 +++++++++++++++++++++++++++++++--------------
 1 files changed, 47 insertions(+), 21 deletions(-)

diff -puN drivers/block/ub.c~ub-atomicity-fix drivers/block/ub.c
--- 25/drivers/block/ub.c~ub-atomicity-fix	2005-03-24 21:56:32.000000000 -0800
+++ 25-akpm/drivers/block/ub.c	2005-03-24 21:56:32.000000000 -0800
@@ -300,6 +300,7 @@ struct ub_dev {
 
 /*
  */
+static void ub_cleanup(struct ub_dev *sc);
 static int ub_bd_rq_fn_1(struct ub_dev *sc, struct request *rq);
 static int ub_cmd_build_block(struct ub_dev *sc, struct ub_scsi_cmd *cmd,
     struct request *rq);
@@ -478,21 +479,47 @@ static int ub_id_get(void)
 
 static void ub_id_put(int id)
 {
+	unsigned long flags;
 
 	if (id < 0 || id >= UB_MAX_HOSTS) {
 		printk(KERN_ERR DRV_NAME ": bad host ID %d\n", id);
 		return;
 	}
+
+	spin_lock_irqsave(&ub_lock, flags);
 	if (ub_hostv[id] == 0) {
+		spin_unlock_irqrestore(&ub_lock, flags);
 		printk(KERN_ERR DRV_NAME ": freeing free host ID %d\n", id);
 		return;
 	}
 	ub_hostv[id] = 0;
+	spin_unlock_irqrestore(&ub_lock, flags);
+}
+
+/*
+ * Downcount for deallocation. This rides on two assumptions:
+ *  - once something is poisoned, its refcount cannot grow
+ *  - opens cannot happen at this time (del_gendisk was done)
+ * If the above is true, we can drop the lock, which we need for
+ * blk_cleanup_queue(): the silly thing may attempt to sleep.
+ * [Actually, it never needs to sleep for us, but it calls might_sleep()]
+ */
+static void ub_put(struct ub_dev *sc)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&ub_lock, flags);
+	--sc->openc;
+	if (sc->openc == 0 && atomic_read(&sc->poison)) {
+		spin_unlock_irqrestore(&ub_lock, flags);
+		ub_cleanup(sc);
+	} else {
+		spin_unlock_irqrestore(&ub_lock, flags);
+	}
 }
 
 /*
  * Final cleanup and deallocation.
- * This must be called with ub_lock taken.
  */
 static void ub_cleanup(struct ub_dev *sc)
 {
@@ -1531,11 +1558,7 @@ static int ub_bd_open(struct inode *inod
 	return 0;
 
 err_open:
-	spin_lock_irqsave(&ub_lock, flags);
-	--sc->openc;
-	if (sc->openc == 0 && atomic_read(&sc->poison))
-		ub_cleanup(sc);
-	spin_unlock_irqrestore(&ub_lock, flags);
+	ub_put(sc);
 	return rc;
 }
 
@@ -1545,15 +1568,8 @@ static int ub_bd_release(struct inode *i
 {
 	struct gendisk *disk = inode->i_bdev->bd_disk;
 	struct ub_dev *sc = disk->private_data;
-	unsigned long flags;
 
-	spin_lock_irqsave(&ub_lock, flags);
-	--sc->openc;
-	if (sc->openc == 0)
-		sc->first_open = 0;
-	if (sc->openc == 0 && atomic_read(&sc->poison))
-		ub_cleanup(sc);
-	spin_unlock_irqrestore(&ub_lock, flags);
+	ub_put(sc);
 	return 0;
 }
 
@@ -2048,9 +2064,7 @@ err_diag:
 	usb_set_intfdata(intf, NULL);
 	// usb_put_intf(sc->intf);
 	usb_put_dev(sc->dev);
-	spin_lock_irq(&ub_lock);
 	ub_id_put(sc->id);
-	spin_unlock_irq(&ub_lock);
 err_id:
 	kfree(sc);
 err_core:
@@ -2064,6 +2078,15 @@ static void ub_disconnect(struct usb_int
 	unsigned long flags;
 
 	/*
+	 * Prevent ub_bd_release from pulling the rug from under us.
+	 * XXX This is starting to look like a kref.
+	 * XXX Why not to take this ref at probe time?
+	 */
+	spin_lock_irqsave(&ub_lock, flags);
+	sc->openc++;
+	spin_unlock_irqrestore(&ub_lock, flags);
+
+	/*
 	 * Fence stall clearnings, operations triggered by unlinkings and so on.
 	 * We do not attempt to unlink any URBs, because we do not trust the
 	 * unlink paths in HC drivers. Also, we get -84 upon disconnect anyway.
@@ -2099,10 +2122,16 @@ static void ub_disconnect(struct usb_int
 	spin_unlock_irqrestore(&sc->lock, flags);
 
 	/*
-	 * Unregister the upper layer, this waits for all commands to end.
+	 * Unregister the upper layer.
 	 */
 	if (disk->flags & GENHD_FL_UP)
 		del_gendisk(disk);
+	/*
+	 * I wish I could do:
+	 *    set_bit(QUEUE_FLAG_DEAD, &q->queue_flags);
+	 * As it is, we rely on our internal poisoning and let
+	 * the upper levels to spin furiously failing all the I/O.
+	 */
 
 	/*
 	 * Taking a lock on a structure which is about to be freed
@@ -2138,10 +2167,7 @@ static void ub_disconnect(struct usb_int
 	usb_put_dev(sc->dev);
 	sc->dev = NULL;
 
-	spin_lock_irqsave(&ub_lock, flags);
-	if (sc->openc == 0)
-		ub_cleanup(sc);
-	spin_unlock_irqrestore(&ub_lock, flags);
+	ub_put(sc);
 }
 
 static struct usb_driver ub_driver = {
_
