diff options
author | Alan Stern <stern@rowland.harvard.edu> | 2005-04-21 15:56:37 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2005-06-27 17:43:45 -0400 |
commit | d5926ae7a827bdd06b588ffbc56fd4525cd9214a (patch) | |
tree | e5c63a32abfff5d504e4201d93ab593427c5e810 /drivers/usb | |
parent | 02597d2deec2a3de0e2b52c1f83904b65626a0d5 (diff) |
[PATCH] usbcore support for root-hub IRQ instead of polling
This is a revised version of an earlier patch to add support to usbcore
for driving root hubs by interrupts rather than polling.
There's a temporary flag added to struct usb_hcd, marking devices whose
drivers are aware of the new mechanism. By default that flag doesn't get
set so drivers will continue to see the same polling behavior as before.
This way we can convert the HCDs one by one to use interrupt-based event
reporting, and the temporary flag can be removed when they're all done.
Also included is a small change to the hcd_disable_endpoint routine.
Although endpoints normally shouldn't be disabled while a controller is
suspended, it's legal to do so when the controller's driver is being
rmmod'ed.
Lastly the patch adds a new callback, .hub_irq_enable, for use by HCDs
where the root hub's port-change interrupts are level-triggered rather
than edge-triggered. The callback is invoked each time khubd has finished
processing a root hub, to let the HCD know that the interrupt can safely
be re-enabled.
Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/usb')
-rw-r--r-- | drivers/usb/core/hcd.c | 207 | ||||
-rw-r--r-- | drivers/usb/core/hcd.h | 15 | ||||
-rw-r--r-- | drivers/usb/core/hub.c | 5 |
3 files changed, 140 insertions, 87 deletions
diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 0da23732e807..1180c157b717 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c | |||
@@ -519,119 +519,120 @@ error: | |||
519 | /*-------------------------------------------------------------------------*/ | 519 | /*-------------------------------------------------------------------------*/ |
520 | 520 | ||
521 | /* | 521 | /* |
522 | * Root Hub interrupt transfers are synthesized with a timer. | 522 | * Root Hub interrupt transfers are polled using a timer if the |
523 | * Completions are called in_interrupt() but not in_irq(). | 523 | * driver requests it; otherwise the driver is responsible for |
524 | * calling usb_hcd_poll_rh_status() when an event occurs. | ||
524 | * | 525 | * |
525 | * Note: some root hubs (including common UHCI based designs) can't | 526 | * Completions are called in_interrupt(), but they may or may not |
526 | * correctly issue port change IRQs. They're the ones that _need_ a | 527 | * be in_irq(). |
527 | * timer; most other root hubs don't. Some systems could save a | ||
528 | * lot of battery power by eliminating these root hub timer IRQs. | ||
529 | */ | 528 | */ |
529 | void usb_hcd_poll_rh_status(struct usb_hcd *hcd) | ||
530 | { | ||
531 | struct urb *urb; | ||
532 | int length; | ||
533 | unsigned long flags; | ||
534 | char buffer[4]; /* Any root hubs with > 31 ports? */ | ||
530 | 535 | ||
531 | static void rh_report_status (unsigned long ptr); | 536 | if (!hcd->uses_new_polling && !hcd->status_urb) |
537 | return; | ||
532 | 538 | ||
533 | static int rh_status_urb (struct usb_hcd *hcd, struct urb *urb) | 539 | length = hcd->driver->hub_status_data(hcd, buffer); |
534 | { | 540 | if (length > 0) { |
535 | int len = 1 + (urb->dev->maxchild / 8); | ||
536 | 541 | ||
537 | /* rh_timer protected by hcd_data_lock */ | 542 | /* try to complete the status urb */ |
538 | if (hcd->rh_timer.data || urb->transfer_buffer_length < len) { | 543 | local_irq_save (flags); |
539 | dev_dbg (hcd->self.controller, | 544 | spin_lock(&hcd_root_hub_lock); |
540 | "not queuing rh status urb, stat %d\n", | 545 | urb = hcd->status_urb; |
541 | urb->status); | 546 | if (urb) { |
542 | return -EINVAL; | 547 | spin_lock(&urb->lock); |
548 | if (urb->status == -EINPROGRESS) { | ||
549 | hcd->poll_pending = 0; | ||
550 | hcd->status_urb = NULL; | ||
551 | urb->status = 0; | ||
552 | urb->hcpriv = NULL; | ||
553 | urb->actual_length = length; | ||
554 | memcpy(urb->transfer_buffer, buffer, length); | ||
555 | } else /* urb has been unlinked */ | ||
556 | length = 0; | ||
557 | spin_unlock(&urb->lock); | ||
558 | } else | ||
559 | length = 0; | ||
560 | spin_unlock(&hcd_root_hub_lock); | ||
561 | |||
562 | /* local irqs are always blocked in completions */ | ||
563 | if (length > 0) | ||
564 | usb_hcd_giveback_urb (hcd, urb, NULL); | ||
565 | else | ||
566 | hcd->poll_pending = 1; | ||
567 | local_irq_restore (flags); | ||
543 | } | 568 | } |
544 | 569 | ||
545 | init_timer (&hcd->rh_timer); | 570 | /* The USB 2.0 spec says 256 ms. This is close enough and won't |
546 | hcd->rh_timer.function = rh_report_status; | 571 | * exceed that limit if HZ is 100. */ |
547 | hcd->rh_timer.data = (unsigned long) urb; | 572 | if (hcd->uses_new_polling ? hcd->poll_rh : |
548 | /* USB 2.0 spec says 256msec; this is close enough */ | 573 | (length == 0 && hcd->status_urb != NULL)) |
549 | hcd->rh_timer.expires = jiffies + HZ/4; | 574 | mod_timer (&hcd->rh_timer, jiffies + msecs_to_jiffies(250)); |
550 | add_timer (&hcd->rh_timer); | ||
551 | urb->hcpriv = hcd; /* nonzero to indicate it's queued */ | ||
552 | return 0; | ||
553 | } | 575 | } |
576 | EXPORT_SYMBOL_GPL(usb_hcd_poll_rh_status); | ||
554 | 577 | ||
555 | /* timer callback */ | 578 | /* timer callback */ |
579 | static void rh_timer_func (unsigned long _hcd) | ||
580 | { | ||
581 | usb_hcd_poll_rh_status((struct usb_hcd *) _hcd); | ||
582 | } | ||
583 | |||
584 | /*-------------------------------------------------------------------------*/ | ||
556 | 585 | ||
557 | static void rh_report_status (unsigned long ptr) | 586 | static int rh_queue_status (struct usb_hcd *hcd, struct urb *urb) |
558 | { | 587 | { |
559 | struct urb *urb; | 588 | int retval; |
560 | struct usb_hcd *hcd; | ||
561 | int length = 0; | ||
562 | unsigned long flags; | 589 | unsigned long flags; |
590 | int len = 1 + (urb->dev->maxchild / 8); | ||
563 | 591 | ||
564 | urb = (struct urb *) ptr; | 592 | spin_lock_irqsave (&hcd_root_hub_lock, flags); |
565 | local_irq_save (flags); | 593 | if (urb->status != -EINPROGRESS) /* already unlinked */ |
566 | spin_lock (&urb->lock); | 594 | retval = urb->status; |
595 | else if (hcd->status_urb || urb->transfer_buffer_length < len) { | ||
596 | dev_dbg (hcd->self.controller, "not queuing rh status urb\n"); | ||
597 | retval = -EINVAL; | ||
598 | } else { | ||
599 | hcd->status_urb = urb; | ||
600 | urb->hcpriv = hcd; /* indicate it's queued */ | ||
567 | 601 | ||
568 | /* do nothing if the urb's been unlinked */ | 602 | if (!hcd->uses_new_polling) |
569 | if (!urb->dev | 603 | mod_timer (&hcd->rh_timer, jiffies + |
570 | || urb->status != -EINPROGRESS | 604 | msecs_to_jiffies(250)); |
571 | || (hcd = urb->dev->bus->hcpriv) == NULL) { | ||
572 | spin_unlock (&urb->lock); | ||
573 | local_irq_restore (flags); | ||
574 | return; | ||
575 | } | ||
576 | 605 | ||
577 | /* complete the status urb, or retrigger the timer */ | 606 | /* If a status change has already occurred, report it ASAP */ |
578 | spin_lock (&hcd_data_lock); | 607 | else if (hcd->poll_pending) |
579 | if (urb->dev->state == USB_STATE_CONFIGURED) { | 608 | mod_timer (&hcd->rh_timer, jiffies); |
580 | length = hcd->driver->hub_status_data ( | 609 | retval = 0; |
581 | hcd, urb->transfer_buffer); | ||
582 | if (length > 0) { | ||
583 | hcd->rh_timer.data = 0; | ||
584 | urb->actual_length = length; | ||
585 | urb->status = 0; | ||
586 | urb->hcpriv = NULL; | ||
587 | } else | ||
588 | mod_timer (&hcd->rh_timer, jiffies + HZ/4); | ||
589 | } | 610 | } |
590 | spin_unlock (&hcd_data_lock); | 611 | spin_unlock_irqrestore (&hcd_root_hub_lock, flags); |
591 | spin_unlock (&urb->lock); | 612 | return retval; |
592 | |||
593 | /* local irqs are always blocked in completions */ | ||
594 | if (length > 0) | ||
595 | usb_hcd_giveback_urb (hcd, urb, NULL); | ||
596 | local_irq_restore (flags); | ||
597 | } | 613 | } |
598 | 614 | ||
599 | /*-------------------------------------------------------------------------*/ | ||
600 | |||
601 | static int rh_urb_enqueue (struct usb_hcd *hcd, struct urb *urb) | 615 | static int rh_urb_enqueue (struct usb_hcd *hcd, struct urb *urb) |
602 | { | 616 | { |
603 | if (usb_pipeint (urb->pipe)) { | 617 | if (usb_pipeint (urb->pipe)) |
604 | int retval; | 618 | return rh_queue_status (hcd, urb); |
605 | unsigned long flags; | ||
606 | |||
607 | spin_lock_irqsave (&hcd_data_lock, flags); | ||
608 | retval = rh_status_urb (hcd, urb); | ||
609 | spin_unlock_irqrestore (&hcd_data_lock, flags); | ||
610 | return retval; | ||
611 | } | ||
612 | if (usb_pipecontrol (urb->pipe)) | 619 | if (usb_pipecontrol (urb->pipe)) |
613 | return rh_call_control (hcd, urb); | 620 | return rh_call_control (hcd, urb); |
614 | else | 621 | return -EINVAL; |
615 | return -EINVAL; | ||
616 | } | 622 | } |
617 | 623 | ||
618 | /*-------------------------------------------------------------------------*/ | 624 | /*-------------------------------------------------------------------------*/ |
619 | 625 | ||
626 | /* Asynchronous unlinks of root-hub control URBs are legal, but they | ||
627 | * don't do anything. Status URB unlinks must be made in process context | ||
628 | * with interrupts enabled. | ||
629 | */ | ||
620 | static int usb_rh_urb_dequeue (struct usb_hcd *hcd, struct urb *urb) | 630 | static int usb_rh_urb_dequeue (struct usb_hcd *hcd, struct urb *urb) |
621 | { | 631 | { |
622 | unsigned long flags; | 632 | if (usb_pipeendpoint(urb->pipe) == 0) { /* Control URB */ |
623 | 633 | if (in_interrupt()) | |
624 | /* note: always a synchronous unlink */ | 634 | return 0; /* nothing to do */ |
625 | if ((unsigned long) urb == hcd->rh_timer.data) { | ||
626 | del_timer_sync (&hcd->rh_timer); | ||
627 | hcd->rh_timer.data = 0; | ||
628 | |||
629 | local_irq_save (flags); | ||
630 | urb->hcpriv = NULL; | ||
631 | usb_hcd_giveback_urb (hcd, urb, NULL); | ||
632 | local_irq_restore (flags); | ||
633 | 635 | ||
634 | } else if (usb_pipeendpoint(urb->pipe) == 0) { | ||
635 | spin_lock_irq(&urb->lock); /* from usb_kill_urb */ | 636 | spin_lock_irq(&urb->lock); /* from usb_kill_urb */ |
636 | ++urb->reject; | 637 | ++urb->reject; |
637 | spin_unlock_irq(&urb->lock); | 638 | spin_unlock_irq(&urb->lock); |
@@ -642,8 +643,22 @@ static int usb_rh_urb_dequeue (struct usb_hcd *hcd, struct urb *urb) | |||
642 | spin_lock_irq(&urb->lock); | 643 | spin_lock_irq(&urb->lock); |
643 | --urb->reject; | 644 | --urb->reject; |
644 | spin_unlock_irq(&urb->lock); | 645 | spin_unlock_irq(&urb->lock); |
645 | } else | 646 | |
646 | return -EINVAL; | 647 | } else { /* Status URB */ |
648 | if (!hcd->uses_new_polling) | ||
649 | del_timer_sync (&hcd->rh_timer); | ||
650 | local_irq_disable (); | ||
651 | spin_lock (&hcd_root_hub_lock); | ||
652 | if (urb == hcd->status_urb) { | ||
653 | hcd->status_urb = NULL; | ||
654 | urb->hcpriv = NULL; | ||
655 | } else | ||
656 | urb = NULL; /* wasn't fully queued */ | ||
657 | spin_unlock (&hcd_root_hub_lock); | ||
658 | if (urb) | ||
659 | usb_hcd_giveback_urb (hcd, urb, NULL); | ||
660 | local_irq_enable (); | ||
661 | } | ||
647 | 662 | ||
648 | return 0; | 663 | return 0; |
649 | } | 664 | } |
@@ -885,6 +900,16 @@ int usb_hcd_register_root_hub (struct usb_device *usb_dev, struct usb_hcd *hcd) | |||
885 | } | 900 | } |
886 | EXPORT_SYMBOL_GPL(usb_hcd_register_root_hub); | 901 | EXPORT_SYMBOL_GPL(usb_hcd_register_root_hub); |
887 | 902 | ||
903 | void usb_enable_root_hub_irq (struct usb_bus *bus) | ||
904 | { | ||
905 | struct usb_hcd *hcd; | ||
906 | |||
907 | hcd = container_of (bus, struct usb_hcd, self); | ||
908 | if (hcd->driver->hub_irq_enable && !hcd->poll_rh && | ||
909 | hcd->state != HC_STATE_HALT) | ||
910 | hcd->driver->hub_irq_enable (hcd); | ||
911 | } | ||
912 | |||
888 | 913 | ||
889 | /*-------------------------------------------------------------------------*/ | 914 | /*-------------------------------------------------------------------------*/ |
890 | 915 | ||
@@ -1348,7 +1373,8 @@ hcd_endpoint_disable (struct usb_device *udev, struct usb_host_endpoint *ep) | |||
1348 | 1373 | ||
1349 | hcd = udev->bus->hcpriv; | 1374 | hcd = udev->bus->hcpriv; |
1350 | 1375 | ||
1351 | WARN_ON (!HC_IS_RUNNING (hcd->state) && hcd->state != HC_STATE_HALT); | 1376 | WARN_ON (!HC_IS_RUNNING (hcd->state) && hcd->state != HC_STATE_HALT && |
1377 | udev->state != USB_STATE_NOTATTACHED); | ||
1352 | 1378 | ||
1353 | local_irq_disable (); | 1379 | local_irq_disable (); |
1354 | 1380 | ||
@@ -1612,6 +1638,8 @@ void usb_hc_died (struct usb_hcd *hcd) | |||
1612 | 1638 | ||
1613 | spin_lock_irqsave (&hcd_root_hub_lock, flags); | 1639 | spin_lock_irqsave (&hcd_root_hub_lock, flags); |
1614 | if (hcd->rh_registered) { | 1640 | if (hcd->rh_registered) { |
1641 | hcd->poll_rh = 0; | ||
1642 | del_timer(&hcd->rh_timer); | ||
1615 | 1643 | ||
1616 | /* make khubd clean up old urbs and devices */ | 1644 | /* make khubd clean up old urbs and devices */ |
1617 | usb_set_device_state (hcd->self.root_hub, | 1645 | usb_set_device_state (hcd->self.root_hub, |
@@ -1665,6 +1693,8 @@ struct usb_hcd *usb_create_hcd (const struct hc_driver *driver, | |||
1665 | hcd->self.bus_name = bus_name; | 1693 | hcd->self.bus_name = bus_name; |
1666 | 1694 | ||
1667 | init_timer(&hcd->rh_timer); | 1695 | init_timer(&hcd->rh_timer); |
1696 | hcd->rh_timer.function = rh_timer_func; | ||
1697 | hcd->rh_timer.data = (unsigned long) hcd; | ||
1668 | 1698 | ||
1669 | hcd->driver = driver; | 1699 | hcd->driver = driver; |
1670 | hcd->product_desc = (driver->product_desc) ? driver->product_desc : | 1700 | hcd->product_desc = (driver->product_desc) ? driver->product_desc : |
@@ -1748,6 +1778,8 @@ int usb_add_hcd(struct usb_hcd *hcd, | |||
1748 | goto err3; | 1778 | goto err3; |
1749 | } | 1779 | } |
1750 | 1780 | ||
1781 | if (hcd->uses_new_polling && hcd->poll_rh) | ||
1782 | usb_hcd_poll_rh_status(hcd); | ||
1751 | return retval; | 1783 | return retval; |
1752 | 1784 | ||
1753 | err3: | 1785 | err3: |
@@ -1782,6 +1814,9 @@ void usb_remove_hcd(struct usb_hcd *hcd) | |||
1782 | spin_unlock_irq (&hcd_root_hub_lock); | 1814 | spin_unlock_irq (&hcd_root_hub_lock); |
1783 | usb_disconnect(&hcd->self.root_hub); | 1815 | usb_disconnect(&hcd->self.root_hub); |
1784 | 1816 | ||
1817 | hcd->poll_rh = 0; | ||
1818 | del_timer_sync(&hcd->rh_timer); | ||
1819 | |||
1785 | hcd->driver->stop(hcd); | 1820 | hcd->driver->stop(hcd); |
1786 | hcd->state = HC_STATE_HALT; | 1821 | hcd->state = HC_STATE_HALT; |
1787 | 1822 | ||
diff --git a/drivers/usb/core/hcd.h b/drivers/usb/core/hcd.h index 325a51656c3f..ac5752778e39 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 */ | |||
65 | const char *product_desc; /* product/vendor string */ | 65 | const char *product_desc; /* product/vendor string */ |
66 | char irq_descr[24]; /* driver + bus # */ | 66 | char irq_descr[24]; /* driver + bus # */ |
67 | 67 | ||
68 | struct timer_list rh_timer; /* drives root hub */ | 68 | struct timer_list rh_timer; /* drives root-hub polling */ |
69 | struct urb *status_urb; /* the current status urb */ | ||
69 | 70 | ||
70 | /* | 71 | /* |
71 | * hardware info/state | 72 | * hardware info/state |
@@ -76,6 +77,12 @@ struct usb_hcd { /* usb_bus.hcpriv points to this */ | |||
76 | unsigned remote_wakeup:1;/* sw should use wakeup? */ | 77 | unsigned remote_wakeup:1;/* sw should use wakeup? */ |
77 | unsigned rh_registered:1;/* is root hub registered? */ | 78 | unsigned rh_registered:1;/* is root hub registered? */ |
78 | 79 | ||
80 | /* The next flag is a stopgap, to be removed when all the HCDs | ||
81 | * support the new root-hub polling mechanism. */ | ||
82 | unsigned uses_new_polling:1; | ||
83 | unsigned poll_rh:1; /* poll for rh status? */ | ||
84 | unsigned poll_pending:1; /* status has changed? */ | ||
85 | |||
79 | int irq; /* irq allocated */ | 86 | int irq; /* irq allocated */ |
80 | void __iomem *regs; /* device memory/io */ | 87 | void __iomem *regs; /* device memory/io */ |
81 | u64 rsrc_start; /* memory/io resource start */ | 88 | u64 rsrc_start; /* memory/io resource start */ |
@@ -207,6 +214,8 @@ struct hc_driver { | |||
207 | int (*hub_suspend)(struct usb_hcd *); | 214 | int (*hub_suspend)(struct usb_hcd *); |
208 | int (*hub_resume)(struct usb_hcd *); | 215 | int (*hub_resume)(struct usb_hcd *); |
209 | int (*start_port_reset)(struct usb_hcd *, unsigned port_num); | 216 | int (*start_port_reset)(struct usb_hcd *, unsigned port_num); |
217 | void (*hub_irq_enable)(struct usb_hcd *); | ||
218 | /* Needed only if port-change IRQs are level-triggered */ | ||
210 | }; | 219 | }; |
211 | 220 | ||
212 | extern void usb_hcd_giveback_urb (struct usb_hcd *hcd, struct urb *urb, struct pt_regs *regs); | 221 | 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, | |||
243 | 252 | ||
244 | /* generic bus glue, needed for host controllers that don't use PCI */ | 253 | /* generic bus glue, needed for host controllers that don't use PCI */ |
245 | extern irqreturn_t usb_hcd_irq (int irq, void *__hcd, struct pt_regs *r); | 254 | extern irqreturn_t usb_hcd_irq (int irq, void *__hcd, struct pt_regs *r); |
255 | |||
246 | extern void usb_hc_died (struct usb_hcd *hcd); | 256 | extern void usb_hc_died (struct usb_hcd *hcd); |
257 | extern void usb_hcd_poll_rh_status(struct usb_hcd *hcd); | ||
247 | 258 | ||
248 | /* -------------------------------------------------------------------------- */ | 259 | /* -------------------------------------------------------------------------- */ |
249 | 260 | ||
@@ -360,6 +371,8 @@ extern wait_queue_head_t usb_kill_urb_queue; | |||
360 | extern struct usb_bus *usb_bus_get (struct usb_bus *bus); | 371 | extern struct usb_bus *usb_bus_get (struct usb_bus *bus); |
361 | extern void usb_bus_put (struct usb_bus *bus); | 372 | extern void usb_bus_put (struct usb_bus *bus); |
362 | 373 | ||
374 | extern void usb_enable_root_hub_irq (struct usb_bus *bus); | ||
375 | |||
363 | extern int usb_find_interface_driver (struct usb_device *dev, | 376 | extern int usb_find_interface_driver (struct usb_device *dev, |
364 | struct usb_interface *interface); | 377 | struct usb_interface *interface); |
365 | 378 | ||
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index a8d879a85d04..6d1a330d577b 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c | |||
@@ -2787,6 +2787,11 @@ static void hub_events(void) | |||
2787 | 2787 | ||
2788 | hub->activating = 0; | 2788 | hub->activating = 0; |
2789 | 2789 | ||
2790 | /* If this is a root hub, tell the HCD it's okay to | ||
2791 | * re-enable port-change interrupts now. */ | ||
2792 | if (!hdev->parent) | ||
2793 | usb_enable_root_hub_irq(hdev->bus); | ||
2794 | |||
2790 | loop: | 2795 | loop: |
2791 | usb_unlock_device(hdev); | 2796 | usb_unlock_device(hdev); |
2792 | usb_put_intf(intf); | 2797 | usb_put_intf(intf); |