]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/usb/core/message.c
USB: fix toggle mismatch in disable_endpoint paths
[linux-2.6-omap-h63xx.git] / drivers / usb / core / message.c
index 6d1048faf08e1679597af0fadd6a5849d3afd299..31fb204f44c6a020467f952b5ed855132ba0dff7 100644 (file)
@@ -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;
@@ -139,9 +141,9 @@ int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request,
 
        dr->bRequestType = requesttype;
        dr->bRequest = request;
-       dr->wValue = cpu_to_le16p(&value);
-       dr->wIndex = cpu_to_le16p(&index);
-       dr->wLength = cpu_to_le16p(&size);
+       dr->wValue = cpu_to_le16(value);
+       dr->wIndex = cpu_to_le16(index);
+       dr->wLength = cpu_to_le16(size);
 
        /* dbg("usb_control_msg"); */
 
@@ -1004,19 +1006,48 @@ int usb_clear_halt(struct usb_device *dev, int pipe)
 }
 EXPORT_SYMBOL_GPL(usb_clear_halt);
 
+static int create_intf_ep_devs(struct usb_interface *intf)
+{
+       struct usb_device *udev = interface_to_usbdev(intf);
+       struct usb_host_interface *alt = intf->cur_altsetting;
+       int i;
+
+       if (intf->ep_devs_created || intf->unregistering)
+               return 0;
+
+       for (i = 0; i < alt->desc.bNumEndpoints; ++i)
+               (void) usb_create_ep_devs(&intf->dev, &alt->endpoint[i], udev);
+       intf->ep_devs_created = 1;
+       return 0;
+}
+
+static void remove_intf_ep_devs(struct usb_interface *intf)
+{
+       struct usb_host_interface *alt = intf->cur_altsetting;
+       int i;
+
+       if (!intf->ep_devs_created)
+               return;
+
+       for (i = 0; i < alt->desc.bNumEndpoints; ++i)
+               usb_remove_ep_devs(&alt->endpoint[i]);
+       intf->ep_devs_created = 0;
+}
+
 /**
  * usb_disable_endpoint -- Disable an endpoint by address
  * @dev: the device whose endpoint is being disabled
  * @epaddr: the endpoint's address.  Endpoint number for output,
  *     endpoint number + USB_DIR_IN for input
+ * @reset_hardware: flag to erase any endpoint state stored in the
+ *     controller hardware
  *
- * Deallocates hcd/hardware state for this endpoint ... and nukes all
- * pending urbs.
- *
- * If the HCD hasn't registered a disable() function, this sets the
- * endpoint's maxpacket size to 0 to prevent further submissions.
+ * Disables the endpoint for URB submission and nukes all pending URBs.
+ * If @reset_hardware is set then also deallocates hcd/hardware state
+ * for the endpoint.
  */
-void usb_disable_endpoint(struct usb_device *dev, unsigned int epaddr)
+void usb_disable_endpoint(struct usb_device *dev, unsigned int epaddr,
+               bool reset_hardware)
 {
        unsigned int epnum = epaddr & USB_ENDPOINT_NUMBER_MASK;
        struct usb_host_endpoint *ep;
@@ -1026,15 +1057,18 @@ void usb_disable_endpoint(struct usb_device *dev, unsigned int epaddr)
 
        if (usb_endpoint_out(epaddr)) {
                ep = dev->ep_out[epnum];
-               dev->ep_out[epnum] = NULL;
+               if (reset_hardware)
+                       dev->ep_out[epnum] = NULL;
        } else {
                ep = dev->ep_in[epnum];
-               dev->ep_in[epnum] = NULL;
+               if (reset_hardware)
+                       dev->ep_in[epnum] = NULL;
        }
        if (ep) {
                ep->enabled = 0;
                usb_hcd_flush_endpoint(dev, ep);
-               usb_hcd_disable_endpoint(dev, ep);
+               if (reset_hardware)
+                       usb_hcd_disable_endpoint(dev, ep);
        }
 }
 
@@ -1042,17 +1076,21 @@ void usb_disable_endpoint(struct usb_device *dev, unsigned int epaddr)
  * usb_disable_interface -- Disable all endpoints for an interface
  * @dev: the device whose interface is being disabled
  * @intf: pointer to the interface descriptor
+ * @reset_hardware: flag to erase any endpoint state stored in the
+ *     controller hardware
  *
  * Disables all the endpoints for the interface's current altsetting.
  */
-void usb_disable_interface(struct usb_device *dev, struct usb_interface *intf)
+void usb_disable_interface(struct usb_device *dev, struct usb_interface *intf,
+               bool reset_hardware)
 {
        struct usb_host_interface *alt = intf->cur_altsetting;
        int i;
 
        for (i = 0; i < alt->desc.bNumEndpoints; ++i) {
                usb_disable_endpoint(dev,
-                               alt->endpoint[i].desc.bEndpointAddress);
+                               alt->endpoint[i].desc.bEndpointAddress,
+                               reset_hardware);
        }
 }
 
