diff options
author | Christopher Pascoe <pascoe@cvs.openbsd.org> | 2006-08-14 00:41:12 +0000 |
---|---|---|
committer | Christopher Pascoe <pascoe@cvs.openbsd.org> | 2006-08-14 00:41:12 +0000 |
commit | 451757c926b61f58afdaa3b9a0b607d17ff8b01b (patch) | |
tree | 1ccd9d41e275c497921bbd425f9d288c1cb54d11 /sys/dev/usb | |
parent | ec523fd888e112e001a5c3907b9829daac5fa0c2 (diff) |
Pull in abort path code from FreeBSD. Without this, the hardware data toggle
code doesn't work correctly when errored transactions occur.
ok dlg@
Diffstat (limited to 'sys/dev/usb')
-rw-r--r-- | sys/dev/usb/ehci.c | 179 | ||||
-rw-r--r-- | sys/dev/usb/ehcivar.h | 9 |
2 files changed, 142 insertions, 46 deletions
diff --git a/sys/dev/usb/ehci.c b/sys/dev/usb/ehci.c index 1b348656298..e8192805b6f 100644 --- a/sys/dev/usb/ehci.c +++ b/sys/dev/usb/ehci.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ehci.c,v 1.63 2006/08/14 00:28:07 pascoe Exp $ */ +/* $OpenBSD: ehci.c,v 1.64 2006/08/14 00:41:11 pascoe Exp $ */ /* $NetBSD: ehci.c,v 1.66 2004/06/30 03:11:56 mycroft Exp $ */ /* @@ -456,7 +456,8 @@ ehci_init(ehci_softc_t *sc) sqh->qh.qh_link = htole32(sqh->physaddr | EHCI_LINK_QH); sqh->qh.qh_curqtd = EHCI_NULL; - sqh->next = NULL; + sqh->prev = sqh; /*It's a circular list.. */ + sqh->next = sqh; /* Fill the overlay qTD */ sqh->qh.qh_qtd.qtd_next = EHCI_NULL; sqh->qh.qh_qtd.qtd_altnext = EHCI_NULL; @@ -735,6 +736,7 @@ ehci_check_intr(ehci_softc_t *sc, struct ehci_xfer *ex) done: DPRINTFN(12, ("ehci_check_intr: ex=%p done\n", ex)); usb_uncallout(ex->xfer.timeout_handle, ehci_timeout, ex); + usb_rem_task(ex->xfer.pipe->device, &ex->abort_task); ehci_idone(ex); } @@ -1120,6 +1122,9 @@ ehci_allocx(struct usbd_bus *bus) if (xfer != NULL) { memset(xfer, 0, sizeof(struct ehci_xfer)); + usb_init_task(&EXFER(xfer)->abort_task, ehci_timeout_task, + xfer); + EXFER(xfer)->ehci_xfer_flags = 0; #ifdef DIAGNOSTIC EXFER(xfer)->isdone = 1; xfer->busy_free = XFER_BUSY; @@ -1434,6 +1439,8 @@ bad0: /* * Add an ED to the schedule. Called at splusb(). + * If in the async schedule, it will always have a next. + * If in the intr schedule it may not. */ void ehci_add_qh(ehci_soft_qh_t *sqh, ehci_soft_qh_t *head) @@ -1441,8 +1448,11 @@ ehci_add_qh(ehci_soft_qh_t *sqh, ehci_soft_qh_t *head) SPLUSBCHECK; sqh->next = head->next; + sqh->prev = head; sqh->qh.qh_link = head->qh.qh_link; head->next = sqh; + if (sqh->next) + sqh->next->prev = sqh; head->qh.qh_link = htole32(sqh->physaddr | EHCI_LINK_QH); #ifdef EHCI_DEBUG @@ -1455,21 +1465,17 @@ ehci_add_qh(ehci_soft_qh_t *sqh, ehci_soft_qh_t *head) /* * Remove an ED from the schedule. Called at splusb(). + * Will always have a 'next' if it's in the async list as it's circular. */ void ehci_rem_qh(ehci_softc_t *sc, ehci_soft_qh_t *sqh, ehci_soft_qh_t *head) { - ehci_soft_qh_t *p; - SPLUSBCHECK; /* XXX */ - for (p = head; p != NULL && p->next != sqh; p = p->next) - ; - if (p == NULL) - panic("ehci_rem_qh: ED not found"); - p->next = sqh->next; - p->qh.qh_link = sqh->qh.qh_link; - + sqh->prev->qh.qh_link = sqh->qh.qh_link; + sqh->prev->next = sqh->next; + if (sqh->next) + sqh->next->prev = sqh->prev; ehci_sync_hc(sc); } @@ -2152,6 +2158,7 @@ ehci_alloc_sqh(ehci_softc_t *sc) sc->sc_freeqhs = sqh->next; memset(&sqh->qh, 0, sizeof(ehci_qh_t)); sqh->next = NULL; + sqh->prev = NULL; return (sqh); } @@ -2401,7 +2408,6 @@ ehci_close_pipe(usbd_pipe_handle pipe, ehci_soft_qh_t *head) * have happened since the hardware runs concurrently. * If the transaction has already happened we rely on the ordinary * interrupt processing to process it. - * XXX This is most probably wrong. */ void ehci_abort_xfer(usbd_xfer_handle xfer, usbd_status status) @@ -2410,11 +2416,11 @@ ehci_abort_xfer(usbd_xfer_handle xfer, usbd_status status) struct ehci_pipe *epipe = (struct ehci_pipe *)xfer->pipe; ehci_softc_t *sc = (ehci_softc_t *)epipe->pipe.device->bus; ehci_soft_qh_t *sqh = epipe->sqh; - ehci_soft_qtd_t *sqtd; - ehci_physaddr_t cur; - u_int32_t qhstatus; + ehci_soft_qtd_t *sqtd, *snext, **psqtd; + ehci_physaddr_t cur, us, next; int s; int hit; + ehci_soft_qh_t *psqh; DPRINTF(("ehci_abort_xfer: xfer=%p pipe=%p\n", xfer, epipe)); @@ -2423,6 +2429,7 @@ ehci_abort_xfer(usbd_xfer_handle xfer, usbd_status status) s = splusb(); xfer->status = status; /* make software ignore it */ usb_uncallout(xfer->timeout_handle, ehci_timeout, xfer); + usb_rem_task(epipe->pipe.device, &exfer->abort_task); usb_transfer_complete(xfer); splx(s); return; @@ -2432,26 +2439,50 @@ ehci_abort_xfer(usbd_xfer_handle xfer, usbd_status status) panic("ehci_abort_xfer: not in process context"); /* + * If an abort is already in progress then just wait for it to + * complete and return. + */ + if (exfer->ehci_xfer_flags & EHCI_XFER_ABORTING) { + DPRINTFN(2, ("ehci_abort_xfer: already aborting\n")); + /* No need to wait if we're aborting from a timeout. */ + if (status == USBD_TIMEOUT) + return; + /* Override the status which might be USBD_TIMEOUT. */ + xfer->status = status; + DPRINTFN(2, ("ehci_abort_xfer: waiting for abort to finish\n")); + exfer->ehci_xfer_flags |= EHCI_XFER_ABORTWAIT; + while (exfer->ehci_xfer_flags & EHCI_XFER_ABORTING) + tsleep(&exfer->ehci_xfer_flags, PZERO, "ehciaw", 0); + return; + } + + /* * Step 1: Make interrupt routine and hardware ignore xfer. */ s = splusb(); + exfer->ehci_xfer_flags |= EHCI_XFER_ABORTING; xfer->status = status; /* make software ignore it */ usb_uncallout(xfer->timeout_handle, ehci_timeout, xfer); - qhstatus = sqh->qh.qh_qtd.qtd_status; - sqh->qh.qh_qtd.qtd_status = qhstatus | htole32(EHCI_QTD_HALTED); - for (sqtd = exfer->sqtdstart; ; sqtd = sqtd->nextqtd) { - sqtd->qtd.qtd_status |= htole32(EHCI_QTD_HALTED); - if (sqtd == exfer->sqtdend) - break; - } + usb_rem_task(epipe->pipe.device, &exfer->abort_task); splx(s); /* * Step 2: Wait until we know hardware has finished any possible + * use of the xfer. We do this by removing the entire + * queue from the async schedule and waiting for the doorbell. + * Nothing else should be touching the queue now. + */ + psqh = sqh->prev; + ehci_rem_qh(sc, sqh, psqh); + + /* + * Step 3: make sure the soft interrupt routine + * has run. This should remove any completed items off the queue. + * The hardware has no reference to completed items (TDs). + * It's safe to remove them at any time. * use of the xfer. Also make sure the soft interrupt routine * has run. */ - ehci_sync_hc(sc); s = splusb(); #ifdef USB_USE_SOFTINTR sc->sc_softwake = 1; @@ -2460,39 +2491,100 @@ ehci_abort_xfer(usbd_xfer_handle xfer, usbd_status status) #ifdef USB_USE_SOFTINTR tsleep(&sc->sc_softwake, PZERO, "ehciab", 0); #endif /* USB_USE_SOFTINTR */ - splx(s); /* - * Step 3: Remove any vestiges of the xfer from the hardware. + * Step 4: Remove any vestiges of the xfer from the hardware. * The complication here is that the hardware may have executed - * beyond the xfer we're trying to abort. So as we're scanning - * the TDs of this xfer we check if the hardware points to - * any of them. + * into or even beyond the xfer we're trying to abort. + * So as we're scanning the TDs of this xfer we check if + * the hardware points to any of them. + * + * first we need to see if there are any transfers + * on this queue before the xfer we are aborting.. we need + * to update any pointers that point to us to point past + * the aborting xfer. (If there is something past us). + * Hardware and software. */ - s = splusb(); /* XXX why? */ cur = EHCI_LINK_ADDR(le32toh(sqh->qh.qh_curqtd)); hit = 0; - for (sqtd = exfer->sqtdstart; ; sqtd = sqtd->nextqtd) { - hit |= cur == sqtd->physaddr; - if (sqtd == exfer->sqtdend) - break; - } - sqtd = sqtd->nextqtd; - /* Zap curqtd register if hardware pointed inside the xfer. */ - if (hit && sqtd != NULL) { - DPRINTFN(1,("ehci_abort_xfer: cur=0x%08x\n", sqtd->physaddr)); - sqh->qh.qh_curqtd = htole32(sqtd->physaddr); /* unlink qTDs */ - sqh->qh.qh_qtd.qtd_status = qhstatus; - } else { - DPRINTFN(1,("ehci_abort_xfer: no hit\n")); - } + /* If they initially point here. */ + us = exfer->sqtdstart->physaddr; + + /* We will change them to point here */ + snext = exfer->sqtdend->nextqtd; + next = snext ? snext->physaddr : htole32(EHCI_NULL); + + /* + * Now loop through any qTDs before us and keep track of the pointer + * that points to us for the end. + */ + psqtd = &sqh->sqtd; + sqtd = sqh->sqtd; + while (sqtd && sqtd != exfer->sqtdstart) { + hit |= (cur == sqtd->physaddr); + if (EHCI_LINK_ADDR(le32toh(sqtd->qtd.qtd_next)) == us) + sqtd->qtd.qtd_next = next; + if (EHCI_LINK_ADDR(le32toh(sqtd->qtd.qtd_altnext)) == us) + sqtd->qtd.qtd_altnext = next; + psqtd = &sqtd->nextqtd; + sqtd = sqtd->nextqtd; + } + /* make the software pointer bypass us too */ + *psqtd = exfer->sqtdend->nextqtd; + + /* + * If we already saw the active one then we are pretty much done. + * We've done all the relinking we need to do. + */ + if (!hit) { + + /* + * Now reinitialise the QH to point to the next qTD + * (if there is one). We only need to do this if + * it was previously pointing to us. + * XXX Not quite sure what to do about the data toggle. + */ + sqtd = exfer->sqtdstart; + for (sqtd = exfer->sqtdstart; ; sqtd = sqtd->nextqtd) { + if (cur == sqtd->physaddr) { + hit++; + } + if (sqtd == exfer->sqtdend) + break; + } + sqtd = sqtd->nextqtd; + /* + * Only need to alter the QH if it was pointing at a qTD + * that we are removing. + */ + if (hit) { + if (snext) { + ehci_set_qh_qtd(sqh, snext); + } else { + + sqh->qh.qh_curqtd = 0; /* unlink qTDs */ + sqh->qh.qh_qtd.qtd_status = 0; + sqh->qh.qh_qtd.qtd_next = + sqh->qh.qh_qtd.qtd_altnext + = htole32(EHCI_NULL); + DPRINTFN(1,("ehci_abort_xfer: no hit\n")); + } + } + } + ehci_add_qh(sqh, psqh); /* * Step 4: Execute callback. */ #ifdef DIAGNOSTIC exfer->isdone = 1; #endif + /* Do the wakeup first to avoid touching the xfer after the callback. */ + exfer->ehci_xfer_flags &= ~EHCI_XFER_ABORTING; + if (exfer->ehci_xfer_flags & EHCI_XFER_ABORTWAIT) { + exfer->ehci_xfer_flags &= ~EHCI_XFER_ABORTWAIT; + wakeup(&exfer->ehci_xfer_flags); + } usb_transfer_complete(xfer); splx(s); @@ -2518,7 +2610,6 @@ ehci_timeout(void *addr) } /* Execute the abort in a process context. */ - usb_init_task(&exfer->abort_task, ehci_timeout_task, addr); usb_add_task(exfer->xfer.pipe->device, &exfer->abort_task); } diff --git a/sys/dev/usb/ehcivar.h b/sys/dev/usb/ehcivar.h index 145e9e85bcc..cdc8df4fff6 100644 --- a/sys/dev/usb/ehcivar.h +++ b/sys/dev/usb/ehcivar.h @@ -1,5 +1,5 @@ -/* $OpenBSD: ehcivar.h,v 1.9 2005/04/11 08:09:32 dlg Exp $ */ -/* $NetBSD: ehcivar.h,v 1.12 2001/12/31 12:16:57 augustss Exp $ */ +/* $OpenBSD: ehcivar.h,v 1.10 2006/08/14 00:41:11 pascoe Exp $ */ +/* $NetBSD: ehcivar.h,v 1.19 2005/04/29 15:04:29 augustss Exp $ */ /* * Copyright (c) 2001 The NetBSD Foundation, Inc. @@ -51,6 +51,7 @@ typedef struct ehci_soft_qtd { typedef struct ehci_soft_qh { ehci_qh_t qh; struct ehci_soft_qh *next; + struct ehci_soft_qh *prev; struct ehci_soft_qtd *sqtd; ehci_physaddr_t physaddr; int islot; @@ -64,10 +65,14 @@ struct ehci_xfer { LIST_ENTRY(ehci_xfer) inext; /* list of active xfers */ ehci_soft_qtd_t *sqtdstart; ehci_soft_qtd_t *sqtdend; + u_int32_t ehci_xfer_flags; #ifdef DIAGNOSTIC int isdone; #endif }; +#define EHCI_XFER_ABORTING 0x0001 /* xfer is aborting. */ +#define EHCI_XFER_ABORTWAIT 0x0002 /* abort completion is being awaited. */ + #define EXFER(xfer) ((struct ehci_xfer *)(xfer)) /* Information about an entry in the interrupt list. */ |