Add interrupt transfers. From OpenBSD.

Having interrupt transfers enables some more device types to be used,
most notably USB2 hubs.  USB2 hubs still cannot handle anything but
USB2 devices, because there is node code to handle hub Transaction Translation
yet.
XXX This code doesn't do good bw scheduling, but it's certainly better than
nothing. :)
This commit is contained in:
augustss 2004-10-22 10:38:17 +00:00
parent 6f9df049df
commit 2c051a4b85
3 changed files with 301 additions and 23 deletions

View File

@ -1,4 +1,4 @@
/* $NetBSD: ehci.c,v 1.77 2004/10/22 09:58:00 augustss Exp $ */
/* $NetBSD: ehci.c,v 1.78 2004/10/22 10:38:17 augustss Exp $ */
/*
* Copyright (c) 2004 The NetBSD Foundation, Inc.
@ -65,7 +65,7 @@
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: ehci.c,v 1.77 2004/10/22 09:58:00 augustss Exp $");
__KERNEL_RCSID(0, "$NetBSD: ehci.c,v 1.78 2004/10/22 10:38:17 augustss Exp $");
#include "ohci.h"
#include "uhci.h"
@ -120,7 +120,9 @@ struct ehci_pipe {
/*ehci_soft_qtd_t *setup, *data, *stat;*/
} ctl;
/* Interrupt pipe */
/* XXX */
struct {
u_int length;
} intr;
/* Bulk pipe */
struct {
u_int length;
@ -207,6 +209,9 @@ Static void ehci_free_sqtd_chain(ehci_softc_t *, ehci_soft_qtd_t *,
Static usbd_status ehci_device_request(usbd_xfer_handle xfer);
Static usbd_status ehci_device_setintr(ehci_softc_t *, ehci_soft_qh_t *,
int ival);
Static void ehci_add_qh(ehci_soft_qh_t *, ehci_soft_qh_t *);
Static void ehci_rem_qh(ehci_softc_t *, ehci_soft_qh_t *,
ehci_soft_qh_t *);
@ -376,18 +381,23 @@ ehci_init(ehci_softc_t *sc)
return (USBD_IOERROR);
}
/* XXX need proper intr scheduling */
sc->sc_rand = 96;
/* frame list size at default, read back what we got and use that */
switch (EHCI_CMD_FLS(EOREAD4(sc, EHCI_USBCMD))) {
case 0: sc->sc_flsize = 1024*4; break;
case 1: sc->sc_flsize = 512*4; break;
case 2: sc->sc_flsize = 256*4; break;
case 0: sc->sc_flsize = 1024; break;
case 1: sc->sc_flsize = 512; break;
case 2: sc->sc_flsize = 256; break;
case 3: return (USBD_IOERROR);
}
err = usb_allocmem(&sc->sc_bus, sc->sc_flsize,
EHCI_FLALIGN_ALIGN, &sc->sc_fldma);
err = usb_allocmem(&sc->sc_bus, sc->sc_flsize * sizeof(ehci_link_t),
EHCI_FLALIGN_ALIGN, &sc->sc_fldma);
if (err)
return (err);
DPRINTF(("%s: flsize=%d\n", USBDEVNAME(sc->sc_bus.bdev),sc->sc_flsize));
sc->sc_flist = KERNADDR(&sc->sc_fldma, 0);
EOWRITE4(sc, EHCI_PERIODICLISTBASE, DMAADDR(&sc->sc_fldma, 0));
/* Set up the bus struct. */
sc->sc_bus.methods = &ehci_bus_methods;
@ -398,6 +408,46 @@ ehci_init(ehci_softc_t *sc)
sc->sc_eintrs = EHCI_NORMAL_INTRS;
/*
* Allocate the interrupt dummy QHs. These are arranged to give poll
* intervals that are powers of 2 times 1ms.
*/
for (i = 0; i < EHCI_INTRQHS; i++) {
sqh = ehci_alloc_sqh(sc);
if (sqh == NULL) {
err = USBD_NOMEM;
goto bad1;
}
sc->sc_islots[i].sqh = sqh;
}
for (i = 0; i < EHCI_INTRQHS; i++) {
sqh = sc->sc_islots[i].sqh;
if (i == 0) {
/* The last (1ms) QH terminates. */
sqh->qh.qh_link = EHCI_NULL;
sqh->next = NULL;
} else {
/* Otherwise the next QH has half the poll interval */
sqh->next = sc->sc_islots[(i + 1) / 2 - 1].sqh;
sqh->qh.qh_link = htole32(sqh->next->physaddr |
EHCI_LINK_QH);
}
sqh->qh.qh_endp = htole32(EHCI_QH_SET_EPS(EHCI_QH_SPEED_HIGH));
sqh->qh.qh_link = EHCI_NULL;
sqh->qh.qh_curqtd = EHCI_NULL;
sqh->next = NULL;
sqh->qh.qh_qtd.qtd_next = EHCI_NULL;
sqh->qh.qh_qtd.qtd_altnext = EHCI_NULL;
sqh->qh.qh_qtd.qtd_status = htole32(EHCI_QTD_HALTED);
sqh->sqtd = NULL;
}
/* Point the frame list at the last level (128ms). */
for (i = 0; i < sc->sc_flsize; i++) {
sc->sc_flist[i] = htole32(EHCI_LINK_QH |
sc->sc_islots[EHCI_IQHIDX(EHCI_IPOLLRATES - 1,
i)].sqh->physaddr);
}
/* Allocate dummy QH that starts the async list. */
sqh = ehci_alloc_sqh(sc);
if (sqh == NULL) {
@ -438,7 +488,7 @@ ehci_init(ehci_softc_t *sc)
EHCI_CMD_ITC_8 | /* 8 microframes */
(EOREAD4(sc, EHCI_USBCMD) & EHCI_CMD_FLS_M) |
EHCI_CMD_ASE |
/* EHCI_CMD_PSE | */
EHCI_CMD_PSE |
EHCI_CMD_RS);
/* Take over port ownership */
@ -476,6 +526,10 @@ ehci_intr(void *v)
/* If we get an interrupt while polling, then just ignore it. */
if (sc->sc_bus.use_polling) {
u_int32_t intrs = EHCI_STS_INTRS(EOREAD4(sc, EHCI_USBSTS));
if (intrs)
EOWRITE4(sc, EHCI_USBSTS, intrs); /* Acknowledge */
#ifdef DIAGNOSTIC
DPRINTFN(16, ("ehci_intr: ignored interrupt while polling\n"));
#endif
@ -763,7 +817,7 @@ ehci_idone(struct ehci_xfer *ex)
"\20\7HALTED\6BUFERR\5BABBLE\4XACTERR"
"\3MISSED", sbuf, sizeof(sbuf));
DPRINTFN((status == EHCI_QTD_HALTED)*/*10*/2,
DPRINTFN((status & EHCI_QTD_HALTED) ? 2 : 0,
("ehci_idone: error, addr=%d, endpt=0x%02x, "
"status 0x%s\n",
xfer->pipe->device->address,
@ -1236,7 +1290,7 @@ ehci_open(usbd_pipe_handle pipe)
ehci_soft_qh_t *sqh;
usbd_status err;
int s;
int speed, naks;
int ival, speed, naks;
DPRINTFN(1, ("ehci_open: pipe=%p, addr=%d, endpt=%d (%d)\n",
pipe, addr, ed->bEndpointAddress, sc->sc_addr));
@ -1283,9 +1337,9 @@ ehci_open(usbd_pipe_handle pipe)
EHCI_QH_SET_NRL(naks)
);
sqh->qh.qh_endphub = htole32(
EHCI_QH_SET_MULT(1)
EHCI_QH_SET_MULT(1) |
/* XXX TT stuff */
/* XXX interrupt mask */
EHCI_QH_SET_SMASK(xfertype == UE_INTERRUPT ? 0x01 : 0)
);
sqh->qh.qh_curqtd = EHCI_NULL;
/* Fill the overlay qTD */
@ -1318,7 +1372,10 @@ ehci_open(usbd_pipe_handle pipe)
break;
case UE_INTERRUPT:
pipe->methods = &ehci_device_intr_methods;
return (USBD_INVAL);
ival = pipe->interval;
if (ival == USBD_DEFAULT_INTERVAL)
ival = ed->bInterval;
return (ehci_device_setintr(sc, sqh, ival));
case UE_ISOCHRONOUS:
pipe->methods = &ehci_device_isoc_methods;
return (USBD_INVAL);
@ -1766,7 +1823,7 @@ ehci_root_ctrl_start(usbd_xfer_handle xfer)
v = EOREAD4(sc, EHCI_HCSPARAMS);
USETW(hubd.wHubCharacteristics,
EHCI_HCS_PPC(v) ? UHD_PWR_INDIVIDUAL : UHD_PWR_NO_SWITCH |
EHCI_HCS_P_INCICATOR(EREAD4(sc, EHCI_HCSPARAMS))
EHCI_HCS_P_INDICATOR(EREAD4(sc, EHCI_HCSPARAMS))
? UHD_PORT_IND : 0);
hubd.bPwrOn2PwrGood = 200; /* XXX can't find out? */
for (i = 0, l = sc->sc_noport; l > 0; i++, l -= 8, v >>= 8)
@ -1960,6 +2017,7 @@ ehci_root_ctrl_close(usbd_pipe_handle pipe)
void
ehci_root_intr_done(usbd_xfer_handle xfer)
{
xfer->hcpriv = NULL;
}
Static usbd_status
@ -2020,6 +2078,7 @@ ehci_root_intr_close(usbd_pipe_handle pipe)
void
ehci_root_ctrl_done(usbd_xfer_handle xfer)
{
xfer->hcpriv = NULL;
}
/************************/
@ -2807,11 +2866,213 @@ ehci_device_bulk_done(usbd_xfer_handle xfer)
/************************/
Static usbd_status ehci_device_intr_transfer(usbd_xfer_handle xfer) { return USBD_IOERROR; }
Static usbd_status ehci_device_intr_start(usbd_xfer_handle xfer) { return USBD_IOERROR; }
Static void ehci_device_intr_abort(usbd_xfer_handle xfer) { }
Static void ehci_device_intr_close(usbd_pipe_handle pipe) { }
Static void ehci_device_intr_done(usbd_xfer_handle xfer) { }
Static usbd_status
ehci_device_setintr(ehci_softc_t *sc, ehci_soft_qh_t *sqh, int ival)
{
struct ehci_soft_islot *isp;
int islot, lev;
/* Find a poll rate that is large enough. */
for (lev = EHCI_IPOLLRATES - 1; lev > 0; lev--)
if (EHCI_ILEV_IVAL(lev) <= ival)
break;
/* Pick an interrupt slot at the right level. */
/* XXX could do better than picking at random */
sc->sc_rand = (sc->sc_rand + 191) % sc->sc_flsize;
islot = EHCI_IQHIDX(lev, sc->sc_rand);
sqh->islot = islot;
isp = &sc->sc_islots[islot];
ehci_add_qh(sqh, isp->sqh);
return (USBD_NORMAL_COMPLETION);
}
Static usbd_status
ehci_device_intr_transfer(usbd_xfer_handle xfer)
{
usbd_status err;
/* Insert last in queue. */
err = usb_insert_transfer(xfer);
if (err)
return (err);
/*
* Pipe isn't running (otherwise err would be USBD_INPROG),
* so start it first.
*/
return (ehci_device_intr_start(SIMPLEQ_FIRST(&xfer->pipe->queue)));
}
Static usbd_status
ehci_device_intr_start(usbd_xfer_handle xfer)
{
#define exfer EXFER(xfer)
struct ehci_pipe *epipe = (struct ehci_pipe *)xfer->pipe;
usbd_device_handle dev = xfer->pipe->device;
ehci_softc_t *sc = (ehci_softc_t *)dev->bus;
ehci_soft_qtd_t *data, *dataend;
ehci_soft_qh_t *sqh;
usbd_status err;
int len, isread, endpt;
int s;
DPRINTFN(2, ("ehci_device_intr_start: xfer=%p len=%d flags=%d\n",
xfer, xfer->length, xfer->flags));
if (sc->sc_dying)
return (USBD_IOERROR);
#ifdef DIAGNOSTIC
if (xfer->rqflags & URQ_REQUEST)
panic("ehci_device_intr_start: a request");
#endif
len = xfer->length;
endpt = epipe->pipe.endpoint->edesc->bEndpointAddress;
isread = UE_GET_DIR(endpt) == UE_DIR_IN;
sqh = epipe->sqh;
epipe->u.intr.length = len;
err = ehci_alloc_sqtd_chain(epipe, sc, len, isread, xfer, &data,
&dataend);
if (err) {
DPRINTFN(-1, ("ehci_device_intr_start: no memory\n"));
xfer->status = err;
usb_transfer_complete(xfer);
return (err);
}
#ifdef EHCI_DEBUG
if (ehcidebug > 5) {
DPRINTF(("ehci_device_intr_start: data(1)\n"));
ehci_dump_sqh(sqh);
ehci_dump_sqtds(data);
}
#endif
/* Set up interrupt info. */
exfer->sqtdstart = data;
exfer->sqtdend = dataend;
#ifdef DIAGNOSTIC
if (!exfer->isdone) {
printf("ehci_device_intr_start: not done, ex=%p\n", exfer);
}
exfer->isdone = 0;
#endif
s = splusb();
ehci_set_qh_qtd(sqh, data);
if (xfer->timeout && !sc->sc_bus.use_polling) {
usb_callout(xfer->timeout_handle, mstohz(xfer->timeout),
ehci_timeout, xfer);
}
ehci_add_intr_list(sc, exfer);
xfer->status = USBD_IN_PROGRESS;
splx(s);
#ifdef EHCI_DEBUG
if (ehcidebug > 10) {
DPRINTF(("ehci_device_intr_start: data(2)\n"));
delay(10000);
DPRINTF(("ehci_device_intr_start: data(3)\n"));
ehci_dump_regs(sc);
printf("sqh:\n");
ehci_dump_sqh(sqh);
ehci_dump_sqtds(data);
}
#endif
if (sc->sc_bus.use_polling)
ehci_waitintr(sc, xfer);
return (USBD_IN_PROGRESS);
#undef exfer
}
Static void
ehci_device_intr_abort(usbd_xfer_handle xfer)
{
DPRINTFN(1, ("ehci_device_intr_abort: xfer=%p\n", xfer));
if (xfer->pipe->intrxfer == xfer) {
DPRINTFN(1, ("echi_device_intr_abort: remove\n"));
xfer->pipe->intrxfer = NULL;
}
ehci_abort_xfer(xfer, USBD_CANCELLED);
}
Static void
ehci_device_intr_close(usbd_pipe_handle pipe)
{
ehci_softc_t *sc = (ehci_softc_t *)pipe->device->bus;
struct ehci_pipe *epipe = (struct ehci_pipe *)pipe;
struct ehci_soft_islot *isp;
isp = &sc->sc_islots[epipe->sqh->islot];
ehci_close_pipe(pipe, isp->sqh);
}
Static void
ehci_device_intr_done(usbd_xfer_handle xfer)
{
#define exfer EXFER(xfer)
struct ehci_xfer *ex = EXFER(xfer);
ehci_softc_t *sc = (ehci_softc_t *)xfer->pipe->device->bus;
struct ehci_pipe *epipe = (struct ehci_pipe *)xfer->pipe;
ehci_soft_qtd_t *data, *dataend;
ehci_soft_qh_t *sqh;
usbd_status err;
int len, isread, endpt, s;
DPRINTFN(10, ("ehci_device_intr_done: xfer=%p, actlen=%d\n",
xfer, xfer->actlen));
if (xfer->pipe->repeat) {
ehci_free_sqtd_chain(sc, ex->sqtdstart, NULL);
len = epipe->u.intr.length;
xfer->length = len;
endpt = epipe->pipe.endpoint->edesc->bEndpointAddress;
isread = UE_GET_DIR(endpt) == UE_DIR_IN;
sqh = epipe->sqh;
err = ehci_alloc_sqtd_chain(epipe, sc, len, isread, xfer,
&data, &dataend);
if (err) {
DPRINTFN(-1, ("ehci_device_intr_done: no memory\n"));
xfer->status = err;
return;
}
/* Set up interrupt info. */
exfer->sqtdstart = data;
exfer->sqtdend = dataend;
#ifdef DIAGNOSTIC
if (!exfer->isdone) {
printf("ehci_device_intr_done: not done, ex=%p\n",
exfer);
}
exfer->isdone = 0;
#endif
s = splusb();
ehci_set_qh_qtd(sqh, data);
if (xfer->timeout && !sc->sc_bus.use_polling) {
usb_callout(xfer->timeout_handle,
mstohz(xfer->timeout), ehci_timeout, xfer);
}
splx(s);
xfer->status = USBD_IN_PROGRESS;
} else if (xfer->status != USBD_NOMEM && ehci_active_intr_list(ex)) {
ehci_del_intr_list(ex); /* remove from active list */
ehci_free_sqtd_chain(sc, ex->sqtdstart, NULL);
}
#undef exfer
}
/************************/

View File

@ -1,4 +1,4 @@
/* $NetBSD: ehcireg.h,v 1.17 2004/06/23 06:45:56 mycroft Exp $ */
/* $NetBSD: ehcireg.h,v 1.18 2004/10/22 10:38:17 augustss Exp $ */
/*
* Copyright (c) 2001 The NetBSD Foundation, Inc.
@ -75,7 +75,7 @@
#define EHCI_HCSPARAMS 0x04 /* RO Structural parameters */
#define EHCI_HCS_DEBUGPORT(x) (((x) >> 20) & 0xf)
#define EHCI_HCS_P_INCICATOR(x) ((x) & 0x10000)
#define EHCI_HCS_P_INDICATOR(x) ((x) & 0x10000)
#define EHCI_HCS_N_CC(x) (((x) >> 12) & 0xf) /* # of companion ctlrs */
#define EHCI_HCS_N_PCC(x) (((x) >> 8) & 0xf) /* # of ports per comp. */
#define EHCI_HCS_PPC(x) ((x) & 0x10) /* port power control */

View File

@ -1,4 +1,4 @@
/* $NetBSD: ehcivar.h,v 1.14 2004/10/22 09:58:00 augustss Exp $ */
/* $NetBSD: ehcivar.h,v 1.15 2004/10/22 10:38:17 augustss Exp $ */
/*
* Copyright (c) 2001 The NetBSD Foundation, Inc.
@ -52,6 +52,7 @@ typedef struct ehci_soft_qh {
struct ehci_soft_qh *next;
struct ehci_soft_qtd *sqtd;
ehci_physaddr_t physaddr;
int islot;
} ehci_soft_qh_t;
#define EHCI_SQH_SIZE ((sizeof (struct ehci_soft_qh) + EHCI_QH_ALIGN - 1) / EHCI_QH_ALIGN * EHCI_QH_ALIGN)
#define EHCI_SQH_CHUNK (EHCI_PAGE_SIZE / EHCI_SQH_SIZE)
@ -68,6 +69,18 @@ struct ehci_xfer {
};
#define EXFER(xfer) ((struct ehci_xfer *)(xfer))
/* Information about an entry in the interrupt list. */
struct ehci_soft_islot {
ehci_soft_qh_t *sqh; /* Queue Head. */
};
#define EHCI_FRAMELIST_MAXCOUNT 1024
#define EHCI_IPOLLRATES 8 /* Poll rates (1ms, 2, 4, 8 .. 128) */
#define EHCI_INTRQHS ((1 << EHCI_IPOLLRATES) - 1)
#define EHCI_IQHIDX(lev, pos) \
((((pos) & ((1 << (lev)) - 1)) | (1 << (lev))) - 1)
#define EHCI_ILEV_IVAL(lev) (1 << (lev))
#define EHCI_HASH_SIZE 128
#define EHCI_COMPANION_MAX 8
@ -91,7 +104,11 @@ typedef struct ehci_softc {
struct usbd_bus *sc_comps[EHCI_COMPANION_MAX];
usb_dma_t sc_fldma;
ehci_link_t *sc_flist;
u_int sc_flsize;
u_int sc_rand; /* XXX need proper intr scheduling */
struct ehci_soft_islot sc_islots[EHCI_INTRQHS];
LIST_HEAD(, ehci_xfer) sc_intrhead;