@@ -1073,8 +1111,8 @@ void usb_disable_device(struct usb_device *dev, int skip_ep0)
        dev_dbg(&dev->dev, "%s nuking %s URBs\n", __func__,
                skip_ep0 ? "non-ep0" : "all");
        for (i = skip_ep0; i < 16; ++i) {
-               usb_disable_endpoint(dev, i);
-               usb_disable_endpoint(dev, i + USB_DIR_IN);
+               usb_disable_endpoint(dev, i, true);
+               usb_disable_endpoint(dev, i + USB_DIR_IN, true);
        }
        dev->toggle[0] = dev->toggle[1] = 0;
 
@@ -1092,7 +1130,7 @@ void usb_disable_device(struct usb_device *dev, int skip_ep0)
                        dev_dbg(&dev->dev, "unregistering interface %s\n",
                                dev_name(&interface->dev));
                        interface->unregistering = 1;
-                       usb_remove_sysfs_intf_files(interface);
+                       remove_intf_ep_devs(interface);
                        device_del(&interface->dev);
                }
 
@@ -1113,22 +1151,26 @@ void usb_disable_device(struct usb_device *dev, int skip_ep0)
  * usb_enable_endpoint - Enable an endpoint for USB communications
  * @dev: the device whose interface is being enabled
  * @ep: the endpoint
+ * @reset_toggle: flag to set the endpoint's toggle back to 0
  *
- * Resets the endpoint toggle, and sets dev->ep_{in,out} pointers.
+ * Resets the endpoint toggle if asked, and sets dev->ep_{in,out} pointers.
  * For control endpoints, both the input and output sides are handled.
  */
-void usb_enable_endpoint(struct usb_device *dev, struct usb_host_endpoint *ep)
+void usb_enable_endpoint(struct usb_device *dev, struct usb_host_endpoint *ep,
+               bool reset_toggle)
 {
        int epnum = usb_endpoint_num(&ep->desc);
        int is_out = usb_endpoint_dir_out(&ep->desc);
        int is_control = usb_endpoint_xfer_control(&ep->desc);
 
        if (is_out || is_control) {
-               usb_settoggle(dev, epnum, 1, 0);
+               if (reset_toggle)
+                       usb_settoggle(dev, epnum, 1, 0);
                dev->ep_out[epnum] = ep;
        }
        if (!is_out || is_control) {
-               usb_settoggle(dev, epnum, 0, 0);
+               if (reset_toggle)
+                       usb_settoggle(dev, epnum, 0, 0);
                dev->ep_in[epnum] = ep;
        }
        ep->enabled = 1;
@@ -1138,17 +1180,18 @@ void usb_enable_endpoint(struct usb_device *dev, struct usb_host_endpoint *ep)
  * usb_enable_interface - Enable all the endpoints for an interface
  * @dev: the device whose interface is being enabled
  * @intf: pointer to the interface descriptor
+ * @reset_toggles: flag to set the endpoints' toggles back to 0
  *
  * Enables all the endpoints for the interface's current altsetting.
  */
-static void usb_enable_interface(struct usb_device *dev,
-                                struct usb_interface *intf)
+void usb_enable_interface(struct usb_device *dev,
+               struct usb_interface *intf, bool reset_toggles)
 {
        struct usb_host_interface *alt = intf->cur_altsetting;
        int i;
 
        for (i = 0; i < alt->desc.bNumEndpoints; ++i)
-               usb_enable_endpoint(dev, &alt->endpoint[i]);
+               usb_enable_endpoint(dev, &alt->endpoint[i], reset_toggles);
 }
 
 /**
@@ -1235,9 +1278,11 @@ int usb_set_interface(struct usb_device *dev, int interface, int alternate)
         */
 
        /* prevent submissions using previous endpoint settings */
-       if (iface->cur_altsetting != alt)
+       if (iface->cur_altsetting != alt) {
+               remove_intf_ep_devs(iface);
                usb_remove_sysfs_intf_files(iface);
-       usb_disable_interface(dev, iface);
+       }
+       usb_disable_interface(dev, iface, true);
 
        iface->cur_altsetting = alt;
 
@@ -1271,10 +1316,11 @@ int usb_set_interface(struct usb_device *dev, int interface, int alternate)
         * during the SETUP stage - hence EP0 toggles are "don't care" here.
         * (Likewise, EP0 never "halts" on well designed devices.)
         */
-       usb_enable_interface(dev, iface);
-       if (device_is_registered(&iface->dev))
+       usb_enable_interface(dev, iface, true);
+       if (device_is_registered(&iface->dev)) {
                usb_create_sysfs_intf_files(iface);
-
+               create_intf_ep_devs(iface);
+       }
        return 0;
 }
 EXPORT_SYMBOL_GPL(usb_set_interface);
