|
@@ -519,119 +519,120 @@ error:
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
/*
|
|
|
- * Root Hub interrupt transfers are synthesized with a timer.
|
|
|
- * Completions are called in_interrupt() but not in_irq().
|
|
|
+ * Root Hub interrupt transfers are polled using a timer if the
|
|
|
+ * driver requests it; otherwise the driver is responsible for
|
|
|
+ * calling usb_hcd_poll_rh_status() when an event occurs.
|
|
|
*
|
|
|
- * Note: some root hubs (including common UHCI based designs) can't
|
|
|
- * correctly issue port change IRQs. They're the ones that _need_ a
|
|
|
- * timer; most other root hubs don't. Some systems could save a
|
|
|
- * lot of battery power by eliminating these root hub timer IRQs.
|
|
|
+ * Completions are called in_interrupt(), but they may or may not
|
|
|
+ * be in_irq().
|
|
|
*/
|
|
|
+void usb_hcd_poll_rh_status(struct usb_hcd *hcd)
|
|
|
+{
|
|
|
+ struct urb *urb;
|
|
|
+ int length;
|
|
|
+ unsigned long flags;
|
|
|
+ char buffer[4]; /* Any root hubs with > 31 ports? */
|
|
|
|
|
|
-static void rh_report_status (unsigned long ptr);
|
|
|
+ if (!hcd->uses_new_polling && !hcd->status_urb)
|
|
|
+ return;
|
|
|
|
|
|
-static int rh_status_urb (struct usb_hcd *hcd, struct urb *urb)
|
|
|
-{
|
|
|
- int len = 1 + (urb->dev->maxchild / 8);
|
|
|
+ length = hcd->driver->hub_status_data(hcd, buffer);
|
|
|
+ if (length > 0) {
|
|
|
|
|
|
- /* rh_timer protected by hcd_data_lock */
|
|
|
- if (hcd->rh_timer.data || urb->transfer_buffer_length < len) {
|
|
|
- dev_dbg (hcd->self.controller,
|
|
|
- "not queuing rh status urb, stat %d\n",
|
|
|
- urb->status);
|
|
|
- return -EINVAL;
|
|
|
+ /* try to complete the status urb */
|
|
|
+ local_irq_save (flags);
|
|
|
+ spin_lock(&hcd_root_hub_lock);
|
|
|
+ urb = hcd->status_urb;
|
|
|
+ if (urb) {
|
|
|
+ spin_lock(&urb->lock);
|
|
|
+ if (urb->status == -EINPROGRESS) {
|
|
|
+ hcd->poll_pending = 0;
|
|
|
+ hcd->status_urb = NULL;
|
|
|
+ urb->status = 0;
|
|
|
+ urb->hcpriv = NULL;
|
|
|
+ urb->actual_length = length;
|
|
|
+ memcpy(urb->transfer_buffer, buffer, length);
|
|
|
+ } else /* urb has been unlinked */
|
|
|
+ length = 0;
|
|
|
+ spin_unlock(&urb->lock);
|
|
|
+ } else
|
|
|
+ length = 0;
|
|
|
+ spin_unlock(&hcd_root_hub_lock);
|
|
|
+
|
|
|
+ /* local irqs are always blocked in completions */
|
|
|
+ if (length > 0)
|
|
|
+ usb_hcd_giveback_urb (hcd, urb, NULL);
|
|
|
+ else
|
|
|
+ hcd->poll_pending = 1;
|
|
|
+ local_irq_restore (flags);
|
|
|
}
|
|
|
|
|
|
- init_timer (&hcd->rh_timer);
|
|
|
- hcd->rh_timer.function = rh_report_status;
|
|
|
- hcd->rh_timer.data = (unsigned long) urb;
|
|
|
- /* USB 2.0 spec says 256msec; this is close enough */
|
|
|
- hcd->rh_timer.expires = jiffies + HZ/4;
|
|
|
- add_timer (&hcd->rh_timer);
|
|
|
- urb->hcpriv = hcd; /* nonzero to indicate it's queued */
|
|
|
- return 0;
|
|
|
+ /* The USB 2.0 spec says 256 ms. This is close enough and won't
|
|
|
+ * exceed that limit if HZ is 100. */
|
|
|
+ if (hcd->uses_new_polling ? hcd->poll_rh :
|
|
|
+ (length == 0 && hcd->status_urb != NULL))
|
|
|
+ mod_timer (&hcd->rh_timer, jiffies + msecs_to_jiffies(250));
|
|
|
}
|
|
|
+EXPORT_SYMBOL_GPL(usb_hcd_poll_rh_status);
|
|
|
|
|
|
/* timer callback */
|
|
|
+static void rh_timer_func (unsigned long _hcd)
|
|
|
+{
|
|
|
+ usb_hcd_poll_rh_status((struct usb_hcd *) _hcd);
|
|
|
+}
|
|
|
+
|
|
|
+/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
-static void rh_report_status (unsigned long ptr)
|
|
|
+static int rh_queue_status (struct usb_hcd *hcd, struct urb *urb)
|
|
|
{
|
|
|
- struct urb *urb;
|
|
|
- struct usb_hcd *hcd;
|
|
|
- int length = 0;
|
|
|
+ int retval;
|
|
|
unsigned long flags;
|
|
|
+ int len = 1 + (urb->dev->maxchild / 8);
|
|
|
|
|
|
- urb = (struct urb *) ptr;
|
|
|
- local_irq_save (flags);
|
|
|
- spin_lock (&urb->lock);
|
|
|
+ spin_lock_irqsave (&hcd_root_hub_lock, flags);
|
|
|
+ if (urb->status != -EINPROGRESS) /* already unlinked */
|
|
|
+ retval = urb->status;
|
|
|
+ else if (hcd->status_urb || urb->transfer_buffer_length < len) {
|
|
|
+ dev_dbg (hcd->self.controller, "not queuing rh status urb\n");
|
|
|
+ retval = -EINVAL;
|
|
|
+ } else {
|
|
|
+ hcd->status_urb = urb;
|
|
|
+ urb->hcpriv = hcd; /* indicate it's queued */
|
|
|
|
|
|
- /* do nothing if the urb's been unlinked */
|
|
|
- if (!urb->dev
|
|
|
- || urb->status != -EINPROGRESS
|
|
|
- || (hcd = urb->dev->bus->hcpriv) == NULL) {
|
|
|
- spin_unlock (&urb->lock);
|
|
|
- local_irq_restore (flags);
|
|
|
- return;
|
|
|
- }
|
|
|
+ if (!hcd->uses_new_polling)
|
|
|
+ mod_timer (&hcd->rh_timer, jiffies +
|
|
|
+ msecs_to_jiffies(250));
|
|
|
|
|
|
- /* complete the status urb, or retrigger the timer */
|
|
|
- spin_lock (&hcd_data_lock);
|
|
|
- if (urb->dev->state == USB_STATE_CONFIGURED) {
|
|
|
- length = hcd->driver->hub_status_data (
|
|
|
- hcd, urb->transfer_buffer);
|
|
|
- if (length > 0) {
|
|
|
- hcd->rh_timer.data = 0;
|
|
|
- urb->actual_length = length;
|
|
|
- urb->status = 0;
|
|
|
- urb->hcpriv = NULL;
|
|
|
- } else
|
|
|
- mod_timer (&hcd->rh_timer, jiffies + HZ/4);
|
|
|
+ /* If a status change has already occurred, report it ASAP */
|
|
|
+ else if (hcd->poll_pending)
|
|
|
+ mod_timer (&hcd->rh_timer, jiffies);
|
|
|
+ retval = 0;
|
|
|
}
|
|
|
- spin_unlock (&hcd_data_lock);
|
|
|
- spin_unlock (&urb->lock);
|
|
|
-
|
|
|
- /* local irqs are always blocked in completions */
|
|
|
- if (length > 0)
|
|
|
- usb_hcd_giveback_urb (hcd, urb, NULL);
|
|
|
- local_irq_restore (flags);
|
|
|
+ spin_unlock_irqrestore (&hcd_root_hub_lock, flags);
|
|
|
+ return retval;
|
|
|
}
|
|
|
|
|
|
-/*-------------------------------------------------------------------------*/
|
|
|
-
|
|
|
static int rh_urb_enqueue (struct usb_hcd *hcd, struct urb *urb)
|
|
|
{
|
|
|
- if (usb_pipeint (urb->pipe)) {
|
|
|
- int retval;
|
|
|
- unsigned long flags;
|
|
|
-
|
|
|
- spin_lock_irqsave (&hcd_data_lock, flags);
|
|
|
- retval = rh_status_urb (hcd, urb);
|
|
|
- spin_unlock_irqrestore (&hcd_data_lock, flags);
|
|
|
- return retval;
|
|
|
- }
|
|
|
+ if (usb_pipeint (urb->pipe))
|
|
|
+ return rh_queue_status (hcd, urb);
|
|
|
if (usb_pipecontrol (urb->pipe))
|
|
|
return rh_call_control (hcd, urb);
|
|
|
- else
|
|
|
- return -EINVAL;
|
|
|
+ return -EINVAL;
|
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
+/* Asynchronous unlinks of root-hub control URBs are legal, but they
|
|
|
+ * don't do anything. Status URB unlinks must be made in process context
|
|
|
+ * with interrupts enabled.
|
|
|
+ */
|
|
|
static int usb_rh_urb_dequeue (struct usb_hcd *hcd, struct urb *urb)
|
|
|
{
|
|
|
- unsigned long flags;
|
|
|
-
|
|
|
- /* note: always a synchronous unlink */
|
|
|
- if ((unsigned long) urb == hcd->rh_timer.data) {
|
|
|
- del_timer_sync (&hcd->rh_timer);
|
|
|
- hcd->rh_timer.data = 0;
|
|
|
-
|
|
|
- local_irq_save (flags);
|
|
|
- urb->hcpriv = NULL;
|
|
|
- usb_hcd_giveback_urb (hcd, urb, NULL);
|
|
|
- local_irq_restore (flags);
|
|
|
+ if (usb_pipeendpoint(urb->pipe) == 0) { /* Control URB */
|
|
|
+ if (in_interrupt())
|
|
|
+ return 0; /* nothing to do */
|
|
|
|
|
|
- } else if (usb_pipeendpoint(urb->pipe) == 0) {
|
|
|
spin_lock_irq(&urb->lock); /* from usb_kill_urb */
|
|
|
++urb->reject;
|
|
|
spin_unlock_irq(&urb->lock);
|
|
@@ -642,8 +643,22 @@ static int usb_rh_urb_dequeue (struct usb_hcd *hcd, struct urb *urb)
|
|
|
spin_lock_irq(&urb->lock);
|
|
|
--urb->reject;
|
|
|
spin_unlock_irq(&urb->lock);
|
|
|
- } else
|
|
|
- return -EINVAL;
|
|
|
+
|
|
|
+ } else { /* Status URB */
|
|
|
+ if (!hcd->uses_new_polling)
|
|
|
+ del_timer_sync (&hcd->rh_timer);
|
|
|
+ local_irq_disable ();
|
|
|
+ spin_lock (&hcd_root_hub_lock);
|
|
|
+ if (urb == hcd->status_urb) {
|
|
|
+ hcd->status_urb = NULL;
|
|
|
+ urb->hcpriv = NULL;
|
|
|
+ } else
|
|
|
+ urb = NULL; /* wasn't fully queued */
|
|
|
+ spin_unlock (&hcd_root_hub_lock);
|
|
|
+ if (urb)
|
|
|
+ usb_hcd_giveback_urb (hcd, urb, NULL);
|
|
|
+ local_irq_enable ();
|
|
|
+ }
|
|
|
|
|
|
return 0;
|
|
|
}
|
|
@@ -885,6 +900,16 @@ int usb_hcd_register_root_hub (struct usb_device *usb_dev, struct usb_hcd *hcd)
|
|
|
}
|
|
|
EXPORT_SYMBOL_GPL(usb_hcd_register_root_hub);
|
|
|
|
|
|
+void usb_enable_root_hub_irq (struct usb_bus *bus)
|
|
|
+{
|
|
|
+ struct usb_hcd *hcd;
|
|
|
+
|
|
|
+ hcd = container_of (bus, struct usb_hcd, self);
|
|
|
+ if (hcd->driver->hub_irq_enable && !hcd->poll_rh &&
|
|
|
+ hcd->state != HC_STATE_HALT)
|
|
|
+ hcd->driver->hub_irq_enable (hcd);
|
|
|
+}
|
|
|
+
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
@@ -1348,7 +1373,8 @@ hcd_endpoint_disable (struct usb_device *udev, struct usb_host_endpoint *ep)
|
|
|
|
|
|
hcd = udev->bus->hcpriv;
|
|
|
|
|
|
- WARN_ON (!HC_IS_RUNNING (hcd->state) && hcd->state != HC_STATE_HALT);
|
|
|
+ WARN_ON (!HC_IS_RUNNING (hcd->state) && hcd->state != HC_STATE_HALT &&
|
|
|
+ udev->state != USB_STATE_NOTATTACHED);
|
|
|
|
|
|
local_irq_disable ();
|
|
|
|
|
@@ -1612,6 +1638,8 @@ void usb_hc_died (struct usb_hcd *hcd)
|
|
|
|
|
|
spin_lock_irqsave (&hcd_root_hub_lock, flags);
|
|
|
if (hcd->rh_registered) {
|
|
|
+ hcd->poll_rh = 0;
|
|
|
+ del_timer(&hcd->rh_timer);
|
|
|
|
|
|
/* make khubd clean up old urbs and devices */
|
|
|
usb_set_device_state (hcd->self.root_hub,
|
|
@@ -1665,6 +1693,8 @@ struct usb_hcd *usb_create_hcd (const struct hc_driver *driver,
|
|
|
hcd->self.bus_name = bus_name;
|
|
|
|
|
|
init_timer(&hcd->rh_timer);
|
|
|
+ hcd->rh_timer.function = rh_timer_func;
|
|
|
+ hcd->rh_timer.data = (unsigned long) hcd;
|
|
|
|
|
|
hcd->driver = driver;
|
|
|
hcd->product_desc = (driver->product_desc) ? driver->product_desc :
|
|
@@ -1748,6 +1778,8 @@ int usb_add_hcd(struct usb_hcd *hcd,
|
|
|
goto err3;
|
|
|
}
|
|
|
|
|
|
+ if (hcd->uses_new_polling && hcd->poll_rh)
|
|
|
+ usb_hcd_poll_rh_status(hcd);
|
|
|
return retval;
|
|
|
|
|
|
err3:
|
|
@@ -1782,6 +1814,9 @@ void usb_remove_hcd(struct usb_hcd *hcd)
|
|
|
spin_unlock_irq (&hcd_root_hub_lock);
|
|
|
usb_disconnect(&hcd->self.root_hub);
|
|
|
|
|
|
+ hcd->poll_rh = 0;
|
|
|
+ del_timer_sync(&hcd->rh_timer);
|
|
|
+
|
|
|
hcd->driver->stop(hcd);
|
|
|
hcd->state = HC_STATE_HALT;
|
|
|
|