Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

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

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
Loading
Loading
Loading
Loading
+38 −4
Original line number Diff line number Diff line
@@ -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(udev);
	spin_lock(&set_config_lock);
	list_del(&req->node);
	spin_unlock(&set_config_lock);

	usb_lock_device(req->udev);
	usb_set_configuration(req->udev, req->config);
	usb_unlock_device(req->udev);
	usb_put_dev(req->udev);
	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;