summaryrefslogtreecommitdiff
path: root/sys/dev/usb
diff options
context:
space:
mode:
authorChristopher Pascoe <pascoe@cvs.openbsd.org>2006-08-14 00:41:12 +0000
committerChristopher Pascoe <pascoe@cvs.openbsd.org>2006-08-14 00:41:12 +0000
commit451757c926b61f58afdaa3b9a0b607d17ff8b01b (patch)
tree1ccd9d41e275c497921bbd425f9d288c1cb54d11 /sys/dev/usb
parentec523fd888e112e001a5c3907b9829daac5fa0c2 (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.c179
-rw-r--r--sys/dev/usb/ehcivar.h9
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. */