diff options
| author | Alan Stern <stern@rowland.harvard.edu> | 2008-12-19 10:27:56 -0500 | 
|---|---|---|
| committer | Greg Kroah-Hartman <gregkh@suse.de> | 2009-01-07 10:00:12 -0800 | 
| commit | df718962bf91c7bd345060aadaa24b03f6140b07 (patch) | |
| tree | cfae38a59ccb944a2af3ef39a14d4d854cba64a3 /drivers/usb/core/message.c | |
| parent | 6fd9086a518d4f14213a32fe6c9ac17fabebbc1e (diff) | |
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/message.c')
| -rw-r--r-- | drivers/usb/core/message.c | 42 | 
1 files changed, 38 insertions, 4 deletions
| diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index 7943901c641..5589686981f 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; | 
