Implement basic support for EHCI interrupt pipes. This is unlikely
to be particularly correct or optimal, but it seems to be enough to allow the attachment of USB2 hubs and USB2 devices connected via USB2 hubs. None of the split transaction support is implemented in our USB stack, so USB1 peripherals will definitely not work when connected via USB2 hubs.
This commit is contained in:
parent
228a86bf7d
commit
2dacb7cc04
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/head/; revision=132964
@ -156,7 +156,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;
|
||||
@ -245,6 +247,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 *);
|
||||
@ -422,6 +427,8 @@ ehci_init(ehci_softc_t *sc)
|
||||
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;
|
||||
@ -434,6 +441,49 @@ 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.
|
||||
* XXX this probably isn't the most sensible arrangement, and it
|
||||
* would be better if we didn't leave all the QHs in the periodic
|
||||
* schedule all the time.
|
||||
*/
|
||||
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 / 4; 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) {
|
||||
@ -474,7 +524,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 */
|
||||
@ -1248,7 +1298,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));
|
||||
@ -1295,9 +1345,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 */
|
||||
@ -1330,7 +1380,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);
|
||||
@ -2843,11 +2896,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. */
|
||||
islot = EHCI_IQHIDX(lev, arc4random());
|
||||
|
||||
sqh->islot = islot;
|
||||
isp = &sc->sc_islots[sqh->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, MS_TO_TICKS(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,("ehci_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,
|
||||
MS_TO_TICKS(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
|
||||
}
|
||||
|
||||
/************************/
|
||||
|
||||
|
@ -53,6 +53,7 @@ typedef struct ehci_soft_qh {
|
||||
struct ehci_soft_qh *next;
|
||||
struct ehci_soft_qtd *sqtd;
|
||||
ehci_physaddr_t physaddr;
|
||||
int islot; /* Interrupt list slot. */
|
||||
} 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)
|
||||
@ -69,6 +70,19 @@ 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
|
||||
@ -99,8 +113,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;
|
||||
|
||||
struct ehci_soft_islot sc_islots[EHCI_INTRQHS];
|
||||
|
||||
LIST_HEAD(, ehci_xfer) sc_intrhead;
|
||||
|
||||
ehci_soft_qh_t *sc_freeqhs;
|
||||
|
Loading…
Reference in New Issue
Block a user