summaryrefslogtreecommitdiffstats
path: root/sys
diff options
context:
space:
mode:
authorjmg <jmg@FreeBSD.org>2003-07-15 23:12:54 +0000
committerjmg <jmg@FreeBSD.org>2003-07-15 23:12:54 +0000
commit32a9aeec73a3c286b3d0e84c1c16ee65b0db3704 (patch)
tree82afe6d165a48c847ccca6c7287b312cf8c433d9 /sys
parentd9b36adf9db6e55ce3c1c5daac171320822b286b (diff)
downloadFreeBSD-src-32a9aeec73a3c286b3d0e84c1c16ee65b0db3704.zip
FreeBSD-src-32a9aeec73a3c286b3d0e84c1c16ee65b0db3704.tar.gz
fix support for umass and related devices on ohci. This is a partial
sync of the NetBSD code. fix isochornous support for ohci. This gets webcams like my OV511 working on sparc64. PR: kern/52589 Submitted by: Bruce R. Montague (isochonous support) Reviewed by: joe among others
Diffstat (limited to 'sys')
-rw-r--r--sys/dev/usb/ohci.c222
-rw-r--r--sys/dev/usb/ohcireg.h2
-rw-r--r--sys/dev/usb/ohcivar.h6
3 files changed, 151 insertions, 79 deletions
diff --git a/sys/dev/usb/ohci.c b/sys/dev/usb/ohci.c
index 7af5dc0..413d143 100644
--- a/sys/dev/usb/ohci.c
+++ b/sys/dev/usb/ohci.c
@@ -267,6 +267,7 @@ Static u_int8_t revbits[OHCI_NO_INTRS] =
struct ohci_pipe {
struct usbd_pipe pipe;
ohci_soft_ed_t *sed;
+ u_int32_t aborting;
union {
ohci_soft_td_t *td;
ohci_soft_itd_t *itd;
@@ -985,6 +986,14 @@ void
ohci_freex(struct usbd_bus *bus, usbd_xfer_handle xfer)
{
struct ohci_softc *sc = (struct ohci_softc *)bus;
+ struct ohci_xfer *oxfer = (struct ohci_xfer *)xfer;
+ ohci_soft_itd_t *sitd;
+
+ if (oxfer->ohci_xfer_flags & OHCI_ISOC_DIRTY) {
+ for (sitd = xfer->hcpriv; sitd != NULL && sitd->xfer == xfer;
+ sitd = sitd->nextitd)
+ ohci_free_sitd(sc, sitd);
+ }
#ifdef DIAGNOSTIC
if (xfer->busy_free != XFER_BUSY) {
@@ -1338,7 +1347,6 @@ ohci_softintr(void *v)
usbd_xfer_handle xfer;
struct ohci_pipe *opipe;
int len, cc, s;
- int i, j, actlen, iframes, uedir;
DPRINTFN(10,("ohci_softintr: enter\n"));
@@ -1453,6 +1461,7 @@ ohci_softintr(void *v)
for (sitd = sidone; sitd != NULL; sitd = sitdnext) {
xfer = sitd->xfer;
sitdnext = sitd->dnext;
+ sitd->flags |= OHCI_ITD_INTFIN;
DPRINTFN(1, ("ohci_process_done: sitd=%p xfer=%p hcpriv=%p\n",
sitd, xfer, xfer ? xfer->hcpriv : 0));
if (xfer == NULL)
@@ -1464,52 +1473,33 @@ ohci_softintr(void *v)
/* Handled by abort routine. */
continue;
}
+ if (xfer->pipe)
+ if (xfer->pipe->aborting)
+ continue; /*Ignore.*/
#ifdef DIAGNOSTIC
if (sitd->isdone)
printf("ohci_softintr: sitd=%p is done\n", sitd);
sitd->isdone = 1;
#endif
- if (sitd->flags & OHCI_CALL_DONE) {
- ohci_soft_itd_t *next;
-
- opipe = (struct ohci_pipe *)xfer->pipe;
- opipe->u.iso.inuse -= xfer->nframes;
- uedir = UE_GET_DIR(xfer->pipe->endpoint->edesc->
- bEndpointAddress);
- xfer->status = USBD_NORMAL_COMPLETION;
- actlen = 0;
- for (i = 0, sitd = xfer->hcpriv;;
- sitd = next) {
- next = sitd->nextitd;
- if (OHCI_ITD_GET_CC(le32toh(sitd->
- itd.itd_flags)) != OHCI_CC_NO_ERROR)
- xfer->status = USBD_IOERROR;
- /* For input, update frlengths with actual */
- /* XXX anything necessary for output? */
- if (uedir == UE_DIR_IN &&
- xfer->status == USBD_NORMAL_COMPLETION) {
- iframes = OHCI_ITD_GET_FC(le32toh(
- sitd->itd.itd_flags));
- for (j = 0; j < iframes; i++, j++) {
- len = le16toh(sitd->
- itd.itd_offset[j]);
- len =
- (OHCI_ITD_PSW_GET_CC(len) ==
- OHCI_CC_NOT_ACCESSED) ? 0 :
- OHCI_ITD_PSW_LENGTH(len);
- xfer->frlengths[i] = len;
- actlen += len;
- }
- }
- if (sitd->flags & OHCI_CALL_DONE)
- break;
- ohci_free_sitd(sc, sitd);
+ struct ohci_pipe *opipe = (struct ohci_pipe *)xfer->pipe;
+ if (opipe->aborting)
+ continue;
+
+ cc = OHCI_ITD_GET_CC(le32toh(sitd->itd.itd_flags));
+ if (cc == OHCI_CC_NO_ERROR) {
+ /* XXX compute length for input */
+ if (sitd->flags & OHCI_CALL_DONE) {
+ opipe->u.iso.inuse -= xfer->nframes;
+ /* XXX update frlengths with actual length */
+ /* XXX xfer->actlen = actlen; */
+ xfer->status = USBD_NORMAL_COMPLETION;
+ s = splusb();
+ usb_transfer_complete(xfer);
+ splx(s);
}
- ohci_free_sitd(sc, sitd);
- if (uedir == UE_DIR_IN &&
- xfer->status == USBD_NORMAL_COMPLETION)
- xfer->actlen = actlen;
-
+ } else {
+ /* XXX Do more */
+ xfer->status = USBD_IOERROR;
s = splusb();
usb_transfer_complete(xfer);
splx(s);
@@ -1704,7 +1694,6 @@ ohci_device_request(usbd_xfer_handle xfer)
usbd_device_handle dev = opipe->pipe.device;
ohci_softc_t *sc = (ohci_softc_t *)dev->bus;
int addr = dev->address;
- ohci_soft_td_t *data = 0;
ohci_soft_td_t *setup, *stat, *next, *tail;
ohci_soft_ed_t *sed;
int isread;
@@ -1745,31 +1734,20 @@ ohci_device_request(usbd_xfer_handle xfer)
OHCI_ED_SET_FA(addr) |
OHCI_ED_SET_MAXP(UGETW(opipe->pipe.endpoint->edesc->wMaxPacketSize)));
+ next = stat;
+
/* Set up data transaction */
if (len != 0) {
- data = ohci_alloc_std(sc);
- if (data == NULL) {
- err = USBD_NOMEM;
- goto bad3;
- }
- data->td.td_flags = htole32(
- (isread ? OHCI_TD_IN : OHCI_TD_OUT) | OHCI_TD_NOCC |
- OHCI_TD_TOGGLE_1 | OHCI_TD_NOINTR |
- (xfer->flags & USBD_SHORT_XFER_OK ? OHCI_TD_R : 0));
- data->td.td_cbp = htole32(DMAADDR(&xfer->dmabuf, 0));
- data->nexttd = stat;
- data->td.td_nexttd = htole32(stat->physaddr);
- data->td.td_be = htole32(le32toh(data->td.td_cbp) + len - 1);
- data->len = len;
- data->xfer = xfer;
- data->flags = OHCI_ADD_LEN;
+ ohci_soft_td_t *std = stat;
- next = data;
- stat->flags = OHCI_CALL_DONE;
- } else {
- next = stat;
- /* XXX ADD_LEN? */
- stat->flags = OHCI_CALL_DONE | OHCI_ADD_LEN;
+ err = ohci_alloc_std_chain(opipe, sc, len, isread, xfer,
+ std, &stat);
+ stat = stat->nexttd; /* point at free TD */
+ if (err)
+ goto bad3;
+ /* Start toggle at 1 and then use the carried toggle. */
+ std->td.td_flags &= htole32(~OHCI_TD_TOGGLE_MASK);
+ std->td.td_flags |= htole32(OHCI_TD_TOGGLE_1);
}
memcpy(KERNADDR(&opipe->u.ctl.reqdma, 0), req, sizeof *req);
@@ -1792,6 +1770,7 @@ ohci_device_request(usbd_xfer_handle xfer)
stat->nexttd = tail;
stat->td.td_nexttd = htole32(tail->physaddr);
stat->td.td_be = 0;
+ stat->flags = OHCI_CALL_DONE;
stat->len = 0;
stat->xfer = xfer;
@@ -2126,6 +2105,7 @@ ohci_open(usbd_pipe_handle pipe)
if (sitd == NULL)
goto bad1;
opipe->tail.itd = sitd;
+ opipe->aborting = 0;
tdphys = sitd->physaddr;
fmt = OHCI_ED_FORMAT_ISO;
if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN)
@@ -2953,6 +2933,11 @@ ohci_device_bulk_start(usbd_xfer_handle xfer)
data = opipe->tail.td;
err = ohci_alloc_std_chain(opipe, sc, len, isread, xfer,
data, &tail);
+ /* We want interrupt at the end of the transfer. */
+ tail->td.td_flags &= htole32(~OHCI_TD_INTR_MASK);
+ tail->td.td_flags |= htole32(OHCI_TD_SET_DI(1));
+ tail->flags |= OHCI_CALL_DONE;
+ tail = tail->nexttd; /* point at sentinel */
if (err)
return (err);
@@ -3270,8 +3255,9 @@ ohci_device_isoc_enter(usbd_xfer_handle xfer)
ohci_softc_t *sc = (ohci_softc_t *)dev->bus;
ohci_soft_ed_t *sed = opipe->sed;
struct iso *iso = &opipe->u.iso;
+ struct ohci_xfer *oxfer = (struct ohci_xfer *)xfer;
ohci_soft_itd_t *sitd, *nsitd;
- ohci_physaddr_t buf, offs, noffs, bp0;
+ ohci_physaddr_t buf, offs, noffs, bp0, tdphys;
int i, ncur, nframes;
int s;
@@ -3289,6 +3275,24 @@ ohci_device_isoc_enter(usbd_xfer_handle xfer)
iso->next));
}
+ if (xfer->hcpriv) {
+ for (sitd = xfer->hcpriv; sitd != NULL && sitd->xfer == xfer;
+ sitd = sitd->nextitd)
+ ohci_free_sitd(sc, sitd); /* Free ITDs in prev xfer*/
+
+ if (sitd == NULL) {
+ sitd = ohci_alloc_sitd(sc);
+ if (sitd == NULL)
+ panic("cant alloc isoc");
+ opipe->tail.itd = sitd;
+ tdphys = sitd->physaddr;
+ sed->ed.ed_flags |= htole32(OHCI_ED_SKIP); /* Stop*/
+ sed->ed.ed_headp =
+ sed->ed.ed_tailp = htole32(tdphys);
+ sed->ed.ed_flags &= htole32(~OHCI_ED_SKIP); /* Start.*/
+ }
+ }
+
sitd = opipe->tail.itd;
buf = DMAADDR(&xfer->dmabuf, 0);
bp0 = OHCI_PAGE(buf);
@@ -3320,7 +3324,7 @@ ohci_device_isoc_enter(usbd_xfer_handle xfer)
sitd->itd.itd_nextitd = htole32(nsitd->physaddr);
sitd->itd.itd_be = htole32(bp0 + offs - 1);
sitd->xfer = xfer;
- sitd->flags = 0;
+ sitd->flags = OHCI_ITD_ACTIVE;
sitd = nsitd;
iso->next = iso->next + ncur;
@@ -3348,7 +3352,7 @@ ohci_device_isoc_enter(usbd_xfer_handle xfer)
sitd->itd.itd_nextitd = htole32(nsitd->physaddr);
sitd->itd.itd_be = htole32(bp0 + offs - 1);
sitd->xfer = xfer;
- sitd->flags = OHCI_CALL_DONE;
+ sitd->flags = OHCI_CALL_DONE | OHCI_ITD_ACTIVE;
iso->next = iso->next + ncur;
iso->inuse += nframes;
@@ -3357,6 +3361,8 @@ ohci_device_isoc_enter(usbd_xfer_handle xfer)
xfer->status = USBD_IN_PROGRESS;
+ oxfer->ohci_xfer_flags |= OHCI_ISOC_DIRTY;
+
#ifdef USB_DEBUG
if (ohcidebug > 5) {
DPRINTF(("ohci_device_isoc_enter: frame=%d\n",
@@ -3388,6 +3394,8 @@ ohci_device_isoc_start(usbd_xfer_handle xfer)
{
struct ohci_pipe *opipe = (struct ohci_pipe *)xfer->pipe;
ohci_softc_t *sc = (ohci_softc_t *)opipe->pipe.device->bus;
+ ohci_soft_ed_t *sed;
+ int s;
DPRINTFN(5,("ohci_device_isoc_start: xfer=%p\n", xfer));
@@ -3401,6 +3409,11 @@ ohci_device_isoc_start(usbd_xfer_handle xfer)
/* XXX anything to do? */
+ s = splusb();
+ sed = opipe->sed; /* Turn off ED skip-bit to start processing */
+ sed->ed.ed_flags &= htole32(~OHCI_ED_SKIP); /* ED's ITD list.*/
+ splx(s);
+
return (USBD_IN_PROGRESS);
}
@@ -3410,10 +3423,11 @@ ohci_device_isoc_abort(usbd_xfer_handle xfer)
struct ohci_pipe *opipe = (struct ohci_pipe *)xfer->pipe;
ohci_softc_t *sc = (ohci_softc_t *)opipe->pipe.device->bus;
ohci_soft_ed_t *sed;
- ohci_soft_itd_t *sitd;
- int s;
+ ohci_soft_itd_t *sitd, *tmp_sitd;
+ int s,undone,num_sitds;
s = splusb();
+ opipe->aborting = 1;
DPRINTFN(1,("ohci_device_isoc_abort: xfer=%p\n", xfer));
@@ -3431,6 +3445,7 @@ ohci_device_isoc_abort(usbd_xfer_handle xfer)
sed = opipe->sed;
sed->ed.ed_flags |= htole32(OHCI_ED_SKIP); /* force hardware skip */
+ num_sitds = 0;
sitd = xfer->hcpriv;
#ifdef DIAGNOSTIC
if (sitd == NULL) {
@@ -3439,7 +3454,8 @@ ohci_device_isoc_abort(usbd_xfer_handle xfer)
return;
}
#endif
- for (; sitd->xfer == xfer; sitd = sitd->nextitd) {
+ for (; sitd != NULL && sitd->xfer == xfer; sitd = sitd->nextitd) {
+ num_sitds++;
#ifdef DIAGNOSTIC
DPRINTFN(1,("abort sets done sitd=%p\n", sitd));
sitd->isdone = 1;
@@ -3448,14 +3464,43 @@ ohci_device_isoc_abort(usbd_xfer_handle xfer)
splx(s);
- usb_delay_ms(&sc->sc_bus, OHCI_ITD_NOFFSET);
+ /*
+ * Each sitd has up to OHCI_ITD_NOFFSET transfers, each can
+ * take a usb 1ms cycle. Conservatively wait for it to drain.
+ * Even with DMA done, it can take awhile for the "batch"
+ * delivery of completion interrupts to occur thru the controller.
+ */
+
+ do {
+ usb_delay_ms(&sc->sc_bus, 2*(num_sitds*OHCI_ITD_NOFFSET));
+
+ undone = 0;
+ tmp_sitd = xfer->hcpriv;
+ for (; tmp_sitd != NULL && tmp_sitd->xfer == xfer;
+ tmp_sitd = tmp_sitd->nextitd) {
+ if (OHCI_CC_NO_ERROR ==
+ OHCI_ITD_GET_CC(le32toh(tmp_sitd->itd.itd_flags)) &&
+ tmp_sitd->flags & OHCI_ITD_ACTIVE &&
+ (tmp_sitd->flags & OHCI_ITD_INTFIN) == 0)
+ undone++;
+ }
+ } while( undone != 0 );
+
s = splusb();
/* Run callback. */
usb_transfer_complete(xfer);
- sed->ed.ed_headp = htole32(sitd->physaddr); /* unlink TDs */
+ if (sitd != NULL)
+ /*
+ * Only if there is a `next' sitd in next xfer...
+ * unlink this xfer's sitds.
+ */
+ sed->ed.ed_headp = htole32(sitd->physaddr);
+ else
+ sed->ed.ed_headp = 0;
+
sed->ed.ed_flags &= htole32(~OHCI_ED_SKIP); /* remove hardware skip */
splx(s);
@@ -3464,10 +3509,23 @@ ohci_device_isoc_abort(usbd_xfer_handle xfer)
void
ohci_device_isoc_done(usbd_xfer_handle xfer)
{
-
- DPRINTFN(1,("ohci_device_isoc_done: xfer=%p\n", xfer));
-
- xfer->hcpriv = NULL;
+ /* This null routine corresponds to non-isoc "done()" routines
+ * that free the stds associated with an xfer after a completed
+ * xfer interrupt. However, in the case of isoc transfers, the
+ * sitds associated with the transfer have already been processed
+ * and reallocated for the next iteration by
+ * "ohci_device_isoc_transfer()".
+ *
+ * Routine "usb_transfer_complete()" is called at the end of every
+ * relevant usb interrupt. "usb_transfer_complete()" indirectly
+ * calls 1) "ohci_device_isoc_transfer()" (which keeps pumping the
+ * pipeline by setting up the next transfer iteration) and 2) then
+ * calls "ohci_device_isoc_done()". Isoc transfers have not been
+ * working for the ohci usb because this routine was trashing the
+ * xfer set up for the next iteration (thus, only the first
+ * UGEN_NISOREQS xfers outstanding on an open would work). Perhaps
+ * this could all be re-factored, but that's another pass...
+ */
}
usbd_status
@@ -3493,11 +3551,19 @@ ohci_device_isoc_close(usbd_pipe_handle pipe)
{
struct ohci_pipe *opipe = (struct ohci_pipe *)pipe;
ohci_softc_t *sc = (ohci_softc_t *)pipe->device->bus;
+ ohci_soft_ed_t *sed;
DPRINTF(("ohci_device_isoc_close: pipe=%p\n", pipe));
- ohci_close_pipe(pipe, sc->sc_isoc_head);
+
+ sed = opipe->sed;
+ sed->ed.ed_flags |= htole32(OHCI_ED_SKIP); /* Stop device. */
+
+ ohci_close_pipe(pipe, sc->sc_isoc_head); /* Stop isoc list, free ED.*/
+
+ /* up to NISOREQs xfers still outstanding. */
+
#ifdef DIAGNOSTIC
opipe->tail.itd->isdone = 1;
#endif
- ohci_free_sitd(sc, opipe->tail.itd);
+ ohci_free_sitd(sc, opipe->tail.itd); /* Next `avail free' sitd.*/
}
diff --git a/sys/dev/usb/ohcireg.h b/sys/dev/usb/ohcireg.h
index 8f8b922..9d647ba 100644
--- a/sys/dev/usb/ohcireg.h
+++ b/sys/dev/usb/ohcireg.h
@@ -187,9 +187,11 @@ typedef struct {
#define OHCI_TD_GET_DI(x) (((x) >> 21) & 7) /* Delay Interrupt */
#define OHCI_TD_SET_DI(x) ((x) << 21)
#define OHCI_TD_NOINTR 0x00e00000
+#define OHCI_TD_INTR_MASK 0x00e00000
#define OHCI_TD_TOGGLE_CARRY 0x00000000
#define OHCI_TD_TOGGLE_0 0x02000000
#define OHCI_TD_TOGGLE_1 0x03000000
+#define OHCI_TD_TOGGLE_MASK 0x03000000
#define OHCI_TD_GET_EC(x) (((x) >> 26) & 3) /* Error Count */
#define OHCI_TD_GET_CC(x) ((x) >> 28) /* Condition Code */
#define OHCI_TD_NOCC 0xf0000000
diff --git a/sys/dev/usb/ohcivar.h b/sys/dev/usb/ohcivar.h
index 6b50122..445c346 100644
--- a/sys/dev/usb/ohcivar.h
+++ b/sys/dev/usb/ohcivar.h
@@ -70,6 +70,8 @@ typedef struct ohci_soft_itd {
LIST_ENTRY(ohci_soft_itd) hnext;
usbd_xfer_handle xfer;
u_int16_t flags;
+#define OHCI_ITD_ACTIVE 0x0010 /* Hardware op in progress */
+#define OHCI_ITD_INTFIN 0x0020 /* Hw completion interrupt seen.*/
#ifdef DIAGNOSTIC
char isdone;
#endif
@@ -149,9 +151,11 @@ typedef struct ohci_softc {
struct ohci_xfer {
struct usbd_xfer xfer;
struct usb_task abort_task;
+ u_int32_t ohci_xfer_flags;
};
+#define OHCI_ISOC_DIRTY 0x01
-#define OXFER(xfer) ((struct ehci_xfer *)(xfer))
+#define OXFER(xfer) ((struct ohci_xfer *)(xfer))
usbd_status ohci_init(ohci_softc_t *);
int ohci_intr(void *);
OpenPOWER on IntegriCloud