[PATCH] RFC: Add asynchronous reset endpoint call to USB core.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Some drivers were calling usb_reset_endpoint() in interrupt or tasklet
context.  The underlying host controller driver function reset_endpoint()
may block, so this is less than ideal.

Create a new USB core function to start an asynchronous endpoint reset,
usb_start_reset_endpoint().  The driver's callback function will be
asynchronously called when the reset is finished.  Add a new host
controller driver function start_endpoint_reset(), that will be called by
usb_hcd_start_reset_endpoint().

WIP: Change WHCI driver to use start_endpoint_reset() and do asynchronous
completions.  Change the hisax and UB drivers to use
usb_start_reset_endpoint()

Signed-off-by: Sarah Sharp <sarah.a.sharp@xxxxxxxxxxxxxxx>
---

Can someone test this patch with a mass storage device that stalls?  I
currently don't have any.

 drivers/usb/core/hcd.c     |   32 +++++++++++++++++++-
 drivers/usb/core/hcd.h     |    7 ++++-
 drivers/usb/core/message.c |   69 ++++++++++++++++++++++++++++++++++++++-----
 include/linux/usb.h        |   12 +++++++
 4 files changed, 110 insertions(+), 10 deletions(-)

diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c
index 42b93da..ad89642 100644
--- a/drivers/usb/core/hcd.c
+++ b/drivers/usb/core/hcd.c
@@ -1540,7 +1540,8 @@ void usb_hcd_disable_endpoint(struct usb_device *udev,
 }
 
 /**
- * usb_hcd_reset_endpoint - reset host endpoint state
+ * usb_hcd_reset_endpoint - reset host endpoint state.
+ * May not be called in interrupt context.
  * @udev: USB device.
  * @ep:   the endpoint to reset.
  *
@@ -1565,6 +1566,35 @@ void usb_hcd_reset_endpoint(struct usb_device *udev,
 	}
 }
 
+/**
+ * usb_hcd_start_reset_endpoint - begin an asynchronous reset of host endpoint state.
+ * May be called in interrupt or tasklet context.
+ * @udev: USB device.
+ * @ep:   the endpoint to reset.
+ *
+ * Resets any host endpoint state such as the toggle bit, sequence
+ * number and current window.
+ */
+void usb_hcd_start_reset_endpoint(struct endpoint_reset_callback *callback_info)
+{
+	struct usb_hcd *hcd = bus_to_hcd(callback_info->dev->bus);
+	struct usb_host_endpoint *ep = callback_info->ep;
+	struct usb_device *udev = callback_info->dev;
+
+	if (hcd->driver->start_endpoint_reset)
+		hcd->driver->start_endpoint_reset(hcd, callback_info);
+	else {
+		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);
+
+		usb_settoggle(udev, epnum, is_out, 0);
+		if (is_control)
+			usb_settoggle(udev, epnum, !is_out, 0);
+		callback_info->callback(callback_info);
+	}
+}
+
 /* Protect against drivers that try to unlink URBs after the device
  * is gone, by waiting until all unlinks for @udev are finished.
  * Since we don't currently track URBs by device, simply wait until
diff --git a/drivers/usb/core/hcd.h b/drivers/usb/core/hcd.h
index e7d4479..d01d597 100644
--- a/drivers/usb/core/hcd.h
+++ b/drivers/usb/core/hcd.h
@@ -207,9 +207,13 @@ struct hc_driver {
 			struct usb_host_endpoint *ep);
 
 	/* (optional) reset any endpoint state such as sequence number
-	   and current window */
+	   and current window in non-interrupt context */
 	void 	(*endpoint_reset)(struct usb_hcd *hcd,
 			struct usb_host_endpoint *ep);
+	/* (optional) reset any endpoint state such as sequence number
+	   and current window in interrupt or tasklet context */
+	void 	(*start_endpoint_reset)(struct usb_hcd *hcd,
+			struct endpoint_reset_callback *callback);
 
 	/* root hub support */
 	int	(*hub_status_data) (struct usb_hcd *hcd, char *buf);
@@ -241,6 +245,7 @@ extern void usb_hcd_disable_endpoint(struct usb_device *udev,
 		struct usb_host_endpoint *ep);
 extern void usb_hcd_reset_endpoint(struct usb_device *udev,
 		struct usb_host_endpoint *ep);
+extern void usb_hcd_start_reset_endpoint(struct endpoint_reset_callback *callback_info);
 extern void usb_hcd_synchronize_unlinks(struct usb_device *udev);
 extern int usb_hcd_get_frame_number(struct usb_device *udev);
 
diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c
index b626283..1c29b92 100644
--- a/drivers/usb/core/message.c
+++ b/drivers/usb/core/message.c
@@ -1074,8 +1074,17 @@ void usb_disable_endpoint(struct usb_device *dev, unsigned int epaddr,
 	}
 }
 
+static void usb_finish_reset_endpoint(struct endpoint_reset_callback *callback_info)
+{
+	struct completion *reset_completion =
+		(struct completion *) callback_info->driver_priv;
+
+	complete(reset_completion);
+}
+
 /**
  * usb_reset_endpoint - Reset an endpoint's state.
+ * Cannot be called in interrupt context.
  * @dev: the device whose endpoint is to be reset
  * @epaddr: the endpoint's address.  Endpoint number for output,
  *	endpoint number + USB_DIR_IN for input
@@ -1085,18 +1094,62 @@ void usb_disable_endpoint(struct usb_device *dev, unsigned int epaddr,
  */
 void usb_reset_endpoint(struct usb_device *dev, unsigned int epaddr)
 {
-	unsigned int epnum = epaddr & USB_ENDPOINT_NUMBER_MASK;
-	struct usb_host_endpoint *ep;
+	struct completion *reset_completion;
+	struct endpoint_reset_callback *callback_info;
 
-	if (usb_endpoint_out(epaddr))
-		ep = dev->ep_out[epnum];
-	else
-		ep = dev->ep_in[epnum];
-	if (ep)
-		usb_hcd_reset_endpoint(dev, ep);
+	reset_completion = kmalloc(sizeof(*reset_completion), GFP_KERNEL);
+	/* FIXME Probably should do something more useful here */
+	if (!reset_completion)
+		return;
+	callback_info = kzalloc(sizeof(*callback_info), GFP_KERNEL);
+	if (!callback_info) {
+		kfree(reset_completion);
+		return;
+	}
+
+	INIT_COMPLETION(*reset_completion);
+	callback_info->dev = dev;
+	callback_info->epaddr = epaddr;
+	callback_info->driver_priv = reset_completion;
+	callback_info->callback = usb_finish_reset_endpoint;
+
+	usb_hcd_start_reset_endpoint(callback_info);
+	wait_for_completion(reset_completion);
+
+	kfree(reset_completion);
+	kfree(callback_info);
 }
 EXPORT_SYMBOL_GPL(usb_reset_endpoint);
 
+/**
+ * usb_start_reset_endpoint - Reset an endpoint's state from interrupt context.
+ * @callback_info - caller-allocated information about which endpoint on the
+ * 	device to reset.  Also contains the function pointer to callback when the HC
+ * 	is done resetting the endpoint.
+ *
+ * Resets any host-side endpoint state such as the toggle bit,
+ * sequence number or current window.
+ */
+void usb_start_reset_endpoint(struct endpoint_reset_callback *callback_info)
+{
+	struct usb_device *udev;
+
+	if (!callback_info)
+		return;
+
+	callback_info->epaddr &= USB_ENDPOINT_NUMBER_MASK;
+	udev = callback_info->dev;
+	if (usb_endpoint_out(callback_info->epaddr))
+		callback_info->ep = udev->ep_out[callback_info->epaddr];
+	else
+		callback_info->ep = udev->ep_in[callback_info->epaddr];
+
+	if (callback_info->ep)
+		usb_hcd_start_reset_endpoint(callback_info);
+	else
+		callback_info->callback(callback_info);
+}
+EXPORT_SYMBOL_GPL(usb_start_reset_endpoint);
 
 /**
  * usb_disable_interface -- Disable all endpoints for an interface
diff --git a/include/linux/usb.h b/include/linux/usb.h
index 3aa2cd1..de84f80 100644
--- a/include/linux/usb.h
+++ b/include/linux/usb.h
@@ -1389,6 +1389,18 @@ extern int usb_reset_configuration(struct usb_device *dev);
 extern int usb_set_interface(struct usb_device *dev, int ifnum, int alternate);
 extern void usb_reset_endpoint(struct usb_device *dev, unsigned int epaddr);
 
+struct endpoint_reset_callback {
+	/* Set by driver submitting reset command */
+	struct usb_device		*dev;
+	unsigned int			epaddr;
+	void				*driver_priv;
+	void (*callback)(struct endpoint_reset_callback *callback);
+	/* Set by USB core. */
+	struct usb_host_endpoint	*ep;
+};
+
+extern void usb_start_reset_endpoint(struct endpoint_reset_callback *callback_info);
+
 /* this request isn't really synchronous, but it belongs with the others */
 extern int usb_driver_set_configuration(struct usb_device *udev, int config);
 
-- 
1.5.6.5

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux