diff options
author | Alan Stern <stern@rowland.harvard.edu> | 2008-12-19 10:27:56 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2009-01-07 13:00:12 -0500 |
commit | df718962bf91c7bd345060aadaa24b03f6140b07 (patch) | |
tree | cfae38a59ccb944a2af3ef39a14d4d854cba64a3 | |
parent | 6fd9086a518d4f14213a32fe6c9ac17fabebbc1e (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>
-rw-r--r-- | drivers/usb/core/message.c | 42 |
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 | ||
21 | static void cancel_async_set_config(struct usb_device *udev); | ||
22 | |||
21 | struct api_context { | 23 | struct 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 | ||
1733 | static LIST_HEAD(set_config_list); | ||
1734 | static DEFINE_SPINLOCK(set_config_lock); | ||
1735 | |||
1728 | struct set_config_request { | 1736 | struct 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 | */ | ||
1765 | static 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; |