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