diff --git a/sys/dev/usb/ehci.c b/sys/dev/usb/ehci.c index c0363c8f414f..89db9531fe32 100644 --- a/sys/dev/usb/ehci.c +++ b/sys/dev/usb/ehci.c @@ -1,3377 +1,3373 @@ /* $NetBSD: ehci.c,v 1.91 2005/02/27 00:27:51 perry Exp $ */ /*- * Copyright (c) 2004 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) and by Charles M. Hannum. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * USB Enhanced Host Controller Driver, a.k.a. USB 2.0 controller. * * The EHCI 1.0 spec can be found at * http://developer.intel.com/technology/usb/download/ehci-r10.pdf * and the USB 2.0 spec at * http://www.usb.org/developers/docs/usb_20.zip * */ /* * TODO: * 1) The EHCI driver lacks support for isochronous transfers, so * devices using them don't work. * * 2) Interrupt transfer scheduling does not manage the time available * in each frame, so it is possible for the transfers to overrun * the end of the frame. * * 3) Command failures are not recovered correctly. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #if defined(DIAGNOSTIC) && defined(__i386__) && defined(__FreeBSD__) #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #define delay(d) DELAY(d) #ifdef USB_DEBUG #define EHCI_DEBUG USB_DEBUG #define DPRINTF(x) do { if (ehcidebug) logprintf x; } while (0) #define DPRINTFN(n,x) do { if (ehcidebug>(n)) logprintf x; } while (0) int ehcidebug = 0; SYSCTL_NODE(_hw_usb, OID_AUTO, ehci, CTLFLAG_RW, 0, "USB ehci"); SYSCTL_INT(_hw_usb_ehci, OID_AUTO, debug, CTLFLAG_RW, &ehcidebug, 0, "ehci debug level"); #define bitmask_snprintf(q,f,b,l) snprintf((b), (l), "%b", (q), (f)) #else #define DPRINTF(x) #define DPRINTFN(n,x) #endif struct ehci_pipe { struct usbd_pipe pipe; ehci_soft_qh_t *sqh; union { ehci_soft_qtd_t *qtd; /* ehci_soft_itd_t *itd; */ } tail; union { /* Control pipe */ struct { usb_dma_t reqdma; u_int length; /*ehci_soft_qtd_t *setup, *data, *stat;*/ } ctl; /* Interrupt pipe */ struct { u_int length; } intr; /* Bulk pipe */ struct { u_int length; } bulk; /* Iso pipe */ /* XXX */ } u; }; static usbd_status ehci_open(usbd_pipe_handle); static void ehci_poll(struct usbd_bus *); static void ehci_softintr(void *); static int ehci_intr1(ehci_softc_t *); static void ehci_waitintr(ehci_softc_t *, usbd_xfer_handle); static void ehci_check_intr(ehci_softc_t *, struct ehci_xfer *); static void ehci_idone(struct ehci_xfer *); static void ehci_timeout(void *); static void ehci_timeout_task(void *); static void ehci_intrlist_timeout(void *); static usbd_status ehci_allocm(struct usbd_bus *, usb_dma_t *, u_int32_t); static void ehci_freem(struct usbd_bus *, usb_dma_t *); static usbd_xfer_handle ehci_allocx(struct usbd_bus *); static void ehci_freex(struct usbd_bus *, usbd_xfer_handle); static usbd_status ehci_root_ctrl_transfer(usbd_xfer_handle); static usbd_status ehci_root_ctrl_start(usbd_xfer_handle); static void ehci_root_ctrl_abort(usbd_xfer_handle); static void ehci_root_ctrl_close(usbd_pipe_handle); static void ehci_root_ctrl_done(usbd_xfer_handle); static usbd_status ehci_root_intr_transfer(usbd_xfer_handle); static usbd_status ehci_root_intr_start(usbd_xfer_handle); static void ehci_root_intr_abort(usbd_xfer_handle); static void ehci_root_intr_close(usbd_pipe_handle); static void ehci_root_intr_done(usbd_xfer_handle); static usbd_status ehci_device_ctrl_transfer(usbd_xfer_handle); static usbd_status ehci_device_ctrl_start(usbd_xfer_handle); static void ehci_device_ctrl_abort(usbd_xfer_handle); static void ehci_device_ctrl_close(usbd_pipe_handle); static void ehci_device_ctrl_done(usbd_xfer_handle); static usbd_status ehci_device_bulk_transfer(usbd_xfer_handle); static usbd_status ehci_device_bulk_start(usbd_xfer_handle); static void ehci_device_bulk_abort(usbd_xfer_handle); static void ehci_device_bulk_close(usbd_pipe_handle); static void ehci_device_bulk_done(usbd_xfer_handle); static usbd_status ehci_device_intr_transfer(usbd_xfer_handle); static usbd_status ehci_device_intr_start(usbd_xfer_handle); static void ehci_device_intr_abort(usbd_xfer_handle); static void ehci_device_intr_close(usbd_pipe_handle); static void ehci_device_intr_done(usbd_xfer_handle); static usbd_status ehci_device_isoc_transfer(usbd_xfer_handle); static usbd_status ehci_device_isoc_start(usbd_xfer_handle); static void ehci_device_isoc_abort(usbd_xfer_handle); static void ehci_device_isoc_close(usbd_pipe_handle); static void ehci_device_isoc_done(usbd_xfer_handle); static void ehci_device_clear_toggle(usbd_pipe_handle pipe); static void ehci_noop(usbd_pipe_handle pipe); static int ehci_str(usb_string_descriptor_t *, int, char *); static void ehci_pcd(ehci_softc_t *, usbd_xfer_handle); static void ehci_pcd_able(ehci_softc_t *, int); static void ehci_pcd_enable(void *); static void ehci_disown(ehci_softc_t *, int, int); static ehci_soft_qh_t *ehci_alloc_sqh(ehci_softc_t *); static void ehci_free_sqh(ehci_softc_t *, ehci_soft_qh_t *); static ehci_soft_qtd_t *ehci_alloc_sqtd(ehci_softc_t *); static void ehci_free_sqtd(ehci_softc_t *, ehci_soft_qtd_t *); static usbd_status ehci_alloc_sqtd_chain(struct ehci_pipe *, ehci_softc_t *, int, int, usbd_xfer_handle, ehci_soft_qtd_t *, ehci_soft_qtd_t *, ehci_soft_qtd_t **, ehci_soft_qtd_t **); static void ehci_free_sqtd_chain(ehci_softc_t *, ehci_soft_qh_t *, ehci_soft_qtd_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 *); static void ehci_activate_qh(ehci_soft_qh_t *, ehci_soft_qtd_t *); static void ehci_sync_hc(ehci_softc_t *); static void ehci_close_pipe(usbd_pipe_handle, ehci_soft_qh_t *); static void ehci_abort_xfer(usbd_xfer_handle, usbd_status); #ifdef EHCI_DEBUG static void ehci_dump_regs(ehci_softc_t *); void ehci_dump(void); static ehci_softc_t *theehci; static void ehci_dump_link(ehci_link_t, int); static void ehci_dump_sqtds(ehci_soft_qtd_t *); static void ehci_dump_sqtd(ehci_soft_qtd_t *); static void ehci_dump_qtd(ehci_qtd_t *); static void ehci_dump_sqh(ehci_soft_qh_t *); #ifdef DIAGNOSTIC static void ehci_dump_exfer(struct ehci_xfer *); #endif #endif #define EHCI_NULL htole32(EHCI_LINK_TERMINATE) #define EHCI_INTR_ENDPT 1 #define ehci_add_intr_list(sc, ex) \ LIST_INSERT_HEAD(&(sc)->sc_intrhead, (ex), inext); #define ehci_del_intr_list(ex) \ do { \ LIST_REMOVE((ex), inext); \ (ex)->inext.le_prev = NULL; \ } while (0) #define ehci_active_intr_list(ex) ((ex)->inext.le_prev != NULL) static struct usbd_bus_methods ehci_bus_methods = { ehci_open, ehci_softintr, ehci_poll, ehci_allocm, ehci_freem, ehci_allocx, ehci_freex, }; static struct usbd_pipe_methods ehci_root_ctrl_methods = { ehci_root_ctrl_transfer, ehci_root_ctrl_start, ehci_root_ctrl_abort, ehci_root_ctrl_close, ehci_noop, ehci_root_ctrl_done, }; static struct usbd_pipe_methods ehci_root_intr_methods = { ehci_root_intr_transfer, ehci_root_intr_start, ehci_root_intr_abort, ehci_root_intr_close, ehci_noop, ehci_root_intr_done, }; static struct usbd_pipe_methods ehci_device_ctrl_methods = { ehci_device_ctrl_transfer, ehci_device_ctrl_start, ehci_device_ctrl_abort, ehci_device_ctrl_close, ehci_noop, ehci_device_ctrl_done, }; static struct usbd_pipe_methods ehci_device_intr_methods = { ehci_device_intr_transfer, ehci_device_intr_start, ehci_device_intr_abort, ehci_device_intr_close, ehci_device_clear_toggle, ehci_device_intr_done, }; static struct usbd_pipe_methods ehci_device_bulk_methods = { ehci_device_bulk_transfer, ehci_device_bulk_start, ehci_device_bulk_abort, ehci_device_bulk_close, ehci_device_clear_toggle, ehci_device_bulk_done, }; static struct usbd_pipe_methods ehci_device_isoc_methods = { ehci_device_isoc_transfer, ehci_device_isoc_start, ehci_device_isoc_abort, ehci_device_isoc_close, ehci_noop, ehci_device_isoc_done, }; usbd_status ehci_init(ehci_softc_t *sc) { u_int32_t version, sparams, cparams, hcr; u_int i; usbd_status err; ehci_soft_qh_t *sqh; u_int ncomp; int lev; DPRINTF(("ehci_init: start\n")); #ifdef EHCI_DEBUG theehci = sc; #endif sc->sc_offs = EREAD1(sc, EHCI_CAPLENGTH); version = EREAD2(sc, EHCI_HCIVERSION); printf("%s: EHCI version %x.%x\n", device_get_nameunit(sc->sc_bus.bdev), version >> 8, version & 0xff); sparams = EREAD4(sc, EHCI_HCSPARAMS); DPRINTF(("ehci_init: sparams=0x%x\n", sparams)); sc->sc_npcomp = EHCI_HCS_N_PCC(sparams); ncomp = EHCI_HCS_N_CC(sparams); if (ncomp != sc->sc_ncomp) { printf("%s: wrong number of companions (%d != %d)\n", device_get_nameunit(sc->sc_bus.bdev), ncomp, sc->sc_ncomp); if (ncomp < sc->sc_ncomp) sc->sc_ncomp = ncomp; } if (sc->sc_ncomp > 0) { printf("%s: companion controller%s, %d port%s each:", device_get_nameunit(sc->sc_bus.bdev), sc->sc_ncomp!=1 ? "s" : "", EHCI_HCS_N_PCC(sparams), EHCI_HCS_N_PCC(sparams)!=1 ? "s" : ""); for (i = 0; i < sc->sc_ncomp; i++) printf(" %s", device_get_nameunit(sc->sc_comps[i]->bdev)); printf("\n"); } sc->sc_noport = EHCI_HCS_N_PORTS(sparams); cparams = EREAD4(sc, EHCI_HCCPARAMS); DPRINTF(("ehci_init: cparams=0x%x\n", cparams)); if (EHCI_HCC_64BIT(cparams)) { /* MUST clear segment register if 64 bit capable. */ EWRITE4(sc, EHCI_CTRLDSSEGMENT, 0); } sc->sc_bus.usbrev = USBREV_2_0; /* Reset the controller */ DPRINTF(("%s: resetting\n", device_get_nameunit(sc->sc_bus.bdev))); EOWRITE4(sc, EHCI_USBCMD, 0); /* Halt controller */ usb_delay_ms(&sc->sc_bus, 1); EOWRITE4(sc, EHCI_USBCMD, EHCI_CMD_HCRESET); for (i = 0; i < 100; i++) { usb_delay_ms(&sc->sc_bus, 1); hcr = EOREAD4(sc, EHCI_USBCMD) & EHCI_CMD_HCRESET; if (!hcr) break; } if (hcr) { printf("%s: reset timeout\n", device_get_nameunit(sc->sc_bus.bdev)); return (USBD_IOERROR); } /* 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; 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 * sizeof(ehci_link_t), EHCI_FLALIGN_ALIGN, &sc->sc_fldma); if (err) return (err); DPRINTF(("%s: flsize=%d\n", device_get_nameunit(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; sc->sc_bus.pipe_size = sizeof(struct ehci_pipe); #if defined(__NetBSD__) || defined(__OpenBSD__) sc->sc_powerhook = powerhook_establish(ehci_power, sc); sc->sc_shutdownhook = shutdownhook_establish(ehci_shutdown, sc); #endif 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; } lev = 0; for (i = 0; i < EHCI_INTRQHS; i++) { if (i == EHCI_IQHIDX(lev + 1, 0)) lev++; 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[EHCI_IQHIDX(lev - 1, i + 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_endphub = htole32(EHCI_QH_SET_MULT(1)); sqh->qh.qh_curqtd = EHCI_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); } /* 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) { err = USBD_NOMEM; goto bad1; } /* Fill the QH */ sqh->qh.qh_endp = htole32(EHCI_QH_SET_EPS(EHCI_QH_SPEED_HIGH) | EHCI_QH_HRECL); sqh->qh.qh_link = htole32(sqh->physaddr | EHCI_LINK_QH); sqh->qh.qh_curqtd = EHCI_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; sqh->qh.qh_qtd.qtd_status = htole32(0); #ifdef EHCI_DEBUG if (ehcidebug) { ehci_dump_sqh(sqh); } #endif /* Point to async list */ sc->sc_async_head = sqh; EOWRITE4(sc, EHCI_ASYNCLISTADDR, sqh->physaddr | EHCI_LINK_QH); - usb_callout_init(sc->sc_tmo_pcd); - usb_callout_init(sc->sc_tmo_intrlist); + callout_init(&sc->sc_tmo_pcd, 0); + callout_init(&sc->sc_tmo_intrlist, 0); lockinit(&sc->sc_doorbell_lock, PZERO, "ehcidb", 0, 0); /* Enable interrupts */ EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs); /* Turn on controller */ EOWRITE4(sc, EHCI_USBCMD, EHCI_CMD_ITC_2 | /* 2 microframes interrupt delay */ (EOREAD4(sc, EHCI_USBCMD) & EHCI_CMD_FLS_M) | EHCI_CMD_ASE | EHCI_CMD_PSE | EHCI_CMD_RS); /* Take over port ownership */ EOWRITE4(sc, EHCI_CONFIGFLAG, EHCI_CONF_CF); for (i = 0; i < 100; i++) { usb_delay_ms(&sc->sc_bus, 1); hcr = EOREAD4(sc, EHCI_USBSTS) & EHCI_STS_HCH; if (!hcr) break; } if (hcr) { printf("%s: run timeout\n", device_get_nameunit(sc->sc_bus.bdev)); return (USBD_IOERROR); } return (USBD_NORMAL_COMPLETION); #if 0 bad2: ehci_free_sqh(sc, sc->sc_async_head); #endif bad1: usb_freemem(&sc->sc_bus, &sc->sc_fldma); return (err); } int ehci_intr(void *v) { ehci_softc_t *sc = v; if (sc == NULL || sc->sc_dying) return (0); /* 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 return (0); } return (ehci_intr1(sc)); } static int ehci_intr1(ehci_softc_t *sc) { u_int32_t intrs, eintrs; DPRINTFN(20,("ehci_intr1: enter\n")); /* In case the interrupt occurs before initialization has completed. */ if (sc == NULL) { #ifdef DIAGNOSTIC printf("ehci_intr1: sc == NULL\n"); #endif return (0); } intrs = EHCI_STS_INTRS(EOREAD4(sc, EHCI_USBSTS)); if (!intrs) return (0); eintrs = intrs & sc->sc_eintrs; DPRINTFN(7, ("ehci_intr1: sc=%p intrs=0x%x(0x%x) eintrs=0x%x\n", sc, (u_int)intrs, EOREAD4(sc, EHCI_USBSTS), (u_int)eintrs)); if (!eintrs) return (0); EOWRITE4(sc, EHCI_USBSTS, intrs); /* Acknowledge */ sc->sc_bus.intr_context++; sc->sc_bus.no_intrs++; if (eintrs & EHCI_STS_IAA) { DPRINTF(("ehci_intr1: door bell\n")); wakeup(&sc->sc_async_head); eintrs &= ~EHCI_STS_IAA; } if (eintrs & (EHCI_STS_INT | EHCI_STS_ERRINT)) { DPRINTFN(5,("ehci_intr1: %s %s\n", eintrs & EHCI_STS_INT ? "INT" : "", eintrs & EHCI_STS_ERRINT ? "ERRINT" : "")); usb_schedsoftintr(&sc->sc_bus); eintrs &= ~(EHCI_STS_INT | EHCI_STS_ERRINT); } if (eintrs & EHCI_STS_HSE) { printf("%s: unrecoverable error, controller halted\n", device_get_nameunit(sc->sc_bus.bdev)); /* XXX what else */ } if (eintrs & EHCI_STS_PCD) { ehci_pcd(sc, sc->sc_intrxfer); /* * Disable PCD interrupt for now, because it will be * on until the port has been reset. */ ehci_pcd_able(sc, 0); /* Do not allow RHSC interrupts > 1 per second */ - usb_callout(sc->sc_tmo_pcd, hz, ehci_pcd_enable, sc); + callout_reset(&sc->sc_tmo_pcd, hz, ehci_pcd_enable, sc); eintrs &= ~EHCI_STS_PCD; } sc->sc_bus.intr_context--; if (eintrs != 0) { /* Block unprocessed interrupts. */ sc->sc_eintrs &= ~eintrs; EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs); printf("%s: blocking intrs 0x%x\n", device_get_nameunit(sc->sc_bus.bdev), eintrs); } return (1); } void ehci_pcd_able(ehci_softc_t *sc, int on) { DPRINTFN(4, ("ehci_pcd_able: on=%d\n", on)); if (on) sc->sc_eintrs |= EHCI_STS_PCD; else sc->sc_eintrs &= ~EHCI_STS_PCD; EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs); } void ehci_pcd_enable(void *v_sc) { ehci_softc_t *sc = v_sc; ehci_pcd_able(sc, 1); } void ehci_pcd(ehci_softc_t *sc, usbd_xfer_handle xfer) { usbd_pipe_handle pipe; u_char *p; int i, m; if (xfer == NULL) { /* Just ignore the change. */ return; } pipe = xfer->pipe; p = xfer->buffer; m = min(sc->sc_noport, xfer->length * 8 - 1); memset(p, 0, xfer->length); for (i = 1; i <= m; i++) { /* Pick out CHANGE bits from the status reg. */ if (EOREAD4(sc, EHCI_PORTSC(i)) & EHCI_PS_CLEAR) p[i/8] |= 1 << (i%8); } DPRINTF(("ehci_pcd: change=0x%02x\n", *p)); xfer->actlen = xfer->length; xfer->status = USBD_NORMAL_COMPLETION; usb_transfer_complete(xfer); } void ehci_softintr(void *v) { ehci_softc_t *sc = v; struct ehci_xfer *ex, *nextex; DPRINTFN(10,("%s: ehci_softintr (%d)\n", device_get_nameunit(sc->sc_bus.bdev), sc->sc_bus.intr_context)); sc->sc_bus.intr_context++; /* * The only explanation I can think of for why EHCI is as brain dead * as UHCI interrupt-wise is that Intel was involved in both. * An interrupt just tells us that something is done, we have no * clue what, so we need to scan through all active transfers. :-( */ for (ex = LIST_FIRST(&sc->sc_intrhead); ex; ex = nextex) { nextex = LIST_NEXT(ex, inext); ehci_check_intr(sc, ex); } /* Schedule a callout to catch any dropped transactions. */ if ((sc->sc_flags & EHCI_SCFLG_LOSTINTRBUG) && !LIST_EMPTY(&sc->sc_intrhead)) - usb_callout(sc->sc_tmo_intrlist, hz / 5, ehci_intrlist_timeout, - sc); + callout_reset(&sc->sc_tmo_intrlist, hz / 5, + ehci_intrlist_timeout, sc); #ifdef USB_USE_SOFTINTR if (sc->sc_softwake) { sc->sc_softwake = 0; wakeup(&sc->sc_softwake); } #endif /* USB_USE_SOFTINTR */ sc->sc_bus.intr_context--; } /* Check for an interrupt. */ void ehci_check_intr(ehci_softc_t *sc, struct ehci_xfer *ex) { ehci_soft_qtd_t *sqtd, *lsqtd; u_int32_t status; DPRINTFN(/*15*/2, ("ehci_check_intr: ex=%p\n", ex)); if (ex->sqtdstart == NULL) { printf("ehci_check_intr: sqtdstart=NULL\n"); return; } lsqtd = ex->sqtdend; #ifdef DIAGNOSTIC if (lsqtd == NULL) { printf("ehci_check_intr: lsqtd==0\n"); return; } #endif /* * If the last TD is still active we need to check whether there * is a an error somewhere in the middle, or whether there was a * short packet (SPD and not ACTIVE). */ if (le32toh(lsqtd->qtd.qtd_status) & EHCI_QTD_ACTIVE) { DPRINTFN(12, ("ehci_check_intr: active ex=%p\n", ex)); for (sqtd = ex->sqtdstart; sqtd != lsqtd; sqtd=sqtd->nextqtd) { status = le32toh(sqtd->qtd.qtd_status); /* If there's an active QTD the xfer isn't done. */ if (status & EHCI_QTD_ACTIVE) break; /* Any kind of error makes the xfer done. */ if (status & EHCI_QTD_HALTED) goto done; /* We want short packets, and it is short: it's done */ if (EHCI_QTD_GET_BYTES(status) != 0) goto done; } DPRINTFN(12, ("ehci_check_intr: ex=%p std=%p still active\n", ex, ex->sqtdstart)); return; } done: DPRINTFN(12, ("ehci_check_intr: ex=%p done\n", ex)); - usb_uncallout(ex->xfer.timeout_handle, ehci_timeout, ex); + callout_stop(&ex->xfer.timeout_handle); usb_rem_task(ex->xfer.pipe->device, &ex->abort_task); ehci_idone(ex); } void ehci_idone(struct ehci_xfer *ex) { usbd_xfer_handle xfer = &ex->xfer; struct ehci_pipe *epipe = (struct ehci_pipe *)xfer->pipe; ehci_soft_qtd_t *sqtd, *lsqtd; u_int32_t status = 0, nstatus = 0; ehci_physaddr_t nextphys, altnextphys; int actlen, cerr; DPRINTFN(/*12*/2, ("ehci_idone: ex=%p\n", ex)); #ifdef DIAGNOSTIC { int s = splhigh(); if (ex->isdone) { splx(s); #ifdef EHCI_DEBUG printf("ehci_idone: ex is done!\n "); ehci_dump_exfer(ex); #else printf("ehci_idone: ex=%p is done!\n", ex); #endif return; } ex->isdone = 1; splx(s); } #endif if (xfer->status == USBD_CANCELLED || xfer->status == USBD_TIMEOUT) { DPRINTF(("ehci_idone: aborted xfer=%p\n", xfer)); return; } #ifdef EHCI_DEBUG DPRINTFN(/*10*/2, ("ehci_idone: xfer=%p, pipe=%p ready\n", xfer, epipe)); if (ehcidebug > 10) ehci_dump_sqtds(ex->sqtdstart); #endif /* * Make sure that the QH overlay qTD does not reference any * of the qTDs we are about to free. This is probably only * necessary if the transfer is marked as HALTED. */ nextphys = EHCI_LINK_ADDR(le32toh(epipe->sqh->qh.qh_qtd.qtd_next)); altnextphys = EHCI_LINK_ADDR(le32toh(epipe->sqh->qh.qh_qtd.qtd_altnext)); for (sqtd = ex->sqtdstart; sqtd != ex->sqtdend->nextqtd; sqtd = sqtd->nextqtd) { if (sqtd->physaddr == nextphys) { epipe->sqh->qh.qh_qtd.qtd_next = htole32(ex->sqtdend->nextqtd->physaddr); DPRINTFN(4, ("ehci_idone: updated overlay next ptr\n")); } if (sqtd->physaddr == altnextphys) { DPRINTFN(4, ("ehci_idone: updated overlay altnext ptr\n")); epipe->sqh->qh.qh_qtd.qtd_altnext = htole32(ex->sqtdend->nextqtd->physaddr); } } /* The transfer is done, compute actual length and status. */ lsqtd = ex->sqtdend; actlen = 0; for (sqtd = ex->sqtdstart; sqtd != lsqtd->nextqtd; sqtd=sqtd->nextqtd) { nstatus = le32toh(sqtd->qtd.qtd_status); if (nstatus & EHCI_QTD_ACTIVE) break; status = nstatus; /* halt is ok if descriptor is last, and complete */ if (sqtd == lsqtd && EHCI_QTD_GET_BYTES(status) == 0) status &= ~EHCI_QTD_HALTED; if (EHCI_QTD_GET_PID(status) != EHCI_QTD_PID_SETUP) actlen += sqtd->len - EHCI_QTD_GET_BYTES(status); } cerr = EHCI_QTD_GET_CERR(status); DPRINTFN(/*10*/2, ("ehci_idone: len=%d, actlen=%d, cerr=%d, " "status=0x%x\n", xfer->length, actlen, cerr, status)); xfer->actlen = actlen; if ((status & EHCI_QTD_HALTED) != 0) { #ifdef EHCI_DEBUG char sbuf[128]; bitmask_snprintf((u_int32_t)status, "\20\7HALTED\6BUFERR\5BABBLE\4XACTERR" "\3MISSED\2SPLIT\1PING", sbuf, sizeof(sbuf)); DPRINTFN(2, ("ehci_idone: error, addr=%d, endpt=0x%02x, " "status 0x%s\n", xfer->pipe->device->address, xfer->pipe->endpoint->edesc->bEndpointAddress, sbuf)); if (ehcidebug > 2) { ehci_dump_sqh(epipe->sqh); ehci_dump_sqtds(ex->sqtdstart); } #endif if ((status & EHCI_QTD_BABBLE) == 0 && cerr > 0) xfer->status = USBD_STALLED; else xfer->status = USBD_IOERROR; /* more info XXX */ } else { xfer->status = USBD_NORMAL_COMPLETION; } usb_transfer_complete(xfer); DPRINTFN(/*12*/2, ("ehci_idone: ex=%p done\n", ex)); } /* * Wait here until controller claims to have an interrupt. * Then call ehci_intr and return. Use timeout to avoid waiting * too long. */ void ehci_waitintr(ehci_softc_t *sc, usbd_xfer_handle xfer) { int timo = xfer->timeout; int usecs; u_int32_t intrs; xfer->status = USBD_IN_PROGRESS; for (usecs = timo * 1000000 / hz; usecs > 0; usecs -= 1000) { usb_delay_ms(&sc->sc_bus, 1); if (sc->sc_dying) break; intrs = EHCI_STS_INTRS(EOREAD4(sc, EHCI_USBSTS)) & sc->sc_eintrs; DPRINTFN(15,("ehci_waitintr: 0x%04x\n", intrs)); #ifdef EHCI_DEBUG if (ehcidebug > 15) ehci_dump_regs(sc); #endif if (intrs) { ehci_intr1(sc); if (xfer->status != USBD_IN_PROGRESS) return; } } /* Timeout */ DPRINTF(("ehci_waitintr: timeout\n")); xfer->status = USBD_TIMEOUT; usb_transfer_complete(xfer); /* XXX should free TD */ } void ehci_poll(struct usbd_bus *bus) { ehci_softc_t *sc = (ehci_softc_t *)bus; #ifdef EHCI_DEBUG static int last; int new; new = EHCI_STS_INTRS(EOREAD4(sc, EHCI_USBSTS)); if (new != last) { DPRINTFN(10,("ehci_poll: intrs=0x%04x\n", new)); last = new; } #endif if (EOREAD4(sc, EHCI_USBSTS) & sc->sc_eintrs) ehci_intr1(sc); } int ehci_detach(struct ehci_softc *sc, int flags) { int rv = 0; sc->sc_dying = 1; EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs); EOWRITE4(sc, EHCI_USBCMD, 0); EOWRITE4(sc, EHCI_USBCMD, EHCI_CMD_HCRESET); - usb_uncallout(sc->sc_tmo_intrlist, ehci_intrlist_timeout, sc); - usb_uncallout(sc->sc_tmo_pcd, ehci_pcd_enable, sc); + callout_stop(&sc->sc_tmo_intrlist); + callout_stop(&sc->sc_tmo_pcd); #if defined(__NetBSD__) || defined(__OpenBSD__) if (sc->sc_powerhook != NULL) powerhook_disestablish(sc->sc_powerhook); if (sc->sc_shutdownhook != NULL) shutdownhook_disestablish(sc->sc_shutdownhook); #endif usb_delay_ms(&sc->sc_bus, 300); /* XXX let stray task complete */ usb_freemem(&sc->sc_bus, &sc->sc_fldma); /* XXX free other data structures XXX */ return (rv); } /* * Handle suspend/resume. * * We need to switch to polling mode here, because this routine is * called from an interrupt context. This is all right since we * are almost suspended anyway. */ void ehci_power(int why, void *v) { ehci_softc_t *sc = v; u_int32_t cmd, hcr; int s, i; #ifdef EHCI_DEBUG DPRINTF(("ehci_power: sc=%p, why=%d\n", sc, why)); if (ehcidebug > 0) ehci_dump_regs(sc); #endif s = splhardusb(); switch (why) { case PWR_SUSPEND: -#if defined(__NetBSD__) || defined(__OpenBSD__) case PWR_STANDBY: -#endif sc->sc_bus.use_polling++; for (i = 1; i <= sc->sc_noport; i++) { cmd = EOREAD4(sc, EHCI_PORTSC(i)); if ((cmd & EHCI_PS_PO) == 0 && (cmd & EHCI_PS_PE) == EHCI_PS_PE) EOWRITE4(sc, EHCI_PORTSC(i), cmd | EHCI_PS_SUSP); } sc->sc_cmd = EOREAD4(sc, EHCI_USBCMD); cmd = sc->sc_cmd & ~(EHCI_CMD_ASE | EHCI_CMD_PSE); EOWRITE4(sc, EHCI_USBCMD, cmd); for (i = 0; i < 100; i++) { hcr = EOREAD4(sc, EHCI_USBSTS) & (EHCI_STS_ASS | EHCI_STS_PSS); if (hcr == 0) break; usb_delay_ms(&sc->sc_bus, 1); } if (hcr != 0) { printf("%s: reset timeout\n", device_get_nameunit(sc->sc_bus.bdev)); } cmd &= ~EHCI_CMD_RS; EOWRITE4(sc, EHCI_USBCMD, cmd); for (i = 0; i < 100; i++) { hcr = EOREAD4(sc, EHCI_USBSTS) & EHCI_STS_HCH; if (hcr == EHCI_STS_HCH) break; usb_delay_ms(&sc->sc_bus, 1); } if (hcr != EHCI_STS_HCH) { printf("%s: config timeout\n", device_get_nameunit(sc->sc_bus.bdev)); } sc->sc_bus.use_polling--; break; case PWR_RESUME: sc->sc_bus.use_polling++; /* restore things in case the bios sucks */ EOWRITE4(sc, EHCI_CTRLDSSEGMENT, 0); EOWRITE4(sc, EHCI_PERIODICLISTBASE, DMAADDR(&sc->sc_fldma, 0)); EOWRITE4(sc, EHCI_ASYNCLISTADDR, sc->sc_async_head->physaddr | EHCI_LINK_QH); EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs); hcr = 0; for (i = 1; i <= sc->sc_noport; i++) { cmd = EOREAD4(sc, EHCI_PORTSC(i)); if ((cmd & EHCI_PS_PO) == 0 && (cmd & EHCI_PS_SUSP) == EHCI_PS_SUSP) { EOWRITE4(sc, EHCI_PORTSC(i), cmd | EHCI_PS_FPR); hcr = 1; } } if (hcr) { usb_delay_ms(&sc->sc_bus, USB_RESUME_WAIT); for (i = 1; i <= sc->sc_noport; i++) { cmd = EOREAD4(sc, EHCI_PORTSC(i)); if ((cmd & EHCI_PS_PO) == 0 && (cmd & EHCI_PS_SUSP) == EHCI_PS_SUSP) EOWRITE4(sc, EHCI_PORTSC(i), cmd & ~EHCI_PS_FPR); } } EOWRITE4(sc, EHCI_USBCMD, sc->sc_cmd); for (i = 0; i < 100; i++) { hcr = EOREAD4(sc, EHCI_USBSTS) & EHCI_STS_HCH; if (hcr != EHCI_STS_HCH) break; usb_delay_ms(&sc->sc_bus, 1); } if (hcr == EHCI_STS_HCH) { printf("%s: config timeout\n", device_get_nameunit(sc->sc_bus.bdev)); } usb_delay_ms(&sc->sc_bus, USB_RESUME_WAIT); sc->sc_bus.use_polling--; break; -#if defined(__NetBSD__) || defined(__OpenBSD__) case PWR_SOFTSUSPEND: case PWR_SOFTSTANDBY: case PWR_SOFTRESUME: break; -#endif } splx(s); #ifdef EHCI_DEBUG DPRINTF(("ehci_power: sc=%p\n", sc)); if (ehcidebug > 0) ehci_dump_regs(sc); #endif } /* * Shut down the controller when the system is going down. */ void ehci_shutdown(void *v) { ehci_softc_t *sc = v; DPRINTF(("ehci_shutdown: stopping the HC\n")); EOWRITE4(sc, EHCI_USBCMD, 0); /* Halt controller */ EOWRITE4(sc, EHCI_USBCMD, EHCI_CMD_HCRESET); } usbd_status ehci_allocm(struct usbd_bus *bus, usb_dma_t *dma, u_int32_t size) { usbd_status err; err = usb_allocmem(bus, size, 0, dma); #ifdef EHCI_DEBUG if (err) printf("ehci_allocm: usb_allocmem()=%d\n", err); #endif return (err); } void ehci_freem(struct usbd_bus *bus, usb_dma_t *dma) { usb_freemem(bus, dma); } usbd_xfer_handle ehci_allocx(struct usbd_bus *bus) { struct ehci_softc *sc = (struct ehci_softc *)bus; usbd_xfer_handle xfer; xfer = STAILQ_FIRST(&sc->sc_free_xfers); if (xfer != NULL) { STAILQ_REMOVE_HEAD(&sc->sc_free_xfers, next); #ifdef DIAGNOSTIC if (xfer->busy_free != XFER_FREE) { printf("ehci_allocx: xfer=%p not free, 0x%08x\n", xfer, xfer->busy_free); } #endif } else { xfer = malloc(sizeof(struct ehci_xfer), M_USB, M_NOWAIT); } 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; #endif } return (xfer); } void ehci_freex(struct usbd_bus *bus, usbd_xfer_handle xfer) { struct ehci_softc *sc = (struct ehci_softc *)bus; #ifdef DIAGNOSTIC if (xfer->busy_free != XFER_BUSY) { printf("ehci_freex: xfer=%p not busy, 0x%08x\n", xfer, xfer->busy_free); return; } xfer->busy_free = XFER_FREE; if (!EXFER(xfer)->isdone) { printf("ehci_freex: !isdone\n"); return; } #endif STAILQ_INSERT_HEAD(&sc->sc_free_xfers, xfer, next); } static void ehci_device_clear_toggle(usbd_pipe_handle pipe) { struct ehci_pipe *epipe = (struct ehci_pipe *)pipe; DPRINTF(("ehci_device_clear_toggle: epipe=%p status=0x%x\n", epipe, epipe->sqh->qh.qh_qtd.qtd_status)); #ifdef USB_DEBUG if (ehcidebug) usbd_dump_pipe(pipe); #endif KASSERT((epipe->sqh->qh.qh_qtd.qtd_status & htole32(EHCI_QTD_ACTIVE)) == 0, ("ehci_device_clear_toggle: queue active")); epipe->sqh->qh.qh_qtd.qtd_status &= htole32(~EHCI_QTD_TOGGLE_MASK); } static void ehci_noop(usbd_pipe_handle pipe) { } #ifdef EHCI_DEBUG void ehci_dump_regs(ehci_softc_t *sc) { int i; printf("cmd=0x%08x, sts=0x%08x, ien=0x%08x\n", EOREAD4(sc, EHCI_USBCMD), EOREAD4(sc, EHCI_USBSTS), EOREAD4(sc, EHCI_USBINTR)); printf("frindex=0x%08x ctrdsegm=0x%08x periodic=0x%08x async=0x%08x\n", EOREAD4(sc, EHCI_FRINDEX), EOREAD4(sc, EHCI_CTRLDSSEGMENT), EOREAD4(sc, EHCI_PERIODICLISTBASE), EOREAD4(sc, EHCI_ASYNCLISTADDR)); for (i = 1; i <= sc->sc_noport; i++) printf("port %d status=0x%08x\n", i, EOREAD4(sc, EHCI_PORTSC(i))); } /* * Unused function - this is meant to be called from a kernel * debugger. */ void ehci_dump() { ehci_dump_regs(theehci); } void ehci_dump_link(ehci_link_t link, int type) { link = le32toh(link); printf("0x%08x", link); if (link & EHCI_LINK_TERMINATE) printf(""); else { printf("<"); if (type) { switch (EHCI_LINK_TYPE(link)) { case EHCI_LINK_ITD: printf("ITD"); break; case EHCI_LINK_QH: printf("QH"); break; case EHCI_LINK_SITD: printf("SITD"); break; case EHCI_LINK_FSTN: printf("FSTN"); break; } } printf(">"); } } void ehci_dump_sqtds(ehci_soft_qtd_t *sqtd) { int i; u_int32_t stop; stop = 0; for (i = 0; sqtd && i < 20 && !stop; sqtd = sqtd->nextqtd, i++) { ehci_dump_sqtd(sqtd); stop = sqtd->qtd.qtd_next & htole32(EHCI_LINK_TERMINATE); } if (sqtd) printf("dump aborted, too many TDs\n"); } void ehci_dump_sqtd(ehci_soft_qtd_t *sqtd) { printf("QTD(%p) at 0x%08x:\n", sqtd, sqtd->physaddr); ehci_dump_qtd(&sqtd->qtd); } void ehci_dump_qtd(ehci_qtd_t *qtd) { u_int32_t s; char sbuf[128]; printf(" next="); ehci_dump_link(qtd->qtd_next, 0); printf(" altnext="); ehci_dump_link(qtd->qtd_altnext, 0); printf("\n"); s = le32toh(qtd->qtd_status); bitmask_snprintf(EHCI_QTD_GET_STATUS(s), "\20\10ACTIVE\7HALTED\6BUFERR\5BABBLE\4XACTERR" "\3MISSED\2SPLIT\1PING", sbuf, sizeof(sbuf)); printf(" status=0x%08x: toggle=%d bytes=0x%x ioc=%d c_page=0x%x\n", s, EHCI_QTD_GET_TOGGLE(s), EHCI_QTD_GET_BYTES(s), EHCI_QTD_GET_IOC(s), EHCI_QTD_GET_C_PAGE(s)); printf(" cerr=%d pid=%d stat=0x%s\n", EHCI_QTD_GET_CERR(s), EHCI_QTD_GET_PID(s), sbuf); for (s = 0; s < 5; s++) printf(" buffer[%d]=0x%08x\n", s, le32toh(qtd->qtd_buffer[s])); } void ehci_dump_sqh(ehci_soft_qh_t *sqh) { ehci_qh_t *qh = &sqh->qh; u_int32_t endp, endphub; printf("QH(%p) at 0x%08x:\n", sqh, sqh->physaddr); printf(" sqtd=%p inactivesqtd=%p\n", sqh->sqtd, sqh->inactivesqtd); printf(" link="); ehci_dump_link(qh->qh_link, 1); printf("\n"); endp = le32toh(qh->qh_endp); printf(" endp=0x%08x\n", endp); printf(" addr=0x%02x inact=%d endpt=%d eps=%d dtc=%d hrecl=%d\n", EHCI_QH_GET_ADDR(endp), EHCI_QH_GET_INACT(endp), EHCI_QH_GET_ENDPT(endp), EHCI_QH_GET_EPS(endp), EHCI_QH_GET_DTC(endp), EHCI_QH_GET_HRECL(endp)); printf(" mpl=0x%x ctl=%d nrl=%d\n", EHCI_QH_GET_MPL(endp), EHCI_QH_GET_CTL(endp), EHCI_QH_GET_NRL(endp)); endphub = le32toh(qh->qh_endphub); printf(" endphub=0x%08x\n", endphub); printf(" smask=0x%02x cmask=0x%02x huba=0x%02x port=%d mult=%d\n", EHCI_QH_GET_SMASK(endphub), EHCI_QH_GET_CMASK(endphub), EHCI_QH_GET_HUBA(endphub), EHCI_QH_GET_PORT(endphub), EHCI_QH_GET_MULT(endphub)); printf(" curqtd="); ehci_dump_link(qh->qh_curqtd, 0); printf("\n"); printf("Overlay qTD:\n"); ehci_dump_qtd(&qh->qh_qtd); } #ifdef DIAGNOSTIC static void ehci_dump_exfer(struct ehci_xfer *ex) { printf("ehci_dump_exfer: ex=%p\n", ex); } #endif #endif usbd_status ehci_open(usbd_pipe_handle pipe) { usbd_device_handle dev = pipe->device; ehci_softc_t *sc = (ehci_softc_t *)dev->bus; usb_endpoint_descriptor_t *ed = pipe->endpoint->edesc; u_int8_t addr = dev->address; u_int8_t xfertype = ed->bmAttributes & UE_XFERTYPE; struct ehci_pipe *epipe = (struct ehci_pipe *)pipe; ehci_soft_qh_t *sqh; usbd_status err; int s; int ival, speed, naks; int hshubaddr, hshubport; DPRINTFN(1, ("ehci_open: pipe=%p, addr=%d, endpt=%d (%d)\n", pipe, addr, ed->bEndpointAddress, sc->sc_addr)); if (dev->myhsport) { hshubaddr = dev->myhsport->parent->address; hshubport = dev->myhsport->portno; } else { hshubaddr = 0; hshubport = 0; } if (sc->sc_dying) return (USBD_IOERROR); if (addr == sc->sc_addr) { switch (ed->bEndpointAddress) { case USB_CONTROL_ENDPOINT: pipe->methods = &ehci_root_ctrl_methods; break; case UE_DIR_IN | EHCI_INTR_ENDPT: pipe->methods = &ehci_root_intr_methods; break; default: return (USBD_INVAL); } return (USBD_NORMAL_COMPLETION); } /* XXX All this stuff is only valid for async. */ switch (dev->speed) { case USB_SPEED_LOW: speed = EHCI_QH_SPEED_LOW; break; case USB_SPEED_FULL: speed = EHCI_QH_SPEED_FULL; break; case USB_SPEED_HIGH: speed = EHCI_QH_SPEED_HIGH; break; default: panic("ehci_open: bad device speed %d", dev->speed); } if (speed != EHCI_QH_SPEED_HIGH && xfertype == UE_ISOCHRONOUS) { printf("%s: *** WARNING: opening low/full speed device, this " "does not work yet.\n", device_get_nameunit(sc->sc_bus.bdev)); DPRINTFN(1,("ehci_open: hshubaddr=%d hshubport=%d\n", hshubaddr, hshubport)); return USBD_INVAL; } naks = 8; /* XXX */ sqh = ehci_alloc_sqh(sc); if (sqh == NULL) goto bad0; /* qh_link filled when the QH is added */ sqh->qh.qh_endp = htole32( EHCI_QH_SET_ADDR(addr) | EHCI_QH_SET_ENDPT(UE_GET_ADDR(ed->bEndpointAddress)) | EHCI_QH_SET_EPS(speed) | (xfertype == UE_CONTROL ? EHCI_QH_DTC : 0) | EHCI_QH_SET_MPL(UGETW(ed->wMaxPacketSize)) | (speed != EHCI_QH_SPEED_HIGH && xfertype == UE_CONTROL ? EHCI_QH_CTL : 0) | EHCI_QH_SET_NRL(naks) ); sqh->qh.qh_endphub = htole32( EHCI_QH_SET_MULT(1) | EHCI_QH_SET_HUBA(hshubaddr) | EHCI_QH_SET_PORT(hshubport) | EHCI_QH_SET_CMASK(0x1c) | EHCI_QH_SET_SMASK(xfertype == UE_INTERRUPT ? 0x01 : 0) ); sqh->qh.qh_curqtd = EHCI_NULL; /* The overlay qTD was already set up by ehci_alloc_sqh(). */ sqh->qh.qh_qtd.qtd_status = htole32(EHCI_QTD_SET_TOGGLE(pipe->endpoint->savedtoggle)); epipe->sqh = sqh; switch (xfertype) { case UE_CONTROL: err = usb_allocmem(&sc->sc_bus, sizeof(usb_device_request_t), 0, &epipe->u.ctl.reqdma); #ifdef EHCI_DEBUG if (err) printf("ehci_open: usb_allocmem()=%d\n", err); #endif if (err) goto bad1; pipe->methods = &ehci_device_ctrl_methods; s = splusb(); ehci_add_qh(sqh, sc->sc_async_head); splx(s); break; case UE_BULK: pipe->methods = &ehci_device_bulk_methods; s = splusb(); ehci_add_qh(sqh, sc->sc_async_head); splx(s); break; case UE_INTERRUPT: pipe->methods = &ehci_device_intr_methods; 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); default: return (USBD_INVAL); } return (USBD_NORMAL_COMPLETION); bad1: ehci_free_sqh(sc, sqh); bad0: return (USBD_NOMEM); } /* * 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) { 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 if (ehcidebug > 5) { printf("ehci_add_qh:\n"); ehci_dump_sqh(sqh); } #endif } /* * 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) { SPLUSBCHECK; /* XXX */ 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); } /* Restart a QH following the addition of a qTD. */ void ehci_activate_qh(ehci_soft_qh_t *sqh, ehci_soft_qtd_t *sqtd) { KASSERT((sqtd->qtd.qtd_status & htole32(EHCI_QTD_ACTIVE)) == 0, ("ehci_activate_qh: already active")); /* * When a QH is idle, the overlay qTD should be marked as not * halted and not active. This causes the host controller to * retrieve the real qTD on each pass (rather than just examinig * the overlay), so it will notice when we activate the qTD. */ if (sqtd == sqh->sqtd) { /* Check that the hardware is in the state we expect. */ if (EHCI_LINK_ADDR(le32toh(sqh->qh.qh_qtd.qtd_next)) != sqtd->physaddr) { #ifdef EHCI_DEBUG printf("ehci_activate_qh: unexpected next ptr\n"); ehci_dump_sqh(sqh); ehci_dump_sqtds(sqh->sqtd); #endif sqh->qh.qh_qtd.qtd_next = htole32(sqtd->physaddr); sqh->qh.qh_qtd.qtd_altnext = EHCI_NULL; } /* Ensure the flags are correct. */ sqh->qh.qh_qtd.qtd_status &= htole32(EHCI_QTD_PINGSTATE | EHCI_QTD_TOGGLE_MASK); } /* Now activate the qTD. */ sqtd->qtd.qtd_status |= htole32(EHCI_QTD_ACTIVE); } /* * Ensure that the HC has released all references to the QH. We do this * by asking for a Async Advance Doorbell interrupt and then we wait for * the interrupt. * To make this easier we first obtain exclusive use of the doorbell. */ void ehci_sync_hc(ehci_softc_t *sc) { int s, error; if (sc->sc_dying) { DPRINTFN(2,("ehci_sync_hc: dying\n")); return; } DPRINTFN(2,("ehci_sync_hc: enter\n")); /* get doorbell */ lockmgr(&sc->sc_doorbell_lock, LK_EXCLUSIVE, NULL, NULL); s = splhardusb(); /* ask for doorbell */ EOWRITE4(sc, EHCI_USBCMD, EOREAD4(sc, EHCI_USBCMD) | EHCI_CMD_IAAD); DPRINTFN(1,("ehci_sync_hc: cmd=0x%08x sts=0x%08x\n", EOREAD4(sc, EHCI_USBCMD), EOREAD4(sc, EHCI_USBSTS))); error = tsleep(&sc->sc_async_head, PZERO, "ehcidi", hz); /* bell wait */ DPRINTFN(1,("ehci_sync_hc: cmd=0x%08x sts=0x%08x\n", EOREAD4(sc, EHCI_USBCMD), EOREAD4(sc, EHCI_USBSTS))); splx(s); /* release doorbell */ lockmgr(&sc->sc_doorbell_lock, LK_RELEASE, NULL, NULL); #ifdef DIAGNOSTIC if (error) printf("ehci_sync_hc: tsleep() = %d\n", error); #endif DPRINTFN(2,("ehci_sync_hc: exit\n")); } /***********/ /* * Data structures and routines to emulate the root hub. */ static usb_device_descriptor_t ehci_devd = { USB_DEVICE_DESCRIPTOR_SIZE, UDESC_DEVICE, /* type */ {0x00, 0x02}, /* USB version */ UDCLASS_HUB, /* class */ UDSUBCLASS_HUB, /* subclass */ UDPROTO_HSHUBSTT, /* protocol */ 64, /* max packet */ {0},{0},{0x00,0x01}, /* device id */ 1,2,0, /* string indicies */ 1 /* # of configurations */ }; static usb_device_qualifier_t ehci_odevd = { USB_DEVICE_DESCRIPTOR_SIZE, UDESC_DEVICE_QUALIFIER, /* type */ {0x00, 0x02}, /* USB version */ UDCLASS_HUB, /* class */ UDSUBCLASS_HUB, /* subclass */ UDPROTO_FSHUB, /* protocol */ 64, /* max packet */ 1, /* # of configurations */ 0 }; static usb_config_descriptor_t ehci_confd = { USB_CONFIG_DESCRIPTOR_SIZE, UDESC_CONFIG, {USB_CONFIG_DESCRIPTOR_SIZE + USB_INTERFACE_DESCRIPTOR_SIZE + USB_ENDPOINT_DESCRIPTOR_SIZE}, 1, 1, 0, UC_SELF_POWERED, 0 /* max power */ }; static usb_interface_descriptor_t ehci_ifcd = { USB_INTERFACE_DESCRIPTOR_SIZE, UDESC_INTERFACE, 0, 0, 1, UICLASS_HUB, UISUBCLASS_HUB, UIPROTO_HSHUBSTT, 0 }; static usb_endpoint_descriptor_t ehci_endpd = { USB_ENDPOINT_DESCRIPTOR_SIZE, UDESC_ENDPOINT, UE_DIR_IN | EHCI_INTR_ENDPT, UE_INTERRUPT, {8, 0}, /* max packet */ 255 }; static usb_hub_descriptor_t ehci_hubd = { USB_HUB_DESCRIPTOR_SIZE, UDESC_HUB, 0, {0,0}, 0, 0, {0}, }; static int ehci_str(usb_string_descriptor_t *p, int l, char *s) { int i; if (l == 0) return (0); p->bLength = 2 * strlen(s) + 2; if (l == 1) return (1); p->bDescriptorType = UDESC_STRING; l -= 2; for (i = 0; s[i] && l > 1; i++, l -= 2) USETW2(p->bString[i], 0, s[i]); return (2*i+2); } /* * Simulate a hardware hub by handling all the necessary requests. */ static usbd_status ehci_root_ctrl_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, start first */ return (ehci_root_ctrl_start(STAILQ_FIRST(&xfer->pipe->queue))); } static usbd_status ehci_root_ctrl_start(usbd_xfer_handle xfer) { ehci_softc_t *sc = (ehci_softc_t *)xfer->pipe->device->bus; usb_device_request_t *req; void *buf = NULL; int port, i; int s, len, value, index, l, totlen = 0; usb_port_status_t ps; usb_hub_descriptor_t hubd; usbd_status err; u_int32_t v; if (sc->sc_dying) return (USBD_IOERROR); #ifdef DIAGNOSTIC if (!(xfer->rqflags & URQ_REQUEST)) /* XXX panic */ return (USBD_INVAL); #endif req = &xfer->request; DPRINTFN(4,("ehci_root_ctrl_start: type=0x%02x request=%02x\n", req->bmRequestType, req->bRequest)); len = UGETW(req->wLength); value = UGETW(req->wValue); index = UGETW(req->wIndex); if (len != 0) buf = xfer->buffer; #define C(x,y) ((x) | ((y) << 8)) switch(C(req->bRequest, req->bmRequestType)) { case C(UR_CLEAR_FEATURE, UT_WRITE_DEVICE): case C(UR_CLEAR_FEATURE, UT_WRITE_INTERFACE): case C(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT): /* * DEVICE_REMOTE_WAKEUP and ENDPOINT_HALT are no-ops * for the integrated root hub. */ break; case C(UR_GET_CONFIG, UT_READ_DEVICE): if (len > 0) { *(u_int8_t *)buf = sc->sc_conf; totlen = 1; } break; case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE): DPRINTFN(8,("ehci_root_ctrl_start: wValue=0x%04x\n", value)); switch(value >> 8) { case UDESC_DEVICE: if ((value & 0xff) != 0) { err = USBD_IOERROR; goto ret; } totlen = l = min(len, USB_DEVICE_DESCRIPTOR_SIZE); USETW(ehci_devd.idVendor, sc->sc_id_vendor); memcpy(buf, &ehci_devd, l); break; /* * We can't really operate at another speed, but the spec says * we need this descriptor. */ case UDESC_DEVICE_QUALIFIER: if ((value & 0xff) != 0) { err = USBD_IOERROR; goto ret; } totlen = l = min(len, USB_DEVICE_DESCRIPTOR_SIZE); memcpy(buf, &ehci_odevd, l); break; /* * We can't really operate at another speed, but the spec says * we need this descriptor. */ case UDESC_OTHER_SPEED_CONFIGURATION: case UDESC_CONFIG: if ((value & 0xff) != 0) { err = USBD_IOERROR; goto ret; } totlen = l = min(len, USB_CONFIG_DESCRIPTOR_SIZE); memcpy(buf, &ehci_confd, l); ((usb_config_descriptor_t *)buf)->bDescriptorType = value >> 8; buf = (char *)buf + l; len -= l; l = min(len, USB_INTERFACE_DESCRIPTOR_SIZE); totlen += l; memcpy(buf, &ehci_ifcd, l); buf = (char *)buf + l; len -= l; l = min(len, USB_ENDPOINT_DESCRIPTOR_SIZE); totlen += l; memcpy(buf, &ehci_endpd, l); break; case UDESC_STRING: if (len == 0) break; *(u_int8_t *)buf = 0; totlen = 1; switch (value & 0xff) { case 0: /* Language table */ totlen = ehci_str(buf, len, "\001"); break; case 1: /* Vendor */ totlen = ehci_str(buf, len, sc->sc_vendor); break; case 2: /* Product */ totlen = ehci_str(buf, len, "EHCI root hub"); break; } break; default: err = USBD_IOERROR; goto ret; } break; case C(UR_GET_INTERFACE, UT_READ_INTERFACE): if (len > 0) { *(u_int8_t *)buf = 0; totlen = 1; } break; case C(UR_GET_STATUS, UT_READ_DEVICE): if (len > 1) { USETW(((usb_status_t *)buf)->wStatus,UDS_SELF_POWERED); totlen = 2; } break; case C(UR_GET_STATUS, UT_READ_INTERFACE): case C(UR_GET_STATUS, UT_READ_ENDPOINT): if (len > 1) { USETW(((usb_status_t *)buf)->wStatus, 0); totlen = 2; } break; case C(UR_SET_ADDRESS, UT_WRITE_DEVICE): if (value >= USB_MAX_DEVICES) { err = USBD_IOERROR; goto ret; } sc->sc_addr = value; break; case C(UR_SET_CONFIG, UT_WRITE_DEVICE): if (value != 0 && value != 1) { err = USBD_IOERROR; goto ret; } sc->sc_conf = value; break; case C(UR_SET_DESCRIPTOR, UT_WRITE_DEVICE): break; case C(UR_SET_FEATURE, UT_WRITE_DEVICE): case C(UR_SET_FEATURE, UT_WRITE_INTERFACE): case C(UR_SET_FEATURE, UT_WRITE_ENDPOINT): err = USBD_IOERROR; goto ret; case C(UR_SET_INTERFACE, UT_WRITE_INTERFACE): break; case C(UR_SYNCH_FRAME, UT_WRITE_ENDPOINT): break; /* Hub requests */ case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_DEVICE): break; case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_OTHER): DPRINTFN(8, ("ehci_root_ctrl_start: UR_CLEAR_PORT_FEATURE " "port=%d feature=%d\n", index, value)); if (index < 1 || index > sc->sc_noport) { err = USBD_IOERROR; goto ret; } port = EHCI_PORTSC(index); v = EOREAD4(sc, port) &~ EHCI_PS_CLEAR; switch(value) { case UHF_PORT_ENABLE: EOWRITE4(sc, port, v &~ EHCI_PS_PE); break; case UHF_PORT_SUSPEND: EOWRITE4(sc, port, v &~ EHCI_PS_SUSP); break; case UHF_PORT_POWER: EOWRITE4(sc, port, v &~ EHCI_PS_PP); break; case UHF_PORT_TEST: DPRINTFN(2,("ehci_root_ctrl_start: clear port test " "%d\n", index)); break; case UHF_PORT_INDICATOR: DPRINTFN(2,("ehci_root_ctrl_start: clear port ind " "%d\n", index)); EOWRITE4(sc, port, v &~ EHCI_PS_PIC); break; case UHF_C_PORT_CONNECTION: EOWRITE4(sc, port, v | EHCI_PS_CSC); break; case UHF_C_PORT_ENABLE: EOWRITE4(sc, port, v | EHCI_PS_PEC); break; case UHF_C_PORT_SUSPEND: /* how? */ break; case UHF_C_PORT_OVER_CURRENT: EOWRITE4(sc, port, v | EHCI_PS_OCC); break; case UHF_C_PORT_RESET: sc->sc_isreset = 0; break; default: err = USBD_IOERROR; goto ret; } #if 0 switch(value) { case UHF_C_PORT_CONNECTION: case UHF_C_PORT_ENABLE: case UHF_C_PORT_SUSPEND: case UHF_C_PORT_OVER_CURRENT: case UHF_C_PORT_RESET: /* Enable RHSC interrupt if condition is cleared. */ if ((OREAD4(sc, port) >> 16) == 0) ehci_pcd_able(sc, 1); break; default: break; } #endif break; case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE): if ((value & 0xff) != 0) { err = USBD_IOERROR; goto ret; } hubd = ehci_hubd; hubd.bNbrPorts = sc->sc_noport; v = EOREAD4(sc, EHCI_HCSPARAMS); USETW(hubd.wHubCharacteristics, EHCI_HCS_PPC(v) ? UHD_PWR_INDIVIDUAL : UHD_PWR_NO_SWITCH | 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) hubd.DeviceRemovable[i++] = 0; /* XXX can't find out? */ hubd.bDescLength = USB_HUB_DESCRIPTOR_SIZE + i; l = min(len, hubd.bDescLength); totlen = l; memcpy(buf, &hubd, l); break; case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE): if (len != 4) { err = USBD_IOERROR; goto ret; } memset(buf, 0, len); /* ? XXX */ totlen = len; break; case C(UR_GET_STATUS, UT_READ_CLASS_OTHER): DPRINTFN(8,("ehci_root_ctrl_start: get port status i=%d\n", index)); if (index < 1 || index > sc->sc_noport) { err = USBD_IOERROR; goto ret; } if (len != 4) { err = USBD_IOERROR; goto ret; } v = EOREAD4(sc, EHCI_PORTSC(index)); DPRINTFN(8,("ehci_root_ctrl_start: port status=0x%04x\n", v)); i = UPS_HIGH_SPEED; if (v & EHCI_PS_CS) i |= UPS_CURRENT_CONNECT_STATUS; if (v & EHCI_PS_PE) i |= UPS_PORT_ENABLED; if (v & EHCI_PS_SUSP) i |= UPS_SUSPEND; if (v & EHCI_PS_OCA) i |= UPS_OVERCURRENT_INDICATOR; if (v & EHCI_PS_PR) i |= UPS_RESET; if (v & EHCI_PS_PP) i |= UPS_PORT_POWER; USETW(ps.wPortStatus, i); i = 0; if (v & EHCI_PS_CSC) i |= UPS_C_CONNECT_STATUS; if (v & EHCI_PS_PEC) i |= UPS_C_PORT_ENABLED; if (v & EHCI_PS_OCC) i |= UPS_C_OVERCURRENT_INDICATOR; if (sc->sc_isreset) i |= UPS_C_PORT_RESET; USETW(ps.wPortChange, i); l = min(len, sizeof ps); memcpy(buf, &ps, l); totlen = l; break; case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE): err = USBD_IOERROR; goto ret; case C(UR_SET_FEATURE, UT_WRITE_CLASS_DEVICE): break; case C(UR_SET_FEATURE, UT_WRITE_CLASS_OTHER): if (index < 1 || index > sc->sc_noport) { err = USBD_IOERROR; goto ret; } port = EHCI_PORTSC(index); v = EOREAD4(sc, port) &~ EHCI_PS_CLEAR; switch(value) { case UHF_PORT_ENABLE: EOWRITE4(sc, port, v | EHCI_PS_PE); break; case UHF_PORT_SUSPEND: EOWRITE4(sc, port, v | EHCI_PS_SUSP); break; case UHF_PORT_RESET: DPRINTFN(5,("ehci_root_ctrl_start: reset port %d\n", index)); if (EHCI_PS_IS_LOWSPEED(v)) { /* Low speed device, give up ownership. */ ehci_disown(sc, index, 1); break; } /* Start reset sequence. */ v &= ~ (EHCI_PS_PE | EHCI_PS_PR); EOWRITE4(sc, port, v | EHCI_PS_PR); /* Wait for reset to complete. */ usb_delay_ms(&sc->sc_bus, USB_PORT_ROOT_RESET_DELAY); if (sc->sc_dying) { err = USBD_IOERROR; goto ret; } /* Terminate reset sequence. */ EOWRITE4(sc, port, v); /* Wait for HC to complete reset. */ usb_delay_ms(&sc->sc_bus, EHCI_PORT_RESET_COMPLETE); if (sc->sc_dying) { err = USBD_IOERROR; goto ret; } v = EOREAD4(sc, port); DPRINTF(("ehci after reset, status=0x%08x\n", v)); if (v & EHCI_PS_PR) { printf("%s: port reset timeout\n", device_get_nameunit(sc->sc_bus.bdev)); return (USBD_TIMEOUT); } if (!(v & EHCI_PS_PE)) { /* Not a high speed device, give up ownership.*/ ehci_disown(sc, index, 0); break; } sc->sc_isreset = 1; DPRINTF(("ehci port %d reset, status = 0x%08x\n", index, v)); break; case UHF_PORT_POWER: DPRINTFN(2,("ehci_root_ctrl_start: set port power " "%d\n", index)); EOWRITE4(sc, port, v | EHCI_PS_PP); break; case UHF_PORT_TEST: DPRINTFN(2,("ehci_root_ctrl_start: set port test " "%d\n", index)); break; case UHF_PORT_INDICATOR: DPRINTFN(2,("ehci_root_ctrl_start: set port ind " "%d\n", index)); EOWRITE4(sc, port, v | EHCI_PS_PIC); break; default: err = USBD_IOERROR; goto ret; } break; case C(UR_CLEAR_TT_BUFFER, UT_WRITE_CLASS_OTHER): case C(UR_RESET_TT, UT_WRITE_CLASS_OTHER): case C(UR_GET_TT_STATE, UT_READ_CLASS_OTHER): case C(UR_STOP_TT, UT_WRITE_CLASS_OTHER): break; default: err = USBD_IOERROR; goto ret; } xfer->actlen = totlen; err = USBD_NORMAL_COMPLETION; ret: xfer->status = err; s = splusb(); usb_transfer_complete(xfer); splx(s); return (USBD_IN_PROGRESS); } void ehci_disown(ehci_softc_t *sc, int index, int lowspeed) { int port; u_int32_t v; DPRINTF(("ehci_disown: index=%d lowspeed=%d\n", index, lowspeed)); #ifdef DIAGNOSTIC if (sc->sc_npcomp != 0) { int i = (index-1) / sc->sc_npcomp; if (i >= sc->sc_ncomp) printf("%s: strange port\n", device_get_nameunit(sc->sc_bus.bdev)); else printf("%s: handing over %s speed device on " "port %d to %s\n", device_get_nameunit(sc->sc_bus.bdev), lowspeed ? "low" : "full", index, device_get_nameunit(sc->sc_comps[i]->bdev)); } else { printf("%s: npcomp == 0\n", device_get_nameunit(sc->sc_bus.bdev)); } #endif port = EHCI_PORTSC(index); v = EOREAD4(sc, port) &~ EHCI_PS_CLEAR; EOWRITE4(sc, port, v | EHCI_PS_PO); } /* Abort a root control request. */ static void ehci_root_ctrl_abort(usbd_xfer_handle xfer) { /* Nothing to do, all transfers are synchronous. */ } /* Close the root pipe. */ static void ehci_root_ctrl_close(usbd_pipe_handle pipe) { DPRINTF(("ehci_root_ctrl_close\n")); /* Nothing to do. */ } void ehci_root_intr_done(usbd_xfer_handle xfer) { } static usbd_status ehci_root_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, start first */ return (ehci_root_intr_start(STAILQ_FIRST(&xfer->pipe->queue))); } static usbd_status ehci_root_intr_start(usbd_xfer_handle xfer) { usbd_pipe_handle pipe = xfer->pipe; ehci_softc_t *sc = (ehci_softc_t *)pipe->device->bus; if (sc->sc_dying) return (USBD_IOERROR); sc->sc_intrxfer = xfer; return (USBD_IN_PROGRESS); } /* Abort a root interrupt request. */ static void ehci_root_intr_abort(usbd_xfer_handle xfer) { int s; if (xfer->pipe->intrxfer == xfer) { DPRINTF(("ehci_root_intr_abort: remove\n")); xfer->pipe->intrxfer = NULL; } xfer->status = USBD_CANCELLED; s = splusb(); usb_transfer_complete(xfer); splx(s); } /* Close the root pipe. */ static void ehci_root_intr_close(usbd_pipe_handle pipe) { ehci_softc_t *sc = (ehci_softc_t *)pipe->device->bus; DPRINTF(("ehci_root_intr_close\n")); sc->sc_intrxfer = NULL; } void ehci_root_ctrl_done(usbd_xfer_handle xfer) { } /************************/ ehci_soft_qh_t * ehci_alloc_sqh(ehci_softc_t *sc) { ehci_soft_qh_t *sqh; ehci_soft_qtd_t *sqtd; usbd_status err; int i, offs; usb_dma_t dma; if (sc->sc_freeqhs == NULL) { DPRINTFN(2, ("ehci_alloc_sqh: allocating chunk\n")); err = usb_allocmem(&sc->sc_bus, EHCI_SQH_SIZE * EHCI_SQH_CHUNK, EHCI_PAGE_SIZE, &dma); #ifdef EHCI_DEBUG if (err) printf("ehci_alloc_sqh: usb_allocmem()=%d\n", err); #endif if (err) return (NULL); for(i = 0; i < EHCI_SQH_CHUNK; i++) { offs = i * EHCI_SQH_SIZE; sqh = KERNADDR(&dma, offs); sqh->physaddr = DMAADDR(&dma, offs); sqh->next = sc->sc_freeqhs; sc->sc_freeqhs = sqh; } } /* Allocate the initial inactive sqtd. */ sqtd = ehci_alloc_sqtd(sc); if (sqtd == NULL) return (NULL); sqtd->qtd.qtd_status = htole32(0); sqtd->qtd.qtd_next = EHCI_NULL; sqtd->qtd.qtd_altnext = EHCI_NULL; sqh = sc->sc_freeqhs; sc->sc_freeqhs = sqh->next; /* The overlay QTD should begin zeroed. */ sqh->qh.qh_qtd.qtd_next = htole32(sqtd->physaddr); sqh->qh.qh_qtd.qtd_altnext = EHCI_NULL; sqh->qh.qh_qtd.qtd_status = 0; for (i = 0; i < EHCI_QTD_NBUFFERS; i++) { sqh->qh.qh_qtd.qtd_buffer[i] = 0; sqh->qh.qh_qtd.qtd_buffer_hi[i] = 0; } sqh->next = NULL; sqh->prev = NULL; sqh->sqtd = sqtd; sqh->inactivesqtd = sqtd; return (sqh); } void ehci_free_sqh(ehci_softc_t *sc, ehci_soft_qh_t *sqh) { ehci_free_sqtd(sc, sqh->inactivesqtd); sqh->next = sc->sc_freeqhs; sc->sc_freeqhs = sqh; } ehci_soft_qtd_t * ehci_alloc_sqtd(ehci_softc_t *sc) { ehci_soft_qtd_t *sqtd; usbd_status err; int i, offs; usb_dma_t dma; int s; if (sc->sc_freeqtds == NULL) { DPRINTFN(2, ("ehci_alloc_sqtd: allocating chunk\n")); err = usb_allocmem(&sc->sc_bus, EHCI_SQTD_SIZE*EHCI_SQTD_CHUNK, EHCI_PAGE_SIZE, &dma); #ifdef EHCI_DEBUG if (err) printf("ehci_alloc_sqtd: usb_allocmem()=%d\n", err); #endif if (err) return (NULL); s = splusb(); for(i = 0; i < EHCI_SQTD_CHUNK; i++) { offs = i * EHCI_SQTD_SIZE; sqtd = KERNADDR(&dma, offs); sqtd->physaddr = DMAADDR(&dma, offs); sqtd->nextqtd = sc->sc_freeqtds; sc->sc_freeqtds = sqtd; } splx(s); } s = splusb(); sqtd = sc->sc_freeqtds; sc->sc_freeqtds = sqtd->nextqtd; sqtd->qtd.qtd_next = EHCI_NULL; sqtd->qtd.qtd_altnext = EHCI_NULL; sqtd->qtd.qtd_status = 0; for (i = 0; i < EHCI_QTD_NBUFFERS; i++) { sqtd->qtd.qtd_buffer[i] = 0; sqtd->qtd.qtd_buffer_hi[i] = 0; } sqtd->nextqtd = NULL; sqtd->xfer = NULL; splx(s); return (sqtd); } void ehci_free_sqtd(ehci_softc_t *sc, ehci_soft_qtd_t *sqtd) { int s; s = splusb(); sqtd->nextqtd = sc->sc_freeqtds; sc->sc_freeqtds = sqtd; splx(s); } usbd_status ehci_alloc_sqtd_chain(struct ehci_pipe *epipe, ehci_softc_t *sc, int alen, int rd, usbd_xfer_handle xfer, ehci_soft_qtd_t *start, ehci_soft_qtd_t *newinactive, ehci_soft_qtd_t **sp, ehci_soft_qtd_t **ep) { ehci_soft_qtd_t *next, *cur; ehci_physaddr_t dataphys, nextphys; u_int32_t qtdstatus; int adj, len, curlen, mps, offset, pagelen, seg, segoff; int i, iscontrol, forceshort; struct usb_dma_mapping *dma = &xfer->dmamap; DPRINTFN(alen<4*4096,("ehci_alloc_sqtd_chain: start len=%d\n", alen)); offset = 0; len = alen; iscontrol = (epipe->pipe.endpoint->edesc->bmAttributes & UE_XFERTYPE) == UE_CONTROL; qtdstatus = EHCI_QTD_ACTIVE | EHCI_QTD_SET_PID(rd ? EHCI_QTD_PID_IN : EHCI_QTD_PID_OUT) | EHCI_QTD_SET_CERR(3) /* IOC set below */ /* BYTES set below */ ; mps = UGETW(epipe->pipe.endpoint->edesc->wMaxPacketSize); forceshort = ((xfer->flags & USBD_FORCE_SHORT_XFER) || len == 0) && len % mps == 0; /* * The control transfer data stage always starts with a toggle of 1. * For other transfers we let the hardware track the toggle state. */ if (iscontrol) qtdstatus |= EHCI_QTD_SET_TOGGLE(1); if (start != NULL) { /* * If we are given a starting qTD, assume it is linked into * an active QH so be careful not to mark it active. */ cur = start; *sp = cur; qtdstatus &= ~EHCI_QTD_ACTIVE; } else { cur = ehci_alloc_sqtd(sc); *sp = cur; if (cur == NULL) goto nomem; } seg = 0; segoff = 0; for (;;) { curlen = 0; /* The EHCI hardware can handle at most 5 pages. */ for (i = 0; i < EHCI_QTD_NBUFFERS && curlen < len; i++) { KASSERT(seg < dma->nsegs, ("ehci_alloc_sqtd_chain: overrun")); dataphys = dma->segs[seg].ds_addr + segoff; pagelen = dma->segs[seg].ds_len - segoff; if (pagelen > len - curlen) pagelen = len - curlen; if (pagelen > EHCI_PAGE_SIZE - EHCI_PAGE_OFFSET(dataphys)) pagelen = EHCI_PAGE_SIZE - EHCI_PAGE_OFFSET(dataphys); segoff += pagelen; if (segoff >= dma->segs[seg].ds_len) { KASSERT(segoff == dma->segs[seg].ds_len, ("ehci_alloc_sqtd_chain: overlap")); seg++; segoff = 0; } cur->qtd.qtd_buffer[i] = htole32(dataphys); cur->qtd.qtd_buffer_hi[i] = 0; curlen += pagelen; /* * Must stop if there is any gap before or after * the page boundary. */ if (EHCI_PAGE_OFFSET(dataphys + pagelen) != 0) break; if (seg < dma->nsegs && EHCI_PAGE_OFFSET(segoff + dma->segs[seg].ds_addr) != 0) break; } /* Adjust down to a multiple of mps if not at the end. */ if (curlen < len && curlen % mps != 0) { adj = curlen % mps; curlen -= adj; KASSERT(curlen > 0, ("ehci_alloc_sqtd_chain: need to copy")); segoff -= adj; if (segoff < 0) { seg--; segoff += dma->segs[seg].ds_len; } KASSERT(seg >= 0 && segoff >= 0, ("ehci_alloc_sqtd_chain: adjust to mps")); } len -= curlen; if (len != 0 || forceshort) { next = ehci_alloc_sqtd(sc); if (next == NULL) goto nomem; nextphys = htole32(next->physaddr); } else { next = NULL; nextphys = EHCI_NULL; } cur->nextqtd = next; cur->qtd.qtd_next = nextphys; /* Make sure to stop after a short transfer. */ cur->qtd.qtd_altnext = htole32(newinactive->physaddr); cur->qtd.qtd_status = htole32(qtdstatus | EHCI_QTD_SET_BYTES(curlen)); cur->xfer = xfer; cur->len = curlen; DPRINTFN(10,("ehci_alloc_sqtd_chain: curlen=%d\n", curlen)); if (iscontrol) { /* * adjust the toggle based on the number of packets * in this qtd */ if ((((curlen + mps - 1) / mps) & 1) || curlen == 0) qtdstatus ^= EHCI_QTD_TOGGLE_MASK; } qtdstatus |= EHCI_QTD_ACTIVE; if (len == 0) { if (!forceshort) break; forceshort = 0; } DPRINTFN(10,("ehci_alloc_sqtd_chain: extend chain\n")); offset += curlen; cur = next; } cur->qtd.qtd_status |= htole32(EHCI_QTD_IOC); *ep = cur; DPRINTFN(10,("ehci_alloc_sqtd_chain: return sqtd=%p sqtdend=%p\n", *sp, *ep)); return (USBD_NORMAL_COMPLETION); nomem: /* XXX free chain */ DPRINTFN(-1,("ehci_alloc_sqtd_chain: no memory\n")); return (USBD_NOMEM); } /* Free the chain starting at sqtd and end at the qTD before sqtdend */ static void ehci_free_sqtd_chain(ehci_softc_t *sc, ehci_soft_qh_t *sqh, ehci_soft_qtd_t *sqtd, ehci_soft_qtd_t *sqtdend) { ehci_soft_qtd_t *p, **prevp; int i; DPRINTFN(10,("ehci_free_sqtd_chain: sqtd=%p sqtdend=%p\n", sqtd, sqtdend)); /* First unlink the chain from the QH's software qTD list. */ prevp = &sqh->sqtd; for (p = sqh->sqtd; p != NULL; p = p->nextqtd) { if (p == sqtd) { *prevp = sqtdend; break; } prevp = &p->nextqtd; } KASSERT(p != NULL, ("ehci_free_sqtd_chain: chain not found")); for (i = 0; sqtd != sqtdend; sqtd = p, i++) { p = sqtd->nextqtd; ehci_free_sqtd(sc, sqtd); } } /****************/ /* * Close a reqular pipe. * Assumes that there are no pending transactions. */ void ehci_close_pipe(usbd_pipe_handle pipe, ehci_soft_qh_t *head) { struct ehci_pipe *epipe = (struct ehci_pipe *)pipe; ehci_softc_t *sc = (ehci_softc_t *)pipe->device->bus; ehci_soft_qh_t *sqh = epipe->sqh; int s; s = splusb(); ehci_rem_qh(sc, sqh, head); splx(s); pipe->endpoint->savedtoggle = EHCI_QTD_GET_TOGGLE(le32toh(sqh->qh.qh_qtd.qtd_status)); ehci_free_sqh(sc, epipe->sqh); } /* * Abort a device request. * If this routine is called at splusb() it guarantees that the request * will be removed from the hardware scheduling and that the callback * for it will be called with USBD_CANCELLED status. * It's impossible to guarantee that the requested transfer will not * have happened since the hardware runs concurrently. * If the transaction has already happened we rely on the ordinary * interrupt processing to process it. */ void ehci_abort_xfer(usbd_xfer_handle xfer, usbd_status status) { #define exfer EXFER(xfer) 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, *snext; ehci_physaddr_t cur, us, next; int s; int hit, i; /* int count = 0; */ ehci_soft_qh_t *psqh; DPRINTF(("ehci_abort_xfer: xfer=%p pipe=%p\n", xfer, epipe)); if (sc->sc_dying) { /* If we're dying, just do the software part. */ s = splusb(); xfer->status = status; /* make software ignore it */ - usb_uncallout(xfer->timeout_handle, ehci_timeout, xfer); + callout_stop(&xfer->timeout_handle); usb_rem_task(epipe->pipe.device, &exfer->abort_task); usb_transfer_complete(xfer); splx(s); return; } if (xfer->device->bus->intr_context || !curproc) 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 timeouts 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); + callout_stop(&xfer->timeout_handle); 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. */ s = splusb(); #ifdef USB_USE_SOFTINTR sc->sc_softwake = 1; #endif /* USB_USE_SOFTINTR */ usb_schedsoftintr(&sc->sc_bus); #ifdef USB_USE_SOFTINTR tsleep(&sc->sc_softwake, PZERO, "ehciab", 0); #endif /* USB_USE_SOFTINTR */ /* * Step 4: Remove any vestiges of the xfer from the hardware. * The complication here is that the hardware may have executed * 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. */ cur = EHCI_LINK_ADDR(le32toh(sqh->qh.qh_curqtd)); hit = 0; /* If they initially point here. */ us = exfer->sqtdstart->physaddr; /* We will change them to point here */ snext = exfer->sqtdend->nextqtd; next = htole32(snext->physaddr); /* * Now loop through any qTDs before us and keep track of the pointer * that points to us for the end. */ 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; sqtd = sqtd->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. */ 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) { sqh->qh.qh_qtd.qtd_next = htole32(snext->physaddr); sqh->qh.qh_qtd.qtd_altnext = EHCI_NULL; sqh->qh.qh_qtd.qtd_status &= htole32(EHCI_QTD_TOGGLE_MASK); for (i = 0; i < EHCI_QTD_NBUFFERS; i++) { sqh->qh.qh_qtd.qtd_buffer[i] = 0; sqh->qh.qh_qtd.qtd_buffer_hi[i] = 0; } } } ehci_add_qh(sqh, psqh); /* * Step 5: 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); /* printf("%s: %d TDs aborted\n", __func__, count); */ splx(s); #undef exfer } void ehci_timeout(void *addr) { struct ehci_xfer *exfer = addr; struct ehci_pipe *epipe = (struct ehci_pipe *)exfer->xfer.pipe; ehci_softc_t *sc = (ehci_softc_t *)epipe->pipe.device->bus; DPRINTF(("ehci_timeout: exfer=%p\n", exfer)); #ifdef USB_DEBUG if (ehcidebug > 1) usbd_dump_pipe(exfer->xfer.pipe); #endif if (sc->sc_dying) { ehci_abort_xfer(&exfer->xfer, USBD_TIMEOUT); return; } /* Execute the abort in a process context. */ usb_add_task(exfer->xfer.pipe->device, &exfer->abort_task, USB_TASKQ_HC); } void ehci_timeout_task(void *addr) { usbd_xfer_handle xfer = addr; int s; DPRINTF(("ehci_timeout_task: xfer=%p\n", xfer)); s = splusb(); ehci_abort_xfer(xfer, USBD_TIMEOUT); splx(s); } /* * Some EHCI chips from VIA / ATI seem to trigger interrupts before writing * back the qTD status, or miss signalling occasionally under heavy load. * If the host machine is too fast, we can miss transaction completion - when * we scan the active list the transaction still seems to be active. This * generally exhibits itself as a umass stall that never recovers. * * We work around this behaviour by setting up this callback after any softintr * that completes with transactions still pending, giving us another chance to * check for completion after the writeback has taken place. */ void ehci_intrlist_timeout(void *arg) { ehci_softc_t *sc = arg; int s = splusb(); DPRINTFN(3, ("ehci_intrlist_timeout\n")); usb_schedsoftintr(&sc->sc_bus); splx(s); } /************************/ static usbd_status ehci_device_ctrl_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, start first */ return (ehci_device_ctrl_start(STAILQ_FIRST(&xfer->pipe->queue))); } static usbd_status ehci_device_ctrl_start(usbd_xfer_handle xfer) { ehci_softc_t *sc = (ehci_softc_t *)xfer->pipe->device->bus; usbd_status err; if (sc->sc_dying) return (USBD_IOERROR); #ifdef DIAGNOSTIC if (!(xfer->rqflags & URQ_REQUEST)) { /* XXX panic */ printf("ehci_device_ctrl_transfer: not a request\n"); return (USBD_INVAL); } #endif err = ehci_device_request(xfer); if (err) return (err); if (sc->sc_bus.use_polling) ehci_waitintr(sc, xfer); return (USBD_IN_PROGRESS); } void ehci_device_ctrl_done(usbd_xfer_handle 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; DPRINTFN(10,("ehci_ctrl_done: xfer=%p\n", xfer)); #ifdef DIAGNOSTIC if (!(xfer->rqflags & URQ_REQUEST)) { panic("ehci_ctrl_done: not a request"); } #endif if (xfer->status != USBD_NOMEM && ehci_active_intr_list(ex)) { ehci_del_intr_list(ex); /* remove from active list */ ehci_free_sqtd_chain(sc, epipe->sqh, ex->sqtdstart, ex->sqtdend->nextqtd); } DPRINTFN(5, ("ehci_ctrl_done: length=%d\n", xfer->actlen)); } /* Abort a device control request. */ static void ehci_device_ctrl_abort(usbd_xfer_handle xfer) { DPRINTF(("ehci_device_ctrl_abort: xfer=%p\n", xfer)); ehci_abort_xfer(xfer, USBD_CANCELLED); } /* Close a device control pipe. */ static void ehci_device_ctrl_close(usbd_pipe_handle pipe) { ehci_softc_t *sc = (ehci_softc_t *)pipe->device->bus; /*struct ehci_pipe *epipe = (struct ehci_pipe *)pipe;*/ DPRINTF(("ehci_device_ctrl_close: pipe=%p\n", pipe)); ehci_close_pipe(pipe, sc->sc_async_head); } usbd_status ehci_device_request(usbd_xfer_handle xfer) { #define exfer EXFER(xfer) struct ehci_pipe *epipe = (struct ehci_pipe *)xfer->pipe; usb_device_request_t *req = &xfer->request; usbd_device_handle dev = epipe->pipe.device; ehci_softc_t *sc = (ehci_softc_t *)dev->bus; ehci_soft_qtd_t *newinactive, *setup, *stat, *next; ehci_soft_qh_t *sqh; int isread; int len; usbd_status err; int s; isread = req->bmRequestType & UT_READ; len = UGETW(req->wLength); DPRINTFN(3,("ehci_device_request: type=0x%02x, request=0x%02x, " "wValue=0x%04x, wIndex=0x%04x len=%d, addr=%d, endpt=%d\n", req->bmRequestType, req->bRequest, UGETW(req->wValue), UGETW(req->wIndex), len, dev->address, epipe->pipe.endpoint->edesc->bEndpointAddress)); newinactive = ehci_alloc_sqtd(sc); if (newinactive == NULL) { err = USBD_NOMEM; goto bad1; } newinactive->qtd.qtd_status = htole32(0); newinactive->qtd.qtd_next = EHCI_NULL; newinactive->qtd.qtd_altnext = EHCI_NULL; stat = ehci_alloc_sqtd(sc); if (stat == NULL) { err = USBD_NOMEM; goto bad2; } sqh = epipe->sqh; setup = sqh->inactivesqtd; sqh->inactivesqtd = newinactive; epipe->u.ctl.length = len; /* Set up data transaction */ if (len != 0) { ehci_soft_qtd_t *end; err = ehci_alloc_sqtd_chain(epipe, sc, len, isread, xfer, NULL, newinactive, &next, &end); if (err) goto bad3; end->qtd.qtd_status &= htole32(~EHCI_QTD_IOC); end->nextqtd = stat; end->qtd.qtd_next = htole32(stat->physaddr); end->qtd.qtd_altnext = htole32(newinactive->physaddr); } else { next = stat; } memcpy(KERNADDR(&epipe->u.ctl.reqdma, 0), req, sizeof *req); /* Clear toggle, and do not activate until complete */ setup->qtd.qtd_status = htole32( EHCI_QTD_SET_PID(EHCI_QTD_PID_SETUP) | EHCI_QTD_SET_CERR(3) | EHCI_QTD_SET_TOGGLE(0) | EHCI_QTD_SET_BYTES(sizeof *req) ); setup->qtd.qtd_buffer[0] = htole32(DMAADDR(&epipe->u.ctl.reqdma, 0)); setup->qtd.qtd_buffer_hi[0] = 0; setup->nextqtd = next; setup->qtd.qtd_next = htole32(next->physaddr); setup->qtd.qtd_altnext = htole32(newinactive->physaddr); setup->xfer = xfer; setup->len = sizeof *req; stat->qtd.qtd_status = htole32( EHCI_QTD_ACTIVE | EHCI_QTD_SET_PID(isread ? EHCI_QTD_PID_OUT : EHCI_QTD_PID_IN) | EHCI_QTD_SET_CERR(3) | EHCI_QTD_SET_TOGGLE(1) | EHCI_QTD_IOC ); stat->qtd.qtd_buffer[0] = 0; /* XXX not needed? */ stat->qtd.qtd_buffer_hi[0] = 0; /* XXX not needed? */ stat->nextqtd = newinactive; stat->qtd.qtd_next = htole32(newinactive->physaddr); stat->qtd.qtd_altnext = htole32(newinactive->physaddr); stat->xfer = xfer; stat->len = 0; #ifdef EHCI_DEBUG if (ehcidebug > 5) { DPRINTF(("ehci_device_request:\n")); ehci_dump_sqh(sqh); ehci_dump_sqtds(setup); } #endif exfer->sqtdstart = setup; exfer->sqtdend = stat; #ifdef DIAGNOSTIC if (!exfer->isdone) { printf("ehci_device_request: not done, exfer=%p\n", exfer); } exfer->isdone = 0; #endif /* Activate the new qTD in the QH list. */ s = splusb(); ehci_activate_qh(sqh, setup); if (xfer->timeout && !sc->sc_bus.use_polling) { - usb_callout(xfer->timeout_handle, MS_TO_TICKS(xfer->timeout), - ehci_timeout, xfer); + callout_reset(&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_request: status=%x\n", EOREAD4(sc, EHCI_USBSTS))); delay(10000); ehci_dump_regs(sc); ehci_dump_sqh(sc->sc_async_head); ehci_dump_sqh(sqh); ehci_dump_sqtds(setup); } #endif return (USBD_NORMAL_COMPLETION); bad3: sqh->inactivesqtd = setup; ehci_free_sqtd(sc, stat); bad2: ehci_free_sqtd(sc, newinactive); bad1: DPRINTFN(-1,("ehci_device_request: no memory\n")); xfer->status = err; usb_transfer_complete(xfer); return (err); #undef exfer } /************************/ static usbd_status ehci_device_bulk_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, start first */ return (ehci_device_bulk_start(STAILQ_FIRST(&xfer->pipe->queue))); } usbd_status ehci_device_bulk_start(usbd_xfer_handle xfer) { #define exfer EXFER(xfer) struct ehci_pipe *epipe = (struct ehci_pipe *)xfer->pipe; usbd_device_handle dev = epipe->pipe.device; ehci_softc_t *sc = (ehci_softc_t *)dev->bus; ehci_soft_qtd_t *data, *dataend, *newinactive; ehci_soft_qh_t *sqh; usbd_status err; int len, isread, endpt; int s; DPRINTFN(2, ("ehci_device_bulk_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_bulk_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.bulk.length = len; newinactive = ehci_alloc_sqtd(sc); if (newinactive == NULL) { DPRINTFN(-1,("ehci_device_bulk_start: no sqtd memory\n")); err = USBD_NOMEM; xfer->status = err; usb_transfer_complete(xfer); return (err); } newinactive->qtd.qtd_status = htole32(0); newinactive->qtd.qtd_next = EHCI_NULL; newinactive->qtd.qtd_altnext = EHCI_NULL; err = ehci_alloc_sqtd_chain(epipe, sc, len, isread, xfer, sqh->inactivesqtd, newinactive, &data, &dataend); if (err) { DPRINTFN(-1,("ehci_device_bulk_start: no memory\n")); ehci_free_sqtd(sc, newinactive); xfer->status = err; usb_transfer_complete(xfer); return (err); } dataend->nextqtd = newinactive; dataend->qtd.qtd_next = htole32(newinactive->physaddr); dataend->qtd.qtd_altnext = htole32(newinactive->physaddr); sqh->inactivesqtd = newinactive; #ifdef EHCI_DEBUG if (ehcidebug > 5) { DPRINTF(("ehci_device_bulk_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_bulk_start: not done, ex=%p\n", exfer); } exfer->isdone = 0; #endif s = splusb(); ehci_activate_qh(sqh, data); if (xfer->timeout && !sc->sc_bus.use_polling) { - usb_callout(xfer->timeout_handle, MS_TO_TICKS(xfer->timeout), - ehci_timeout, xfer); + callout_reset(&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_bulk_start: data(2)\n")); delay(10000); DPRINTF(("ehci_device_bulk_start: data(3)\n")); ehci_dump_regs(sc); #if 0 printf("async_head:\n"); ehci_dump_sqh(sc->sc_async_head); #endif 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_bulk_abort(usbd_xfer_handle xfer) { DPRINTF(("ehci_device_bulk_abort: xfer=%p\n", xfer)); ehci_abort_xfer(xfer, USBD_CANCELLED); } /* * Close a device bulk pipe. */ static void ehci_device_bulk_close(usbd_pipe_handle pipe) { ehci_softc_t *sc = (ehci_softc_t *)pipe->device->bus; DPRINTF(("ehci_device_bulk_close: pipe=%p\n", pipe)); ehci_close_pipe(pipe, sc->sc_async_head); } void ehci_device_bulk_done(usbd_xfer_handle 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; DPRINTFN(10,("ehci_bulk_done: xfer=%p, actlen=%d\n", xfer, xfer->actlen)); if (xfer->status != USBD_NOMEM && ehci_active_intr_list(ex)) { ehci_del_intr_list(ex); /* remove from active list */ ehci_free_sqtd_chain(sc, epipe->sqh, ex->sqtdstart, ex->sqtdend->nextqtd); } DPRINTFN(5, ("ehci_bulk_done: length=%d\n", xfer->actlen)); } /************************/ 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[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(STAILQ_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, *newinactive; 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; newinactive = ehci_alloc_sqtd(sc); if (newinactive == NULL) { DPRINTFN(-1,("ehci_device_intr_start: no sqtd memory\n")); err = USBD_NOMEM; xfer->status = err; usb_transfer_complete(xfer); return (err); } newinactive->qtd.qtd_status = htole32(0); newinactive->qtd.qtd_next = EHCI_NULL; newinactive->qtd.qtd_altnext = EHCI_NULL; err = ehci_alloc_sqtd_chain(epipe, sc, len, isread, xfer, sqh->inactivesqtd, newinactive, &data, &dataend); if (err) { DPRINTFN(-1, ("ehci_device_intr_start: no memory\n")); xfer->status = err; usb_transfer_complete(xfer); return (err); } dataend->nextqtd = newinactive; dataend->qtd.qtd_next = htole32(newinactive->physaddr); dataend->qtd.qtd_altnext = htole32(newinactive->physaddr); sqh->inactivesqtd = newinactive; #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_activate_qh(sqh, data); if (xfer->timeout && !sc->sc_bus.use_polling) { - usb_callout(xfer->timeout_handle, MS_TO_TICKS(xfer->timeout), + callout_reset(&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, *newinactive; 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)); sqh = epipe->sqh; if (xfer->pipe->repeat) { ehci_free_sqtd_chain(sc, sqh, ex->sqtdstart, ex->sqtdend->nextqtd); len = epipe->u.intr.length; xfer->length = len; endpt = epipe->pipe.endpoint->edesc->bEndpointAddress; isread = UE_GET_DIR(endpt) == UE_DIR_IN; newinactive = ehci_alloc_sqtd(sc); if (newinactive == NULL) { DPRINTFN(-1, ("ehci_device_intr_done: no sqtd memory\n")); err = USBD_NOMEM; xfer->status = err; return; } newinactive->qtd.qtd_status = htole32(0); newinactive->qtd.qtd_next = EHCI_NULL; newinactive->qtd.qtd_altnext = EHCI_NULL; err = ehci_alloc_sqtd_chain(epipe, sc, len, isread, xfer, sqh->inactivesqtd, newinactive, &data, &dataend); if (err) { DPRINTFN(-1, ("ehci_device_intr_done: no memory\n")); xfer->status = err; return; } dataend->nextqtd = newinactive; dataend->qtd.qtd_next = htole32(newinactive->physaddr); dataend->qtd.qtd_altnext = htole32(newinactive->physaddr); sqh->inactivesqtd = newinactive; /* 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_activate_qh(sqh, data); if (xfer->timeout && !sc->sc_bus.use_polling) { - usb_callout(xfer->timeout_handle, + callout_reset(&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, sqh, ex->sqtdstart, ex->sqtdend->nextqtd); } #undef exfer } /************************/ static usbd_status ehci_device_isoc_transfer(usbd_xfer_handle xfer) { return USBD_IOERROR; } static usbd_status ehci_device_isoc_start(usbd_xfer_handle xfer) { return USBD_IOERROR; } static void ehci_device_isoc_abort(usbd_xfer_handle xfer) { } static void ehci_device_isoc_close(usbd_pipe_handle pipe) { } static void ehci_device_isoc_done(usbd_xfer_handle xfer) { } diff --git a/sys/dev/usb/ehcivar.h b/sys/dev/usb/ehcivar.h index d93b8a84561f..7b2284538021 100644 --- a/sys/dev/usb/ehcivar.h +++ b/sys/dev/usb/ehcivar.h @@ -1,192 +1,189 @@ /* $NetBSD: ehcivar.h,v 1.19 2005/04/29 15:04:29 augustss Exp $ */ /* $FreeBSD$ */ /*- * Copyright (c) 2001 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net). * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ typedef struct ehci_soft_qtd { ehci_qtd_t qtd; struct ehci_soft_qtd *nextqtd; /* mirrors nextqtd in TD */ ehci_physaddr_t physaddr; usbd_xfer_handle xfer; LIST_ENTRY(ehci_soft_qtd) hnext; u_int16_t len; } ehci_soft_qtd_t; #define EHCI_SQTD_SIZE ((sizeof (struct ehci_soft_qtd) + EHCI_QTD_ALIGN - 1) / EHCI_QTD_ALIGN * EHCI_QTD_ALIGN) #define EHCI_SQTD_CHUNK (EHCI_PAGE_SIZE / EHCI_SQTD_SIZE) typedef struct ehci_soft_qh { ehci_qh_t qh; struct ehci_soft_qh *next; struct ehci_soft_qh *prev; struct ehci_soft_qtd *sqtd; struct ehci_soft_qtd *inactivesqtd; 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) struct ehci_xfer { struct usbd_xfer xfer; struct usb_task abort_task; 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. */ 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_MAX_POLLRATE (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 #define EHCI_SCFLG_DONEINIT 0x0001 /* ehci_init() has been called. */ #define EHCI_SCFLG_LOSTINTRBUG 0x0002 /* workaround for VIA / ATI chipsets */ typedef struct ehci_softc { struct usbd_bus sc_bus; /* base device */ int sc_flags; bus_space_tag_t iot; bus_space_handle_t ioh; bus_size_t sc_size; #if defined(__FreeBSD__) void *ih; struct resource *io_res; struct resource *irq_res; #endif u_int sc_offs; /* offset to operational regs */ char sc_vendor[32]; /* vendor string for root hub */ int sc_id_vendor; /* vendor ID for root hub */ u_int32_t sc_cmd; /* shadow of cmd reg during suspend */ #if defined(__NetBSD__) || defined(__OpenBSD__) void *sc_powerhook; /* cookie from power hook */ void *sc_shutdownhook; /* cookie from shutdown hook */ #endif u_int sc_ncomp; u_int sc_npcomp; struct usbd_bus *sc_comps[EHCI_COMPANION_MAX]; usb_dma_t sc_fldma; ehci_link_t *sc_flist; u_int sc_flsize; #ifndef __FreeBSD__ u_int sc_rand; /* XXX need proper intr scheduling */ #endif struct ehci_soft_islot sc_islots[EHCI_INTRQHS]; LIST_HEAD(, ehci_xfer) sc_intrhead; ehci_soft_qh_t *sc_freeqhs; ehci_soft_qtd_t *sc_freeqtds; int sc_noport; u_int8_t sc_addr; /* device address */ u_int8_t sc_conf; /* device configuration */ usbd_xfer_handle sc_intrxfer; char sc_isreset; #ifdef USB_USE_SOFTINTR char sc_softwake; #endif /* USB_USE_SOFTINTR */ u_int32_t sc_eintrs; ehci_soft_qh_t *sc_async_head; STAILQ_HEAD(, usbd_xfer) sc_free_xfers; /* free xfers */ struct lock sc_doorbell_lock; - usb_callout_t sc_tmo_pcd; - usb_callout_t sc_tmo_intrlist; + struct callout sc_tmo_pcd; + struct callout sc_tmo_intrlist; -#if defined(__NetBSD__) || defined(__OpenBSD__) - device_t sc_child; /* /dev/usb# device */ -#endif char sc_dying; #if defined(__NetBSD__) struct usb_dma_reserve sc_dma_reserve; #endif } ehci_softc_t; #define EREAD1(sc, a) bus_space_read_1((sc)->iot, (sc)->ioh, (a)) #define EREAD2(sc, a) bus_space_read_2((sc)->iot, (sc)->ioh, (a)) #define EREAD4(sc, a) bus_space_read_4((sc)->iot, (sc)->ioh, (a)) #define EWRITE1(sc, a, x) bus_space_write_1((sc)->iot, (sc)->ioh, (a), (x)) #define EWRITE2(sc, a, x) bus_space_write_2((sc)->iot, (sc)->ioh, (a), (x)) #define EWRITE4(sc, a, x) bus_space_write_4((sc)->iot, (sc)->ioh, (a), (x)) #define EOREAD1(sc, a) bus_space_read_1((sc)->iot, (sc)->ioh, (sc)->sc_offs+(a)) #define EOREAD2(sc, a) bus_space_read_2((sc)->iot, (sc)->ioh, (sc)->sc_offs+(a)) #define EOREAD4(sc, a) bus_space_read_4((sc)->iot, (sc)->ioh, (sc)->sc_offs+(a)) #define EOWRITE1(sc, a, x) bus_space_write_1((sc)->iot, (sc)->ioh, (sc)->sc_offs+(a), (x)) #define EOWRITE2(sc, a, x) bus_space_write_2((sc)->iot, (sc)->ioh, (sc)->sc_offs+(a), (x)) #define EOWRITE4(sc, a, x) bus_space_write_4((sc)->iot, (sc)->ioh, (sc)->sc_offs+(a), (x)) usbd_status ehci_init(ehci_softc_t *); int ehci_intr(void *); int ehci_detach(ehci_softc_t *, int); #if defined(__NetBSD__) || defined(__OpenBSD__) int ehci_activate(device_t, enum devact); #endif void ehci_power(int state, void *priv); void ehci_shutdown(void *v); #define MS_TO_TICKS(ms) ((ms) * hz / 1000) diff --git a/sys/dev/usb/ohci.c b/sys/dev/usb/ohci.c index 293f42d973f3..40836e780577 100644 --- a/sys/dev/usb/ohci.c +++ b/sys/dev/usb/ohci.c @@ -1,3619 +1,3619 @@ /* $NetBSD: ohci.c,v 1.138 2003/02/08 03:32:50 ichiro Exp $ */ /* Also, already ported: * $NetBSD: ohci.c,v 1.140 2003/05/13 04:42:00 gson Exp $ * $NetBSD: ohci.c,v 1.141 2003/09/10 20:08:29 mycroft Exp $ * $NetBSD: ohci.c,v 1.142 2003/10/11 03:04:26 toshii Exp $ * $NetBSD: ohci.c,v 1.143 2003/10/18 04:50:35 simonb Exp $ * $NetBSD: ohci.c,v 1.144 2003/11/23 19:18:06 augustss Exp $ * $NetBSD: ohci.c,v 1.145 2003/11/23 19:20:25 augustss Exp $ * $NetBSD: ohci.c,v 1.146 2003/12/29 08:17:10 toshii Exp $ * $NetBSD: ohci.c,v 1.147 2004/06/22 07:20:35 mycroft Exp $ * $NetBSD: ohci.c,v 1.148 2004/06/22 18:27:46 mycroft Exp $ */ #include __FBSDID("$FreeBSD$"); /*- * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * USB Open Host Controller driver. * * OHCI spec: http://www.compaq.com/productinfo/development/openhci.html * USB spec: http://www.usb.org/developers/docs/usbspec.zip */ #include #include #include #include #include #include #include #if defined(DIAGNOSTIC) && defined(__i386__) && defined(__FreeBSD__) #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #define delay(d) DELAY(d) #ifdef USB_DEBUG #define DPRINTF(x) if (ohcidebug) logprintf x #define DPRINTFN(n,x) if (ohcidebug>(n)) logprintf x int ohcidebug = 0; SYSCTL_NODE(_hw_usb, OID_AUTO, ohci, CTLFLAG_RW, 0, "USB ohci"); SYSCTL_INT(_hw_usb_ohci, OID_AUTO, debug, CTLFLAG_RW, &ohcidebug, 0, "ohci debug level"); #define bitmask_snprintf(q,f,b,l) snprintf((b), (l), "%b", (q), (f)) #else #define DPRINTF(x) #define DPRINTFN(n,x) #endif struct ohci_pipe; static ohci_soft_ed_t *ohci_alloc_sed(ohci_softc_t *); static void ohci_free_sed(ohci_softc_t *, ohci_soft_ed_t *); static ohci_soft_td_t *ohci_alloc_std(ohci_softc_t *); static void ohci_free_std(ohci_softc_t *, ohci_soft_td_t *); static ohci_soft_itd_t *ohci_alloc_sitd(ohci_softc_t *); static void ohci_free_sitd(ohci_softc_t *,ohci_soft_itd_t *); #if 0 static void ohci_free_std_chain(ohci_softc_t *, ohci_soft_td_t *, ohci_soft_td_t *); #endif static usbd_status ohci_alloc_std_chain(struct ohci_pipe *, ohci_softc_t *, int, int, usbd_xfer_handle, ohci_soft_td_t *, ohci_soft_td_t **); #if defined(__NetBSD__) || defined(__OpenBSD__) static void ohci_shutdown(void *v); static void ohci_power(int, void *); #endif static usbd_status ohci_open(usbd_pipe_handle); static void ohci_poll(struct usbd_bus *); static void ohci_softintr(void *); static void ohci_waitintr(ohci_softc_t *, usbd_xfer_handle); static void ohci_add_done(ohci_softc_t *, ohci_physaddr_t); static void ohci_rhsc(ohci_softc_t *, usbd_xfer_handle); static usbd_status ohci_device_request(usbd_xfer_handle xfer); static void ohci_add_ed(ohci_soft_ed_t *, ohci_soft_ed_t *); static void ohci_rem_ed(ohci_soft_ed_t *, ohci_soft_ed_t *); static void ohci_hash_add_td(ohci_softc_t *, ohci_soft_td_t *); static void ohci_hash_rem_td(ohci_softc_t *, ohci_soft_td_t *); static ohci_soft_td_t *ohci_hash_find_td(ohci_softc_t *, ohci_physaddr_t); static void ohci_hash_add_itd(ohci_softc_t *, ohci_soft_itd_t *); static void ohci_hash_rem_itd(ohci_softc_t *, ohci_soft_itd_t *); static ohci_soft_itd_t *ohci_hash_find_itd(ohci_softc_t *, ohci_physaddr_t); static usbd_status ohci_setup_isoc(usbd_pipe_handle pipe); static void ohci_device_isoc_enter(usbd_xfer_handle); static usbd_status ohci_allocm(struct usbd_bus *, usb_dma_t *, u_int32_t); static void ohci_freem(struct usbd_bus *, usb_dma_t *); static usbd_xfer_handle ohci_allocx(struct usbd_bus *); static void ohci_freex(struct usbd_bus *, usbd_xfer_handle); static usbd_status ohci_root_ctrl_transfer(usbd_xfer_handle); static usbd_status ohci_root_ctrl_start(usbd_xfer_handle); static void ohci_root_ctrl_abort(usbd_xfer_handle); static void ohci_root_ctrl_close(usbd_pipe_handle); static void ohci_root_ctrl_done(usbd_xfer_handle); static usbd_status ohci_root_intr_transfer(usbd_xfer_handle); static usbd_status ohci_root_intr_start(usbd_xfer_handle); static void ohci_root_intr_abort(usbd_xfer_handle); static void ohci_root_intr_close(usbd_pipe_handle); static void ohci_root_intr_done(usbd_xfer_handle); static usbd_status ohci_device_ctrl_transfer(usbd_xfer_handle); static usbd_status ohci_device_ctrl_start(usbd_xfer_handle); static void ohci_device_ctrl_abort(usbd_xfer_handle); static void ohci_device_ctrl_close(usbd_pipe_handle); static void ohci_device_ctrl_done(usbd_xfer_handle); static usbd_status ohci_device_bulk_transfer(usbd_xfer_handle); static usbd_status ohci_device_bulk_start(usbd_xfer_handle); static void ohci_device_bulk_abort(usbd_xfer_handle); static void ohci_device_bulk_close(usbd_pipe_handle); static void ohci_device_bulk_done(usbd_xfer_handle); static usbd_status ohci_device_intr_transfer(usbd_xfer_handle); static usbd_status ohci_device_intr_start(usbd_xfer_handle); static void ohci_device_intr_abort(usbd_xfer_handle); static void ohci_device_intr_close(usbd_pipe_handle); static void ohci_device_intr_done(usbd_xfer_handle); static usbd_status ohci_device_isoc_transfer(usbd_xfer_handle); static usbd_status ohci_device_isoc_start(usbd_xfer_handle); static void ohci_device_isoc_abort(usbd_xfer_handle); static void ohci_device_isoc_close(usbd_pipe_handle); static void ohci_device_isoc_done(usbd_xfer_handle); static usbd_status ohci_device_setintr(ohci_softc_t *sc, struct ohci_pipe *pipe, int ival); static usbd_status ohci_device_intr_insert(ohci_softc_t *sc, usbd_xfer_handle xfer); static int ohci_str(usb_string_descriptor_t *, int, const char *); static void ohci_timeout(void *); static void ohci_timeout_task(void *); static void ohci_rhsc_able(ohci_softc_t *, int); static void ohci_rhsc_enable(void *); static void ohci_close_pipe(usbd_pipe_handle, ohci_soft_ed_t *); static void ohci_abort_xfer(usbd_xfer_handle, usbd_status); static void ohci_device_clear_toggle(usbd_pipe_handle pipe); static void ohci_noop(usbd_pipe_handle pipe); static usbd_status ohci_controller_init(ohci_softc_t *sc); #ifdef USB_DEBUG static void ohci_dumpregs(ohci_softc_t *); static void ohci_dump_tds(ohci_soft_td_t *); static void ohci_dump_td(ohci_soft_td_t *); static void ohci_dump_ed(ohci_soft_ed_t *); static void ohci_dump_itd(ohci_soft_itd_t *); static void ohci_dump_itds(ohci_soft_itd_t *); #endif #define OBARR(sc) bus_space_barrier((sc)->iot, (sc)->ioh, 0, (sc)->sc_size, \ BUS_SPACE_BARRIER_READ|BUS_SPACE_BARRIER_WRITE) #define OWRITE1(sc, r, x) \ do { OBARR(sc); bus_space_write_1((sc)->iot, (sc)->ioh, (r), (x)); } while (0) #define OWRITE2(sc, r, x) \ do { OBARR(sc); bus_space_write_2((sc)->iot, (sc)->ioh, (r), (x)); } while (0) #define OWRITE4(sc, r, x) \ do { OBARR(sc); bus_space_write_4((sc)->iot, (sc)->ioh, (r), (x)); } while (0) #define OREAD1(sc, r) (OBARR(sc), bus_space_read_1((sc)->iot, (sc)->ioh, (r))) #define OREAD2(sc, r) (OBARR(sc), bus_space_read_2((sc)->iot, (sc)->ioh, (r))) #define OREAD4(sc, r) (OBARR(sc), bus_space_read_4((sc)->iot, (sc)->ioh, (r))) /* Reverse the bits in a value 0 .. 31 */ static u_int8_t revbits[OHCI_NO_INTRS] = { 0x00, 0x10, 0x08, 0x18, 0x04, 0x14, 0x0c, 0x1c, 0x02, 0x12, 0x0a, 0x1a, 0x06, 0x16, 0x0e, 0x1e, 0x01, 0x11, 0x09, 0x19, 0x05, 0x15, 0x0d, 0x1d, 0x03, 0x13, 0x0b, 0x1b, 0x07, 0x17, 0x0f, 0x1f }; 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; } tail; /* Info needed for different pipe kinds. */ union { /* Control pipe */ struct { usb_dma_t reqdma; u_int length; ohci_soft_td_t *setup, *data, *stat; } ctl; /* Interrupt pipe */ struct { int nslots; int pos; } intr; /* Bulk pipe */ struct { u_int length; int isread; } bulk; /* Iso pipe */ struct iso { int next, inuse; } iso; } u; }; #define OHCI_INTR_ENDPT 1 static struct usbd_bus_methods ohci_bus_methods = { ohci_open, ohci_softintr, ohci_poll, ohci_allocm, ohci_freem, ohci_allocx, ohci_freex, }; static struct usbd_pipe_methods ohci_root_ctrl_methods = { ohci_root_ctrl_transfer, ohci_root_ctrl_start, ohci_root_ctrl_abort, ohci_root_ctrl_close, ohci_noop, ohci_root_ctrl_done, }; static struct usbd_pipe_methods ohci_root_intr_methods = { ohci_root_intr_transfer, ohci_root_intr_start, ohci_root_intr_abort, ohci_root_intr_close, ohci_noop, ohci_root_intr_done, }; static struct usbd_pipe_methods ohci_device_ctrl_methods = { ohci_device_ctrl_transfer, ohci_device_ctrl_start, ohci_device_ctrl_abort, ohci_device_ctrl_close, ohci_noop, ohci_device_ctrl_done, }; static struct usbd_pipe_methods ohci_device_intr_methods = { ohci_device_intr_transfer, ohci_device_intr_start, ohci_device_intr_abort, ohci_device_intr_close, ohci_device_clear_toggle, ohci_device_intr_done, }; static struct usbd_pipe_methods ohci_device_bulk_methods = { ohci_device_bulk_transfer, ohci_device_bulk_start, ohci_device_bulk_abort, ohci_device_bulk_close, ohci_device_clear_toggle, ohci_device_bulk_done, }; static struct usbd_pipe_methods ohci_device_isoc_methods = { ohci_device_isoc_transfer, ohci_device_isoc_start, ohci_device_isoc_abort, ohci_device_isoc_close, ohci_noop, ohci_device_isoc_done, }; int ohci_detach(struct ohci_softc *sc, int flags) { int i, rv = 0; sc->sc_dying = 1; - usb_uncallout(sc->sc_tmo_rhsc, ohci_rhsc_enable, sc); + callout_stop(&sc->sc_tmo_rhsc); #if defined(__NetBSD__) || defined(__OpenBSD__) powerhook_disestablish(sc->sc_powerhook); shutdownhook_disestablish(sc->sc_shutdownhook); #endif OWRITE4(sc, OHCI_INTERRUPT_DISABLE, OHCI_ALL_INTRS); OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET); usb_delay_ms(&sc->sc_bus, 300); /* XXX let stray task complete */ for (i = 0; i < OHCI_NO_EDS; i++) ohci_free_sed(sc, sc->sc_eds[i]); ohci_free_sed(sc, sc->sc_isoc_head); ohci_free_sed(sc, sc->sc_bulk_head); ohci_free_sed(sc, sc->sc_ctrl_head); usb_freemem(&sc->sc_bus, &sc->sc_hccadma); return (rv); } ohci_soft_ed_t * ohci_alloc_sed(ohci_softc_t *sc) { ohci_soft_ed_t *sed; usbd_status err; int i, offs; usb_dma_t dma; if (sc->sc_freeeds == NULL) { DPRINTFN(2, ("ohci_alloc_sed: allocating chunk\n")); err = usb_allocmem(&sc->sc_bus, OHCI_SED_SIZE * OHCI_SED_CHUNK, OHCI_ED_ALIGN, &dma); if (err) return (NULL); for(i = 0; i < OHCI_SED_CHUNK; i++) { offs = i * OHCI_SED_SIZE; sed = KERNADDR(&dma, offs); sed->physaddr = DMAADDR(&dma, offs); sed->next = sc->sc_freeeds; sc->sc_freeeds = sed; } } sed = sc->sc_freeeds; sc->sc_freeeds = sed->next; memset(&sed->ed, 0, sizeof(ohci_ed_t)); sed->next = 0; return (sed); } void ohci_free_sed(ohci_softc_t *sc, ohci_soft_ed_t *sed) { sed->next = sc->sc_freeeds; sc->sc_freeeds = sed; } ohci_soft_td_t * ohci_alloc_std(ohci_softc_t *sc) { ohci_soft_td_t *std; usbd_status err; int i, offs; usb_dma_t dma; int s; if (sc->sc_freetds == NULL) { DPRINTFN(2, ("ohci_alloc_std: allocating chunk\n")); err = usb_allocmem(&sc->sc_bus, OHCI_STD_SIZE * OHCI_STD_CHUNK, OHCI_TD_ALIGN, &dma); if (err) return (NULL); s = splusb(); for(i = 0; i < OHCI_STD_CHUNK; i++) { offs = i * OHCI_STD_SIZE; std = KERNADDR(&dma, offs); std->physaddr = DMAADDR(&dma, offs); std->nexttd = sc->sc_freetds; sc->sc_freetds = std; } splx(s); } s = splusb(); std = sc->sc_freetds; sc->sc_freetds = std->nexttd; memset(&std->td, 0, sizeof(ohci_td_t)); std->nexttd = NULL; std->xfer = NULL; ohci_hash_add_td(sc, std); splx(s); return (std); } void ohci_free_std(ohci_softc_t *sc, ohci_soft_td_t *std) { int s; s = splusb(); ohci_hash_rem_td(sc, std); std->nexttd = sc->sc_freetds; sc->sc_freetds = std; splx(s); } usbd_status ohci_alloc_std_chain(struct ohci_pipe *opipe, ohci_softc_t *sc, int alen, int rd, usbd_xfer_handle xfer, ohci_soft_td_t *sp, ohci_soft_td_t **ep) { ohci_soft_td_t *next, *cur, *end; ohci_physaddr_t dataphys, physend; u_int32_t tdflags; int offset = 0; int len, maxp, curlen, curlen2, seg, segoff; struct usb_dma_mapping *dma = &xfer->dmamap; u_int16_t flags = xfer->flags; DPRINTFN(alen < 4096,("ohci_alloc_std_chain: start len=%d\n", alen)); len = alen; cur = sp; end = NULL; maxp = UGETW(opipe->pipe.endpoint->edesc->wMaxPacketSize); tdflags = htole32( (rd ? OHCI_TD_IN : OHCI_TD_OUT) | (flags & USBD_SHORT_XFER_OK ? OHCI_TD_R : 0) | OHCI_TD_NOCC | OHCI_TD_TOGGLE_CARRY | OHCI_TD_SET_DI(6)); seg = 0; segoff = 0; while (len > 0) { next = ohci_alloc_std(sc); if (next == NULL) goto nomem; /* * The OHCI hardware can handle at most one 4k crossing. * The OHCI spec says: If during the data transfer the buffer * address contained in the HC's working copy of * CurrentBufferPointer crosses a 4K boundary, the upper 20 * bits of Buffer End are copied to the working value of * CurrentBufferPointer causing the next buffer address to * be the 0th byte in the same 4K page that contains the * last byte of the buffer (the 4K boundary crossing may * occur within a data packet transfer.) */ KASSERT(seg < dma->nsegs, ("ohci_alloc_std_chain: overrun")); dataphys = dma->segs[seg].ds_addr + segoff; curlen = dma->segs[seg].ds_len - segoff; if (curlen > len) curlen = len; physend = dataphys + curlen - 1; if (OHCI_PAGE(dataphys) != OHCI_PAGE(physend)) { /* Truncate to two OHCI pages if there are more. */ if (curlen > 2 * OHCI_PAGE_SIZE - OHCI_PAGE_OFFSET(dataphys)) curlen = 2 * OHCI_PAGE_SIZE - OHCI_PAGE_OFFSET(dataphys); if (curlen < len) curlen -= curlen % maxp; physend = dataphys + curlen - 1; } else if (OHCI_PAGE_OFFSET(physend + 1) == 0 && curlen < len && curlen + segoff == dma->segs[seg].ds_len) { /* We can possibly include another segment. */ KASSERT(seg + 1 < dma->nsegs, ("ohci_alloc_std_chain: overrun2")); seg++; /* Determine how much of the second segment to use. */ curlen2 = dma->segs[seg].ds_len; if (curlen + curlen2 > len) curlen2 = len - curlen; if (OHCI_PAGE(dma->segs[seg].ds_addr) != OHCI_PAGE(dma->segs[seg].ds_addr + curlen2 - 1)) curlen2 = OHCI_PAGE_SIZE - OHCI_PAGE_OFFSET(dma->segs[seg].ds_addr); if (curlen + curlen2 < len) curlen2 -= (curlen + curlen2) % maxp; if (curlen2 > 0) { /* We can include a second segment */ segoff = curlen2; physend = dma->segs[seg].ds_addr + curlen2 - 1; curlen += curlen2; } else { /* Second segment not usable now. */ seg--; segoff += curlen; } } else { /* Simple case where there is just one OHCI page. */ segoff += curlen; } if (curlen == 0 && len != 0) { /* * A maxp length packet would need to be split. * This shouldn't be possible if PAGE_SIZE >= 4k * and the buffer is contiguous in virtual memory. */ panic("ohci_alloc_std_chain: XXX need to copy"); } if (segoff >= dma->segs[seg].ds_len) { KASSERT(segoff == dma->segs[seg].ds_len, ("ohci_alloc_std_chain: overlap")); seg++; segoff = 0; } DPRINTFN(4,("ohci_alloc_std_chain: dataphys=0x%08x " "len=%d curlen=%d\n", dataphys, len, curlen)); len -= curlen; cur->td.td_flags = tdflags; cur->td.td_cbp = htole32(dataphys); cur->nexttd = next; cur->td.td_nexttd = htole32(next->physaddr); cur->td.td_be = htole32(physend); cur->len = curlen; cur->flags = OHCI_ADD_LEN; cur->xfer = xfer; DPRINTFN(10,("ohci_alloc_std_chain: cbp=0x%08x be=0x%08x\n", dataphys, dataphys + curlen - 1)); if (len < 0) panic("Length went negative: %d curlen %d dma %p offset %08x", len, curlen, dma, (int)0); DPRINTFN(10,("ohci_alloc_std_chain: extend chain\n")); offset += curlen; end = cur; cur = next; } if (((flags & USBD_FORCE_SHORT_XFER) || alen == 0) && alen % UGETW(opipe->pipe.endpoint->edesc->wMaxPacketSize) == 0) { /* Force a 0 length transfer at the end. */ next = ohci_alloc_std(sc); if (next == NULL) goto nomem; cur->td.td_flags = tdflags; cur->td.td_cbp = 0; /* indicate 0 length packet */ cur->nexttd = next; cur->td.td_nexttd = htole32(next->physaddr); cur->td.td_be = ~0; cur->len = 0; cur->flags = 0; cur->xfer = xfer; DPRINTFN(2,("ohci_alloc_std_chain: add 0 xfer\n")); end = cur; } *ep = end; return (USBD_NORMAL_COMPLETION); nomem: /* XXX free chain */ return (USBD_NOMEM); } #if 0 static void ohci_free_std_chain(ohci_softc_t *sc, ohci_soft_td_t *std, ohci_soft_td_t *stdend) { ohci_soft_td_t *p; for (; std != stdend; std = p) { p = std->nexttd; ohci_free_std(sc, std); } } #endif ohci_soft_itd_t * ohci_alloc_sitd(ohci_softc_t *sc) { ohci_soft_itd_t *sitd; usbd_status err; int i, s, offs; usb_dma_t dma; if (sc->sc_freeitds == NULL) { DPRINTFN(2, ("ohci_alloc_sitd: allocating chunk\n")); err = usb_allocmem(&sc->sc_bus, OHCI_SITD_SIZE * OHCI_SITD_CHUNK, OHCI_ITD_ALIGN, &dma); if (err) return (NULL); s = splusb(); for(i = 0; i < OHCI_SITD_CHUNK; i++) { offs = i * OHCI_SITD_SIZE; sitd = KERNADDR(&dma, offs); sitd->physaddr = DMAADDR(&dma, offs); sitd->nextitd = sc->sc_freeitds; sc->sc_freeitds = sitd; } splx(s); } s = splusb(); sitd = sc->sc_freeitds; sc->sc_freeitds = sitd->nextitd; memset(&sitd->itd, 0, sizeof(ohci_itd_t)); sitd->nextitd = NULL; sitd->xfer = NULL; ohci_hash_add_itd(sc, sitd); splx(s); #ifdef DIAGNOSTIC sitd->isdone = 0; #endif return (sitd); } void ohci_free_sitd(ohci_softc_t *sc, ohci_soft_itd_t *sitd) { int s; DPRINTFN(10,("ohci_free_sitd: sitd=%p\n", sitd)); #ifdef DIAGNOSTIC if (!sitd->isdone) { panic("ohci_free_sitd: sitd=%p not done", sitd); return; } /* Warn double free */ sitd->isdone = 0; #endif s = splusb(); ohci_hash_rem_itd(sc, sitd); sitd->nextitd = sc->sc_freeitds; sc->sc_freeitds = sitd; splx(s); } usbd_status ohci_init(ohci_softc_t *sc) { ohci_soft_ed_t *sed, *psed; usbd_status err; int i; u_int32_t rev; DPRINTF(("ohci_init: start\n")); printf("%s:", device_get_nameunit(sc->sc_bus.bdev)); rev = OREAD4(sc, OHCI_REVISION); printf(" OHCI version %d.%d%s\n", OHCI_REV_HI(rev), OHCI_REV_LO(rev), OHCI_REV_LEGACY(rev) ? ", legacy support" : ""); if (OHCI_REV_HI(rev) != 1 || OHCI_REV_LO(rev) != 0) { printf("%s: unsupported OHCI revision\n", device_get_nameunit(sc->sc_bus.bdev)); sc->sc_bus.usbrev = USBREV_UNKNOWN; return (USBD_INVAL); } sc->sc_bus.usbrev = USBREV_1_0; for (i = 0; i < OHCI_HASH_SIZE; i++) LIST_INIT(&sc->sc_hash_tds[i]); for (i = 0; i < OHCI_HASH_SIZE; i++) LIST_INIT(&sc->sc_hash_itds[i]); STAILQ_INIT(&sc->sc_free_xfers); /* XXX determine alignment by R/W */ /* Allocate the HCCA area. */ err = usb_allocmem(&sc->sc_bus, OHCI_HCCA_SIZE, OHCI_HCCA_ALIGN, &sc->sc_hccadma); if (err) return (err); sc->sc_hcca = KERNADDR(&sc->sc_hccadma, 0); memset(sc->sc_hcca, 0, OHCI_HCCA_SIZE); sc->sc_eintrs = OHCI_NORMAL_INTRS; /* Allocate dummy ED that starts the control list. */ sc->sc_ctrl_head = ohci_alloc_sed(sc); if (sc->sc_ctrl_head == NULL) { err = USBD_NOMEM; goto bad1; } sc->sc_ctrl_head->ed.ed_flags |= htole32(OHCI_ED_SKIP); /* Allocate dummy ED that starts the bulk list. */ sc->sc_bulk_head = ohci_alloc_sed(sc); if (sc->sc_bulk_head == NULL) { err = USBD_NOMEM; goto bad2; } sc->sc_bulk_head->ed.ed_flags |= htole32(OHCI_ED_SKIP); /* Allocate dummy ED that starts the isochronous list. */ sc->sc_isoc_head = ohci_alloc_sed(sc); if (sc->sc_isoc_head == NULL) { err = USBD_NOMEM; goto bad3; } sc->sc_isoc_head->ed.ed_flags |= htole32(OHCI_ED_SKIP); /* Allocate all the dummy EDs that make up the interrupt tree. */ for (i = 0; i < OHCI_NO_EDS; i++) { sed = ohci_alloc_sed(sc); if (sed == NULL) { while (--i >= 0) ohci_free_sed(sc, sc->sc_eds[i]); err = USBD_NOMEM; goto bad4; } /* All ED fields are set to 0. */ sc->sc_eds[i] = sed; sed->ed.ed_flags |= htole32(OHCI_ED_SKIP); if (i != 0) psed = sc->sc_eds[(i-1) / 2]; else psed= sc->sc_isoc_head; sed->next = psed; sed->ed.ed_nexted = htole32(psed->physaddr); } /* * Fill HCCA interrupt table. The bit reversal is to get * the tree set up properly to spread the interrupts. */ for (i = 0; i < OHCI_NO_INTRS; i++) sc->sc_hcca->hcca_interrupt_table[revbits[i]] = htole32(sc->sc_eds[OHCI_NO_EDS-OHCI_NO_INTRS+i]->physaddr); #ifdef USB_DEBUG if (ohcidebug > 15) { for (i = 0; i < OHCI_NO_EDS; i++) { printf("ed#%d ", i); ohci_dump_ed(sc->sc_eds[i]); } printf("iso "); ohci_dump_ed(sc->sc_isoc_head); } #endif err = ohci_controller_init(sc); if (err != USBD_NORMAL_COMPLETION) goto bad5; /* Set up the bus struct. */ sc->sc_bus.methods = &ohci_bus_methods; sc->sc_bus.pipe_size = sizeof(struct ohci_pipe); #if defined(__NetBSD__) || defined(__OpenBSD__) sc->sc_powerhook = powerhook_establish(ohci_power, sc); sc->sc_shutdownhook = shutdownhook_establish(ohci_shutdown, sc); #endif - usb_callout_init(sc->sc_tmo_rhsc); + callout_init(&sc->sc_tmo_rhsc, 0); return (USBD_NORMAL_COMPLETION); bad5: for (i = 0; i < OHCI_NO_EDS; i++) ohci_free_sed(sc, sc->sc_eds[i]); bad4: ohci_free_sed(sc, sc->sc_isoc_head); bad3: ohci_free_sed(sc, sc->sc_bulk_head); bad2: ohci_free_sed(sc, sc->sc_ctrl_head); bad1: usb_freemem(&sc->sc_bus, &sc->sc_hccadma); return (err); } static usbd_status ohci_controller_init(ohci_softc_t *sc) { int i; u_int32_t s, ctl, ival, hcr, fm, per, desca; /* Determine in what context we are running. */ ctl = OREAD4(sc, OHCI_CONTROL); if (ctl & OHCI_IR) { /* SMM active, request change */ DPRINTF(("ohci_init: SMM active, request owner change\n")); s = OREAD4(sc, OHCI_COMMAND_STATUS); OWRITE4(sc, OHCI_COMMAND_STATUS, s | OHCI_OCR); for (i = 0; i < 100 && (ctl & OHCI_IR); i++) { usb_delay_ms(&sc->sc_bus, 1); ctl = OREAD4(sc, OHCI_CONTROL); } if ((ctl & OHCI_IR) == 0) { printf("%s: SMM does not respond, resetting\n", device_get_nameunit(sc->sc_bus.bdev)); OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET); goto reset; } #if 0 /* Don't bother trying to reuse the BIOS init, we'll reset it anyway. */ } else if ((ctl & OHCI_HCFS_MASK) != OHCI_HCFS_RESET) { /* BIOS started controller. */ DPRINTF(("ohci_init: BIOS active\n")); if ((ctl & OHCI_HCFS_MASK) != OHCI_HCFS_OPERATIONAL) { OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_OPERATIONAL); usb_delay_ms(&sc->sc_bus, USB_RESUME_DELAY); } #endif } else { DPRINTF(("ohci_init: cold started\n")); reset: /* Controller was cold started. */ usb_delay_ms(&sc->sc_bus, USB_BUS_RESET_DELAY); } /* * This reset should not be necessary according to the OHCI spec, but * without it some controllers do not start. */ DPRINTF(("%s: resetting\n", device_get_nameunit(sc->sc_bus.bdev))); OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET); usb_delay_ms(&sc->sc_bus, USB_BUS_RESET_DELAY); /* We now own the host controller and the bus has been reset. */ ival = OHCI_GET_IVAL(OREAD4(sc, OHCI_FM_INTERVAL)); OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_HCR); /* Reset HC */ /* Nominal time for a reset is 10 us. */ for (i = 0; i < 10; i++) { delay(10); hcr = OREAD4(sc, OHCI_COMMAND_STATUS) & OHCI_HCR; if (!hcr) break; } if (hcr) { printf("%s: reset timeout\n", device_get_nameunit(sc->sc_bus.bdev)); return (USBD_IOERROR); } #ifdef USB_DEBUG if (ohcidebug > 15) ohci_dumpregs(sc); #endif /* The controller is now in SUSPEND state, we have 2ms to finish. */ /* Set up HC registers. */ OWRITE4(sc, OHCI_HCCA, DMAADDR(&sc->sc_hccadma, 0)); OWRITE4(sc, OHCI_CONTROL_HEAD_ED, sc->sc_ctrl_head->physaddr); OWRITE4(sc, OHCI_BULK_HEAD_ED, sc->sc_bulk_head->physaddr); /* disable all interrupts and then switch on all desired interrupts */ OWRITE4(sc, OHCI_INTERRUPT_DISABLE, OHCI_ALL_INTRS); OWRITE4(sc, OHCI_INTERRUPT_ENABLE, sc->sc_eintrs | OHCI_MIE); /* switch on desired functional features */ ctl = OREAD4(sc, OHCI_CONTROL); ctl &= ~(OHCI_CBSR_MASK | OHCI_LES | OHCI_HCFS_MASK | OHCI_IR); ctl |= OHCI_PLE | OHCI_IE | OHCI_CLE | OHCI_BLE | OHCI_RATIO_1_4 | OHCI_HCFS_OPERATIONAL; /* And finally start it! */ OWRITE4(sc, OHCI_CONTROL, ctl); /* * The controller is now OPERATIONAL. Set a some final * registers that should be set earlier, but that the * controller ignores when in the SUSPEND state. */ fm = (OREAD4(sc, OHCI_FM_INTERVAL) & OHCI_FIT) ^ OHCI_FIT; fm |= OHCI_FSMPS(ival) | ival; OWRITE4(sc, OHCI_FM_INTERVAL, fm); per = OHCI_PERIODIC(ival); /* 90% periodic */ OWRITE4(sc, OHCI_PERIODIC_START, per); /* Fiddle the No OverCurrent Protection bit to avoid chip bug. */ desca = OREAD4(sc, OHCI_RH_DESCRIPTOR_A); OWRITE4(sc, OHCI_RH_DESCRIPTOR_A, desca | OHCI_NOCP); OWRITE4(sc, OHCI_RH_STATUS, OHCI_LPSC); /* Enable port power */ usb_delay_ms(&sc->sc_bus, OHCI_ENABLE_POWER_DELAY); OWRITE4(sc, OHCI_RH_DESCRIPTOR_A, desca); /* * The AMD756 requires a delay before re-reading the register, * otherwise it will occasionally report 0 ports. */ sc->sc_noport = 0; for (i = 0; i < 10 && sc->sc_noport == 0; i++) { usb_delay_ms(&sc->sc_bus, OHCI_READ_DESC_DELAY); sc->sc_noport = OHCI_GET_NDP(OREAD4(sc, OHCI_RH_DESCRIPTOR_A)); } #ifdef USB_DEBUG if (ohcidebug > 5) ohci_dumpregs(sc); #endif return (USBD_NORMAL_COMPLETION); } usbd_status ohci_allocm(struct usbd_bus *bus, usb_dma_t *dma, u_int32_t size) { return (usb_allocmem(bus, size, 0, dma)); } void ohci_freem(struct usbd_bus *bus, usb_dma_t *dma) { usb_freemem(bus, dma); } usbd_xfer_handle ohci_allocx(struct usbd_bus *bus) { struct ohci_softc *sc = (struct ohci_softc *)bus; usbd_xfer_handle xfer; xfer = STAILQ_FIRST(&sc->sc_free_xfers); if (xfer != NULL) { STAILQ_REMOVE_HEAD(&sc->sc_free_xfers, next); #ifdef DIAGNOSTIC if (xfer->busy_free != XFER_FREE) { printf("ohci_allocx: xfer=%p not free, 0x%08x\n", xfer, xfer->busy_free); } #endif } else { xfer = malloc(sizeof(struct ohci_xfer), M_USB, M_NOWAIT); } if (xfer != NULL) { memset(xfer, 0, sizeof (struct ohci_xfer)); usb_init_task(&OXFER(xfer)->abort_task, ohci_timeout_task, xfer); OXFER(xfer)->ohci_xfer_flags = 0; #ifdef DIAGNOSTIC xfer->busy_free = XFER_BUSY; #endif } return (xfer); } void ohci_freex(struct usbd_bus *bus, usbd_xfer_handle xfer) { struct ohci_softc *sc = (struct ohci_softc *)bus; #ifdef DIAGNOSTIC if (xfer->busy_free != XFER_BUSY) { printf("ohci_freex: xfer=%p not busy, 0x%08x\n", xfer, xfer->busy_free); return; } xfer->busy_free = XFER_FREE; #endif STAILQ_INSERT_HEAD(&sc->sc_free_xfers, xfer, next); } /* * Shut down the controller when the system is going down. */ void ohci_shutdown(void *v) { ohci_softc_t *sc = v; DPRINTF(("ohci_shutdown: stopping the HC\n")); OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET); } /* * Handle suspend/resume. * * We need to switch to polling mode here, because this routine is * called from an intterupt context. This is all right since we * are almost suspended anyway. */ void ohci_power(int why, void *v) { ohci_softc_t *sc = v; u_int32_t ctl; int s; #ifdef USB_DEBUG DPRINTF(("ohci_power: sc=%p, why=%d\n", sc, why)); ohci_dumpregs(sc); #endif s = splhardusb(); if (why != PWR_RESUME) { sc->sc_bus.use_polling++; ctl = OREAD4(sc, OHCI_CONTROL) & ~OHCI_HCFS_MASK; if (sc->sc_control == 0) { /* * Preserve register values, in case that APM BIOS * does not recover them. */ sc->sc_control = ctl; sc->sc_intre = OREAD4(sc, OHCI_INTERRUPT_ENABLE); } ctl |= OHCI_HCFS_SUSPEND; OWRITE4(sc, OHCI_CONTROL, ctl); usb_delay_ms(&sc->sc_bus, USB_RESUME_WAIT); sc->sc_bus.use_polling--; } else { sc->sc_bus.use_polling++; /* Some broken BIOSes never initialize Controller chip */ ohci_controller_init(sc); if (sc->sc_intre) OWRITE4(sc, OHCI_INTERRUPT_ENABLE, sc->sc_intre & (OHCI_ALL_INTRS | OHCI_MIE)); if (sc->sc_control) ctl = sc->sc_control; else ctl = OREAD4(sc, OHCI_CONTROL); ctl |= OHCI_HCFS_RESUME; OWRITE4(sc, OHCI_CONTROL, ctl); usb_delay_ms(&sc->sc_bus, USB_RESUME_DELAY); ctl = (ctl & ~OHCI_HCFS_MASK) | OHCI_HCFS_OPERATIONAL; OWRITE4(sc, OHCI_CONTROL, ctl); usb_delay_ms(&sc->sc_bus, USB_RESUME_RECOVERY); sc->sc_control = sc->sc_intre = 0; sc->sc_bus.use_polling--; } splx(s); } #ifdef USB_DEBUG void ohci_dumpregs(ohci_softc_t *sc) { DPRINTF(("ohci_dumpregs: rev=0x%08x control=0x%08x command=0x%08x\n", OREAD4(sc, OHCI_REVISION), OREAD4(sc, OHCI_CONTROL), OREAD4(sc, OHCI_COMMAND_STATUS))); DPRINTF((" intrstat=0x%08x intre=0x%08x intrd=0x%08x\n", OREAD4(sc, OHCI_INTERRUPT_STATUS), OREAD4(sc, OHCI_INTERRUPT_ENABLE), OREAD4(sc, OHCI_INTERRUPT_DISABLE))); DPRINTF((" hcca=0x%08x percur=0x%08x ctrlhd=0x%08x\n", OREAD4(sc, OHCI_HCCA), OREAD4(sc, OHCI_PERIOD_CURRENT_ED), OREAD4(sc, OHCI_CONTROL_HEAD_ED))); DPRINTF((" ctrlcur=0x%08x bulkhd=0x%08x bulkcur=0x%08x\n", OREAD4(sc, OHCI_CONTROL_CURRENT_ED), OREAD4(sc, OHCI_BULK_HEAD_ED), OREAD4(sc, OHCI_BULK_CURRENT_ED))); DPRINTF((" done=0x%08x fmival=0x%08x fmrem=0x%08x\n", OREAD4(sc, OHCI_DONE_HEAD), OREAD4(sc, OHCI_FM_INTERVAL), OREAD4(sc, OHCI_FM_REMAINING))); DPRINTF((" fmnum=0x%08x perst=0x%08x lsthrs=0x%08x\n", OREAD4(sc, OHCI_FM_NUMBER), OREAD4(sc, OHCI_PERIODIC_START), OREAD4(sc, OHCI_LS_THRESHOLD))); DPRINTF((" desca=0x%08x descb=0x%08x stat=0x%08x\n", OREAD4(sc, OHCI_RH_DESCRIPTOR_A), OREAD4(sc, OHCI_RH_DESCRIPTOR_B), OREAD4(sc, OHCI_RH_STATUS))); DPRINTF((" port1=0x%08x port2=0x%08x\n", OREAD4(sc, OHCI_RH_PORT_STATUS(1)), OREAD4(sc, OHCI_RH_PORT_STATUS(2)))); DPRINTF((" HCCA: frame_number=0x%04x done_head=0x%08x\n", le32toh(sc->sc_hcca->hcca_frame_number), le32toh(sc->sc_hcca->hcca_done_head))); } #endif static int ohci_intr1(ohci_softc_t *); void ohci_intr(void *p) { ohci_softc_t *sc = p; if (sc == NULL || sc->sc_dying) return; /* If we get an interrupt while polling, then just ignore it. */ if (sc->sc_bus.use_polling) { #ifdef DIAGNOSTIC printf("ohci_intr: ignored interrupt while polling\n"); #endif return; } ohci_intr1(sc); } static int ohci_intr1(ohci_softc_t *sc) { u_int32_t intrs, eintrs; ohci_physaddr_t done; DPRINTFN(14,("ohci_intr1: enter\n")); /* In case the interrupt occurs before initialization has completed. */ if (sc == NULL || sc->sc_hcca == NULL) { #ifdef DIAGNOSTIC printf("ohci_intr: sc->sc_hcca == NULL\n"); #endif return (0); } intrs = 0; done = le32toh(sc->sc_hcca->hcca_done_head); /* The LSb of done is used to inform the HC Driver that an interrupt * condition exists for both the Done list and for another event * recorded in HcInterruptStatus. On an interrupt from the HC, the HC * Driver checks the HccaDoneHead Value. If this value is 0, then the * interrupt was caused by other than the HccaDoneHead update and the * HcInterruptStatus register needs to be accessed to determine that * exact interrupt cause. If HccaDoneHead is nonzero, then a Done list * update interrupt is indicated and if the LSb of done is nonzero, * then an additional interrupt event is indicated and * HcInterruptStatus should be checked to determine its cause. */ if (done != 0) { if (done & ~OHCI_DONE_INTRS) intrs = OHCI_WDH; if (done & OHCI_DONE_INTRS) { intrs |= OREAD4(sc, OHCI_INTERRUPT_STATUS); done &= ~OHCI_DONE_INTRS; } sc->sc_hcca->hcca_done_head = 0; } else intrs = OREAD4(sc, OHCI_INTERRUPT_STATUS) & ~OHCI_WDH; if (intrs == 0) /* nothing to be done (PCI shared interrupt) */ return (0); intrs &= ~OHCI_MIE; OWRITE4(sc, OHCI_INTERRUPT_STATUS, intrs); /* Acknowledge */ eintrs = intrs & sc->sc_eintrs; if (!eintrs) return (0); sc->sc_bus.intr_context++; sc->sc_bus.no_intrs++; DPRINTFN(7, ("ohci_intr: sc=%p intrs=0x%x(0x%x) eintrs=0x%x\n", sc, (u_int)intrs, OREAD4(sc, OHCI_INTERRUPT_STATUS), (u_int)eintrs)); if (eintrs & OHCI_SO) { sc->sc_overrun_cnt++; if (usbd_ratecheck(&sc->sc_overrun_ntc)) { printf("%s: %u scheduling overruns\n", device_get_nameunit(sc->sc_bus.bdev), sc->sc_overrun_cnt); sc->sc_overrun_cnt = 0; } /* XXX do what */ eintrs &= ~OHCI_SO; } if (eintrs & OHCI_WDH) { ohci_add_done(sc, done &~ OHCI_DONE_INTRS); usb_schedsoftintr(&sc->sc_bus); eintrs &= ~OHCI_WDH; } if (eintrs & OHCI_RD) { printf("%s: resume detect\n", device_get_nameunit(sc->sc_bus.bdev)); /* XXX process resume detect */ } if (eintrs & OHCI_UE) { printf("%s: unrecoverable error, controller halted\n", device_get_nameunit(sc->sc_bus.bdev)); OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET); /* XXX what else */ } if (eintrs & OHCI_RHSC) { ohci_rhsc(sc, sc->sc_intrxfer); /* * Disable RHSC interrupt for now, because it will be * on until the port has been reset. */ ohci_rhsc_able(sc, 0); /* Do not allow RHSC interrupts > 1 per second */ - usb_callout(sc->sc_tmo_rhsc, hz, ohci_rhsc_enable, sc); + callout_reset(&sc->sc_tmo_rhsc, hz, ohci_rhsc_enable, sc); eintrs &= ~OHCI_RHSC; } sc->sc_bus.intr_context--; if (eintrs != 0) { /* Block unprocessed interrupts. XXX */ OWRITE4(sc, OHCI_INTERRUPT_DISABLE, eintrs); sc->sc_eintrs &= ~eintrs; printf("%s: blocking intrs 0x%x\n", device_get_nameunit(sc->sc_bus.bdev), eintrs); } return (1); } void ohci_rhsc_able(ohci_softc_t *sc, int on) { DPRINTFN(4, ("ohci_rhsc_able: on=%d\n", on)); if (on) { sc->sc_eintrs |= OHCI_RHSC; OWRITE4(sc, OHCI_INTERRUPT_ENABLE, OHCI_RHSC); } else { sc->sc_eintrs &= ~OHCI_RHSC; OWRITE4(sc, OHCI_INTERRUPT_DISABLE, OHCI_RHSC); } } void ohci_rhsc_enable(void *v_sc) { ohci_softc_t *sc = v_sc; int s; s = splhardusb(); ohci_rhsc_able(sc, 1); splx(s); } #ifdef USB_DEBUG char *ohci_cc_strs[] = { "NO_ERROR", "CRC", "BIT_STUFFING", "DATA_TOGGLE_MISMATCH", "STALL", "DEVICE_NOT_RESPONDING", "PID_CHECK_FAILURE", "UNEXPECTED_PID", "DATA_OVERRUN", "DATA_UNDERRUN", "BUFFER_OVERRUN", "BUFFER_UNDERRUN", "reserved", "reserved", "NOT_ACCESSED", "NOT_ACCESSED" }; #endif void ohci_add_done(ohci_softc_t *sc, ohci_physaddr_t done) { ohci_soft_itd_t *sitd, *sidone, **ip; ohci_soft_td_t *std, *sdone, **p; /* Reverse the done list. */ for (sdone = NULL, sidone = NULL; done != 0; ) { std = ohci_hash_find_td(sc, done); if (std != NULL) { std->dnext = sdone; done = le32toh(std->td.td_nexttd); sdone = std; DPRINTFN(10,("add TD %p\n", std)); continue; } sitd = ohci_hash_find_itd(sc, done); if (sitd != NULL) { sitd->dnext = sidone; done = le32toh(sitd->itd.itd_nextitd); sidone = sitd; DPRINTFN(5,("add ITD %p\n", sitd)); continue; } panic("ohci_add_done: addr 0x%08lx not found", (u_long)done); } /* sdone & sidone now hold the done lists. */ /* Put them on the already processed lists. */ for (p = &sc->sc_sdone; *p != NULL; p = &(*p)->dnext) ; *p = sdone; for (ip = &sc->sc_sidone; *ip != NULL; ip = &(*ip)->dnext) ; *ip = sidone; } void ohci_softintr(void *v) { ohci_softc_t *sc = v; ohci_soft_itd_t *sitd, *sidone, *sitdnext; ohci_soft_td_t *std, *sdone, *stdnext, *p, *n; usbd_xfer_handle xfer; struct ohci_pipe *opipe; int len, cc, s; int i, j, iframes; DPRINTFN(10,("ohci_softintr: enter\n")); sc->sc_bus.intr_context++; s = splhardusb(); sdone = sc->sc_sdone; sc->sc_sdone = NULL; sidone = sc->sc_sidone; sc->sc_sidone = NULL; splx(s); DPRINTFN(10,("ohci_softintr: sdone=%p sidone=%p\n", sdone, sidone)); #ifdef USB_DEBUG if (ohcidebug > 10) { DPRINTF(("ohci_process_done: TD done:\n")); ohci_dump_tds(sdone); } #endif for (std = sdone; std; std = stdnext) { xfer = std->xfer; stdnext = std->dnext; DPRINTFN(10, ("ohci_process_done: std=%p xfer=%p hcpriv=%p\n", std, xfer, (xfer ? xfer->hcpriv : NULL))); if (xfer == NULL) { /* * xfer == NULL: There seems to be no xfer associated * with this TD. It is tailp that happened to end up on * the done queue. */ continue; } if (xfer->status == USBD_CANCELLED || xfer->status == USBD_TIMEOUT) { DPRINTF(("ohci_process_done: cancel/timeout %p\n", xfer)); /* Handled by abort routine. */ continue; } len = std->len; if (std->td.td_cbp != 0) len -= le32toh(std->td.td_be) - le32toh(std->td.td_cbp) + 1; DPRINTFN(10, ("ohci_process_done: len=%d, flags=0x%x\n", len, std->flags)); if (std->flags & OHCI_ADD_LEN) xfer->actlen += len; cc = OHCI_TD_GET_CC(le32toh(std->td.td_flags)); if (cc != OHCI_CC_NO_ERROR) { /* * Endpoint is halted. First unlink all the TDs * belonging to the failed transfer, and then restart * the endpoint. */ opipe = (struct ohci_pipe *)xfer->pipe; DPRINTFN(15,("ohci_process_done: error cc=%d (%s)\n", OHCI_TD_GET_CC(le32toh(std->td.td_flags)), ohci_cc_strs[OHCI_TD_GET_CC(le32toh(std->td.td_flags))])); - usb_uncallout(xfer->timeout_handle, ohci_timeout, xfer); + callout_stop(&xfer->timeout_handle); usb_rem_task(OXFER(xfer)->xfer.pipe->device, &OXFER(xfer)->abort_task); /* Remove all this xfer's TDs from the done queue. */ for (p = std; p->dnext != NULL; p = p->dnext) { if (p->dnext->xfer != xfer) continue; p->dnext = p->dnext->dnext; } /* The next TD may have been removed. */ stdnext = std->dnext; /* Remove all TDs belonging to this xfer. */ for (p = xfer->hcpriv; p->xfer == xfer; p = n) { n = p->nexttd; ohci_free_std(sc, p); } /* clear halt */ opipe->sed->ed.ed_headp = htole32(p->physaddr); OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_CLF); if (cc == OHCI_CC_STALL) xfer->status = USBD_STALLED; else xfer->status = USBD_IOERROR; s = splusb(); usb_transfer_complete(xfer); splx(s); continue; } /* * Skip intermediate TDs. They remain linked from * xfer->hcpriv and we free them when the transfer completes. */ if ((std->flags & OHCI_CALL_DONE) == 0) continue; /* Normal transfer completion */ - usb_uncallout(xfer->timeout_handle, ohci_timeout, xfer); + callout_stop(&xfer->timeout_handle); usb_rem_task(OXFER(xfer)->xfer.pipe->device, &OXFER(xfer)->abort_task); for (p = xfer->hcpriv; p->xfer == xfer; p = n) { n = p->nexttd; ohci_free_std(sc, p); } xfer->status = USBD_NORMAL_COMPLETION; s = splusb(); usb_transfer_complete(xfer); splx(s); } #ifdef USB_DEBUG if (ohcidebug > 10) { DPRINTF(("ohci_softintr: ITD done:\n")); ohci_dump_itds(sidone); } #endif 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) continue; if (xfer->status == USBD_CANCELLED || xfer->status == USBD_TIMEOUT) { DPRINTF(("ohci_process_done: cancel/timeout %p\n", xfer)); /* 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 opipe = (struct ohci_pipe *)xfer->pipe; if (opipe->aborting) continue; if (sitd->flags & OHCI_CALL_DONE) { ohci_soft_itd_t *next; opipe->u.iso.inuse -= xfer->nframes; xfer->status = USBD_NORMAL_COMPLETION; for (i = 0, sitd = xfer->hcpriv;;sitd = next) { next = sitd->nextitd; if (OHCI_ITD_GET_CC(sitd->itd.itd_flags) != OHCI_CC_NO_ERROR) xfer->status = USBD_IOERROR; if (xfer->status == USBD_NORMAL_COMPLETION) { iframes = OHCI_ITD_GET_FC(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; } } if (sitd->flags & OHCI_CALL_DONE) break; } for (sitd = xfer->hcpriv; sitd->xfer == xfer; sitd = next) { next = sitd->nextitd; ohci_free_sitd(sc, sitd); } s = splusb(); usb_transfer_complete(xfer); splx(s); } } #ifdef USB_USE_SOFTINTR if (sc->sc_softwake) { sc->sc_softwake = 0; wakeup(&sc->sc_softwake); } #endif /* USB_USE_SOFTINTR */ sc->sc_bus.intr_context--; DPRINTFN(10,("ohci_softintr: done:\n")); } void ohci_device_ctrl_done(usbd_xfer_handle xfer) { DPRINTFN(10,("ohci_device_ctrl_done: xfer=%p\n", xfer)); #ifdef DIAGNOSTIC if (!(xfer->rqflags & URQ_REQUEST)) { panic("ohci_device_ctrl_done: not a request"); } #endif xfer->hcpriv = NULL; } void ohci_device_intr_done(usbd_xfer_handle xfer) { struct ohci_pipe *opipe = (struct ohci_pipe *)xfer->pipe; ohci_softc_t *sc = (ohci_softc_t *)opipe->pipe.device->bus; usbd_status err; DPRINTFN(10,("ohci_device_intr_done: xfer=%p, actlen=%d\n", xfer, xfer->actlen)); xfer->hcpriv = NULL; if (xfer->pipe->repeat) { err = ohci_device_intr_insert(sc, xfer); if (err) { xfer->status = err; return; } } } void ohci_device_bulk_done(usbd_xfer_handle xfer) { DPRINTFN(10,("ohci_device_bulk_done: xfer=%p, actlen=%d\n", xfer, xfer->actlen)); xfer->hcpriv = NULL; } void ohci_rhsc(ohci_softc_t *sc, usbd_xfer_handle xfer) { usbd_pipe_handle pipe; u_char *p; int i, m; int hstatus; hstatus = OREAD4(sc, OHCI_RH_STATUS); DPRINTF(("ohci_rhsc: sc=%p xfer=%p hstatus=0x%08x\n", sc, xfer, hstatus)); if (xfer == NULL) { /* Just ignore the change. */ return; } pipe = xfer->pipe; p = xfer->buffer; m = min(sc->sc_noport, xfer->length * 8 - 1); memset(p, 0, xfer->length); for (i = 1; i <= m; i++) { /* Pick out CHANGE bits from the status reg. */ if (OREAD4(sc, OHCI_RH_PORT_STATUS(i)) >> 16) p[i/8] |= 1 << (i%8); } DPRINTF(("ohci_rhsc: change=0x%02x\n", *p)); xfer->actlen = xfer->length; xfer->status = USBD_NORMAL_COMPLETION; usb_transfer_complete(xfer); } void ohci_root_intr_done(usbd_xfer_handle xfer) { xfer->hcpriv = NULL; } void ohci_root_ctrl_done(usbd_xfer_handle xfer) { xfer->hcpriv = NULL; } /* * Wait here until controller claims to have an interrupt. * Then call ohci_intr and return. Use timeout to avoid waiting * too long. */ void ohci_waitintr(ohci_softc_t *sc, usbd_xfer_handle xfer) { int timo = xfer->timeout; int usecs; u_int32_t intrs; xfer->status = USBD_IN_PROGRESS; for (usecs = timo * 1000000 / hz; usecs > 0; usecs -= 1000) { usb_delay_ms(&sc->sc_bus, 1); if (sc->sc_dying) break; intrs = OREAD4(sc, OHCI_INTERRUPT_STATUS) & sc->sc_eintrs; DPRINTFN(15,("ohci_waitintr: 0x%04x\n", intrs)); #ifdef USB_DEBUG if (ohcidebug > 15) ohci_dumpregs(sc); #endif if (intrs) { ohci_intr1(sc); if (xfer->status != USBD_IN_PROGRESS) return; } } /* Timeout */ DPRINTF(("ohci_waitintr: timeout\n")); xfer->status = USBD_TIMEOUT; usb_transfer_complete(xfer); /* XXX should free TD */ } void ohci_poll(struct usbd_bus *bus) { ohci_softc_t *sc = (ohci_softc_t *)bus; #ifdef USB_DEBUG static int last; int new; new = OREAD4(sc, OHCI_INTERRUPT_STATUS); if (new != last) { DPRINTFN(10,("ohci_poll: intrs=0x%04x\n", new)); last = new; } #endif if (OREAD4(sc, OHCI_INTERRUPT_STATUS) & sc->sc_eintrs) ohci_intr1(sc); } usbd_status ohci_device_request(usbd_xfer_handle xfer) { struct ohci_pipe *opipe = (struct ohci_pipe *)xfer->pipe; usb_device_request_t *req = &xfer->request; usbd_device_handle dev = opipe->pipe.device; ohci_softc_t *sc = (ohci_softc_t *)dev->bus; ohci_soft_td_t *setup, *stat, *next, *tail; ohci_soft_ed_t *sed; int isread; int len; usbd_status err; int s; isread = req->bmRequestType & UT_READ; len = UGETW(req->wLength); DPRINTFN(3,("ohci_device_control type=0x%02x, request=0x%02x, " "wValue=0x%04x, wIndex=0x%04x len=%d, addr=%d, endpt=%d\n", req->bmRequestType, req->bRequest, UGETW(req->wValue), UGETW(req->wIndex), len, dev->address, opipe->pipe.endpoint->edesc->bEndpointAddress)); setup = opipe->tail.td; stat = ohci_alloc_std(sc); if (stat == NULL) { err = USBD_NOMEM; goto bad1; } tail = ohci_alloc_std(sc); if (tail == NULL) { err = USBD_NOMEM; goto bad2; } tail->xfer = NULL; sed = opipe->sed; opipe->u.ctl.length = len; next = stat; /* Set up data transaction */ if (len != 0) { ohci_soft_td_t *std = stat; 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); setup->td.td_flags = htole32(OHCI_TD_SETUP | OHCI_TD_NOCC | OHCI_TD_TOGGLE_0 | OHCI_TD_SET_DI(6)); setup->td.td_cbp = htole32(DMAADDR(&opipe->u.ctl.reqdma, 0)); setup->nexttd = next; setup->td.td_nexttd = htole32(next->physaddr); setup->td.td_be = htole32(le32toh(setup->td.td_cbp) + sizeof *req - 1); setup->len = 0; setup->xfer = xfer; setup->flags = 0; xfer->hcpriv = setup; stat->td.td_flags = htole32( (isread ? OHCI_TD_OUT : OHCI_TD_IN) | OHCI_TD_NOCC | OHCI_TD_TOGGLE_1 | OHCI_TD_SET_DI(1)); stat->td.td_cbp = 0; 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; #ifdef USB_DEBUG if (ohcidebug > 5) { DPRINTF(("ohci_device_request:\n")); ohci_dump_ed(sed); ohci_dump_tds(setup); } #endif /* Insert ED in schedule */ s = splusb(); sed->ed.ed_tailp = htole32(tail->physaddr); opipe->tail.td = tail; OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_CLF); if (xfer->timeout && !sc->sc_bus.use_polling) { - usb_callout(xfer->timeout_handle, MS_TO_TICKS(xfer->timeout), - ohci_timeout, xfer); + callout_reset(&xfer->timeout_handle, MS_TO_TICKS(xfer->timeout), + ohci_timeout, xfer); } splx(s); #ifdef USB_DEBUG if (ohcidebug > 20) { delay(10000); DPRINTF(("ohci_device_request: status=%x\n", OREAD4(sc, OHCI_COMMAND_STATUS))); ohci_dumpregs(sc); printf("ctrl head:\n"); ohci_dump_ed(sc->sc_ctrl_head); printf("sed:\n"); ohci_dump_ed(sed); ohci_dump_tds(setup); } #endif return (USBD_NORMAL_COMPLETION); bad3: ohci_free_std(sc, tail); bad2: ohci_free_std(sc, stat); bad1: return (err); } /* * Add an ED to the schedule. Called at splusb(). */ void ohci_add_ed(ohci_soft_ed_t *sed, ohci_soft_ed_t *head) { DPRINTFN(8,("ohci_add_ed: sed=%p head=%p\n", sed, head)); SPLUSBCHECK; sed->next = head->next; sed->ed.ed_nexted = head->ed.ed_nexted; head->next = sed; head->ed.ed_nexted = htole32(sed->physaddr); } /* * Remove an ED from the schedule. Called at splusb(). */ void ohci_rem_ed(ohci_soft_ed_t *sed, ohci_soft_ed_t *head) { ohci_soft_ed_t *p; SPLUSBCHECK; /* XXX */ for (p = head; p != NULL && p->next != sed; p = p->next) ; if (p == NULL) panic("ohci_rem_ed: ED not found"); p->next = sed->next; p->ed.ed_nexted = sed->ed.ed_nexted; } /* * When a transfer is completed the TD is added to the done queue by * the host controller. This queue is the processed by software. * Unfortunately the queue contains the physical address of the TD * and we have no simple way to translate this back to a kernel address. * To make the translation possible (and fast) we use a hash table of * TDs currently in the schedule. The physical address is used as the * hash value. */ #define HASH(a) (((a) >> 4) % OHCI_HASH_SIZE) /* Called at splusb() */ void ohci_hash_add_td(ohci_softc_t *sc, ohci_soft_td_t *std) { int h = HASH(std->physaddr); SPLUSBCHECK; LIST_INSERT_HEAD(&sc->sc_hash_tds[h], std, hnext); } /* Called at splusb() */ void ohci_hash_rem_td(ohci_softc_t *sc, ohci_soft_td_t *std) { SPLUSBCHECK; LIST_REMOVE(std, hnext); } ohci_soft_td_t * ohci_hash_find_td(ohci_softc_t *sc, ohci_physaddr_t a) { int h = HASH(a); ohci_soft_td_t *std; /* if these are present they should be masked out at an earlier * stage. */ KASSERT((a&~OHCI_HEADMASK) == 0, ("%s: 0x%b has lower bits set\n", device_get_nameunit(sc->sc_bus.bdev), (int) a, "\20\1HALT\2TOGGLE")); for (std = LIST_FIRST(&sc->sc_hash_tds[h]); std != NULL; std = LIST_NEXT(std, hnext)) if (std->physaddr == a) return (std); DPRINTF(("%s: ohci_hash_find_td: addr 0x%08lx not found\n", device_get_nameunit(sc->sc_bus.bdev), (u_long) a)); return (NULL); } /* Called at splusb() */ void ohci_hash_add_itd(ohci_softc_t *sc, ohci_soft_itd_t *sitd) { int h = HASH(sitd->physaddr); SPLUSBCHECK; DPRINTFN(10,("ohci_hash_add_itd: sitd=%p physaddr=0x%08lx\n", sitd, (u_long)sitd->physaddr)); LIST_INSERT_HEAD(&sc->sc_hash_itds[h], sitd, hnext); } /* Called at splusb() */ void ohci_hash_rem_itd(ohci_softc_t *sc, ohci_soft_itd_t *sitd) { SPLUSBCHECK; DPRINTFN(10,("ohci_hash_rem_itd: sitd=%p physaddr=0x%08lx\n", sitd, (u_long)sitd->physaddr)); LIST_REMOVE(sitd, hnext); } ohci_soft_itd_t * ohci_hash_find_itd(ohci_softc_t *sc, ohci_physaddr_t a) { int h = HASH(a); ohci_soft_itd_t *sitd; for (sitd = LIST_FIRST(&sc->sc_hash_itds[h]); sitd != NULL; sitd = LIST_NEXT(sitd, hnext)) if (sitd->physaddr == a) return (sitd); return (NULL); } void ohci_timeout(void *addr) { struct ohci_xfer *oxfer = addr; struct ohci_pipe *opipe = (struct ohci_pipe *)oxfer->xfer.pipe; ohci_softc_t *sc = (ohci_softc_t *)opipe->pipe.device->bus; DPRINTF(("ohci_timeout: oxfer=%p\n", oxfer)); if (sc->sc_dying) { ohci_abort_xfer(&oxfer->xfer, USBD_TIMEOUT); return; } /* Execute the abort in a process context. */ usb_add_task(oxfer->xfer.pipe->device, &oxfer->abort_task, USB_TASKQ_HC); } void ohci_timeout_task(void *addr) { usbd_xfer_handle xfer = addr; int s; DPRINTF(("ohci_timeout_task: xfer=%p\n", xfer)); s = splusb(); ohci_abort_xfer(xfer, USBD_TIMEOUT); splx(s); } #ifdef USB_DEBUG void ohci_dump_tds(ohci_soft_td_t *std) { for (; std; std = std->nexttd) ohci_dump_td(std); } void ohci_dump_td(ohci_soft_td_t *std) { char sbuf[128]; bitmask_snprintf((u_int32_t)le32toh(std->td.td_flags), "\20\23R\24OUT\25IN\31TOG1\32SETTOGGLE", sbuf, sizeof(sbuf)); printf("TD(%p) at %08lx: %s delay=%d ec=%d cc=%d\ncbp=0x%08lx " "nexttd=0x%08lx be=0x%08lx\n", std, (u_long)std->physaddr, sbuf, OHCI_TD_GET_DI(le32toh(std->td.td_flags)), OHCI_TD_GET_EC(le32toh(std->td.td_flags)), OHCI_TD_GET_CC(le32toh(std->td.td_flags)), (u_long)le32toh(std->td.td_cbp), (u_long)le32toh(std->td.td_nexttd), (u_long)le32toh(std->td.td_be)); } void ohci_dump_itd(ohci_soft_itd_t *sitd) { int i; printf("ITD(%p) at %08lx: sf=%d di=%d fc=%d cc=%d\n" "bp0=0x%08lx next=0x%08lx be=0x%08lx\n", sitd, (u_long)sitd->physaddr, OHCI_ITD_GET_SF(le32toh(sitd->itd.itd_flags)), OHCI_ITD_GET_DI(le32toh(sitd->itd.itd_flags)), OHCI_ITD_GET_FC(le32toh(sitd->itd.itd_flags)), OHCI_ITD_GET_CC(le32toh(sitd->itd.itd_flags)), (u_long)le32toh(sitd->itd.itd_bp0), (u_long)le32toh(sitd->itd.itd_nextitd), (u_long)le32toh(sitd->itd.itd_be)); for (i = 0; i < OHCI_ITD_NOFFSET; i++) printf("offs[%d]=0x%04x ", i, (u_int)le16toh(sitd->itd.itd_offset[i])); printf("\n"); } void ohci_dump_itds(ohci_soft_itd_t *sitd) { for (; sitd; sitd = sitd->nextitd) ohci_dump_itd(sitd); } void ohci_dump_ed(ohci_soft_ed_t *sed) { char sbuf[128], sbuf2[128]; bitmask_snprintf((u_int32_t)le32toh(sed->ed.ed_flags), "\20\14OUT\15IN\16LOWSPEED\17SKIP\20ISO", sbuf, sizeof(sbuf)); bitmask_snprintf((u_int32_t)le32toh(sed->ed.ed_headp), "\20\1HALT\2CARRY", sbuf2, sizeof(sbuf2)); printf("ED(%p) at 0x%08lx: addr=%d endpt=%d maxp=%d flags=%s\ntailp=0x%08lx " "headflags=%s headp=0x%08lx nexted=0x%08lx\n", sed, (u_long)sed->physaddr, OHCI_ED_GET_FA(le32toh(sed->ed.ed_flags)), OHCI_ED_GET_EN(le32toh(sed->ed.ed_flags)), OHCI_ED_GET_MAXP(le32toh(sed->ed.ed_flags)), sbuf, (u_long)le32toh(sed->ed.ed_tailp), sbuf2, (u_long)le32toh(sed->ed.ed_headp), (u_long)le32toh(sed->ed.ed_nexted)); } #endif usbd_status ohci_open(usbd_pipe_handle pipe) { usbd_device_handle dev = pipe->device; ohci_softc_t *sc = (ohci_softc_t *)dev->bus; usb_endpoint_descriptor_t *ed = pipe->endpoint->edesc; struct ohci_pipe *opipe = (struct ohci_pipe *)pipe; u_int8_t addr = dev->address; u_int8_t xfertype = ed->bmAttributes & UE_XFERTYPE; ohci_soft_ed_t *sed; ohci_soft_td_t *std; ohci_soft_itd_t *sitd; ohci_physaddr_t tdphys; u_int32_t fmt; usbd_status err; int s; int ival; DPRINTFN(1, ("ohci_open: pipe=%p, addr=%d, endpt=%d (%d)\n", pipe, addr, ed->bEndpointAddress, sc->sc_addr)); if (sc->sc_dying) return (USBD_IOERROR); std = NULL; sed = NULL; if (addr == sc->sc_addr) { switch (ed->bEndpointAddress) { case USB_CONTROL_ENDPOINT: pipe->methods = &ohci_root_ctrl_methods; break; case UE_DIR_IN | OHCI_INTR_ENDPT: pipe->methods = &ohci_root_intr_methods; break; default: return (USBD_INVAL); } } else { sed = ohci_alloc_sed(sc); if (sed == NULL) goto bad0; opipe->sed = sed; if (xfertype == UE_ISOCHRONOUS) { sitd = ohci_alloc_sitd(sc); 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) fmt |= OHCI_ED_DIR_IN; else fmt |= OHCI_ED_DIR_OUT; } else { std = ohci_alloc_std(sc); if (std == NULL) goto bad1; opipe->tail.td = std; tdphys = std->physaddr; fmt = OHCI_ED_FORMAT_GEN | OHCI_ED_DIR_TD; } sed->ed.ed_flags = htole32( OHCI_ED_SET_FA(addr) | OHCI_ED_SET_EN(UE_GET_ADDR(ed->bEndpointAddress)) | (dev->speed == USB_SPEED_LOW ? OHCI_ED_SPEED : 0) | fmt | OHCI_ED_SET_MAXP(UGETW(ed->wMaxPacketSize))); sed->ed.ed_headp = htole32(tdphys | (pipe->endpoint->savedtoggle ? OHCI_TOGGLECARRY : 0)); sed->ed.ed_tailp = htole32(tdphys); switch (xfertype) { case UE_CONTROL: pipe->methods = &ohci_device_ctrl_methods; err = usb_allocmem(&sc->sc_bus, sizeof(usb_device_request_t), 0, &opipe->u.ctl.reqdma); if (err) goto bad; s = splusb(); ohci_add_ed(sed, sc->sc_ctrl_head); splx(s); break; case UE_INTERRUPT: pipe->methods = &ohci_device_intr_methods; ival = pipe->interval; if (ival == USBD_DEFAULT_INTERVAL) ival = ed->bInterval; return (ohci_device_setintr(sc, opipe, ival)); case UE_ISOCHRONOUS: pipe->methods = &ohci_device_isoc_methods; return (ohci_setup_isoc(pipe)); case UE_BULK: pipe->methods = &ohci_device_bulk_methods; s = splusb(); ohci_add_ed(sed, sc->sc_bulk_head); splx(s); break; } } return (USBD_NORMAL_COMPLETION); bad: if (std != NULL) ohci_free_std(sc, std); bad1: if (sed != NULL) ohci_free_sed(sc, sed); bad0: return (USBD_NOMEM); } /* * Close a reqular pipe. * Assumes that there are no pending transactions. */ void ohci_close_pipe(usbd_pipe_handle pipe, ohci_soft_ed_t *head) { struct ohci_pipe *opipe = (struct ohci_pipe *)pipe; ohci_softc_t *sc = (ohci_softc_t *)pipe->device->bus; ohci_soft_ed_t *sed = opipe->sed; int s; s = splusb(); #ifdef DIAGNOSTIC sed->ed.ed_flags |= htole32(OHCI_ED_SKIP); if ((le32toh(sed->ed.ed_tailp) & OHCI_HEADMASK) != (le32toh(sed->ed.ed_headp) & OHCI_HEADMASK)) { ohci_soft_td_t *std; std = ohci_hash_find_td(sc, le32toh(sed->ed.ed_headp)); printf("ohci_close_pipe: pipe not empty sed=%p hd=0x%x " "tl=0x%x pipe=%p, std=%p\n", sed, (int)le32toh(sed->ed.ed_headp), (int)le32toh(sed->ed.ed_tailp), pipe, std); #ifdef USB_DEBUG usbd_dump_pipe(&opipe->pipe); #endif #ifdef USB_DEBUG ohci_dump_ed(sed); if (std) ohci_dump_td(std); #endif usb_delay_ms(&sc->sc_bus, 2); if ((le32toh(sed->ed.ed_tailp) & OHCI_HEADMASK) != (le32toh(sed->ed.ed_headp) & OHCI_HEADMASK)) printf("ohci_close_pipe: pipe still not empty\n"); } #endif ohci_rem_ed(sed, head); /* Make sure the host controller is not touching this ED */ usb_delay_ms(&sc->sc_bus, 1); splx(s); pipe->endpoint->savedtoggle = (le32toh(sed->ed.ed_headp) & OHCI_TOGGLECARRY) ? 1 : 0; ohci_free_sed(sc, opipe->sed); } /* * Abort a device request. * If this routine is called at splusb() it guarantees that the request * will be removed from the hardware scheduling and that the callback * for it will be called with USBD_CANCELLED status. * It's impossible to guarantee that the requested transfer will not * have happened since the hardware runs concurrently. * If the transaction has already happened we rely on the ordinary * interrupt processing to process it. */ void ohci_abort_xfer(usbd_xfer_handle xfer, usbd_status status) { struct ohci_xfer *oxfer = OXFER(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 = opipe->sed; ohci_soft_td_t *p, *n; ohci_physaddr_t headp; int s, hit; DPRINTF(("ohci_abort_xfer: xfer=%p pipe=%p sed=%p\n", xfer, opipe,sed)); if (sc->sc_dying) { /* If we're dying, just do the software part. */ s = splusb(); xfer->status = status; /* make software ignore it */ - usb_uncallout(xfer->timeout_handle, ohci_timeout, xfer); + callout_stop(&xfer->timeout_handle); usb_rem_task(xfer->pipe->device, &OXFER(xfer)->abort_task); usb_transfer_complete(xfer); splx(s); return; } if (xfer->device->bus->intr_context || !curproc) panic("ohci_abort_xfer: not in process context"); /* * If an abort is already in progress then just wait for it to * complete and return. */ if (oxfer->ohci_xfer_flags & OHCI_XFER_ABORTING) { DPRINTFN(2, ("ohci_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, ("ohci_abort_xfer: waiting for abort to finish\n")); oxfer->ohci_xfer_flags |= OHCI_XFER_ABORTWAIT; while (oxfer->ohci_xfer_flags & OHCI_XFER_ABORTING) tsleep(&oxfer->ohci_xfer_flags, PZERO, "ohciaw", 0); return; } /* * Step 1: Make interrupt routine and hardware ignore xfer. */ s = splusb(); oxfer->ohci_xfer_flags |= OHCI_XFER_ABORTING; xfer->status = status; /* make software ignore it */ - usb_uncallout(xfer->timeout_handle, ohci_timeout, xfer); + callout_stop(&xfer->timeout_handle); usb_rem_task(xfer->pipe->device, &OXFER(xfer)->abort_task); splx(s); DPRINTFN(1,("ohci_abort_xfer: stop ed=%p\n", sed)); sed->ed.ed_flags |= htole32(OHCI_ED_SKIP); /* force hardware skip */ /* * Step 2: Wait until we know hardware has finished any possible * use of the xfer. Also make sure the soft interrupt routine * has run. */ usb_delay_ms(opipe->pipe.device->bus, 20); /* Hardware finishes in 1ms */ s = splusb(); #ifdef USB_USE_SOFTINTR sc->sc_softwake = 1; #endif /* USB_USE_SOFTINTR */ usb_schedsoftintr(&sc->sc_bus); #ifdef USB_USE_SOFTINTR tsleep(&sc->sc_softwake, PZERO, "ohciab", 0); #endif /* USB_USE_SOFTINTR */ splx(s); /* * Step 3: 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. */ s = splusb(); /* XXX why? */ p = xfer->hcpriv; #ifdef DIAGNOSTIC if (p == NULL) { oxfer->ohci_xfer_flags &= ~OHCI_XFER_ABORTING; /* XXX */ splx(s); printf("ohci_abort_xfer: hcpriv is NULL\n"); return; } #endif #ifdef USB_DEBUG if (ohcidebug > 1) { DPRINTF(("ohci_abort_xfer: sed=\n")); ohci_dump_ed(sed); ohci_dump_tds(p); } #endif headp = le32toh(sed->ed.ed_headp) & OHCI_HEADMASK; hit = 0; for (; p->xfer == xfer; p = n) { hit |= headp == p->physaddr; n = p->nexttd; ohci_free_std(sc, p); } /* Zap headp register if hardware pointed inside the xfer. */ if (hit) { DPRINTFN(1,("ohci_abort_xfer: set hd=0x08%x, tl=0x%08x\n", (int)p->physaddr, (int)le32toh(sed->ed.ed_tailp))); sed->ed.ed_headp = htole32(p->physaddr); /* unlink TDs */ } else { DPRINTFN(1,("ohci_abort_xfer: no hit\n")); } /* * Step 4: Turn on hardware again. */ sed->ed.ed_flags &= htole32(~OHCI_ED_SKIP); /* remove hardware skip */ /* * Step 5: Execute callback. */ /* Do the wakeup first to avoid touching the xfer after the callback. */ oxfer->ohci_xfer_flags &= ~OHCI_XFER_ABORTING; if (oxfer->ohci_xfer_flags & OHCI_XFER_ABORTWAIT) { oxfer->ohci_xfer_flags &= ~OHCI_XFER_ABORTWAIT; wakeup(&oxfer->ohci_xfer_flags); } usb_transfer_complete(xfer); splx(s); } /* * Data structures and routines to emulate the root hub. */ static usb_device_descriptor_t ohci_devd = { USB_DEVICE_DESCRIPTOR_SIZE, UDESC_DEVICE, /* type */ {0x00, 0x01}, /* USB version */ UDCLASS_HUB, /* class */ UDSUBCLASS_HUB, /* subclass */ UDPROTO_FSHUB, /* protocol */ 64, /* max packet */ {0},{0},{0x00,0x01}, /* device id */ 1,2,0, /* string indicies */ 1 /* # of configurations */ }; static usb_config_descriptor_t ohci_confd = { USB_CONFIG_DESCRIPTOR_SIZE, UDESC_CONFIG, {USB_CONFIG_DESCRIPTOR_SIZE + USB_INTERFACE_DESCRIPTOR_SIZE + USB_ENDPOINT_DESCRIPTOR_SIZE}, 1, 1, 0, UC_SELF_POWERED, 0 /* max power */ }; static usb_interface_descriptor_t ohci_ifcd = { USB_INTERFACE_DESCRIPTOR_SIZE, UDESC_INTERFACE, 0, 0, 1, UICLASS_HUB, UISUBCLASS_HUB, UIPROTO_FSHUB, 0 }; static usb_endpoint_descriptor_t ohci_endpd = { USB_ENDPOINT_DESCRIPTOR_SIZE, UDESC_ENDPOINT, UE_DIR_IN | OHCI_INTR_ENDPT, UE_INTERRUPT, {8, 0}, /* max packet */ 255 }; static usb_hub_descriptor_t ohci_hubd = { USB_HUB_DESCRIPTOR_SIZE, UDESC_HUB, 0, {0,0}, 0, 0, {0}, }; static int ohci_str(usb_string_descriptor_t *p, int l, const char *s) { int i; if (l == 0) return (0); p->bLength = 2 * strlen(s) + 2; if (l == 1) return (1); p->bDescriptorType = UDESC_STRING; l -= 2; for (i = 0; s[i] && l > 1; i++, l -= 2) USETW2(p->bString[i], 0, s[i]); return (2*i+2); } /* * Simulate a hardware hub by handling all the necessary requests. */ static usbd_status ohci_root_ctrl_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, start first */ return (ohci_root_ctrl_start(STAILQ_FIRST(&xfer->pipe->queue))); } static usbd_status ohci_root_ctrl_start(usbd_xfer_handle xfer) { ohci_softc_t *sc = (ohci_softc_t *)xfer->pipe->device->bus; usb_device_request_t *req; void *buf = NULL; int port, i; int s, len, value, index, l, totlen = 0; usb_port_status_t ps; usb_hub_descriptor_t hubd; usbd_status err; u_int32_t v; if (sc->sc_dying) return (USBD_IOERROR); #ifdef DIAGNOSTIC if (!(xfer->rqflags & URQ_REQUEST)) /* XXX panic */ return (USBD_INVAL); #endif req = &xfer->request; DPRINTFN(4,("ohci_root_ctrl_control type=0x%02x request=%02x\n", req->bmRequestType, req->bRequest)); len = UGETW(req->wLength); value = UGETW(req->wValue); index = UGETW(req->wIndex); if (len != 0) buf = xfer->buffer; #define C(x,y) ((x) | ((y) << 8)) switch(C(req->bRequest, req->bmRequestType)) { case C(UR_CLEAR_FEATURE, UT_WRITE_DEVICE): case C(UR_CLEAR_FEATURE, UT_WRITE_INTERFACE): case C(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT): /* * DEVICE_REMOTE_WAKEUP and ENDPOINT_HALT are no-ops * for the integrated root hub. */ break; case C(UR_GET_CONFIG, UT_READ_DEVICE): if (len > 0) { *(u_int8_t *)buf = sc->sc_conf; totlen = 1; } break; case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE): DPRINTFN(8,("ohci_root_ctrl_control wValue=0x%04x\n", value)); switch(value >> 8) { case UDESC_DEVICE: if ((value & 0xff) != 0) { err = USBD_IOERROR; goto ret; } totlen = l = min(len, USB_DEVICE_DESCRIPTOR_SIZE); USETW(ohci_devd.idVendor, sc->sc_id_vendor); memcpy(buf, &ohci_devd, l); break; case UDESC_CONFIG: if ((value & 0xff) != 0) { err = USBD_IOERROR; goto ret; } totlen = l = min(len, USB_CONFIG_DESCRIPTOR_SIZE); memcpy(buf, &ohci_confd, l); buf = (char *)buf + l; len -= l; l = min(len, USB_INTERFACE_DESCRIPTOR_SIZE); totlen += l; memcpy(buf, &ohci_ifcd, l); buf = (char *)buf + l; len -= l; l = min(len, USB_ENDPOINT_DESCRIPTOR_SIZE); totlen += l; memcpy(buf, &ohci_endpd, l); break; case UDESC_STRING: if (len == 0) break; *(u_int8_t *)buf = 0; totlen = 1; switch (value & 0xff) { case 1: /* Vendor */ totlen = ohci_str(buf, len, sc->sc_vendor); break; case 2: /* Product */ totlen = ohci_str(buf, len, "OHCI root hub"); break; } break; default: err = USBD_IOERROR; goto ret; } break; case C(UR_GET_INTERFACE, UT_READ_INTERFACE): if (len > 0) { *(u_int8_t *)buf = 0; totlen = 1; } break; case C(UR_GET_STATUS, UT_READ_DEVICE): if (len > 1) { USETW(((usb_status_t *)buf)->wStatus,UDS_SELF_POWERED); totlen = 2; } break; case C(UR_GET_STATUS, UT_READ_INTERFACE): case C(UR_GET_STATUS, UT_READ_ENDPOINT): if (len > 1) { USETW(((usb_status_t *)buf)->wStatus, 0); totlen = 2; } break; case C(UR_SET_ADDRESS, UT_WRITE_DEVICE): if (value >= USB_MAX_DEVICES) { err = USBD_IOERROR; goto ret; } sc->sc_addr = value; break; case C(UR_SET_CONFIG, UT_WRITE_DEVICE): if (value != 0 && value != 1) { err = USBD_IOERROR; goto ret; } sc->sc_conf = value; break; case C(UR_SET_DESCRIPTOR, UT_WRITE_DEVICE): break; case C(UR_SET_FEATURE, UT_WRITE_DEVICE): case C(UR_SET_FEATURE, UT_WRITE_INTERFACE): case C(UR_SET_FEATURE, UT_WRITE_ENDPOINT): err = USBD_IOERROR; goto ret; case C(UR_SET_INTERFACE, UT_WRITE_INTERFACE): break; case C(UR_SYNCH_FRAME, UT_WRITE_ENDPOINT): break; /* Hub requests */ case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_DEVICE): break; case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_OTHER): DPRINTFN(8, ("ohci_root_ctrl_control: UR_CLEAR_PORT_FEATURE " "port=%d feature=%d\n", index, value)); if (index < 1 || index > sc->sc_noport) { err = USBD_IOERROR; goto ret; } port = OHCI_RH_PORT_STATUS(index); switch(value) { case UHF_PORT_ENABLE: OWRITE4(sc, port, UPS_CURRENT_CONNECT_STATUS); break; case UHF_PORT_SUSPEND: OWRITE4(sc, port, UPS_OVERCURRENT_INDICATOR); break; case UHF_PORT_POWER: /* Yes, writing to the LOW_SPEED bit clears power. */ OWRITE4(sc, port, UPS_LOW_SPEED); break; case UHF_C_PORT_CONNECTION: OWRITE4(sc, port, UPS_C_CONNECT_STATUS << 16); break; case UHF_C_PORT_ENABLE: OWRITE4(sc, port, UPS_C_PORT_ENABLED << 16); break; case UHF_C_PORT_SUSPEND: OWRITE4(sc, port, UPS_C_SUSPEND << 16); break; case UHF_C_PORT_OVER_CURRENT: OWRITE4(sc, port, UPS_C_OVERCURRENT_INDICATOR << 16); break; case UHF_C_PORT_RESET: OWRITE4(sc, port, UPS_C_PORT_RESET << 16); break; default: err = USBD_IOERROR; goto ret; } switch(value) { case UHF_C_PORT_CONNECTION: case UHF_C_PORT_ENABLE: case UHF_C_PORT_SUSPEND: case UHF_C_PORT_OVER_CURRENT: case UHF_C_PORT_RESET: /* Enable RHSC interrupt if condition is cleared. */ if ((OREAD4(sc, port) >> 16) == 0) ohci_rhsc_able(sc, 1); break; default: break; } break; case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE): if ((value & 0xff) != 0) { err = USBD_IOERROR; goto ret; } v = OREAD4(sc, OHCI_RH_DESCRIPTOR_A); hubd = ohci_hubd; hubd.bNbrPorts = sc->sc_noport; USETW(hubd.wHubCharacteristics, (v & OHCI_NPS ? UHD_PWR_NO_SWITCH : v & OHCI_PSM ? UHD_PWR_GANGED : UHD_PWR_INDIVIDUAL) /* XXX overcurrent */ ); hubd.bPwrOn2PwrGood = OHCI_GET_POTPGT(v); v = OREAD4(sc, OHCI_RH_DESCRIPTOR_B); for (i = 0, l = sc->sc_noport; l > 0; i++, l -= 8, v >>= 8) hubd.DeviceRemovable[i++] = (u_int8_t)v; hubd.bDescLength = USB_HUB_DESCRIPTOR_SIZE + i; l = min(len, hubd.bDescLength); totlen = l; memcpy(buf, &hubd, l); break; case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE): if (len != 4) { err = USBD_IOERROR; goto ret; } memset(buf, 0, len); /* ? XXX */ totlen = len; break; case C(UR_GET_STATUS, UT_READ_CLASS_OTHER): DPRINTFN(8,("ohci_root_ctrl_transfer: get port status i=%d\n", index)); if (index < 1 || index > sc->sc_noport) { err = USBD_IOERROR; goto ret; } if (len != 4) { err = USBD_IOERROR; goto ret; } v = OREAD4(sc, OHCI_RH_PORT_STATUS(index)); DPRINTFN(8,("ohci_root_ctrl_transfer: port status=0x%04x\n", v)); USETW(ps.wPortStatus, v); USETW(ps.wPortChange, v >> 16); l = min(len, sizeof ps); memcpy(buf, &ps, l); totlen = l; break; case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE): err = USBD_IOERROR; goto ret; case C(UR_SET_FEATURE, UT_WRITE_CLASS_DEVICE): break; case C(UR_SET_FEATURE, UT_WRITE_CLASS_OTHER): if (index < 1 || index > sc->sc_noport) { err = USBD_IOERROR; goto ret; } port = OHCI_RH_PORT_STATUS(index); switch(value) { case UHF_PORT_ENABLE: OWRITE4(sc, port, UPS_PORT_ENABLED); break; case UHF_PORT_SUSPEND: OWRITE4(sc, port, UPS_SUSPEND); break; case UHF_PORT_RESET: DPRINTFN(5,("ohci_root_ctrl_transfer: reset port %d\n", index)); OWRITE4(sc, port, UPS_RESET); for (i = 0; i < 5; i++) { usb_delay_ms(&sc->sc_bus, USB_PORT_ROOT_RESET_DELAY); if (sc->sc_dying) { err = USBD_IOERROR; goto ret; } if ((OREAD4(sc, port) & UPS_RESET) == 0) break; } DPRINTFN(8,("ohci port %d reset, status = 0x%04x\n", index, OREAD4(sc, port))); break; case UHF_PORT_POWER: DPRINTFN(2,("ohci_root_ctrl_transfer: set port power " "%d\n", index)); OWRITE4(sc, port, UPS_PORT_POWER); break; default: err = USBD_IOERROR; goto ret; } break; default: err = USBD_IOERROR; goto ret; } xfer->actlen = totlen; err = USBD_NORMAL_COMPLETION; ret: xfer->status = err; s = splusb(); usb_transfer_complete(xfer); splx(s); return (USBD_IN_PROGRESS); } /* Abort a root control request. */ static void ohci_root_ctrl_abort(usbd_xfer_handle xfer) { /* Nothing to do, all transfers are synchronous. */ } /* Close the root pipe. */ static void ohci_root_ctrl_close(usbd_pipe_handle pipe) { DPRINTF(("ohci_root_ctrl_close\n")); /* Nothing to do. */ } static usbd_status ohci_root_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, start first */ return (ohci_root_intr_start(STAILQ_FIRST(&xfer->pipe->queue))); } static usbd_status ohci_root_intr_start(usbd_xfer_handle xfer) { usbd_pipe_handle pipe = xfer->pipe; ohci_softc_t *sc = (ohci_softc_t *)pipe->device->bus; if (sc->sc_dying) return (USBD_IOERROR); sc->sc_intrxfer = xfer; return (USBD_IN_PROGRESS); } /* Abort a root interrupt request. */ static void ohci_root_intr_abort(usbd_xfer_handle xfer) { int s; if (xfer->pipe->intrxfer == xfer) { DPRINTF(("ohci_root_intr_abort: remove\n")); xfer->pipe->intrxfer = NULL; } xfer->status = USBD_CANCELLED; s = splusb(); usb_transfer_complete(xfer); splx(s); } /* Close the root pipe. */ static void ohci_root_intr_close(usbd_pipe_handle pipe) { ohci_softc_t *sc = (ohci_softc_t *)pipe->device->bus; DPRINTF(("ohci_root_intr_close\n")); sc->sc_intrxfer = NULL; } /************************/ static usbd_status ohci_device_ctrl_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, start first */ return (ohci_device_ctrl_start(STAILQ_FIRST(&xfer->pipe->queue))); } static usbd_status ohci_device_ctrl_start(usbd_xfer_handle xfer) { ohci_softc_t *sc = (ohci_softc_t *)xfer->pipe->device->bus; usbd_status err; if (sc->sc_dying) return (USBD_IOERROR); #ifdef DIAGNOSTIC if (!(xfer->rqflags & URQ_REQUEST)) { /* XXX panic */ printf("ohci_device_ctrl_transfer: not a request\n"); return (USBD_INVAL); } #endif err = ohci_device_request(xfer); if (err) return (err); if (sc->sc_bus.use_polling) ohci_waitintr(sc, xfer); return (USBD_IN_PROGRESS); } /* Abort a device control request. */ static void ohci_device_ctrl_abort(usbd_xfer_handle xfer) { DPRINTF(("ohci_device_ctrl_abort: xfer=%p\n", xfer)); ohci_abort_xfer(xfer, USBD_CANCELLED); } /* Close a device control pipe. */ static void ohci_device_ctrl_close(usbd_pipe_handle pipe) { struct ohci_pipe *opipe = (struct ohci_pipe *)pipe; ohci_softc_t *sc = (ohci_softc_t *)pipe->device->bus; DPRINTF(("ohci_device_ctrl_close: pipe=%p\n", pipe)); ohci_close_pipe(pipe, sc->sc_ctrl_head); ohci_free_std(sc, opipe->tail.td); } /************************/ static void ohci_device_clear_toggle(usbd_pipe_handle pipe) { struct ohci_pipe *opipe = (struct ohci_pipe *)pipe; opipe->sed->ed.ed_headp &= htole32(~OHCI_TOGGLECARRY); } static void ohci_noop(usbd_pipe_handle pipe) { } static usbd_status ohci_device_bulk_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, start first */ return (ohci_device_bulk_start(STAILQ_FIRST(&xfer->pipe->queue))); } static usbd_status ohci_device_bulk_start(usbd_xfer_handle xfer) { struct ohci_pipe *opipe = (struct ohci_pipe *)xfer->pipe; 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, *tail, *tdp; ohci_soft_ed_t *sed; int s, len, isread, endpt; usbd_status err; if (sc->sc_dying) return (USBD_IOERROR); #ifdef DIAGNOSTIC if (xfer->rqflags & URQ_REQUEST) { /* XXX panic */ printf("ohci_device_bulk_start: a request\n"); return (USBD_INVAL); } #endif len = xfer->length; endpt = xfer->pipe->endpoint->edesc->bEndpointAddress; isread = UE_GET_DIR(endpt) == UE_DIR_IN; sed = opipe->sed; DPRINTFN(4,("ohci_device_bulk_start: xfer=%p len=%d isread=%d " "flags=%d endpt=%d\n", xfer, len, isread, xfer->flags, endpt)); opipe->u.bulk.isread = isread; opipe->u.bulk.length = len; /* Update device address */ sed->ed.ed_flags = htole32( (le32toh(sed->ed.ed_flags) & ~OHCI_ED_ADDRMASK) | OHCI_ED_SET_FA(addr)); /* Allocate a chain of new TDs (including a new tail). */ 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); tail->xfer = NULL; xfer->hcpriv = data; DPRINTFN(4,("ohci_device_bulk_start: ed_flags=0x%08x td_flags=0x%08x " "td_cbp=0x%08x td_be=0x%08x\n", (int)le32toh(sed->ed.ed_flags), (int)le32toh(data->td.td_flags), (int)le32toh(data->td.td_cbp), (int)le32toh(data->td.td_be))); #ifdef USB_DEBUG if (ohcidebug > 5) { ohci_dump_ed(sed); ohci_dump_tds(data); } #endif /* Insert ED in schedule */ s = splusb(); for (tdp = data; tdp != tail; tdp = tdp->nexttd) { tdp->xfer = xfer; } sed->ed.ed_tailp = htole32(tail->physaddr); opipe->tail.td = tail; sed->ed.ed_flags &= htole32(~OHCI_ED_SKIP); OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_BLF); if (xfer->timeout && !sc->sc_bus.use_polling) { - usb_callout(xfer->timeout_handle, MS_TO_TICKS(xfer->timeout), - ohci_timeout, xfer); + callout_reset(&xfer->timeout_handle, MS_TO_TICKS(xfer->timeout), + ohci_timeout, xfer); } #if 0 /* This goes wrong if we are too slow. */ if (ohcidebug > 10) { delay(10000); DPRINTF(("ohci_device_intr_transfer: status=%x\n", OREAD4(sc, OHCI_COMMAND_STATUS))); ohci_dump_ed(sed); ohci_dump_tds(data); } #endif splx(s); if (sc->sc_bus.use_polling) ohci_waitintr(sc, xfer); return (USBD_IN_PROGRESS); } static void ohci_device_bulk_abort(usbd_xfer_handle xfer) { DPRINTF(("ohci_device_bulk_abort: xfer=%p\n", xfer)); ohci_abort_xfer(xfer, USBD_CANCELLED); } /* * Close a device bulk pipe. */ static void ohci_device_bulk_close(usbd_pipe_handle pipe) { struct ohci_pipe *opipe = (struct ohci_pipe *)pipe; ohci_softc_t *sc = (ohci_softc_t *)pipe->device->bus; DPRINTF(("ohci_device_bulk_close: pipe=%p\n", pipe)); ohci_close_pipe(pipe, sc->sc_bulk_head); ohci_free_std(sc, opipe->tail.td); } /************************/ static usbd_status ohci_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, start first */ return (ohci_device_intr_start(STAILQ_FIRST(&xfer->pipe->queue))); } static usbd_status ohci_device_intr_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 = opipe->sed; usbd_status err; if (sc->sc_dying) return (USBD_IOERROR); DPRINTFN(3, ("ohci_device_intr_start: xfer=%p len=%d " "flags=%d priv=%p\n", xfer, xfer->length, xfer->flags, xfer->priv)); #ifdef DIAGNOSTIC if (xfer->rqflags & URQ_REQUEST) panic("ohci_device_intr_start: a request"); #endif err = ohci_device_intr_insert(sc, xfer); if (err) return (err); sed->ed.ed_flags &= htole32(~OHCI_ED_SKIP); return (USBD_IN_PROGRESS); } /* * Insert an interrupt transfer into an endpoint descriptor list */ static usbd_status ohci_device_intr_insert(ohci_softc_t *sc, usbd_xfer_handle xfer) { struct ohci_pipe *opipe = (struct ohci_pipe *)xfer->pipe; ohci_soft_ed_t *sed = opipe->sed; ohci_soft_td_t *data, *tail; ohci_physaddr_t dataphys, physend; int s; DPRINTFN(4, ("ohci_device_intr_insert: xfer=%p", xfer)); data = opipe->tail.td; tail = ohci_alloc_std(sc); if (tail == NULL) return (USBD_NOMEM); tail->xfer = NULL; data->td.td_flags = htole32( OHCI_TD_IN | OHCI_TD_NOCC | OHCI_TD_SET_DI(1) | OHCI_TD_TOGGLE_CARRY); if (xfer->flags & USBD_SHORT_XFER_OK) data->td.td_flags |= htole32(OHCI_TD_R); /* * Assume a short mapping with no complications, which * should always be true for <= 4k buffers in contiguous * virtual memory. The data can take the following forms: * 1 segment in 1 OHCI page * 1 segment in 2 OHCI pages * 2 segments in 2 OHCI pages * (see comment in ohci_alloc_std_chain() for details) */ KASSERT(xfer->length > 0 && xfer->length <= OHCI_PAGE_SIZE, ("ohci_device_intr_insert: bad length %d", xfer->length)); dataphys = xfer->dmamap.segs[0].ds_addr; physend = dataphys + xfer->length - 1; if (xfer->dmamap.nsegs == 2) { KASSERT(OHCI_PAGE_OFFSET(dataphys + xfer->dmamap.segs[0].ds_len) == 0, ("ohci_device_intr_insert: bad seg 0 termination")); physend = xfer->dmamap.segs[1].ds_addr + xfer->length - xfer->dmamap.segs[0].ds_len - 1; } else { KASSERT(xfer->dmamap.nsegs == 1, ("ohci_device_intr_insert: bad seg count %d", (u_int)xfer->dmamap.nsegs)); } data->td.td_cbp = htole32(dataphys); data->nexttd = tail; data->td.td_nexttd = htole32(tail->physaddr); data->td.td_be = htole32(physend); data->len = xfer->length; data->xfer = xfer; data->flags = OHCI_CALL_DONE | OHCI_ADD_LEN; xfer->hcpriv = data; xfer->actlen = 0; #ifdef USB_DEBUG if (ohcidebug > 5) { DPRINTF(("ohci_device_intr_insert:\n")); ohci_dump_ed(sed); ohci_dump_tds(data); } #endif /* Insert ED in schedule */ s = splusb(); sed->ed.ed_tailp = htole32(tail->physaddr); opipe->tail.td = tail; splx(s); return (USBD_NORMAL_COMPLETION); } /* Abort a device control request. */ static void ohci_device_intr_abort(usbd_xfer_handle xfer) { if (xfer->pipe->intrxfer == xfer) { DPRINTF(("ohci_device_intr_abort: remove\n")); xfer->pipe->intrxfer = NULL; } ohci_abort_xfer(xfer, USBD_CANCELLED); } /* Close a device interrupt pipe. */ static void ohci_device_intr_close(usbd_pipe_handle pipe) { struct ohci_pipe *opipe = (struct ohci_pipe *)pipe; ohci_softc_t *sc = (ohci_softc_t *)pipe->device->bus; int nslots = opipe->u.intr.nslots; int pos = opipe->u.intr.pos; int j; ohci_soft_ed_t *p, *sed = opipe->sed; int s; DPRINTFN(1,("ohci_device_intr_close: pipe=%p nslots=%d pos=%d\n", pipe, nslots, pos)); s = splusb(); sed->ed.ed_flags |= htole32(OHCI_ED_SKIP); if ((le32toh(sed->ed.ed_tailp) & OHCI_HEADMASK) != (le32toh(sed->ed.ed_headp) & OHCI_HEADMASK)) usb_delay_ms(&sc->sc_bus, 2); #ifdef DIAGNOSTIC if ((le32toh(sed->ed.ed_tailp) & OHCI_HEADMASK) != (le32toh(sed->ed.ed_headp) & OHCI_HEADMASK)) panic("%s: Intr pipe %p still has TDs queued", device_get_nameunit(sc->sc_bus.bdev), pipe); #endif for (p = sc->sc_eds[pos]; p && p->next != sed; p = p->next) ; #ifdef DIAGNOSTIC if (p == NULL) panic("ohci_device_intr_close: ED not found"); #endif p->next = sed->next; p->ed.ed_nexted = sed->ed.ed_nexted; splx(s); for (j = 0; j < nslots; j++) --sc->sc_bws[(pos * nslots + j) % OHCI_NO_INTRS]; ohci_free_std(sc, opipe->tail.td); ohci_free_sed(sc, opipe->sed); } static usbd_status ohci_device_setintr(ohci_softc_t *sc, struct ohci_pipe *opipe, int ival) { int i, j, s, best; u_int npoll, slow, shigh, nslots; u_int bestbw, bw; ohci_soft_ed_t *hsed, *sed = opipe->sed; DPRINTFN(2, ("ohci_setintr: pipe=%p\n", opipe)); if (ival == 0) { printf("ohci_setintr: 0 interval\n"); return (USBD_INVAL); } npoll = OHCI_NO_INTRS; while (npoll > ival) npoll /= 2; DPRINTFN(2, ("ohci_setintr: ival=%d npoll=%d\n", ival, npoll)); /* * We now know which level in the tree the ED must go into. * Figure out which slot has most bandwidth left over. * Slots to examine: * npoll * 1 0 * 2 1 2 * 4 3 4 5 6 * 8 7 8 9 10 11 12 13 14 * N (N-1) .. (N-1+N-1) */ slow = npoll-1; shigh = slow + npoll; nslots = OHCI_NO_INTRS / npoll; for (best = i = slow, bestbw = ~0; i < shigh; i++) { bw = 0; for (j = 0; j < nslots; j++) bw += sc->sc_bws[(i * nslots + j) % OHCI_NO_INTRS]; if (bw < bestbw) { best = i; bestbw = bw; } } DPRINTFN(2, ("ohci_setintr: best=%d(%d..%d) bestbw=%d\n", best, slow, shigh, bestbw)); s = splusb(); hsed = sc->sc_eds[best]; sed->next = hsed->next; sed->ed.ed_nexted = hsed->ed.ed_nexted; hsed->next = sed; hsed->ed.ed_nexted = htole32(sed->physaddr); splx(s); for (j = 0; j < nslots; j++) ++sc->sc_bws[(best * nslots + j) % OHCI_NO_INTRS]; opipe->u.intr.nslots = nslots; opipe->u.intr.pos = best; DPRINTFN(5, ("ohci_setintr: returns %p\n", opipe)); return (USBD_NORMAL_COMPLETION); } /***********************/ usbd_status ohci_device_isoc_transfer(usbd_xfer_handle xfer) { usbd_status err; DPRINTFN(5,("ohci_device_isoc_transfer: xfer=%p\n", xfer)); /* Put it on our queue, */ err = usb_insert_transfer(xfer); /* bail out on error, */ if (err && err != USBD_IN_PROGRESS) return (err); /* XXX should check inuse here */ /* insert into schedule, */ ohci_device_isoc_enter(xfer); /* and start if the pipe wasn't running */ if (!err) ohci_device_isoc_start(STAILQ_FIRST(&xfer->pipe->queue)); return (err); } void ohci_device_isoc_enter(usbd_xfer_handle xfer) { struct ohci_pipe *opipe = (struct ohci_pipe *)xfer->pipe; usbd_device_handle dev = opipe->pipe.device; ohci_softc_t *sc = (ohci_softc_t *)dev->bus; ohci_soft_ed_t *sed = opipe->sed; struct iso *iso = &opipe->u.iso; struct usb_dma_mapping *dma = &xfer->dmamap; ohci_soft_itd_t *sitd, *nsitd; ohci_physaddr_t dataphys, bp0, physend, prevpage; int curlen, i, len, ncur, nframes, npages, seg, segoff; int s; DPRINTFN(1,("ohci_device_isoc_enter: used=%d next=%d xfer=%p " "nframes=%d\n", iso->inuse, iso->next, xfer, xfer->nframes)); if (sc->sc_dying) return; if (iso->next == -1) { /* Not in use yet, schedule it a few frames ahead. */ iso->next = le32toh(sc->sc_hcca->hcca_frame_number) + 5; DPRINTFN(2,("ohci_device_isoc_enter: start next=%d\n", iso->next)); } sitd = opipe->tail.itd; nframes = xfer->nframes; xfer->hcpriv = sitd; seg = 0; segoff = 0; i = 0; while (i < nframes) { /* * Fill in as many ITD frames as possible. */ KASSERT(seg < dma->nsegs, ("ohci_device_isoc_enter: overrun")); bp0 = dma->segs[seg].ds_addr + segoff; sitd->itd.itd_bp0 = htole32(bp0); prevpage = OHCI_PAGE(bp0); npages = 1; ncur = 0; while (ncur < OHCI_ITD_NOFFSET && i < nframes) { /* Find the frame start and end physical addresses. */ len = xfer->frlengths[i]; dataphys = dma->segs[seg].ds_addr + segoff; curlen = dma->segs[seg].ds_len - segoff; if (len > curlen) { KASSERT(seg + 1 < dma->nsegs, ("ohci_device_isoc_enter: overrun2")); seg++; segoff = len - curlen; } else { segoff += len; } KASSERT(segoff <= dma->segs[seg].ds_len, ("ohci_device_isoc_enter: overrun3")); physend = dma->segs[seg].ds_addr + segoff - 1; /* Check if there would be more than 2 pages . */ if (OHCI_PAGE(dataphys) != prevpage) { prevpage = OHCI_PAGE(dataphys); npages++; } if (OHCI_PAGE(physend) != prevpage) { prevpage = OHCI_PAGE(physend); npages++; } if (npages > 2) { /* We cannot fit this frame now. */ segoff -= len; if (segoff < 0) { seg--; segoff += dma->segs[seg].ds_len; } break; } sitd->itd.itd_be = htole32(physend); sitd->itd.itd_offset[ncur] = htole16(OHCI_ITD_MK_OFFS(OHCI_PAGE(dataphys) == OHCI_PAGE(bp0) ? 0 : 1, dataphys)); i++; ncur++; } if (segoff >= dma->segs[seg].ds_len) { KASSERT(segoff == dma->segs[seg].ds_len, ("ohci_device_isoc_enter: overlap")); seg++; segoff = 0; } /* Allocate next ITD */ nsitd = ohci_alloc_sitd(sc); if (nsitd == NULL) { /* XXX what now? */ printf("%s: isoc TD alloc failed\n", device_get_nameunit(sc->sc_bus.bdev)); return; } /* Fill out remaining fields of current ITD */ sitd->nextitd = nsitd; sitd->itd.itd_nextitd = htole32(nsitd->physaddr); sitd->xfer = xfer; if (i < nframes) { sitd->itd.itd_flags = htole32( OHCI_ITD_NOCC | OHCI_ITD_SET_SF(iso->next) | OHCI_ITD_SET_DI(6) | /* delay intr a little */ OHCI_ITD_SET_FC(ncur)); sitd->flags = OHCI_ITD_ACTIVE; } else { sitd->itd.itd_flags = htole32( OHCI_ITD_NOCC | OHCI_ITD_SET_SF(iso->next) | OHCI_ITD_SET_DI(0) | OHCI_ITD_SET_FC(ncur)); sitd->flags = OHCI_CALL_DONE | OHCI_ITD_ACTIVE; } iso->next += ncur; sitd = nsitd; } iso->inuse += nframes; /* XXX pretend we did it all */ xfer->actlen = 0; for (i = 0; i < nframes; i++) xfer->actlen += xfer->frlengths[i]; xfer->status = USBD_IN_PROGRESS; #ifdef USB_DEBUG if (ohcidebug > 5) { DPRINTF(("ohci_device_isoc_enter: frame=%d\n", le32toh(sc->sc_hcca->hcca_frame_number))); ohci_dump_itds(xfer->hcpriv); ohci_dump_ed(sed); } #endif s = splusb(); opipe->tail.itd = sitd; sed->ed.ed_flags &= htole32(~OHCI_ED_SKIP); sed->ed.ed_tailp = htole32(sitd->physaddr); splx(s); #ifdef USB_DEBUG if (ohcidebug > 5) { delay(150000); DPRINTF(("ohci_device_isoc_enter: after frame=%d\n", le32toh(sc->sc_hcca->hcca_frame_number))); ohci_dump_itds(xfer->hcpriv); ohci_dump_ed(sed); } #endif } usbd_status 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)); if (sc->sc_dying) return (USBD_IOERROR); #ifdef DIAGNOSTIC if (xfer->status != USBD_IN_PROGRESS) printf("ohci_device_isoc_start: not in progress %p\n", xfer); #endif /* 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); } void 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, *sitdnext, *tmp_sitd; int s,undone,num_sitds; s = splusb(); opipe->aborting = 1; DPRINTFN(1,("ohci_device_isoc_abort: xfer=%p\n", xfer)); /* Transfer is already done. */ if (xfer->status != USBD_NOT_STARTED && xfer->status != USBD_IN_PROGRESS) { splx(s); printf("ohci_device_isoc_abort: early return\n"); return; } /* Give xfer the requested abort code. */ xfer->status = USBD_CANCELLED; 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) { splx(s); printf("ohci_device_isoc_abort: hcpriv==0\n"); return; } #endif 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; #endif } splx(s); /* * 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 ); /* Free the sitds */ for (sitd = xfer->hcpriv; sitd->xfer == xfer; sitd = sitdnext) { sitdnext = sitd->nextitd; ohci_free_sitd(sc, sitd); } s = splusb(); /* Run callback. */ usb_transfer_complete(xfer); /* There is always a `next' sitd so link it up. */ sed->ed.ed_headp = htole32(sitd->physaddr); sed->ed.ed_flags &= htole32(~OHCI_ED_SKIP); /* remove hardware skip */ splx(s); } void ohci_device_isoc_done(usbd_xfer_handle xfer) { /* 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 ohci_setup_isoc(usbd_pipe_handle pipe) { struct ohci_pipe *opipe = (struct ohci_pipe *)pipe; ohci_softc_t *sc = (ohci_softc_t *)pipe->device->bus; struct iso *iso = &opipe->u.iso; int s; iso->next = -1; iso->inuse = 0; s = splusb(); ohci_add_ed(opipe->sed, sc->sc_isoc_head); splx(s); return (USBD_NORMAL_COMPLETION); } void 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)); 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); /* Next `avail free' sitd.*/ } diff --git a/sys/dev/usb/ohcivar.h b/sys/dev/usb/ohcivar.h index 23855f4a9add..48f99e769f4c 100644 --- a/sys/dev/usb/ohcivar.h +++ b/sys/dev/usb/ohcivar.h @@ -1,164 +1,164 @@ /* $NetBSD: ohcivar.h,v 1.30 2001/12/31 12:20:35 augustss Exp $ */ /* $FreeBSD$ */ /*- * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ typedef struct ohci_soft_ed { ohci_ed_t ed; struct ohci_soft_ed *next; ohci_physaddr_t physaddr; } ohci_soft_ed_t; #define OHCI_SED_SIZE ((sizeof (struct ohci_soft_ed) + OHCI_ED_ALIGN - 1) / OHCI_ED_ALIGN * OHCI_ED_ALIGN) #define OHCI_SED_CHUNK (PAGE_SIZE / OHCI_SED_SIZE) typedef struct ohci_soft_td { ohci_td_t td; struct ohci_soft_td *nexttd; /* mirrors nexttd in TD */ struct ohci_soft_td *dnext; /* next in done list */ ohci_physaddr_t physaddr; LIST_ENTRY(ohci_soft_td) hnext; usbd_xfer_handle xfer; u_int16_t len; u_int16_t flags; #define OHCI_CALL_DONE 0x0001 #define OHCI_ADD_LEN 0x0002 } ohci_soft_td_t; #define OHCI_STD_SIZE ((sizeof (struct ohci_soft_td) + OHCI_TD_ALIGN - 1) / OHCI_TD_ALIGN * OHCI_TD_ALIGN) #define OHCI_STD_CHUNK (PAGE_SIZE / OHCI_STD_SIZE) typedef struct ohci_soft_itd { ohci_itd_t itd; struct ohci_soft_itd *nextitd; /* mirrors nexttd in ITD */ struct ohci_soft_itd *dnext; /* next in done list */ ohci_physaddr_t physaddr; 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 } ohci_soft_itd_t; #define OHCI_SITD_SIZE ((sizeof (struct ohci_soft_itd) + OHCI_ITD_ALIGN - 1) / OHCI_ITD_ALIGN * OHCI_ITD_ALIGN) #define OHCI_SITD_CHUNK (PAGE_SIZE / OHCI_SITD_SIZE) #define OHCI_NO_EDS (2*OHCI_NO_INTRS-1) #define OHCI_HASH_SIZE 128 #define OHCI_SCFLG_DONEINIT 0x0001 /* ohci_init() done. */ typedef struct ohci_softc { struct usbd_bus sc_bus; /* base device */ int sc_flags; bus_space_tag_t iot; bus_space_handle_t ioh; bus_size_t sc_size; void *ih; struct resource *io_res; struct resource *irq_res; usb_dma_t sc_hccadma; struct ohci_hcca *sc_hcca; ohci_soft_ed_t *sc_eds[OHCI_NO_EDS]; u_int sc_bws[OHCI_NO_INTRS]; u_int32_t sc_eintrs; /* enabled interrupts */ ohci_soft_ed_t *sc_isoc_head; ohci_soft_ed_t *sc_ctrl_head; ohci_soft_ed_t *sc_bulk_head; LIST_HEAD(, ohci_soft_td) sc_hash_tds[OHCI_HASH_SIZE]; LIST_HEAD(, ohci_soft_itd) sc_hash_itds[OHCI_HASH_SIZE]; int sc_noport; u_int8_t sc_addr; /* device address */ u_int8_t sc_conf; /* device configuration */ #ifdef USB_USE_SOFTINTR char sc_softwake; #endif /* USB_USE_SOFTINTR */ ohci_soft_ed_t *sc_freeeds; ohci_soft_td_t *sc_freetds; ohci_soft_itd_t *sc_freeitds; STAILQ_HEAD(, usbd_xfer) sc_free_xfers; /* free xfers */ usbd_xfer_handle sc_intrxfer; ohci_soft_itd_t *sc_sidone; ohci_soft_td_t *sc_sdone; char sc_vendor[16]; int sc_id_vendor; #if defined(__NetBSD__) || defined(__OpenBSD__) void *sc_powerhook; /* cookie from power hook */ void *sc_shutdownhook; /* cookie from shutdown hook */ #endif u_int32_t sc_control; /* Preserved during suspend/standby */ u_int32_t sc_intre; u_int sc_overrun_cnt; struct timeval sc_overrun_ntc; - usb_callout_t sc_tmo_rhsc; + struct callout sc_tmo_rhsc; char sc_dying; } ohci_softc_t; struct ohci_xfer { struct usbd_xfer xfer; struct usb_task abort_task; u_int32_t ohci_xfer_flags; }; #define OHCI_XFER_ABORTING 0x01 /* xfer is aborting. */ #define OHCI_XFER_ABORTWAIT 0x02 /* abort completion is being awaited. */ #define OXFER(xfer) ((struct ohci_xfer *)(xfer)) #define MS_TO_TICKS(ms) ((ms) * hz / 1000) usbd_status ohci_init(ohci_softc_t *); void ohci_intr(void *); int ohci_detach(ohci_softc_t *, int); void ohci_shutdown(void *v); void ohci_power(int state, void *priv); diff --git a/sys/dev/usb/uhci.c b/sys/dev/usb/uhci.c index 8b84c2720635..4d4c61bf65c9 100644 --- a/sys/dev/usb/uhci.c +++ b/sys/dev/usb/uhci.c @@ -1,3705 +1,3704 @@ /* $NetBSD: uhci.c,v 1.170 2003/02/19 01:35:04 augustss Exp $ */ /* Also already incorporated from NetBSD: * $NetBSD: uhci.c,v 1.172 2003/02/23 04:19:26 simonb Exp $ * $NetBSD: uhci.c,v 1.173 2003/05/13 04:41:59 gson Exp $ * $NetBSD: uhci.c,v 1.175 2003/09/12 16:18:08 mycroft Exp $ * $NetBSD: uhci.c,v 1.176 2003/11/04 19:11:21 mycroft Exp $ * $NetBSD: uhci.c,v 1.177 2003/12/29 08:17:10 toshii Exp $ * $NetBSD: uhci.c,v 1.178 2004/03/02 16:32:05 martin Exp $ * $NetBSD: uhci.c,v 1.180 2004/07/17 20:12:03 mycroft Exp $ */ #include __FBSDID("$FreeBSD$"); /*- * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * USB Universal Host Controller driver. * Handles e.g. PIIX3 and PIIX4. * * UHCI spec: http://developer.intel.com/design/USB/UHCI11D.htm * USB spec: http://www.usb.org/developers/docs/usbspec.zip * PIIXn spec: ftp://download.intel.com/design/intarch/datashts/29055002.pdf * ftp://download.intel.com/design/intarch/datashts/29056201.pdf */ #include #include #include #include #include #include #include #if defined(DIAGNOSTIC) && defined(__i386__) #include #endif #include #include #include #include #include #include #include #include #include #include #include #include /* Use bandwidth reclamation for control transfers. Some devices choke on it. */ /*#define UHCI_CTL_LOOP */ #define delay(d) DELAY(d) #define MS_TO_TICKS(ms) ((ms) * hz / 1000) #ifdef USB_DEBUG uhci_softc_t *thesc; #define DPRINTF(x) if (uhcidebug) printf x #define DPRINTFN(n,x) if (uhcidebug>(n)) printf x int uhcidebug = 0; int uhcinoloop = 0; SYSCTL_NODE(_hw_usb, OID_AUTO, uhci, CTLFLAG_RW, 0, "USB uhci"); SYSCTL_INT(_hw_usb_uhci, OID_AUTO, debug, CTLFLAG_RW, &uhcidebug, 0, "uhci debug level"); SYSCTL_INT(_hw_usb_uhci, OID_AUTO, loop, CTLFLAG_RW, &uhcinoloop, 0, "uhci noloop"); #define bitmask_snprintf(q,f,b,l) snprintf((b), (l), "%b", (q), (f)) #else #define DPRINTF(x) #define DPRINTFN(n,x) #endif struct uhci_pipe { struct usbd_pipe pipe; int nexttoggle; u_char aborting; usbd_xfer_handle abortstart, abortend; /* Info needed for different pipe kinds. */ union { /* Control pipe */ struct { uhci_soft_qh_t *sqh; usb_dma_t reqdma; uhci_soft_td_t *setup, *stat; u_int length; } ctl; /* Interrupt pipe */ struct { int npoll; int isread; uhci_soft_qh_t **qhs; } intr; /* Bulk pipe */ struct { uhci_soft_qh_t *sqh; u_int length; int isread; } bulk; /* Iso pipe */ struct iso { uhci_soft_td_t **stds; int next, inuse; } iso; } u; }; static void uhci_globalreset(uhci_softc_t *); static usbd_status uhci_portreset(uhci_softc_t*, int); static void uhci_reset(uhci_softc_t *); #if defined(__NetBSD__) || defined(__OpenBSD__) static void uhci_shutdown(void *v); static void uhci_power(int, void *); #endif static usbd_status uhci_run(uhci_softc_t *, int run); static uhci_soft_td_t *uhci_alloc_std(uhci_softc_t *); static void uhci_free_std(uhci_softc_t *, uhci_soft_td_t *); static uhci_soft_qh_t *uhci_alloc_sqh(uhci_softc_t *); static void uhci_free_sqh(uhci_softc_t *, uhci_soft_qh_t *); static usbd_status uhci_aux_dma_alloc(uhci_softc_t *, uhci_soft_td_t *, void *data, int len); static uhci_physaddr_t uhci_aux_dma_prepare(uhci_soft_td_t *, int); static void uhci_aux_dma_complete(uhci_soft_td_t *, int); #if 0 static void uhci_enter_ctl_q(uhci_softc_t *, uhci_soft_qh_t *, uhci_intr_info_t *); static void uhci_exit_ctl_q(uhci_softc_t *, uhci_soft_qh_t *); #endif static void uhci_free_std_chain(uhci_softc_t *, uhci_soft_td_t *, uhci_soft_td_t *); static usbd_status uhci_alloc_std_chain(struct uhci_pipe *, uhci_softc_t *, int, int, u_int16_t, usbd_xfer_handle xfer, uhci_soft_td_t **, uhci_soft_td_t **); static void uhci_poll_hub(void *); static void uhci_waitintr(uhci_softc_t *, usbd_xfer_handle); static void uhci_check_intr(uhci_softc_t *, uhci_intr_info_t *); static void uhci_idone(uhci_intr_info_t *); static void uhci_abort_xfer(usbd_xfer_handle, usbd_status status); static void uhci_transfer_complete(usbd_xfer_handle xfer); static void uhci_timeout(void *); static void uhci_timeout_task(void *); static void uhci_add_ls_ctrl(uhci_softc_t *, uhci_soft_qh_t *); static void uhci_add_hs_ctrl(uhci_softc_t *, uhci_soft_qh_t *); static void uhci_add_bulk(uhci_softc_t *, uhci_soft_qh_t *); static void uhci_remove_ls_ctrl(uhci_softc_t *,uhci_soft_qh_t *); static void uhci_remove_hs_ctrl(uhci_softc_t *,uhci_soft_qh_t *); static void uhci_remove_bulk(uhci_softc_t *,uhci_soft_qh_t *); static int uhci_str(usb_string_descriptor_t *, int, char *); static void uhci_add_loop(uhci_softc_t *sc); static void uhci_rem_loop(uhci_softc_t *sc); static usbd_status uhci_setup_isoc(usbd_pipe_handle pipe); static void uhci_device_isoc_enter(usbd_xfer_handle); static usbd_status uhci_allocm(struct usbd_bus *, usb_dma_t *, u_int32_t); static void uhci_freem(struct usbd_bus *, usb_dma_t *); static usbd_xfer_handle uhci_allocx(struct usbd_bus *); static void uhci_freex(struct usbd_bus *, usbd_xfer_handle); static usbd_status uhci_device_ctrl_transfer(usbd_xfer_handle); static usbd_status uhci_device_ctrl_start(usbd_xfer_handle); static void uhci_device_ctrl_abort(usbd_xfer_handle); static void uhci_device_ctrl_close(usbd_pipe_handle); static void uhci_device_ctrl_done(usbd_xfer_handle); static usbd_status uhci_device_intr_transfer(usbd_xfer_handle); static usbd_status uhci_device_intr_start(usbd_xfer_handle); static void uhci_device_intr_abort(usbd_xfer_handle); static void uhci_device_intr_close(usbd_pipe_handle); static void uhci_device_intr_done(usbd_xfer_handle); static usbd_status uhci_device_bulk_transfer(usbd_xfer_handle); static usbd_status uhci_device_bulk_start(usbd_xfer_handle); static void uhci_device_bulk_abort(usbd_xfer_handle); static void uhci_device_bulk_close(usbd_pipe_handle); static void uhci_device_bulk_done(usbd_xfer_handle); static usbd_status uhci_device_isoc_transfer(usbd_xfer_handle); static usbd_status uhci_device_isoc_start(usbd_xfer_handle); static void uhci_device_isoc_abort(usbd_xfer_handle); static void uhci_device_isoc_close(usbd_pipe_handle); static void uhci_device_isoc_done(usbd_xfer_handle); static usbd_status uhci_root_ctrl_transfer(usbd_xfer_handle); static usbd_status uhci_root_ctrl_start(usbd_xfer_handle); static void uhci_root_ctrl_abort(usbd_xfer_handle); static void uhci_root_ctrl_close(usbd_pipe_handle); static void uhci_root_ctrl_done(usbd_xfer_handle); static usbd_status uhci_root_intr_transfer(usbd_xfer_handle); static usbd_status uhci_root_intr_start(usbd_xfer_handle); static void uhci_root_intr_abort(usbd_xfer_handle); static void uhci_root_intr_close(usbd_pipe_handle); static void uhci_root_intr_done(usbd_xfer_handle); static usbd_status uhci_open(usbd_pipe_handle); static void uhci_poll(struct usbd_bus *); static void uhci_softintr(void *); static usbd_status uhci_device_request(usbd_xfer_handle xfer); static void uhci_add_intr(uhci_softc_t *, uhci_soft_qh_t *); static void uhci_remove_intr(uhci_softc_t *, uhci_soft_qh_t *); static usbd_status uhci_device_setintr(uhci_softc_t *sc, struct uhci_pipe *pipe, int ival); static void uhci_device_clear_toggle(usbd_pipe_handle pipe); static void uhci_noop(usbd_pipe_handle pipe); static __inline uhci_soft_qh_t *uhci_find_prev_qh(uhci_soft_qh_t *, uhci_soft_qh_t *); #ifdef USB_DEBUG static void uhci_dump_all(uhci_softc_t *); static void uhci_dumpregs(uhci_softc_t *); static void uhci_dump_qhs(uhci_soft_qh_t *); static void uhci_dump_qh(uhci_soft_qh_t *); static void uhci_dump_tds(uhci_soft_td_t *); static void uhci_dump_td(uhci_soft_td_t *); static void uhci_dump_ii(uhci_intr_info_t *ii); void uhci_dump(void); #endif #define UBARR(sc) bus_space_barrier((sc)->iot, (sc)->ioh, 0, (sc)->sc_size, \ BUS_SPACE_BARRIER_READ|BUS_SPACE_BARRIER_WRITE) #define UWRITE1(sc, r, x) \ do { UBARR(sc); bus_space_write_1((sc)->iot, (sc)->ioh, (r), (x)); \ } while (/*CONSTCOND*/0) #define UWRITE2(sc, r, x) \ do { UBARR(sc); bus_space_write_2((sc)->iot, (sc)->ioh, (r), (x)); \ } while (/*CONSTCOND*/0) #define UWRITE4(sc, r, x) \ do { UBARR(sc); bus_space_write_4((sc)->iot, (sc)->ioh, (r), (x)); \ } while (/*CONSTCOND*/0) #define UREAD1(sc, r) (UBARR(sc), bus_space_read_1((sc)->iot, (sc)->ioh, (r))) #define UREAD2(sc, r) (UBARR(sc), bus_space_read_2((sc)->iot, (sc)->ioh, (r))) #define UREAD4(sc, r) (UBARR(sc), bus_space_read_4((sc)->iot, (sc)->ioh, (r))) #define UHCICMD(sc, cmd) UWRITE2(sc, UHCI_CMD, cmd) #define UHCISTS(sc) UREAD2(sc, UHCI_STS) #define UHCI_RESET_TIMEOUT 100 /* ms, reset timeout */ #define UHCI_CURFRAME(sc) (UREAD2(sc, UHCI_FRNUM) & UHCI_FRNUM_MASK) #define UHCI_INTR_ENDPT 1 struct usbd_bus_methods uhci_bus_methods = { uhci_open, uhci_softintr, uhci_poll, uhci_allocm, uhci_freem, uhci_allocx, uhci_freex, }; struct usbd_pipe_methods uhci_root_ctrl_methods = { uhci_root_ctrl_transfer, uhci_root_ctrl_start, uhci_root_ctrl_abort, uhci_root_ctrl_close, uhci_noop, uhci_root_ctrl_done, }; struct usbd_pipe_methods uhci_root_intr_methods = { uhci_root_intr_transfer, uhci_root_intr_start, uhci_root_intr_abort, uhci_root_intr_close, uhci_noop, uhci_root_intr_done, }; struct usbd_pipe_methods uhci_device_ctrl_methods = { uhci_device_ctrl_transfer, uhci_device_ctrl_start, uhci_device_ctrl_abort, uhci_device_ctrl_close, uhci_noop, uhci_device_ctrl_done, }; struct usbd_pipe_methods uhci_device_intr_methods = { uhci_device_intr_transfer, uhci_device_intr_start, uhci_device_intr_abort, uhci_device_intr_close, uhci_device_clear_toggle, uhci_device_intr_done, }; struct usbd_pipe_methods uhci_device_bulk_methods = { uhci_device_bulk_transfer, uhci_device_bulk_start, uhci_device_bulk_abort, uhci_device_bulk_close, uhci_device_clear_toggle, uhci_device_bulk_done, }; struct usbd_pipe_methods uhci_device_isoc_methods = { uhci_device_isoc_transfer, uhci_device_isoc_start, uhci_device_isoc_abort, uhci_device_isoc_close, uhci_noop, uhci_device_isoc_done, }; #define uhci_add_intr_info(sc, ii) \ LIST_INSERT_HEAD(&(sc)->sc_intrhead, (ii), list) #define uhci_del_intr_info(ii) \ do { \ LIST_REMOVE((ii), list); \ (ii)->list.le_prev = NULL; \ } while (0) #define uhci_active_intr_info(ii) ((ii)->list.le_prev != NULL) static __inline uhci_soft_qh_t * uhci_find_prev_qh(uhci_soft_qh_t *pqh, uhci_soft_qh_t *sqh) { DPRINTFN(15,("uhci_find_prev_qh: pqh=%p sqh=%p\n", pqh, sqh)); for (; pqh->hlink != sqh; pqh = pqh->hlink) { #if defined(DIAGNOSTIC) || defined(USB_DEBUG) if (le32toh(pqh->qh.qh_hlink) & UHCI_PTR_T) { printf("uhci_find_prev_qh: QH not found\n"); return (NULL); } #endif } return (pqh); } void uhci_globalreset(uhci_softc_t *sc) { UHCICMD(sc, UHCI_CMD_GRESET); /* global reset */ usb_delay_ms(&sc->sc_bus, USB_BUS_RESET_DELAY); /* wait a little */ UHCICMD(sc, 0); /* do nothing */ } usbd_status uhci_init(uhci_softc_t *sc) { usbd_status err; int i, j; uhci_soft_qh_t *clsqh, *chsqh, *bsqh, *sqh, *lsqh; uhci_soft_td_t *std; DPRINTFN(1,("uhci_init: start\n")); #ifdef USB_DEBUG thesc = sc; if (uhcidebug > 2) uhci_dumpregs(sc); #endif UWRITE2(sc, UHCI_INTR, 0); /* disable interrupts */ uhci_globalreset(sc); /* reset the controller */ uhci_reset(sc); /* Allocate and initialize real frame array. */ err = usb_allocmem(&sc->sc_bus, UHCI_FRAMELIST_COUNT * sizeof(uhci_physaddr_t), UHCI_FRAMELIST_ALIGN, &sc->sc_dma); if (err) return (err); sc->sc_pframes = KERNADDR(&sc->sc_dma, 0); UWRITE2(sc, UHCI_FRNUM, 0); /* set frame number to 0 */ UWRITE4(sc, UHCI_FLBASEADDR, DMAADDR(&sc->sc_dma, 0)); /* set frame list*/ /* * Allocate a TD, inactive, that hangs from the last QH. * This is to avoid a bug in the PIIX that makes it run berserk * otherwise. */ std = uhci_alloc_std(sc); if (std == NULL) return (USBD_NOMEM); std->link.std = NULL; std->td.td_link = htole32(UHCI_PTR_T); std->td.td_status = htole32(0); /* inactive */ std->td.td_token = htole32(0); std->td.td_buffer = htole32(0); /* Allocate the dummy QH marking the end and used for looping the QHs.*/ lsqh = uhci_alloc_sqh(sc); if (lsqh == NULL) return (USBD_NOMEM); lsqh->hlink = NULL; lsqh->qh.qh_hlink = htole32(UHCI_PTR_T); /* end of QH chain */ lsqh->elink = std; lsqh->qh.qh_elink = htole32(std->physaddr | UHCI_PTR_TD); sc->sc_last_qh = lsqh; /* Allocate the dummy QH where bulk traffic will be queued. */ bsqh = uhci_alloc_sqh(sc); if (bsqh == NULL) return (USBD_NOMEM); bsqh->hlink = lsqh; bsqh->qh.qh_hlink = htole32(lsqh->physaddr | UHCI_PTR_QH); bsqh->elink = NULL; bsqh->qh.qh_elink = htole32(UHCI_PTR_T); sc->sc_bulk_start = sc->sc_bulk_end = bsqh; /* Allocate dummy QH where high speed control traffic will be queued. */ chsqh = uhci_alloc_sqh(sc); if (chsqh == NULL) return (USBD_NOMEM); chsqh->hlink = bsqh; chsqh->qh.qh_hlink = htole32(bsqh->physaddr | UHCI_PTR_QH); chsqh->elink = NULL; chsqh->qh.qh_elink = htole32(UHCI_PTR_T); sc->sc_hctl_start = sc->sc_hctl_end = chsqh; /* Allocate dummy QH where control traffic will be queued. */ clsqh = uhci_alloc_sqh(sc); if (clsqh == NULL) return (USBD_NOMEM); clsqh->hlink = chsqh; clsqh->qh.qh_hlink = htole32(chsqh->physaddr | UHCI_PTR_QH); clsqh->elink = NULL; clsqh->qh.qh_elink = htole32(UHCI_PTR_T); sc->sc_lctl_start = sc->sc_lctl_end = clsqh; /* * Make all (virtual) frame list pointers point to the interrupt * queue heads and the interrupt queue heads at the control * queue head and point the physical frame list to the virtual. */ for(i = 0; i < UHCI_VFRAMELIST_COUNT; i++) { std = uhci_alloc_std(sc); sqh = uhci_alloc_sqh(sc); if (std == NULL || sqh == NULL) return (USBD_NOMEM); std->link.sqh = sqh; std->td.td_link = htole32(sqh->physaddr | UHCI_PTR_QH); std->td.td_status = htole32(UHCI_TD_IOS); /* iso, inactive */ std->td.td_token = htole32(0); std->td.td_buffer = htole32(0); sqh->hlink = clsqh; sqh->qh.qh_hlink = htole32(clsqh->physaddr | UHCI_PTR_QH); sqh->elink = NULL; sqh->qh.qh_elink = htole32(UHCI_PTR_T); sc->sc_vframes[i].htd = std; sc->sc_vframes[i].etd = std; sc->sc_vframes[i].hqh = sqh; sc->sc_vframes[i].eqh = sqh; for (j = i; j < UHCI_FRAMELIST_COUNT; j += UHCI_VFRAMELIST_COUNT) sc->sc_pframes[j] = htole32(std->physaddr); } LIST_INIT(&sc->sc_intrhead); STAILQ_INIT(&sc->sc_free_xfers); - usb_callout_init(sc->sc_poll_handle); + callout_init(&sc->sc_poll_handle, 0); /* Set up the bus struct. */ sc->sc_bus.methods = &uhci_bus_methods; sc->sc_bus.pipe_size = sizeof(struct uhci_pipe); #if defined(__NetBSD__) || defined(__OpenBSD__) sc->sc_suspend = PWR_RESUME; sc->sc_powerhook = powerhook_establish(uhci_power, sc); sc->sc_shutdownhook = shutdownhook_establish(uhci_shutdown, sc); #endif DPRINTFN(1,("uhci_init: enabling\n")); UWRITE2(sc, UHCI_INTR, UHCI_INTR_TOCRCIE | UHCI_INTR_RIE | UHCI_INTR_IOCE | UHCI_INTR_SPIE); /* enable interrupts */ UHCICMD(sc, UHCI_CMD_MAXP); /* Assume 64 byte packets at frame end */ return (uhci_run(sc, 1)); /* and here we go... */ } int uhci_detach(struct uhci_softc *sc, int flags) { usbd_xfer_handle xfer; int rv = 0; sc->sc_dying = 1; UWRITE2(sc, UHCI_INTR, 0); /* disable interrupts */ uhci_run(sc, 0); #if defined(__NetBSD__) || defined(__OpenBSD__) powerhook_disestablish(sc->sc_powerhook); shutdownhook_disestablish(sc->sc_shutdownhook); #endif /* Free all xfers associated with this HC. */ for (;;) { xfer = STAILQ_FIRST(&sc->sc_free_xfers); if (xfer == NULL) break; STAILQ_REMOVE_HEAD(&sc->sc_free_xfers, next); free(xfer, M_USB); } /* XXX free other data structures XXX */ usb_freemem(&sc->sc_bus, &sc->sc_dma); return (rv); } usbd_status uhci_allocm(struct usbd_bus *bus, usb_dma_t *dma, u_int32_t size) { return (usb_allocmem(bus, size, 0, dma)); } void uhci_freem(struct usbd_bus *bus, usb_dma_t *dma) { usb_freemem(bus, dma); } usbd_xfer_handle uhci_allocx(struct usbd_bus *bus) { struct uhci_softc *sc = (struct uhci_softc *)bus; usbd_xfer_handle xfer; xfer = STAILQ_FIRST(&sc->sc_free_xfers); if (xfer != NULL) { STAILQ_REMOVE_HEAD(&sc->sc_free_xfers, next); #ifdef DIAGNOSTIC if (xfer->busy_free != XFER_FREE) { printf("uhci_allocx: xfer=%p not free, 0x%08x\n", xfer, xfer->busy_free); } #endif } else { xfer = malloc(sizeof(struct uhci_xfer), M_USB, M_NOWAIT); } if (xfer != NULL) { memset(xfer, 0, sizeof (struct uhci_xfer)); UXFER(xfer)->iinfo.sc = sc; usb_init_task(&UXFER(xfer)->abort_task, uhci_timeout_task, xfer); UXFER(xfer)->uhci_xfer_flags = 0; #ifdef DIAGNOSTIC UXFER(xfer)->iinfo.isdone = 1; xfer->busy_free = XFER_BUSY; #endif } return (xfer); } void uhci_freex(struct usbd_bus *bus, usbd_xfer_handle xfer) { struct uhci_softc *sc = (struct uhci_softc *)bus; #ifdef DIAGNOSTIC if (xfer->busy_free != XFER_BUSY) { printf("uhci_freex: xfer=%p not busy, 0x%08x\n", xfer, xfer->busy_free); return; } xfer->busy_free = XFER_FREE; if (!UXFER(xfer)->iinfo.isdone) { printf("uhci_freex: !isdone\n"); return; } #endif STAILQ_INSERT_HEAD(&sc->sc_free_xfers, xfer, next); } /* * Shut down the controller when the system is going down. */ void uhci_shutdown(void *v) { uhci_softc_t *sc = v; DPRINTF(("uhci_shutdown: stopping the HC\n")); uhci_run(sc, 0); /* stop the controller */ } /* * Handle suspend/resume. * * We need to switch to polling mode here, because this routine is * called from an interrupt context. This is all right since we * are almost suspended anyway. */ void uhci_power(int why, void *v) { uhci_softc_t *sc = v; int cmd; int s; s = splhardusb(); cmd = UREAD2(sc, UHCI_CMD); DPRINTF(("uhci_power: sc=%p, why=%d (was %d), cmd=0x%x\n", sc, why, sc->sc_suspend, cmd)); if (why != PWR_RESUME) { #ifdef USB_DEBUG if (uhcidebug > 2) uhci_dumpregs(sc); #endif if (sc->sc_intr_xfer != NULL) - usb_uncallout(sc->sc_poll_handle, uhci_poll_hub, - sc->sc_intr_xfer); + callout_stop(&sc->sc_poll_handle); sc->sc_bus.use_polling++; uhci_run(sc, 0); /* stop the controller */ cmd &= ~UHCI_CMD_RS; /* save some state if BIOS doesn't */ sc->sc_saved_frnum = UREAD2(sc, UHCI_FRNUM); sc->sc_saved_sof = UREAD1(sc, UHCI_SOF); UWRITE2(sc, UHCI_INTR, 0); /* disable intrs */ UHCICMD(sc, cmd | UHCI_CMD_EGSM); /* enter global suspend */ usb_delay_ms(&sc->sc_bus, USB_RESUME_WAIT); sc->sc_suspend = why; sc->sc_bus.use_polling--; DPRINTF(("uhci_power: cmd=0x%x\n", UREAD2(sc, UHCI_CMD))); } else { #ifdef DIAGNOSTIC if (sc->sc_suspend == PWR_RESUME) printf("uhci_power: weird, resume without suspend.\n"); #endif sc->sc_bus.use_polling++; sc->sc_suspend = why; UWRITE2(sc, UHCI_INTR, 0); /* disable interrupts */ uhci_globalreset(sc); /* reset the controller */ uhci_reset(sc); if (cmd & UHCI_CMD_RS) uhci_run(sc, 0); /* in case BIOS has started it */ uhci_globalreset(sc); uhci_reset(sc); /* restore saved state */ UWRITE4(sc, UHCI_FLBASEADDR, DMAADDR(&sc->sc_dma, 0)); UWRITE2(sc, UHCI_FRNUM, sc->sc_saved_frnum); UWRITE1(sc, UHCI_SOF, sc->sc_saved_sof); UHCICMD(sc, cmd | UHCI_CMD_FGR); /* force global resume */ usb_delay_ms(&sc->sc_bus, USB_RESUME_DELAY); UHCICMD(sc, cmd & ~UHCI_CMD_EGSM); /* back to normal */ UWRITE2(sc, UHCI_INTR, UHCI_INTR_TOCRCIE | UHCI_INTR_RIE | UHCI_INTR_IOCE | UHCI_INTR_SPIE); /* re-enable intrs */ UHCICMD(sc, UHCI_CMD_MAXP); uhci_run(sc, 1); /* and start traffic again */ usb_delay_ms(&sc->sc_bus, USB_RESUME_RECOVERY); sc->sc_bus.use_polling--; if (sc->sc_intr_xfer != NULL) - usb_callout(sc->sc_poll_handle, sc->sc_ival, - uhci_poll_hub, sc->sc_intr_xfer); + callout_reset(&sc->sc_poll_handle, sc->sc_ival, + uhci_poll_hub, sc->sc_intr_xfer); #ifdef USB_DEBUG if (uhcidebug > 2) uhci_dumpregs(sc); #endif } splx(s); } #ifdef USB_DEBUG static void uhci_dumpregs(uhci_softc_t *sc) { DPRINTFN(-1,("%s regs: cmd=%04x, sts=%04x, intr=%04x, frnum=%04x, " "flbase=%08x, sof=%04x, portsc1=%04x, portsc2=%04x\n", device_get_nameunit(sc->sc_bus.bdev), UREAD2(sc, UHCI_CMD), UREAD2(sc, UHCI_STS), UREAD2(sc, UHCI_INTR), UREAD2(sc, UHCI_FRNUM), UREAD4(sc, UHCI_FLBASEADDR), UREAD1(sc, UHCI_SOF), UREAD2(sc, UHCI_PORTSC1), UREAD2(sc, UHCI_PORTSC2))); } void uhci_dump_td(uhci_soft_td_t *p) { char sbuf[128], sbuf2[128]; DPRINTFN(-1,("TD(%p) at %08lx = link=0x%08lx status=0x%08lx " "token=0x%08lx buffer=0x%08lx\n", p, (long)p->physaddr, (long)le32toh(p->td.td_link), (long)le32toh(p->td.td_status), (long)le32toh(p->td.td_token), (long)le32toh(p->td.td_buffer))); bitmask_snprintf((u_int32_t)le32toh(p->td.td_link), "\20\1T\2Q\3VF", sbuf, sizeof(sbuf)); bitmask_snprintf((u_int32_t)le32toh(p->td.td_status), "\20\22BITSTUFF\23CRCTO\24NAK\25BABBLE\26DBUFFER\27" "STALLED\30ACTIVE\31IOC\32ISO\33LS\36SPD", sbuf2, sizeof(sbuf2)); DPRINTFN(-1,(" %s %s,errcnt=%d,actlen=%d pid=%02x,addr=%d,endpt=%d," "D=%d,maxlen=%d\n", sbuf, sbuf2, UHCI_TD_GET_ERRCNT(le32toh(p->td.td_status)), UHCI_TD_GET_ACTLEN(le32toh(p->td.td_status)), UHCI_TD_GET_PID(le32toh(p->td.td_token)), UHCI_TD_GET_DEVADDR(le32toh(p->td.td_token)), UHCI_TD_GET_ENDPT(le32toh(p->td.td_token)), UHCI_TD_GET_DT(le32toh(p->td.td_token)), UHCI_TD_GET_MAXLEN(le32toh(p->td.td_token)))); } void uhci_dump_qh(uhci_soft_qh_t *sqh) { DPRINTFN(-1,("QH(%p) at %08x: hlink=%08x elink=%08x\n", sqh, (int)sqh->physaddr, le32toh(sqh->qh.qh_hlink), le32toh(sqh->qh.qh_elink))); } #if 1 void uhci_dump(void) { uhci_dump_all(thesc); } #endif void uhci_dump_all(uhci_softc_t *sc) { uhci_dumpregs(sc); printf("intrs=%d\n", sc->sc_bus.no_intrs); /*printf("framelist[i].link = %08x\n", sc->sc_framelist[0].link);*/ uhci_dump_qh(sc->sc_lctl_start); } void uhci_dump_qhs(uhci_soft_qh_t *sqh) { uhci_dump_qh(sqh); /* uhci_dump_qhs displays all the QHs and TDs from the given QH onwards * Traverses sideways first, then down. * * QH1 * QH2 * No QH * TD2.1 * TD2.2 * TD1.1 * etc. * * TD2.x being the TDs queued at QH2 and QH1 being referenced from QH1. */ if (sqh->hlink != NULL && !(le32toh(sqh->qh.qh_hlink) & UHCI_PTR_T)) uhci_dump_qhs(sqh->hlink); else DPRINTF(("No QH\n")); if (sqh->elink != NULL && !(le32toh(sqh->qh.qh_elink) & UHCI_PTR_T)) uhci_dump_tds(sqh->elink); else DPRINTF(("No TD\n")); } void uhci_dump_tds(uhci_soft_td_t *std) { uhci_soft_td_t *td; for(td = std; td != NULL; td = td->link.std) { uhci_dump_td(td); /* Check whether the link pointer in this TD marks * the link pointer as end of queue. This avoids * printing the free list in case the queue/TD has * already been moved there (seatbelt). */ if (le32toh(td->td.td_link) & UHCI_PTR_T || le32toh(td->td.td_link) == 0) break; } } static void uhci_dump_ii(uhci_intr_info_t *ii) { usbd_pipe_handle pipe; usb_endpoint_descriptor_t *ed; usbd_device_handle dev; #ifdef DIAGNOSTIC #define DONE ii->isdone #else #define DONE 0 #endif if (ii == NULL) { printf("ii NULL\n"); return; } if (ii->xfer == NULL) { printf("ii %p: done=%d xfer=NULL\n", ii, DONE); return; } pipe = ii->xfer->pipe; if (pipe == NULL) { printf("ii %p: done=%d xfer=%p pipe=NULL\n", ii, DONE, ii->xfer); return; } if (pipe->endpoint == NULL) { printf("ii %p: done=%d xfer=%p pipe=%p pipe->endpoint=NULL\n", ii, DONE, ii->xfer, pipe); return; } if (pipe->device == NULL) { printf("ii %p: done=%d xfer=%p pipe=%p pipe->device=NULL\n", ii, DONE, ii->xfer, pipe); return; } ed = pipe->endpoint->edesc; dev = pipe->device; printf("ii %p: done=%d xfer=%p dev=%p vid=0x%04x pid=0x%04x addr=%d pipe=%p ep=0x%02x attr=0x%02x\n", ii, DONE, ii->xfer, dev, UGETW(dev->ddesc.idVendor), UGETW(dev->ddesc.idProduct), dev->address, pipe, ed->bEndpointAddress, ed->bmAttributes); #undef DONE } void uhci_dump_iis(struct uhci_softc *sc); void uhci_dump_iis(struct uhci_softc *sc) { uhci_intr_info_t *ii; printf("intr_info list:\n"); for (ii = LIST_FIRST(&sc->sc_intrhead); ii; ii = LIST_NEXT(ii, list)) uhci_dump_ii(ii); } void iidump(void); void iidump(void) { uhci_dump_iis(thesc); } #endif /* * This routine is executed periodically and simulates interrupts * from the root controller interrupt pipe for port status change. */ void uhci_poll_hub(void *addr) { usbd_xfer_handle xfer = addr; usbd_pipe_handle pipe = xfer->pipe; usbd_device_handle dev = pipe->device; uhci_softc_t *sc = (uhci_softc_t *)dev->bus; int s; u_char *p; DPRINTFN(20, ("uhci_poll_hub\n")); - usb_callout(sc->sc_poll_handle, sc->sc_ival, uhci_poll_hub, xfer); + callout_reset(&sc->sc_poll_handle, sc->sc_ival, uhci_poll_hub, xfer); p = xfer->buffer; p[0] = 0; if (UREAD2(sc, UHCI_PORTSC1) & (UHCI_PORTSC_CSC|UHCI_PORTSC_OCIC)) p[0] |= 1<<1; if (UREAD2(sc, UHCI_PORTSC2) & (UHCI_PORTSC_CSC|UHCI_PORTSC_OCIC)) p[0] |= 1<<2; if (p[0] == 0) /* No change, try again in a while */ return; xfer->actlen = 1; xfer->status = USBD_NORMAL_COMPLETION; s = splusb(); dev->bus->intr_context++; uhci_transfer_complete(xfer); dev->bus->intr_context--; splx(s); } void uhci_root_intr_done(usbd_xfer_handle xfer) { } void uhci_root_ctrl_done(usbd_xfer_handle xfer) { } /* * Let the last QH loop back to the high speed control transfer QH. * This is what intel calls "bandwidth reclamation" and improves * USB performance a lot for some devices. * If we are already looping, just count it. */ void uhci_add_loop(uhci_softc_t *sc) { #ifdef USB_DEBUG if (uhcinoloop) return; #endif if (++sc->sc_loops == 1) { DPRINTFN(5,("uhci_start_loop: add\n")); /* Note, we don't loop back the soft pointer. */ sc->sc_last_qh->qh.qh_hlink = htole32(sc->sc_hctl_start->physaddr | UHCI_PTR_QH); } } void uhci_rem_loop(uhci_softc_t *sc) { #ifdef USB_DEBUG if (uhcinoloop) return; #endif if (--sc->sc_loops == 0) { DPRINTFN(5,("uhci_end_loop: remove\n")); sc->sc_last_qh->qh.qh_hlink = htole32(UHCI_PTR_T); } } /* Add high speed control QH, called at splusb(). */ void uhci_add_hs_ctrl(uhci_softc_t *sc, uhci_soft_qh_t *sqh) { uhci_soft_qh_t *eqh; SPLUSBCHECK; DPRINTFN(10, ("uhci_add_ctrl: sqh=%p\n", sqh)); eqh = sc->sc_hctl_end; sqh->hlink = eqh->hlink; sqh->qh.qh_hlink = eqh->qh.qh_hlink; eqh->hlink = sqh; eqh->qh.qh_hlink = htole32(sqh->physaddr | UHCI_PTR_QH); sc->sc_hctl_end = sqh; #ifdef UHCI_CTL_LOOP uhci_add_loop(sc); #endif } /* Remove high speed control QH, called at splusb(). */ void uhci_remove_hs_ctrl(uhci_softc_t *sc, uhci_soft_qh_t *sqh) { uhci_soft_qh_t *pqh; SPLUSBCHECK; DPRINTFN(10, ("uhci_remove_hs_ctrl: sqh=%p\n", sqh)); #ifdef UHCI_CTL_LOOP uhci_rem_loop(sc); #endif /* * The T bit should be set in the elink of the QH so that the HC * doesn't follow the pointer. This condition may fail if the * the transferred packet was short so that the QH still points * at the last used TD. * In this case we set the T bit and wait a little for the HC * to stop looking at the TD. */ if (!(sqh->qh.qh_elink & htole32(UHCI_PTR_T))) { sqh->qh.qh_elink = htole32(UHCI_PTR_T); delay(UHCI_QH_REMOVE_DELAY); } pqh = uhci_find_prev_qh(sc->sc_hctl_start, sqh); pqh->hlink = sqh->hlink; pqh->qh.qh_hlink = sqh->qh.qh_hlink; delay(UHCI_QH_REMOVE_DELAY); if (sc->sc_hctl_end == sqh) sc->sc_hctl_end = pqh; } /* Add low speed control QH, called at splusb(). */ void uhci_add_ls_ctrl(uhci_softc_t *sc, uhci_soft_qh_t *sqh) { uhci_soft_qh_t *eqh; SPLUSBCHECK; DPRINTFN(10, ("uhci_add_ls_ctrl: sqh=%p\n", sqh)); eqh = sc->sc_lctl_end; sqh->hlink = eqh->hlink; sqh->qh.qh_hlink = eqh->qh.qh_hlink; eqh->hlink = sqh; eqh->qh.qh_hlink = htole32(sqh->physaddr | UHCI_PTR_QH); sc->sc_lctl_end = sqh; } /* Remove low speed control QH, called at splusb(). */ void uhci_remove_ls_ctrl(uhci_softc_t *sc, uhci_soft_qh_t *sqh) { uhci_soft_qh_t *pqh; SPLUSBCHECK; DPRINTFN(10, ("uhci_remove_ls_ctrl: sqh=%p\n", sqh)); /* See comment in uhci_remove_hs_ctrl() */ if (!(sqh->qh.qh_elink & htole32(UHCI_PTR_T))) { sqh->qh.qh_elink = htole32(UHCI_PTR_T); delay(UHCI_QH_REMOVE_DELAY); } pqh = uhci_find_prev_qh(sc->sc_lctl_start, sqh); pqh->hlink = sqh->hlink; pqh->qh.qh_hlink = sqh->qh.qh_hlink; delay(UHCI_QH_REMOVE_DELAY); if (sc->sc_lctl_end == sqh) sc->sc_lctl_end = pqh; } /* Add bulk QH, called at splusb(). */ void uhci_add_bulk(uhci_softc_t *sc, uhci_soft_qh_t *sqh) { uhci_soft_qh_t *eqh; SPLUSBCHECK; DPRINTFN(10, ("uhci_add_bulk: sqh=%p\n", sqh)); eqh = sc->sc_bulk_end; sqh->hlink = eqh->hlink; sqh->qh.qh_hlink = eqh->qh.qh_hlink; eqh->hlink = sqh; eqh->qh.qh_hlink = htole32(sqh->physaddr | UHCI_PTR_QH); sc->sc_bulk_end = sqh; uhci_add_loop(sc); } /* Remove bulk QH, called at splusb(). */ void uhci_remove_bulk(uhci_softc_t *sc, uhci_soft_qh_t *sqh) { uhci_soft_qh_t *pqh; SPLUSBCHECK; DPRINTFN(10, ("uhci_remove_bulk: sqh=%p\n", sqh)); uhci_rem_loop(sc); /* See comment in uhci_remove_hs_ctrl() */ if (!(sqh->qh.qh_elink & htole32(UHCI_PTR_T))) { sqh->qh.qh_elink = htole32(UHCI_PTR_T); delay(UHCI_QH_REMOVE_DELAY); } pqh = uhci_find_prev_qh(sc->sc_bulk_start, sqh); pqh->hlink = sqh->hlink; pqh->qh.qh_hlink = sqh->qh.qh_hlink; delay(UHCI_QH_REMOVE_DELAY); if (sc->sc_bulk_end == sqh) sc->sc_bulk_end = pqh; } static int uhci_intr1(uhci_softc_t *); int uhci_intr(void *arg) { uhci_softc_t *sc = arg; if (sc->sc_dying) return (0); DPRINTFN(15,("uhci_intr: real interrupt\n")); if (sc->sc_bus.use_polling) { #ifdef DIAGNOSTIC printf("uhci_intr: ignored interrupt while polling\n"); #endif return (0); } return (uhci_intr1(sc)); } int uhci_intr1(uhci_softc_t *sc) { int status; int ack; /* * It can happen that an interrupt will be delivered to * us before the device has been fully attached and the * softc struct has been configured. Usually this happens * when kldloading the USB support as a module after the * system has been booted. If we detect this condition, * we need to squelch the unwanted interrupts until we're * ready for them. */ if (sc->sc_bus.bdev == NULL) { UWRITE2(sc, UHCI_STS, 0xFFFF); /* ack pending interrupts */ uhci_run(sc, 0); /* stop the controller */ UWRITE2(sc, UHCI_INTR, 0); /* disable interrupts */ return(0); } #ifdef USB_DEBUG if (uhcidebug > 15) { DPRINTF(("%s: uhci_intr1\n", device_get_nameunit(sc->sc_bus.bdev))); uhci_dumpregs(sc); } #endif status = UREAD2(sc, UHCI_STS) & UHCI_STS_ALLINTRS; if (status == 0) /* The interrupt was not for us. */ return (0); #if defined(DIAGNOSTIC) && defined(__NetBSD__) if (sc->sc_suspend != PWR_RESUME) printf("uhci_intr: suspended sts=0x%x\n", status); #endif if (sc->sc_suspend != PWR_RESUME) { printf("%s: interrupt while not operating ignored\n", device_get_nameunit(sc->sc_bus.bdev)); UWRITE2(sc, UHCI_STS, status); /* acknowledge the ints */ return (0); } ack = 0; if (status & UHCI_STS_USBINT) ack |= UHCI_STS_USBINT; if (status & UHCI_STS_USBEI) ack |= UHCI_STS_USBEI; if (status & UHCI_STS_RD) { ack |= UHCI_STS_RD; #ifdef USB_DEBUG printf("%s: resume detect\n", device_get_nameunit(sc->sc_bus.bdev)); #endif } if (status & UHCI_STS_HSE) { ack |= UHCI_STS_HSE; printf("%s: host system error\n", device_get_nameunit(sc->sc_bus.bdev)); } if (status & UHCI_STS_HCPE) { ack |= UHCI_STS_HCPE; printf("%s: host controller process error\n", device_get_nameunit(sc->sc_bus.bdev)); } if (status & UHCI_STS_HCH) { /* no acknowledge needed */ if (!sc->sc_dying) { printf("%s: host controller halted\n", device_get_nameunit(sc->sc_bus.bdev)); #ifdef USB_DEBUG uhci_dump_all(sc); #endif } sc->sc_dying = 1; } if (!ack) return (0); /* nothing to acknowledge */ UWRITE2(sc, UHCI_STS, ack); /* acknowledge the ints */ sc->sc_bus.no_intrs++; usb_schedsoftintr(&sc->sc_bus); DPRINTFN(15, ("%s: uhci_intr: exit\n", device_get_nameunit(sc->sc_bus.bdev))); return (1); } void uhci_softintr(void *v) { uhci_softc_t *sc = v; uhci_intr_info_t *ii, *nextii; DPRINTFN(10,("%s: uhci_softintr (%d)\n", device_get_nameunit(sc->sc_bus.bdev), sc->sc_bus.intr_context)); sc->sc_bus.intr_context++; /* * Interrupts on UHCI really suck. When the host controller * interrupts because a transfer is completed there is no * way of knowing which transfer it was. You can scan down * the TDs and QHs of the previous frame to limit the search, * but that assumes that the interrupt was not delayed by more * than 1 ms, which may not always be true (e.g. after debug * output on a slow console). * We scan all interrupt descriptors to see if any have * completed. */ LIST_FOREACH_SAFE(ii, &sc->sc_intrhead, list, nextii) uhci_check_intr(sc, ii); #ifdef USB_USE_SOFTINTR if (sc->sc_softwake) { sc->sc_softwake = 0; wakeup(&sc->sc_softwake); } #endif /* USB_USE_SOFTINTR */ sc->sc_bus.intr_context--; } /* Check for an interrupt. */ void uhci_check_intr(uhci_softc_t *sc, uhci_intr_info_t *ii) { uhci_soft_td_t *std, *lstd; u_int32_t status; DPRINTFN(15, ("uhci_check_intr: ii=%p\n", ii)); #ifdef DIAGNOSTIC if (ii == NULL) { printf("uhci_check_intr: no ii? %p\n", ii); return; } #endif if (ii->xfer->status == USBD_CANCELLED || ii->xfer->status == USBD_TIMEOUT) { DPRINTF(("uhci_check_intr: aborted xfer=%p\n", ii->xfer)); return; } if (ii->stdstart == NULL) return; lstd = ii->stdend; #ifdef DIAGNOSTIC if (lstd == NULL) { printf("uhci_check_intr: std==0\n"); return; } #endif /* * If the last TD is still active we need to check whether there * is an error somewhere in the middle, or whether there was a * short packet (SPD and not ACTIVE). */ if (le32toh(lstd->td.td_status) & UHCI_TD_ACTIVE) { DPRINTFN(12, ("uhci_check_intr: active ii=%p\n", ii)); for (std = ii->stdstart; std != lstd; std = std->link.std) { status = le32toh(std->td.td_status); /* If there's an active TD the xfer isn't done. */ if (status & UHCI_TD_ACTIVE) break; /* Any kind of error makes the xfer done. */ if (status & UHCI_TD_STALLED) goto done; /* We want short packets, and it is short: it's done */ if ((status & UHCI_TD_SPD) && UHCI_TD_GET_ACTLEN(status) < UHCI_TD_GET_MAXLEN(le32toh(std->td.td_token))) goto done; } DPRINTFN(12, ("uhci_check_intr: ii=%p std=%p still active\n", ii, ii->stdstart)); return; } done: DPRINTFN(12, ("uhci_check_intr: ii=%p done\n", ii)); - usb_uncallout(ii->xfer->timeout_handle, uhci_timeout, ii); + callout_stop(&ii->xfer->timeout_handle); usb_rem_task(ii->xfer->pipe->device, &UXFER(ii->xfer)->abort_task); uhci_idone(ii); } /* Called at splusb() */ void uhci_idone(uhci_intr_info_t *ii) { usbd_xfer_handle xfer = ii->xfer; struct uhci_pipe *upipe = (struct uhci_pipe *)xfer->pipe; uhci_soft_td_t *std; u_int32_t status = 0, nstatus; int actlen; DPRINTFN(12, ("uhci_idone: ii=%p\n", ii)); #ifdef DIAGNOSTIC { int s = splhigh(); if (ii->isdone) { splx(s); #ifdef USB_DEBUG printf("uhci_idone: ii is done!\n "); uhci_dump_ii(ii); #else printf("uhci_idone: ii=%p is done!\n", ii); #endif return; } ii->isdone = 1; splx(s); } #endif if (xfer->nframes != 0) { /* Isoc transfer, do things differently. */ uhci_soft_td_t **stds = upipe->u.iso.stds; int i, n, nframes, len; DPRINTFN(5,("uhci_idone: ii=%p isoc ready\n", ii)); nframes = xfer->nframes; actlen = 0; n = UXFER(xfer)->curframe; for (i = 0; i < nframes; i++) { std = stds[n]; #ifdef USB_DEBUG if (uhcidebug > 5) { DPRINTFN(-1,("uhci_idone: isoc TD %d\n", i)); uhci_dump_td(std); } #endif if (++n >= UHCI_VFRAMELIST_COUNT) n = 0; status = le32toh(std->td.td_status); len = UHCI_TD_GET_ACTLEN(status); xfer->frlengths[i] = len; actlen += len; } upipe->u.iso.inuse -= nframes; xfer->actlen = actlen; xfer->status = USBD_NORMAL_COMPLETION; goto end; } #ifdef USB_DEBUG DPRINTFN(10, ("uhci_idone: ii=%p, xfer=%p, pipe=%p ready\n", ii, xfer, upipe)); if (uhcidebug > 10) uhci_dump_tds(ii->stdstart); #endif /* The transfer is done, compute actual length and status. */ actlen = 0; for (std = ii->stdstart; std != NULL; std = std->link.std) { nstatus = le32toh(std->td.td_status); if (nstatus & UHCI_TD_ACTIVE) break; status = nstatus; if (UHCI_TD_GET_PID(le32toh(std->td.td_token)) != UHCI_TD_PID_SETUP) actlen += UHCI_TD_GET_ACTLEN(status); else { /* * UHCI will report CRCTO in addition to a STALL or NAK * for a SETUP transaction. See section 3.2.2, "TD * CONTROL AND STATUS". */ if (status & (UHCI_TD_STALLED | UHCI_TD_NAK)) status &= ~UHCI_TD_CRCTO; } } /* If there are left over TDs we need to update the toggle. */ if (std != NULL) upipe->nexttoggle = UHCI_TD_GET_DT(le32toh(std->td.td_token)); status &= UHCI_TD_ERROR; DPRINTFN(10, ("uhci_idone: actlen=%d, status=0x%x\n", actlen, status)); xfer->actlen = actlen; if (status != 0) { #ifdef USB_DEBUG char sbuf[128]; bitmask_snprintf((u_int32_t)status, "\20\22BITSTUFF\23CRCTO\24NAK\25" "BABBLE\26DBUFFER\27STALLED\30ACTIVE", sbuf, sizeof(sbuf)); DPRINTFN((status == UHCI_TD_STALLED)*10, ("uhci_idone: error, addr=%d, endpt=0x%02x, " "status 0x%s\n", xfer->pipe->device->address, xfer->pipe->endpoint->edesc->bEndpointAddress, sbuf)); #endif if (status == UHCI_TD_STALLED) xfer->status = USBD_STALLED; else xfer->status = USBD_IOERROR; /* more info XXX */ } else { xfer->status = USBD_NORMAL_COMPLETION; } end: uhci_transfer_complete(xfer); DPRINTFN(12, ("uhci_idone: ii=%p done\n", ii)); } /* * Called when a request does not complete. */ void uhci_timeout(void *addr) { uhci_intr_info_t *ii = addr; struct uhci_xfer *uxfer = UXFER(ii->xfer); struct uhci_pipe *upipe = (struct uhci_pipe *)uxfer->xfer.pipe; uhci_softc_t *sc = (uhci_softc_t *)upipe->pipe.device->bus; DPRINTF(("uhci_timeout: uxfer=%p\n", uxfer)); if (sc->sc_dying) { uhci_abort_xfer(&uxfer->xfer, USBD_TIMEOUT); return; } /* Execute the abort in a process context. */ usb_add_task(uxfer->xfer.pipe->device, &uxfer->abort_task, USB_TASKQ_HC); } void uhci_timeout_task(void *addr) { usbd_xfer_handle xfer = addr; int s; DPRINTF(("uhci_timeout_task: xfer=%p\n", xfer)); s = splusb(); uhci_abort_xfer(xfer, USBD_TIMEOUT); splx(s); } /* * Wait here until controller claims to have an interrupt. * Then call uhci_intr and return. Use timeout to avoid waiting * too long. * Only used during boot when interrupts are not enabled yet. */ void uhci_waitintr(uhci_softc_t *sc, usbd_xfer_handle xfer) { int timo = xfer->timeout; uhci_intr_info_t *ii; DPRINTFN(10,("uhci_waitintr: timeout = %dms\n", timo)); xfer->status = USBD_IN_PROGRESS; for (; timo >= 0; timo--) { usb_delay_ms(&sc->sc_bus, 1); DPRINTFN(20,("uhci_waitintr: 0x%04x\n", UREAD2(sc, UHCI_STS))); if (UREAD2(sc, UHCI_STS) & UHCI_STS_ALLINTRS) uhci_intr1(sc); if (xfer->status != USBD_IN_PROGRESS) return; } /* Timeout */ DPRINTF(("uhci_waitintr: timeout\n")); for (ii = LIST_FIRST(&sc->sc_intrhead); ii != NULL && ii->xfer != xfer; ii = LIST_NEXT(ii, list)) ; #ifdef DIAGNOSTIC if (ii == NULL) panic("uhci_waitintr: lost intr_info"); #endif uhci_idone(ii); } void uhci_poll(struct usbd_bus *bus) { uhci_softc_t *sc = (uhci_softc_t *)bus; if (UREAD2(sc, UHCI_STS) & UHCI_STS_ALLINTRS) uhci_intr1(sc); } void uhci_reset(uhci_softc_t *sc) { int n; UHCICMD(sc, UHCI_CMD_HCRESET); /* The reset bit goes low when the controller is done. */ for (n = 0; n < UHCI_RESET_TIMEOUT && (UREAD2(sc, UHCI_CMD) & UHCI_CMD_HCRESET); n++) usb_delay_ms(&sc->sc_bus, 1); if (n >= UHCI_RESET_TIMEOUT) printf("%s: controller did not reset\n", device_get_nameunit(sc->sc_bus.bdev)); } usbd_status uhci_run(uhci_softc_t *sc, int run) { int s, n, running; u_int16_t cmd; run = run != 0; s = splhardusb(); DPRINTF(("uhci_run: setting run=%d\n", run)); cmd = UREAD2(sc, UHCI_CMD); if (run) cmd |= UHCI_CMD_RS; else cmd &= ~UHCI_CMD_RS; UHCICMD(sc, cmd); for(n = 0; n < 10; n++) { running = !(UREAD2(sc, UHCI_STS) & UHCI_STS_HCH); /* return when we've entered the state we want */ if (run == running) { splx(s); DPRINTF(("uhci_run: done cmd=0x%x sts=0x%x\n", UREAD2(sc, UHCI_CMD), UREAD2(sc, UHCI_STS))); return (USBD_NORMAL_COMPLETION); } usb_delay_ms(&sc->sc_bus, 1); } splx(s); printf("%s: cannot %s\n", device_get_nameunit(sc->sc_bus.bdev), run ? "start" : "stop"); return (USBD_IOERROR); } /* * Memory management routines. * uhci_alloc_std allocates TDs * uhci_alloc_sqh allocates QHs * These two routines do their own free list management, * partly for speed, partly because allocating DMAable memory * has page size granularaity so much memory would be wasted if * only one TD/QH (32 bytes) was placed in each allocated chunk. */ uhci_soft_td_t * uhci_alloc_std(uhci_softc_t *sc) { uhci_soft_td_t *std; usbd_status err; int i, offs; usb_dma_t dma; if (sc->sc_freetds == NULL) { DPRINTFN(2,("uhci_alloc_std: allocating chunk\n")); err = usb_allocmem(&sc->sc_bus, UHCI_STD_SIZE * UHCI_STD_CHUNK, UHCI_TD_ALIGN, &dma); if (err) return (0); for(i = 0; i < UHCI_STD_CHUNK; i++) { offs = i * UHCI_STD_SIZE; std = KERNADDR(&dma, offs); std->physaddr = DMAADDR(&dma, offs); std->link.std = sc->sc_freetds; std->aux_dma.block = NULL; std->aux_data = NULL; std->aux_len = 0; sc->sc_freetds = std; } } std = sc->sc_freetds; sc->sc_freetds = std->link.std; memset(&std->td, 0, sizeof(uhci_td_t)); return std; } void uhci_free_std(uhci_softc_t *sc, uhci_soft_td_t *std) { #ifdef DIAGNOSTIC #define TD_IS_FREE 0x12345678 if (le32toh(std->td.td_token) == TD_IS_FREE) { printf("uhci_free_std: freeing free TD %p\n", std); return; } std->td.td_token = htole32(TD_IS_FREE); #endif if (std->aux_dma.block != NULL) { usb_freemem(&sc->sc_bus, &std->aux_dma); std->aux_dma.block = NULL; std->aux_data = NULL; std->aux_len = 0; } std->link.std = sc->sc_freetds; sc->sc_freetds = std; } uhci_soft_qh_t * uhci_alloc_sqh(uhci_softc_t *sc) { uhci_soft_qh_t *sqh; usbd_status err; int i, offs; usb_dma_t dma; if (sc->sc_freeqhs == NULL) { DPRINTFN(2, ("uhci_alloc_sqh: allocating chunk\n")); err = usb_allocmem(&sc->sc_bus, UHCI_SQH_SIZE * UHCI_SQH_CHUNK, UHCI_QH_ALIGN, &dma); if (err) return (0); for(i = 0; i < UHCI_SQH_CHUNK; i++) { offs = i * UHCI_SQH_SIZE; sqh = KERNADDR(&dma, offs); sqh->physaddr = DMAADDR(&dma, offs); sqh->hlink = sc->sc_freeqhs; sc->sc_freeqhs = sqh; } } sqh = sc->sc_freeqhs; sc->sc_freeqhs = sqh->hlink; memset(&sqh->qh, 0, sizeof(uhci_qh_t)); return (sqh); } void uhci_free_sqh(uhci_softc_t *sc, uhci_soft_qh_t *sqh) { sqh->hlink = sc->sc_freeqhs; sc->sc_freeqhs = sqh; } void uhci_free_std_chain(uhci_softc_t *sc, uhci_soft_td_t *std, uhci_soft_td_t *stdend) { uhci_soft_td_t *p; for (; std != stdend; std = p) { p = std->link.std; uhci_free_std(sc, std); } } usbd_status uhci_alloc_std_chain(struct uhci_pipe *upipe, uhci_softc_t *sc, int len, int rd, u_int16_t flags, usbd_xfer_handle xfer, uhci_soft_td_t **sp, uhci_soft_td_t **ep) { struct usb_dma_mapping *dma = &xfer->dmamap; uhci_soft_td_t *p, *prevp, *startp; int err, i, ntd, l, tog, maxp, seg, segoff; u_int32_t status; int addr = upipe->pipe.device->address; int endpt = upipe->pipe.endpoint->edesc->bEndpointAddress; DPRINTFN(8, ("uhci_alloc_std_chain: addr=%d endpt=%d len=%d speed=%d " "flags=0x%x\n", addr, UE_GET_ADDR(endpt), len, upipe->pipe.device->speed, flags)); maxp = UGETW(upipe->pipe.endpoint->edesc->wMaxPacketSize); if (maxp == 0) { printf("uhci_alloc_std_chain: maxp=0\n"); return (USBD_INVAL); } ntd = (len + maxp - 1) / maxp; if (len == 0) flags |= USBD_FORCE_SHORT_XFER; if ((flags & USBD_FORCE_SHORT_XFER) && len % maxp == 0) ntd++; DPRINTFN(10, ("uhci_alloc_std_chain: maxp=%d ntd=%d\n", maxp, ntd)); KASSERT(ntd > 0, ("uhci_alloc_std_chain: ntd=0")); tog = upipe->nexttoggle; prevp = NULL; startp = NULL; status = UHCI_TD_ZERO_ACTLEN(UHCI_TD_SET_ERRCNT(3) | UHCI_TD_ACTIVE); if (upipe->pipe.device->speed == USB_SPEED_LOW) status |= UHCI_TD_LS; if (flags & USBD_SHORT_XFER_OK) status |= UHCI_TD_SPD; seg = 0; segoff = 0; for (i = 0; i < ntd; i++) { p = uhci_alloc_std(sc); if (p == NULL) { uhci_free_std_chain(sc, startp, NULL); return (USBD_NOMEM); } p->link.std = NULL; if (prevp != NULL) { prevp->link.std = p; prevp->td.td_link = htole32(p->physaddr | UHCI_PTR_VF | UHCI_PTR_TD); } else { startp = p; } p->td.td_status = htole32(status); if (i == ntd - 1) { /* last TD */ l = len % maxp; if (l == 0 && !(flags & USBD_FORCE_SHORT_XFER)) l = maxp; *ep = p; } else l = maxp; p->td.td_token = htole32(rd ? UHCI_TD_IN (l, endpt, addr, tog) : UHCI_TD_OUT(l, endpt, addr, tog)); KASSERT(seg < dma->nsegs || l == 0, ("uhci_alloc_std_chain: too few segments")); if (l == 0) { p->td.td_buffer = 0; } else if (l > dma->segs[seg].ds_len - segoff) { /* UHCI can't handle non-contiguous data. */ err = uhci_aux_dma_alloc(sc, p, (char *)xfer->buffer + i * maxp, l); if (err) { uhci_free_std_chain(sc, startp, NULL); return (err); } p->td.td_buffer = htole32(uhci_aux_dma_prepare(p, rd)); l -= dma->segs[seg].ds_len - segoff; seg++; KASSERT(seg < dma->nsegs, ("uhci_alloc_std_chain: too few segments 2")); segoff = 0; } else { p->td.td_buffer = htole32(dma->segs[seg].ds_addr + segoff); } segoff += l; if (l > 0 && segoff >= dma->segs[seg].ds_len) { KASSERT(segoff == dma->segs[seg].ds_len, ("uhci_alloc_std_chain: overlap")); if (i * maxp + l != len) { seg++; segoff = 0; } } prevp = p; tog ^= 1; } prevp->td.td_link = htole32(UHCI_PTR_T | UHCI_PTR_VF | UHCI_PTR_TD); upipe->nexttoggle = tog; *sp = startp; DPRINTFN(10, ("uhci_alloc_std_chain: nexttog=%d\n", upipe->nexttoggle)); return (USBD_NORMAL_COMPLETION); } /* * Allocate a physically contiguous buffer to handle cases where UHCI * cannot handle a packet because it is not physically contiguous. * If the usb_dma_t was already allocated this just ensures it is * large enough for the specified size. */ static usbd_status uhci_aux_dma_alloc(uhci_softc_t *sc, uhci_soft_td_t *std, void *data, int len) { int err, align; if (std->aux_dma.block == NULL || std->aux_dma.block->size < len) { /* Align to avoid crossing a page boundary. */ if (powerof2(len)) align = len; else align = 1 << fls(len); if (std->aux_dma.block != NULL) usb_freemem(&sc->sc_bus, &std->aux_dma); std->aux_dma.block = NULL; err = usb_allocmem(&sc->sc_bus, len, align, &std->aux_dma); if (err) return (err); } std->aux_data = data; std->aux_len = len; return (USBD_NORMAL_COMPLETION); } static uhci_physaddr_t uhci_aux_dma_prepare(uhci_soft_td_t *std, int isread) { if (!isread) { bcopy(std->aux_data, KERNADDR(&std->aux_dma, 0), std->aux_len); bus_dmamap_sync(std->aux_dma.block->tag, std->aux_dma.block->map, BUS_DMASYNC_PREWRITE); } return (DMAADDR(&std->aux_dma, 0)); } static void uhci_aux_dma_complete(uhci_soft_td_t *std, int isread) { if (isread) { bus_dmamap_sync(std->aux_dma.block->tag, std->aux_dma.block->map, BUS_DMASYNC_POSTREAD); bcopy(KERNADDR(&std->aux_dma, 0), std->aux_data, std->aux_len); } } void uhci_device_clear_toggle(usbd_pipe_handle pipe) { struct uhci_pipe *upipe = (struct uhci_pipe *)pipe; upipe->nexttoggle = 0; } void uhci_noop(usbd_pipe_handle pipe) { } usbd_status uhci_device_bulk_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 (uhci_device_bulk_start(STAILQ_FIRST(&xfer->pipe->queue))); } usbd_status uhci_device_bulk_start(usbd_xfer_handle xfer) { struct uhci_pipe *upipe = (struct uhci_pipe *)xfer->pipe; usbd_device_handle dev = upipe->pipe.device; uhci_softc_t *sc = (uhci_softc_t *)dev->bus; uhci_intr_info_t *ii = &UXFER(xfer)->iinfo; uhci_soft_td_t *data, *dataend; uhci_soft_qh_t *sqh; usbd_status err; int len, isread, endpt; int s; DPRINTFN(3, ("uhci_device_bulk_start: xfer=%p len=%d flags=%d ii=%p\n", xfer, xfer->length, xfer->flags, ii)); if (sc->sc_dying) return (USBD_IOERROR); #ifdef DIAGNOSTIC if (xfer->rqflags & URQ_REQUEST) panic("uhci_device_bulk_transfer: a request"); #endif len = xfer->length; endpt = upipe->pipe.endpoint->edesc->bEndpointAddress; isread = UE_GET_DIR(endpt) == UE_DIR_IN; sqh = upipe->u.bulk.sqh; upipe->u.bulk.isread = isread; upipe->u.bulk.length = len; err = uhci_alloc_std_chain(upipe, sc, len, isread, xfer->flags, xfer, &data, &dataend); if (err) return (err); dataend->td.td_status |= htole32(UHCI_TD_IOC); #ifdef USB_DEBUG if (uhcidebug > 8) { DPRINTF(("uhci_device_bulk_transfer: data(1)\n")); uhci_dump_tds(data); } #endif /* Set up interrupt info. */ ii->xfer = xfer; ii->stdstart = data; ii->stdend = dataend; #ifdef DIAGNOSTIC if (!ii->isdone) { printf("uhci_device_bulk_transfer: not done, ii=%p\n", ii); } ii->isdone = 0; #endif sqh->elink = data; sqh->qh.qh_elink = htole32(data->physaddr | UHCI_PTR_TD); s = splusb(); uhci_add_bulk(sc, sqh); uhci_add_intr_info(sc, ii); if (xfer->timeout && !sc->sc_bus.use_polling) { - usb_callout(xfer->timeout_handle, MS_TO_TICKS(xfer->timeout), + callout_reset(&xfer->timeout_handle, MS_TO_TICKS(xfer->timeout), uhci_timeout, ii); } xfer->status = USBD_IN_PROGRESS; splx(s); #ifdef USB_DEBUG if (uhcidebug > 10) { DPRINTF(("uhci_device_bulk_transfer: data(2)\n")); uhci_dump_tds(data); } #endif if (sc->sc_bus.use_polling) uhci_waitintr(sc, xfer); return (USBD_IN_PROGRESS); } /* Abort a device bulk request. */ void uhci_device_bulk_abort(usbd_xfer_handle xfer) { DPRINTF(("uhci_device_bulk_abort:\n")); uhci_abort_xfer(xfer, USBD_CANCELLED); } /* * Abort a device request. * If this routine is called at splusb() it guarantees that the request * will be removed from the hardware scheduling and that the callback * for it will be called with USBD_CANCELLED status. * It's impossible to guarantee that the requested transfer will not * have happened since the hardware runs concurrently. * If the transaction has already happened we rely on the ordinary * interrupt processing to process it. */ void uhci_abort_xfer(usbd_xfer_handle xfer, usbd_status status) { struct uhci_xfer *uxfer = UXFER(xfer); uhci_intr_info_t *ii = &uxfer->iinfo; struct uhci_pipe *upipe = (struct uhci_pipe *)xfer->pipe; uhci_softc_t *sc = (uhci_softc_t *)upipe->pipe.device->bus; uhci_soft_td_t *std; int s; DPRINTFN(1,("uhci_abort_xfer: xfer=%p, status=%d\n", xfer, status)); if (sc->sc_dying) { /* If we're dying, just do the software part. */ s = splusb(); xfer->status = status; /* make software ignore it */ - usb_uncallout(xfer->timeout_handle, uhci_timeout, xfer); + callout_stop(&xfer->timeout_handle); usb_rem_task(xfer->pipe->device, &UXFER(xfer)->abort_task); uhci_transfer_complete(xfer); splx(s); return; } if (xfer->device->bus->intr_context || !curproc) panic("uhci_abort_xfer: not in process context"); /* * If an abort is already in progress then just wait for it to * complete and return. */ if (uxfer->uhci_xfer_flags & UHCI_XFER_ABORTING) { DPRINTFN(2, ("uhci_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, ("uhci_abort_xfer: waiting for abort to finish\n")); uxfer->uhci_xfer_flags |= UHCI_XFER_ABORTWAIT; while (uxfer->uhci_xfer_flags & UHCI_XFER_ABORTING) tsleep(&uxfer->uhci_xfer_flags, PZERO, "uhciaw", 0); return; } /* * Step 1: Make interrupt routine and hardware ignore xfer. */ s = splusb(); uxfer->uhci_xfer_flags |= UHCI_XFER_ABORTING; xfer->status = status; /* make software ignore it */ - usb_uncallout(xfer->timeout_handle, uhci_timeout, ii); + callout_stop(&xfer->timeout_handle); usb_rem_task(xfer->pipe->device, &UXFER(xfer)->abort_task); DPRINTFN(1,("uhci_abort_xfer: stop ii=%p\n", ii)); for (std = ii->stdstart; std != NULL; std = std->link.std) std->td.td_status &= htole32(~(UHCI_TD_ACTIVE | UHCI_TD_IOC)); splx(s); /* * Step 2: Wait until we know hardware has finished any possible * use of the xfer. Also make sure the soft interrupt routine * has run. */ usb_delay_ms(upipe->pipe.device->bus, 2); /* Hardware finishes in 1ms */ s = splusb(); #ifdef USB_USE_SOFTINTR sc->sc_softwake = 1; #endif /* USB_USE_SOFTINTR */ usb_schedsoftintr(&sc->sc_bus); #ifdef USB_USE_SOFTINTR DPRINTFN(1,("uhci_abort_xfer: tsleep\n")); tsleep(&sc->sc_softwake, PZERO, "uhciab", 0); #endif /* USB_USE_SOFTINTR */ splx(s); /* * Step 3: Execute callback. */ DPRINTFN(1,("uhci_abort_xfer: callback\n")); s = splusb(); #ifdef DIAGNOSTIC ii->isdone = 1; #endif /* Do the wakeup first to avoid touching the xfer after the callback. */ uxfer->uhci_xfer_flags &= ~UHCI_XFER_ABORTING; if (uxfer->uhci_xfer_flags & UHCI_XFER_ABORTWAIT) { uxfer->uhci_xfer_flags &= ~UHCI_XFER_ABORTWAIT; wakeup(&uxfer->uhci_xfer_flags); } uhci_transfer_complete(xfer); splx(s); } /* * Perform any UHCI-specific transfer completion operations, then * call usb_transfer_complete(). */ static void uhci_transfer_complete(usbd_xfer_handle xfer) { uhci_intr_info_t *ii = &UXFER(xfer)->iinfo; struct uhci_pipe *upipe = (struct uhci_pipe *)xfer->pipe; uhci_soft_td_t *p; int i, isread, n; /* XXX, must be an easier way to detect reads... */ isread = ((xfer->rqflags & URQ_REQUEST) && (xfer->request.bmRequestType & UT_READ)) || (xfer->pipe->endpoint->edesc->bEndpointAddress & UE_DIR_IN); /* Copy back from any auxillary buffers after a read operation. */ if (xfer->nframes == 0) { for (p = ii->stdstart; p != NULL; p = p->link.std) { if (p->aux_data != NULL) uhci_aux_dma_complete(p, isread); } } else { if (xfer->nframes != 0) { /* Isoc transfer, do things differently. */ n = UXFER(xfer)->curframe; for (i = 0; i < xfer->nframes; i++) { p = upipe->u.iso.stds[n]; if (p->aux_data != NULL) uhci_aux_dma_complete(p, isread); if (++n >= UHCI_VFRAMELIST_COUNT) n = 0; } } } usb_transfer_complete(xfer); } /* Close a device bulk pipe. */ void uhci_device_bulk_close(usbd_pipe_handle pipe) { struct uhci_pipe *upipe = (struct uhci_pipe *)pipe; usbd_device_handle dev = upipe->pipe.device; uhci_softc_t *sc = (uhci_softc_t *)dev->bus; uhci_free_sqh(sc, upipe->u.bulk.sqh); pipe->endpoint->savedtoggle = upipe->nexttoggle; } usbd_status uhci_device_ctrl_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 (uhci_device_ctrl_start(STAILQ_FIRST(&xfer->pipe->queue))); } usbd_status uhci_device_ctrl_start(usbd_xfer_handle xfer) { uhci_softc_t *sc = (uhci_softc_t *)xfer->pipe->device->bus; usbd_status err; if (sc->sc_dying) return (USBD_IOERROR); #ifdef DIAGNOSTIC if (!(xfer->rqflags & URQ_REQUEST)) panic("uhci_device_ctrl_transfer: not a request"); #endif err = uhci_device_request(xfer); if (err) return (err); if (sc->sc_bus.use_polling) uhci_waitintr(sc, xfer); return (USBD_IN_PROGRESS); } usbd_status uhci_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 (uhci_device_intr_start(STAILQ_FIRST(&xfer->pipe->queue))); } usbd_status uhci_device_intr_start(usbd_xfer_handle xfer) { struct uhci_pipe *upipe = (struct uhci_pipe *)xfer->pipe; usbd_device_handle dev = upipe->pipe.device; uhci_softc_t *sc = (uhci_softc_t *)dev->bus; uhci_intr_info_t *ii = &UXFER(xfer)->iinfo; uhci_soft_td_t *data, *dataend; uhci_soft_qh_t *sqh; usbd_status err; int isread, endpt; int i, s; if (sc->sc_dying) return (USBD_IOERROR); DPRINTFN(3,("uhci_device_intr_transfer: xfer=%p len=%d flags=%d\n", xfer, xfer->length, xfer->flags)); #ifdef DIAGNOSTIC if (xfer->rqflags & URQ_REQUEST) panic("uhci_device_intr_transfer: a request"); #endif endpt = upipe->pipe.endpoint->edesc->bEndpointAddress; isread = UE_GET_DIR(endpt) == UE_DIR_IN; sqh = upipe->u.bulk.sqh; upipe->u.intr.isread = isread; err = uhci_alloc_std_chain(upipe, sc, xfer->length, isread, xfer->flags, xfer, &data, &dataend); if (err) return (err); dataend->td.td_status |= htole32(UHCI_TD_IOC); #ifdef USB_DEBUG if (uhcidebug > 10) { DPRINTF(("uhci_device_intr_transfer: data(1)\n")); uhci_dump_tds(data); uhci_dump_qh(upipe->u.intr.qhs[0]); } #endif s = splusb(); /* Set up interrupt info. */ ii->xfer = xfer; ii->stdstart = data; ii->stdend = dataend; #ifdef DIAGNOSTIC if (!ii->isdone) { printf("uhci_device_intr_transfer: not done, ii=%p\n", ii); } ii->isdone = 0; #endif DPRINTFN(10,("uhci_device_intr_transfer: qhs[0]=%p\n", upipe->u.intr.qhs[0])); for (i = 0; i < upipe->u.intr.npoll; i++) { sqh = upipe->u.intr.qhs[i]; sqh->elink = data; sqh->qh.qh_elink = htole32(data->physaddr | UHCI_PTR_TD); } uhci_add_intr_info(sc, ii); xfer->status = USBD_IN_PROGRESS; splx(s); #ifdef USB_DEBUG if (uhcidebug > 10) { DPRINTF(("uhci_device_intr_transfer: data(2)\n")); uhci_dump_tds(data); uhci_dump_qh(upipe->u.intr.qhs[0]); } #endif return (USBD_IN_PROGRESS); } /* Abort a device control request. */ void uhci_device_ctrl_abort(usbd_xfer_handle xfer) { DPRINTF(("uhci_device_ctrl_abort:\n")); uhci_abort_xfer(xfer, USBD_CANCELLED); } /* Close a device control pipe. */ void uhci_device_ctrl_close(usbd_pipe_handle pipe) { } /* Abort a device interrupt request. */ void uhci_device_intr_abort(usbd_xfer_handle xfer) { DPRINTFN(1,("uhci_device_intr_abort: xfer=%p\n", xfer)); if (xfer->pipe->intrxfer == xfer) { DPRINTFN(1,("uhci_device_intr_abort: remove\n")); xfer->pipe->intrxfer = NULL; } uhci_abort_xfer(xfer, USBD_CANCELLED); } /* Close a device interrupt pipe. */ void uhci_device_intr_close(usbd_pipe_handle pipe) { struct uhci_pipe *upipe = (struct uhci_pipe *)pipe; uhci_softc_t *sc = (uhci_softc_t *)pipe->device->bus; int i, npoll; int s; /* Unlink descriptors from controller data structures. */ npoll = upipe->u.intr.npoll; s = splusb(); for (i = 0; i < npoll; i++) uhci_remove_intr(sc, upipe->u.intr.qhs[i]); splx(s); /* * We now have to wait for any activity on the physical * descriptors to stop. */ usb_delay_ms(&sc->sc_bus, 2); for(i = 0; i < npoll; i++) uhci_free_sqh(sc, upipe->u.intr.qhs[i]); free(upipe->u.intr.qhs, M_USBHC); /* XXX free other resources */ } usbd_status uhci_device_request(usbd_xfer_handle xfer) { struct uhci_pipe *upipe = (struct uhci_pipe *)xfer->pipe; usb_device_request_t *req = &xfer->request; usbd_device_handle dev = upipe->pipe.device; uhci_softc_t *sc = (uhci_softc_t *)dev->bus; int addr = dev->address; int endpt = upipe->pipe.endpoint->edesc->bEndpointAddress; uhci_intr_info_t *ii = &UXFER(xfer)->iinfo; uhci_soft_td_t *setup, *data, *stat, *next, *dataend; uhci_soft_qh_t *sqh; int len; u_int32_t ls; usbd_status err; int isread; int s; DPRINTFN(3,("uhci_device_control type=0x%02x, request=0x%02x, " "wValue=0x%04x, wIndex=0x%04x len=%d, addr=%d, endpt=%d\n", req->bmRequestType, req->bRequest, UGETW(req->wValue), UGETW(req->wIndex), UGETW(req->wLength), addr, endpt)); ls = dev->speed == USB_SPEED_LOW ? UHCI_TD_LS : 0; isread = req->bmRequestType & UT_READ; len = UGETW(req->wLength); setup = upipe->u.ctl.setup; stat = upipe->u.ctl.stat; sqh = upipe->u.ctl.sqh; /* Set up data transaction */ if (len != 0) { upipe->nexttoggle = 1; err = uhci_alloc_std_chain(upipe, sc, len, isread, xfer->flags, xfer, &data, &dataend); if (err) return (err); next = data; dataend->link.std = stat; dataend->td.td_link = htole32(stat->physaddr | UHCI_PTR_VF | UHCI_PTR_TD); } else { next = stat; } upipe->u.ctl.length = len; memcpy(KERNADDR(&upipe->u.ctl.reqdma, 0), req, sizeof *req); setup->link.std = next; setup->td.td_link = htole32(next->physaddr | UHCI_PTR_VF | UHCI_PTR_TD); setup->td.td_status = htole32(UHCI_TD_SET_ERRCNT(3) | ls | UHCI_TD_ACTIVE); setup->td.td_token = htole32(UHCI_TD_SETUP(sizeof *req, endpt, addr)); setup->td.td_buffer = htole32(DMAADDR(&upipe->u.ctl.reqdma, 0)); stat->link.std = NULL; stat->td.td_link = htole32(UHCI_PTR_T); stat->td.td_status = htole32(UHCI_TD_SET_ERRCNT(3) | ls | UHCI_TD_ACTIVE | UHCI_TD_IOC); stat->td.td_token = htole32(isread ? UHCI_TD_OUT(0, endpt, addr, 1) : UHCI_TD_IN (0, endpt, addr, 1)); stat->td.td_buffer = htole32(0); #ifdef USB_DEBUG if (uhcidebug > 10) { DPRINTF(("uhci_device_request: before transfer\n")); uhci_dump_tds(setup); } #endif /* Set up interrupt info. */ ii->xfer = xfer; ii->stdstart = setup; ii->stdend = stat; #ifdef DIAGNOSTIC if (!ii->isdone) { printf("uhci_device_request: not done, ii=%p\n", ii); } ii->isdone = 0; #endif sqh->elink = setup; sqh->qh.qh_elink = htole32(setup->physaddr | UHCI_PTR_TD); s = splusb(); if (dev->speed == USB_SPEED_LOW) uhci_add_ls_ctrl(sc, sqh); else uhci_add_hs_ctrl(sc, sqh); uhci_add_intr_info(sc, ii); #ifdef USB_DEBUG if (uhcidebug > 12) { uhci_soft_td_t *std; uhci_soft_qh_t *xqh; uhci_soft_qh_t *sxqh; int maxqh = 0; uhci_physaddr_t link; DPRINTF(("uhci_enter_ctl_q: follow from [0]\n")); for (std = sc->sc_vframes[0].htd, link = 0; (link & UHCI_PTR_QH) == 0; std = std->link.std) { link = le32toh(std->td.td_link); uhci_dump_td(std); } sxqh = (uhci_soft_qh_t *)std; uhci_dump_qh(sxqh); for (xqh = sxqh; xqh != NULL; xqh = (maxqh++ == 5 || xqh->hlink == sxqh || xqh->hlink == xqh ? NULL : xqh->hlink)) { uhci_dump_qh(xqh); } DPRINTF(("Enqueued QH:\n")); uhci_dump_qh(sqh); uhci_dump_tds(sqh->elink); } #endif if (xfer->timeout && !sc->sc_bus.use_polling) { - usb_callout(xfer->timeout_handle, MS_TO_TICKS(xfer->timeout), + callout_reset(&xfer->timeout_handle, MS_TO_TICKS(xfer->timeout), uhci_timeout, ii); } xfer->status = USBD_IN_PROGRESS; splx(s); return (USBD_NORMAL_COMPLETION); } usbd_status uhci_device_isoc_transfer(usbd_xfer_handle xfer) { usbd_status err; DPRINTFN(5,("uhci_device_isoc_transfer: xfer=%p\n", xfer)); /* Put it on our queue, */ err = usb_insert_transfer(xfer); /* bail out on error, */ if (err && err != USBD_IN_PROGRESS) return (err); /* XXX should check inuse here */ /* insert into schedule, */ uhci_device_isoc_enter(xfer); /* and start if the pipe wasn't running */ if (!err) uhci_device_isoc_start(STAILQ_FIRST(&xfer->pipe->queue)); return (err); } void uhci_device_isoc_enter(usbd_xfer_handle xfer) { struct uhci_pipe *upipe = (struct uhci_pipe *)xfer->pipe; usbd_device_handle dev = upipe->pipe.device; uhci_softc_t *sc = (uhci_softc_t *)dev->bus; struct iso *iso = &upipe->u.iso; uhci_soft_td_t *std; void *dataptr; u_int32_t len, status; int err, s, i, isread, next, nframes, seg, segoff; DPRINTFN(5,("uhci_device_isoc_enter: used=%d next=%d xfer=%p " "nframes=%d\n", iso->inuse, iso->next, xfer, xfer->nframes)); if (sc->sc_dying) return; if (xfer->status == USBD_IN_PROGRESS) { /* This request has already been entered into the frame list */ printf("uhci_device_isoc_enter: xfer=%p in frame list\n", xfer); /* XXX */ } #ifdef DIAGNOSTIC if (iso->inuse >= UHCI_VFRAMELIST_COUNT) printf("uhci_device_isoc_enter: overflow!\n"); #endif next = iso->next; if (next == -1) { /* Not in use yet, schedule it a few frames ahead. */ next = (UREAD2(sc, UHCI_FRNUM) + 3) % UHCI_VFRAMELIST_COUNT; DPRINTFN(2,("uhci_device_isoc_enter: start next=%d\n", next)); } xfer->status = USBD_IN_PROGRESS; UXFER(xfer)->curframe = next; seg = 0; segoff = 0; dataptr = xfer->allocbuf; /* Normal buffers not possible for isoc? */ isread = xfer->pipe->endpoint->edesc->bEndpointAddress & UE_DIR_IN; status = UHCI_TD_ZERO_ACTLEN(UHCI_TD_SET_ERRCNT(0) | UHCI_TD_ACTIVE | UHCI_TD_IOS); nframes = xfer->nframes; s = splusb(); for (i = 0; i < nframes; i++) { std = iso->stds[next]; if (++next >= UHCI_VFRAMELIST_COUNT) next = 0; len = xfer->frlengths[i]; KASSERT(seg < xfer->dmamap.nsegs, ("uhci_device_isoc_enter: too few segments")); if (len + segoff > xfer->dmamap.segs[seg].ds_len) { /* UHCI can't handle non-contiguous data. */ err = uhci_aux_dma_alloc(sc, std, dataptr, len); /* XXX */ if (err) printf("uhci_device_isoc_enter: aux alloc\n"); std->td.td_buffer = htole32(uhci_aux_dma_prepare(std, isread)); segoff += len; while (segoff >= xfer->dmamap.segs[seg].ds_len) { KASSERT(seg < xfer->dmamap.nsegs - 1 || segoff == xfer->dmamap.segs[seg].ds_len, ("uhci_device_isoc_enter: overlap2")); segoff -= xfer->dmamap.segs[seg].ds_len; seg++; } } else { std->td.td_buffer = htole32(xfer->dmamap.segs[seg].ds_addr + segoff); segoff += len; if (segoff >= xfer->dmamap.segs[seg].ds_len) { KASSERT(segoff == xfer->dmamap.segs[seg].ds_len, ("uhci_device_isoc_enter: overlap")); segoff = 0; seg++; } } if (i == nframes - 1) status |= UHCI_TD_IOC; std->td.td_status = htole32(status); std->td.td_token &= htole32(~UHCI_TD_MAXLEN_MASK); std->td.td_token |= htole32(UHCI_TD_SET_MAXLEN(len)); #ifdef USB_DEBUG if (uhcidebug > 5) { DPRINTFN(5,("uhci_device_isoc_enter: TD %d\n", i)); uhci_dump_td(std); } #endif dataptr = (char *)dataptr + len; } iso->next = next; iso->inuse += xfer->nframes; splx(s); } usbd_status uhci_device_isoc_start(usbd_xfer_handle xfer) { struct uhci_pipe *upipe = (struct uhci_pipe *)xfer->pipe; uhci_softc_t *sc = (uhci_softc_t *)upipe->pipe.device->bus; uhci_intr_info_t *ii = &UXFER(xfer)->iinfo; uhci_soft_td_t *end; int s, i; DPRINTFN(5,("uhci_device_isoc_start: xfer=%p\n", xfer)); if (sc->sc_dying) return (USBD_IOERROR); #ifdef DIAGNOSTIC if (xfer->status != USBD_IN_PROGRESS) printf("uhci_device_isoc_start: not in progress %p\n", xfer); #endif /* Find the last TD */ i = UXFER(xfer)->curframe + xfer->nframes; if (i >= UHCI_VFRAMELIST_COUNT) i -= UHCI_VFRAMELIST_COUNT; end = upipe->u.iso.stds[i]; #ifdef DIAGNOSTIC if (end == NULL) { printf("uhci_device_isoc_start: end == NULL\n"); return (USBD_INVAL); } #endif s = splusb(); /* Set up interrupt info. */ ii->xfer = xfer; ii->stdstart = end; ii->stdend = end; #ifdef DIAGNOSTIC if (!ii->isdone) printf("uhci_device_isoc_start: not done, ii=%p\n", ii); ii->isdone = 0; #endif uhci_add_intr_info(sc, ii); splx(s); return (USBD_IN_PROGRESS); } void uhci_device_isoc_abort(usbd_xfer_handle xfer) { struct uhci_pipe *upipe = (struct uhci_pipe *)xfer->pipe; uhci_soft_td_t **stds = upipe->u.iso.stds; uhci_soft_td_t *std; int i, n, s, nframes, maxlen, len; s = splusb(); /* Transfer is already done. */ if (xfer->status != USBD_NOT_STARTED && xfer->status != USBD_IN_PROGRESS) { splx(s); return; } /* Give xfer the requested abort code. */ xfer->status = USBD_CANCELLED; /* make hardware ignore it, */ nframes = xfer->nframes; n = UXFER(xfer)->curframe; maxlen = 0; for (i = 0; i < nframes; i++) { std = stds[n]; std->td.td_status &= htole32(~(UHCI_TD_ACTIVE | UHCI_TD_IOC)); len = UHCI_TD_GET_MAXLEN(le32toh(std->td.td_token)); if (len > maxlen) maxlen = len; if (++n >= UHCI_VFRAMELIST_COUNT) n = 0; } /* and wait until we are sure the hardware has finished. */ delay(maxlen); #ifdef DIAGNOSTIC UXFER(xfer)->iinfo.isdone = 1; #endif /* Run callback and remove from interrupt list. */ uhci_transfer_complete(xfer); splx(s); } void uhci_device_isoc_close(usbd_pipe_handle pipe) { struct uhci_pipe *upipe = (struct uhci_pipe *)pipe; usbd_device_handle dev = upipe->pipe.device; uhci_softc_t *sc = (uhci_softc_t *)dev->bus; uhci_soft_td_t *std, *vstd; struct iso *iso; int i, s; /* * Make sure all TDs are marked as inactive. * Wait for completion. * Unschedule. * Deallocate. */ iso = &upipe->u.iso; for (i = 0; i < UHCI_VFRAMELIST_COUNT; i++) iso->stds[i]->td.td_status &= htole32(~UHCI_TD_ACTIVE); usb_delay_ms(&sc->sc_bus, 2); /* wait for completion */ s = splusb(); for (i = 0; i < UHCI_VFRAMELIST_COUNT; i++) { std = iso->stds[i]; for (vstd = sc->sc_vframes[i].htd; vstd != NULL && vstd->link.std != std; vstd = vstd->link.std) ; if (vstd == NULL) { /*panic*/ printf("uhci_device_isoc_close: %p not found\n", std); splx(s); return; } vstd->link = std->link; vstd->td.td_link = std->td.td_link; uhci_free_std(sc, std); } splx(s); free(iso->stds, M_USBHC); } usbd_status uhci_setup_isoc(usbd_pipe_handle pipe) { struct uhci_pipe *upipe = (struct uhci_pipe *)pipe; usbd_device_handle dev = upipe->pipe.device; uhci_softc_t *sc = (uhci_softc_t *)dev->bus; int addr = upipe->pipe.device->address; int endpt = upipe->pipe.endpoint->edesc->bEndpointAddress; int rd = UE_GET_DIR(endpt) == UE_DIR_IN; uhci_soft_td_t *std, *vstd; u_int32_t token; struct iso *iso; int i, s; iso = &upipe->u.iso; iso->stds = malloc(UHCI_VFRAMELIST_COUNT * sizeof (uhci_soft_td_t *), M_USBHC, M_WAITOK); token = rd ? UHCI_TD_IN (0, endpt, addr, 0) : UHCI_TD_OUT(0, endpt, addr, 0); /* Allocate the TDs and mark as inactive; */ for (i = 0; i < UHCI_VFRAMELIST_COUNT; i++) { std = uhci_alloc_std(sc); if (std == 0) goto bad; std->td.td_status = htole32(UHCI_TD_IOS); /* iso, inactive */ std->td.td_token = htole32(token); iso->stds[i] = std; } /* Insert TDs into schedule. */ s = splusb(); for (i = 0; i < UHCI_VFRAMELIST_COUNT; i++) { std = iso->stds[i]; vstd = sc->sc_vframes[i].htd; std->link = vstd->link; std->td.td_link = vstd->td.td_link; vstd->link.std = std; vstd->td.td_link = htole32(std->physaddr | UHCI_PTR_TD); } splx(s); iso->next = -1; iso->inuse = 0; return (USBD_NORMAL_COMPLETION); bad: while (--i >= 0) uhci_free_std(sc, iso->stds[i]); free(iso->stds, M_USBHC); return (USBD_NOMEM); } void uhci_device_isoc_done(usbd_xfer_handle xfer) { uhci_intr_info_t *ii = &UXFER(xfer)->iinfo; DPRINTFN(4, ("uhci_isoc_done: length=%d\n", xfer->actlen)); if (ii->xfer != xfer) /* Not on interrupt list, ignore it. */ return; if (!uhci_active_intr_info(ii)) return; #ifdef DIAGNOSTIC if (xfer->busy_free != XFER_BUSY) { printf("uhci_device_isoc_done: xfer=%p not busy 0x%08x\n", xfer, xfer->busy_free); return; } if (ii->stdend == NULL) { printf("uhci_device_isoc_done: xfer=%p stdend==NULL\n", xfer); #ifdef USB_DEBUG uhci_dump_ii(ii); #endif return; } #endif /* Turn off the interrupt since it is active even if the TD is not. */ ii->stdend->td.td_status &= htole32(~UHCI_TD_IOC); uhci_del_intr_info(ii); /* remove from active list */ #ifdef DIAGNOSTIC if (ii->stdend == NULL) { printf("uhci_device_isoc_done: xfer=%p stdend==NULL\n", xfer); #ifdef USB_DEBUG uhci_dump_ii(ii); #endif return; } #endif ii->stdstart = NULL; ii->stdend = NULL; } void uhci_device_intr_done(usbd_xfer_handle xfer) { uhci_intr_info_t *ii = &UXFER(xfer)->iinfo; uhci_softc_t *sc = ii->sc; struct uhci_pipe *upipe = (struct uhci_pipe *)xfer->pipe; uhci_soft_qh_t *sqh; int i, npoll; DPRINTFN(5, ("uhci_device_intr_done: length=%d\n", xfer->actlen)); npoll = upipe->u.intr.npoll; for(i = 0; i < npoll; i++) { sqh = upipe->u.intr.qhs[i]; sqh->elink = NULL; sqh->qh.qh_elink = htole32(UHCI_PTR_T); } uhci_free_std_chain(sc, ii->stdstart, NULL); /* XXX Wasteful. */ if (xfer->pipe->repeat) { uhci_soft_td_t *data, *dataend; DPRINTFN(5,("uhci_device_intr_done: requeing\n")); /* This alloc cannot fail since we freed the chain above. */ uhci_alloc_std_chain(upipe, sc, xfer->length, upipe->u.intr.isread, xfer->flags, xfer, &data, &dataend); dataend->td.td_status |= htole32(UHCI_TD_IOC); #ifdef USB_DEBUG if (uhcidebug > 10) { DPRINTF(("uhci_device_intr_done: data(1)\n")); uhci_dump_tds(data); uhci_dump_qh(upipe->u.intr.qhs[0]); } #endif ii->stdstart = data; ii->stdend = dataend; #ifdef DIAGNOSTIC if (!ii->isdone) { printf("uhci_device_intr_done: not done, ii=%p\n", ii); } ii->isdone = 0; #endif for (i = 0; i < npoll; i++) { sqh = upipe->u.intr.qhs[i]; sqh->elink = data; sqh->qh.qh_elink = htole32(data->physaddr | UHCI_PTR_TD); } xfer->status = USBD_IN_PROGRESS; /* The ii is already on the examined list, just leave it. */ } else { DPRINTFN(5,("uhci_device_intr_done: removing\n")); if (uhci_active_intr_info(ii)) { uhci_del_intr_info(ii); ii->stdstart = NULL; ii->stdend = NULL; } } } /* Deallocate request data structures */ void uhci_device_ctrl_done(usbd_xfer_handle xfer) { uhci_intr_info_t *ii = &UXFER(xfer)->iinfo; uhci_softc_t *sc = ii->sc; struct uhci_pipe *upipe = (struct uhci_pipe *)xfer->pipe; #ifdef DIAGNOSTIC if (!(xfer->rqflags & URQ_REQUEST)) panic("uhci_device_ctrl_done: not a request"); #endif if (!uhci_active_intr_info(ii)) return; uhci_del_intr_info(ii); /* remove from active list */ if (upipe->pipe.device->speed == USB_SPEED_LOW) uhci_remove_ls_ctrl(sc, upipe->u.ctl.sqh); else uhci_remove_hs_ctrl(sc, upipe->u.ctl.sqh); if (upipe->u.ctl.length != 0) uhci_free_std_chain(sc, ii->stdstart->link.std, ii->stdend); ii->stdstart = NULL; ii->stdend = NULL; DPRINTFN(5, ("uhci_device_ctrl_done: length=%d\n", xfer->actlen)); } /* Deallocate request data structures */ void uhci_device_bulk_done(usbd_xfer_handle xfer) { uhci_intr_info_t *ii = &UXFER(xfer)->iinfo; uhci_softc_t *sc = ii->sc; struct uhci_pipe *upipe = (struct uhci_pipe *)xfer->pipe; DPRINTFN(5,("uhci_device_bulk_done: xfer=%p ii=%p sc=%p upipe=%p\n", xfer, ii, sc, upipe)); if (!uhci_active_intr_info(ii)) return; uhci_del_intr_info(ii); /* remove from active list */ uhci_remove_bulk(sc, upipe->u.bulk.sqh); uhci_free_std_chain(sc, ii->stdstart, NULL); ii->stdstart = NULL; ii->stdend = NULL; DPRINTFN(5, ("uhci_device_bulk_done: length=%d\n", xfer->actlen)); } /* Add interrupt QH, called with vflock. */ void uhci_add_intr(uhci_softc_t *sc, uhci_soft_qh_t *sqh) { struct uhci_vframe *vf = &sc->sc_vframes[sqh->pos]; uhci_soft_qh_t *eqh; DPRINTFN(4, ("uhci_add_intr: n=%d sqh=%p\n", sqh->pos, sqh)); eqh = vf->eqh; sqh->hlink = eqh->hlink; sqh->qh.qh_hlink = eqh->qh.qh_hlink; eqh->hlink = sqh; eqh->qh.qh_hlink = htole32(sqh->physaddr | UHCI_PTR_QH); vf->eqh = sqh; vf->bandwidth++; } /* Remove interrupt QH. */ void uhci_remove_intr(uhci_softc_t *sc, uhci_soft_qh_t *sqh) { struct uhci_vframe *vf = &sc->sc_vframes[sqh->pos]; uhci_soft_qh_t *pqh; DPRINTFN(4, ("uhci_remove_intr: n=%d sqh=%p\n", sqh->pos, sqh)); /* See comment in uhci_remove_ctrl() */ if (!(sqh->qh.qh_elink & htole32(UHCI_PTR_T))) { sqh->qh.qh_elink = htole32(UHCI_PTR_T); delay(UHCI_QH_REMOVE_DELAY); } pqh = uhci_find_prev_qh(vf->hqh, sqh); pqh->hlink = sqh->hlink; pqh->qh.qh_hlink = sqh->qh.qh_hlink; delay(UHCI_QH_REMOVE_DELAY); if (vf->eqh == sqh) vf->eqh = pqh; vf->bandwidth--; } usbd_status uhci_device_setintr(uhci_softc_t *sc, struct uhci_pipe *upipe, int ival) { uhci_soft_qh_t *sqh; int i, npoll, s; u_int bestbw, bw, bestoffs, offs; DPRINTFN(2, ("uhci_device_setintr: pipe=%p\n", upipe)); if (ival == 0) { printf("uhci_setintr: 0 interval\n"); return (USBD_INVAL); } if (ival > UHCI_VFRAMELIST_COUNT) ival = UHCI_VFRAMELIST_COUNT; npoll = (UHCI_VFRAMELIST_COUNT + ival - 1) / ival; DPRINTFN(2, ("uhci_device_setintr: ival=%d npoll=%d\n", ival, npoll)); upipe->u.intr.npoll = npoll; upipe->u.intr.qhs = malloc(npoll * sizeof(uhci_soft_qh_t *), M_USBHC, M_WAITOK); /* * Figure out which offset in the schedule that has most * bandwidth left over. */ #define MOD(i) ((i) & (UHCI_VFRAMELIST_COUNT-1)) for (bestoffs = offs = 0, bestbw = ~0; offs < ival; offs++) { for (bw = i = 0; i < npoll; i++) bw += sc->sc_vframes[MOD(i * ival + offs)].bandwidth; if (bw < bestbw) { bestbw = bw; bestoffs = offs; } } DPRINTFN(1, ("uhci_device_setintr: bw=%d offs=%d\n", bestbw, bestoffs)); for(i = 0; i < npoll; i++) { upipe->u.intr.qhs[i] = sqh = uhci_alloc_sqh(sc); sqh->elink = NULL; sqh->qh.qh_elink = htole32(UHCI_PTR_T); sqh->pos = MOD(i * ival + bestoffs); } #undef MOD s = splusb(); /* Enter QHs into the controller data structures. */ for(i = 0; i < npoll; i++) uhci_add_intr(sc, upipe->u.intr.qhs[i]); splx(s); DPRINTFN(5, ("uhci_device_setintr: returns %p\n", upipe)); return (USBD_NORMAL_COMPLETION); } /* Open a new pipe. */ usbd_status uhci_open(usbd_pipe_handle pipe) { uhci_softc_t *sc = (uhci_softc_t *)pipe->device->bus; struct uhci_pipe *upipe = (struct uhci_pipe *)pipe; usb_endpoint_descriptor_t *ed = pipe->endpoint->edesc; usbd_status err; int ival; DPRINTFN(1, ("uhci_open: pipe=%p, addr=%d, endpt=%d (%d)\n", pipe, pipe->device->address, ed->bEndpointAddress, sc->sc_addr)); upipe->aborting = 0; upipe->nexttoggle = pipe->endpoint->savedtoggle; if (pipe->device->address == sc->sc_addr) { switch (ed->bEndpointAddress) { case USB_CONTROL_ENDPOINT: pipe->methods = &uhci_root_ctrl_methods; break; case UE_DIR_IN | UHCI_INTR_ENDPT: pipe->methods = &uhci_root_intr_methods; break; default: return (USBD_INVAL); } } else { switch (ed->bmAttributes & UE_XFERTYPE) { case UE_CONTROL: pipe->methods = &uhci_device_ctrl_methods; upipe->u.ctl.sqh = uhci_alloc_sqh(sc); if (upipe->u.ctl.sqh == NULL) goto bad; upipe->u.ctl.setup = uhci_alloc_std(sc); if (upipe->u.ctl.setup == NULL) { uhci_free_sqh(sc, upipe->u.ctl.sqh); goto bad; } upipe->u.ctl.stat = uhci_alloc_std(sc); if (upipe->u.ctl.stat == NULL) { uhci_free_sqh(sc, upipe->u.ctl.sqh); uhci_free_std(sc, upipe->u.ctl.setup); goto bad; } err = usb_allocmem(&sc->sc_bus, sizeof(usb_device_request_t), 0, &upipe->u.ctl.reqdma); if (err) { uhci_free_sqh(sc, upipe->u.ctl.sqh); uhci_free_std(sc, upipe->u.ctl.setup); uhci_free_std(sc, upipe->u.ctl.stat); goto bad; } break; case UE_INTERRUPT: pipe->methods = &uhci_device_intr_methods; ival = pipe->interval; if (ival == USBD_DEFAULT_INTERVAL) ival = ed->bInterval; return (uhci_device_setintr(sc, upipe, ival)); case UE_ISOCHRONOUS: pipe->methods = &uhci_device_isoc_methods; return (uhci_setup_isoc(pipe)); case UE_BULK: pipe->methods = &uhci_device_bulk_methods; upipe->u.bulk.sqh = uhci_alloc_sqh(sc); if (upipe->u.bulk.sqh == NULL) goto bad; break; } } return (USBD_NORMAL_COMPLETION); bad: return (USBD_NOMEM); } /* * Data structures and routines to emulate the root hub. */ usb_device_descriptor_t uhci_devd = { USB_DEVICE_DESCRIPTOR_SIZE, UDESC_DEVICE, /* type */ {0x00, 0x01}, /* USB version */ UDCLASS_HUB, /* class */ UDSUBCLASS_HUB, /* subclass */ UDPROTO_FSHUB, /* protocol */ 64, /* max packet */ {0},{0},{0x00,0x01}, /* device id */ 1,2,0, /* string indicies */ 1 /* # of configurations */ }; usb_config_descriptor_t uhci_confd = { USB_CONFIG_DESCRIPTOR_SIZE, UDESC_CONFIG, {USB_CONFIG_DESCRIPTOR_SIZE + USB_INTERFACE_DESCRIPTOR_SIZE + USB_ENDPOINT_DESCRIPTOR_SIZE}, 1, 1, 0, UC_SELF_POWERED, 0 /* max power */ }; usb_interface_descriptor_t uhci_ifcd = { USB_INTERFACE_DESCRIPTOR_SIZE, UDESC_INTERFACE, 0, 0, 1, UICLASS_HUB, UISUBCLASS_HUB, UIPROTO_FSHUB, 0 }; usb_endpoint_descriptor_t uhci_endpd = { USB_ENDPOINT_DESCRIPTOR_SIZE, UDESC_ENDPOINT, UE_DIR_IN | UHCI_INTR_ENDPT, UE_INTERRUPT, {8}, 255 }; usb_hub_descriptor_t uhci_hubd_piix = { USB_HUB_DESCRIPTOR_SIZE, UDESC_HUB, 2, { UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL, 0 }, 50, /* power on to power good */ 0, { 0x00 }, /* both ports are removable */ }; int uhci_str(usb_string_descriptor_t *p, int l, char *s) { int i; if (l == 0) return (0); p->bLength = 2 * strlen(s) + 2; if (l == 1) return (1); p->bDescriptorType = UDESC_STRING; l -= 2; for (i = 0; s[i] && l > 1; i++, l -= 2) USETW2(p->bString[i], 0, s[i]); return (2*i+2); } /* * The USB hub protocol requires that SET_FEATURE(PORT_RESET) also * enables the port, and also states that SET_FEATURE(PORT_ENABLE) * should not be used by the USB subsystem. As we cannot issue a * SET_FEATURE(PORT_ENABLE) externally, we must ensure that the port * will be enabled as part of the reset. * * On the VT83C572, the port cannot be successfully enabled until the * outstanding "port enable change" and "connection status change" * events have been reset. */ static usbd_status uhci_portreset(uhci_softc_t *sc, int index) { int lim, port, x; if (index == 1) port = UHCI_PORTSC1; else if (index == 2) port = UHCI_PORTSC2; else return (USBD_IOERROR); x = URWMASK(UREAD2(sc, port)); UWRITE2(sc, port, x | UHCI_PORTSC_PR); usb_delay_ms(&sc->sc_bus, USB_PORT_ROOT_RESET_DELAY); DPRINTFN(3,("uhci port %d reset, status0 = 0x%04x\n", index, UREAD2(sc, port))); x = URWMASK(UREAD2(sc, port)); UWRITE2(sc, port, x & ~UHCI_PORTSC_PR); delay(100); DPRINTFN(3,("uhci port %d reset, status1 = 0x%04x\n", index, UREAD2(sc, port))); x = URWMASK(UREAD2(sc, port)); UWRITE2(sc, port, x | UHCI_PORTSC_PE); for (lim = 10; --lim > 0;) { usb_delay_ms(&sc->sc_bus, USB_PORT_RESET_DELAY); x = UREAD2(sc, port); DPRINTFN(3,("uhci port %d iteration %u, status = 0x%04x\n", index, lim, x)); if (!(x & UHCI_PORTSC_CCS)) { /* * No device is connected (or was disconnected * during reset). Consider the port reset. * The delay must be long enough to ensure on * the initial iteration that the device * connection will have been registered. 50ms * appears to be sufficient, but 20ms is not. */ DPRINTFN(3,("uhci port %d loop %u, device detached\n", index, lim)); break; } if (x & (UHCI_PORTSC_POEDC | UHCI_PORTSC_CSC)) { /* * Port enabled changed and/or connection * status changed were set. Reset either or * both raised flags (by writing a 1 to that * bit), and wait again for state to settle. */ UWRITE2(sc, port, URWMASK(x) | (x & (UHCI_PORTSC_POEDC | UHCI_PORTSC_CSC))); continue; } if (x & UHCI_PORTSC_PE) /* Port is enabled */ break; UWRITE2(sc, port, URWMASK(x) | UHCI_PORTSC_PE); } DPRINTFN(3,("uhci port %d reset, status2 = 0x%04x\n", index, UREAD2(sc, port))); if (lim <= 0) { DPRINTFN(1,("uhci port %d reset timed out\n", index)); return (USBD_TIMEOUT); } sc->sc_isreset = 1; return (USBD_NORMAL_COMPLETION); } /* * Simulate a hardware hub by handling all the necessary requests. */ usbd_status uhci_root_ctrl_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 (uhci_root_ctrl_start(STAILQ_FIRST(&xfer->pipe->queue))); } usbd_status uhci_root_ctrl_start(usbd_xfer_handle xfer) { uhci_softc_t *sc = (uhci_softc_t *)xfer->pipe->device->bus; usb_device_request_t *req; void *buf = NULL; int port, x; int s, len, value, index, status, change, l, totlen = 0; usb_port_status_t ps; usbd_status err; if (sc->sc_dying) return (USBD_IOERROR); #ifdef DIAGNOSTIC if (!(xfer->rqflags & URQ_REQUEST)) panic("uhci_root_ctrl_transfer: not a request"); #endif req = &xfer->request; DPRINTFN(2,("uhci_root_ctrl_control type=0x%02x request=%02x\n", req->bmRequestType, req->bRequest)); len = UGETW(req->wLength); value = UGETW(req->wValue); index = UGETW(req->wIndex); if (len != 0) buf = xfer->buffer; #define C(x,y) ((x) | ((y) << 8)) switch(C(req->bRequest, req->bmRequestType)) { case C(UR_CLEAR_FEATURE, UT_WRITE_DEVICE): case C(UR_CLEAR_FEATURE, UT_WRITE_INTERFACE): case C(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT): /* * DEVICE_REMOTE_WAKEUP and ENDPOINT_HALT are no-ops * for the integrated root hub. */ break; case C(UR_GET_CONFIG, UT_READ_DEVICE): if (len > 0) { *(u_int8_t *)buf = sc->sc_conf; totlen = 1; } break; case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE): DPRINTFN(2,("uhci_root_ctrl_control wValue=0x%04x\n", value)); switch(value >> 8) { case UDESC_DEVICE: if ((value & 0xff) != 0) { err = USBD_IOERROR; goto ret; } totlen = l = min(len, USB_DEVICE_DESCRIPTOR_SIZE); USETW(uhci_devd.idVendor, sc->sc_id_vendor); memcpy(buf, &uhci_devd, l); break; case UDESC_CONFIG: if ((value & 0xff) != 0) { err = USBD_IOERROR; goto ret; } totlen = l = min(len, USB_CONFIG_DESCRIPTOR_SIZE); memcpy(buf, &uhci_confd, l); buf = (char *)buf + l; len -= l; l = min(len, USB_INTERFACE_DESCRIPTOR_SIZE); totlen += l; memcpy(buf, &uhci_ifcd, l); buf = (char *)buf + l; len -= l; l = min(len, USB_ENDPOINT_DESCRIPTOR_SIZE); totlen += l; memcpy(buf, &uhci_endpd, l); break; case UDESC_STRING: if (len == 0) break; *(u_int8_t *)buf = 0; totlen = 1; switch (value & 0xff) { case 1: /* Vendor */ totlen = uhci_str(buf, len, sc->sc_vendor); break; case 2: /* Product */ totlen = uhci_str(buf, len, "UHCI root hub"); break; } break; default: err = USBD_IOERROR; goto ret; } break; case C(UR_GET_INTERFACE, UT_READ_INTERFACE): if (len > 0) { *(u_int8_t *)buf = 0; totlen = 1; } break; case C(UR_GET_STATUS, UT_READ_DEVICE): if (len > 1) { USETW(((usb_status_t *)buf)->wStatus,UDS_SELF_POWERED); totlen = 2; } break; case C(UR_GET_STATUS, UT_READ_INTERFACE): case C(UR_GET_STATUS, UT_READ_ENDPOINT): if (len > 1) { USETW(((usb_status_t *)buf)->wStatus, 0); totlen = 2; } break; case C(UR_SET_ADDRESS, UT_WRITE_DEVICE): if (value >= USB_MAX_DEVICES) { err = USBD_IOERROR; goto ret; } sc->sc_addr = value; break; case C(UR_SET_CONFIG, UT_WRITE_DEVICE): if (value != 0 && value != 1) { err = USBD_IOERROR; goto ret; } sc->sc_conf = value; break; case C(UR_SET_DESCRIPTOR, UT_WRITE_DEVICE): break; case C(UR_SET_FEATURE, UT_WRITE_DEVICE): case C(UR_SET_FEATURE, UT_WRITE_INTERFACE): case C(UR_SET_FEATURE, UT_WRITE_ENDPOINT): err = USBD_IOERROR; goto ret; case C(UR_SET_INTERFACE, UT_WRITE_INTERFACE): break; case C(UR_SYNCH_FRAME, UT_WRITE_ENDPOINT): break; /* Hub requests */ case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_DEVICE): break; case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_OTHER): DPRINTFN(3, ("uhci_root_ctrl_control: UR_CLEAR_PORT_FEATURE " "port=%d feature=%d\n", index, value)); if (index == 1) port = UHCI_PORTSC1; else if (index == 2) port = UHCI_PORTSC2; else { err = USBD_IOERROR; goto ret; } switch(value) { case UHF_PORT_ENABLE: x = URWMASK(UREAD2(sc, port)); UWRITE2(sc, port, x & ~UHCI_PORTSC_PE); break; case UHF_PORT_SUSPEND: x = URWMASK(UREAD2(sc, port)); UWRITE2(sc, port, x & ~UHCI_PORTSC_SUSP); break; case UHF_PORT_RESET: x = URWMASK(UREAD2(sc, port)); UWRITE2(sc, port, x & ~UHCI_PORTSC_PR); break; case UHF_C_PORT_CONNECTION: x = URWMASK(UREAD2(sc, port)); UWRITE2(sc, port, x | UHCI_PORTSC_CSC); break; case UHF_C_PORT_ENABLE: x = URWMASK(UREAD2(sc, port)); UWRITE2(sc, port, x | UHCI_PORTSC_POEDC); break; case UHF_C_PORT_OVER_CURRENT: x = URWMASK(UREAD2(sc, port)); UWRITE2(sc, port, x | UHCI_PORTSC_OCIC); break; case UHF_C_PORT_RESET: sc->sc_isreset = 0; err = USBD_NORMAL_COMPLETION; goto ret; case UHF_PORT_CONNECTION: case UHF_PORT_OVER_CURRENT: case UHF_PORT_POWER: case UHF_PORT_LOW_SPEED: case UHF_C_PORT_SUSPEND: default: err = USBD_IOERROR; goto ret; } break; case C(UR_GET_BUS_STATE, UT_READ_CLASS_OTHER): if (index == 1) port = UHCI_PORTSC1; else if (index == 2) port = UHCI_PORTSC2; else { err = USBD_IOERROR; goto ret; } if (len > 0) { *(u_int8_t *)buf = (UREAD2(sc, port) & UHCI_PORTSC_LS) >> UHCI_PORTSC_LS_SHIFT; totlen = 1; } break; case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE): if ((value & 0xff) != 0) { err = USBD_IOERROR; goto ret; } l = min(len, USB_HUB_DESCRIPTOR_SIZE); totlen = l; memcpy(buf, &uhci_hubd_piix, l); break; case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE): if (len != 4) { err = USBD_IOERROR; goto ret; } memset(buf, 0, len); totlen = len; break; case C(UR_GET_STATUS, UT_READ_CLASS_OTHER): if (index == 1) port = UHCI_PORTSC1; else if (index == 2) port = UHCI_PORTSC2; else { err = USBD_IOERROR; goto ret; } if (len != 4) { err = USBD_IOERROR; goto ret; } x = UREAD2(sc, port); status = change = 0; if (x & UHCI_PORTSC_CCS) status |= UPS_CURRENT_CONNECT_STATUS; if (x & UHCI_PORTSC_CSC) change |= UPS_C_CONNECT_STATUS; if (x & UHCI_PORTSC_PE) status |= UPS_PORT_ENABLED; if (x & UHCI_PORTSC_POEDC) change |= UPS_C_PORT_ENABLED; if (x & UHCI_PORTSC_OCI) status |= UPS_OVERCURRENT_INDICATOR; if (x & UHCI_PORTSC_OCIC) change |= UPS_C_OVERCURRENT_INDICATOR; if (x & UHCI_PORTSC_SUSP) status |= UPS_SUSPEND; if (x & UHCI_PORTSC_LSDA) status |= UPS_LOW_SPEED; status |= UPS_PORT_POWER; if (sc->sc_isreset) change |= UPS_C_PORT_RESET; USETW(ps.wPortStatus, status); USETW(ps.wPortChange, change); l = min(len, sizeof ps); memcpy(buf, &ps, l); totlen = l; break; case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE): err = USBD_IOERROR; goto ret; case C(UR_SET_FEATURE, UT_WRITE_CLASS_DEVICE): break; case C(UR_SET_FEATURE, UT_WRITE_CLASS_OTHER): if (index == 1) port = UHCI_PORTSC1; else if (index == 2) port = UHCI_PORTSC2; else { err = USBD_IOERROR; goto ret; } switch(value) { case UHF_PORT_ENABLE: x = URWMASK(UREAD2(sc, port)); UWRITE2(sc, port, x | UHCI_PORTSC_PE); break; case UHF_PORT_SUSPEND: x = URWMASK(UREAD2(sc, port)); UWRITE2(sc, port, x | UHCI_PORTSC_SUSP); break; case UHF_PORT_RESET: err = uhci_portreset(sc, index); goto ret; case UHF_PORT_POWER: /* Pretend we turned on power */ err = USBD_NORMAL_COMPLETION; goto ret; case UHF_C_PORT_CONNECTION: case UHF_C_PORT_ENABLE: case UHF_C_PORT_OVER_CURRENT: case UHF_PORT_CONNECTION: case UHF_PORT_OVER_CURRENT: case UHF_PORT_LOW_SPEED: case UHF_C_PORT_SUSPEND: case UHF_C_PORT_RESET: default: err = USBD_IOERROR; goto ret; } break; default: err = USBD_IOERROR; goto ret; } xfer->actlen = totlen; err = USBD_NORMAL_COMPLETION; ret: xfer->status = err; s = splusb(); uhci_transfer_complete(xfer); splx(s); return (USBD_IN_PROGRESS); } /* Abort a root control request. */ void uhci_root_ctrl_abort(usbd_xfer_handle xfer) { /* Nothing to do, all transfers are synchronous. */ } /* Close the root pipe. */ void uhci_root_ctrl_close(usbd_pipe_handle pipe) { DPRINTF(("uhci_root_ctrl_close\n")); } /* Abort a root interrupt request. */ void uhci_root_intr_abort(usbd_xfer_handle xfer) { uhci_softc_t *sc = (uhci_softc_t *)xfer->pipe->device->bus; - usb_uncallout(sc->sc_poll_handle, uhci_poll_hub, xfer); + callout_stop(&sc->sc_poll_handle); sc->sc_intr_xfer = NULL; if (xfer->pipe->intrxfer == xfer) { DPRINTF(("uhci_root_intr_abort: remove\n")); xfer->pipe->intrxfer = 0; } xfer->status = USBD_CANCELLED; #ifdef DIAGNOSTIC UXFER(xfer)->iinfo.isdone = 1; #endif uhci_transfer_complete(xfer); } usbd_status uhci_root_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 (uhci_root_intr_start(STAILQ_FIRST(&xfer->pipe->queue))); } /* Start a transfer on the root interrupt pipe */ usbd_status uhci_root_intr_start(usbd_xfer_handle xfer) { usbd_pipe_handle pipe = xfer->pipe; uhci_softc_t *sc = (uhci_softc_t *)pipe->device->bus; DPRINTFN(3, ("uhci_root_intr_start: xfer=%p len=%d flags=%d\n", xfer, xfer->length, xfer->flags)); if (sc->sc_dying) return (USBD_IOERROR); sc->sc_ival = MS_TO_TICKS(xfer->pipe->endpoint->edesc->bInterval); - usb_callout(sc->sc_poll_handle, sc->sc_ival, uhci_poll_hub, xfer); + callout_reset(&sc->sc_poll_handle, sc->sc_ival, uhci_poll_hub, xfer); sc->sc_intr_xfer = xfer; return (USBD_IN_PROGRESS); } /* Close the root interrupt pipe. */ void uhci_root_intr_close(usbd_pipe_handle pipe) { uhci_softc_t *sc = (uhci_softc_t *)pipe->device->bus; - usb_uncallout(sc->sc_poll_handle, uhci_poll_hub, sc->sc_intr_xfer); + callout_stop(&sc->sc_poll_handle); sc->sc_intr_xfer = NULL; DPRINTF(("uhci_root_intr_close\n")); } diff --git a/sys/dev/usb/uhcivar.h b/sys/dev/usb/uhcivar.h index 9c86d368492c..0df23f186ae7 100644 --- a/sys/dev/usb/uhcivar.h +++ b/sys/dev/usb/uhcivar.h @@ -1,206 +1,206 @@ /* $NetBSD: uhcivar.h,v 1.33 2002/02/11 11:41:30 augustss Exp $ */ /* $FreeBSD$ */ /*- * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * To avoid having 1024 TDs for each isochronous transfer we introduce * a virtual frame list. Every UHCI_VFRAMELIST_COUNT entries in the real * frame list points to a non-active TD. These, in turn, form the * starts of the virtual frame list. This also has the advantage that it * simplifies linking in/out of TDs/QHs in the schedule. * Furthermore, initially each of the inactive TDs point to an inactive * QH that forms the start of the interrupt traffic for that slot. * Each of these QHs point to the same QH that is the start of control * traffic. This QH points at another QH which is the start of the * bulk traffic. * * UHCI_VFRAMELIST_COUNT should be a power of 2 and <= UHCI_FRAMELIST_COUNT. */ #define UHCI_VFRAMELIST_COUNT 128 typedef struct uhci_soft_qh uhci_soft_qh_t; typedef struct uhci_soft_td uhci_soft_td_t; typedef union { struct uhci_soft_qh *sqh; struct uhci_soft_td *std; } uhci_soft_td_qh_t; /* * An interrupt info struct contains the information needed to * execute a requested routine when the controller generates an * interrupt. Since we cannot know which transfer generated * the interrupt all structs are linked together so they can be * searched at interrupt time. */ typedef struct uhci_intr_info { struct uhci_softc *sc; usbd_xfer_handle xfer; uhci_soft_td_t *stdstart; uhci_soft_td_t *stdend; LIST_ENTRY(uhci_intr_info) list; #ifdef DIAGNOSTIC int isdone; #endif } uhci_intr_info_t; struct uhci_xfer { struct usbd_xfer xfer; uhci_intr_info_t iinfo; struct usb_task abort_task; int curframe; u_int32_t uhci_xfer_flags; }; #define UHCI_XFER_ABORTING 0x0001 /* xfer is aborting. */ #define UHCI_XFER_ABORTWAIT 0x0002 /* abort completion is being awaited. */ #define UXFER(xfer) ((struct uhci_xfer *)(xfer)) /* * Extra information that we need for a TD. */ struct uhci_soft_td { uhci_td_t td; /* The real TD, must be first */ uhci_soft_td_qh_t link; /* soft version of the td_link field */ uhci_physaddr_t physaddr; /* TD's physical address. */ usb_dma_t aux_dma; /* Auxillary storage if needed. */ void *aux_data; /* Original aux data virtual address. */ int aux_len; /* Auxillary storage size. */ }; /* * Make the size such that it is a multiple of UHCI_TD_ALIGN. This way * we can pack a number of soft TD together and have the real TD well * aligned. * NOTE: Minimum size is 32 bytes. */ #define UHCI_STD_SIZE ((sizeof (struct uhci_soft_td) + UHCI_TD_ALIGN - 1) / UHCI_TD_ALIGN * UHCI_TD_ALIGN) #define UHCI_STD_CHUNK (PAGE_SIZE / UHCI_STD_SIZE) /* * Extra information that we need for a QH. */ struct uhci_soft_qh { uhci_qh_t qh; /* The real QH, must be first */ uhci_soft_qh_t *hlink; /* soft version of qh_hlink */ uhci_soft_td_t *elink; /* soft version of qh_elink */ uhci_physaddr_t physaddr; /* QH's physical address. */ int pos; /* Timeslot position */ }; /* See comment about UHCI_STD_SIZE. */ #define UHCI_SQH_SIZE ((sizeof (struct uhci_soft_qh) + UHCI_QH_ALIGN - 1) / UHCI_QH_ALIGN * UHCI_QH_ALIGN) #define UHCI_SQH_CHUNK (PAGE_SIZE / UHCI_SQH_SIZE) /* * Information about an entry in the virtual frame list. */ struct uhci_vframe { uhci_soft_td_t *htd; /* pointer to dummy TD */ uhci_soft_td_t *etd; /* pointer to last TD */ uhci_soft_qh_t *hqh; /* pointer to dummy QH */ uhci_soft_qh_t *eqh; /* pointer to last QH */ u_int bandwidth; /* max bandwidth used by this frame */ }; #define UHCI_SCFLG_DONEINIT 0x0001 /* uhci_init() done */ typedef struct uhci_softc { struct usbd_bus sc_bus; /* base device */ int sc_flags; bus_space_tag_t iot; bus_space_handle_t ioh; bus_size_t sc_size; void *ih; struct resource *io_res; struct resource *irq_res; uhci_physaddr_t *sc_pframes; usb_dma_t sc_dma; struct uhci_vframe sc_vframes[UHCI_VFRAMELIST_COUNT]; uhci_soft_qh_t *sc_lctl_start; /* dummy QH for low speed control */ uhci_soft_qh_t *sc_lctl_end; /* last control QH */ uhci_soft_qh_t *sc_hctl_start; /* dummy QH for high speed control */ uhci_soft_qh_t *sc_hctl_end; /* last control QH */ uhci_soft_qh_t *sc_bulk_start; /* dummy QH for bulk */ uhci_soft_qh_t *sc_bulk_end; /* last bulk transfer */ uhci_soft_qh_t *sc_last_qh; /* dummy QH at the end */ u_int32_t sc_loops; /* number of QHs that wants looping */ uhci_soft_td_t *sc_freetds; /* TD free list */ uhci_soft_qh_t *sc_freeqhs; /* QH free list */ STAILQ_HEAD(, usbd_xfer) sc_free_xfers; /* free xfers */ u_int8_t sc_addr; /* device address */ u_int8_t sc_conf; /* device configuration */ u_int8_t sc_saved_sof; u_int16_t sc_saved_frnum; #ifdef USB_USE_SOFTINTR char sc_softwake; #endif /* USB_USE_SOFTINTR */ char sc_isreset; char sc_suspend; char sc_dying; LIST_HEAD(, uhci_intr_info) sc_intrhead; /* Info for the root hub interrupt channel. */ int sc_ival; /* time between root hub intrs */ usbd_xfer_handle sc_intr_xfer; /* root hub interrupt transfer */ - usb_callout_t sc_poll_handle; + struct callout sc_poll_handle; char sc_vendor[16]; /* vendor string for root hub */ int sc_id_vendor; /* vendor ID for root hub */ #if defined(__NetBSD__) void *sc_powerhook; /* cookie from power hook */ void *sc_shutdownhook; /* cookie from shutdown hook */ #endif } uhci_softc_t; usbd_status uhci_init(uhci_softc_t *); int uhci_intr(void *); int uhci_detach(uhci_softc_t *, int); void uhci_shutdown(void *v); void uhci_power(int state, void *priv); diff --git a/sys/dev/usb/ukbd.c b/sys/dev/usb/ukbd.c index cbd536b5d0e0..039bb94140ad 100644 --- a/sys/dev/usb/ukbd.c +++ b/sys/dev/usb/ukbd.c @@ -1,1509 +1,1509 @@ /*- * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * Modifications for SUN TYPE 6 USB Keyboard by * Jörg Peter Schley (jps@scxnet.de) */ #include __FBSDID("$FreeBSD$"); /* * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf */ #include "opt_compat.h" #include "opt_kbd.h" #include "opt_ukbd.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #include #include #include #include #define UKBD_EMULATE_ATSCANCODE 1 #define DRIVER_NAME "ukbd" #define delay(d) DELAY(d) #ifdef USB_DEBUG #define DPRINTF(x) if (ukbddebug) logprintf x #define DPRINTFN(n,x) if (ukbddebug>(n)) logprintf x int ukbddebug = 0; SYSCTL_NODE(_hw_usb, OID_AUTO, ukbd, CTLFLAG_RW, 0, "USB ukbd"); SYSCTL_INT(_hw_usb_ukbd, OID_AUTO, debug, CTLFLAG_RW, &ukbddebug, 0, "ukbd debug level"); #else #define DPRINTF(x) #define DPRINTFN(n,x) #endif #define UPROTO_BOOT_KEYBOARD 1 #define NKEYCODE 6 struct ukbd_data { u_int8_t modifiers; #define MOD_CONTROL_L 0x01 #define MOD_CONTROL_R 0x10 #define MOD_SHIFT_L 0x02 #define MOD_SHIFT_R 0x20 #define MOD_ALT_L 0x04 #define MOD_ALT_R 0x40 #define MOD_WIN_L 0x08 #define MOD_WIN_R 0x80 u_int8_t reserved; u_int8_t keycode[NKEYCODE]; }; #define MAXKEYS (NMOD+2*NKEYCODE) typedef struct ukbd_softc { device_t sc_dev; /* base device */ } ukbd_softc_t; #define UKBD_CHUNK 128 /* chunk size for read */ #define UKBD_BSIZE 1020 /* buffer size */ typedef void usbd_intr_t(usbd_xfer_handle, usbd_private_handle, usbd_status); typedef void usbd_disco_t(void *); static int ukbd_resume(device_t self); static usbd_intr_t ukbd_intr; static int ukbd_driver_load(module_t mod, int what, void *arg); USB_DECLARE_DRIVER_INIT(ukbd, DEVMETHOD(device_resume, ukbd_resume)); static int ukbd_match(device_t self) { USB_MATCH_START(ukbd, uaa); keyboard_switch_t *sw; void *arg[2]; int unit = device_get_unit(self); sw = kbd_get_switch(DRIVER_NAME); if (sw == NULL) return (UMATCH_NONE); arg[0] = (void *)uaa; arg[1] = (void *)ukbd_intr; if ((*sw->probe)(unit, (void *)arg, 0)) return (UMATCH_NONE); if (usbd_get_quirks(uaa->device)->uq_flags & UQ_KBD_IGNORE) return (UMATCH_NONE); return (UMATCH_IFACECLASS_IFACESUBCLASS_IFACEPROTO); } static int ukbd_attach(device_t self) { USB_ATTACH_START(ukbd, sc, uaa); usbd_interface_handle iface = uaa->iface; usb_interface_descriptor_t *id; keyboard_switch_t *sw; keyboard_t *kbd; void *arg[2]; int unit = device_get_unit(self); sc->sc_dev = self; sw = kbd_get_switch(DRIVER_NAME); if (sw == NULL) return ENXIO; id = usbd_get_interface_descriptor(iface); arg[0] = (void *)uaa; arg[1] = (void *)ukbd_intr; kbd = NULL; if ((*sw->probe)(unit, (void *)arg, 0)) return ENXIO; if ((*sw->init)(unit, &kbd, (void *)arg, 0)) return ENXIO; (*sw->enable)(kbd); #ifdef KBD_INSTALL_CDEV if (kbd_attach(kbd)) return ENXIO; #endif if (bootverbose) (*sw->diag)(kbd, bootverbose); return 0; } int ukbd_detach(device_t self) { keyboard_t *kbd; int error; kbd = kbd_get_keyboard(kbd_find_keyboard(DRIVER_NAME, device_get_unit(self))); if (kbd == NULL) { DPRINTF(("%s: keyboard not attached!?\n", device_get_nameunit(self))); return ENXIO; } (*kbdsw[kbd->kb_index]->disable)(kbd); #ifdef KBD_INSTALL_CDEV error = kbd_detach(kbd); if (error) return error; #endif error = (*kbdsw[kbd->kb_index]->term)(kbd); if (error) return error; DPRINTF(("%s: disconnected\n", device_get_nameunit(self))); return (0); } static int ukbd_resume(device_t self) { keyboard_t *kbd; kbd = kbd_get_keyboard(kbd_find_keyboard(DRIVER_NAME, device_get_unit(self))); if (kbd) (*kbdsw[kbd->kb_index]->clear_state)(kbd); return (0); } void ukbd_intr(usbd_xfer_handle xfer, usbd_private_handle addr, usbd_status status) { keyboard_t *kbd = (keyboard_t *)addr; (*kbdsw[kbd->kb_index]->intr)(kbd, (void *)status); } DRIVER_MODULE(ukbd, uhub, ukbd_driver, ukbd_devclass, ukbd_driver_load, 0); #define UKBD_DEFAULT 0 #define KEY_ERROR 0x01 #define KEY_PRESS 0 #define KEY_RELEASE 0x400 #define KEY_INDEX(c) ((c) & ~KEY_RELEASE) #define SCAN_PRESS 0 #define SCAN_RELEASE 0x80 #define SCAN_PREFIX_E0 0x100 #define SCAN_PREFIX_E1 0x200 #define SCAN_PREFIX_CTL 0x400 #define SCAN_PREFIX_SHIFT 0x800 #define SCAN_PREFIX (SCAN_PREFIX_E0 | SCAN_PREFIX_E1 | SCAN_PREFIX_CTL \ | SCAN_PREFIX_SHIFT) #define SCAN_CHAR(c) ((c) & 0x7f) #define NMOD 8 static struct { int mask, key; } ukbd_mods[NMOD] = { { MOD_CONTROL_L, 0xe0 }, { MOD_CONTROL_R, 0xe4 }, { MOD_SHIFT_L, 0xe1 }, { MOD_SHIFT_R, 0xe5 }, { MOD_ALT_L, 0xe2 }, { MOD_ALT_R, 0xe6 }, { MOD_WIN_L, 0xe3 }, { MOD_WIN_R, 0xe7 }, }; #define NN 0 /* no translation */ /* * Translate USB keycodes to AT keyboard scancodes. */ /* * FIXME: Mac USB keyboard generates: * 0x53: keypad NumLock/Clear * 0x66: Power * 0x67: keypad = * 0x68: F13 * 0x69: F14 * 0x6a: F15 */ static u_int8_t ukbd_trtab[256] = { 0, 0, 0, 0, 30, 48, 46, 32, /* 00 - 07 */ 18, 33, 34, 35, 23, 36, 37, 38, /* 08 - 0F */ 50, 49, 24, 25, 16, 19, 31, 20, /* 10 - 17 */ 22, 47, 17, 45, 21, 44, 2, 3, /* 18 - 1F */ 4, 5, 6, 7, 8, 9, 10, 11, /* 20 - 27 */ 28, 1, 14, 15, 57, 12, 13, 26, /* 28 - 2F */ 27, 43, 43, 39, 40, 41, 51, 52, /* 30 - 37 */ 53, 58, 59, 60, 61, 62, 63, 64, /* 38 - 3F */ 65, 66, 67, 68, 87, 88, 92, 70, /* 40 - 47 */ 104, 102, 94, 96, 103, 99, 101, 98, /* 48 - 4F */ 97, 100, 95, 69, 91, 55, 74, 78, /* 50 - 57 */ 89, 79, 80, 81, 75, 76, 77, 71, /* 58 - 5F */ 72, 73, 82, 83, 86, 107, 122, NN, /* 60 - 67 */ NN, NN, NN, NN, NN, NN, NN, NN, /* 68 - 6F */ NN, NN, NN, NN, 115, 108, 111, 113, /* 70 - 77 */ 109, 110, 112, 118, 114, 116, 117, 119, /* 78 - 7F */ 121, 120, NN, NN, NN, NN, NN, 115, /* 80 - 87 */ 112, 125, 121, 123, NN, NN, NN, NN, /* 88 - 8F */ NN, NN, NN, NN, NN, NN, NN, NN, /* 90 - 97 */ NN, NN, NN, NN, NN, NN, NN, NN, /* 98 - 9F */ NN, NN, NN, NN, NN, NN, NN, NN, /* A0 - A7 */ NN, NN, NN, NN, NN, NN, NN, NN, /* A8 - AF */ NN, NN, NN, NN, NN, NN, NN, NN, /* B0 - B7 */ NN, NN, NN, NN, NN, NN, NN, NN, /* B8 - BF */ NN, NN, NN, NN, NN, NN, NN, NN, /* C0 - C7 */ NN, NN, NN, NN, NN, NN, NN, NN, /* C8 - CF */ NN, NN, NN, NN, NN, NN, NN, NN, /* D0 - D7 */ NN, NN, NN, NN, NN, NN, NN, NN, /* D8 - DF */ 29, 42, 56, 105, 90, 54, 93, 106, /* E0 - E7 */ NN, NN, NN, NN, NN, NN, NN, NN, /* E8 - EF */ NN, NN, NN, NN, NN, NN, NN, NN, /* F0 - F7 */ NN, NN, NN, NN, NN, NN, NN, NN, /* F8 - FF */ }; typedef struct ukbd_state { usbd_interface_handle ks_iface; /* interface */ usbd_pipe_handle ks_intrpipe; /* interrupt pipe */ struct usb_attach_arg *ks_uaa; int ks_ep_addr; struct ukbd_data ks_ndata; struct ukbd_data ks_odata; u_long ks_ntime[NKEYCODE]; u_long ks_otime[NKEYCODE]; #define INPUTBUFSIZE (NMOD + 2*NKEYCODE) u_int ks_input[INPUTBUFSIZE]; /* input buffer */ int ks_inputs; int ks_inputhead; int ks_inputtail; int ks_ifstate; #define INTRENABLED (1 << 0) #define DISCONNECTED (1 << 1) - usb_callout_t ks_timeout_handle; + struct callout ks_timeout_handle; int ks_mode; /* input mode (K_XLATE,K_RAW,K_CODE) */ int ks_flags; /* flags */ #define COMPOSE (1 << 0) int ks_polling; int ks_state; /* shift/lock key state */ int ks_accents; /* accent key index (> 0) */ u_int ks_composed_char; /* composed char code (> 0) */ #ifdef UKBD_EMULATE_ATSCANCODE u_int ks_buffered_char[2]; #endif } ukbd_state_t; /* keyboard driver declaration */ static int ukbd_configure(int flags); static kbd_probe_t ukbd_probe; static kbd_init_t ukbd_init; static kbd_term_t ukbd_term; static kbd_intr_t ukbd_interrupt; static kbd_test_if_t ukbd_test_if; static kbd_enable_t ukbd_enable; static kbd_disable_t ukbd_disable; static kbd_read_t ukbd_read; static kbd_check_t ukbd_check; static kbd_read_char_t ukbd_read_char; static kbd_check_char_t ukbd_check_char; static kbd_ioctl_t ukbd_ioctl; static kbd_lock_t ukbd_lock; static kbd_clear_state_t ukbd_clear_state; static kbd_get_state_t ukbd_get_state; static kbd_set_state_t ukbd_set_state; static kbd_poll_mode_t ukbd_poll; keyboard_switch_t ukbdsw = { ukbd_probe, ukbd_init, ukbd_term, ukbd_interrupt, ukbd_test_if, ukbd_enable, ukbd_disable, ukbd_read, ukbd_check, ukbd_read_char, ukbd_check_char, ukbd_ioctl, ukbd_lock, ukbd_clear_state, ukbd_get_state, ukbd_set_state, genkbd_get_fkeystr, ukbd_poll, genkbd_diag, }; KEYBOARD_DRIVER(ukbd, ukbdsw, ukbd_configure); /* local functions */ static int ukbd_enable_intr(keyboard_t *kbd, int on, usbd_intr_t *func); static void ukbd_timeout(void *arg); static int ukbd_getc(ukbd_state_t *state); static int probe_keyboard(struct usb_attach_arg *uaa, int flags); static int init_keyboard(ukbd_state_t *state, int *type, int flags); static void set_leds(ukbd_state_t *state, int leds); static int set_typematic(keyboard_t *kbd, int code); #ifdef UKBD_EMULATE_ATSCANCODE static int keycode2scancode(int keycode, int shift, int up); #endif /* local variables */ /* the initial key map, accent map and fkey strings */ #if defined(UKBD_DFLT_KEYMAP) && !defined(KLD_MODULE) #define KBD_DFLT_KEYMAP #include "ukbdmap.h" #endif #include /* structures for the default keyboard */ static keyboard_t default_kbd; static ukbd_state_t default_kbd_state; static keymap_t default_keymap; static accentmap_t default_accentmap; static fkeytab_t default_fkeytab[NUM_FKEYS]; /* * The back door to the keyboard driver! * This function is called by the console driver, via the kbdio module, * to tickle keyboard drivers when the low-level console is being initialized. * Almost nothing in the kernel has been initialied yet. Try to probe * keyboards if possible. * NOTE: because of the way the low-level conole is initialized, this routine * may be called more than once!! */ static int ukbd_configure(int flags) { return 0; #if 0 /* not yet */ keyboard_t *kbd; device_t device; struct usb_attach_arg *uaa; void *arg[2]; device = devclass_get_device(ukbd_devclass, UKBD_DEFAULT); if (device == NULL) return 0; uaa = (struct usb_attach_arg *)device_get_ivars(device); if (uaa == NULL) return 0; /* probe the default keyboard */ arg[0] = (void *)uaa; arg[1] = (void *)ukbd_intr; kbd = NULL; if (ukbd_probe(UKBD_DEFAULT, arg, flags)) return 0; if (ukbd_init(UKBD_DEFAULT, &kbd, arg, flags)) return 0; /* return the number of found keyboards */ return 1; #endif } /* low-level functions */ /* detect a keyboard */ static int ukbd_probe(int unit, void *arg, int flags) { void **data; struct usb_attach_arg *uaa; data = (void **)arg; uaa = (struct usb_attach_arg *)data[0]; /* XXX */ if (unit == UKBD_DEFAULT) { if (KBD_IS_PROBED(&default_kbd)) return 0; } if (probe_keyboard(uaa, flags)) return ENXIO; return 0; } /* reset and initialize the device */ static int ukbd_init(int unit, keyboard_t **kbdp, void *arg, int flags) { keyboard_t *kbd; ukbd_state_t *state; keymap_t *keymap; accentmap_t *accmap; fkeytab_t *fkeymap; int fkeymap_size; void **data = (void **)arg; struct usb_attach_arg *uaa = (struct usb_attach_arg *)data[0]; /* XXX */ if (unit == UKBD_DEFAULT) { *kbdp = kbd = &default_kbd; if (KBD_IS_INITIALIZED(kbd) && KBD_IS_CONFIGURED(kbd)) return 0; state = &default_kbd_state; keymap = &default_keymap; accmap = &default_accentmap; fkeymap = default_fkeytab; fkeymap_size = sizeof(default_fkeytab)/sizeof(default_fkeytab[0]); } else if (*kbdp == NULL) { *kbdp = kbd = malloc(sizeof(*kbd), M_DEVBUF, M_NOWAIT); if (kbd == NULL) return ENOMEM; bzero(kbd, sizeof(*kbd)); state = malloc(sizeof(*state), M_DEVBUF, M_NOWAIT); keymap = malloc(sizeof(key_map), M_DEVBUF, M_NOWAIT); accmap = malloc(sizeof(accent_map), M_DEVBUF, M_NOWAIT); fkeymap = malloc(sizeof(fkey_tab), M_DEVBUF, M_NOWAIT); fkeymap_size = sizeof(fkey_tab)/sizeof(fkey_tab[0]); if ((state == NULL) || (keymap == NULL) || (accmap == NULL) || (fkeymap == NULL)) { if (state != NULL) free(state, M_DEVBUF); if (keymap != NULL) free(keymap, M_DEVBUF); if (accmap != NULL) free(accmap, M_DEVBUF); if (fkeymap != NULL) free(fkeymap, M_DEVBUF); free(kbd, M_DEVBUF); return ENOMEM; } } else if (KBD_IS_INITIALIZED(*kbdp) && KBD_IS_CONFIGURED(*kbdp)) { return 0; } else { kbd = *kbdp; state = (ukbd_state_t *)kbd->kb_data; keymap = kbd->kb_keymap; accmap = kbd->kb_accentmap; fkeymap = kbd->kb_fkeytab; fkeymap_size = kbd->kb_fkeytab_size; } if (!KBD_IS_PROBED(kbd)) { kbd_init_struct(kbd, DRIVER_NAME, KB_OTHER, unit, flags, 0, 0); bzero(state, sizeof(*state)); bcopy(&key_map, keymap, sizeof(key_map)); bcopy(&accent_map, accmap, sizeof(accent_map)); bcopy(fkey_tab, fkeymap, imin(fkeymap_size*sizeof(fkeymap[0]), sizeof(fkey_tab))); kbd_set_maps(kbd, keymap, accmap, fkeymap, fkeymap_size); kbd->kb_data = (void *)state; if (probe_keyboard(uaa, flags)) return ENXIO; else KBD_FOUND_DEVICE(kbd); ukbd_clear_state(kbd); state->ks_mode = K_XLATE; state->ks_iface = uaa->iface; state->ks_uaa = uaa; state->ks_ifstate = 0; - usb_callout_init(state->ks_timeout_handle); + callout_init(&state->ks_timeout_handle, 0); /* * FIXME: set the initial value for lock keys in ks_state * according to the BIOS data? */ KBD_PROBE_DONE(kbd); } if (!KBD_IS_INITIALIZED(kbd) && !(flags & KB_CONF_PROBE_ONLY)) { if (KBD_HAS_DEVICE(kbd) && init_keyboard((ukbd_state_t *)kbd->kb_data, &kbd->kb_type, kbd->kb_flags)) return ENXIO; ukbd_ioctl(kbd, KDSETLED, (caddr_t)&(state->ks_state)); KBD_INIT_DONE(kbd); } if (!KBD_IS_CONFIGURED(kbd)) { if (kbd_register(kbd) < 0) return ENXIO; if (ukbd_enable_intr(kbd, TRUE, (usbd_intr_t *)data[1]) == 0) ukbd_timeout((void *)kbd); KBD_CONFIG_DONE(kbd); } return 0; } static int ukbd_enable_intr(keyboard_t *kbd, int on, usbd_intr_t *func) { ukbd_state_t *state = (ukbd_state_t *)kbd->kb_data; usbd_status err; if (on) { /* Set up interrupt pipe. */ if (state->ks_ifstate & INTRENABLED) return EBUSY; state->ks_ifstate |= INTRENABLED; err = usbd_open_pipe_intr(state->ks_iface, state->ks_ep_addr, USBD_SHORT_XFER_OK, &state->ks_intrpipe, kbd, &state->ks_ndata, sizeof(state->ks_ndata), func, USBD_DEFAULT_INTERVAL); if (err) return (EIO); } else { /* Disable interrupts. */ usbd_abort_pipe(state->ks_intrpipe); usbd_close_pipe(state->ks_intrpipe); state->ks_ifstate &= ~INTRENABLED; } return (0); } /* finish using this keyboard */ static int ukbd_term(keyboard_t *kbd) { ukbd_state_t *state; int error; int s; s = splusb(); state = (ukbd_state_t *)kbd->kb_data; DPRINTF(("ukbd_term: ks_ifstate=0x%x\n", state->ks_ifstate)); - usb_uncallout(state->ks_timeout_handle, ukbd_timeout, kbd); + callout_stop(&state->ks_timeout_handle); if (state->ks_ifstate & INTRENABLED) ukbd_enable_intr(kbd, FALSE, NULL); if (state->ks_ifstate & INTRENABLED) { splx(s); DPRINTF(("ukbd_term: INTRENABLED!\n")); return ENXIO; } error = kbd_unregister(kbd); DPRINTF(("ukbd_term: kbd_unregister() %d\n", error)); if (error == 0) { kbd->kb_flags = 0; if (kbd != &default_kbd) { free(kbd->kb_keymap, M_DEVBUF); free(kbd->kb_accentmap, M_DEVBUF); free(kbd->kb_fkeytab, M_DEVBUF); free(state, M_DEVBUF); free(kbd, M_DEVBUF); } } splx(s); return error; } /* keyboard interrupt routine */ static void ukbd_timeout(void *arg) { keyboard_t *kbd; ukbd_state_t *state; int s; kbd = (keyboard_t *)arg; state = (ukbd_state_t *)kbd->kb_data; s = splusb(); (*kbdsw[kbd->kb_index]->intr)(kbd, (void *)USBD_NORMAL_COMPLETION); - usb_callout(state->ks_timeout_handle, hz / 40, ukbd_timeout, arg); + callout_reset(&state->ks_timeout_handle, hz / 40, ukbd_timeout, arg); splx(s); } static int ukbd_interrupt(keyboard_t *kbd, void *arg) { usbd_status status = (usbd_status)arg; ukbd_state_t *state; struct ukbd_data *ud; struct timeval tv; u_long now; int mod, omod; int key, c; int i, j; DPRINTFN(5, ("ukbd_intr: status=%d\n", status)); if (status == USBD_CANCELLED) return 0; state = (ukbd_state_t *)kbd->kb_data; ud = &state->ks_ndata; if (status != USBD_NORMAL_COMPLETION) { DPRINTF(("ukbd_intr: status=%d\n", status)); if (status == USBD_STALLED) usbd_clear_endpoint_stall_async(state->ks_intrpipe); return 0; } if (ud->keycode[0] == KEY_ERROR) return 0; /* ignore */ getmicrouptime(&tv); now = (u_long)tv.tv_sec*1000 + (u_long)tv.tv_usec/1000; #define ADDKEY1(c) \ if (state->ks_inputs < INPUTBUFSIZE) { \ state->ks_input[state->ks_inputtail] = (c); \ ++state->ks_inputs; \ state->ks_inputtail = (state->ks_inputtail + 1)%INPUTBUFSIZE; \ } mod = ud->modifiers; omod = state->ks_odata.modifiers; if (mod != omod) { for (i = 0; i < NMOD; i++) if (( mod & ukbd_mods[i].mask) != (omod & ukbd_mods[i].mask)) ADDKEY1(ukbd_mods[i].key | (mod & ukbd_mods[i].mask ? KEY_PRESS : KEY_RELEASE)); } /* Check for released keys. */ for (i = 0; i < NKEYCODE; i++) { key = state->ks_odata.keycode[i]; if (key == 0) continue; for (j = 0; j < NKEYCODE; j++) { if (ud->keycode[j] == 0) continue; if (key == ud->keycode[j]) goto rfound; } ADDKEY1(key | KEY_RELEASE); rfound: ; } /* Check for pressed keys. */ for (i = 0; i < NKEYCODE; i++) { key = ud->keycode[i]; if (key == 0) continue; state->ks_ntime[i] = now + kbd->kb_delay1; for (j = 0; j < NKEYCODE; j++) { if (state->ks_odata.keycode[j] == 0) continue; if (key == state->ks_odata.keycode[j]) { state->ks_ntime[i] = state->ks_otime[j]; if (state->ks_otime[j] > now) goto pfound; state->ks_ntime[i] = now + kbd->kb_delay2; break; } } ADDKEY1(key | KEY_PRESS); pfound: ; } state->ks_odata = *ud; bcopy(state->ks_ntime, state->ks_otime, sizeof(state->ks_ntime)); if (state->ks_inputs <= 0) return 0; #ifdef USB_DEBUG for (i = state->ks_inputhead, j = 0; j < state->ks_inputs; ++j, i = (i + 1)%INPUTBUFSIZE) { c = state->ks_input[i]; DPRINTF(("0x%x (%d) %s\n", c, c, (c & KEY_RELEASE) ? "released":"pressed")); } if (ud->modifiers) DPRINTF(("mod:0x%04x ", ud->modifiers)); for (i = 0; i < NKEYCODE; i++) { if (ud->keycode[i]) DPRINTF(("%d ", ud->keycode[i])); } DPRINTF(("\n")); #endif /* USB_DEBUG */ if (state->ks_polling) return 0; if (KBD_IS_ACTIVE(kbd) && KBD_IS_BUSY(kbd)) { /* let the callback function to process the input */ (*kbd->kb_callback.kc_func)(kbd, KBDIO_KEYINPUT, kbd->kb_callback.kc_arg); } else { /* read and discard the input; no one is waiting for it */ do { c = ukbd_read_char(kbd, FALSE); } while (c != NOKEY); } return 0; } static int ukbd_getc(ukbd_state_t *state) { int c; int s; if (state->ks_polling) { DPRINTFN(1,("ukbd_getc: polling\n")); s = splusb(); while (state->ks_inputs <= 0) usbd_dopoll(state->ks_iface); splx(s); } s = splusb(); if (state->ks_inputs <= 0) { c = -1; } else { c = state->ks_input[state->ks_inputhead]; --state->ks_inputs; state->ks_inputhead = (state->ks_inputhead + 1)%INPUTBUFSIZE; } splx(s); return c; } /* test the interface to the device */ static int ukbd_test_if(keyboard_t *kbd) { return 0; } /* * Enable the access to the device; until this function is called, * the client cannot read from the keyboard. */ static int ukbd_enable(keyboard_t *kbd) { int s; s = splusb(); KBD_ACTIVATE(kbd); splx(s); return 0; } /* disallow the access to the device */ static int ukbd_disable(keyboard_t *kbd) { int s; s = splusb(); KBD_DEACTIVATE(kbd); splx(s); return 0; } /* read one byte from the keyboard if it's allowed */ static int ukbd_read(keyboard_t *kbd, int wait) { ukbd_state_t *state; int usbcode; #ifdef UKBD_EMULATE_ATSCANCODE int keycode; int scancode; #endif state = (ukbd_state_t *)kbd->kb_data; #ifdef UKBD_EMULATE_ATSCANCODE if (state->ks_buffered_char[0]) { scancode = state->ks_buffered_char[0]; if (scancode & SCAN_PREFIX) { state->ks_buffered_char[0] = scancode & ~SCAN_PREFIX; return ((scancode & SCAN_PREFIX_E0) ? 0xe0 : 0xe1); } else { state->ks_buffered_char[0] = state->ks_buffered_char[1]; state->ks_buffered_char[1] = 0; return scancode; } } #endif /* UKBD_EMULATE_ATSCANCODE */ /* XXX */ usbcode = ukbd_getc(state); if (!KBD_IS_ACTIVE(kbd) || (usbcode == -1)) return -1; ++kbd->kb_count; #ifdef UKBD_EMULATE_ATSCANCODE keycode = ukbd_trtab[KEY_INDEX(usbcode)]; if (keycode == NN) return -1; scancode = keycode2scancode(keycode, state->ks_ndata.modifiers, usbcode & KEY_RELEASE); if (scancode & SCAN_PREFIX) { if (scancode & SCAN_PREFIX_CTL) { state->ks_buffered_char[0] = 0x1d | (scancode & SCAN_RELEASE); /* Ctrl */ state->ks_buffered_char[1] = scancode & ~SCAN_PREFIX; } else if (scancode & SCAN_PREFIX_SHIFT) { state->ks_buffered_char[0] = 0x2a | (scancode & SCAN_RELEASE); /* Shift */ state->ks_buffered_char[1] = scancode & ~SCAN_PREFIX_SHIFT; } else { state->ks_buffered_char[0] = scancode & ~SCAN_PREFIX; state->ks_buffered_char[1] = 0; } return ((scancode & SCAN_PREFIX_E0) ? 0xe0 : 0xe1); } return scancode; #else /* !UKBD_EMULATE_ATSCANCODE */ return usbcode; #endif /* UKBD_EMULATE_ATSCANCODE */ } /* check if data is waiting */ static int ukbd_check(keyboard_t *kbd) { if (!KBD_IS_ACTIVE(kbd)) return FALSE; #ifdef UKBD_EMULATE_ATSCANCODE if (((ukbd_state_t *)kbd->kb_data)->ks_buffered_char[0]) return TRUE; #endif if (((ukbd_state_t *)kbd->kb_data)->ks_inputs > 0) return TRUE; return FALSE; } /* read char from the keyboard */ static u_int ukbd_read_char(keyboard_t *kbd, int wait) { ukbd_state_t *state; u_int action; int usbcode; int keycode; #ifdef UKBD_EMULATE_ATSCANCODE int scancode; #endif state = (ukbd_state_t *)kbd->kb_data; next_code: /* do we have a composed char to return? */ if (!(state->ks_flags & COMPOSE) && (state->ks_composed_char > 0)) { action = state->ks_composed_char; state->ks_composed_char = 0; if (action > UCHAR_MAX) return ERRKEY; return action; } #ifdef UKBD_EMULATE_ATSCANCODE /* do we have a pending raw scan code? */ if (state->ks_mode == K_RAW) { if (state->ks_buffered_char[0]) { scancode = state->ks_buffered_char[0]; if (scancode & SCAN_PREFIX) { state->ks_buffered_char[0] = scancode & ~SCAN_PREFIX; return ((scancode & SCAN_PREFIX_E0) ? 0xe0 : 0xe1); } else { state->ks_buffered_char[0] = state->ks_buffered_char[1]; state->ks_buffered_char[1] = 0; return scancode; } } } #endif /* UKBD_EMULATE_ATSCANCODE */ /* see if there is something in the keyboard port */ /* XXX */ usbcode = ukbd_getc(state); if (usbcode == -1) return NOKEY; ++kbd->kb_count; #ifdef UKBD_EMULATE_ATSCANCODE /* USB key index -> key code -> AT scan code */ keycode = ukbd_trtab[KEY_INDEX(usbcode)]; if (keycode == NN) return NOKEY; /* return an AT scan code for the K_RAW mode */ if (state->ks_mode == K_RAW) { scancode = keycode2scancode(keycode, state->ks_ndata.modifiers, usbcode & KEY_RELEASE); if (scancode & SCAN_PREFIX) { if (scancode & SCAN_PREFIX_CTL) { state->ks_buffered_char[0] = 0x1d | (scancode & SCAN_RELEASE); state->ks_buffered_char[1] = scancode & ~SCAN_PREFIX; } else if (scancode & SCAN_PREFIX_SHIFT) { state->ks_buffered_char[0] = 0x2a | (scancode & SCAN_RELEASE); state->ks_buffered_char[1] = scancode & ~SCAN_PREFIX_SHIFT; } else { state->ks_buffered_char[0] = scancode & ~SCAN_PREFIX; state->ks_buffered_char[1] = 0; } return ((scancode & SCAN_PREFIX_E0) ? 0xe0 : 0xe1); } return scancode; } #else /* !UKBD_EMULATE_ATSCANCODE */ /* return the byte as is for the K_RAW mode */ if (state->ks_mode == K_RAW) return usbcode; /* USB key index -> key code */ keycode = ukbd_trtab[KEY_INDEX(usbcode)]; if (keycode == NN) return NOKEY; #endif /* UKBD_EMULATE_ATSCANCODE */ switch (keycode) { case 0x38: /* left alt (compose key) */ if (usbcode & KEY_RELEASE) { if (state->ks_flags & COMPOSE) { state->ks_flags &= ~COMPOSE; if (state->ks_composed_char > UCHAR_MAX) state->ks_composed_char = 0; } } else { if (!(state->ks_flags & COMPOSE)) { state->ks_flags |= COMPOSE; state->ks_composed_char = 0; } } break; /* XXX: I don't like these... */ case 0x5c: /* print screen */ if (state->ks_flags & ALTS) keycode = 0x54; /* sysrq */ break; case 0x68: /* pause/break */ if (state->ks_flags & CTLS) keycode = 0x6c; /* break */ break; } /* return the key code in the K_CODE mode */ if (usbcode & KEY_RELEASE) keycode |= SCAN_RELEASE; if (state->ks_mode == K_CODE) return keycode; /* compose a character code */ if (state->ks_flags & COMPOSE) { switch (keycode) { /* key pressed, process it */ case 0x47: case 0x48: case 0x49: /* keypad 7,8,9 */ state->ks_composed_char *= 10; state->ks_composed_char += keycode - 0x40; if (state->ks_composed_char > UCHAR_MAX) return ERRKEY; goto next_code; case 0x4B: case 0x4C: case 0x4D: /* keypad 4,5,6 */ state->ks_composed_char *= 10; state->ks_composed_char += keycode - 0x47; if (state->ks_composed_char > UCHAR_MAX) return ERRKEY; goto next_code; case 0x4F: case 0x50: case 0x51: /* keypad 1,2,3 */ state->ks_composed_char *= 10; state->ks_composed_char += keycode - 0x4E; if (state->ks_composed_char > UCHAR_MAX) return ERRKEY; goto next_code; case 0x52: /* keypad 0 */ state->ks_composed_char *= 10; if (state->ks_composed_char > UCHAR_MAX) return ERRKEY; goto next_code; /* key released, no interest here */ case SCAN_RELEASE | 0x47: case SCAN_RELEASE | 0x48: case SCAN_RELEASE | 0x49: /* keypad 7,8,9 */ case SCAN_RELEASE | 0x4B: case SCAN_RELEASE | 0x4C: case SCAN_RELEASE | 0x4D: /* keypad 4,5,6 */ case SCAN_RELEASE | 0x4F: case SCAN_RELEASE | 0x50: case SCAN_RELEASE | 0x51: /* keypad 1,2,3 */ case SCAN_RELEASE | 0x52: /* keypad 0 */ goto next_code; case 0x38: /* left alt key */ break; default: if (state->ks_composed_char > 0) { state->ks_flags &= ~COMPOSE; state->ks_composed_char = 0; return ERRKEY; } break; } } /* keycode to key action */ action = genkbd_keyaction(kbd, SCAN_CHAR(keycode), keycode & SCAN_RELEASE, &state->ks_state, &state->ks_accents); if (action == NOKEY) goto next_code; else return action; } /* check if char is waiting */ static int ukbd_check_char(keyboard_t *kbd) { ukbd_state_t *state; if (!KBD_IS_ACTIVE(kbd)) return FALSE; state = (ukbd_state_t *)kbd->kb_data; if (!(state->ks_flags & COMPOSE) && (state->ks_composed_char > 0)) return TRUE; return ukbd_check(kbd); } /* some useful control functions */ static int ukbd_ioctl(keyboard_t *kbd, u_long cmd, caddr_t arg) { /* trasnlate LED_XXX bits into the device specific bits */ static u_char ledmap[8] = { 0, 2, 1, 3, 4, 6, 5, 7, }; ukbd_state_t *state = kbd->kb_data; int s; int i; #if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ defined(COMPAT_FREEBSD4) || defined(COMPAT_43) int ival; #endif s = splusb(); switch (cmd) { case KDGKBMODE: /* get keyboard mode */ *(int *)arg = state->ks_mode; break; #if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ defined(COMPAT_FREEBSD4) || defined(COMPAT_43) case _IO('K', 7): ival = IOCPARM_IVAL(arg); arg = (caddr_t)&ival; /* FALLTHROUGH */ #endif case KDSKBMODE: /* set keyboard mode */ switch (*(int *)arg) { case K_XLATE: if (state->ks_mode != K_XLATE) { /* make lock key state and LED state match */ state->ks_state &= ~LOCK_MASK; state->ks_state |= KBD_LED_VAL(kbd); } /* FALLTHROUGH */ case K_RAW: case K_CODE: if (state->ks_mode != *(int *)arg) { ukbd_clear_state(kbd); state->ks_mode = *(int *)arg; } break; default: splx(s); return EINVAL; } break; case KDGETLED: /* get keyboard LED */ *(int *)arg = KBD_LED_VAL(kbd); break; #if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ defined(COMPAT_FREEBSD4) || defined(COMPAT_43) case _IO('K', 66): ival = IOCPARM_IVAL(arg); arg = (caddr_t)&ival; /* FALLTHROUGH */ #endif case KDSETLED: /* set keyboard LED */ /* NOTE: lock key state in ks_state won't be changed */ if (*(int *)arg & ~LOCK_MASK) { splx(s); return EINVAL; } i = *(int *)arg; /* replace CAPS LED with ALTGR LED for ALTGR keyboards */ if (state->ks_mode == K_XLATE && kbd->kb_keymap->n_keys > ALTGR_OFFSET) { if (i & ALKED) i |= CLKED; else i &= ~CLKED; } if (KBD_HAS_DEVICE(kbd)) { set_leds(state, ledmap[i & LED_MASK]); /* XXX: error check? */ } KBD_LED_VAL(kbd) = *(int *)arg; break; case KDGKBSTATE: /* get lock key state */ *(int *)arg = state->ks_state & LOCK_MASK; break; #if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ defined(COMPAT_FREEBSD4) || defined(COMPAT_43) case _IO('K', 20): ival = IOCPARM_IVAL(arg); arg = (caddr_t)&ival; /* FALLTHROUGH */ #endif case KDSKBSTATE: /* set lock key state */ if (*(int *)arg & ~LOCK_MASK) { splx(s); return EINVAL; } state->ks_state &= ~LOCK_MASK; state->ks_state |= *(int *)arg; splx(s); /* set LEDs and quit */ return ukbd_ioctl(kbd, KDSETLED, arg); case KDSETREPEAT: /* set keyboard repeat rate (new interface) */ splx(s); if (!KBD_HAS_DEVICE(kbd)) return 0; if (((int *)arg)[1] < 0) return EINVAL; if (((int *)arg)[0] < 0) return EINVAL; else if (((int *)arg)[0] == 0) /* fastest possible value */ kbd->kb_delay1 = 200; else kbd->kb_delay1 = ((int *)arg)[0]; kbd->kb_delay2 = ((int *)arg)[1]; return 0; #if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ defined(COMPAT_FREEBSD4) || defined(COMPAT_43) case _IO('K', 67): ival = IOCPARM_IVAL(arg); arg = (caddr_t)&ival; /* FALLTHROUGH */ #endif case KDSETRAD: /* set keyboard repeat rate (old interface) */ splx(s); return set_typematic(kbd, *(int *)arg); case PIO_KEYMAP: /* set keyboard translation table */ case PIO_KEYMAPENT: /* set keyboard translation table entry */ case PIO_DEADKEYMAP: /* set accent key translation table */ state->ks_accents = 0; /* FALLTHROUGH */ default: splx(s); return genkbd_commonioctl(kbd, cmd, arg); #ifdef USB_DEBUG case USB_SETDEBUG: ukbddebug = *(int *)arg; break; #endif } splx(s); return 0; } /* lock the access to the keyboard */ static int ukbd_lock(keyboard_t *kbd, int lock) { /* XXX ? */ return TRUE; } /* clear the internal state of the keyboard */ static void ukbd_clear_state(keyboard_t *kbd) { ukbd_state_t *state; state = (ukbd_state_t *)kbd->kb_data; state->ks_flags = 0; state->ks_polling = 0; state->ks_state &= LOCK_MASK; /* preserve locking key state */ state->ks_accents = 0; state->ks_composed_char = 0; #ifdef UKBD_EMULATE_ATSCANCODE state->ks_buffered_char[0] = 0; state->ks_buffered_char[1] = 0; #endif bzero(&state->ks_ndata, sizeof(state->ks_ndata)); bzero(&state->ks_odata, sizeof(state->ks_odata)); bzero(&state->ks_ntime, sizeof(state->ks_ntime)); bzero(&state->ks_otime, sizeof(state->ks_otime)); } /* save the internal state */ static int ukbd_get_state(keyboard_t *kbd, void *buf, size_t len) { if (len == 0) return sizeof(ukbd_state_t); if (len < sizeof(ukbd_state_t)) return -1; bcopy(kbd->kb_data, buf, sizeof(ukbd_state_t)); return 0; } /* set the internal state */ static int ukbd_set_state(keyboard_t *kbd, void *buf, size_t len) { if (len < sizeof(ukbd_state_t)) return ENOMEM; bcopy(buf, kbd->kb_data, sizeof(ukbd_state_t)); return 0; } static int ukbd_poll(keyboard_t *kbd, int on) { ukbd_state_t *state; usbd_device_handle dev; int s; state = (ukbd_state_t *)kbd->kb_data; usbd_interface2device_handle(state->ks_iface, &dev); s = splusb(); if (on) { if (state->ks_polling == 0) usbd_set_polling(dev, on); ++state->ks_polling; } else { --state->ks_polling; if (state->ks_polling == 0) usbd_set_polling(dev, on); } splx(s); return 0; } /* local functions */ static int probe_keyboard(struct usb_attach_arg *uaa, int flags) { usb_interface_descriptor_t *id; if (!uaa->iface) /* we attach to ifaces only */ return EINVAL; /* Check that this is a keyboard that speaks the boot protocol. */ id = usbd_get_interface_descriptor(uaa->iface); if (id && id->bInterfaceClass == UICLASS_HID && id->bInterfaceSubClass == UISUBCLASS_BOOT && id->bInterfaceProtocol == UPROTO_BOOT_KEYBOARD) return 0; /* found it */ return EINVAL; } static int init_keyboard(ukbd_state_t *state, int *type, int flags) { usb_endpoint_descriptor_t *ed; usbd_status err; *type = KB_OTHER; state->ks_ifstate |= DISCONNECTED; ed = usbd_interface2endpoint_descriptor(state->ks_iface, 0); if (!ed) { printf("ukbd: could not read endpoint descriptor\n"); return EIO; } DPRINTFN(10,("ukbd:init_keyboard: \ bLength=%d bDescriptorType=%d bEndpointAddress=%d-%s bmAttributes=%d wMaxPacketSize=%d bInterval=%d\n", ed->bLength, ed->bDescriptorType, UE_GET_ADDR(ed->bEndpointAddress), UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN ? "in":"out", UE_GET_XFERTYPE(ed->bmAttributes), UGETW(ed->wMaxPacketSize), ed->bInterval)); if (UE_GET_DIR(ed->bEndpointAddress) != UE_DIR_IN || UE_GET_XFERTYPE(ed->bmAttributes) != UE_INTERRUPT) { printf("ukbd: unexpected endpoint\n"); return EINVAL; } if ((usbd_get_quirks(state->ks_uaa->device)->uq_flags & UQ_NO_SET_PROTO) == 0) { err = usbd_set_protocol(state->ks_iface, 0); DPRINTFN(5, ("ukbd:init_keyboard: protocol set\n")); if (err) { printf("ukbd: set protocol failed\n"); return EIO; } } /* Ignore if SETIDLE fails since it is not crucial. */ usbd_set_idle(state->ks_iface, 0, 0); state->ks_ep_addr = ed->bEndpointAddress; state->ks_ifstate &= ~DISCONNECTED; return 0; } static void set_leds(ukbd_state_t *state, int leds) { u_int8_t res = leds; DPRINTF(("ukbd:set_leds: state=%p leds=%d\n", state, leds)); usbd_set_report_async(state->ks_iface, UHID_OUTPUT_REPORT, 0, &res, 1); } static int set_typematic(keyboard_t *kbd, int code) { static int delays[] = { 250, 500, 750, 1000 }; static int rates[] = { 34, 38, 42, 46, 50, 55, 59, 63, 68, 76, 84, 92, 100, 110, 118, 126, 136, 152, 168, 184, 200, 220, 236, 252, 272, 304, 336, 368, 400, 440, 472, 504 }; if (code & ~0x7f) return EINVAL; kbd->kb_delay1 = delays[(code >> 5) & 3]; kbd->kb_delay2 = rates[code & 0x1f]; return 0; } #ifdef UKBD_EMULATE_ATSCANCODE static int keycode2scancode(int keycode, int shift, int up) { static int scan[] = { 0x1c, 0x1d, 0x35, 0x37 | SCAN_PREFIX_SHIFT, /* PrintScreen */ 0x38, 0x47, 0x48, 0x49, 0x4b, 0x4d, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x46, /* XXX Pause/Break */ 0x5b, 0x5c, 0x5d, /* SUN TYPE 6 USB KEYBOARD */ 0x68, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x25, 0x1f, 0x1e, 0x20, }; int scancode; scancode = keycode; if ((keycode >= 89) && (keycode < 89 + sizeof(scan)/sizeof(scan[0]))) scancode = scan[keycode - 89] | SCAN_PREFIX_E0; /* Pause/Break */ if ((keycode == 104) && !(shift & (MOD_CONTROL_L | MOD_CONTROL_R))) scancode = 0x45 | SCAN_PREFIX_E1 | SCAN_PREFIX_CTL; if (shift & (MOD_SHIFT_L | MOD_SHIFT_R)) scancode &= ~SCAN_PREFIX_SHIFT; return (scancode | (up ? SCAN_RELEASE : SCAN_PRESS)); } #endif /* UKBD_EMULATE_ATSCANCODE */ static int ukbd_driver_load(module_t mod, int what, void *arg) { switch (what) { case MOD_LOAD: kbd_add_driver(&ukbd_kbd_driver); break; case MOD_UNLOAD: kbd_delete_driver(&ukbd_kbd_driver); break; } return usbd_driver_load(mod, what, 0); } diff --git a/sys/dev/usb/usbdivar.h b/sys/dev/usb/usbdivar.h index eeb386c6f4b9..95d8e0eff1fe 100644 --- a/sys/dev/usb/usbdivar.h +++ b/sys/dev/usb/usbdivar.h @@ -1,320 +1,320 @@ /* $NetBSD: usbdivar.h,v 1.70 2002/07/11 21:14:36 augustss Exp $ */ /* $FreeBSD$ */ /*- * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* From usb_mem.h */ struct usb_dma_block; typedef struct { struct usb_dma_block *block; u_int offs; u_int len; } usb_dma_t; struct usbd_xfer; struct usbd_pipe; struct usbd_endpoint { usb_endpoint_descriptor_t *edesc; int refcnt; int savedtoggle; }; struct usbd_bus_methods { usbd_status (*open_pipe)(struct usbd_pipe *pipe); void (*soft_intr)(void *); void (*do_poll)(struct usbd_bus *); usbd_status (*allocm)(struct usbd_bus *, usb_dma_t *, u_int32_t bufsize); void (*freem)(struct usbd_bus *, usb_dma_t *); struct usbd_xfer * (*allocx)(struct usbd_bus *); void (*freex)(struct usbd_bus *, struct usbd_xfer *); }; struct usbd_pipe_methods { usbd_status (*transfer)(usbd_xfer_handle xfer); usbd_status (*start)(usbd_xfer_handle xfer); void (*abort)(usbd_xfer_handle xfer); void (*close)(usbd_pipe_handle pipe); void (*cleartoggle)(usbd_pipe_handle pipe); void (*done)(usbd_xfer_handle xfer); }; struct usbd_tt { struct usbd_hub *hub; }; struct usbd_port { usb_port_status_t status; u_int16_t power; /* mA of current on port */ u_int8_t portno; u_int8_t restartcnt; #define USBD_RESTART_MAX 5 struct usbd_device *device; /* Connected device */ struct usbd_device *parent; /* The ports hub */ struct usbd_tt *tt; /* Transaction translator (if any) */ }; struct usbd_hub { usbd_status (*explore)(usbd_device_handle hub); void *hubsoftc; usb_hub_descriptor_t hubdesc; struct usbd_port ports[1]; }; struct usb_softc; /*****/ struct usbd_bus { /* Filled by HC driver */ device_t bdev; /* base device, host adapter */ struct usbd_bus_methods *methods; u_int32_t pipe_size; /* size of a pipe struct */ /* Filled by usb driver */ struct usbd_device *root_hub; usbd_device_handle devices[USB_MAX_DEVICES]; char needs_explore;/* a hub a signalled a change */ char use_polling; struct usb_softc *usbctl; struct usb_device_stats stats; int intr_context; u_int no_intrs; int usbrev; /* USB revision */ #define USBREV_UNKNOWN 0 #define USBREV_PRE_1_0 1 #define USBREV_1_0 2 #define USBREV_1_1 3 #define USBREV_2_0 4 #define USBREV_STR { "unknown", "pre 1.0", "1.0", "1.1", "2.0" } #ifdef USB_USE_SOFTINTR #ifdef __HAVE_GENERIC_SOFT_INTERRUPTS void *soft; /* soft interrupt cookie */ #else struct callout softi; #endif #endif bus_dma_tag_t parent_dmatag; /* Base DMA tag */ bus_dma_tag_t buffer_dmatag; /* Tag for transfer buffers */ }; struct usbd_device { struct usbd_bus *bus; /* our controller */ struct usbd_pipe *default_pipe; /* pipe 0 */ u_int8_t address; /* device addess */ u_int8_t config; /* current configuration # */ u_int8_t depth; /* distance from root hub */ u_int8_t speed; /* low/full/high speed */ u_int8_t self_powered; /* flag for self powered */ u_int16_t power; /* mA the device uses */ int16_t langid; /* language for strings */ #define USBD_NOLANG (-1) usb_event_cookie_t cookie; /* unique connection id */ struct usbd_port *powersrc; /* upstream hub port, or 0 */ struct usbd_device *myhub; /* upstream hub */ struct usbd_port *myhsport; /* closest high speed port */ struct usbd_endpoint def_ep; /* for pipe 0 */ usb_endpoint_descriptor_t def_ep_desc; /* for pipe 0 */ struct usbd_interface *ifaces; /* array of all interfaces */ usb_device_descriptor_t ddesc; /* device descriptor */ usb_config_descriptor_t *cdesc; /* full config descr */ const struct usbd_quirks *quirks; /* device quirks, always set */ struct usbd_hub *hub; /* only if this is a hub */ device_t *subdevs; /* sub-devices, 0 terminated */ uint8_t *ifacenums; /* sub-device interfacenumbers */ }; struct usbd_interface { struct usbd_device *device; usb_interface_descriptor_t *idesc; int index; int altindex; struct usbd_endpoint *endpoints; void *priv; LIST_HEAD(, usbd_pipe) pipes; }; struct usbd_pipe { struct usbd_interface *iface; struct usbd_device *device; struct usbd_endpoint *endpoint; int refcnt; char running; char aborting; STAILQ_HEAD(, usbd_xfer) queue; LIST_ENTRY(usbd_pipe) next; usbd_xfer_handle intrxfer; /* used for repeating requests */ char repeat; int interval; /* Filled by HC driver. */ struct usbd_pipe_methods *methods; }; #define USB_DMA_NSEG (btoc(MAXPHYS) + 1) /* DMA-capable memory buffer. */ struct usb_dma_mapping { bus_dma_segment_t segs[USB_DMA_NSEG]; /* The physical segments. */ int nsegs; /* Number of segments. */ bus_dmamap_t map; /* DMA mapping. */ }; struct usbd_xfer { struct usbd_pipe *pipe; void *priv; void *buffer; u_int32_t length; u_int32_t actlen; u_int16_t flags; u_int32_t timeout; usbd_status status; usbd_callback callback; __volatile char done; #ifdef DIAGNOSTIC u_int32_t busy_free; #define XFER_FREE 0x46524545 #define XFER_BUSY 0x42555359 #define XFER_ONQU 0x4f4e5155 #endif /* For control pipe */ usb_device_request_t request; /* For isoc */ u_int16_t *frlengths; int nframes; /* For memory allocation */ struct usbd_device *device; struct usb_dma_mapping dmamap; void *allocbuf; int rqflags; #define URQ_REQUEST 0x01 #define URQ_AUTO_DMABUF 0x10 #define URQ_DEV_DMABUF 0x20 STAILQ_ENTRY(usbd_xfer) next; void *hcpriv; /* private use by the HC driver */ - usb_callout_t timeout_handle; + struct callout timeout_handle; }; void usbd_init(void); void usbd_finish(void); #ifdef USB_DEBUG void usbd_dump_iface(struct usbd_interface *iface); void usbd_dump_device(struct usbd_device *dev); void usbd_dump_endpoint(struct usbd_endpoint *endp); void usbd_dump_queue(usbd_pipe_handle pipe); void usbd_dump_pipe(usbd_pipe_handle pipe); #endif /* Routines from usb_subr.c */ int usbctlprint(void *, const char *); void usb_delay_ms(usbd_bus_handle, u_int); usbd_status usbd_reset_port(usbd_device_handle dev, int port, usb_port_status_t *ps); usbd_status usbd_setup_pipe(usbd_device_handle dev, usbd_interface_handle iface, struct usbd_endpoint *, int, usbd_pipe_handle *pipe); usbd_status usbd_new_device(device_t parent, usbd_bus_handle bus, int depth, int lowspeed, int port, struct usbd_port *); void usbd_remove_device(usbd_device_handle, struct usbd_port *); int usbd_printBCD(char *cp, int bcd); usbd_status usbd_fill_iface_data(usbd_device_handle dev, int i, int a); void usb_free_device(usbd_device_handle); usbd_status usb_insert_transfer(usbd_xfer_handle xfer); void usb_transfer_complete(usbd_xfer_handle xfer); void usb_disconnect_port(struct usbd_port *up, device_t); /* Routines from usb.c */ void usb_needs_explore(usbd_device_handle); void usb_schedsoftintr(struct usbd_bus *); /* * XXX This check is extremely bogus. Bad Bad Bad. */ #if defined(DIAGNOSTIC) && 0 #define SPLUSBCHECK \ do { int _s = splusb(), _su = splusb(); \ if (!cold && _s != _su) printf("SPLUSBCHECK failed 0x%x!=0x%x, %s:%d\n", \ _s, _su, __FILE__, __LINE__); \ splx(_s); \ } while (0) #else #define SPLUSBCHECK #endif /* Locator stuff. */ /* XXX these values are used to statically bind some elements in the USB tree * to specific driver instances. This should be somehow emulated in FreeBSD * but can be done later on. * The values are copied from the files.usb file in the NetBSD sources. */ #define UHUBCF_PORT_DEFAULT -1 #define UHUBCF_CONFIGURATION_DEFAULT -1 #define UHUBCF_INTERFACE_DEFAULT -1 #define UHUBCF_VENDOR_DEFAULT -1 #define UHUBCF_PRODUCT_DEFAULT -1 #define UHUBCF_RELEASE_DEFAULT -1 #define uhubcf_port cf_loc[UHUBCF_PORT] #define uhubcf_configuration cf_loc[UHUBCF_CONFIGURATION] #define uhubcf_interface cf_loc[UHUBCF_INTERFACE] #define uhubcf_vendor cf_loc[UHUBCF_VENDOR] #define uhubcf_product cf_loc[UHUBCF_PRODUCT] #define uhubcf_release cf_loc[UHUBCF_RELEASE] #define UHUB_UNK_PORT UHUBCF_PORT_DEFAULT /* wildcarded 'port' */ #define UHUB_UNK_CONFIGURATION UHUBCF_CONFIGURATION_DEFAULT /* wildcarded 'configuration' */ #define UHUB_UNK_INTERFACE UHUBCF_INTERFACE_DEFAULT /* wildcarded 'interface' */ #define UHUB_UNK_VENDOR UHUBCF_VENDOR_DEFAULT /* wildcarded 'vendor' */ #define UHUB_UNK_PRODUCT UHUBCF_PRODUCT_DEFAULT /* wildcarded 'product' */ #define UHUB_UNK_RELEASE UHUBCF_RELEASE_DEFAULT /* wildcarded 'release' */