On Sat, 19 Feb 2005, Alan Stern wrote:

> I already wrote a patch for it a couple of months ago.  It wasn't complete 
> and it needs to be updated.

Here is a somewhat updated version of that old patch.  I haven't tested 
it.  Also the HCDs should be changed to match.

Alan Stern



diff -u usb-2.6/drivers/usb/core0/hcd-pci.c usb-2.6/drivers/usb/core/hcd-pci.c
--- usb-2.6/drivers/usb/core0/hcd-pci.c Sat Feb 19 21:51:08 2005
+++ usb-2.6/drivers/usb/core/hcd-pci.c  Sat Feb 19 22:17:46 2005
@@ -211,6 +211,7 @@
        int                     retval = 0;
        int                     has_pci_pm;
        pci_power_t             state;
+       unsigned                poll_rh;
 
        hcd = pci_get_drvdata(dev);
 
@@ -259,6 +260,11 @@
                        free_irq (hcd->irq, hcd);
                }
 
+               poll_rh = hcd->poll_rh;
+               hcd->poll_rh = 0;
+               del_timer_sync (&hcd->rh_timer);
+               hcd->poll_rh = poll_rh;
+
                if (!has_pci_pm) {
                        dev_dbg (hcd->self.controller, "--> PCI D0/legacy\n");
                        break;
@@ -352,6 +358,8 @@
                usb_hc_died (hcd);
        }
 
+       if (hcd->poll_rh)
+               usb_hcd_poll_rh_status (hcd);
        return retval;
 }
 EXPORT_SYMBOL (usb_hcd_pci_resume);
diff -u usb-2.6/drivers/usb/core0/hcd.c usb-2.6/drivers/usb/core/hcd.c
--- usb-2.6/drivers/usb/core0/hcd.c     Sat Feb 19 21:51:08 2005
+++ usb-2.6/drivers/usb/core/hcd.c      Sat Feb 19 22:17:46 2005
@@ -103,6 +103,9 @@
 /* used when updating hcd data */
 static DEFINE_SPINLOCK(hcd_data_lock);
 
+/* used for queuing URBs to virtual root hubs */
+static DEFINE_SPINLOCK(hcd_root_hub_lock);
+
 /* wait queue for synchronous unlinks */
 DECLARE_WAIT_QUEUE_HEAD(usb_kill_urb_queue);
 
@@ -515,119 +518,113 @@
 /*-------------------------------------------------------------------------*/
 
 /*
- * 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;
+       u8              buffer[4];      /* Any root hubs with > 31 ports? */
 
-static void rh_report_status (unsigned long ptr);
+       length = hcd->driver->hub_status_data(hcd, buffer);
+       if (length > 0) {
 
-static int rh_status_urb (struct usb_hcd *hcd, struct urb *urb) 
-{
-       int     len = 1 + (urb->dev->maxchild / 8);
+               /* 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);
 
-       /* 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;
+               /* 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;
+       if (hcd->poll_rh)
+               mod_timer (&hcd->rh_timer, jiffies + HZ/4);
 }
 
 /* timer callback */
-
-static void rh_report_status (unsigned long ptr)
+static void rh_timer_func (unsigned long _hcd)
 {
-       struct urb      *urb;
-       struct usb_hcd  *hcd;
-       int             length = 0;
-       unsigned long   flags;
+       struct usb_hcd  *hcd = (struct usb_hcd *) _hcd;
 
-       urb = (struct urb *) ptr;
-       local_irq_save (flags);
-       spin_lock (&urb->lock);
+       if (hcd->poll_rh || hcd->poll_pending)
+               usb_hcd_poll_rh_status(hcd);
+}
 
-       /* 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;
-       }
+/*-------------------------------------------------------------------------*/
 
-       /* 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);
-       }
-       spin_unlock (&hcd_data_lock);
-       spin_unlock (&urb->lock);
+static int rh_queue_status (struct usb_hcd *hcd, struct urb *urb)
+{
+       int             retval;
+       unsigned long   flags;
+       int             len = 1 + (urb->dev->maxchild / 8);
 
-       /* local irqs are always blocked in completions */
-       if (length > 0)
-               usb_hcd_giveback_urb (hcd, urb, NULL);
-       local_irq_restore (flags);
+       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 */
+               retval = 0;
+       }
+       spin_unlock_irqrestore (&hcd_root_hub_lock, flags);
+
+       /* If a status change has already occurred, report it ASAP */
+       if (hcd->poll_pending)
+               mod_timer (&hcd->rh_timer, jiffies);
+       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.  All other callers must be 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) {
+               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);
@@ -638,8 +635,20 @@
                spin_lock_irq(&urb->lock);
                --urb->reject;
                spin_unlock_irq(&urb->lock);
-       } else
-               return -EINVAL;
+
+       } else {
+               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;
 }
@@ -1613,6 +1622,9 @@
        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->poll_rh = 1;               /* Backward compatibility */
 
        hcd->driver = driver;
        hcd->product_desc = (driver->product_desc) ? driver->product_desc :
@@ -1694,6 +1706,8 @@
                goto err3;
        }
 
+       if (hcd->poll_rh)
+               usb_hcd_poll_rh_status(hcd);
        return retval;
 
  err3:
@@ -1723,6 +1737,8 @@
                hcd->state = USB_STATE_QUIESCING;
 
        dev_dbg(hcd->self.controller, "roothub graceful disconnect\n");
+       hcd->poll_rh = 0;
+       del_timer_sync(&hcd->rh_timer);
        usb_disconnect(&hcd->self.root_hub);
 
        hcd->driver->stop(hcd);
diff -u usb-2.6/drivers/usb/core0/hcd.h usb-2.6/drivers/usb/core/hcd.h
--- usb-2.6/drivers/usb/core0/hcd.h     Sat Feb 19 21:51:08 2005
+++ usb-2.6/drivers/usb/core/hcd.h      Sat Feb 19 22:17:46 2005
@@ -66,6 +66,7 @@
        char                    irq_descr[24];  /* driver + bus # */
 
        struct timer_list       rh_timer;       /* drives root hub */
+       struct urb              *status_urb;    /* the current status urb */
 
        /*
         * hardware info/state
@@ -74,6 +75,9 @@
        unsigned                saw_irq : 1;
        unsigned                can_wakeup:1;   /* hw supports wakeup? */
        unsigned                remote_wakeup:1;/* sw should use wakeup? */
+       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 */
@@ -359,6 +363,8 @@
 
        return usb_register_root_hub (usb_dev, hcd->self.controller);
 }
+
+extern void usb_hcd_poll_rh_status(struct usb_hcd *hcd);
 
 extern void usb_set_device_state(struct usb_device *udev,
                enum usb_device_state new_state);



-------------------------------------------------------
SF email is sponsored by - The IT Product Guide
Read honest & candid reviews on hundreds of IT Products from real users.
Discover which products truly live up to the hype. Start reading now.
http://ads.osdn.com/?ad_id=6595&alloc_id=14396&op=click
_______________________________________________
linux-usb-devel@lists.sourceforge.net
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel

Reply via email to