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>
This commit is contained in:
committed by
Greg Kroah-Hartman
parent
6fd9086a51
commit
df718962bf
@@ -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:
|
|||||||
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:
|
|||||||
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)
|
|||||||
{
|
{
|
||||||
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)
|
|||||||
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;
|
||||||
|
Reference in New Issue
Block a user