aboutsummaryrefslogtreecommitdiffstats
path: root/usb/usb-storage-fix-race-between-reset-and-disconnect.patch
blob: 60454e66dfc39f97a8eff62af5f36515461c45ff (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
From stern@rowland.harvard.edu Mon Jun 19 11:50:19 2006
Date: Mon, 19 Jun 2006 14:50:15 -0400 (EDT)
From: Alan Stern <stern@rowland.harvard.edu>
To: Greg KH <greg@kroah.com>
cc: Matthew Dharm <mdharm-usb@one-eyed-alien.net>, USB Storage list <usb-storage@lists.one-eyed-alien.net>
Subject: usb-storage: fix race between reset and disconnect
Message-ID: <Pine.LNX.4.44L0.0606191442070.5722-100000@iolanthe.rowland.org>

My recent patch converting usb-storage to use
usb_reset_composite_device() added a bug, a race between reset and
disconnect.  It was necessary to drop the private lock while executing a
reset, and if a disconnect occurs at that time it will cause a crash.

This patch (as722) fixes the problem by explicitly checking for an early
termination after executing each command.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>

---
 drivers/usb/storage/usb.c |   22 ++++++++++++++--------
 1 file changed, 14 insertions(+), 8 deletions(-)

--- gregkh-2.6.orig/drivers/usb/storage/usb.c
+++ gregkh-2.6/drivers/usb/storage/usb.c
@@ -373,8 +373,12 @@ static int usb_stor_control_thread(void 
 		/* lock access to the state */
 		scsi_lock(host);
 
+		/* did the command already complete because of a disconnect? */
+		if (!us->srb)
+			;		/* nothing to do */
+
 		/* indicate that the command is done */
-		if (us->srb->result != DID_ABORT << 16) {
+		else if (us->srb->result != DID_ABORT << 16) {
 			US_DEBUGP("scsi cmd done, result=0x%x\n", 
 				   us->srb->result);
 			us->srb->scsi_done(us->srb);
@@ -836,32 +840,34 @@ static void dissociate_dev(struct us_dat
  * the host */
 static void quiesce_and_remove_host(struct us_data *us)
 {
+	struct Scsi_Host *host = us_to_host(us);
+
 	/* Prevent new USB transfers, stop the current command, and
 	 * interrupt a SCSI-scan or device-reset delay */
+	scsi_lock(host);
 	set_bit(US_FLIDX_DISCONNECTING, &us->flags);
+	scsi_unlock(host);
 	usb_stor_stop_transport(us);
 	wake_up(&us->delay_wait);
 
 	/* It doesn't matter if the SCSI-scanning thread is still running.
 	 * The thread will exit when it sees the DISCONNECTING flag. */
 
-	/* Wait for the current command to finish, then remove the host */
-	mutex_lock(&us->dev_mutex);
-	mutex_unlock(&us->dev_mutex);
-
 	/* queuecommand won't accept any new commands and the control
 	 * thread won't execute a previously-queued command.  If there
 	 * is such a command pending, complete it with an error. */
+	mutex_lock(&us->dev_mutex);
 	if (us->srb) {
 		us->srb->result = DID_NO_CONNECT << 16;
-		scsi_lock(us_to_host(us));
+		scsi_lock(host);
 		us->srb->scsi_done(us->srb);
 		us->srb = NULL;
-		scsi_unlock(us_to_host(us));
+		scsi_unlock(host);
 	}
+	mutex_unlock(&us->dev_mutex);
 
 	/* Now we own no commands so it's safe to remove the SCSI host */
-	scsi_remove_host(us_to_host(us));
+	scsi_remove_host(host);
 }
 
 /* Second stage of disconnect processing: deallocate all resources */