2005-04-17 06:20:36 +08:00
|
|
|
/*
|
|
|
|
* Universal Host Controller Interface driver for USB.
|
|
|
|
*
|
|
|
|
* Maintainer: Alan Stern <stern@rowland.harvard.edu>
|
|
|
|
*
|
|
|
|
* (C) Copyright 1999 Linus Torvalds
|
|
|
|
* (C) Copyright 1999-2002 Johannes Erdfelt, johannes@erdfelt.com
|
|
|
|
* (C) Copyright 1999 Randy Dunlap
|
|
|
|
* (C) Copyright 1999 Georg Acher, acher@in.tum.de
|
|
|
|
* (C) Copyright 1999 Deti Fliegl, deti@fliegl.de
|
|
|
|
* (C) Copyright 1999 Thomas Sailer, sailer@ife.ee.ethz.ch
|
|
|
|
* (C) Copyright 1999 Roman Weissgaerber, weissg@vienna.at
|
|
|
|
* (C) Copyright 2000 Yggdrasil Computing, Inc. (port of new PCI interface
|
|
|
|
* support from usb-ohci.c by Adam Richter, adam@yggdrasil.com).
|
|
|
|
* (C) Copyright 1999 Gregory P. Smith (from usb-ohci.c)
|
2007-02-20 04:52:45 +08:00
|
|
|
* (C) Copyright 2004-2007 Alan Stern, stern@rowland.harvard.edu
|
2005-04-17 06:20:36 +08:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Technically, updating td->status here is a race, but it's not really a
|
|
|
|
* problem. The worst that can happen is that we set the IOC bit again
|
|
|
|
* generating a spurious interrupt. We could fix this by creating another
|
|
|
|
* QH and leaving the IOC bit always set, but then we would have to play
|
|
|
|
* games with the FSBR code to make sure we get the correct order in all
|
|
|
|
* the cases. I don't think it's worth the effort
|
|
|
|
*/
|
2005-12-18 06:58:46 +08:00
|
|
|
static void uhci_set_next_interrupt(struct uhci_hcd *uhci)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-04-22 04:04:58 +08:00
|
|
|
if (uhci->is_stopped)
|
2005-09-06 01:59:51 +08:00
|
|
|
mod_timer(&uhci_to_hcd(uhci)->rh_timer, jiffies);
|
2005-04-17 06:20:36 +08:00
|
|
|
uhci->term_td->status |= cpu_to_le32(TD_CTRL_IOC);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void uhci_clear_next_interrupt(struct uhci_hcd *uhci)
|
|
|
|
{
|
|
|
|
uhci->term_td->status &= ~cpu_to_le32(TD_CTRL_IOC);
|
|
|
|
}
|
|
|
|
|
2006-05-12 23:35:45 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Full-Speed Bandwidth Reclamation (FSBR).
|
|
|
|
* We turn on FSBR whenever a queue that wants it is advancing,
|
|
|
|
* and leave it on for a short time thereafter.
|
|
|
|
*/
|
|
|
|
static void uhci_fsbr_on(struct uhci_hcd *uhci)
|
|
|
|
{
|
2007-03-20 03:31:42 +08:00
|
|
|
struct uhci_qh *lqh;
|
2007-02-20 04:52:45 +08:00
|
|
|
|
2007-03-20 03:31:42 +08:00
|
|
|
/* The terminating skeleton QH always points back to the first
|
|
|
|
* FSBR QH. Make the last async QH point to the terminating
|
|
|
|
* skeleton QH. */
|
2006-05-12 23:35:45 +08:00
|
|
|
uhci->fsbr_is_on = 1;
|
2007-02-20 04:52:45 +08:00
|
|
|
lqh = list_entry(uhci->skel_async_qh->node.prev,
|
|
|
|
struct uhci_qh, node);
|
2007-03-20 03:31:42 +08:00
|
|
|
lqh->link = LINK_TO_QH(uhci->skel_term_qh);
|
2006-05-12 23:35:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void uhci_fsbr_off(struct uhci_hcd *uhci)
|
|
|
|
{
|
2007-02-20 04:52:45 +08:00
|
|
|
struct uhci_qh *lqh;
|
|
|
|
|
2007-03-20 03:31:42 +08:00
|
|
|
/* Remove the link from the last async QH to the terminating
|
|
|
|
* skeleton QH. */
|
2006-05-12 23:35:45 +08:00
|
|
|
uhci->fsbr_is_on = 0;
|
2007-02-20 04:52:45 +08:00
|
|
|
lqh = list_entry(uhci->skel_async_qh->node.prev,
|
|
|
|
struct uhci_qh, node);
|
2007-03-20 03:31:42 +08:00
|
|
|
lqh->link = UHCI_PTR_TERM;
|
2006-05-12 23:35:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void uhci_add_fsbr(struct uhci_hcd *uhci, struct urb *urb)
|
|
|
|
{
|
|
|
|
struct urb_priv *urbp = urb->hcpriv;
|
|
|
|
|
|
|
|
if (!(urb->transfer_flags & URB_NO_FSBR))
|
|
|
|
urbp->fsbr = 1;
|
|
|
|
}
|
|
|
|
|
2006-06-06 00:28:57 +08:00
|
|
|
static void uhci_urbp_wants_fsbr(struct uhci_hcd *uhci, struct urb_priv *urbp)
|
2006-05-12 23:35:45 +08:00
|
|
|
{
|
|
|
|
if (urbp->fsbr) {
|
2006-06-06 00:28:57 +08:00
|
|
|
uhci->fsbr_is_wanted = 1;
|
2006-05-12 23:35:45 +08:00
|
|
|
if (!uhci->fsbr_is_on)
|
|
|
|
uhci_fsbr_on(uhci);
|
2006-06-06 00:28:57 +08:00
|
|
|
else if (uhci->fsbr_expiring) {
|
|
|
|
uhci->fsbr_expiring = 0;
|
|
|
|
del_timer(&uhci->fsbr_timer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void uhci_fsbr_timeout(unsigned long _uhci)
|
|
|
|
{
|
|
|
|
struct uhci_hcd *uhci = (struct uhci_hcd *) _uhci;
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&uhci->lock, flags);
|
|
|
|
if (uhci->fsbr_expiring) {
|
|
|
|
uhci->fsbr_expiring = 0;
|
|
|
|
uhci_fsbr_off(uhci);
|
2006-05-12 23:35:45 +08:00
|
|
|
}
|
2006-06-06 00:28:57 +08:00
|
|
|
spin_unlock_irqrestore(&uhci->lock, flags);
|
2006-05-12 23:35:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-04-25 23:14:31 +08:00
|
|
|
static struct uhci_td *uhci_alloc_td(struct uhci_hcd *uhci)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
dma_addr_t dma_handle;
|
|
|
|
struct uhci_td *td;
|
|
|
|
|
|
|
|
td = dma_pool_alloc(uhci->td_pool, GFP_ATOMIC, &dma_handle);
|
|
|
|
if (!td)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
td->dma_handle = dma_handle;
|
|
|
|
td->frame = -1;
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&td->list);
|
|
|
|
INIT_LIST_HEAD(&td->fl_list);
|
|
|
|
|
|
|
|
return td;
|
|
|
|
}
|
|
|
|
|
2005-12-18 06:58:46 +08:00
|
|
|
static void uhci_free_td(struct uhci_hcd *uhci, struct uhci_td *td)
|
|
|
|
{
|
2008-09-21 10:09:37 +08:00
|
|
|
if (!list_empty(&td->list))
|
|
|
|
dev_WARN(uhci_dev(uhci), "td %p still in list!\n", td);
|
|
|
|
if (!list_empty(&td->fl_list))
|
|
|
|
dev_WARN(uhci_dev(uhci), "td %p still in fl_list!\n", td);
|
2005-12-18 06:58:46 +08:00
|
|
|
|
|
|
|
dma_pool_free(uhci->td_pool, td, td->dma_handle);
|
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
static inline void uhci_fill_td(struct uhci_td *td, u32 status,
|
|
|
|
u32 token, u32 buffer)
|
|
|
|
{
|
|
|
|
td->status = cpu_to_le32(status);
|
|
|
|
td->token = cpu_to_le32(token);
|
|
|
|
td->buffer = cpu_to_le32(buffer);
|
|
|
|
}
|
|
|
|
|
2006-05-12 23:29:04 +08:00
|
|
|
static void uhci_add_td_to_urbp(struct uhci_td *td, struct urb_priv *urbp)
|
|
|
|
{
|
|
|
|
list_add_tail(&td->list, &urbp->td_list);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void uhci_remove_td_from_urbp(struct uhci_td *td)
|
|
|
|
{
|
|
|
|
list_del_init(&td->list);
|
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/*
|
2005-12-01 06:16:19 +08:00
|
|
|
* We insert Isochronous URBs directly into the frame list at the beginning
|
2005-04-17 06:20:36 +08:00
|
|
|
*/
|
2005-12-18 06:58:46 +08:00
|
|
|
static inline void uhci_insert_td_in_frame_list(struct uhci_hcd *uhci,
|
|
|
|
struct uhci_td *td, unsigned framenum)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
framenum &= (UHCI_NUMFRAMES - 1);
|
|
|
|
|
|
|
|
td->frame = framenum;
|
|
|
|
|
|
|
|
/* Is there a TD already mapped there? */
|
2005-09-17 02:22:51 +08:00
|
|
|
if (uhci->frame_cpu[framenum]) {
|
2005-04-17 06:20:36 +08:00
|
|
|
struct uhci_td *ftd, *ltd;
|
|
|
|
|
2005-09-17 02:22:51 +08:00
|
|
|
ftd = uhci->frame_cpu[framenum];
|
2005-04-17 06:20:36 +08:00
|
|
|
ltd = list_entry(ftd->fl_list.prev, struct uhci_td, fl_list);
|
|
|
|
|
|
|
|
list_add_tail(&td->fl_list, &ftd->fl_list);
|
|
|
|
|
|
|
|
td->link = ltd->link;
|
|
|
|
wmb();
|
2007-02-20 04:51:51 +08:00
|
|
|
ltd->link = LINK_TO_TD(td);
|
2005-04-17 06:20:36 +08:00
|
|
|
} else {
|
2005-09-17 02:22:51 +08:00
|
|
|
td->link = uhci->frame[framenum];
|
2005-04-17 06:20:36 +08:00
|
|
|
wmb();
|
2007-02-20 04:51:51 +08:00
|
|
|
uhci->frame[framenum] = LINK_TO_TD(td);
|
2005-09-17 02:22:51 +08:00
|
|
|
uhci->frame_cpu[framenum] = td;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-12-18 06:58:46 +08:00
|
|
|
static inline void uhci_remove_td_from_frame_list(struct uhci_hcd *uhci,
|
2005-10-14 05:00:24 +08:00
|
|
|
struct uhci_td *td)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
/* If it's not inserted, don't remove it */
|
2005-10-14 05:00:24 +08:00
|
|
|
if (td->frame == -1) {
|
|
|
|
WARN_ON(!list_empty(&td->fl_list));
|
2005-04-17 06:20:36 +08:00
|
|
|
return;
|
2005-10-14 05:00:24 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-10-14 05:00:24 +08:00
|
|
|
if (uhci->frame_cpu[td->frame] == td) {
|
2005-04-17 06:20:36 +08:00
|
|
|
if (list_empty(&td->fl_list)) {
|
2005-09-17 02:22:51 +08:00
|
|
|
uhci->frame[td->frame] = td->link;
|
|
|
|
uhci->frame_cpu[td->frame] = NULL;
|
2005-04-17 06:20:36 +08:00
|
|
|
} else {
|
|
|
|
struct uhci_td *ntd;
|
|
|
|
|
|
|
|
ntd = list_entry(td->fl_list.next, struct uhci_td, fl_list);
|
2007-02-20 04:51:51 +08:00
|
|
|
uhci->frame[td->frame] = LINK_TO_TD(ntd);
|
2005-09-17 02:22:51 +08:00
|
|
|
uhci->frame_cpu[td->frame] = ntd;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
struct uhci_td *ptd;
|
|
|
|
|
|
|
|
ptd = list_entry(td->fl_list.prev, struct uhci_td, fl_list);
|
|
|
|
ptd->link = td->link;
|
|
|
|
}
|
|
|
|
|
|
|
|
list_del_init(&td->fl_list);
|
|
|
|
td->frame = -1;
|
|
|
|
}
|
|
|
|
|
2006-05-20 04:52:35 +08:00
|
|
|
static inline void uhci_remove_tds_from_frame(struct uhci_hcd *uhci,
|
|
|
|
unsigned int framenum)
|
|
|
|
{
|
|
|
|
struct uhci_td *ftd, *ltd;
|
|
|
|
|
|
|
|
framenum &= (UHCI_NUMFRAMES - 1);
|
|
|
|
|
|
|
|
ftd = uhci->frame_cpu[framenum];
|
|
|
|
if (ftd) {
|
|
|
|
ltd = list_entry(ftd->fl_list.prev, struct uhci_td, fl_list);
|
|
|
|
uhci->frame[framenum] = ltd->link;
|
|
|
|
uhci->frame_cpu[framenum] = NULL;
|
|
|
|
|
|
|
|
while (!list_empty(&ftd->fl_list))
|
|
|
|
list_del_init(ftd->fl_list.prev);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-12-18 06:58:46 +08:00
|
|
|
/*
|
|
|
|
* Remove all the TDs for an Isochronous URB from the frame list
|
|
|
|
*/
|
|
|
|
static void uhci_unlink_isochronous_tds(struct uhci_hcd *uhci, struct urb *urb)
|
2005-10-14 05:00:24 +08:00
|
|
|
{
|
|
|
|
struct urb_priv *urbp = (struct urb_priv *) urb->hcpriv;
|
|
|
|
struct uhci_td *td;
|
|
|
|
|
|
|
|
list_for_each_entry(td, &urbp->td_list, list)
|
2005-12-18 06:58:46 +08:00
|
|
|
uhci_remove_td_from_frame_list(uhci, td);
|
2005-10-14 05:00:24 +08:00
|
|
|
}
|
|
|
|
|
2005-12-18 06:58:46 +08:00
|
|
|
static struct uhci_qh *uhci_alloc_qh(struct uhci_hcd *uhci,
|
|
|
|
struct usb_device *udev, struct usb_host_endpoint *hep)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
dma_addr_t dma_handle;
|
|
|
|
struct uhci_qh *qh;
|
|
|
|
|
|
|
|
qh = dma_pool_alloc(uhci->qh_pool, GFP_ATOMIC, &dma_handle);
|
|
|
|
if (!qh)
|
|
|
|
return NULL;
|
|
|
|
|
2006-05-12 23:19:19 +08:00
|
|
|
memset(qh, 0, sizeof(*qh));
|
2005-04-17 06:20:36 +08:00
|
|
|
qh->dma_handle = dma_handle;
|
|
|
|
|
|
|
|
qh->element = UHCI_PTR_TERM;
|
|
|
|
qh->link = UHCI_PTR_TERM;
|
|
|
|
|
2005-12-18 06:58:46 +08:00
|
|
|
INIT_LIST_HEAD(&qh->queue);
|
|
|
|
INIT_LIST_HEAD(&qh->node);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-12-18 06:58:46 +08:00
|
|
|
if (udev) { /* Normal QH */
|
2007-01-09 01:01:43 +08:00
|
|
|
qh->type = hep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
|
|
|
|
if (qh->type != USB_ENDPOINT_XFER_ISOC) {
|
|
|
|
qh->dummy_td = uhci_alloc_td(uhci);
|
|
|
|
if (!qh->dummy_td) {
|
|
|
|
dma_pool_free(uhci->qh_pool, qh, dma_handle);
|
|
|
|
return NULL;
|
|
|
|
}
|
2005-12-18 07:00:12 +08:00
|
|
|
}
|
2005-12-18 06:58:46 +08:00
|
|
|
qh->state = QH_STATE_IDLE;
|
|
|
|
qh->hep = hep;
|
|
|
|
qh->udev = udev;
|
|
|
|
hep->hcpriv = qh;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-01-17 00:56:32 +08:00
|
|
|
if (qh->type == USB_ENDPOINT_XFER_INT ||
|
|
|
|
qh->type == USB_ENDPOINT_XFER_ISOC)
|
|
|
|
qh->load = usb_calc_bus_time(udev->speed,
|
|
|
|
usb_endpoint_dir_in(&hep->desc),
|
|
|
|
qh->type == USB_ENDPOINT_XFER_ISOC,
|
|
|
|
le16_to_cpu(hep->desc.wMaxPacketSize))
|
|
|
|
/ 1000 + 1;
|
|
|
|
|
2005-12-18 06:58:46 +08:00
|
|
|
} else { /* Skeleton QH */
|
|
|
|
qh->state = QH_STATE_ACTIVE;
|
2006-05-06 04:26:58 +08:00
|
|
|
qh->type = -1;
|
2005-12-18 06:58:46 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
return qh;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void uhci_free_qh(struct uhci_hcd *uhci, struct uhci_qh *qh)
|
|
|
|
{
|
2005-12-18 06:58:46 +08:00
|
|
|
WARN_ON(qh->state != QH_STATE_IDLE && qh->udev);
|
2008-09-21 10:09:37 +08:00
|
|
|
if (!list_empty(&qh->queue))
|
|
|
|
dev_WARN(uhci_dev(uhci), "qh %p list not empty!\n", qh);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-12-18 06:58:46 +08:00
|
|
|
list_del(&qh->node);
|
|
|
|
if (qh->udev) {
|
|
|
|
qh->hep->hcpriv = NULL;
|
2007-01-09 01:01:43 +08:00
|
|
|
if (qh->dummy_td)
|
|
|
|
uhci_free_td(uhci, qh->dummy_td);
|
2005-12-18 06:58:46 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
dma_pool_free(uhci->qh_pool, qh, qh->dma_handle);
|
|
|
|
}
|
|
|
|
|
2005-12-18 07:02:38 +08:00
|
|
|
/*
|
2006-05-12 23:23:19 +08:00
|
|
|
* When a queue is stopped and a dequeued URB is given back, adjust
|
|
|
|
* the previous TD link (if the URB isn't first on the queue) or
|
|
|
|
* save its toggle value (if it is first and is currently executing).
|
2006-05-20 04:39:52 +08:00
|
|
|
*
|
|
|
|
* Returns 0 if the URB should not yet be given back, 1 otherwise.
|
2005-12-18 07:02:38 +08:00
|
|
|
*/
|
2006-05-20 04:39:52 +08:00
|
|
|
static int uhci_cleanup_queue(struct uhci_hcd *uhci, struct uhci_qh *qh,
|
2006-05-12 23:23:19 +08:00
|
|
|
struct urb *urb)
|
2005-12-18 07:02:38 +08:00
|
|
|
{
|
2006-05-12 23:23:19 +08:00
|
|
|
struct urb_priv *urbp = urb->hcpriv;
|
2005-12-18 07:02:38 +08:00
|
|
|
struct uhci_td *td;
|
2006-05-20 04:39:52 +08:00
|
|
|
int ret = 1;
|
2005-12-18 07:02:38 +08:00
|
|
|
|
2006-05-12 23:23:19 +08:00
|
|
|
/* Isochronous pipes don't use toggles and their TD link pointers
|
2006-05-20 04:39:52 +08:00
|
|
|
* get adjusted during uhci_urb_dequeue(). But since their queues
|
|
|
|
* cannot truly be stopped, we have to watch out for dequeues
|
|
|
|
* occurring after the nominal unlink frame. */
|
|
|
|
if (qh->type == USB_ENDPOINT_XFER_ISOC) {
|
|
|
|
ret = (uhci->frame_number + uhci->is_stopped !=
|
|
|
|
qh->unlink_frame);
|
2006-06-06 00:28:57 +08:00
|
|
|
goto done;
|
2006-05-20 04:39:52 +08:00
|
|
|
}
|
2006-05-12 23:23:19 +08:00
|
|
|
|
|
|
|
/* If the URB isn't first on its queue, adjust the link pointer
|
|
|
|
* of the last TD in the previous URB. The toggle doesn't need
|
|
|
|
* to be saved since this URB can't be executing yet. */
|
|
|
|
if (qh->queue.next != &urbp->node) {
|
|
|
|
struct urb_priv *purbp;
|
|
|
|
struct uhci_td *ptd;
|
|
|
|
|
|
|
|
purbp = list_entry(urbp->node.prev, struct urb_priv, node);
|
|
|
|
WARN_ON(list_empty(&purbp->td_list));
|
|
|
|
ptd = list_entry(purbp->td_list.prev, struct uhci_td,
|
|
|
|
list);
|
|
|
|
td = list_entry(urbp->td_list.prev, struct uhci_td,
|
|
|
|
list);
|
|
|
|
ptd->link = td->link;
|
2006-06-06 00:28:57 +08:00
|
|
|
goto done;
|
2006-05-12 23:23:19 +08:00
|
|
|
}
|
|
|
|
|
2005-12-18 07:02:38 +08:00
|
|
|
/* If the QH element pointer is UHCI_PTR_TERM then then currently
|
|
|
|
* executing URB has already been unlinked, so this one isn't it. */
|
2006-05-12 23:23:19 +08:00
|
|
|
if (qh_element(qh) == UHCI_PTR_TERM)
|
2006-06-06 00:28:57 +08:00
|
|
|
goto done;
|
2005-12-18 07:02:38 +08:00
|
|
|
qh->element = UHCI_PTR_TERM;
|
|
|
|
|
2007-01-09 01:01:43 +08:00
|
|
|
/* Control pipes don't have to worry about toggles */
|
2006-05-12 23:23:19 +08:00
|
|
|
if (qh->type == USB_ENDPOINT_XFER_CONTROL)
|
2006-06-06 00:28:57 +08:00
|
|
|
goto done;
|
2005-12-18 07:02:38 +08:00
|
|
|
|
2006-05-12 23:23:19 +08:00
|
|
|
/* Save the next toggle value */
|
2006-05-12 23:19:19 +08:00
|
|
|
WARN_ON(list_empty(&urbp->td_list));
|
|
|
|
td = list_entry(urbp->td_list.next, struct uhci_td, list);
|
|
|
|
qh->needs_fixup = 1;
|
|
|
|
qh->initial_toggle = uhci_toggle(td_token(td));
|
2006-06-06 00:28:57 +08:00
|
|
|
|
|
|
|
done:
|
2006-05-20 04:39:52 +08:00
|
|
|
return ret;
|
2005-12-18 07:02:38 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Fix up the data toggles for URBs in a queue, when one of them
|
|
|
|
* terminates early (short transfer, error, or dequeued).
|
|
|
|
*/
|
|
|
|
static void uhci_fixup_toggles(struct uhci_qh *qh, int skip_first)
|
|
|
|
{
|
|
|
|
struct urb_priv *urbp = NULL;
|
|
|
|
struct uhci_td *td;
|
|
|
|
unsigned int toggle = qh->initial_toggle;
|
|
|
|
unsigned int pipe;
|
|
|
|
|
|
|
|
/* Fixups for a short transfer start with the second URB in the
|
|
|
|
* queue (the short URB is the first). */
|
|
|
|
if (skip_first)
|
|
|
|
urbp = list_entry(qh->queue.next, struct urb_priv, node);
|
|
|
|
|
|
|
|
/* When starting with the first URB, if the QH element pointer is
|
|
|
|
* still valid then we know the URB's toggles are okay. */
|
|
|
|
else if (qh_element(qh) != UHCI_PTR_TERM)
|
|
|
|
toggle = 2;
|
|
|
|
|
|
|
|
/* Fix up the toggle for the URBs in the queue. Normally this
|
|
|
|
* loop won't run more than once: When an error or short transfer
|
|
|
|
* occurs, the queue usually gets emptied. */
|
2006-01-31 23:02:55 +08:00
|
|
|
urbp = list_prepare_entry(urbp, &qh->queue, node);
|
2005-12-18 07:02:38 +08:00
|
|
|
list_for_each_entry_continue(urbp, &qh->queue, node) {
|
|
|
|
|
|
|
|
/* If the first TD has the right toggle value, we don't
|
|
|
|
* need to change any toggles in this URB */
|
|
|
|
td = list_entry(urbp->td_list.next, struct uhci_td, list);
|
|
|
|
if (toggle > 1 || uhci_toggle(td_token(td)) == toggle) {
|
2006-09-01 02:18:39 +08:00
|
|
|
td = list_entry(urbp->td_list.prev, struct uhci_td,
|
2005-12-18 07:02:38 +08:00
|
|
|
list);
|
|
|
|
toggle = uhci_toggle(td_token(td)) ^ 1;
|
|
|
|
|
|
|
|
/* Otherwise all the toggles in the URB have to be switched */
|
|
|
|
} else {
|
|
|
|
list_for_each_entry(td, &urbp->td_list, list) {
|
|
|
|
td->token ^= __constant_cpu_to_le32(
|
|
|
|
TD_TOKEN_TOGGLE);
|
|
|
|
toggle ^= 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
wmb();
|
|
|
|
pipe = list_entry(qh->queue.next, struct urb_priv, node)->urb->pipe;
|
|
|
|
usb_settoggle(qh->udev, usb_pipeendpoint(pipe),
|
|
|
|
usb_pipeout(pipe), toggle);
|
|
|
|
qh->needs_fixup = 0;
|
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/*
|
2007-02-20 04:52:45 +08:00
|
|
|
* Link an Isochronous QH into its skeleton's list
|
2005-04-17 06:20:36 +08:00
|
|
|
*/
|
2007-02-20 04:52:45 +08:00
|
|
|
static inline void link_iso(struct uhci_hcd *uhci, struct uhci_qh *qh)
|
|
|
|
{
|
|
|
|
list_add_tail(&qh->node, &uhci->skel_iso_qh->node);
|
|
|
|
|
|
|
|
/* Isochronous QHs aren't linked by the hardware */
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Link a high-period interrupt QH into the schedule at the end of its
|
|
|
|
* skeleton's list
|
|
|
|
*/
|
|
|
|
static void link_interrupt(struct uhci_hcd *uhci, struct uhci_qh *qh)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-12-18 06:58:46 +08:00
|
|
|
struct uhci_qh *pqh;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-02-20 04:52:45 +08:00
|
|
|
list_add_tail(&qh->node, &uhci->skelqh[qh->skel]->node);
|
|
|
|
|
|
|
|
pqh = list_entry(qh->node.prev, struct uhci_qh, node);
|
|
|
|
qh->link = pqh->link;
|
|
|
|
wmb();
|
|
|
|
pqh->link = LINK_TO_QH(qh);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Link a period-1 interrupt or async QH into the schedule at the
|
|
|
|
* correct spot in the async skeleton's list, and update the FSBR link
|
|
|
|
*/
|
|
|
|
static void link_async(struct uhci_hcd *uhci, struct uhci_qh *qh)
|
|
|
|
{
|
2007-03-20 03:31:42 +08:00
|
|
|
struct uhci_qh *pqh;
|
2007-02-20 04:52:45 +08:00
|
|
|
__le32 link_to_new_qh;
|
|
|
|
|
|
|
|
/* Find the predecessor QH for our new one and insert it in the list.
|
|
|
|
* The list of QHs is expected to be short, so linear search won't
|
|
|
|
* take too long. */
|
|
|
|
list_for_each_entry_reverse(pqh, &uhci->skel_async_qh->node, node) {
|
|
|
|
if (pqh->skel <= qh->skel)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
list_add(&qh->node, &pqh->node);
|
|
|
|
|
|
|
|
/* Link it into the schedule */
|
2007-03-20 03:31:42 +08:00
|
|
|
qh->link = pqh->link;
|
2007-02-20 04:52:45 +08:00
|
|
|
wmb();
|
2007-03-20 03:31:42 +08:00
|
|
|
link_to_new_qh = LINK_TO_QH(qh);
|
|
|
|
pqh->link = link_to_new_qh;
|
|
|
|
|
|
|
|
/* If this is now the first FSBR QH, link the terminating skeleton
|
|
|
|
* QH to it. */
|
|
|
|
if (pqh->skel < SKEL_FSBR && qh->skel >= SKEL_FSBR)
|
|
|
|
uhci->skel_term_qh->link = link_to_new_qh;
|
2007-02-20 04:52:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Put a QH on the schedule in both hardware and software
|
|
|
|
*/
|
|
|
|
static void uhci_activate_qh(struct uhci_hcd *uhci, struct uhci_qh *qh)
|
|
|
|
{
|
2005-12-18 06:58:46 +08:00
|
|
|
WARN_ON(list_empty(&qh->queue));
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-12-18 06:58:46 +08:00
|
|
|
/* Set the element pointer if it isn't set already.
|
|
|
|
* This isn't needed for Isochronous queues, but it doesn't hurt. */
|
|
|
|
if (qh_element(qh) == UHCI_PTR_TERM) {
|
|
|
|
struct urb_priv *urbp = list_entry(qh->queue.next,
|
|
|
|
struct urb_priv, node);
|
|
|
|
struct uhci_td *td = list_entry(urbp->td_list.next,
|
|
|
|
struct uhci_td, list);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-02-20 04:51:51 +08:00
|
|
|
qh->element = LINK_TO_TD(td);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2006-05-12 23:35:45 +08:00
|
|
|
/* Treat the queue as if it has just advanced */
|
|
|
|
qh->wait_expired = 0;
|
|
|
|
qh->advance_jiffies = jiffies;
|
|
|
|
|
2005-12-18 06:58:46 +08:00
|
|
|
if (qh->state == QH_STATE_ACTIVE)
|
|
|
|
return;
|
|
|
|
qh->state = QH_STATE_ACTIVE;
|
|
|
|
|
2007-02-20 04:52:45 +08:00
|
|
|
/* Move the QH from its old list to the correct spot in the appropriate
|
2005-12-18 06:58:46 +08:00
|
|
|
* skeleton's list */
|
2005-12-18 07:02:38 +08:00
|
|
|
if (qh == uhci->next_qh)
|
|
|
|
uhci->next_qh = list_entry(qh->node.next, struct uhci_qh,
|
|
|
|
node);
|
2007-02-20 04:52:45 +08:00
|
|
|
list_del(&qh->node);
|
|
|
|
|
|
|
|
if (qh->skel == SKEL_ISO)
|
|
|
|
link_iso(uhci, qh);
|
|
|
|
else if (qh->skel < SKEL_ASYNC)
|
|
|
|
link_interrupt(uhci, qh);
|
|
|
|
else
|
|
|
|
link_async(uhci, qh);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Unlink a high-period interrupt QH from the schedule
|
|
|
|
*/
|
|
|
|
static void unlink_interrupt(struct uhci_hcd *uhci, struct uhci_qh *qh)
|
|
|
|
{
|
|
|
|
struct uhci_qh *pqh;
|
2005-12-18 06:58:46 +08:00
|
|
|
|
|
|
|
pqh = list_entry(qh->node.prev, struct uhci_qh, node);
|
2007-02-20 04:52:45 +08:00
|
|
|
pqh->link = qh->link;
|
|
|
|
mb();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Unlink a period-1 interrupt or async QH from the schedule
|
|
|
|
*/
|
|
|
|
static void unlink_async(struct uhci_hcd *uhci, struct uhci_qh *qh)
|
|
|
|
{
|
2007-03-20 03:31:42 +08:00
|
|
|
struct uhci_qh *pqh;
|
2007-02-20 04:52:45 +08:00
|
|
|
__le32 link_to_next_qh = qh->link;
|
|
|
|
|
|
|
|
pqh = list_entry(qh->node.prev, struct uhci_qh, node);
|
|
|
|
pqh->link = link_to_next_qh;
|
2007-03-20 03:31:42 +08:00
|
|
|
|
|
|
|
/* If this was the old first FSBR QH, link the terminating skeleton
|
|
|
|
* QH to the next (new first FSBR) QH. */
|
|
|
|
if (pqh->skel < SKEL_FSBR && qh->skel >= SKEL_FSBR)
|
|
|
|
uhci->skel_term_qh->link = link_to_next_qh;
|
2007-02-20 04:52:45 +08:00
|
|
|
mb();
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2005-12-18 06:58:46 +08:00
|
|
|
* Take a QH off the hardware schedule
|
2005-04-17 06:20:36 +08:00
|
|
|
*/
|
2005-12-18 06:58:46 +08:00
|
|
|
static void uhci_unlink_qh(struct uhci_hcd *uhci, struct uhci_qh *qh)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-12-18 06:58:46 +08:00
|
|
|
if (qh->state == QH_STATE_UNLINKING)
|
2005-04-17 06:20:36 +08:00
|
|
|
return;
|
2005-12-18 06:58:46 +08:00
|
|
|
WARN_ON(qh->state != QH_STATE_ACTIVE || !qh->udev);
|
|
|
|
qh->state = QH_STATE_UNLINKING;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-12-18 06:58:46 +08:00
|
|
|
/* Unlink the QH from the schedule and record when we did it */
|
2007-02-20 04:52:45 +08:00
|
|
|
if (qh->skel == SKEL_ISO)
|
|
|
|
;
|
|
|
|
else if (qh->skel < SKEL_ASYNC)
|
|
|
|
unlink_interrupt(uhci, qh);
|
|
|
|
else
|
|
|
|
unlink_async(uhci, qh);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
uhci_get_current_frame_number(uhci);
|
2005-12-18 06:58:46 +08:00
|
|
|
qh->unlink_frame = uhci->frame_number;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-12-18 06:58:46 +08:00
|
|
|
/* Force an interrupt so we know when the QH is fully unlinked */
|
|
|
|
if (list_empty(&uhci->skel_unlink_qh->node))
|
2005-04-17 06:20:36 +08:00
|
|
|
uhci_set_next_interrupt(uhci);
|
|
|
|
|
2005-12-18 06:58:46 +08:00
|
|
|
/* Move the QH from its old list to the end of the unlinking list */
|
2005-12-18 07:02:38 +08:00
|
|
|
if (qh == uhci->next_qh)
|
|
|
|
uhci->next_qh = list_entry(qh->node.next, struct uhci_qh,
|
|
|
|
node);
|
2005-12-18 06:58:46 +08:00
|
|
|
list_move_tail(&qh->node, &uhci->skel_unlink_qh->node);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2005-12-18 06:58:46 +08:00
|
|
|
/*
|
|
|
|
* When we and the controller are through with a QH, it becomes IDLE.
|
|
|
|
* This happens when a QH has been off the schedule (on the unlinking
|
|
|
|
* list) for more than one frame, or when an error occurs while adding
|
|
|
|
* the first URB onto a new QH.
|
|
|
|
*/
|
|
|
|
static void uhci_make_qh_idle(struct uhci_hcd *uhci, struct uhci_qh *qh)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-12-18 06:58:46 +08:00
|
|
|
WARN_ON(qh->state == QH_STATE_ACTIVE);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-12-18 07:02:38 +08:00
|
|
|
if (qh == uhci->next_qh)
|
|
|
|
uhci->next_qh = list_entry(qh->node.next, struct uhci_qh,
|
|
|
|
node);
|
2005-12-18 06:58:46 +08:00
|
|
|
list_move(&qh->node, &uhci->idle_qh_list);
|
|
|
|
qh->state = QH_STATE_IDLE;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-05-12 23:19:19 +08:00
|
|
|
/* Now that the QH is idle, its post_td isn't being used */
|
|
|
|
if (qh->post_td) {
|
|
|
|
uhci_free_td(uhci, qh->post_td);
|
|
|
|
qh->post_td = NULL;
|
|
|
|
}
|
|
|
|
|
2005-12-18 06:58:46 +08:00
|
|
|
/* If anyone is waiting for a QH to become idle, wake them up */
|
|
|
|
if (uhci->num_waiting)
|
|
|
|
wake_up_all(&uhci->waitqh);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2007-01-17 00:56:32 +08:00
|
|
|
/*
|
|
|
|
* Find the highest existing bandwidth load for a given phase and period.
|
|
|
|
*/
|
|
|
|
static int uhci_highest_load(struct uhci_hcd *uhci, int phase, int period)
|
|
|
|
{
|
|
|
|
int highest_load = uhci->load[phase];
|
|
|
|
|
|
|
|
for (phase += period; phase < MAX_PHASE; phase += period)
|
|
|
|
highest_load = max_t(int, highest_load, uhci->load[phase]);
|
|
|
|
return highest_load;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set qh->phase to the optimal phase for a periodic transfer and
|
|
|
|
* check whether the bandwidth requirement is acceptable.
|
|
|
|
*/
|
|
|
|
static int uhci_check_bandwidth(struct uhci_hcd *uhci, struct uhci_qh *qh)
|
|
|
|
{
|
|
|
|
int minimax_load;
|
|
|
|
|
|
|
|
/* Find the optimal phase (unless it is already set) and get
|
|
|
|
* its load value. */
|
|
|
|
if (qh->phase >= 0)
|
|
|
|
minimax_load = uhci_highest_load(uhci, qh->phase, qh->period);
|
|
|
|
else {
|
|
|
|
int phase, load;
|
|
|
|
int max_phase = min_t(int, MAX_PHASE, qh->period);
|
|
|
|
|
|
|
|
qh->phase = 0;
|
|
|
|
minimax_load = uhci_highest_load(uhci, qh->phase, qh->period);
|
|
|
|
for (phase = 1; phase < max_phase; ++phase) {
|
|
|
|
load = uhci_highest_load(uhci, phase, qh->period);
|
|
|
|
if (load < minimax_load) {
|
|
|
|
minimax_load = load;
|
|
|
|
qh->phase = phase;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Maximum allowable periodic bandwidth is 90%, or 900 us per frame */
|
|
|
|
if (minimax_load + qh->load > 900) {
|
|
|
|
dev_dbg(uhci_dev(uhci), "bandwidth allocation failed: "
|
|
|
|
"period %d, phase %d, %d + %d us\n",
|
|
|
|
qh->period, qh->phase, minimax_load, qh->load);
|
|
|
|
return -ENOSPC;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Reserve a periodic QH's bandwidth in the schedule
|
|
|
|
*/
|
|
|
|
static void uhci_reserve_bandwidth(struct uhci_hcd *uhci, struct uhci_qh *qh)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
int load = qh->load;
|
|
|
|
char *p = "??";
|
|
|
|
|
|
|
|
for (i = qh->phase; i < MAX_PHASE; i += qh->period) {
|
|
|
|
uhci->load[i] += load;
|
|
|
|
uhci->total_load += load;
|
|
|
|
}
|
|
|
|
uhci_to_hcd(uhci)->self.bandwidth_allocated =
|
|
|
|
uhci->total_load / MAX_PHASE;
|
|
|
|
switch (qh->type) {
|
|
|
|
case USB_ENDPOINT_XFER_INT:
|
|
|
|
++uhci_to_hcd(uhci)->self.bandwidth_int_reqs;
|
|
|
|
p = "INT";
|
|
|
|
break;
|
|
|
|
case USB_ENDPOINT_XFER_ISOC:
|
|
|
|
++uhci_to_hcd(uhci)->self.bandwidth_isoc_reqs;
|
|
|
|
p = "ISO";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
qh->bandwidth_reserved = 1;
|
|
|
|
dev_dbg(uhci_dev(uhci),
|
|
|
|
"%s dev %d ep%02x-%s, period %d, phase %d, %d us\n",
|
|
|
|
"reserve", qh->udev->devnum,
|
|
|
|
qh->hep->desc.bEndpointAddress, p,
|
|
|
|
qh->period, qh->phase, load);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Release a periodic QH's bandwidth reservation
|
|
|
|
*/
|
|
|
|
static void uhci_release_bandwidth(struct uhci_hcd *uhci, struct uhci_qh *qh)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
int load = qh->load;
|
|
|
|
char *p = "??";
|
|
|
|
|
|
|
|
for (i = qh->phase; i < MAX_PHASE; i += qh->period) {
|
|
|
|
uhci->load[i] -= load;
|
|
|
|
uhci->total_load -= load;
|
|
|
|
}
|
|
|
|
uhci_to_hcd(uhci)->self.bandwidth_allocated =
|
|
|
|
uhci->total_load / MAX_PHASE;
|
|
|
|
switch (qh->type) {
|
|
|
|
case USB_ENDPOINT_XFER_INT:
|
|
|
|
--uhci_to_hcd(uhci)->self.bandwidth_int_reqs;
|
|
|
|
p = "INT";
|
|
|
|
break;
|
|
|
|
case USB_ENDPOINT_XFER_ISOC:
|
|
|
|
--uhci_to_hcd(uhci)->self.bandwidth_isoc_reqs;
|
|
|
|
p = "ISO";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
qh->bandwidth_reserved = 0;
|
|
|
|
dev_dbg(uhci_dev(uhci),
|
|
|
|
"%s dev %d ep%02x-%s, period %d, phase %d, %d us\n",
|
|
|
|
"release", qh->udev->devnum,
|
|
|
|
qh->hep->desc.bEndpointAddress, p,
|
|
|
|
qh->period, qh->phase, load);
|
|
|
|
}
|
|
|
|
|
2005-12-18 06:58:46 +08:00
|
|
|
static inline struct urb_priv *uhci_alloc_urb_priv(struct uhci_hcd *uhci,
|
|
|
|
struct urb *urb)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
struct urb_priv *urbp;
|
|
|
|
|
2007-02-10 17:45:03 +08:00
|
|
|
urbp = kmem_cache_zalloc(uhci_up_cachep, GFP_ATOMIC);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!urbp)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
urbp->urb = urb;
|
2005-12-18 06:58:46 +08:00
|
|
|
urb->hcpriv = urbp;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-12-18 06:58:46 +08:00
|
|
|
INIT_LIST_HEAD(&urbp->node);
|
2005-04-17 06:20:36 +08:00
|
|
|
INIT_LIST_HEAD(&urbp->td_list);
|
|
|
|
|
|
|
|
return urbp;
|
|
|
|
}
|
|
|
|
|
2005-12-18 06:58:46 +08:00
|
|
|
static void uhci_free_urb_priv(struct uhci_hcd *uhci,
|
|
|
|
struct urb_priv *urbp)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
struct uhci_td *td, *tmp;
|
|
|
|
|
2008-09-21 10:09:37 +08:00
|
|
|
if (!list_empty(&urbp->node))
|
|
|
|
dev_WARN(uhci_dev(uhci), "urb %p still on QH's list!\n",
|
2005-12-18 06:58:46 +08:00
|
|
|
urbp->urb);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
list_for_each_entry_safe(td, tmp, &urbp->td_list, list) {
|
2006-05-12 23:29:04 +08:00
|
|
|
uhci_remove_td_from_urbp(td);
|
|
|
|
uhci_free_td(uhci, td);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
kmem_cache_free(uhci_up_cachep, urbp);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Map status to standard result codes
|
|
|
|
*
|
|
|
|
* <status> is (td_status(td) & 0xF60000), a.k.a.
|
|
|
|
* uhci_status_bits(td_status(td)).
|
|
|
|
* Note: <status> does not include the TD_CTRL_NAK bit.
|
|
|
|
* <dir_out> is True for output TDs and False for input TDs.
|
|
|
|
*/
|
|
|
|
static int uhci_map_status(int status, int dir_out)
|
|
|
|
{
|
|
|
|
if (!status)
|
|
|
|
return 0;
|
|
|
|
if (status & TD_CTRL_BITSTUFF) /* Bitstuff error */
|
|
|
|
return -EPROTO;
|
|
|
|
if (status & TD_CTRL_CRCTIMEO) { /* CRC/Timeout */
|
|
|
|
if (dir_out)
|
|
|
|
return -EPROTO;
|
|
|
|
else
|
|
|
|
return -EILSEQ;
|
|
|
|
}
|
|
|
|
if (status & TD_CTRL_BABBLE) /* Babble */
|
|
|
|
return -EOVERFLOW;
|
|
|
|
if (status & TD_CTRL_DBUFERR) /* Buffer error */
|
|
|
|
return -ENOSR;
|
|
|
|
if (status & TD_CTRL_STALLED) /* Stalled */
|
|
|
|
return -EPIPE;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Control transfers
|
|
|
|
*/
|
2005-12-18 06:58:46 +08:00
|
|
|
static int uhci_submit_control(struct uhci_hcd *uhci, struct urb *urb,
|
|
|
|
struct uhci_qh *qh)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
struct uhci_td *td;
|
|
|
|
unsigned long destination, status;
|
2005-12-18 06:58:46 +08:00
|
|
|
int maxsze = le16_to_cpu(qh->hep->desc.wMaxPacketSize);
|
2005-04-17 06:20:36 +08:00
|
|
|
int len = urb->transfer_buffer_length;
|
|
|
|
dma_addr_t data = urb->transfer_dma;
|
2005-12-18 06:58:46 +08:00
|
|
|
__le32 *plink;
|
2006-05-12 23:29:04 +08:00
|
|
|
struct urb_priv *urbp = urb->hcpriv;
|
2007-02-20 04:52:45 +08:00
|
|
|
int skel;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* The "pipe" thing contains the destination in bits 8--18 */
|
|
|
|
destination = (urb->pipe & PIPE_DEVEP_MASK) | USB_PID_SETUP;
|
|
|
|
|
2005-12-18 07:00:12 +08:00
|
|
|
/* 3 errors, dummy TD remains inactive */
|
|
|
|
status = uhci_maxerr(3);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (urb->dev->speed == USB_SPEED_LOW)
|
|
|
|
status |= TD_CTRL_LS;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Build the TD for the control request setup packet
|
|
|
|
*/
|
2005-12-18 07:00:12 +08:00
|
|
|
td = qh->dummy_td;
|
2006-05-12 23:29:04 +08:00
|
|
|
uhci_add_td_to_urbp(td, urbp);
|
2005-12-01 00:57:51 +08:00
|
|
|
uhci_fill_td(td, status, destination | uhci_explen(8),
|
2005-12-18 06:58:46 +08:00
|
|
|
urb->setup_dma);
|
|
|
|
plink = &td->link;
|
2005-12-18 07:00:12 +08:00
|
|
|
status |= TD_CTRL_ACTIVE;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* If direction is "send", change the packet ID from SETUP (0x2D)
|
|
|
|
* to OUT (0xE1). Else change it from SETUP to IN (0x69) and
|
|
|
|
* set Short Packet Detect (SPD) for all data packets.
|
2007-07-14 03:46:29 +08:00
|
|
|
*
|
|
|
|
* 0-length transfers always get treated as "send".
|
2005-04-17 06:20:36 +08:00
|
|
|
*/
|
2007-07-14 03:46:29 +08:00
|
|
|
if (usb_pipeout(urb->pipe) || len == 0)
|
2005-04-17 06:20:36 +08:00
|
|
|
destination ^= (USB_PID_SETUP ^ USB_PID_OUT);
|
|
|
|
else {
|
|
|
|
destination ^= (USB_PID_SETUP ^ USB_PID_IN);
|
|
|
|
status |= TD_CTRL_SPD;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2005-12-01 06:16:19 +08:00
|
|
|
* Build the DATA TDs
|
2005-04-17 06:20:36 +08:00
|
|
|
*/
|
|
|
|
while (len > 0) {
|
2007-07-14 03:46:29 +08:00
|
|
|
int pktsze = maxsze;
|
|
|
|
|
|
|
|
if (len <= pktsze) { /* The last data packet */
|
|
|
|
pktsze = len;
|
|
|
|
status &= ~TD_CTRL_SPD;
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-04-25 23:14:31 +08:00
|
|
|
td = uhci_alloc_td(uhci);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!td)
|
2005-12-18 07:00:12 +08:00
|
|
|
goto nomem;
|
2007-02-20 04:51:51 +08:00
|
|
|
*plink = LINK_TO_TD(td);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* Alternate Data0/1 (start with Data1) */
|
|
|
|
destination ^= TD_TOKEN_TOGGLE;
|
|
|
|
|
2006-05-12 23:29:04 +08:00
|
|
|
uhci_add_td_to_urbp(td, urbp);
|
2005-12-01 00:57:51 +08:00
|
|
|
uhci_fill_td(td, status, destination | uhci_explen(pktsze),
|
2005-12-18 06:58:46 +08:00
|
|
|
data);
|
|
|
|
plink = &td->link;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
data += pktsze;
|
|
|
|
len -= pktsze;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Build the final TD for control status
|
|
|
|
*/
|
2005-04-25 23:14:31 +08:00
|
|
|
td = uhci_alloc_td(uhci);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!td)
|
2005-12-18 07:00:12 +08:00
|
|
|
goto nomem;
|
2007-02-20 04:51:51 +08:00
|
|
|
*plink = LINK_TO_TD(td);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-07-14 03:46:29 +08:00
|
|
|
/* Change direction for the status transaction */
|
|
|
|
destination ^= (USB_PID_IN ^ USB_PID_OUT);
|
2005-04-17 06:20:36 +08:00
|
|
|
destination |= TD_TOKEN_TOGGLE; /* End in Data1 */
|
|
|
|
|
2006-05-12 23:29:04 +08:00
|
|
|
uhci_add_td_to_urbp(td, urbp);
|
2005-04-17 06:20:36 +08:00
|
|
|
uhci_fill_td(td, status | TD_CTRL_IOC,
|
2005-12-18 06:58:46 +08:00
|
|
|
destination | uhci_explen(0), 0);
|
2005-12-18 07:00:12 +08:00
|
|
|
plink = &td->link;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Build the new dummy TD and activate the old one
|
|
|
|
*/
|
|
|
|
td = uhci_alloc_td(uhci);
|
|
|
|
if (!td)
|
|
|
|
goto nomem;
|
2007-02-20 04:51:51 +08:00
|
|
|
*plink = LINK_TO_TD(td);
|
2005-12-18 07:00:12 +08:00
|
|
|
|
|
|
|
uhci_fill_td(td, 0, USB_PID_OUT | uhci_explen(0), 0);
|
|
|
|
wmb();
|
|
|
|
qh->dummy_td->status |= __constant_cpu_to_le32(TD_CTRL_ACTIVE);
|
|
|
|
qh->dummy_td = td;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* Low-speed transfers get a different queue, and won't hog the bus.
|
|
|
|
* Also, some devices enumerate better without FSBR; the easiest way
|
|
|
|
* to do that is to put URBs on the low-speed queue while the device
|
2006-01-24 06:17:21 +08:00
|
|
|
* isn't in the CONFIGURED state. */
|
2005-04-17 06:20:36 +08:00
|
|
|
if (urb->dev->speed == USB_SPEED_LOW ||
|
2006-01-24 06:17:21 +08:00
|
|
|
urb->dev->state != USB_STATE_CONFIGURED)
|
2007-02-20 04:52:45 +08:00
|
|
|
skel = SKEL_LS_CONTROL;
|
2005-04-17 06:20:36 +08:00
|
|
|
else {
|
2007-02-20 04:52:45 +08:00
|
|
|
skel = SKEL_FS_CONTROL;
|
2006-05-12 23:35:45 +08:00
|
|
|
uhci_add_fsbr(uhci, urb);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2007-02-20 04:52:45 +08:00
|
|
|
if (qh->state != QH_STATE_ACTIVE)
|
|
|
|
qh->skel = skel;
|
2006-05-12 23:19:19 +08:00
|
|
|
|
|
|
|
urb->actual_length = -8; /* Account for the SETUP packet */
|
2005-12-18 06:58:46 +08:00
|
|
|
return 0;
|
2005-12-18 07:00:12 +08:00
|
|
|
|
|
|
|
nomem:
|
|
|
|
/* Remove the dummy TD from the td_list so it doesn't get freed */
|
2006-05-12 23:29:04 +08:00
|
|
|
uhci_remove_td_from_urbp(qh->dummy_td);
|
2005-12-18 07:00:12 +08:00
|
|
|
return -ENOMEM;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Common submit for bulk and interrupt
|
|
|
|
*/
|
2005-12-18 06:58:46 +08:00
|
|
|
static int uhci_submit_common(struct uhci_hcd *uhci, struct urb *urb,
|
|
|
|
struct uhci_qh *qh)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
struct uhci_td *td;
|
|
|
|
unsigned long destination, status;
|
2005-12-18 06:58:46 +08:00
|
|
|
int maxsze = le16_to_cpu(qh->hep->desc.wMaxPacketSize);
|
2005-04-17 06:20:36 +08:00
|
|
|
int len = urb->transfer_buffer_length;
|
|
|
|
dma_addr_t data = urb->transfer_dma;
|
2005-12-18 07:00:12 +08:00
|
|
|
__le32 *plink;
|
2006-05-12 23:29:04 +08:00
|
|
|
struct urb_priv *urbp = urb->hcpriv;
|
2005-12-18 07:00:12 +08:00
|
|
|
unsigned int toggle;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
if (len < 0)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
/* The "pipe" thing contains the destination in bits 8--18 */
|
|
|
|
destination = (urb->pipe & PIPE_DEVEP_MASK) | usb_packetid(urb->pipe);
|
2005-12-18 07:00:12 +08:00
|
|
|
toggle = usb_gettoggle(urb->dev, usb_pipeendpoint(urb->pipe),
|
|
|
|
usb_pipeout(urb->pipe));
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-12-18 07:00:12 +08:00
|
|
|
/* 3 errors, dummy TD remains inactive */
|
|
|
|
status = uhci_maxerr(3);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (urb->dev->speed == USB_SPEED_LOW)
|
|
|
|
status |= TD_CTRL_LS;
|
|
|
|
if (usb_pipein(urb->pipe))
|
|
|
|
status |= TD_CTRL_SPD;
|
|
|
|
|
|
|
|
/*
|
2005-12-01 06:16:19 +08:00
|
|
|
* Build the DATA TDs
|
2005-04-17 06:20:36 +08:00
|
|
|
*/
|
2005-12-18 07:00:12 +08:00
|
|
|
plink = NULL;
|
|
|
|
td = qh->dummy_td;
|
2005-04-17 06:20:36 +08:00
|
|
|
do { /* Allow zero length packets */
|
|
|
|
int pktsze = maxsze;
|
|
|
|
|
2005-12-18 06:58:46 +08:00
|
|
|
if (len <= pktsze) { /* The last packet */
|
2005-04-17 06:20:36 +08:00
|
|
|
pktsze = len;
|
|
|
|
if (!(urb->transfer_flags & URB_SHORT_NOT_OK))
|
|
|
|
status &= ~TD_CTRL_SPD;
|
|
|
|
}
|
|
|
|
|
2005-12-18 07:00:12 +08:00
|
|
|
if (plink) {
|
|
|
|
td = uhci_alloc_td(uhci);
|
|
|
|
if (!td)
|
|
|
|
goto nomem;
|
2007-02-20 04:51:51 +08:00
|
|
|
*plink = LINK_TO_TD(td);
|
2005-12-18 07:00:12 +08:00
|
|
|
}
|
2006-05-12 23:29:04 +08:00
|
|
|
uhci_add_td_to_urbp(td, urbp);
|
2005-12-18 06:58:46 +08:00
|
|
|
uhci_fill_td(td, status,
|
2005-12-18 07:00:12 +08:00
|
|
|
destination | uhci_explen(pktsze) |
|
|
|
|
(toggle << TD_TOKEN_TOGGLE_SHIFT),
|
|
|
|
data);
|
2005-12-18 06:58:46 +08:00
|
|
|
plink = &td->link;
|
2005-12-18 07:00:12 +08:00
|
|
|
status |= TD_CTRL_ACTIVE;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
data += pktsze;
|
|
|
|
len -= maxsze;
|
2005-12-18 07:00:12 +08:00
|
|
|
toggle ^= 1;
|
2005-04-17 06:20:36 +08:00
|
|
|
} while (len > 0);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* URB_ZERO_PACKET means adding a 0-length packet, if direction
|
|
|
|
* is OUT and the transfer_length was an exact multiple of maxsze,
|
|
|
|
* hence (len = transfer_length - N * maxsze) == 0
|
|
|
|
* however, if transfer_length == 0, the zero packet was already
|
|
|
|
* prepared above.
|
|
|
|
*/
|
2005-12-18 06:58:46 +08:00
|
|
|
if ((urb->transfer_flags & URB_ZERO_PACKET) &&
|
|
|
|
usb_pipeout(urb->pipe) && len == 0 &&
|
|
|
|
urb->transfer_buffer_length > 0) {
|
2005-04-25 23:14:31 +08:00
|
|
|
td = uhci_alloc_td(uhci);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!td)
|
2005-12-18 07:00:12 +08:00
|
|
|
goto nomem;
|
2007-02-20 04:51:51 +08:00
|
|
|
*plink = LINK_TO_TD(td);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-05-12 23:29:04 +08:00
|
|
|
uhci_add_td_to_urbp(td, urbp);
|
2005-12-18 07:00:12 +08:00
|
|
|
uhci_fill_td(td, status,
|
|
|
|
destination | uhci_explen(0) |
|
|
|
|
(toggle << TD_TOKEN_TOGGLE_SHIFT),
|
|
|
|
data);
|
|
|
|
plink = &td->link;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-12-18 07:00:12 +08:00
|
|
|
toggle ^= 1;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Set the interrupt-on-completion flag on the last packet.
|
|
|
|
* A more-or-less typical 4 KB URB (= size of one memory page)
|
|
|
|
* will require about 3 ms to transfer; that's a little on the
|
|
|
|
* fast side but not enough to justify delaying an interrupt
|
|
|
|
* more than 2 or 3 URBs, so we will ignore the URB_NO_INTERRUPT
|
|
|
|
* flag setting. */
|
2005-12-18 06:58:46 +08:00
|
|
|
td->status |= __constant_cpu_to_le32(TD_CTRL_IOC);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-12-18 07:00:12 +08:00
|
|
|
/*
|
|
|
|
* Build the new dummy TD and activate the old one
|
|
|
|
*/
|
|
|
|
td = uhci_alloc_td(uhci);
|
|
|
|
if (!td)
|
|
|
|
goto nomem;
|
2007-02-20 04:51:51 +08:00
|
|
|
*plink = LINK_TO_TD(td);
|
2005-12-18 07:00:12 +08:00
|
|
|
|
|
|
|
uhci_fill_td(td, 0, USB_PID_OUT | uhci_explen(0), 0);
|
|
|
|
wmb();
|
|
|
|
qh->dummy_td->status |= __constant_cpu_to_le32(TD_CTRL_ACTIVE);
|
|
|
|
qh->dummy_td = td;
|
|
|
|
|
|
|
|
usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe),
|
|
|
|
usb_pipeout(urb->pipe), toggle);
|
2005-12-18 06:58:46 +08:00
|
|
|
return 0;
|
2005-12-18 07:00:12 +08:00
|
|
|
|
|
|
|
nomem:
|
|
|
|
/* Remove the dummy TD from the td_list so it doesn't get freed */
|
2006-05-12 23:29:04 +08:00
|
|
|
uhci_remove_td_from_urbp(qh->dummy_td);
|
2005-12-18 07:00:12 +08:00
|
|
|
return -ENOMEM;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2007-02-20 04:52:45 +08:00
|
|
|
static int uhci_submit_bulk(struct uhci_hcd *uhci, struct urb *urb,
|
2005-12-18 06:58:46 +08:00
|
|
|
struct uhci_qh *qh)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
/* Can't have low-speed bulk transfers */
|
|
|
|
if (urb->dev->speed == USB_SPEED_LOW)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2007-02-20 04:52:45 +08:00
|
|
|
if (qh->state != QH_STATE_ACTIVE)
|
|
|
|
qh->skel = SKEL_BULK;
|
2005-12-18 06:58:46 +08:00
|
|
|
ret = uhci_submit_common(uhci, urb, qh);
|
|
|
|
if (ret == 0)
|
2006-05-12 23:35:45 +08:00
|
|
|
uhci_add_fsbr(uhci, urb);
|
2005-04-17 06:20:36 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2006-05-20 04:44:55 +08:00
|
|
|
static int uhci_submit_interrupt(struct uhci_hcd *uhci, struct urb *urb,
|
2005-12-18 06:58:46 +08:00
|
|
|
struct uhci_qh *qh)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2007-01-17 00:56:32 +08:00
|
|
|
int ret;
|
2006-05-20 04:44:55 +08:00
|
|
|
|
2005-12-18 06:58:46 +08:00
|
|
|
/* USB 1.1 interrupt transfers only involve one packet per interval.
|
|
|
|
* Drivers can submit URBs of any length, but longer ones will need
|
|
|
|
* multiple intervals to complete.
|
2005-04-17 06:20:36 +08:00
|
|
|
*/
|
2006-05-20 04:44:55 +08:00
|
|
|
|
2007-01-17 00:56:32 +08:00
|
|
|
if (!qh->bandwidth_reserved) {
|
|
|
|
int exponent;
|
2006-05-20 04:44:55 +08:00
|
|
|
|
2007-01-17 00:56:32 +08:00
|
|
|
/* Figure out which power-of-two queue to use */
|
|
|
|
for (exponent = 7; exponent >= 0; --exponent) {
|
|
|
|
if ((1 << exponent) <= urb->interval)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (exponent < 0)
|
|
|
|
return -EINVAL;
|
2006-05-20 04:44:55 +08:00
|
|
|
|
2008-09-26 04:59:57 +08:00
|
|
|
/* If the slot is full, try a lower period */
|
|
|
|
do {
|
|
|
|
qh->period = 1 << exponent;
|
|
|
|
qh->skel = SKEL_INDEX(exponent);
|
|
|
|
|
|
|
|
/* For now, interrupt phase is fixed by the layout
|
|
|
|
* of the QH lists.
|
|
|
|
*/
|
|
|
|
qh->phase = (qh->period / 2) & (MAX_PHASE - 1);
|
|
|
|
ret = uhci_check_bandwidth(uhci, qh);
|
|
|
|
} while (ret != 0 && --exponent >= 0);
|
2007-01-17 00:56:32 +08:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
} else if (qh->period > urb->interval)
|
|
|
|
return -EINVAL; /* Can't decrease the period */
|
|
|
|
|
|
|
|
ret = uhci_submit_common(uhci, urb, qh);
|
|
|
|
if (ret == 0) {
|
|
|
|
urb->interval = qh->period;
|
|
|
|
if (!qh->bandwidth_reserved)
|
|
|
|
uhci_reserve_bandwidth(uhci, qh);
|
|
|
|
}
|
|
|
|
return ret;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2006-05-12 23:14:25 +08:00
|
|
|
/*
|
|
|
|
* Fix up the data structures following a short transfer
|
|
|
|
*/
|
|
|
|
static int uhci_fixup_short_transfer(struct uhci_hcd *uhci,
|
2006-05-12 23:19:19 +08:00
|
|
|
struct uhci_qh *qh, struct urb_priv *urbp)
|
2006-05-12 23:14:25 +08:00
|
|
|
{
|
|
|
|
struct uhci_td *td;
|
2006-05-12 23:19:19 +08:00
|
|
|
struct list_head *tmp;
|
|
|
|
int ret;
|
2006-05-12 23:14:25 +08:00
|
|
|
|
|
|
|
td = list_entry(urbp->td_list.prev, struct uhci_td, list);
|
|
|
|
if (qh->type == USB_ENDPOINT_XFER_CONTROL) {
|
|
|
|
|
|
|
|
/* When a control transfer is short, we have to restart
|
|
|
|
* the queue at the status stage transaction, which is
|
|
|
|
* the last TD. */
|
2006-05-12 23:19:19 +08:00
|
|
|
WARN_ON(list_empty(&urbp->td_list));
|
2007-02-20 04:51:51 +08:00
|
|
|
qh->element = LINK_TO_TD(td);
|
2006-05-12 23:19:19 +08:00
|
|
|
tmp = td->list.prev;
|
2006-05-12 23:14:25 +08:00
|
|
|
ret = -EINPROGRESS;
|
|
|
|
|
2006-05-12 23:19:19 +08:00
|
|
|
} else {
|
2006-05-12 23:14:25 +08:00
|
|
|
|
|
|
|
/* When a bulk/interrupt transfer is short, we have to
|
|
|
|
* fix up the toggles of the following URBs on the queue
|
|
|
|
* before restarting the queue at the next URB. */
|
2006-05-12 23:19:19 +08:00
|
|
|
qh->initial_toggle = uhci_toggle(td_token(qh->post_td)) ^ 1;
|
2006-05-12 23:14:25 +08:00
|
|
|
uhci_fixup_toggles(qh, 1);
|
|
|
|
|
2006-05-12 23:19:19 +08:00
|
|
|
if (list_empty(&urbp->td_list))
|
|
|
|
td = qh->post_td;
|
2006-05-12 23:14:25 +08:00
|
|
|
qh->element = td->link;
|
2006-05-12 23:19:19 +08:00
|
|
|
tmp = urbp->td_list.prev;
|
|
|
|
ret = 0;
|
2006-05-12 23:14:25 +08:00
|
|
|
}
|
|
|
|
|
2006-05-12 23:19:19 +08:00
|
|
|
/* Remove all the TDs we skipped over, from tmp back to the start */
|
|
|
|
while (tmp != &urbp->td_list) {
|
|
|
|
td = list_entry(tmp, struct uhci_td, list);
|
|
|
|
tmp = tmp->prev;
|
|
|
|
|
2006-05-12 23:29:04 +08:00
|
|
|
uhci_remove_td_from_urbp(td);
|
|
|
|
uhci_free_td(uhci, td);
|
2006-05-12 23:19:19 +08:00
|
|
|
}
|
2006-05-12 23:14:25 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Common result for control, bulk, and interrupt
|
|
|
|
*/
|
|
|
|
static int uhci_result_common(struct uhci_hcd *uhci, struct urb *urb)
|
|
|
|
{
|
|
|
|
struct urb_priv *urbp = urb->hcpriv;
|
|
|
|
struct uhci_qh *qh = urbp->qh;
|
2006-05-12 23:19:19 +08:00
|
|
|
struct uhci_td *td, *tmp;
|
2006-05-12 23:14:25 +08:00
|
|
|
unsigned status;
|
|
|
|
int ret = 0;
|
|
|
|
|
2006-05-12 23:19:19 +08:00
|
|
|
list_for_each_entry_safe(td, tmp, &urbp->td_list, list) {
|
2006-05-12 23:14:25 +08:00
|
|
|
unsigned int ctrlstat;
|
|
|
|
int len;
|
|
|
|
|
|
|
|
ctrlstat = td_status(td);
|
|
|
|
status = uhci_status_bits(ctrlstat);
|
|
|
|
if (status & TD_CTRL_ACTIVE)
|
|
|
|
return -EINPROGRESS;
|
|
|
|
|
|
|
|
len = uhci_actual_length(ctrlstat);
|
|
|
|
urb->actual_length += len;
|
|
|
|
|
|
|
|
if (status) {
|
|
|
|
ret = uhci_map_status(status,
|
|
|
|
uhci_packetout(td_token(td)));
|
|
|
|
if ((debug == 1 && ret != -EPIPE) || debug > 1) {
|
|
|
|
/* Some debugging code */
|
2006-06-06 00:16:39 +08:00
|
|
|
dev_dbg(&urb->dev->dev,
|
2006-05-12 23:14:25 +08:00
|
|
|
"%s: failed with status %x\n",
|
2008-03-04 08:08:34 +08:00
|
|
|
__func__, status);
|
2006-05-12 23:14:25 +08:00
|
|
|
|
|
|
|
if (debug > 1 && errbuf) {
|
|
|
|
/* Print the chain for debugging */
|
2007-03-20 03:31:42 +08:00
|
|
|
uhci_show_qh(uhci, urbp->qh, errbuf,
|
2006-05-12 23:14:25 +08:00
|
|
|
ERRBUF_LEN, 0);
|
|
|
|
lprintk(errbuf);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-07-14 03:46:29 +08:00
|
|
|
/* Did we receive a short packet? */
|
2006-05-12 23:14:25 +08:00
|
|
|
} else if (len < uhci_expected_length(td_token(td))) {
|
|
|
|
|
2007-07-14 03:46:29 +08:00
|
|
|
/* For control transfers, go to the status TD if
|
|
|
|
* this isn't already the last data TD */
|
|
|
|
if (qh->type == USB_ENDPOINT_XFER_CONTROL) {
|
|
|
|
if (td->list.next != urbp->td_list.prev)
|
|
|
|
ret = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* For bulk and interrupt, this may be an error */
|
|
|
|
else if (urb->transfer_flags & URB_SHORT_NOT_OK)
|
2006-05-12 23:14:25 +08:00
|
|
|
ret = -EREMOTEIO;
|
2006-07-31 22:16:24 +08:00
|
|
|
|
|
|
|
/* Fixup needed only if this isn't the URB's last TD */
|
|
|
|
else if (&td->list != urbp->td_list.prev)
|
2006-05-12 23:14:25 +08:00
|
|
|
ret = 1;
|
|
|
|
}
|
|
|
|
|
2006-05-12 23:29:04 +08:00
|
|
|
uhci_remove_td_from_urbp(td);
|
2006-05-12 23:19:19 +08:00
|
|
|
if (qh->post_td)
|
2006-05-12 23:29:04 +08:00
|
|
|
uhci_free_td(uhci, qh->post_td);
|
2006-05-12 23:19:19 +08:00
|
|
|
qh->post_td = td;
|
|
|
|
|
2006-05-12 23:14:25 +08:00
|
|
|
if (ret != 0)
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
err:
|
|
|
|
if (ret < 0) {
|
|
|
|
/* Note that the queue has stopped and save
|
|
|
|
* the next toggle value */
|
|
|
|
qh->element = UHCI_PTR_TERM;
|
|
|
|
qh->is_stopped = 1;
|
|
|
|
qh->needs_fixup = (qh->type != USB_ENDPOINT_XFER_CONTROL);
|
|
|
|
qh->initial_toggle = uhci_toggle(td_token(td)) ^
|
|
|
|
(ret == -EREMOTEIO);
|
|
|
|
|
|
|
|
} else /* Short packet received */
|
2006-05-12 23:19:19 +08:00
|
|
|
ret = uhci_fixup_short_transfer(uhci, qh, urbp);
|
2006-05-12 23:14:25 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/*
|
|
|
|
* Isochronous transfers
|
|
|
|
*/
|
2005-12-18 07:02:38 +08:00
|
|
|
static int uhci_submit_isochronous(struct uhci_hcd *uhci, struct urb *urb,
|
|
|
|
struct uhci_qh *qh)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-12-18 07:02:38 +08:00
|
|
|
struct uhci_td *td = NULL; /* Since urb->number_of_packets > 0 */
|
|
|
|
int i, frame;
|
|
|
|
unsigned long destination, status;
|
|
|
|
struct urb_priv *urbp = (struct urb_priv *) urb->hcpriv;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-05-20 04:44:55 +08:00
|
|
|
/* Values must not be too big (could overflow below) */
|
|
|
|
if (urb->interval >= UHCI_NUMFRAMES ||
|
|
|
|
urb->number_of_packets >= UHCI_NUMFRAMES)
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EFBIG;
|
|
|
|
|
2006-05-20 04:44:55 +08:00
|
|
|
/* Check the period and figure out the starting frame number */
|
2007-01-17 00:56:32 +08:00
|
|
|
if (!qh->bandwidth_reserved) {
|
|
|
|
qh->period = urb->interval;
|
2006-05-20 04:44:55 +08:00
|
|
|
if (urb->transfer_flags & URB_ISO_ASAP) {
|
2007-01-17 00:56:32 +08:00
|
|
|
qh->phase = -1; /* Find the best phase */
|
|
|
|
i = uhci_check_bandwidth(uhci, qh);
|
|
|
|
if (i)
|
|
|
|
return i;
|
|
|
|
|
|
|
|
/* Allow a little time to allocate the TDs */
|
2006-05-20 04:52:35 +08:00
|
|
|
uhci_get_current_frame_number(uhci);
|
2007-01-17 00:56:32 +08:00
|
|
|
frame = uhci->frame_number + 10;
|
|
|
|
|
|
|
|
/* Move forward to the first frame having the
|
|
|
|
* correct phase */
|
|
|
|
urb->start_frame = frame + ((qh->phase - frame) &
|
|
|
|
(qh->period - 1));
|
2006-05-20 04:44:55 +08:00
|
|
|
} else {
|
2006-05-20 04:52:35 +08:00
|
|
|
i = urb->start_frame - uhci->last_iso_frame;
|
2006-05-20 04:44:55 +08:00
|
|
|
if (i <= 0 || i >= UHCI_NUMFRAMES)
|
|
|
|
return -EINVAL;
|
2007-01-17 00:56:32 +08:00
|
|
|
qh->phase = urb->start_frame & (qh->period - 1);
|
|
|
|
i = uhci_check_bandwidth(uhci, qh);
|
|
|
|
if (i)
|
|
|
|
return i;
|
2006-05-20 04:44:55 +08:00
|
|
|
}
|
2007-01-17 00:56:32 +08:00
|
|
|
|
2006-05-20 04:44:55 +08:00
|
|
|
} else if (qh->period != urb->interval) {
|
|
|
|
return -EINVAL; /* Can't change the period */
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-10-16 23:55:30 +08:00
|
|
|
} else {
|
|
|
|
/* Find the next unused frame */
|
2005-12-18 07:02:38 +08:00
|
|
|
if (list_empty(&qh->queue)) {
|
2006-05-20 04:52:35 +08:00
|
|
|
frame = qh->iso_frame;
|
2006-05-20 04:44:55 +08:00
|
|
|
} else {
|
|
|
|
struct urb *lurb;
|
2005-12-18 07:02:38 +08:00
|
|
|
|
2006-05-20 04:44:55 +08:00
|
|
|
lurb = list_entry(qh->queue.prev,
|
2005-12-18 07:02:38 +08:00
|
|
|
struct urb_priv, node)->urb;
|
2006-05-20 04:44:55 +08:00
|
|
|
frame = lurb->start_frame +
|
|
|
|
lurb->number_of_packets *
|
|
|
|
lurb->interval;
|
2005-12-18 07:02:38 +08:00
|
|
|
}
|
2007-10-16 23:55:30 +08:00
|
|
|
if (urb->transfer_flags & URB_ISO_ASAP) {
|
|
|
|
/* Skip some frames if necessary to insure
|
|
|
|
* the start frame is in the future.
|
|
|
|
*/
|
|
|
|
uhci_get_current_frame_number(uhci);
|
|
|
|
if (uhci_frame_before_eq(frame, uhci->frame_number)) {
|
|
|
|
frame = uhci->frame_number + 1;
|
|
|
|
frame += ((qh->phase - frame) &
|
|
|
|
(qh->period - 1));
|
|
|
|
}
|
|
|
|
} /* Otherwise pick up where the last URB leaves off */
|
|
|
|
urb->start_frame = frame;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2006-05-20 04:44:55 +08:00
|
|
|
/* Make sure we won't have to go too far into the future */
|
2006-05-20 04:52:35 +08:00
|
|
|
if (uhci_frame_before_eq(uhci->last_iso_frame + UHCI_NUMFRAMES,
|
2006-05-20 04:44:55 +08:00
|
|
|
urb->start_frame + urb->number_of_packets *
|
|
|
|
urb->interval))
|
|
|
|
return -EFBIG;
|
|
|
|
|
|
|
|
status = TD_CTRL_ACTIVE | TD_CTRL_IOS;
|
|
|
|
destination = (urb->pipe & PIPE_DEVEP_MASK) | usb_packetid(urb->pipe);
|
|
|
|
|
2005-10-14 05:00:24 +08:00
|
|
|
for (i = 0; i < urb->number_of_packets; i++) {
|
2005-04-25 23:14:31 +08:00
|
|
|
td = uhci_alloc_td(uhci);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!td)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2006-05-12 23:29:04 +08:00
|
|
|
uhci_add_td_to_urbp(td, urbp);
|
2005-12-18 06:58:46 +08:00
|
|
|
uhci_fill_td(td, status, destination |
|
|
|
|
uhci_explen(urb->iso_frame_desc[i].length),
|
|
|
|
urb->transfer_dma +
|
|
|
|
urb->iso_frame_desc[i].offset);
|
2005-10-14 05:00:24 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-12-18 06:58:46 +08:00
|
|
|
/* Set the interrupt-on-completion flag on the last packet. */
|
|
|
|
td->status |= __constant_cpu_to_le32(TD_CTRL_IOC);
|
|
|
|
|
|
|
|
/* Add the TDs to the frame list */
|
2005-10-14 05:00:24 +08:00
|
|
|
frame = urb->start_frame;
|
|
|
|
list_for_each_entry(td, &urbp->td_list, list) {
|
2005-12-18 06:58:46 +08:00
|
|
|
uhci_insert_td_in_frame_list(uhci, td, frame);
|
2006-05-20 04:52:35 +08:00
|
|
|
frame += qh->period;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (list_empty(&qh->queue)) {
|
|
|
|
qh->iso_packet_desc = &urb->iso_frame_desc[0];
|
|
|
|
qh->iso_frame = urb->start_frame;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2007-02-20 04:52:45 +08:00
|
|
|
qh->skel = SKEL_ISO;
|
2007-01-17 00:56:32 +08:00
|
|
|
if (!qh->bandwidth_reserved)
|
|
|
|
uhci_reserve_bandwidth(uhci, qh);
|
2005-12-18 06:58:46 +08:00
|
|
|
return 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int uhci_result_isochronous(struct uhci_hcd *uhci, struct urb *urb)
|
|
|
|
{
|
2006-05-20 04:52:35 +08:00
|
|
|
struct uhci_td *td, *tmp;
|
|
|
|
struct urb_priv *urbp = urb->hcpriv;
|
|
|
|
struct uhci_qh *qh = urbp->qh;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-05-20 04:52:35 +08:00
|
|
|
list_for_each_entry_safe(td, tmp, &urbp->td_list, list) {
|
|
|
|
unsigned int ctrlstat;
|
|
|
|
int status;
|
2005-04-17 06:20:36 +08:00
|
|
|
int actlength;
|
|
|
|
|
2006-05-20 04:52:35 +08:00
|
|
|
if (uhci_frame_before_eq(uhci->cur_iso_frame, qh->iso_frame))
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EINPROGRESS;
|
|
|
|
|
2006-05-20 04:52:35 +08:00
|
|
|
uhci_remove_tds_from_frame(uhci, qh->iso_frame);
|
|
|
|
|
|
|
|
ctrlstat = td_status(td);
|
|
|
|
if (ctrlstat & TD_CTRL_ACTIVE) {
|
|
|
|
status = -EXDEV; /* TD was added too late? */
|
|
|
|
} else {
|
|
|
|
status = uhci_map_status(uhci_status_bits(ctrlstat),
|
|
|
|
usb_pipeout(urb->pipe));
|
|
|
|
actlength = uhci_actual_length(ctrlstat);
|
|
|
|
|
|
|
|
urb->actual_length += actlength;
|
|
|
|
qh->iso_packet_desc->actual_length = actlength;
|
|
|
|
qh->iso_packet_desc->status = status;
|
|
|
|
}
|
2007-08-22 03:37:50 +08:00
|
|
|
if (status)
|
2005-04-17 06:20:36 +08:00
|
|
|
urb->error_count++;
|
|
|
|
|
2006-05-20 04:52:35 +08:00
|
|
|
uhci_remove_td_from_urbp(td);
|
|
|
|
uhci_free_td(uhci, td);
|
|
|
|
qh->iso_frame += qh->period;
|
|
|
|
++qh->iso_packet_desc;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2007-08-22 03:37:50 +08:00
|
|
|
return 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int uhci_urb_enqueue(struct usb_hcd *hcd,
|
2005-10-21 15:21:58 +08:00
|
|
|
struct urb *urb, gfp_t mem_flags)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
|
|
|
|
unsigned long flags;
|
2005-12-18 06:58:46 +08:00
|
|
|
struct urb_priv *urbp;
|
|
|
|
struct uhci_qh *qh;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
spin_lock_irqsave(&uhci->lock, flags);
|
|
|
|
|
2007-08-08 23:48:02 +08:00
|
|
|
ret = usb_hcd_link_urb_to_ep(hcd, urb);
|
|
|
|
if (ret)
|
|
|
|
goto done_not_linked;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-12-18 06:58:46 +08:00
|
|
|
ret = -ENOMEM;
|
|
|
|
urbp = uhci_alloc_urb_priv(uhci, urb);
|
|
|
|
if (!urbp)
|
|
|
|
goto done;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-08-08 23:48:02 +08:00
|
|
|
if (urb->ep->hcpriv)
|
|
|
|
qh = urb->ep->hcpriv;
|
2005-12-18 06:58:46 +08:00
|
|
|
else {
|
2007-08-08 23:48:02 +08:00
|
|
|
qh = uhci_alloc_qh(uhci, urb->dev, urb->ep);
|
2005-12-18 06:58:46 +08:00
|
|
|
if (!qh)
|
|
|
|
goto err_no_qh;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2005-12-18 06:58:46 +08:00
|
|
|
urbp->qh = qh;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-05-06 04:26:58 +08:00
|
|
|
switch (qh->type) {
|
|
|
|
case USB_ENDPOINT_XFER_CONTROL:
|
2005-12-18 06:58:46 +08:00
|
|
|
ret = uhci_submit_control(uhci, urb, qh);
|
|
|
|
break;
|
2006-05-06 04:26:58 +08:00
|
|
|
case USB_ENDPOINT_XFER_BULK:
|
2005-12-18 06:58:46 +08:00
|
|
|
ret = uhci_submit_bulk(uhci, urb, qh);
|
2005-04-17 06:20:36 +08:00
|
|
|
break;
|
2006-05-06 04:26:58 +08:00
|
|
|
case USB_ENDPOINT_XFER_INT:
|
2007-01-17 00:56:32 +08:00
|
|
|
ret = uhci_submit_interrupt(uhci, urb, qh);
|
2005-04-17 06:20:36 +08:00
|
|
|
break;
|
2006-05-06 04:26:58 +08:00
|
|
|
case USB_ENDPOINT_XFER_ISOC:
|
2006-05-20 04:52:35 +08:00
|
|
|
urb->error_count = 0;
|
2005-12-18 06:58:46 +08:00
|
|
|
ret = uhci_submit_isochronous(uhci, urb, qh);
|
2005-04-17 06:20:36 +08:00
|
|
|
break;
|
|
|
|
}
|
2005-12-18 06:58:46 +08:00
|
|
|
if (ret != 0)
|
|
|
|
goto err_submit_failed;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-12-18 06:58:46 +08:00
|
|
|
/* Add this URB to the QH */
|
|
|
|
urbp->qh = qh;
|
|
|
|
list_add_tail(&urbp->node, &qh->queue);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-12-18 06:58:46 +08:00
|
|
|
/* If the new URB is the first and only one on this QH then either
|
|
|
|
* the QH is new and idle or else it's unlinked and waiting to
|
2006-05-06 04:32:02 +08:00
|
|
|
* become idle, so we can activate it right away. But only if the
|
|
|
|
* queue isn't stopped. */
|
2006-05-12 23:35:45 +08:00
|
|
|
if (qh->queue.next == &urbp->node && !qh->is_stopped) {
|
2005-12-18 06:58:46 +08:00
|
|
|
uhci_activate_qh(uhci, qh);
|
2006-06-06 00:28:57 +08:00
|
|
|
uhci_urbp_wants_fsbr(uhci, urbp);
|
2006-05-12 23:35:45 +08:00
|
|
|
}
|
2005-12-18 06:58:46 +08:00
|
|
|
goto done;
|
|
|
|
|
|
|
|
err_submit_failed:
|
|
|
|
if (qh->state == QH_STATE_IDLE)
|
|
|
|
uhci_make_qh_idle(uhci, qh); /* Reclaim unused QH */
|
|
|
|
err_no_qh:
|
|
|
|
uhci_free_urb_priv(uhci, urbp);
|
|
|
|
done:
|
2007-08-08 23:48:02 +08:00
|
|
|
if (ret)
|
|
|
|
usb_hcd_unlink_urb_from_ep(hcd, urb);
|
|
|
|
done_not_linked:
|
2005-04-17 06:20:36 +08:00
|
|
|
spin_unlock_irqrestore(&uhci->lock, flags);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2007-08-08 23:48:02 +08:00
|
|
|
static int uhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
|
2005-12-18 07:02:38 +08:00
|
|
|
{
|
|
|
|
struct uhci_hcd *uhci = hcd_to_uhci(hcd);
|
|
|
|
unsigned long flags;
|
2006-05-20 04:39:52 +08:00
|
|
|
struct uhci_qh *qh;
|
2007-08-08 23:48:02 +08:00
|
|
|
int rc;
|
2005-12-18 07:02:38 +08:00
|
|
|
|
|
|
|
spin_lock_irqsave(&uhci->lock, flags);
|
2007-08-08 23:48:02 +08:00
|
|
|
rc = usb_hcd_check_unlink_urb(hcd, urb, status);
|
|
|
|
if (rc)
|
2005-12-18 07:02:38 +08:00
|
|
|
goto done;
|
2007-08-08 23:48:02 +08:00
|
|
|
|
|
|
|
qh = ((struct urb_priv *) urb->hcpriv)->qh;
|
2005-12-18 07:02:38 +08:00
|
|
|
|
|
|
|
/* Remove Isochronous TDs from the frame list ASAP */
|
2006-05-20 04:39:52 +08:00
|
|
|
if (qh->type == USB_ENDPOINT_XFER_ISOC) {
|
2005-12-18 07:02:38 +08:00
|
|
|
uhci_unlink_isochronous_tds(uhci, urb);
|
2006-05-20 04:39:52 +08:00
|
|
|
mb();
|
|
|
|
|
|
|
|
/* If the URB has already started, update the QH unlink time */
|
|
|
|
uhci_get_current_frame_number(uhci);
|
|
|
|
if (uhci_frame_before_eq(urb->start_frame, uhci->frame_number))
|
|
|
|
qh->unlink_frame = uhci->frame_number;
|
|
|
|
}
|
|
|
|
|
|
|
|
uhci_unlink_qh(uhci, qh);
|
2005-12-18 07:02:38 +08:00
|
|
|
|
|
|
|
done:
|
|
|
|
spin_unlock_irqrestore(&uhci->lock, flags);
|
2007-08-08 23:48:02 +08:00
|
|
|
return rc;
|
2005-12-18 07:02:38 +08:00
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/*
|
2005-12-18 07:02:38 +08:00
|
|
|
* Finish unlinking an URB and give it back
|
2005-04-17 06:20:36 +08:00
|
|
|
*/
|
2005-12-18 07:02:38 +08:00
|
|
|
static void uhci_giveback_urb(struct uhci_hcd *uhci, struct uhci_qh *qh,
|
2007-08-25 03:42:24 +08:00
|
|
|
struct urb *urb, int status)
|
2005-12-18 07:02:38 +08:00
|
|
|
__releases(uhci->lock)
|
|
|
|
__acquires(uhci->lock)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-12-18 06:58:46 +08:00
|
|
|
struct urb_priv *urbp = (struct urb_priv *) urb->hcpriv;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-07-14 03:46:29 +08:00
|
|
|
if (qh->type == USB_ENDPOINT_XFER_CONTROL) {
|
|
|
|
|
|
|
|
/* urb->actual_length < 0 means the setup transaction didn't
|
|
|
|
* complete successfully. Either it failed or the URB was
|
|
|
|
* unlinked first. Regardless, don't confuse people with a
|
|
|
|
* negative length. */
|
|
|
|
urb->actual_length = max(urb->actual_length, 0);
|
|
|
|
}
|
|
|
|
|
2006-05-20 04:52:35 +08:00
|
|
|
/* When giving back the first URB in an Isochronous queue,
|
|
|
|
* reinitialize the QH's iso-related members for the next URB. */
|
2007-07-14 03:46:29 +08:00
|
|
|
else if (qh->type == USB_ENDPOINT_XFER_ISOC &&
|
2006-05-20 04:52:35 +08:00
|
|
|
urbp->node.prev == &qh->queue &&
|
|
|
|
urbp->node.next != &qh->queue) {
|
|
|
|
struct urb *nurb = list_entry(urbp->node.next,
|
|
|
|
struct urb_priv, node)->urb;
|
|
|
|
|
|
|
|
qh->iso_packet_desc = &nurb->iso_frame_desc[0];
|
|
|
|
qh->iso_frame = nurb->start_frame;
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-12-18 07:02:38 +08:00
|
|
|
/* Take the URB off the QH's queue. If the queue is now empty,
|
|
|
|
* this is a perfect time for a toggle fixup. */
|
|
|
|
list_del_init(&urbp->node);
|
|
|
|
if (list_empty(&qh->queue) && qh->needs_fixup) {
|
|
|
|
usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe),
|
|
|
|
usb_pipeout(urb->pipe), qh->initial_toggle);
|
|
|
|
qh->needs_fixup = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
uhci_free_urb_priv(uhci, urbp);
|
2007-08-08 23:48:02 +08:00
|
|
|
usb_hcd_unlink_urb_from_ep(uhci_to_hcd(uhci), urb);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-12-18 07:02:38 +08:00
|
|
|
spin_unlock(&uhci->lock);
|
2007-08-25 03:42:24 +08:00
|
|
|
usb_hcd_giveback_urb(uhci_to_hcd(uhci), urb, status);
|
2005-12-18 07:02:38 +08:00
|
|
|
spin_lock(&uhci->lock);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-12-18 07:02:38 +08:00
|
|
|
/* If the queue is now empty, we can unlink the QH and give up its
|
|
|
|
* reserved bandwidth. */
|
|
|
|
if (list_empty(&qh->queue)) {
|
|
|
|
uhci_unlink_qh(uhci, qh);
|
2007-01-17 00:56:32 +08:00
|
|
|
if (qh->bandwidth_reserved)
|
|
|
|
uhci_release_bandwidth(uhci, qh);
|
2005-12-18 07:02:38 +08:00
|
|
|
}
|
2005-12-18 06:58:46 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-12-18 06:58:46 +08:00
|
|
|
/*
|
2005-12-18 07:02:38 +08:00
|
|
|
* Scan the URBs in a QH's queue
|
2005-12-18 06:58:46 +08:00
|
|
|
*/
|
2005-12-18 07:02:38 +08:00
|
|
|
#define QH_FINISHED_UNLINKING(qh) \
|
|
|
|
(qh->state == QH_STATE_UNLINKING && \
|
|
|
|
uhci->frame_number + uhci->is_stopped != qh->unlink_frame)
|
2005-04-17 06:20:36 +08:00
|
|
|
|
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers
Maintain a per-CPU global "struct pt_regs *" variable which can be used instead
of passing regs around manually through all ~1800 interrupt handlers in the
Linux kernel.
The regs pointer is used in few places, but it potentially costs both stack
space and code to pass it around. On the FRV arch, removing the regs parameter
from all the genirq function results in a 20% speed up of the IRQ exit path
(ie: from leaving timer_interrupt() to leaving do_IRQ()).
Where appropriate, an arch may override the generic storage facility and do
something different with the variable. On FRV, for instance, the address is
maintained in GR28 at all times inside the kernel as part of general exception
handling.
Having looked over the code, it appears that the parameter may be handed down
through up to twenty or so layers of functions. Consider a USB character
device attached to a USB hub, attached to a USB controller that posts its
interrupts through a cascaded auxiliary interrupt controller. A character
device driver may want to pass regs to the sysrq handler through the input
layer which adds another few layers of parameter passing.
I've build this code with allyesconfig for x86_64 and i386. I've runtested the
main part of the code on FRV and i386, though I can't test most of the drivers.
I've also done partial conversion for powerpc and MIPS - these at least compile
with minimal configurations.
This will affect all archs. Mostly the changes should be relatively easy.
Take do_IRQ(), store the regs pointer at the beginning, saving the old one:
struct pt_regs *old_regs = set_irq_regs(regs);
And put the old one back at the end:
set_irq_regs(old_regs);
Don't pass regs through to generic_handle_irq() or __do_IRQ().
In timer_interrupt(), this sort of change will be necessary:
- update_process_times(user_mode(regs));
- profile_tick(CPU_PROFILING, regs);
+ update_process_times(user_mode(get_irq_regs()));
+ profile_tick(CPU_PROFILING);
I'd like to move update_process_times()'s use of get_irq_regs() into itself,
except that i386, alone of the archs, uses something other than user_mode().
Some notes on the interrupt handling in the drivers:
(*) input_dev() is now gone entirely. The regs pointer is no longer stored in
the input_dev struct.
(*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does
something different depending on whether it's been supplied with a regs
pointer or not.
(*) Various IRQ handler function pointers have been moved to type
irq_handler_t.
Signed-Off-By: David Howells <dhowells@redhat.com>
(cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 21:55:46 +08:00
|
|
|
static void uhci_scan_qh(struct uhci_hcd *uhci, struct uhci_qh *qh)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
struct urb_priv *urbp;
|
2005-12-18 07:02:38 +08:00
|
|
|
struct urb *urb;
|
|
|
|
int status;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-12-18 07:02:38 +08:00
|
|
|
while (!list_empty(&qh->queue)) {
|
|
|
|
urbp = list_entry(qh->queue.next, struct urb_priv, node);
|
|
|
|
urb = urbp->urb;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-05-12 23:14:25 +08:00
|
|
|
if (qh->type == USB_ENDPOINT_XFER_ISOC)
|
2005-12-18 07:02:38 +08:00
|
|
|
status = uhci_result_isochronous(uhci, urb);
|
2006-05-12 23:14:25 +08:00
|
|
|
else
|
2005-12-18 07:02:38 +08:00
|
|
|
status = uhci_result_common(uhci, urb);
|
|
|
|
if (status == -EINPROGRESS)
|
|
|
|
break;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-12-18 07:02:38 +08:00
|
|
|
/* Dequeued but completed URBs can't be given back unless
|
|
|
|
* the QH is stopped or has finished unlinking. */
|
2007-08-22 03:40:36 +08:00
|
|
|
if (urb->unlinked) {
|
2006-05-06 04:32:02 +08:00
|
|
|
if (QH_FINISHED_UNLINKING(qh))
|
|
|
|
qh->is_stopped = 1;
|
|
|
|
else if (!qh->is_stopped)
|
|
|
|
return;
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-08-25 03:42:24 +08:00
|
|
|
uhci_giveback_urb(uhci, qh, urb, status);
|
2007-08-22 03:37:50 +08:00
|
|
|
if (status < 0)
|
2005-12-18 07:02:38 +08:00
|
|
|
break;
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-12-18 07:02:38 +08:00
|
|
|
/* If the QH is neither stopped nor finished unlinking (normal case),
|
|
|
|
* our work here is done. */
|
2006-05-06 04:32:02 +08:00
|
|
|
if (QH_FINISHED_UNLINKING(qh))
|
|
|
|
qh->is_stopped = 1;
|
|
|
|
else if (!qh->is_stopped)
|
2005-12-18 07:02:38 +08:00
|
|
|
return;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-12-18 07:02:38 +08:00
|
|
|
/* Otherwise give back each of the dequeued URBs */
|
2006-05-06 04:32:02 +08:00
|
|
|
restart:
|
2005-12-18 07:02:38 +08:00
|
|
|
list_for_each_entry(urbp, &qh->queue, node) {
|
|
|
|
urb = urbp->urb;
|
2007-08-22 03:40:36 +08:00
|
|
|
if (urb->unlinked) {
|
2006-05-20 04:39:52 +08:00
|
|
|
|
|
|
|
/* Fix up the TD links and save the toggles for
|
|
|
|
* non-Isochronous queues. For Isochronous queues,
|
|
|
|
* test for too-recent dequeues. */
|
|
|
|
if (!uhci_cleanup_queue(uhci, qh, urb)) {
|
|
|
|
qh->is_stopped = 0;
|
|
|
|
return;
|
|
|
|
}
|
2007-08-25 03:42:24 +08:00
|
|
|
uhci_giveback_urb(uhci, qh, urb, 0);
|
2005-12-18 07:02:38 +08:00
|
|
|
goto restart;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
qh->is_stopped = 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-12-18 07:02:38 +08:00
|
|
|
/* There are no more dequeued URBs. If there are still URBs on the
|
|
|
|
* queue, the QH can now be re-activated. */
|
|
|
|
if (!list_empty(&qh->queue)) {
|
|
|
|
if (qh->needs_fixup)
|
|
|
|
uhci_fixup_toggles(qh, 0);
|
2006-05-12 23:35:45 +08:00
|
|
|
|
|
|
|
/* If the first URB on the queue wants FSBR but its time
|
|
|
|
* limit has expired, set the next TD to interrupt on
|
|
|
|
* completion before reactivating the QH. */
|
|
|
|
urbp = list_entry(qh->queue.next, struct urb_priv, node);
|
|
|
|
if (urbp->fsbr && qh->wait_expired) {
|
|
|
|
struct uhci_td *td = list_entry(urbp->td_list.next,
|
|
|
|
struct uhci_td, list);
|
|
|
|
|
|
|
|
td->status |= __cpu_to_le32(TD_CTRL_IOC);
|
|
|
|
}
|
|
|
|
|
2005-12-18 07:02:38 +08:00
|
|
|
uhci_activate_qh(uhci, qh);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2005-12-18 07:02:38 +08:00
|
|
|
/* The queue is empty. The QH can become idle if it is fully
|
|
|
|
* unlinked. */
|
|
|
|
else if (QH_FINISHED_UNLINKING(qh))
|
|
|
|
uhci_make_qh_idle(uhci, qh);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2006-05-12 23:35:45 +08:00
|
|
|
/*
|
|
|
|
* Check for queues that have made some forward progress.
|
|
|
|
* Returns 0 if the queue is not Isochronous, is ACTIVE, and
|
|
|
|
* has not advanced since last examined; 1 otherwise.
|
2006-05-12 23:41:59 +08:00
|
|
|
*
|
|
|
|
* Early Intel controllers have a bug which causes qh->element sometimes
|
|
|
|
* not to advance when a TD completes successfully. The queue remains
|
|
|
|
* stuck on the inactive completed TD. We detect such cases and advance
|
|
|
|
* the element pointer by hand.
|
2006-05-12 23:35:45 +08:00
|
|
|
*/
|
|
|
|
static int uhci_advance_check(struct uhci_hcd *uhci, struct uhci_qh *qh)
|
|
|
|
{
|
|
|
|
struct urb_priv *urbp = NULL;
|
|
|
|
struct uhci_td *td;
|
|
|
|
int ret = 1;
|
|
|
|
unsigned status;
|
|
|
|
|
|
|
|
if (qh->type == USB_ENDPOINT_XFER_ISOC)
|
2006-06-06 00:28:57 +08:00
|
|
|
goto done;
|
2006-05-12 23:35:45 +08:00
|
|
|
|
|
|
|
/* Treat an UNLINKING queue as though it hasn't advanced.
|
|
|
|
* This is okay because reactivation will treat it as though
|
|
|
|
* it has advanced, and if it is going to become IDLE then
|
|
|
|
* this doesn't matter anyway. Furthermore it's possible
|
|
|
|
* for an UNLINKING queue not to have any URBs at all, or
|
|
|
|
* for its first URB not to have any TDs (if it was dequeued
|
|
|
|
* just as it completed). So it's not easy in any case to
|
|
|
|
* test whether such queues have advanced. */
|
|
|
|
if (qh->state != QH_STATE_ACTIVE) {
|
|
|
|
urbp = NULL;
|
|
|
|
status = 0;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
urbp = list_entry(qh->queue.next, struct urb_priv, node);
|
|
|
|
td = list_entry(urbp->td_list.next, struct uhci_td, list);
|
|
|
|
status = td_status(td);
|
|
|
|
if (!(status & TD_CTRL_ACTIVE)) {
|
|
|
|
|
|
|
|
/* We're okay, the queue has advanced */
|
|
|
|
qh->wait_expired = 0;
|
|
|
|
qh->advance_jiffies = jiffies;
|
2006-06-06 00:28:57 +08:00
|
|
|
goto done;
|
2006-05-12 23:35:45 +08:00
|
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* The queue hasn't advanced; check for timeout */
|
2006-06-06 00:28:57 +08:00
|
|
|
if (qh->wait_expired)
|
|
|
|
goto done;
|
|
|
|
|
|
|
|
if (time_after(jiffies, qh->advance_jiffies + QH_WAIT_TIMEOUT)) {
|
2006-05-12 23:41:59 +08:00
|
|
|
|
|
|
|
/* Detect the Intel bug and work around it */
|
2007-02-20 04:51:51 +08:00
|
|
|
if (qh->post_td && qh_element(qh) == LINK_TO_TD(qh->post_td)) {
|
2006-05-12 23:41:59 +08:00
|
|
|
qh->element = qh->post_td->link;
|
|
|
|
qh->advance_jiffies = jiffies;
|
2006-06-06 00:28:57 +08:00
|
|
|
ret = 1;
|
|
|
|
goto done;
|
2006-05-12 23:41:59 +08:00
|
|
|
}
|
|
|
|
|
2006-05-12 23:35:45 +08:00
|
|
|
qh->wait_expired = 1;
|
|
|
|
|
|
|
|
/* If the current URB wants FSBR, unlink it temporarily
|
|
|
|
* so that we can safely set the next TD to interrupt on
|
|
|
|
* completion. That way we'll know as soon as the queue
|
|
|
|
* starts moving again. */
|
|
|
|
if (urbp && urbp->fsbr && !(status & TD_CTRL_IOC))
|
|
|
|
uhci_unlink_qh(uhci, qh);
|
2006-06-06 00:28:57 +08:00
|
|
|
|
|
|
|
} else {
|
|
|
|
/* Unmoving but not-yet-expired queues keep FSBR alive */
|
|
|
|
if (urbp)
|
|
|
|
uhci_urbp_wants_fsbr(uhci, urbp);
|
2006-05-12 23:35:45 +08:00
|
|
|
}
|
2006-06-06 00:28:57 +08:00
|
|
|
|
|
|
|
done:
|
2006-05-12 23:35:45 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2005-12-18 07:02:38 +08:00
|
|
|
/*
|
|
|
|
* Process events in the schedule, but only in one thread at a time
|
|
|
|
*/
|
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers
Maintain a per-CPU global "struct pt_regs *" variable which can be used instead
of passing regs around manually through all ~1800 interrupt handlers in the
Linux kernel.
The regs pointer is used in few places, but it potentially costs both stack
space and code to pass it around. On the FRV arch, removing the regs parameter
from all the genirq function results in a 20% speed up of the IRQ exit path
(ie: from leaving timer_interrupt() to leaving do_IRQ()).
Where appropriate, an arch may override the generic storage facility and do
something different with the variable. On FRV, for instance, the address is
maintained in GR28 at all times inside the kernel as part of general exception
handling.
Having looked over the code, it appears that the parameter may be handed down
through up to twenty or so layers of functions. Consider a USB character
device attached to a USB hub, attached to a USB controller that posts its
interrupts through a cascaded auxiliary interrupt controller. A character
device driver may want to pass regs to the sysrq handler through the input
layer which adds another few layers of parameter passing.
I've build this code with allyesconfig for x86_64 and i386. I've runtested the
main part of the code on FRV and i386, though I can't test most of the drivers.
I've also done partial conversion for powerpc and MIPS - these at least compile
with minimal configurations.
This will affect all archs. Mostly the changes should be relatively easy.
Take do_IRQ(), store the regs pointer at the beginning, saving the old one:
struct pt_regs *old_regs = set_irq_regs(regs);
And put the old one back at the end:
set_irq_regs(old_regs);
Don't pass regs through to generic_handle_irq() or __do_IRQ().
In timer_interrupt(), this sort of change will be necessary:
- update_process_times(user_mode(regs));
- profile_tick(CPU_PROFILING, regs);
+ update_process_times(user_mode(get_irq_regs()));
+ profile_tick(CPU_PROFILING);
I'd like to move update_process_times()'s use of get_irq_regs() into itself,
except that i386, alone of the archs, uses something other than user_mode().
Some notes on the interrupt handling in the drivers:
(*) input_dev() is now gone entirely. The regs pointer is no longer stored in
the input_dev struct.
(*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does
something different depending on whether it's been supplied with a regs
pointer or not.
(*) Various IRQ handler function pointers have been moved to type
irq_handler_t.
Signed-Off-By: David Howells <dhowells@redhat.com>
(cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 21:55:46 +08:00
|
|
|
static void uhci_scan_schedule(struct uhci_hcd *uhci)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2005-12-18 07:02:38 +08:00
|
|
|
int i;
|
|
|
|
struct uhci_qh *qh;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* Don't allow re-entrant calls */
|
|
|
|
if (uhci->scan_in_progress) {
|
|
|
|
uhci->need_rescan = 1;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
uhci->scan_in_progress = 1;
|
2006-05-12 23:35:45 +08:00
|
|
|
rescan:
|
2005-04-17 06:20:36 +08:00
|
|
|
uhci->need_rescan = 0;
|
2006-06-06 00:28:57 +08:00
|
|
|
uhci->fsbr_is_wanted = 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-04-22 04:04:58 +08:00
|
|
|
uhci_clear_next_interrupt(uhci);
|
2005-04-17 06:20:36 +08:00
|
|
|
uhci_get_current_frame_number(uhci);
|
2006-05-20 04:52:35 +08:00
|
|
|
uhci->cur_iso_frame = uhci->frame_number;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-12-18 07:02:38 +08:00
|
|
|
/* Go through all the QH queues and process the URBs in each one */
|
|
|
|
for (i = 0; i < UHCI_NUM_SKELQH - 1; ++i) {
|
|
|
|
uhci->next_qh = list_entry(uhci->skelqh[i]->node.next,
|
|
|
|
struct uhci_qh, node);
|
|
|
|
while ((qh = uhci->next_qh) != uhci->skelqh[i]) {
|
|
|
|
uhci->next_qh = list_entry(qh->node.next,
|
|
|
|
struct uhci_qh, node);
|
2006-05-12 23:35:45 +08:00
|
|
|
|
|
|
|
if (uhci_advance_check(uhci, qh)) {
|
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers
Maintain a per-CPU global "struct pt_regs *" variable which can be used instead
of passing regs around manually through all ~1800 interrupt handlers in the
Linux kernel.
The regs pointer is used in few places, but it potentially costs both stack
space and code to pass it around. On the FRV arch, removing the regs parameter
from all the genirq function results in a 20% speed up of the IRQ exit path
(ie: from leaving timer_interrupt() to leaving do_IRQ()).
Where appropriate, an arch may override the generic storage facility and do
something different with the variable. On FRV, for instance, the address is
maintained in GR28 at all times inside the kernel as part of general exception
handling.
Having looked over the code, it appears that the parameter may be handed down
through up to twenty or so layers of functions. Consider a USB character
device attached to a USB hub, attached to a USB controller that posts its
interrupts through a cascaded auxiliary interrupt controller. A character
device driver may want to pass regs to the sysrq handler through the input
layer which adds another few layers of parameter passing.
I've build this code with allyesconfig for x86_64 and i386. I've runtested the
main part of the code on FRV and i386, though I can't test most of the drivers.
I've also done partial conversion for powerpc and MIPS - these at least compile
with minimal configurations.
This will affect all archs. Mostly the changes should be relatively easy.
Take do_IRQ(), store the regs pointer at the beginning, saving the old one:
struct pt_regs *old_regs = set_irq_regs(regs);
And put the old one back at the end:
set_irq_regs(old_regs);
Don't pass regs through to generic_handle_irq() or __do_IRQ().
In timer_interrupt(), this sort of change will be necessary:
- update_process_times(user_mode(regs));
- profile_tick(CPU_PROFILING, regs);
+ update_process_times(user_mode(get_irq_regs()));
+ profile_tick(CPU_PROFILING);
I'd like to move update_process_times()'s use of get_irq_regs() into itself,
except that i386, alone of the archs, uses something other than user_mode().
Some notes on the interrupt handling in the drivers:
(*) input_dev() is now gone entirely. The regs pointer is no longer stored in
the input_dev struct.
(*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does
something different depending on whether it's been supplied with a regs
pointer or not.
(*) Various IRQ handler function pointers have been moved to type
irq_handler_t.
Signed-Off-By: David Howells <dhowells@redhat.com>
(cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 21:55:46 +08:00
|
|
|
uhci_scan_qh(uhci, qh);
|
2006-06-06 00:28:57 +08:00
|
|
|
if (qh->state == QH_STATE_ACTIVE) {
|
|
|
|
uhci_urbp_wants_fsbr(uhci,
|
|
|
|
list_entry(qh->queue.next, struct urb_priv, node));
|
|
|
|
}
|
2006-05-12 23:35:45 +08:00
|
|
|
}
|
2005-12-18 07:02:38 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2006-05-20 04:52:35 +08:00
|
|
|
uhci->last_iso_frame = uhci->cur_iso_frame;
|
2005-04-17 06:20:36 +08:00
|
|
|
if (uhci->need_rescan)
|
|
|
|
goto rescan;
|
|
|
|
uhci->scan_in_progress = 0;
|
|
|
|
|
2006-06-06 00:28:57 +08:00
|
|
|
if (uhci->fsbr_is_on && !uhci->fsbr_is_wanted &&
|
|
|
|
!uhci->fsbr_expiring) {
|
|
|
|
uhci->fsbr_expiring = 1;
|
|
|
|
mod_timer(&uhci->fsbr_timer, jiffies + FSBR_OFF_DELAY);
|
|
|
|
}
|
2006-05-12 23:35:45 +08:00
|
|
|
|
2006-05-12 23:29:04 +08:00
|
|
|
if (list_empty(&uhci->skel_unlink_qh->node))
|
2005-04-17 06:20:36 +08:00
|
|
|
uhci_clear_next_interrupt(uhci);
|
|
|
|
else
|
|
|
|
uhci_set_next_interrupt(uhci);
|
|
|
|
}
|