Commit df718962 authored by Alan Stern's avatar Alan Stern Committed by Greg Kroah-Hartman

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: default avatarAlan Stern <stern@rowland.harvard.edu>
Cc: Oliver Neukum <oliver@neukum.org>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent 6fd9086a
...@@ -18,6 +18,8 @@ ...@@ -18,6 +18,8 @@
#include "hcd.h" /* for usbcore internals */ #include "hcd.h" /* for usbcore internals */
#include "usb.h" #include "usb.h"
static void cancel_async_set_config(struct usb_device *udev);
struct api_context { struct api_context {
struct completion done; struct completion done;
int status; int status;
...@@ -1636,6 +1638,9 @@ free_interfaces: ...@@ -1636,6 +1638,9 @@ free_interfaces:
if (dev->state != USB_STATE_ADDRESS) if (dev->state != USB_STATE_ADDRESS)
usb_disable_device(dev, 1); /* Skip ep0 */ 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), ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
USB_REQ_SET_CONFIGURATION, 0, configuration, 0, USB_REQ_SET_CONFIGURATION, 0, configuration, 0,
NULL, 0, USB_CTRL_SET_TIMEOUT); NULL, 0, USB_CTRL_SET_TIMEOUT);
...@@ -1725,10 +1730,14 @@ free_interfaces: ...@@ -1725,10 +1730,14 @@ free_interfaces:
return 0; return 0;
} }
static LIST_HEAD(set_config_list);
static DEFINE_SPINLOCK(set_config_lock);
struct set_config_request { struct set_config_request {
struct usb_device *udev; struct usb_device *udev;
int config; int config;
struct work_struct work; struct work_struct work;
struct list_head node;
}; };
/* Worker routine for usb_driver_set_configuration() */ /* Worker routine for usb_driver_set_configuration() */
...@@ -1736,14 +1745,35 @@ static void driver_set_config_work(struct work_struct *work) ...@@ -1736,14 +1745,35 @@ static void driver_set_config_work(struct work_struct *work)
{ {
struct set_config_request *req = struct set_config_request *req =
container_of(work, struct set_config_request, work); container_of(work, struct set_config_request, work);
struct usb_device *udev = req->udev;
usb_lock_device(req->udev); usb_lock_device(udev);
usb_set_configuration(req->udev, req->config); spin_lock(&set_config_lock);
usb_unlock_device(req->udev); list_del(&req->node);
usb_put_dev(req->udev); 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); 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 * usb_driver_set_configuration - Provide a way for drivers to change device configurations
* @udev: the device whose configuration is being updated * @udev: the device whose configuration is being updated
...@@ -1775,6 +1805,10 @@ int usb_driver_set_configuration(struct usb_device *udev, int config) ...@@ -1775,6 +1805,10 @@ int usb_driver_set_configuration(struct usb_device *udev, int config)
req->config = config; req->config = config;
INIT_WORK(&req->work, driver_set_config_work); 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); usb_get_dev(udev);
schedule_work(&req->work); schedule_work(&req->work);
return 0; return 0;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment