summary refs log tree commit diff
path: root/drivers/usb/core
diff options
context:
space:
mode:
authorAlan Stern <stern@rowland.harvard.edu>2008-12-19 10:27:56 -0500
committerGreg Kroah-Hartman <gregkh@suse.de>2009-01-07 10:00:12 -0800
commitdf718962bf91c7bd345060aadaa24b03f6140b07 (patch)
treecfae38a59ccb944a2af3ef39a14d4d854cba64a3 /drivers/usb/core
parent6fd9086a518d4f14213a32fe6c9ac17fabebbc1e (diff)
downloadlinux-df718962bf91c7bd345060aadaa24b03f6140b07.tar.gz
USB: cancel pending Set-Config requests if userspace gets there first
This patch (as1195) eliminates a potential problem identified by
Oliver Neukum.  When a driver queues an asynchronous Set-Config
request using usb_driver_set_configuration(), the request should be
cancelled if userspace changes the configuration first.  The patch
introduces a linked list of pending async Set-Config requests, and
uses it to invalidate the requests for a particular device whenever
that device's configuration is set.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Cc: Oliver Neukum <oliver@neukum.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>

Diffstat (limited to 'drivers/usb/core')
-rw-r--r--drivers/usb/core/message.c42
1 files changed, 38 insertions, 4 deletions
diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c
index 7943901c641c..5589686981f1 100644
--- a/drivers/usb/core/message.c
+++ b/drivers/usb/core/message.c
@@ -18,6 +18,8 @@
 #include "hcd.h"	/* for usbcore internals */
 #include "usb.h"
 
+static void cancel_async_set_config(struct usb_device *udev);
+
 struct api_context {
 	struct completion	done;
 	int			status;
@@ -1636,6 +1638,9 @@ free_interfaces:
 	if (dev->state != USB_STATE_ADDRESS)
 		usb_disable_device(dev, 1);	/* Skip ep0 */
 
+	/* Get rid of pending async Set-Config requests for this device */
+	cancel_async_set_config(dev);
+
 	ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
 			      USB_REQ_SET_CONFIGURATION, 0, configuration, 0,
 			      NULL, 0, USB_CTRL_SET_TIMEOUT);
@@ -1725,10 +1730,14 @@ free_interfaces:
 	return 0;
 }
 
+static LIST_HEAD(set_config_list);
+static DEFINE_SPINLOCK(set_config_lock);
+
 struct set_config_request {
 	struct usb_device	*udev;
 	int			config;
 	struct work_struct	work;
+	struct list_head	node;
 };
 
 /* Worker routine for usb_driver_set_configuration() */
@@ -1736,14 +1745,35 @@ static void driver_set_config_work(struct work_struct *work)
 {
 	struct set_config_request *req =
 		container_of(work, struct set_config_request, work);
+	struct usb_device *udev = req->udev;
 
-	usb_lock_device(req->udev);
-	usb_set_configuration(req->udev, req->config);
-	usb_unlock_device(req->udev);
-	usb_put_dev(req->udev);
+	usb_lock_device(udev);
+	spin_lock(&set_config_lock);
+	list_del(&req->node);
+	spin_unlock(&set_config_lock);
+
+	if (req->config >= -1)		/* Is req still valid? */
+		usb_set_configuration(udev, req->config);
+	usb_unlock_device(udev);
+	usb_put_dev(udev);
 	kfree(req);
 }
 
+/* Cancel pending Set-Config requests for a device whose configuration
+ * was just changed
+ */
+static void cancel_async_set_config(struct usb_device *udev)
+{
+	struct set_config_request *req;
+
+	spin_lock(&set_config_lock);
+	list_for_each_entry(req, &set_config_list, node) {
+		if (req->udev == udev)
+			req->config = -999;	/* Mark as cancelled */
+	}
+	spin_unlock(&set_config_lock);
+}
+
 /**
  * usb_driver_set_configuration - Provide a way for drivers to change device configurations
  * @udev: the device whose configuration is being updated
@@ -1775,6 +1805,10 @@ int usb_driver_set_configuration(struct usb_device *udev, int config)
 	req->config = config;
 	INIT_WORK(&req->work, driver_set_config_work);
 
+	spin_lock(&set_config_lock);
+	list_add(&req->node, &set_config_list);
+	spin_unlock(&set_config_lock);
+
 	usb_get_dev(udev);
 	schedule_work(&req->work);
 	return 0;