# This is a BitKeeper generated patch for the following project: # Project Name: Linux kernel tree # This patch format is intended for GNU patch command version 2.5 or higher. # This patch includes the following deltas: # ChangeSet 1.470 -> 1.471 # drivers/usb/host/ohci-hcd.c 1.20 -> 1.21 # drivers/usb/host/ohci-q.c 1.14 -> 1.15 # drivers/usb/host/ohci.h 1.9 -> 1.10 # # The following is the BitKeeper ChangeSet Log # -------------------------------------------- # 02/07/25 david-b@pacbell.net 1.471 # [PATCH] ohci unlink cleanups # # Attached is a patch that cleans up a few more issues in the OHCI unlink # code. # # There may still be an ISO-IN data problem, I'll look at that separately # since it seems unrelated to unlink issues. # # - Simplify/correct ED lifecycle # * UNLINK is now for real: descheduled and waiting for SOF # * finish_unlinks() expects descheduled EDs (may reschedule) # * only ed_deschedule() turns off hardware schedule processing # * no more NEW state # * no more ED_URB_DEL flag (it added extra states) # * new IDLE state, "not scheduled" (replaces previous UNLINKing) # - Bugfixes # * ed_get(), potential memleak is now gone # * urb_enqueue(), won't submit to dead/sleeping hc # * free_config(), rescans after SOF when needed # * ed_schedule(), use wmb() # * ed_schedule() and finish_unlinks(), more thorough about # restarting control or bulk processing # * finish_unlinks(), more cautious about reentering # - General: # * ed->ed_rm_list renamed ed_next; to be used more later # * slightly shrink object code # * rename some functions # # This leaves one notable issue in the unlink paths: the driver never waits # for SOF after descheduling (empty) EDs. That's racey in most cases, though # there are a few light-traffic cases where that's correct (in part because # the ED is empty). Easy to fix once the rest of this is known to behave. # -------------------------------------------- # diff -Nru a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c --- a/drivers/usb/host/ohci-hcd.c Fri Jul 26 13:49:13 2002 +++ b/drivers/usb/host/ohci-hcd.c Fri Jul 26 13:49:13 2002 @@ -10,8 +10,15 @@ * [ (C) Copyright 1999 Gregory P. Smith] * * + * OHCI is the main "non-Intel/VIA" standard for USB 1.1 host controller + * interfaces (though some non-x86 Intel chips use it). It supports + * smarter hardware than UHCI. A download link for the spec available + * through the http://www.usb.org website. + * * History: * + * 2002/07/19 fixes to management of ED and schedule state. + * 2002/06/09 SA-1111 support (Christopher Hoover) * 2002/06/01 remember frame when HC won't see EDs any more; use that info * to fix urb unlink races caused by interrupt latency assumptions; * minor ED field and function naming updates @@ -95,12 +102,12 @@ /* * TO DO: * - * - "disabled" should be the hcd state + * - "disabled" and "sleeping" should be in hcd->state * - bandwidth alloc to generic code * - lots more testing!! */ -#define DRIVER_VERSION "2002-Jun-15" +#define DRIVER_VERSION "2002-Jul-19" #define DRIVER_AUTHOR "Roman Weissgaerber , David Brownell" #define DRIVER_DESC "USB 1.1 'Open' Host Controller (OHCI) Driver" @@ -140,6 +147,7 @@ int i, size = 0; unsigned long flags; int bustime = 0; + int retval = 0; #ifdef OHCI_VERBOSE_DEBUG urb_print (urb, "SUB", usb_pipein (pipe)); @@ -191,19 +199,25 @@ return -ENOMEM; memset (urb_priv, 0, sizeof (urb_priv_t) + size * sizeof (struct td *)); + spin_lock_irqsave (&ohci->lock, flags); + + /* don't submit to a dead HC */ + if (ohci->disabled || ohci->sleeping) { + retval = -ENODEV; + goto fail; + } + /* fill the private part of the URB */ urb_priv->length = size; urb_priv->ed = ed; /* allocate the TDs (updating hash chains) */ - spin_lock_irqsave (&ohci->lock, flags); for (i = 0; i < size; i++) { urb_priv->td [i] = td_alloc (ohci, SLAB_ATOMIC); if (!urb_priv->td [i]) { urb_priv->length = i; - urb_free_priv (ohci, urb_priv); - spin_unlock_irqrestore (&ohci->lock, flags); - return -ENOMEM; + retval = -ENOMEM; + goto fail; } } @@ -217,11 +231,11 @@ switch (usb_pipetype (pipe)) { case PIPE_ISOCHRONOUS: if (urb->transfer_flags & USB_ISO_ASAP) { - urb->start_frame = ( (ed->state == ED_OPER) + urb->start_frame = ((ed->state != ED_IDLE) ? (ed->intriso.last_iso + 1) : (le16_to_cpu (ohci->hcca->frame_no) + 10)) & 0xffff; - } + } /* FALLTHROUGH */ case PIPE_INTERRUPT: if (urb->bandwidth == 0) { @@ -238,18 +252,20 @@ urb->hcpriv = urb_priv; - /* link the ed into a chain if is not already */ - if (ed->state != ED_OPER) - ep_link (ohci, ed); + /* schedule the ed if needed */ + if (ed->state == ED_IDLE) + ed_schedule (ohci, ed); /* fill the TDs and link them to the ed; and * enable that part of the schedule, if needed */ td_submit_urb (urb); +fail: + if (retval) + urb_free_priv (ohci, urb_priv); spin_unlock_irqrestore (&ohci->lock, flags); - - return 0; + return retval; } /* @@ -270,19 +286,17 @@ if (!ohci->disabled) { urb_priv_t *urb_priv; - /* flag the urb's data for deletion in some upcoming - * SF interrupt's delete list processing + /* Unless an IRQ completed the unlink while it was being + * handed to us, flag it for unlink and giveback, and force + * some upcoming INTR_SF to call finish_unlinks() */ spin_lock_irqsave (&ohci->lock, flags); urb_priv = urb->hcpriv; - - if (!urb_priv || (urb_priv->state == URB_DEL)) { - spin_unlock_irqrestore (&ohci->lock, flags); - return 0; + if (urb_priv) { + urb_priv->state = URB_DEL; + if (urb_priv->ed->state == ED_OPER) + start_urb_unlink (ohci, urb_priv->ed); } - - urb_priv->state = URB_DEL; - start_urb_unlink (ohci, urb_priv->ed); spin_unlock_irqrestore (&ohci->lock, flags); } else { /* @@ -290,12 +304,16 @@ * any more ... just clean up every urb's memory. */ finish_urb (ohci, urb); - } + } return 0; } /*-------------------------------------------------------------------------*/ +/* frees config/altsetting state for endpoints, + * including ED memory, dummy TD, and bulk/intr data toggle + */ + static void ohci_free_config (struct usb_hcd *hcd, struct usb_device *udev) { @@ -303,7 +321,11 @@ struct hcd_dev *dev = (struct hcd_dev *) udev->hcpriv; int i; unsigned long flags; +#ifdef DEBUG + int rescans = 0; +#endif +rescan: /* free any eds, and dummy tds, still hanging around */ spin_lock_irqsave (&ohci->lock, flags); for (i = 0; i < 32; i++) { @@ -312,27 +334,47 @@ if (!ed) continue; - ed->state &= ~ED_URB_DEL; - if (ohci->disabled && ed->state == ED_OPER) - ed->state = ED_UNLINK; + if (ohci->disabled && ed->state != ED_IDLE) + ed->state = ED_IDLE; switch (ed->state) { - case ED_NEW: - break; - case ED_UNLINK: + case ED_UNLINK: /* wait a frame? */ + goto do_rescan; + case ED_IDLE: /* fully unlinked */ td_free (ohci, ed->dummy); break; - - case ED_OPER: default: +#ifdef DEBUG err ("illegal ED %d state in free_config, %d", i, ed->state); -#ifdef DEBUG - BUG (); #endif + /* ED_OPER: some driver disconnect() is broken, + * it didn't even start its unlinks much less wait + * for their completions. + * OTHERWISE: hcd bug, ed is garbage + */ + BUG (); } ed_free (ohci, ed); } spin_unlock_irqrestore (&ohci->lock, flags); + return; + +do_rescan: +#ifdef DEBUG + /* a driver->disconnect() returned before its unlinks completed? */ + if (in_interrupt ()) { + dbg ("WARNING: spin in interrupt; driver->disconnect() bug"); + dbg ("dev usb-%s-%s ep 0x%x", + ohci->hcd.self.bus_name, udev->devpath, i); + } + BUG_ON (!(readl (&ohci->regs->intrenable) & OHCI_INTR_SF)); + BUG_ON (rescans >= 2); /* HWBUG */ + rescans++; +#endif + + spin_unlock_irqrestore (&ohci->lock, flags); + wait_ms (1); + goto rescan; } static int ohci_get_frame (struct usb_hcd *hcd) diff -Nru a/drivers/usb/host/ohci-q.c b/drivers/usb/host/ohci-q.c --- a/drivers/usb/host/ohci-q.c Fri Jul 26 13:49:13 2002 +++ b/drivers/usb/host/ohci-q.c Fri Jul 26 13:49:13 2002 @@ -170,50 +170,50 @@ /* link an ed into one of the HC chains */ -static int ep_link (struct ohci_hcd *ohci, struct ed *edi) +static void ed_schedule (struct ohci_hcd *ohci, struct ed *ed) { int int_branch, i; int inter, interval, load; __u32 *ed_p; - volatile struct ed *ed = edi; ed->state = ED_OPER; + ed->hwNextED = 0; + wmb (); + + /* we care about rm_list when setting CLE/BLE in case the HC was at + * work on some TD when CLE/BLE was turned off, and isn't quiesced + * yet. finish_unlinks() restarts as needed, some upcoming INTR_SF. + */ switch (ed->type) { case PIPE_CONTROL: - ed->hwNextED = 0; if (ohci->ed_controltail == NULL) { writel (ed->dma, &ohci->regs->ed_controlhead); } else { ohci->ed_controltail->hwNextED = cpu_to_le32 (ed->dma); } ed->ed_prev = ohci->ed_controltail; - if (!ohci->ed_controltail - && !ohci->ed_rm_list - && !ohci->sleeping - ) { + if (!ohci->ed_controltail && !ohci->ed_rm_list) { ohci->hc_control |= OHCI_CTRL_CLE; + writel (0, &ohci->regs->ed_controlcurrent); writel (ohci->hc_control, &ohci->regs->control); } - ohci->ed_controltail = edi; + ohci->ed_controltail = ed; break; case PIPE_BULK: - ed->hwNextED = 0; if (ohci->ed_bulktail == NULL) { writel (ed->dma, &ohci->regs->ed_bulkhead); } else { ohci->ed_bulktail->hwNextED = cpu_to_le32 (ed->dma); } ed->ed_prev = ohci->ed_bulktail; - if (!ohci->ed_bulktail - && !ohci->ed_rm_list - && !ohci->sleeping - ) { + if (!ohci->ed_bulktail && !ohci->ed_rm_list) { ohci->hc_control |= OHCI_CTRL_BLE; + writel (0, &ohci->regs->ed_bulkcurrent); writel (ohci->hc_control, &ohci->regs->control); } - ohci->ed_bulktail = edi; + ohci->ed_bulktail = ed; break; case PIPE_INTERRUPT: @@ -231,17 +231,16 @@ ed->hwNextED = *ed_p; *ed_p = cpu_to_le32 (ed->dma); } + wmb (); #ifdef OHCI_VERBOSE_DEBUG ohci_dump_periodic (ohci, "LINK_INT"); #endif break; case PIPE_ISOCHRONOUS: - ed->hwNextED = 0; - ed->interval = 1; + ed->ed_prev = ohci->ed_isotail; if (ohci->ed_isotail != NULL) { ohci->ed_isotail->hwNextED = cpu_to_le32 (ed->dma); - ed->ed_prev = ohci->ed_isotail; } else { for ( i = 0; i < NUM_INTS; i += inter) { inter = 1; @@ -251,15 +250,18 @@ inter = ep_rev (6, (dma_to_ed (ohci, le32_to_cpup (ed_p)))->interval); *ed_p = cpu_to_le32 (ed->dma); } - ed->ed_prev = NULL; } - ohci->ed_isotail = edi; + wmb (); + ohci->ed_isotail = ed; #ifdef OHCI_VERBOSE_DEBUG ohci_dump_periodic (ohci, "LINK_ISO"); #endif break; } - return 0; + + /* the HC may not see the schedule updates yet, but if it does + * then they'll be properly ordered. + */ } /*-------------------------------------------------------------------------*/ @@ -288,9 +290,8 @@ * just the link to the ed is unlinked. * the link from the ed still points to another operational ed or 0 * so the HC can eventually finish the processing of the unlinked ed - * caller guarantees the ED has no active TDs. */ -static int start_ed_unlink (struct ohci_hcd *ohci, struct ed *ed) +static void ed_deschedule (struct ohci_hcd *ohci, struct ed *ed) { int i; @@ -361,15 +362,14 @@ break; } - /* FIXME ED's "unlink" state is indeterminate; - * the HC might still be caching it (till SOF). - * - use ed_rm_list and finish_unlinks(), adding some state that - * prevents clobbering hw linkage before the appropriate SOF - * - a speedup: when only one urb is queued on the ed, save 1msec - * by making start_urb_unlink() use this routine to deschedule. + /* FIXME Except for a couple of exceptionally clean unlink cases + * (like unlinking the only c/b ED, with no TDs) HCs may still be + * caching this (till SOF). + * + * To avoid racing with the hardware, this needs to use ED_UNLINK + * and delay til next INTR_SF. Merge with start_urb_unlink(). */ - ed->state = ED_UNLINK; - return 0; + ed->state = ED_IDLE; } @@ -403,35 +403,27 @@ spin_lock_irqsave (&ohci->lock, flags); if (!(ed = dev->ep [ep])) { + struct td *td; + ed = ed_alloc (ohci, SLAB_ATOMIC); if (!ed) { /* out of memory */ goto done; } dev->ep [ep] = ed; - } - - if (ed->state & ED_URB_DEL) { - /* pending unlink request */ - ed = 0; - goto done; - } - - if (ed->state == ED_NEW) { - struct td *td; - ed->hwINFO = ED_SKIP; /* dummy td; end of td list for ed */ td = td_alloc (ohci, SLAB_ATOMIC); if (!td) { /* out of memory */ + ed_free (ohci, ed); ed = 0; goto done; } ed->dummy = td; ed->hwTailP = cpu_to_le32 (td->td_dma); ed->hwHeadP = ed->hwTailP; /* ED_C, ED_H zeroed */ - ed->state = ED_UNLINK; + ed->state = ED_IDLE; ed->type = type; } @@ -439,7 +431,7 @@ * state/mode info. Currently the upper layers don't support such * guarantees; we're lucky changing config/altsetting is rare. */ - if (ed->state == ED_UNLINK) { + if (ed->state == ED_IDLE) { u32 info; info = usb_pipedevice (pipe); @@ -494,30 +486,13 @@ /*-------------------------------------------------------------------------*/ /* request unlinking of an endpoint from an operational HC. - * put the ep on the rm_list and stop the bulk or ctrl list + * put the ep on the rm_list * real work is done at the next start frame (SF) hardware interrupt */ static void start_urb_unlink (struct ohci_hcd *ohci, struct ed *ed) { - /* already pending? */ - if (ed->state & ED_URB_DEL) - return; - ed->state |= ED_URB_DEL; - - ed->hwINFO |= ED_SKIP; - - switch (ed->type) { - case PIPE_CONTROL: /* stop control list */ - ohci->hc_control &= ~OHCI_CTRL_CLE; - writel (ohci->hc_control, - &ohci->regs->control); - break; - case PIPE_BULK: /* stop bulk list */ - ohci->hc_control &= ~OHCI_CTRL_BLE; - writel (ohci->hc_control, - &ohci->regs->control); - break; - } + ed_deschedule (ohci, ed); + ed->state = ED_UNLINK; /* SF interrupt might get delayed; record the frame counter value that * indicates when the HC isn't looking at it, so concurrent unlinks @@ -526,7 +501,7 @@ */ ed->tick = le16_to_cpu (ohci->hcca->frame_no) + 1; - ed->ed_rm_list = ohci->ed_rm_list; + ed->ed_next = ohci->ed_rm_list; ohci->ed_rm_list = ed; /* enable SOF interrupt */ @@ -744,13 +719,15 @@ u32 tdINFO = le32_to_cpup (&td->hwINFO); int cc = 0; - /* ISO ... drivers see per-TD length/status */ if (tdINFO & TD_ISO) { u16 tdPSW = le16_to_cpu (td->hwPSW [0]); int dlen = 0; cc = (tdPSW >> 12) & 0xF; + if (cc >= 0x0E) /* hc didn't touch? */ + return; + if (usb_pipeout (urb->pipe)) dlen = urb->iso_frame_desc [td->index].length; else @@ -759,9 +736,11 @@ urb->iso_frame_desc [td->index].actual_length = dlen; urb->iso_frame_desc [td->index].status = cc_to_error [cc]; - if (cc != 0) +#ifdef VERBOSE_DEBUG + if (cc != TD_CC_NOERROR) dbg (" urb %p iso TD %p (%d) len %d CC %d", urb, td, 1 + td->index, dlen, cc); +#endif /* BULK, INT, CONTROL ... drivers see aggregate length/status, * except that "setup" bytes aren't counted and "short" transfers @@ -783,7 +762,7 @@ if (cc == TD_DATAUNDERRUN && !(urb->transfer_flags & URB_SHORT_NOT_OK)) cc = TD_CC_NOERROR; - if (cc != TD_CC_NOERROR) { + if (cc != TD_CC_NOERROR && cc < 0x0E) { spin_lock (&urb->lock); if (urb->status == -EINPROGRESS) urb->status = cc_to_error [cc]; @@ -801,7 +780,7 @@ } #ifdef VERBOSE_DEBUG - if (cc != 0) + if (cc != TD_CC_NOERROR && cc < 0x0E) dbg (" urb %p TD %p (%d) CC %d, len=%d/%d", urb, td, 1 + td->index, cc, urb->actual_length, @@ -876,28 +855,39 @@ static void finish_unlinks (struct ohci_hcd *ohci, u16 tick) { struct ed *ed, **last; - int ctrl = 0, bulk = 0; +rescan_all: for (last = &ohci->ed_rm_list, ed = *last; ed != NULL; ed = *last) { struct td *td, *td_next, *tdHeadP, *tdTailP; u32 *td_p; - int unlinked; + int completed, modified; /* only take off EDs that the HC isn't using, accounting for - * frame counter wraps. completion callbacks might prepend - * EDs to the list, they'll be checked next irq. + * frame counter wraps. */ - if (tick_before (tick, ed->tick)) { - last = &ed->ed_rm_list; + if (tick_before (tick, ed->tick) && !ohci->disabled) { + last = &ed->ed_next; continue; } - *last = ed->ed_rm_list; - ed->ed_rm_list = 0; - unlinked = 0; - /* unlink urbs from first one requested to queue end; - * leave earlier urbs alone + /* reentrancy: if we drop the schedule lock, someone might + * have modified this list. normally it's just prepending + * entries (which we'd ignore), but paranoia won't hurt. + */ + *last = ed->ed_next; + ed->ed_next = 0; + modified = 0; + + /* unlink urbs as requested, but rescan the list after + * we call a completion since it might have unlinked + * another (earlier) urb + * + * FIXME use td_list to scan, not ed hashtables. + * completely abolish ed hashtables! */ +rescan_this: + completed = 0; + tdTailP = dma_to_td (ohci, le32_to_cpup (&ed->hwTailP)); tdHeadP = dma_to_td (ohci, le32_to_cpup (&ed->hwHeadP)); td_p = &ed->hwHeadP; @@ -908,21 +898,18 @@ td_next = dma_to_td (ohci, le32_to_cpup (&td->hwNextTD)); - if (unlinked || (urb_priv->state == URB_DEL)) { - u32 tdINFO = le32_to_cpup (&td->hwINFO); - - unlinked = 1; + if (urb_priv->state == URB_DEL) { /* HC may have partly processed this TD */ - if (TD_CC_GET (tdINFO) < 0xE) - td_done (urb, td); + td_done (urb, td); + urb_priv->td_cnt++; + *td_p = td->hwNextTD | (*td_p & __constant_cpu_to_le32 (0x3)); /* URB is done; clean up */ - if (++ (urb_priv->td_cnt) == urb_priv->length) { - if (urb->status == -EINPROGRESS) - urb->status = -ECONNRESET; + if (urb_priv->td_cnt == urb_priv->length) { + modified = completed = 1; spin_unlock (&ohci->lock); finish_urb (ohci, urb); spin_lock (&ohci->lock); @@ -932,49 +919,52 @@ } } - /* FIXME actually want four cases here: - * (a) finishing URB unlink - * [a1] no URBs queued, so start ED unlink - * [a2] some (earlier) URBs still linked, re-enable - * (b) finishing ED unlink - * [b1] no URBs queued, ED is truly idle now - * ... we could set state ED_NEW and free dummy - * [b2] URBs now queued, link ED back into schedule - * right now we only have (a) - */ - ed->state &= ~ED_URB_DEL; - tdHeadP = dma_to_td (ohci, le32_to_cpup (&ed->hwHeadP)); + /* ED's now officially unlinked, hc doesn't see */ + ed->state = ED_IDLE; + ed->hwINFO &= ~ED_SKIP; + ed->hwHeadP &= ~cpu_to_le32 (ED_H); + ed->hwNextED = 0; - if (tdHeadP == tdTailP) { - if (ed->state == ED_OPER) - start_ed_unlink (ohci, ed); - } else - ed->hwINFO &= ~ED_SKIP; - - switch (ed->type) { - case PIPE_CONTROL: - ctrl = 1; - break; - case PIPE_BULK: - bulk = 1; - break; + /* but if there's work queued, reschedule */ + tdHeadP = dma_to_td (ohci, le32_to_cpup (&ed->hwHeadP)); + if (tdHeadP != tdTailP) { + if (completed) + goto rescan_this; + if (!ohci->disabled && !ohci->sleeping) + ed_schedule (ohci, ed); } + + if (modified) + goto rescan_all; } /* maybe reenable control and bulk lists */ - if (!ohci->disabled) { - if (ctrl) /* reset control list */ - writel (0, &ohci->regs->ed_controlcurrent); - if (bulk) /* reset bulk list */ - writel (0, &ohci->regs->ed_bulkcurrent); - if (!ohci->ed_rm_list) { - if (ohci->ed_controltail) - ohci->hc_control |= OHCI_CTRL_CLE; - if (ohci->ed_bulktail) - ohci->hc_control |= OHCI_CTRL_BLE; - writel (ohci->hc_control, &ohci->regs->control); - } - } + if (!ohci->disabled && !ohci->ed_rm_list) { + u32 command = 0, control = 0; + + if (ohci->ed_controltail) { + command |= OHCI_CLF; + if (!(ohci->hc_control & OHCI_CTRL_CLE)) { + control |= OHCI_CTRL_CLE; + writel (0, &ohci->regs->ed_controlcurrent); + } + } + if (ohci->ed_bulktail) { + command |= OHCI_BLF; + if (!(ohci->hc_control & OHCI_CTRL_BLE)) { + control |= OHCI_CTRL_BLE; + writel (0, &ohci->regs->ed_bulkcurrent); + } + } + + /* CLE/BLE to enable, CLF/BLF to (maybe) kickstart */ + if (control) { + ohci->hc_control |= control; + writel (ohci->hc_control, &ohci->regs->control); + } + if (command) + writel (command, &ohci->regs->cmdstatus); + } } @@ -1026,7 +1016,7 @@ if ((ed->hwHeadP & __constant_cpu_to_le32 (TD_MASK)) == ed->hwTailP && (ed->state == ED_OPER)) - start_ed_unlink (ohci, ed); + ed_deschedule (ohci, ed); td = td_next; } spin_unlock_irqrestore (&ohci->lock, flags); diff -Nru a/drivers/usb/host/ohci.h b/drivers/usb/host/ohci.h --- a/drivers/usb/host/ohci.h Fri Jul 26 13:49:13 2002 +++ b/drivers/usb/host/ohci.h Fri Jul 26 13:49:13 2002 @@ -31,15 +31,21 @@ /* rest are purely for the driver's use */ dma_addr_t dma; /* addr of ED */ + struct td *dummy; /* next TD to activate */ + + /* host's view of schedule */ + struct ed *ed_next; /* on schedule or rm_list */ struct ed *ed_prev; /* for non-interrupt EDs */ - struct td *dummy; struct list_head td_list; /* "shadow list" of our TDs */ - u8 state; /* ED_{NEW,UNLINK,OPER} */ -#define ED_NEW 0x00 /* unused, no dummy td */ -#define ED_UNLINK 0x01 /* dummy td, maybe linked to hc */ -#define ED_OPER 0x02 /* dummy td, _is_ linked to hc */ -#define ED_URB_DEL 0x08 /* for unlinking; masked in */ + /* create --> IDLE --> OPER --> ... --> IDLE --> destroy + * usually: OPER --> UNLINK --> (IDLE | OPER) --> ... + * some special cases : OPER --> IDLE ... + */ + u8 state; /* ED_{IDLE,UNLINK,OPER} */ +#define ED_IDLE 0x00 /* NOT linked to HC */ +#define ED_UNLINK 0x01 /* being unlinked from hc */ +#define ED_OPER 0x02 /* IS linked to hc */ u8 type; /* PIPE_{BULK,...} */ u16 interval; /* interrupt, isochronous */ @@ -53,7 +59,6 @@ /* HC may see EDs on rm_list until next frame (frame_no == tick) */ u16 tick; - struct ed *ed_rm_list; } __attribute__ ((aligned(16))); #define ED_MASK ((u32)~0x0f) /* strip hw status in low addr bits */