aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb
diff options
context:
space:
mode:
authorAlan Stern <stern@rowland.harvard.edu>2008-12-19 10:27:56 -0500
committerGreg Kroah-Hartman <gregkh@suse.de>2009-01-07 13:00:12 -0500
commitdf718962bf91c7bd345060aadaa24b03f6140b07 (patch)
treecfae38a59ccb944a2af3ef39a14d4d854cba64a3 /drivers/usb
parent6fd9086a518d4f14213a32fe6c9ac17fabebbc1e (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')
-rw-r--r--drivers/usb/core/message.c42
1 files changed, 38 insertions, 4 deletions
diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c
index 7943901c641c..5589686981f1 100644
--- a/drivers/usb/core/message.c
+++ b/drivers/usb/core/message.c
@@ -18,6 +18,8 @@
18#include "hcd.h" /* for usbcore internals */ 18#include "hcd.h" /* for usbcore internals */
19#include "usb.h" 19#include "usb.h"
20 20
21static void cancel_async_set_config(struct usb_device *udev);
22
21struct api_context { 23struct api_context {
22 struct completion done; 24 struct completion done;
23 int status; 25 int status;
@@ -1636,6 +1638,9 @@ free_interfaces:
1636 if (dev->state != USB_STATE_ADDRESS) 1638 if (dev->state != USB_STATE_ADDRESS)
1637 usb_disable_device(dev, 1); /* Skip ep0 */ 1639 usb_disable_device(dev, 1); /* Skip ep0 */
1638 1640
1641 /* Get rid of pending async Set-Config requests for this device */
1642 cancel_async_set_config(dev);
1643
1639 ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), 1644 ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
1640 USB_REQ_SET_CONFIGURATION, 0, configuration, 0, 1645 USB_REQ_SET_CONFIGURATION, 0, configuration, 0,
1641 NULL, 0, USB_CTRL_SET_TIMEOUT); 1646 NULL, 0, USB_CTRL_SET_TIMEOUT);
@@ -1725,10 +1730,14 @@ free_interfaces:
1725 return 0; 1730 return 0;
1726} 1731}
1727 1732
1733static LIST_HEAD(set_config_list);
1734static DEFINE_SPINLOCK(set_config_lock);
1735
1728struct set_config_request { 1736struct set_config_request {
1729 struct usb_device *udev; 1737 struct usb_device *udev;
1730 int config; 1738 int config;
1731 struct work_struct work; 1739 struct work_struct work;
1740 struct list_head node;
1732}; 1741};
1733 1742
1734/* Worker routine for usb_driver_set_configuration() */ 1743/* Worker routine for usb_driver_set_configuration() */
@@ -1736,14 +1745,35 @@ static void driver_set_config_work(struct work_struct *work)
1736{ 1745{
1737 struct set_config_request *req = 1746 struct set_config_request *req =
1738 container_of(work, struct set_config_request, work); 1747 container_of(work, struct set_config_request, work);
1748 struct usb_device *udev = req->udev;
1739 1749
1740 usb_lock_device(req->udev); 1750 usb_lock_device(udev);
1741 usb_set_configuration(req->udev, req->config); 1751 spin_lock(&set_config_lock);
1742 usb_unlock_device(req->udev); 1752 list_del(&req->node);
1743 usb_put_dev(req->udev); 1753 spin_unlock(&set_config_lock);
1754
1755 if (req->config >= -1) /* Is req still valid? */
1756 usb_set_configuration(udev, req->config);
1757 usb_unlock_device(udev);
1758 usb_put_dev(udev);
1744 kfree(req); 1759 kfree(req);
1745} 1760}
1746 1761
1762/* Cancel pending Set-Config requests for a device whose configuration
1763 * was just changed
1764 */
1765static void cancel_async_set_config(struct usb_device *udev)
1766{
1767 struct set_config_request *req;
1768
1769 spin_lock(&set_config_lock);
1770 list_for_each_entry(req, &set_config_list, node) {
1771 if (req->udev == udev)
1772 req->config = -999; /* Mark as cancelled */
1773 }
1774 spin_unlock(&set_config_lock);
1775}
1776
1747/** 1777/**
1748 * usb_driver_set_configuration - Provide a way for drivers to change device configurations 1778 * usb_driver_set_configuration - Provide a way for drivers to change device configurations
1749 * @udev: the device whose configuration is being updated 1779 * @udev: the device whose configuration is being updated
@@ -1775,6 +1805,10 @@ int usb_driver_set_configuration(struct usb_device *udev, int config)
1775 req->config = config; 1805 req->config = config;
1776 INIT_WORK(&req->work, driver_set_config_work); 1806 INIT_WORK(&req->work, driver_set_config_work);
1777 1807
1808 spin_lock(&set_config_lock);
1809 list_add(&req->node, &set_config_list);
1810 spin_unlock(&set_config_lock);
1811
1778 usb_get_dev(udev); 1812 usb_get_dev(udev);
1779 schedule_work(&req->work); 1813 schedule_work(&req->work);
1780 return 0; 1814 return 0;