Skip to content

Commit

Permalink
USB: cancel pending Set-Config requests if userspace gets there first
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Alan Stern authored and Greg Kroah-Hartman committed Jan 7, 2009
1 parent 6fd9086 commit df71896
Showing 1 changed file with 38 additions and 4 deletions.
42 changes: 38 additions & 4 deletions drivers/usb/core/message.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -1636,6 +1638,9 @@ int usb_set_configuration(struct usb_device *dev, int configuration)
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);
Expand Down Expand Up @@ -1725,25 +1730,50 @@ int usb_set_configuration(struct usb_device *dev, int configuration)
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() */
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
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit df71896

Please sign in to comment.