aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/usb/core/hcd.c207
-rw-r--r--drivers/usb/core/hcd.h15
-rw-r--r--drivers/usb/core/hub.c5
3 files changed, 140 insertions, 87 deletions
diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c
index 0da23732e80..1180c157b71 100644
--- a/drivers/usb/core/hcd.c
+++ b/drivers/usb/core/hcd.c
@@ -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;
diff --git a/drivers/usb/core/hcd.h b/drivers/usb/core/hcd.h
index 325a51656c3..ac5752778e3 100644
--- a/drivers/usb/core/hcd.h
+++ b/drivers/usb/core/hcd.h
@@ -65,7 +65,8 @@ struct usb_hcd { /* usb_bus.hcpriv points to this */
const char *product_desc; /* product/vendor string */
char irq_descr[24]; /* driver + bus # */
- struct timer_list rh_timer; /* drives root hub */
+ struct timer_list rh_timer; /* drives root-hub polling */
+ struct urb *status_urb; /* the current status urb */
/*
* hardware info/state
@@ -76,6 +77,12 @@ struct usb_hcd { /* usb_bus.hcpriv points to this */
unsigned remote_wakeup:1;/* sw should use wakeup? */
unsigned rh_registered:1;/* is root hub registered? */
+ /* The next flag is a stopgap, to be removed when all the HCDs
+ * support the new root-hub polling mechanism. */
+ unsigned uses_new_polling:1;
+ unsigned poll_rh:1; /* poll for rh status? */
+ unsigned poll_pending:1; /* status has changed? */
+
int irq; /* irq allocated */
void __iomem *regs; /* device memory/io */
u64 rsrc_start; /* memory/io resource start */
@@ -207,6 +214,8 @@ struct hc_driver {
int (*hub_suspend)(struct usb_hcd *);
int (*hub_resume)(struct usb_hcd *);
int (*start_port_reset)(struct usb_hcd *, unsigned port_num);
+ void (*hub_irq_enable)(struct usb_hcd *);
+ /* Needed only if port-change IRQs are level-triggered */
};
extern void usb_hcd_giveback_urb (struct usb_hcd *hcd, struct urb *urb, struct pt_regs *regs);
@@ -243,7 +252,9 @@ void hcd_buffer_free (struct usb_bus *bus, size_t size,
/* generic bus glue, needed for host controllers that don't use PCI */
extern irqreturn_t usb_hcd_irq (int irq, void *__hcd, struct pt_regs *r);
+
extern void usb_hc_died (struct usb_hcd *hcd);
+extern void usb_hcd_poll_rh_status(struct usb_hcd *hcd);
/* -------------------------------------------------------------------------- */
@@ -360,6 +371,8 @@ extern wait_queue_head_t usb_kill_urb_queue;
extern struct usb_bus *usb_bus_get (struct usb_bus *bus);
extern void usb_bus_put (struct usb_bus *bus);
+extern void usb_enable_root_hub_irq (struct usb_bus *bus);
+
extern int usb_find_interface_driver (struct usb_device *dev,
struct usb_interface *interface);
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index a8d879a85d0..6d1a330d577 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -2787,6 +2787,11 @@ static void hub_events(void)
hub->activating = 0;
+ /* If this is a root hub, tell the HCD it's okay to
+ * re-enable port-change interrupts now. */
+ if (!hdev->parent)
+ usb_enable_root_hub_irq(hdev->bus);
+
loop:
usb_unlock_device(hdev);
usb_put_intf(intf);