@@ -1315,8 +1361,8 @@ int usb_reset_configuration(struct usb_device *dev)
         */
 
        for (i = 1; i < 16; ++i) {
-               usb_disable_endpoint(dev, i);
-               usb_disable_endpoint(dev, i + USB_DIR_IN);
+               usb_disable_endpoint(dev, i, true);
+               usb_disable_endpoint(dev, i + USB_DIR_IN, true);
        }
 
        config = dev->actconfig;
@@ -1334,7 +1380,6 @@ int usb_reset_configuration(struct usb_device *dev)
                struct usb_interface *intf = config->interface[i];
                struct usb_host_interface *alt;
 
-               usb_remove_sysfs_intf_files(intf);
                alt = usb_altnum_to_altsetting(intf, 0);
 
                /* No altsetting 0?  We'll assume the first altsetting.
@@ -1345,10 +1390,16 @@ int usb_reset_configuration(struct usb_device *dev)
                if (!alt)
                        alt = &intf->altsetting[0];
 
+               if (alt != intf->cur_altsetting) {
+                       remove_intf_ep_devs(intf);
+                       usb_remove_sysfs_intf_files(intf);
+               }
                intf->cur_altsetting = alt;
-               usb_enable_interface(dev, intf);
-               if (device_is_registered(&intf->dev))
+               usb_enable_interface(dev, intf, true);
+               if (device_is_registered(&intf->dev)) {
                        usb_create_sysfs_intf_files(intf);
+                       create_intf_ep_devs(intf);
+               }
        }
        return 0;
 }
@@ -1441,6 +1492,46 @@ static struct usb_interface_assoc_descriptor *find_iad(struct usb_device *dev,
        return retval;
 }
 
+
+/*
+ * Internal function to queue a device reset
+ *
+ * This is initialized into the workstruct in 'struct
+ * usb_device->reset_ws' that is launched by
+ * message.c:usb_set_configuration() when initializing each 'struct
+ * usb_interface'.
+ *
+ * It is safe to get the USB device without reference counts because
+ * the life cycle of @iface is bound to the life cycle of @udev. Then,
+ * this function will be ran only if @iface is alive (and before
+ * freeing it any scheduled instances of it will have been cancelled).
+ *
+ * We need to set a flag (usb_dev->reset_running) because when we call
+ * the reset, the interfaces might be unbound. The current interface
+ * cannot try to remove the queued work as it would cause a deadlock
+ * (you cannot remove your work from within your executing
+ * workqueue). This flag lets it know, so that
+ * usb_cancel_queued_reset() doesn't try to do it.
+ *
+ * See usb_queue_reset_device() for more details
+ */
+void __usb_queue_reset_device(struct work_struct *ws)
+{
+       int rc;
+       struct usb_interface *iface =
+               container_of(ws, struct usb_interface, reset_ws);
+       struct usb_device *udev = interface_to_usbdev(iface);
+
+       rc = usb_lock_device_for_reset(udev, iface);
+       if (rc >= 0) {
+               iface->reset_running = 1;
+               usb_reset_device(udev);
+               iface->reset_running = 0;
+               usb_unlock_device(udev);
+       }
+}
+
+
 /*
  * usb_set_configuration - Makes a particular device setting be current
  * @dev: the device whose configuration is being updated
@@ -1560,6 +1651,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);
@@ -1604,13 +1698,14 @@ free_interfaces:
                        alt = &intf->altsetting[0];
 
                intf->cur_altsetting = alt;
-               usb_enable_interface(dev, intf);
+               usb_enable_interface(dev, intf, true);
                intf->dev.parent = &dev->dev;
                intf->dev.driver = NULL;
                intf->dev.bus = &usb_bus_type;
                intf->dev.type = &usb_if_device_type;
                intf->dev.groups = usb_interface_groups;
                intf->dev.dma_mask = dev->dev.dma_mask;
+               INIT_WORK(&intf->reset_ws, __usb_queue_reset_device);
                device_initialize(&intf->dev);
                mark_quiesced(intf);
                dev_set_name(&intf->dev, "%d-%s:%d.%d",
@@ -1641,17 +1736,21 @@ free_interfaces:
                                dev_name(&intf->dev), ret);
                        continue;
                }
-               usb_create_sysfs_intf_files(intf);
+               create_intf_ep_devs(intf);
        }
 
        usb_autosuspend_device(dev);
        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() */
@@ -1659,14 +1758,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
@@ -1698,6 +1818,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;