diff --git a/sys/dev/usb/ehci.c b/sys/dev/usb/ehci.c index ca311ec7e417..6a396b97678e 100644 --- a/sys/dev/usb/ehci.c +++ b/sys/dev/usb/ehci.c @@ -1,3416 +1,3417 @@ /* $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 #if defined(__NetBSD__) || defined(__OpenBSD__) #include #include #elif defined(__FreeBSD__) #include #include #include #include #if defined(DIAGNOSTIC) && defined(__i386__) && defined(__FreeBSD__) #include #endif #endif #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__FreeBSD__) #define delay(d) DELAY(d) #endif #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"); #ifndef __NetBSD__ #define bitmask_snprintf(q,f,b,l) snprintf((b), (l), "%b", (q), (f)) #endif #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); 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); 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); #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); 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; #if defined(__NetBSD__) || defined(__OpenBSD__) if (sc->sc_child != NULL) rv = config_detach(sc->sc_child, flags); if (rv != 0) return (rv); #else sc->sc_dying = 1; #endif 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); #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); } #if defined(__NetBSD__) || defined(__OpenBSD__) int ehci_activate(device_t self, enum devact act) { struct ehci_softc *sc = (struct ehci_softc *)self; int rv = 0; switch (act) { case DVACT_ACTIVATE: return (EOPNOTSUPP); case DVACT_DEACTIVATE: if (sc->sc_child != NULL) rv = config_deactivate(sc->sc_child); sc->sc_dying = 1; break; } return (rv); } #endif /* * 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); 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); 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_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); } 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); } 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), 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, 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/if_axe.c b/sys/dev/usb/if_axe.c index 89219194536f..94e386c2ddc7 100644 --- a/sys/dev/usb/if_axe.c +++ b/sys/dev/usb/if_axe.c @@ -1,1167 +1,1167 @@ /*- * Copyright (c) 1997, 1998, 1999, 2000-2003 * Bill Paul . All rights reserved. * * 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 Bill Paul. * 4. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY Bill Paul 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 Bill Paul OR THE VOICES IN HIS HEAD * 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. */ #include __FBSDID("$FreeBSD$"); /* * ASIX Electronics AX88172 USB 2.0 ethernet driver. Used in the * LinkSys USB200M and various other adapters. * * Manuals available from: * http://www.asix.com.tw/datasheet/mac/Ax88172.PDF * Note: you need the manual for the AX88170 chip (USB 1.x ethernet * controller) to find the definitions for the RX control register. * http://www.asix.com.tw/datasheet/mac/Ax88170.PDF * * Written by Bill Paul * Senior Engineer * Wind River Systems */ /* * The AX88172 provides USB ethernet supports at 10 and 100Mbps. * It uses an external PHY (reference designs use a RealTek chip), * and has a 64-bit multicast hash filter. There is some information * missing from the manual which one needs to know in order to make * the chip function: * * - You must set bit 7 in the RX control register, otherwise the * chip won't receive any packets. * - You must initialize all 3 IPG registers, or you won't be able * to send any packets. * * Note that this device appears to only support loading the station * address via autload from the EEPROM (i.e. there's no way to manaully * set it). * * (Adam Weinberger wanted me to name this driver if_gir.c.) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if __FreeBSD_version < 500000 #include #endif #include #include #include #include #include "usbdevs.h" #include #include #include /* "device miibus" required. See GENERIC if you get errors here. */ #include "miibus_if.h" #include /* * Various supported device vendors/products. */ static struct axe_type axe_devs[] = { { USB_VENDOR_ASIX, USB_PRODUCT_ASIX_AX88172 }, { USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DUBE100 }, { USB_VENDOR_JVC, USB_PRODUCT_JVC_MP_PRX1 }, { USB_VENDOR_LINKSYS2, USB_PRODUCT_LINKSYS2_USB200M }, { USB_VENDOR_MELCO, USB_PRODUCT_MELCO_LUAU2KTX }, { USB_VENDOR_NETGEAR, USB_PRODUCT_NETGEAR_FA120 }, { USB_VENDOR_SYSTEMTALKS, USB_PRODUCT_SYSTEMTALKS_SGCX2UL }, { USB_VENDOR_SITECOM, USB_PRODUCT_SITECOM_LN029 }, { 0, 0 } }; static int axe_match(device_t); static int axe_attach(device_t); static int axe_detach(device_t); static int axe_encap(struct axe_softc *, struct mbuf *, int); static void axe_rxeof(usbd_xfer_handle, usbd_private_handle, usbd_status); static void axe_txeof(usbd_xfer_handle, usbd_private_handle, usbd_status); static void axe_tick(void *); static void axe_tick_task(void *); static void axe_rxstart(struct ifnet *); static void axe_start(struct ifnet *); static int axe_ioctl(struct ifnet *, u_long, caddr_t); static void axe_init(void *); static void axe_stop(struct axe_softc *); static void axe_watchdog(struct ifnet *); static void axe_shutdown(device_t); static int axe_miibus_readreg(device_t, int, int); static int axe_miibus_writereg(device_t, int, int, int); static void axe_miibus_statchg(device_t); static int axe_cmd(struct axe_softc *, int, int, int, void *); static int axe_ifmedia_upd(struct ifnet *); static void axe_ifmedia_sts(struct ifnet *, struct ifmediareq *); static void axe_setmulti(struct axe_softc *); static device_method_t axe_methods[] = { /* Device interface */ DEVMETHOD(device_probe, axe_match), DEVMETHOD(device_attach, axe_attach), DEVMETHOD(device_detach, axe_detach), DEVMETHOD(device_shutdown, axe_shutdown), /* bus interface */ DEVMETHOD(bus_print_child, bus_generic_print_child), DEVMETHOD(bus_driver_added, bus_generic_driver_added), /* MII interface */ DEVMETHOD(miibus_readreg, axe_miibus_readreg), DEVMETHOD(miibus_writereg, axe_miibus_writereg), DEVMETHOD(miibus_statchg, axe_miibus_statchg), { 0, 0 } }; static driver_t axe_driver = { "axe", axe_methods, sizeof(struct axe_softc) }; static devclass_t axe_devclass; DRIVER_MODULE(axe, uhub, axe_driver, axe_devclass, usbd_driver_load, 0); DRIVER_MODULE(miibus, axe, miibus_driver, miibus_devclass, 0, 0); MODULE_DEPEND(axe, usb, 1, 1, 1); MODULE_DEPEND(axe, miibus, 1, 1, 1); static int axe_cmd(struct axe_softc *sc, int cmd, int index, int val, void *buf) { usb_device_request_t req; usbd_status err; AXE_SLEEPLOCKASSERT(sc); if (sc->axe_dying) return(0); if (AXE_CMD_DIR(cmd)) req.bmRequestType = UT_WRITE_VENDOR_DEVICE; else req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = AXE_CMD_CMD(cmd); USETW(req.wValue, val); USETW(req.wIndex, index); USETW(req.wLength, AXE_CMD_LEN(cmd)); err = usbd_do_request(sc->axe_udev, &req, buf); if (err) return(-1); return(0); } static int axe_miibus_readreg(device_t dev, int phy, int reg) { struct axe_softc *sc = USBGETSOFTC(dev); usbd_status err; u_int16_t val; if (sc->axe_dying) return(0); AXE_SLEEPLOCKASSERT(sc); #ifdef notdef /* * The chip tells us the MII address of any supported * PHYs attached to the chip, so only read from those. */ if (sc->axe_phyaddrs[0] != AXE_NOPHY && phy != sc->axe_phyaddrs[0]) return (0); if (sc->axe_phyaddrs[1] != AXE_NOPHY && phy != sc->axe_phyaddrs[1]) return (0); #endif if (sc->axe_phyaddrs[0] != 0xFF && sc->axe_phyaddrs[0] != phy) return (0); AXE_LOCK(sc); axe_cmd(sc, AXE_CMD_MII_OPMODE_SW, 0, 0, NULL); err = axe_cmd(sc, AXE_CMD_MII_READ_REG, reg, phy, (void *)&val); axe_cmd(sc, AXE_CMD_MII_OPMODE_HW, 0, 0, NULL); AXE_UNLOCK(sc); if (err) { printf("axe%d: read PHY failed\n", sc->axe_unit); return(-1); } if (val) sc->axe_phyaddrs[0] = phy; return (val); } static int axe_miibus_writereg(device_t dev, int phy, int reg, int val) { struct axe_softc *sc = USBGETSOFTC(dev); usbd_status err; if (sc->axe_dying) return(0); AXE_SLEEPLOCKASSERT(sc); AXE_LOCK(sc); axe_cmd(sc, AXE_CMD_MII_OPMODE_SW, 0, 0, NULL); err = axe_cmd(sc, AXE_CMD_MII_WRITE_REG, reg, phy, (void *)&val); axe_cmd(sc, AXE_CMD_MII_OPMODE_HW, 0, 0, NULL); AXE_UNLOCK(sc); if (err) { printf("axe%d: write PHY failed\n", sc->axe_unit); return(-1); } return (0); } static void axe_miibus_statchg(device_t dev) { #ifdef notdef struct axe_softc *sc = USBGETSOFTC(dev); struct mii_data *mii = GET_MII(sc); #endif /* doesn't seem to be necessary */ return; } /* * Set media options. */ static int axe_ifmedia_upd(struct ifnet *ifp) { struct axe_softc *sc = ifp->if_softc; struct mii_data *mii = GET_MII(sc); sc->axe_link = 0; if (mii->mii_instance) { struct mii_softc *miisc; LIST_FOREACH(miisc, &mii->mii_phys, mii_list) mii_phy_reset(miisc); } mii_mediachg(mii); return (0); } /* * Report current media status. */ static void axe_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr) { struct axe_softc *sc = ifp->if_softc; struct mii_data *mii = GET_MII(sc); mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; return; } static void axe_setmulti(struct axe_softc *sc) { struct ifnet *ifp; struct ifmultiaddr *ifma; u_int32_t h = 0; u_int16_t rxmode; u_int8_t hashtbl[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; ifp = sc->axe_ifp; AXE_LOCK(sc); axe_cmd(sc, AXE_CMD_RXCTL_READ, 0, 0, (void *)&rxmode); if (ifp->if_flags & IFF_ALLMULTI || ifp->if_flags & IFF_PROMISC) { rxmode |= AXE_RXCMD_ALLMULTI; axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, rxmode, NULL); AXE_UNLOCK(sc); return; } else rxmode &= ~AXE_RXCMD_ALLMULTI; IF_ADDR_LOCK(ifp); #if __FreeBSD_version >= 500000 TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) #else LIST_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) #endif { if (ifma->ifma_addr->sa_family != AF_LINK) continue; h = ether_crc32_be(LLADDR((struct sockaddr_dl *) ifma->ifma_addr), ETHER_ADDR_LEN) >> 26; hashtbl[h / 8] |= 1 << (h % 8); } IF_ADDR_UNLOCK(ifp); axe_cmd(sc, AXE_CMD_WRITE_MCAST, 0, 0, (void *)&hashtbl); axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, rxmode, NULL); AXE_UNLOCK(sc); return; } static void axe_reset(struct axe_softc *sc) { if (sc->axe_dying) return; if (usbd_set_config_no(sc->axe_udev, AXE_CONFIG_NO, 1) || usbd_device2interface_handle(sc->axe_udev, AXE_IFACE_IDX, &sc->axe_iface)) { printf("axe%d: getting interface handle failed\n", sc->axe_unit); } /* Wait a little while for the chip to get its brains in order. */ DELAY(1000); return; } /* * Probe for a AX88172 chip. */ USB_MATCH(axe) { USB_MATCH_START(axe, uaa); struct axe_type *t; if (!uaa->iface) return(UMATCH_NONE); t = axe_devs; while(t->axe_vid) { if (uaa->vendor == t->axe_vid && uaa->product == t->axe_did) { return(UMATCH_VENDOR_PRODUCT); } t++; } return(UMATCH_NONE); } /* * Attach the interface. Allocate softc structures, do ifmedia * setup and ethernet/BPF attach. */ USB_ATTACH(axe) { USB_ATTACH_START(axe, sc, uaa); char devinfo[1024]; u_char eaddr[ETHER_ADDR_LEN]; struct ifnet *ifp; usb_interface_descriptor_t *id; usb_endpoint_descriptor_t *ed; int i; bzero(sc, sizeof(struct axe_softc)); sc->axe_udev = uaa->device; sc->axe_dev = self; sc->axe_unit = device_get_unit(self); if (usbd_set_config_no(sc->axe_udev, AXE_CONFIG_NO, 1)) { printf("axe%d: getting interface handle failed\n", sc->axe_unit); USB_ATTACH_ERROR_RETURN; } usb_init_task(&sc->axe_tick_task, axe_tick_task, sc); if (usbd_device2interface_handle(uaa->device, AXE_IFACE_IDX, &sc->axe_iface)) { printf("axe%d: getting interface handle failed\n", sc->axe_unit); USB_ATTACH_ERROR_RETURN; } id = usbd_get_interface_descriptor(sc->axe_iface); usbd_devinfo(uaa->device, 0, devinfo); device_set_desc_copy(self, devinfo); printf("%s: %s\n", device_get_nameunit(self), devinfo); /* Find endpoints. */ for (i = 0; i < id->bNumEndpoints; i++) { ed = usbd_interface2endpoint_descriptor(sc->axe_iface, i); if (!ed) { printf("axe%d: couldn't get ep %d\n", sc->axe_unit, i); USB_ATTACH_ERROR_RETURN; } if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { sc->axe_ed[AXE_ENDPT_RX] = ed->bEndpointAddress; } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT && UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { sc->axe_ed[AXE_ENDPT_TX] = ed->bEndpointAddress; } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && UE_GET_XFERTYPE(ed->bmAttributes) == UE_INTERRUPT) { sc->axe_ed[AXE_ENDPT_INTR] = ed->bEndpointAddress; } } #if __FreeBSD_version >= 500000 mtx_init(&sc->axe_mtx, device_get_nameunit(self), MTX_NETWORK_LOCK, MTX_DEF | MTX_RECURSE); #endif sx_init(&sc->axe_sleeplock, device_get_nameunit(self)); AXE_SLEEPLOCK(sc); AXE_LOCK(sc); /* * Get station address. */ axe_cmd(sc, AXE_CMD_READ_NODEID, 0, 0, &eaddr); /* * Load IPG values and PHY indexes. */ axe_cmd(sc, AXE_CMD_READ_IPG012, 0, 0, (void *)&sc->axe_ipgs); axe_cmd(sc, AXE_CMD_READ_PHYID, 0, 0, (void *)&sc->axe_phyaddrs); /* * Work around broken adapters that appear to lie about * their PHY addresses. */ sc->axe_phyaddrs[0] = sc->axe_phyaddrs[1] = 0xFF; ifp = sc->axe_ifp = if_alloc(IFT_ETHER); if (ifp == NULL) { printf("axe%d: can not if_alloc()\n", sc->axe_unit); AXE_UNLOCK(sc); AXE_SLEEPUNLOCK(sc); sx_destroy(&sc->axe_sleeplock); #if __FreeBSD_version >= 500000 mtx_destroy(&sc->axe_mtx); #endif USB_ATTACH_ERROR_RETURN; } ifp->if_softc = sc; if_initname(ifp, "axe", sc->axe_unit); ifp->if_mtu = ETHERMTU; ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST | IFF_NEEDSGIANT; ifp->if_ioctl = axe_ioctl; ifp->if_start = axe_start; ifp->if_watchdog = axe_watchdog; ifp->if_init = axe_init; ifp->if_snd.ifq_maxlen = IFQ_MAXLEN; sc->axe_qdat.ifp = ifp; sc->axe_qdat.if_rxstart = axe_rxstart; if (mii_phy_probe(self, &sc->axe_miibus, axe_ifmedia_upd, axe_ifmedia_sts)) { printf("axe%d: MII without any PHY!\n", sc->axe_unit); if_free(ifp); AXE_UNLOCK(sc); AXE_SLEEPUNLOCK(sc); sx_destroy(&sc->axe_sleeplock); #if __FreeBSD_version >= 500000 mtx_destroy(&sc->axe_mtx); #endif USB_ATTACH_ERROR_RETURN; } /* * Call MI attach routine. */ #if __FreeBSD_version >= 500000 ether_ifattach(ifp, eaddr); #else ether_ifattach(ifp, ETHER_BPF_SUPPORTED); #endif callout_handle_init(&sc->axe_stat_ch); usb_register_netisr(); sc->axe_dying = 0; AXE_UNLOCK(sc); AXE_SLEEPUNLOCK(sc); USB_ATTACH_SUCCESS_RETURN; } static int axe_detach(device_t dev) { struct axe_softc *sc; struct ifnet *ifp; sc = device_get_softc(dev); AXE_LOCK(sc); ifp = sc->axe_ifp; sc->axe_dying = 1; untimeout(axe_tick, sc, sc->axe_stat_ch); usb_rem_task(sc->axe_udev, &sc->axe_tick_task); #if __FreeBSD_version >= 500000 ether_ifdetach(ifp); if_free(ifp); #else ether_ifdetach(ifp, ETHER_BPF_SUPPORTED); #endif if (sc->axe_ep[AXE_ENDPT_TX] != NULL) usbd_abort_pipe(sc->axe_ep[AXE_ENDPT_TX]); if (sc->axe_ep[AXE_ENDPT_RX] != NULL) usbd_abort_pipe(sc->axe_ep[AXE_ENDPT_RX]); if (sc->axe_ep[AXE_ENDPT_INTR] != NULL) usbd_abort_pipe(sc->axe_ep[AXE_ENDPT_INTR]); AXE_UNLOCK(sc); sx_destroy(&sc->axe_sleeplock); #if __FreeBSD_version >= 500000 mtx_destroy(&sc->axe_mtx); #endif return(0); } static void axe_rxstart(struct ifnet *ifp) { struct axe_softc *sc; struct ue_chain *c; sc = ifp->if_softc; AXE_LOCK(sc); c = &sc->axe_cdata.ue_rx_chain[sc->axe_cdata.ue_rx_prod]; c->ue_mbuf = usb_ether_newbuf(); if (c->ue_mbuf == NULL) { printf("%s: no memory for rx list " "-- packet dropped!\n", device_get_nameunit(sc->axe_dev)); ifp->if_ierrors++; AXE_UNLOCK(sc); return; } /* Setup new transfer. */ usbd_setup_xfer(c->ue_xfer, sc->axe_ep[AXE_ENDPT_RX], c, mtod(c->ue_mbuf, char *), UE_BUFSZ, USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT, axe_rxeof); usbd_transfer(c->ue_xfer); AXE_UNLOCK(sc); return; } /* * A frame has been uploaded: pass the resulting mbuf chain up to * the higher level protocols. */ static void axe_rxeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) { struct axe_softc *sc; struct ue_chain *c; struct mbuf *m; struct ifnet *ifp; int total_len = 0; c = priv; sc = c->ue_sc; AXE_LOCK(sc); ifp = sc->axe_ifp; if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { AXE_UNLOCK(sc); return; } if (status != USBD_NORMAL_COMPLETION) { if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) { AXE_UNLOCK(sc); return; } if (usbd_ratecheck(&sc->axe_rx_notice)) printf("axe%d: usb error on rx: %s\n", sc->axe_unit, usbd_errstr(status)); if (status == USBD_STALLED) usbd_clear_endpoint_stall(sc->axe_ep[AXE_ENDPT_RX]); goto done; } usbd_get_xfer_status(xfer, NULL, NULL, &total_len, NULL); m = c->ue_mbuf; if (total_len < sizeof(struct ether_header)) { ifp->if_ierrors++; goto done; } ifp->if_ipackets++; m->m_pkthdr.rcvif = (void *)&sc->axe_qdat; m->m_pkthdr.len = m->m_len = total_len; /* Put the packet on the special USB input queue. */ usb_ether_input(m); AXE_UNLOCK(sc); return; done: /* Setup new transfer. */ usbd_setup_xfer(c->ue_xfer, sc->axe_ep[AXE_ENDPT_RX], c, mtod(c->ue_mbuf, char *), UE_BUFSZ, USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT, axe_rxeof); usbd_transfer(c->ue_xfer); AXE_UNLOCK(sc); return; } /* * A frame was downloaded to the chip. It's safe for us to clean up * the list buffers. */ static void axe_txeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) { struct axe_softc *sc; struct ue_chain *c; struct ifnet *ifp; usbd_status err; c = priv; sc = c->ue_sc; AXE_LOCK(sc); ifp = sc->axe_ifp; if (status != USBD_NORMAL_COMPLETION) { if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) { AXE_UNLOCK(sc); return; } printf("axe%d: usb error on tx: %s\n", sc->axe_unit, usbd_errstr(status)); if (status == USBD_STALLED) usbd_clear_endpoint_stall(sc->axe_ep[AXE_ENDPT_TX]); AXE_UNLOCK(sc); return; } ifp->if_timer = 0; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; usbd_get_xfer_status(c->ue_xfer, NULL, NULL, NULL, &err); if (c->ue_mbuf != NULL) { c->ue_mbuf->m_pkthdr.rcvif = ifp; usb_tx_done(c->ue_mbuf); c->ue_mbuf = NULL; } if (err) ifp->if_oerrors++; else ifp->if_opackets++; AXE_UNLOCK(sc); return; } static void axe_tick(void *xsc) { struct axe_softc *sc = xsc; if (sc == NULL) return; if (sc->axe_dying) return; /* Perform periodic stuff in process context */ - usb_add_task(sc->axe_udev, &sc->axe_tick_task); + usb_add_task(sc->axe_udev, &sc->axe_tick_task, USB_TASKQ_DRIVER); } static void axe_tick_task(void *xsc) { struct axe_softc *sc; struct ifnet *ifp; struct mii_data *mii; sc = xsc; if (sc == NULL) return; AXE_SLEEPLOCK(sc); AXE_LOCK(sc); ifp = sc->axe_ifp; mii = GET_MII(sc); if (mii == NULL) { AXE_UNLOCK(sc); AXE_SLEEPUNLOCK(sc); return; } mii_tick(mii); if (!sc->axe_link && mii->mii_media_status & IFM_ACTIVE && IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE) { sc->axe_link++; if (ifp->if_snd.ifq_head != NULL) axe_start(ifp); } sc->axe_stat_ch = timeout(axe_tick, sc, hz); AXE_UNLOCK(sc); AXE_SLEEPUNLOCK(sc); return; } static int axe_encap(struct axe_softc *sc, struct mbuf *m, int idx) { struct ue_chain *c; usbd_status err; c = &sc->axe_cdata.ue_tx_chain[idx]; /* * Copy the mbuf data into a contiguous buffer, leaving two * bytes at the beginning to hold the frame length. */ m_copydata(m, 0, m->m_pkthdr.len, c->ue_buf); c->ue_mbuf = m; usbd_setup_xfer(c->ue_xfer, sc->axe_ep[AXE_ENDPT_TX], c, c->ue_buf, m->m_pkthdr.len, USBD_FORCE_SHORT_XFER, 10000, axe_txeof); /* Transmit */ err = usbd_transfer(c->ue_xfer); if (err != USBD_IN_PROGRESS) { /* XXX probably don't want to sleep here */ AXE_SLEEPLOCK(sc); axe_stop(sc); AXE_SLEEPUNLOCK(sc); return(EIO); } sc->axe_cdata.ue_tx_cnt++; return(0); } static void axe_start(struct ifnet *ifp) { struct axe_softc *sc; struct mbuf *m_head = NULL; sc = ifp->if_softc; AXE_LOCK(sc); if (!sc->axe_link) { AXE_UNLOCK(sc); return; } if (ifp->if_drv_flags & IFF_DRV_OACTIVE) { AXE_UNLOCK(sc); return; } IF_DEQUEUE(&ifp->if_snd, m_head); if (m_head == NULL) { AXE_UNLOCK(sc); return; } if (axe_encap(sc, m_head, 0)) { IF_PREPEND(&ifp->if_snd, m_head); ifp->if_drv_flags |= IFF_DRV_OACTIVE; AXE_UNLOCK(sc); return; } /* * If there's a BPF listener, bounce a copy of this frame * to him. */ BPF_MTAP(ifp, m_head); ifp->if_drv_flags |= IFF_DRV_OACTIVE; /* * Set a timeout in case the chip goes out to lunch. */ ifp->if_timer = 5; AXE_UNLOCK(sc); return; } static void axe_init(void *xsc) { struct axe_softc *sc = xsc; struct ifnet *ifp = sc->axe_ifp; struct ue_chain *c; usbd_status err; int i; int rxmode; if (ifp->if_drv_flags & IFF_DRV_RUNNING) return; AXE_SLEEPLOCK(sc); AXE_LOCK(sc); /* * Cancel pending I/O and free all RX/TX buffers. */ axe_reset(sc); #ifdef notdef /* Set MAC address */ axe_mac(sc, IF_LLADDR(sc->axe_ifp), 1); #endif /* Enable RX logic. */ /* Init TX ring. */ if (usb_ether_tx_list_init(sc, &sc->axe_cdata, sc->axe_udev) == ENOBUFS) { printf("axe%d: tx list init failed\n", sc->axe_unit); AXE_UNLOCK(sc); AXE_SLEEPUNLOCK(sc); return; } /* Init RX ring. */ if (usb_ether_rx_list_init(sc, &sc->axe_cdata, sc->axe_udev) == ENOBUFS) { printf("axe%d: rx list init failed\n", sc->axe_unit); AXE_UNLOCK(sc); AXE_SLEEPUNLOCK(sc); return; } /* Set transmitter IPG values */ axe_cmd(sc, AXE_CMD_WRITE_IPG0, 0, sc->axe_ipgs[0], NULL); axe_cmd(sc, AXE_CMD_WRITE_IPG1, 0, sc->axe_ipgs[1], NULL); axe_cmd(sc, AXE_CMD_WRITE_IPG2, 0, sc->axe_ipgs[2], NULL); /* Enable receiver, set RX mode */ rxmode = AXE_RXCMD_UNICAST|AXE_RXCMD_MULTICAST|AXE_RXCMD_ENABLE; /* If we want promiscuous mode, set the allframes bit. */ if (ifp->if_flags & IFF_PROMISC) rxmode |= AXE_RXCMD_PROMISC; if (ifp->if_flags & IFF_BROADCAST) rxmode |= AXE_RXCMD_BROADCAST; axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, rxmode, NULL); /* Load the multicast filter. */ axe_setmulti(sc); /* Open RX and TX pipes. */ err = usbd_open_pipe(sc->axe_iface, sc->axe_ed[AXE_ENDPT_RX], USBD_EXCLUSIVE_USE, &sc->axe_ep[AXE_ENDPT_RX]); if (err) { printf("axe%d: open rx pipe failed: %s\n", sc->axe_unit, usbd_errstr(err)); AXE_UNLOCK(sc); AXE_SLEEPUNLOCK(sc); return; } err = usbd_open_pipe(sc->axe_iface, sc->axe_ed[AXE_ENDPT_TX], USBD_EXCLUSIVE_USE, &sc->axe_ep[AXE_ENDPT_TX]); if (err) { printf("axe%d: open tx pipe failed: %s\n", sc->axe_unit, usbd_errstr(err)); AXE_UNLOCK(sc); AXE_SLEEPUNLOCK(sc); return; } /* Start up the receive pipe. */ for (i = 0; i < UE_RX_LIST_CNT; i++) { c = &sc->axe_cdata.ue_rx_chain[i]; usbd_setup_xfer(c->ue_xfer, sc->axe_ep[AXE_ENDPT_RX], c, mtod(c->ue_mbuf, char *), UE_BUFSZ, USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT, axe_rxeof); usbd_transfer(c->ue_xfer); } ifp->if_drv_flags |= IFF_DRV_RUNNING; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; AXE_UNLOCK(sc); AXE_SLEEPUNLOCK(sc); sc->axe_stat_ch = timeout(axe_tick, sc, hz); return; } static int axe_ioctl(struct ifnet *ifp, u_long command, caddr_t data) { struct axe_softc *sc = ifp->if_softc; struct ifreq *ifr = (struct ifreq *)data; struct mii_data *mii; u_int16_t rxmode; int error = 0; switch(command) { case SIOCSIFFLAGS: if (ifp->if_flags & IFF_UP) { if (ifp->if_drv_flags & IFF_DRV_RUNNING && ifp->if_flags & IFF_PROMISC && !(sc->axe_if_flags & IFF_PROMISC)) { AXE_SLEEPLOCK(sc); AXE_LOCK(sc); axe_cmd(sc, AXE_CMD_RXCTL_READ, 0, 0, (void *)&rxmode); rxmode |= AXE_RXCMD_PROMISC; axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, rxmode, NULL); AXE_UNLOCK(sc); axe_setmulti(sc); AXE_SLEEPUNLOCK(sc); } else if (ifp->if_drv_flags & IFF_DRV_RUNNING && !(ifp->if_flags & IFF_PROMISC) && sc->axe_if_flags & IFF_PROMISC) { AXE_SLEEPLOCK(sc); AXE_LOCK(sc); axe_cmd(sc, AXE_CMD_RXCTL_READ, 0, 0, (void *)&rxmode); rxmode &= ~AXE_RXCMD_PROMISC; axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, rxmode, NULL); AXE_UNLOCK(sc); axe_setmulti(sc); AXE_SLEEPUNLOCK(sc); } else if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) axe_init(sc); } else { if (ifp->if_drv_flags & IFF_DRV_RUNNING) { AXE_SLEEPLOCK(sc); axe_stop(sc); AXE_SLEEPUNLOCK(sc); } } sc->axe_if_flags = ifp->if_flags; error = 0; break; case SIOCADDMULTI: case SIOCDELMULTI: AXE_SLEEPLOCK(sc); axe_setmulti(sc); AXE_SLEEPUNLOCK(sc); error = 0; break; case SIOCGIFMEDIA: case SIOCSIFMEDIA: AXE_SLEEPLOCK(sc); mii = GET_MII(sc); error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, command); AXE_SLEEPUNLOCK(sc); break; default: error = ether_ioctl(ifp, command, data); break; } return(error); } static void axe_watchdog(struct ifnet *ifp) { struct axe_softc *sc; struct ue_chain *c; usbd_status stat; sc = ifp->if_softc; AXE_LOCK(sc); ifp->if_oerrors++; printf("axe%d: watchdog timeout\n", sc->axe_unit); c = &sc->axe_cdata.ue_tx_chain[0]; usbd_get_xfer_status(c->ue_xfer, NULL, NULL, NULL, &stat); axe_txeof(c->ue_xfer, c, stat); AXE_UNLOCK(sc); if (ifp->if_snd.ifq_head != NULL) axe_start(ifp); return; } /* * Stop the adapter and free any mbufs allocated to the * RX and TX lists. */ static void axe_stop(struct axe_softc *sc) { usbd_status err; struct ifnet *ifp; AXE_SLEEPLOCKASSERT(sc); AXE_LOCK(sc); ifp = sc->axe_ifp; ifp->if_timer = 0; untimeout(axe_tick, sc, sc->axe_stat_ch); /* Stop transfers. */ if (sc->axe_ep[AXE_ENDPT_RX] != NULL) { err = usbd_abort_pipe(sc->axe_ep[AXE_ENDPT_RX]); if (err) { printf("axe%d: abort rx pipe failed: %s\n", sc->axe_unit, usbd_errstr(err)); } err = usbd_close_pipe(sc->axe_ep[AXE_ENDPT_RX]); if (err) { printf("axe%d: close rx pipe failed: %s\n", sc->axe_unit, usbd_errstr(err)); } sc->axe_ep[AXE_ENDPT_RX] = NULL; } if (sc->axe_ep[AXE_ENDPT_TX] != NULL) { err = usbd_abort_pipe(sc->axe_ep[AXE_ENDPT_TX]); if (err) { printf("axe%d: abort tx pipe failed: %s\n", sc->axe_unit, usbd_errstr(err)); } err = usbd_close_pipe(sc->axe_ep[AXE_ENDPT_TX]); if (err) { printf("axe%d: close tx pipe failed: %s\n", sc->axe_unit, usbd_errstr(err)); } sc->axe_ep[AXE_ENDPT_TX] = NULL; } if (sc->axe_ep[AXE_ENDPT_INTR] != NULL) { err = usbd_abort_pipe(sc->axe_ep[AXE_ENDPT_INTR]); if (err) { printf("axe%d: abort intr pipe failed: %s\n", sc->axe_unit, usbd_errstr(err)); } err = usbd_close_pipe(sc->axe_ep[AXE_ENDPT_INTR]); if (err) { printf("axe%d: close intr pipe failed: %s\n", sc->axe_unit, usbd_errstr(err)); } sc->axe_ep[AXE_ENDPT_INTR] = NULL; } axe_reset(sc); /* Free RX resources. */ usb_ether_rx_list_free(&sc->axe_cdata); /* Free TX resources. */ usb_ether_tx_list_free(&sc->axe_cdata); ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); sc->axe_link = 0; AXE_UNLOCK(sc); return; } /* * Stop all chip I/O so that the kernel's probe routines don't * get confused by errant DMAs when rebooting. */ static void axe_shutdown(device_t dev) { struct axe_softc *sc; sc = device_get_softc(dev); AXE_SLEEPLOCK(sc); axe_stop(sc); AXE_SLEEPUNLOCK(sc); return; } diff --git a/sys/dev/usb/if_udav.c b/sys/dev/usb/if_udav.c index e2a2a24e318c..dbc5ab9f1188 100644 --- a/sys/dev/usb/if_udav.c +++ b/sys/dev/usb/if_udav.c @@ -1,1978 +1,1978 @@ /* $NetBSD: if_udav.c,v 1.2 2003/09/04 15:17:38 tsutsui Exp $ */ /* $nabe: if_udav.c,v 1.3 2003/08/21 16:57:19 nabe Exp $ */ /* $FreeBSD$ */ /*- * Copyright (c) 2003 * Shingo WATANABE . All rights reserved. * * 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. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. * */ /* * DM9601(DAVICOM USB to Ethernet MAC Controller with Integrated 10/100 PHY) * The spec can be found at the following url. * http://www.davicom.com.tw/big5/download/Data%20Sheet/DM9601-DS-P01-930914.pdf */ /* * TODO: * Interrupt Endpoint support * External PHYs * powerhook() support? */ #include __FBSDID("$FreeBSD$"); #include "opt_inet.h" #if defined(__NetBSD__) #include "opt_ns.h" #endif #if defined(__NetBSD__) #include "bpfilter.h" #endif #if defined(__FreeBSD__) #define NBPFILTER 1 #endif #if defined(__NetBSD__) #include "rnd.h" #endif #include #include #include #include #include #include #include #if defined(__FreeBSD__) #include #include #include #endif #if defined(__NetBSD__) #include #endif #if defined(NRND) && NRND > 0 #include #endif #include #include #include #include #include #include #if NBPFILTER > 0 #include #endif #if defined(__NetBSD__) #ifndef BPF_MTAP #define BPF_MTAP(_ifp, _m) do { \ if ((_ifp)->if_bpf)) { \ bpf_mtap((_ifp)->if_bpf, (_m)) ; \ } \ } while (0) #endif #endif #if defined(__NetBSD__) #include #ifdef INET #include #include #endif /* INET */ #elif defined(__FreeBSD__) /* defined(__NetBSD__) */ #include #include #endif /* defined(__FreeBSD__) */ #if defined(__NetBSD__) #ifdef NS #include #include #endif #endif /* defined (__NetBSD__) */ #include #include #if __FreeBSD_version < 500000 #include #endif #include #include #include #include #include #include "usbdevs.h" #include #include #include #if defined(__FreeBSD__) MODULE_DEPEND(udav, usb, 1, 1, 1); MODULE_DEPEND(udav, ether, 1, 1, 1); MODULE_DEPEND(udav, miibus, 1, 1, 1); #endif /* "device miibus" required. See GENERIC if you get errors here. */ #include "miibus_if.h" #if !defined(__FreeBSD__) /* Function declarations */ USB_DECLARE_DRIVER(udav); #endif #if defined(__FreeBSD__) static int udav_match(device_t); static int udav_attach(device_t); static int udav_detach(device_t); static void udav_shutdown(device_t); #endif static int udav_openpipes(struct udav_softc *); static void udav_start(struct ifnet *); static int udav_send(struct udav_softc *, struct mbuf *, int); static void udav_txeof(usbd_xfer_handle, usbd_private_handle, usbd_status); #if defined(__FreeBSD__) static void udav_rxstart(struct ifnet *ifp); #endif static void udav_rxeof(usbd_xfer_handle, usbd_private_handle, usbd_status); static void udav_tick(void *); static void udav_tick_task(void *); static int udav_ioctl(struct ifnet *, u_long, caddr_t); static void udav_stop_task(struct udav_softc *); static void udav_stop(struct ifnet *, int); static void udav_watchdog(struct ifnet *); static int udav_ifmedia_change(struct ifnet *); static void udav_ifmedia_status(struct ifnet *, struct ifmediareq *); static void udav_lock_mii(struct udav_softc *); static void udav_unlock_mii(struct udav_softc *); static int udav_miibus_readreg(device_t, int, int); static void udav_miibus_writereg(device_t, int, int, int); static void udav_miibus_statchg(device_t); #if defined(__NetBSD__) static int udav_init(struct ifnet *); #elif defined(__FreeBSD__) static void udav_init(void *); #endif static void udav_setmulti(struct udav_softc *); static void udav_reset(struct udav_softc *); static int udav_csr_read(struct udav_softc *, int, void *, int); static int udav_csr_write(struct udav_softc *, int, void *, int); static int udav_csr_read1(struct udav_softc *, int); static int udav_csr_write1(struct udav_softc *, int, unsigned char); #if 0 static int udav_mem_read(struct udav_softc *, int, void *, int); static int udav_mem_write(struct udav_softc *, int, void *, int); static int udav_mem_write1(struct udav_softc *, int, unsigned char); #endif #if defined(__FreeBSD__) static device_method_t udav_methods[] = { /* Device interface */ DEVMETHOD(device_probe, udav_match), DEVMETHOD(device_attach, udav_attach), DEVMETHOD(device_detach, udav_detach), DEVMETHOD(device_shutdown, udav_shutdown), /* bus interface */ DEVMETHOD(bus_print_child, bus_generic_print_child), DEVMETHOD(bus_driver_added, bus_generic_driver_added), /* MII interface */ DEVMETHOD(miibus_readreg, udav_miibus_readreg), DEVMETHOD(miibus_writereg, udav_miibus_writereg), DEVMETHOD(miibus_statchg, udav_miibus_statchg), { 0, 0 } }; static driver_t udav_driver = { "udav", udav_methods, sizeof(struct udav_softc) }; static devclass_t udav_devclass; DRIVER_MODULE(udav, uhub, udav_driver, udav_devclass, usbd_driver_load, 0); DRIVER_MODULE(miibus, udav, miibus_driver, miibus_devclass, 0, 0); #endif /* defined(__FreeBSD__) */ /* Macros */ #ifdef UDAV_DEBUG #define DPRINTF(x) if (udavdebug) logprintf x #define DPRINTFN(n,x) if (udavdebug >= (n)) logprintf x int udavdebug = 0; #else #define DPRINTF(x) #define DPRINTFN(n,x) #endif #define delay(d) DELAY(d) #define UDAV_SETBIT(sc, reg, x) \ udav_csr_write1(sc, reg, udav_csr_read1(sc, reg) | (x)) #define UDAV_CLRBIT(sc, reg, x) \ udav_csr_write1(sc, reg, udav_csr_read1(sc, reg) & ~(x)) static const struct udav_type { struct usb_devno udav_dev; u_int16_t udav_flags; #define UDAV_EXT_PHY 0x0001 } udav_devs [] = { /* Corega USB-TXC */ {{ USB_VENDOR_COREGA, USB_PRODUCT_COREGA_FETHER_USB_TXC }, 0}, #if 0 /* DAVICOM DM9601 Generic? */ /* XXX: The following ids was obtained from the data sheet. */ {{ 0x0a46, 0x9601 }, 0}, #endif }; #define udav_lookup(v, p) ((const struct udav_type *)usb_lookup(udav_devs, v, p)) /* Probe */ USB_MATCH(udav) { USB_MATCH_START(udav, uaa); if (uaa->iface != NULL) return (UMATCH_NONE); return (udav_lookup(uaa->vendor, uaa->product) != NULL ? UMATCH_VENDOR_PRODUCT : UMATCH_NONE); } /* Attach */ USB_ATTACH(udav) { USB_ATTACH_START(udav, sc, uaa); usbd_device_handle dev = uaa->device; usbd_interface_handle iface; usbd_status err; usb_interface_descriptor_t *id; usb_endpoint_descriptor_t *ed; char devinfo[1024]; const char *devname ; struct ifnet *ifp; #if defined(__NetBSD__) struct mii_data *mii; #endif u_char eaddr[ETHER_ADDR_LEN]; int i; #if defined(__NetBSD__) int s; #endif bzero(sc, sizeof(struct udav_softc)); usbd_devinfo(dev, 0, devinfo); USB_ATTACH_SETUP; devname = device_get_nameunit(sc->sc_dev); printf("%s: %s\n", devname, devinfo); /* Move the device into the configured state. */ err = usbd_set_config_no(dev, UDAV_CONFIG_NO, 1); if (err) { printf("%s: setting config no failed\n", devname); goto bad; } usb_init_task(&sc->sc_tick_task, udav_tick_task, sc); lockinit(&sc->sc_mii_lock, PZERO, "udavmii", 0, 0); usb_init_task(&sc->sc_stop_task, (void (*)(void *)) udav_stop_task, sc); /* get control interface */ err = usbd_device2interface_handle(dev, UDAV_IFACE_INDEX, &iface); if (err) { printf("%s: failed to get interface, err=%s\n", devname, usbd_errstr(err)); goto bad; } sc->sc_udev = dev; sc->sc_ctl_iface = iface; sc->sc_flags = udav_lookup(uaa->vendor, uaa->product)->udav_flags; /* get interface descriptor */ id = usbd_get_interface_descriptor(sc->sc_ctl_iface); /* find endpoints */ sc->sc_bulkin_no = sc->sc_bulkout_no = sc->sc_intrin_no = -1; for (i = 0; i < id->bNumEndpoints; i++) { ed = usbd_interface2endpoint_descriptor(sc->sc_ctl_iface, i); if (ed == NULL) { printf("%s: couldn't get endpoint %d\n", devname, i); goto bad; } if ((ed->bmAttributes & UE_XFERTYPE) == UE_BULK && UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN) sc->sc_bulkin_no = ed->bEndpointAddress; /* RX */ else if ((ed->bmAttributes & UE_XFERTYPE) == UE_BULK && UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT) sc->sc_bulkout_no = ed->bEndpointAddress; /* TX */ else if ((ed->bmAttributes & UE_XFERTYPE) == UE_INTERRUPT && UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN) sc->sc_intrin_no = ed->bEndpointAddress; /* Status */ } if (sc->sc_bulkin_no == -1 || sc->sc_bulkout_no == -1 || sc->sc_intrin_no == -1) { printf("%s: missing endpoint\n", devname); goto bad; } #if defined(__FreeBSD__) && __FreeBSD_version >= 500000 mtx_init(&sc->sc_mtx, device_get_nameunit(self), MTX_NETWORK_LOCK, MTX_DEF | MTX_RECURSE); #endif #if defined(__NetBSD__) s = splnet(); #elif defined(__FreeBSD__) UDAV_LOCK(sc); #endif /* reset the adapter */ udav_reset(sc); /* Get Ethernet Address */ err = udav_csr_read(sc, UDAV_PAR, (void *)eaddr, ETHER_ADDR_LEN); if (err) { printf("%s: read MAC address failed\n", devname); #if defined(__NetBSD__) splx(s); #elif defined(__FreeBSD__) UDAV_UNLOCK(sc); mtx_destroy(&sc->sc_mtx); #endif goto bad; } /* Print Ethernet Address */ printf("%s: Ethernet address %s\n", devname, ether_sprintf(eaddr)); /* initialize interface infomation */ #if defined(__FreeBSD__) ifp = GET_IFP(sc) = if_alloc(IFT_ETHER); if (ifp == NULL) { printf("%s: can not if_alloc\n", devname); UDAV_UNLOCK(sc); mtx_destroy(&sc->sc_mtx); goto bad; } #else ifp = GET_IFP(sc); #endif ifp->if_softc = sc; ifp->if_mtu = ETHERMTU; #if defined(__NetBSD__) strncpy(ifp->if_xname, devname, IFNAMSIZ); #elif defined(__FreeBSD__) if_initname(ifp, "udav", device_get_unit(self)); #endif ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST | IFF_NEEDSGIANT; ifp->if_start = udav_start; ifp->if_ioctl = udav_ioctl; ifp->if_watchdog = udav_watchdog; ifp->if_init = udav_init; #if defined(__NetBSD__) ifp->if_stop = udav_stop; #endif #if defined(__FreeBSD__) ifp->if_snd.ifq_maxlen = IFQ_MAXLEN; #endif #if defined(__NetBSD__) IFQ_SET_READY(&ifp->if_snd); #endif #if defined(__NetBSD__) /* * Do ifmedia setup. */ mii = &sc->sc_mii; mii->mii_ifp = ifp; mii->mii_readreg = udav_miibus_readreg; mii->mii_writereg = udav_miibus_writereg; mii->mii_statchg = udav_miibus_statchg; mii->mii_flags = MIIF_AUTOTSLEEP; ifmedia_init(&mii->mii_media, 0, udav_ifmedia_change, udav_ifmedia_status); mii_attach(self, mii, 0xffffffff, MII_PHY_ANY, MII_OFFSET_ANY, 0); if (LIST_FIRST(&mii->mii_phys) == NULL) { ifmedia_add(&mii->mii_media, IFM_ETHER | IFM_NONE, 0, NULL); ifmedia_set(&mii->mii_media, IFM_ETHER | IFM_NONE); } else ifmedia_set(&mii->mii_media, IFM_ETHER | IFM_AUTO); /* attach the interface */ if_attach(ifp); Ether_ifattach(ifp, eaddr); #elif defined(__FreeBSD__) if (mii_phy_probe(self, &sc->sc_miibus, udav_ifmedia_change, udav_ifmedia_status)) { printf("%s: MII without any PHY!\n", device_get_nameunit(sc->sc_dev)); if_free(ifp); UDAV_UNLOCK(sc); mtx_destroy(&sc->sc_mtx); USB_ATTACH_ERROR_RETURN; } sc->sc_qdat.ifp = ifp; sc->sc_qdat.if_rxstart = udav_rxstart; /* * Call MI attach routine. */ ether_ifattach(ifp, eaddr); #endif #if defined(NRND) && NRND > 0 rnd_attach_source(&sc->rnd_source, devname, RND_TYPE_NET, 0); #endif usb_callout_init(sc->sc_stat_ch); #if defined(__FreeBSD__) usb_register_netisr(); #endif sc->sc_attached = 1; #if defined(__NetBSD__) splx(s); #elif defined(__FreeBSD__) UDAV_UNLOCK(sc); #endif usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, dev, USBDEV(sc->sc_dev)); USB_ATTACH_SUCCESS_RETURN; bad: sc->sc_dying = 1; USB_ATTACH_ERROR_RETURN; } /* detach */ USB_DETACH(udav) { USB_DETACH_START(udav, sc); struct ifnet *ifp = GET_IFP(sc); #if defined(__NetBSD__) int s; #endif DPRINTF(("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); /* Detached before attached finished */ if (!sc->sc_attached) return (0); UDAV_LOCK(sc); usb_uncallout(sc->sc_stat_ch, udav_tick, sc); /* Remove any pending tasks */ usb_rem_task(sc->sc_udev, &sc->sc_tick_task); usb_rem_task(sc->sc_udev, &sc->sc_stop_task); #if defined(__NetBSD__) s = splusb(); #elif defined(__FreeBSD__) UDAV_LOCK(sc); #endif if (--sc->sc_refcnt >= 0) { /* Wait for processes to go away */ usb_detach_wait(USBDEV(sc->sc_dev)); } #if defined(__FreeBSD__) if (ifp->if_drv_flags & IFF_DRV_RUNNING) #else if (ifp->if_flags & IFF_RUNNING) #endif udav_stop(GET_IFP(sc), 1); #if defined(NRND) && NRND > 0 rnd_detach_source(&sc->rnd_source); #endif #if defined(__NetBSD__) mii_detach(&sc->sc_mii, MII_PHY_ANY, MII_OFFSET_ANY); ifmedia_delete_instance(&sc->sc_mii.mii_media, IFM_INST_ANY); #endif ether_ifdetach(ifp); #if defined(__NetBSD__) if_detach(ifp); #endif #if defined(__FreeBSD__) if_free(ifp); #endif #ifdef DIAGNOSTIC if (sc->sc_pipe_tx != NULL) printf("%s: detach has active tx endpoint.\n", device_get_nameunit(sc->sc_dev)); if (sc->sc_pipe_rx != NULL) printf("%s: detach has active rx endpoint.\n", device_get_nameunit(sc->sc_dev)); if (sc->sc_pipe_intr != NULL) printf("%s: detach has active intr endpoint.\n", device_get_nameunit(sc->sc_dev)); #endif sc->sc_attached = 0; #if defined(__NetBSD__) splx(s); #elif defined(__FreeBSD__) UDAV_UNLOCK(sc); #endif #if defined(__FreeBSD__) mtx_destroy(&sc->sc_mtx); #endif usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_udev, USBDEV(sc->sc_dev)); return (0); } #if 0 /* read memory */ static int udav_mem_read(struct udav_softc *sc, int offset, void *buf, int len) { usb_device_request_t req; usbd_status err; if (sc == NULL) return (0); DPRINTFN(0x200, ("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); if (sc->sc_dying) return (0); offset &= 0xffff; len &= 0xff; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = UDAV_REQ_MEM_READ; USETW(req.wValue, 0x0000); USETW(req.wIndex, offset); USETW(req.wLength, len); sc->sc_refcnt++; err = usbd_do_request(sc->sc_udev, &req, buf); if (--sc->sc_refcnt < 0) usb_detach_wakeup(USBDEV(sc->sc_dev)); if (err) { DPRINTF(("%s: %s: read failed. off=%04x, err=%d\n", device_get_nameunit(sc->sc_dev), __func__, offset, err)); } return (err); } /* write memory */ static int udav_mem_write(struct udav_softc *sc, int offset, void *buf, int len) { usb_device_request_t req; usbd_status err; if (sc == NULL) return (0); DPRINTFN(0x200, ("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); if (sc->sc_dying) return (0); offset &= 0xffff; len &= 0xff; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = UDAV_REQ_MEM_WRITE; USETW(req.wValue, 0x0000); USETW(req.wIndex, offset); USETW(req.wLength, len); sc->sc_refcnt++; err = usbd_do_request(sc->sc_udev, &req, buf); if (--sc->sc_refcnt < 0) usb_detach_wakeup(USBDEV(sc->sc_dev)); if (err) { DPRINTF(("%s: %s: write failed. off=%04x, err=%d\n", device_get_nameunit(sc->sc_dev), __func__, offset, err)); } return (err); } /* write memory */ static int udav_mem_write1(struct udav_softc *sc, int offset, unsigned char ch) { usb_device_request_t req; usbd_status err; if (sc == NULL) return (0); DPRINTFN(0x200, ("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); if (sc->sc_dying) return (0); offset &= 0xffff; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = UDAV_REQ_MEM_WRITE1; USETW(req.wValue, ch); USETW(req.wIndex, offset); USETW(req.wLength, 0x0000); sc->sc_refcnt++; err = usbd_do_request(sc->sc_udev, &req, NULL); if (--sc->sc_refcnt < 0) usb_detach_wakeup(USBDEV(sc->sc_dev)); if (err) { DPRINTF(("%s: %s: write failed. off=%04x, err=%d\n", device_get_nameunit(sc->sc_dev), __func__, offset, err)); } return (err); } #endif /* read register(s) */ static int udav_csr_read(struct udav_softc *sc, int offset, void *buf, int len) { usb_device_request_t req; usbd_status err; if (sc == NULL) return (0); DPRINTFN(0x200, ("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); if (sc->sc_dying) return (0); offset &= 0xff; len &= 0xff; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = UDAV_REQ_REG_READ; USETW(req.wValue, 0x0000); USETW(req.wIndex, offset); USETW(req.wLength, len); sc->sc_refcnt++; err = usbd_do_request(sc->sc_udev, &req, buf); if (--sc->sc_refcnt < 0) usb_detach_wakeup(USBDEV(sc->sc_dev)); if (err) { DPRINTF(("%s: %s: read failed. off=%04x, err=%d\n", device_get_nameunit(sc->sc_dev), __func__, offset, err)); } return (err); } /* write register(s) */ static int udav_csr_write(struct udav_softc *sc, int offset, void *buf, int len) { usb_device_request_t req; usbd_status err; if (sc == NULL) return (0); DPRINTFN(0x200, ("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); if (sc->sc_dying) return (0); offset &= 0xff; len &= 0xff; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = UDAV_REQ_REG_WRITE; USETW(req.wValue, 0x0000); USETW(req.wIndex, offset); USETW(req.wLength, len); sc->sc_refcnt++; err = usbd_do_request(sc->sc_udev, &req, buf); if (--sc->sc_refcnt < 0) usb_detach_wakeup(USBDEV(sc->sc_dev)); if (err) { DPRINTF(("%s: %s: write failed. off=%04x, err=%d\n", device_get_nameunit(sc->sc_dev), __func__, offset, err)); } return (err); } static int udav_csr_read1(struct udav_softc *sc, int offset) { u_int8_t val = 0; if (sc == NULL) return (0); DPRINTFN(0x200, ("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); if (sc->sc_dying) return (0); return (udav_csr_read(sc, offset, &val, 1) ? 0 : val); } /* write a register */ static int udav_csr_write1(struct udav_softc *sc, int offset, unsigned char ch) { usb_device_request_t req; usbd_status err; if (sc == NULL) return (0); DPRINTFN(0x200, ("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); if (sc->sc_dying) return (0); offset &= 0xff; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = UDAV_REQ_REG_WRITE1; USETW(req.wValue, ch); USETW(req.wIndex, offset); USETW(req.wLength, 0x0000); sc->sc_refcnt++; err = usbd_do_request(sc->sc_udev, &req, NULL); if (--sc->sc_refcnt < 0) usb_detach_wakeup(USBDEV(sc->sc_dev)); if (err) { DPRINTF(("%s: %s: write failed. off=%04x, err=%d\n", device_get_nameunit(sc->sc_dev), __func__, offset, err)); } return (err); } #if defined(__NetBSD__) static int udav_init(struct ifnet *ifp) #elif defined(__FreeBSD__) static void udav_init(void *xsc) #endif { #if defined(__NetBSD__) struct udav_softc *sc = ifp->if_softc; #elif defined(__FreeBSD__) struct udav_softc *sc = (struct udav_softc *)xsc; struct ifnet *ifp = GET_IFP(sc); #endif struct mii_data *mii = GET_MII(sc); u_char *eaddr; #if defined(__NetBSD__) int s; #endif DPRINTF(("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); if (sc->sc_dying) #if defined(__NetBSD__) return (EIO); #elif defined(__FreeBSD__) return ; #endif #if defined(__NetBSD__) s = splnet(); #elif defined(__FreeBSD__) UDAV_LOCK(sc); #endif /* Cancel pending I/O and free all TX/RX buffers */ udav_stop(ifp, 1); #if defined(__NetBSD__) eaddr = LLADDR(ifp->if_sadl); #elif defined(__FreeBSD__) eaddr = IF_LLADDR(ifp); #endif udav_csr_write(sc, UDAV_PAR, eaddr, ETHER_ADDR_LEN); /* Initialize network control register */ /* Disable loopback */ UDAV_CLRBIT(sc, UDAV_NCR, UDAV_NCR_LBK0 | UDAV_NCR_LBK1); /* Initialize RX control register */ UDAV_SETBIT(sc, UDAV_RCR, UDAV_RCR_DIS_LONG | UDAV_RCR_DIS_CRC); /* If we want promiscuous mode, accept all physical frames. */ if (ifp->if_flags & IFF_PROMISC) UDAV_SETBIT(sc, UDAV_RCR, UDAV_RCR_ALL|UDAV_RCR_PRMSC); else UDAV_CLRBIT(sc, UDAV_RCR, UDAV_RCR_ALL|UDAV_RCR_PRMSC); /* Initialize transmit ring */ if (usb_ether_tx_list_init(sc, &sc->sc_cdata, sc->sc_udev) == ENOBUFS) { printf("%s: tx list init failed\n", device_get_nameunit(sc->sc_dev)); #if defined(__NetBSD__) splx(s); return (EIO); #elif defined(__FreeBSD__) UDAV_UNLOCK(sc); return ; #endif } /* Initialize receive ring */ if (usb_ether_rx_list_init(sc, &sc->sc_cdata, sc->sc_udev) == ENOBUFS) { printf("%s: rx list init failed\n", device_get_nameunit(sc->sc_dev)); #if defined(__NetBSD__) splx(s); return (EIO); #elif defined(__FreeBSD__) UDAV_UNLOCK(sc); return ; #endif } /* Load the multicast filter */ udav_setmulti(sc); /* Enable RX */ UDAV_SETBIT(sc, UDAV_RCR, UDAV_RCR_RXEN); /* clear POWER_DOWN state of internal PHY */ UDAV_SETBIT(sc, UDAV_GPCR, UDAV_GPCR_GEP_CNTL0); UDAV_CLRBIT(sc, UDAV_GPR, UDAV_GPR_GEPIO0); mii_mediachg(mii); if (sc->sc_pipe_tx == NULL || sc->sc_pipe_rx == NULL) { if (udav_openpipes(sc)) { #if defined(__NetBSD__) splx(s); return (EIO); #elif defined(__FreeBSD__) UDAV_UNLOCK(sc); return ; #endif } } #if defined(__FreeBSD__) ifp->if_drv_flags |= IFF_DRV_RUNNING; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; #else ifp->if_flags |= IFF_RUNNING; ifp->if_flags &= ~IFF_OACTIVE; #endif #if defined(__NetBSD__) splx(s); #elif defined(__FreeBSD__) UDAV_UNLOCK(sc); #endif usb_callout(sc->sc_stat_ch, hz, udav_tick, sc); #if defined(__NetBSD__) return (0); #elif defined(__FreeBSD__) return ; #endif } static void udav_reset(struct udav_softc *sc) { int i; DPRINTF(("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); if (sc->sc_dying) return; /* Select PHY */ #if 1 /* * XXX: force select internal phy. * external phy routines are not tested. */ UDAV_CLRBIT(sc, UDAV_NCR, UDAV_NCR_EXT_PHY); #else if (sc->sc_flags & UDAV_EXT_PHY) { UDAV_SETBIT(sc, UDAV_NCR, UDAV_NCR_EXT_PHY); } else { UDAV_CLRBIT(sc, UDAV_NCR, UDAV_NCR_EXT_PHY); } #endif UDAV_SETBIT(sc, UDAV_NCR, UDAV_NCR_RST); for (i = 0; i < UDAV_TX_TIMEOUT; i++) { if (!(udav_csr_read1(sc, UDAV_NCR) & UDAV_NCR_RST)) break; delay(10); /* XXX */ } delay(10000); /* XXX */ } #if defined(__NetBSD__) || defined(__OpenBSD__) int udav_activate(device_t self, enum devact act) { struct udav_softc *sc = (struct udav_softc *)self; DPRINTF(("%s: %s: enter, act=%d\n", device_get_nameunit(sc->sc_dev), __func__, act)); switch (act) { case DVACT_ACTIVATE: return (EOPNOTSUPP); break; case DVACT_DEACTIVATE: if_deactivate(&sc->sc_ec.ec_if); sc->sc_dying = 1; break; } return (0); } #endif #define UDAV_BITS 6 #define UDAV_CALCHASH(addr) \ (ether_crc32_le((addr), ETHER_ADDR_LEN) & ((1 << UDAV_BITS) - 1)) static void udav_setmulti(struct udav_softc *sc) { struct ifnet *ifp; #if defined(__NetBSD__) struct ether_multi *enm; struct ether_multistep step; #elif defined(__FreeBSD__) struct ifmultiaddr *ifma; #endif u_int8_t hashes[8]; int h = 0; DPRINTF(("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); if (sc->sc_dying) return; ifp = GET_IFP(sc); if (ifp->if_flags & IFF_PROMISC) { UDAV_SETBIT(sc, UDAV_RCR, UDAV_RCR_ALL|UDAV_RCR_PRMSC); return; } else if (ifp->if_flags & IFF_ALLMULTI) { #if defined(__NetBSD__) allmulti: #endif ifp->if_flags |= IFF_ALLMULTI; UDAV_SETBIT(sc, UDAV_RCR, UDAV_RCR_ALL); UDAV_CLRBIT(sc, UDAV_RCR, UDAV_RCR_PRMSC); return; } /* first, zot all the existing hash bits */ memset(hashes, 0x00, sizeof(hashes)); hashes[7] |= 0x80; /* broadcast address */ udav_csr_write(sc, UDAV_MAR, hashes, sizeof(hashes)); /* now program new ones */ #if defined(__NetBSD__) ETHER_FIRST_MULTI(step, &sc->sc_ec, enm); while (enm != NULL) { if (memcmp(enm->enm_addrlo, enm->enm_addrhi, ETHER_ADDR_LEN) != 0) goto allmulti; h = UDAV_CALCHASH(enm->enm_addrlo); hashes[h>>3] |= 1 << (h & 0x7); ETHER_NEXT_MULTI(step, enm); } #elif defined(__FreeBSD__) IF_ADDR_LOCK(ifp); #if __FreeBSD_version >= 500000 TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) #else LIST_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) #endif { if (ifma->ifma_addr->sa_family != AF_LINK) continue; h = UDAV_CALCHASH(LLADDR((struct sockaddr_dl *) ifma->ifma_addr)); hashes[h>>3] |= 1 << (h & 0x7); } IF_ADDR_UNLOCK(ifp); #endif /* disable all multicast */ ifp->if_flags &= ~IFF_ALLMULTI; UDAV_CLRBIT(sc, UDAV_RCR, UDAV_RCR_ALL); /* write hash value to the register */ udav_csr_write(sc, UDAV_MAR, hashes, sizeof(hashes)); } static int udav_openpipes(struct udav_softc *sc) { struct ue_chain *c; usbd_status err; int i; int error = 0; if (sc->sc_dying) return (EIO); sc->sc_refcnt++; /* Open RX pipe */ err = usbd_open_pipe(sc->sc_ctl_iface, sc->sc_bulkin_no, USBD_EXCLUSIVE_USE, &sc->sc_pipe_rx); if (err) { printf("%s: open rx pipe failed: %s\n", device_get_nameunit(sc->sc_dev), usbd_errstr(err)); error = EIO; goto done; } /* Open TX pipe */ err = usbd_open_pipe(sc->sc_ctl_iface, sc->sc_bulkout_no, USBD_EXCLUSIVE_USE, &sc->sc_pipe_tx); if (err) { printf("%s: open tx pipe failed: %s\n", device_get_nameunit(sc->sc_dev), usbd_errstr(err)); error = EIO; goto done; } #if 0 /* XXX: interrupt endpoint is not yet supported */ /* Open Interrupt pipe */ err = usbd_open_pipe_intr(sc->sc_ctl_iface, sc->sc_intrin_no, USBD_EXCLUSIVE_USE, &sc->sc_pipe_intr, sc, &sc->sc_cdata.ue_ibuf, UDAV_INTR_PKGLEN, udav_intr, UDAV_INTR_INTERVAL); if (err) { printf("%s: open intr pipe failed: %s\n", device_get_nameunit(sc->sc_dev), usbd_errstr(err)); error = EIO; goto done; } #endif /* Start up the receive pipe. */ for (i = 0; i < UE_RX_LIST_CNT; i++) { c = &sc->sc_cdata.ue_rx_chain[i]; usbd_setup_xfer(c->ue_xfer, sc->sc_pipe_rx, c, c->ue_buf, UE_BUFSZ, USBD_SHORT_XFER_OK | USBD_NO_COPY, USBD_NO_TIMEOUT, udav_rxeof); (void)usbd_transfer(c->ue_xfer); DPRINTF(("%s: %s: start read\n", device_get_nameunit(sc->sc_dev), __func__)); } done: if (--sc->sc_refcnt < 0) usb_detach_wakeup(USBDEV(sc->sc_dev)); return (error); } static void udav_start(struct ifnet *ifp) { struct udav_softc *sc = ifp->if_softc; struct mbuf *m_head = NULL; DPRINTF(("%s: %s: enter, link=%d\n", device_get_nameunit(sc->sc_dev), __func__, sc->sc_link)); if (sc->sc_dying) return; if (!sc->sc_link) return; #if defined(__FreeBSD__) if (ifp->if_drv_flags & IFF_DRV_OACTIVE) #else if (ifp->if_flags & IFF_OACTIVE) #endif return; #if defined(__NetBSD__) IFQ_POLL(&ifp->if_snd, m_head); #elif defined(__FreeBSD__) IF_DEQUEUE(&ifp->if_snd, m_head); #endif if (m_head == NULL) return; if (udav_send(sc, m_head, 0)) { #if defined(__FreeBSD__) IF_PREPEND(&ifp->if_snd, m_head); ifp->if_drv_flags |= IFF_DRV_OACTIVE; #else ifp->if_flags |= IFF_OACTIVE; #endif return; } #if defined(__NetBSD__) IFQ_DEQUEUE(&ifp->if_snd, m_head); #endif #if NBPFILTER > 0 BPF_MTAP(ifp, m_head); #endif #if defined(__FreeBSD__) ifp->if_drv_flags |= IFF_DRV_OACTIVE; #else ifp->if_flags |= IFF_OACTIVE; #endif /* Set a timeout in case the chip goes out to lunch. */ ifp->if_timer = 5; } static int udav_send(struct udav_softc *sc, struct mbuf *m, int idx) { int total_len; struct ue_chain *c; usbd_status err; DPRINTF(("%s: %s: enter\n", device_get_nameunit(sc->sc_dev),__func__)); c = &sc->sc_cdata.ue_tx_chain[idx]; /* Copy the mbuf data into a contiguous buffer */ /* first 2 bytes are packet length */ m_copydata(m, 0, m->m_pkthdr.len, c->ue_buf + 2); c->ue_mbuf = m; total_len = m->m_pkthdr.len; if (total_len < UDAV_MIN_FRAME_LEN) { memset(c->ue_buf + 2 + total_len, 0, UDAV_MIN_FRAME_LEN - total_len); total_len = UDAV_MIN_FRAME_LEN; } /* Frame length is specified in the first 2bytes of the buffer */ c->ue_buf[0] = (u_int8_t)total_len; c->ue_buf[1] = (u_int8_t)(total_len >> 8); total_len += 2; usbd_setup_xfer(c->ue_xfer, sc->sc_pipe_tx, c, c->ue_buf, total_len, USBD_FORCE_SHORT_XFER | USBD_NO_COPY, UDAV_TX_TIMEOUT, udav_txeof); /* Transmit */ sc->sc_refcnt++; err = usbd_transfer(c->ue_xfer); if (--sc->sc_refcnt < 0) usb_detach_wakeup(USBDEV(sc->sc_dev)); if (err != USBD_IN_PROGRESS) { printf("%s: udav_send error=%s\n", device_get_nameunit(sc->sc_dev), usbd_errstr(err)); /* Stop the interface */ - usb_add_task(sc->sc_udev, &sc->sc_stop_task); + usb_add_task(sc->sc_udev, &sc->sc_stop_task, USB_TASKQ_DRIVER); return (EIO); } DPRINTF(("%s: %s: send %d bytes\n", device_get_nameunit(sc->sc_dev), __func__, total_len)); sc->sc_cdata.ue_tx_cnt++; return (0); } static void udav_txeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) { struct ue_chain *c = priv; struct udav_softc *sc = c->ue_sc; struct ifnet *ifp = GET_IFP(sc); #if defined(__NetBSD__) int s; #endif if (sc->sc_dying) return; #if defined(__NetBSD__) s = splnet(); #elif defined(__FreeBSD__) UDAV_LOCK(sc); #endif DPRINTF(("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); ifp->if_timer = 0; #if defined(__FreeBSD__) ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; #else ifp->if_flags &= ~IFF_OACTIVE; #endif if (status != USBD_NORMAL_COMPLETION) { if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) { #if defined(__NetBSD__) splx(s); #elif defined(__FreeBSD__) UDAV_UNLOCK(sc); #endif return; } ifp->if_oerrors++; printf("%s: usb error on tx: %s\n", device_get_nameunit(sc->sc_dev), usbd_errstr(status)); if (status == USBD_STALLED) { sc->sc_refcnt++; usbd_clear_endpoint_stall(sc->sc_pipe_tx); if (--sc->sc_refcnt < 0) usb_detach_wakeup(USBDEV(sc->sc_dev)); } #if defined(__NetBSD__) splx(s); #elif defined(__FreeBSD__) UDAV_UNLOCK(sc); #endif return; } ifp->if_opackets++; m_freem(c->ue_mbuf); c->ue_mbuf = NULL; #if defined(__NetBSD__) if (IFQ_IS_EMPTY(&ifp->if_snd) == 0) #elif defined(__FreeBSD__) if ( ifp->if_snd.ifq_head != NULL ) #endif udav_start(ifp); #if defined(__NetBSD__) splx(s); #elif defined(__FreeBSD__) UDAV_UNLOCK(sc); #endif } static void udav_rxeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) { struct ue_chain *c = priv; struct udav_softc *sc = c->ue_sc; struct ifnet *ifp = GET_IFP(sc); struct mbuf *m; u_int32_t total_len; u_int8_t *pktstat; #if defined(__NetBSD__) int s; #endif DPRINTF(("%s: %s: enter\n", device_get_nameunit(sc->sc_dev),__func__)); if (sc->sc_dying) return; if (status != USBD_NORMAL_COMPLETION) { if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) return; sc->sc_rx_errs++; if (usbd_ratecheck(&sc->sc_rx_notice)) { printf("%s: %u usb errors on rx: %s\n", device_get_nameunit(sc->sc_dev), sc->sc_rx_errs, usbd_errstr(status)); sc->sc_rx_errs = 0; } if (status == USBD_STALLED) { sc->sc_refcnt++; usbd_clear_endpoint_stall(sc->sc_pipe_rx); if (--sc->sc_refcnt < 0) usb_detach_wakeup(USBDEV(sc->sc_dev)); } goto done; } usbd_get_xfer_status(xfer, NULL, NULL, &total_len, NULL); /* copy data to mbuf */ m = c->ue_mbuf; memcpy(mtod(m, char *), c->ue_buf, total_len); /* first byte in received data */ pktstat = mtod(m, u_int8_t *); m_adj(m, sizeof(u_int8_t)); DPRINTF(("%s: RX Status: 0x%02x\n", device_get_nameunit(sc->sc_dev), *pktstat)); total_len = UGETW(mtod(m, u_int8_t *)); m_adj(m, sizeof(u_int16_t)); if (*pktstat & UDAV_RSR_LCS) { ifp->if_collisions++; goto done; } if (total_len < sizeof(struct ether_header) || *pktstat & UDAV_RSR_ERR) { ifp->if_ierrors++; goto done; } ifp->if_ipackets++; total_len -= ETHER_CRC_LEN; m->m_pkthdr.len = m->m_len = total_len; #if defined(__NetBSD__) m->m_pkthdr.rcvif = ifp; #elif defined(__FreeBSD__) m->m_pkthdr.rcvif = (struct ifnet *)&sc->sc_qdat; #endif #if defined(__NetBSD__) s = splnet(); #elif defined(__FreeBSD__) UDAV_LOCK(sc); #endif #if defined(__NetBSD__) c->ue_mbuf = usb_ether_newbuf(); if (c->ue_mbuf == NULL) { printf("%s: no memory for rx list " "-- packet dropped!\n", device_get_nameunit(sc->sc_dev)); ifp->if_ierrors++; goto done1; } #endif #if NBPFILTER > 0 BPF_MTAP(ifp, m); #endif DPRINTF(("%s: %s: deliver %d\n", device_get_nameunit(sc->sc_dev), __func__, m->m_len)); #if defined(__NetBSD__) IF_INPUT(ifp, m); #endif #if defined(__FreeBSD__) usb_ether_input(m); UDAV_UNLOCK(sc); return ; #endif #if defined(__NetBSD__) done1: splx(s); #elif defined(__FreeBSD__) UDAV_UNLOCK(sc); #endif done: /* Setup new transfer */ usbd_setup_xfer(xfer, sc->sc_pipe_rx, c, c->ue_buf, UE_BUFSZ, USBD_SHORT_XFER_OK | USBD_NO_COPY, USBD_NO_TIMEOUT, udav_rxeof); sc->sc_refcnt++; usbd_transfer(xfer); if (--sc->sc_refcnt < 0) usb_detach_wakeup(USBDEV(sc->sc_dev)); DPRINTF(("%s: %s: start rx\n", device_get_nameunit(sc->sc_dev), __func__)); } #if 0 static void udav_intr() { } #endif static int udav_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) { struct udav_softc *sc = ifp->if_softc; struct ifreq *ifr = (struct ifreq *)data; struct mii_data *mii; #if defined(__NetBSD__) int s; #endif int error = 0; DPRINTF(("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); if (sc->sc_dying) return (EIO); #if defined(__NetBSD__) s = splnet(); #elif defined(__FreeBSD__) UDAV_LOCK(sc); #endif switch (cmd) { #if defined(__FreeBSD__) case SIOCSIFFLAGS: if (ifp->if_flags & IFF_UP) { if (ifp->if_drv_flags & IFF_DRV_RUNNING && ifp->if_flags & IFF_PROMISC) { UDAV_SETBIT(sc, UDAV_RCR, UDAV_RCR_ALL|UDAV_RCR_PRMSC); } else if (ifp->if_drv_flags & IFF_DRV_RUNNING && !(ifp->if_flags & IFF_PROMISC)) { if (ifp->if_flags & IFF_ALLMULTI) UDAV_CLRBIT(sc, UDAV_RCR, UDAV_RCR_PRMSC); else UDAV_CLRBIT(sc, UDAV_RCR, UDAV_RCR_ALL|UDAV_RCR_PRMSC); } else if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) udav_init(sc); } else { if (ifp->if_drv_flags & IFF_DRV_RUNNING) udav_stop(ifp, 1); } error = 0; break; case SIOCADDMULTI: case SIOCDELMULTI: udav_setmulti(sc); error = 0; break; #endif case SIOCGIFMEDIA: case SIOCSIFMEDIA: mii = GET_MII(sc); error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, cmd); break; default: error = ether_ioctl(ifp, cmd, data); #if defined(__NetBSD__) if (error == ENETRESET) { udav_setmulti(sc); error = 0; } #endif break; } #if defined(__NetBSD__) splx(s); #elif defined(__FreeBSD__) UDAV_UNLOCK(sc); #endif return (error); } static void udav_watchdog(struct ifnet *ifp) { struct udav_softc *sc = ifp->if_softc; struct ue_chain *c; usbd_status stat; #if defined(__NetBSD__) int s; #endif DPRINTF(("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); ifp->if_oerrors++; printf("%s: watchdog timeout\n", device_get_nameunit(sc->sc_dev)); #if defined(__NetBSD__) s = splusb(); #elif defined(__FreeBSD__) UDAV_LOCK(sc) #endif c = &sc->sc_cdata.ue_tx_chain[0]; usbd_get_xfer_status(c->ue_xfer, NULL, NULL, NULL, &stat); udav_txeof(c->ue_xfer, c, stat); #if defined(__NetBSD__) if (IFQ_IS_EMPTY(&ifp->if_snd) == 0) #elif defined(__FreeBSD__) if ( ifp->if_snd.ifq_head != NULL ) #endif udav_start(ifp); #if defined(__NetBSD__) splx(s); #elif defined(__FreeBSD__) UDAV_UNLOCK(sc); #endif } static void udav_stop_task(struct udav_softc *sc) { udav_stop(GET_IFP(sc), 1); } /* Stop the adapter and free any mbufs allocated to the RX and TX lists. */ static void udav_stop(struct ifnet *ifp, int disable) { struct udav_softc *sc = ifp->if_softc; usbd_status err; DPRINTF(("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); ifp->if_timer = 0; udav_reset(sc); usb_uncallout(sc->sc_stat_ch, udav_tick, sc); /* Stop transfers */ /* RX endpoint */ if (sc->sc_pipe_rx != NULL) { err = usbd_abort_pipe(sc->sc_pipe_rx); if (err) printf("%s: abort rx pipe failed: %s\n", device_get_nameunit(sc->sc_dev), usbd_errstr(err)); err = usbd_close_pipe(sc->sc_pipe_rx); if (err) printf("%s: close rx pipe failed: %s\n", device_get_nameunit(sc->sc_dev), usbd_errstr(err)); sc->sc_pipe_rx = NULL; } /* TX endpoint */ if (sc->sc_pipe_tx != NULL) { err = usbd_abort_pipe(sc->sc_pipe_tx); if (err) printf("%s: abort tx pipe failed: %s\n", device_get_nameunit(sc->sc_dev), usbd_errstr(err)); err = usbd_close_pipe(sc->sc_pipe_tx); if (err) printf("%s: close tx pipe failed: %s\n", device_get_nameunit(sc->sc_dev), usbd_errstr(err)); sc->sc_pipe_tx = NULL; } #if 0 /* XXX: Interrupt endpoint is not yet supported!! */ /* Interrupt endpoint */ if (sc->sc_pipe_intr != NULL) { err = usbd_abort_pipe(sc->sc_pipe_intr); if (err) printf("%s: abort intr pipe failed: %s\n", device_get_nameunit(sc->sc_dev), usbd_errstr(err)); err = usbd_close_pipe(sc->sc_pipe_intr); if (err) printf("%s: close intr pipe failed: %s\n", device_get_nameunit(sc->sc_dev), usbd_errstr(err)); sc->sc_pipe_intr = NULL; } #endif /* Free RX resources. */ usb_ether_rx_list_free(&sc->sc_cdata); /* Free TX resources. */ usb_ether_tx_list_free(&sc->sc_cdata); sc->sc_link = 0; #if defined(__FreeBSD__) ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); #else ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE); #endif } /* Set media options */ static int udav_ifmedia_change(struct ifnet *ifp) { struct udav_softc *sc = ifp->if_softc; struct mii_data *mii = GET_MII(sc); DPRINTF(("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); if (sc->sc_dying) return (0); sc->sc_link = 0; if (mii->mii_instance) { struct mii_softc *miisc; for (miisc = LIST_FIRST(&mii->mii_phys); miisc != NULL; miisc = LIST_NEXT(miisc, mii_list)) mii_phy_reset(miisc); } return (mii_mediachg(mii)); } /* Report current media status. */ static void udav_ifmedia_status(struct ifnet *ifp, struct ifmediareq *ifmr) { struct udav_softc *sc = ifp->if_softc; struct mii_data *mii = GET_MII(sc); DPRINTF(("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); if (sc->sc_dying) return; #if defined(__FreeBSD__) if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) { #else if ((ifp->if_flags & IFF_RUNNING) == 0) { #endif ifmr->ifm_active = IFM_ETHER | IFM_NONE; ifmr->ifm_status = 0; return; } mii_pollstat(mii); ifmr->ifm_active = mii->mii_media_active; ifmr->ifm_status = mii->mii_media_status; } static void udav_tick(void *xsc) { struct udav_softc *sc = xsc; if (sc == NULL) return; DPRINTFN(0xff, ("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); if (sc->sc_dying) return; /* Perform periodic stuff in process context */ - usb_add_task(sc->sc_udev, &sc->sc_tick_task); + usb_add_task(sc->sc_udev, &sc->sc_tick_task, USB_TASKQ_DRIVER); } static void udav_tick_task(void *xsc) { struct udav_softc *sc = xsc; struct ifnet *ifp; struct mii_data *mii; #if defined(__NetBSD__) int s; #endif if (sc == NULL) return; DPRINTFN(0xff, ("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); if (sc->sc_dying) return; ifp = GET_IFP(sc); mii = GET_MII(sc); if (mii == NULL) return; #if defined(__NetBSD__) s = splnet(); #elif defined(__FreeBSD__) UDAV_LOCK(sc); #endif mii_tick(mii); if (!sc->sc_link) { mii_pollstat(mii); if (mii->mii_media_status & IFM_ACTIVE && IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE) { DPRINTF(("%s: %s: got link\n", device_get_nameunit(sc->sc_dev), __func__)); sc->sc_link++; #if defined(__NetBSD__) if (IFQ_IS_EMPTY(&ifp->if_snd) == 0) #elif defined(__FreeBSD__) if ( ifp->if_snd.ifq_head != NULL ) #endif udav_start(ifp); } } usb_callout(sc->sc_stat_ch, hz, udav_tick, sc); #if defined(__NetBSD__) splx(s); #elif defined(__FreeBSD__) UDAV_UNLOCK(sc); #endif } /* Get exclusive access to the MII registers */ static void udav_lock_mii(struct udav_softc *sc) { DPRINTFN(0xff, ("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); sc->sc_refcnt++; #if defined(__NetBSD__) lockmgr(&sc->sc_mii_lock, LK_EXCLUSIVE, NULL); #elif defined(__FreeBSD__) lockmgr(&sc->sc_mii_lock, LK_EXCLUSIVE, NULL, NULL); #endif } static void udav_unlock_mii(struct udav_softc *sc) { DPRINTFN(0xff, ("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); #if defined(__NetBSD__) lockmgr(&sc->sc_mii_lock, LK_RELEASE, NULL); #elif defined(__FreeBSD__) lockmgr(&sc->sc_mii_lock, LK_RELEASE, NULL, NULL); #endif if (--sc->sc_refcnt < 0) usb_detach_wakeup(USBDEV(sc->sc_dev)); } static int udav_miibus_readreg(device_t dev, int phy, int reg) { struct udav_softc *sc; u_int8_t val[2]; u_int16_t data16; if (dev == NULL) return (0); sc = USBGETSOFTC(dev); DPRINTFN(0xff, ("%s: %s: enter, phy=%d reg=0x%04x\n", device_get_nameunit(sc->sc_dev), __func__, phy, reg)); if (sc->sc_dying) { #ifdef DIAGNOSTIC printf("%s: %s: dying\n", device_get_nameunit(sc->sc_dev), __func__); #endif return (0); } /* XXX: one PHY only for the internal PHY */ if (phy != 0) { DPRINTFN(0xff, ("%s: %s: phy=%d is not supported\n", device_get_nameunit(sc->sc_dev), __func__, phy)); return (0); } udav_lock_mii(sc); /* select internal PHY and set PHY register address */ udav_csr_write1(sc, UDAV_EPAR, UDAV_EPAR_PHY_ADR0 | (reg & UDAV_EPAR_EROA_MASK)); /* select PHY operation and start read command */ udav_csr_write1(sc, UDAV_EPCR, UDAV_EPCR_EPOS | UDAV_EPCR_ERPRR); /* XXX: should be wait? */ /* end read command */ UDAV_CLRBIT(sc, UDAV_EPCR, UDAV_EPCR_ERPRR); /* retrieve the result from data registers */ udav_csr_read(sc, UDAV_EPDRL, val, 2); udav_unlock_mii(sc); data16 = val[0] | (val[1] << 8); DPRINTFN(0xff, ("%s: %s: phy=%d reg=0x%04x => 0x%04x\n", device_get_nameunit(sc->sc_dev), __func__, phy, reg, data16)); return (data16); } static void udav_miibus_writereg(device_t dev, int phy, int reg, int data) { struct udav_softc *sc; u_int8_t val[2]; if (dev == NULL) return; sc = USBGETSOFTC(dev); DPRINTFN(0xff, ("%s: %s: enter, phy=%d reg=0x%04x data=0x%04x\n", device_get_nameunit(sc->sc_dev), __func__, phy, reg, data)); if (sc->sc_dying) { #ifdef DIAGNOSTIC printf("%s: %s: dying\n", device_get_nameunit(sc->sc_dev), __func__); #endif return; } /* XXX: one PHY only for the internal PHY */ if (phy != 0) { DPRINTFN(0xff, ("%s: %s: phy=%d is not supported\n", device_get_nameunit(sc->sc_dev), __func__, phy)); return; } udav_lock_mii(sc); /* select internal PHY and set PHY register address */ udav_csr_write1(sc, UDAV_EPAR, UDAV_EPAR_PHY_ADR0 | (reg & UDAV_EPAR_EROA_MASK)); /* put the value to the data registers */ val[0] = data & 0xff; val[1] = (data >> 8) & 0xff; udav_csr_write(sc, UDAV_EPDRL, val, 2); /* select PHY operation and start write command */ udav_csr_write1(sc, UDAV_EPCR, UDAV_EPCR_EPOS | UDAV_EPCR_ERPRW); /* XXX: should be wait? */ /* end write command */ UDAV_CLRBIT(sc, UDAV_EPCR, UDAV_EPCR_ERPRW); udav_unlock_mii(sc); return; } static void udav_miibus_statchg(device_t dev) { #ifdef UDAV_DEBUG struct udav_softc *sc; if (dev == NULL) return; sc = USBGETSOFTC(dev); DPRINTF(("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); #endif /* Nothing to do */ } #if defined(__FreeBSD__) /* * Stop all chip I/O so that the kernel's probe routines don't * get confused by errant DMAs when rebooting. */ static void udav_shutdown(device_t dev) { struct udav_softc *sc; sc = device_get_softc(dev); udav_stop_task(sc); return; } static void udav_rxstart(struct ifnet *ifp) { struct udav_softc *sc; struct ue_chain *c; sc = ifp->if_softc; UDAV_LOCK(sc); c = &sc->sc_cdata.ue_rx_chain[sc->sc_cdata.ue_rx_prod]; c->ue_mbuf = usb_ether_newbuf(); if (c->ue_mbuf == NULL) { printf("%s: no memory for rx list " "-- packet dropped!\n", device_get_nameunit(sc->sc_dev)); ifp->if_ierrors++; UDAV_UNLOCK(sc); return; } /* Setup new transfer. */ usbd_setup_xfer(c->ue_xfer, sc->sc_pipe_rx, c, c->ue_buf, UE_BUFSZ, USBD_SHORT_XFER_OK | USBD_NO_COPY, USBD_NO_TIMEOUT, udav_rxeof); usbd_transfer(c->ue_xfer); UDAV_UNLOCK(sc); return; } #endif diff --git a/sys/dev/usb/if_ural.c b/sys/dev/usb/if_ural.c index 4c8e181a4237..9708256a5ca1 100644 --- a/sys/dev/usb/if_ural.c +++ b/sys/dev/usb/if_ural.c @@ -1,2520 +1,2520 @@ /* $FreeBSD$ */ /*- * Copyright (c) 2005, 2006 * Damien Bergamini * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include __FBSDID("$FreeBSD$"); /*- * Ralink Technology RT2500USB chipset driver * http://www.ralinktech.com/ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #include #include #ifdef USB_DEBUG #define DPRINTF(x) do { if (uraldebug > 0) logprintf x; } while (0) #define DPRINTFN(n, x) do { if (uraldebug >= (n)) logprintf x; } while (0) int uraldebug = 0; SYSCTL_NODE(_hw_usb, OID_AUTO, ural, CTLFLAG_RW, 0, "USB ural"); SYSCTL_INT(_hw_usb_ural, OID_AUTO, debug, CTLFLAG_RW, &uraldebug, 0, "ural debug level"); #else #define DPRINTF(x) #define DPRINTFN(n, x) #endif /* various supported device vendors/products */ static const struct usb_devno ural_devs[] = { { USB_VENDOR_ASUS, USB_PRODUCT_ASUS_WL167G }, { USB_VENDOR_ASUS, USB_PRODUCT_RALINK_RT2570 }, { USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5D7050 }, { USB_VENDOR_CONCEPTRONIC, USB_PRODUCT_CONCEPTRONIC_C54U }, { USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DWLG122 }, { USB_VENDOR_GIGABYTE, USB_PRODUCT_GIGABYTE_GNWBKG }, { USB_VENDOR_GUILLEMOT, USB_PRODUCT_GUILLEMOT_HWGUSB254 }, { USB_VENDOR_LINKSYS4, USB_PRODUCT_LINKSYS4_WUSB54G }, { USB_VENDOR_LINKSYS4, USB_PRODUCT_LINKSYS4_WUSB54GP }, { USB_VENDOR_LINKSYS4, USB_PRODUCT_LINKSYS4_HU200TS }, { USB_VENDOR_MELCO, USB_PRODUCT_MELCO_KG54 }, { USB_VENDOR_MELCO, USB_PRODUCT_MELCO_KG54AI }, { USB_VENDOR_MELCO, USB_PRODUCT_MELCO_KG54YB }, { USB_VENDOR_MELCO, USB_PRODUCT_MELCO_NINWIFI }, { USB_VENDOR_MSI, USB_PRODUCT_MSI_RT2570 }, { USB_VENDOR_MSI, USB_PRODUCT_MSI_RT2570_2 }, { USB_VENDOR_MSI, USB_PRODUCT_MSI_RT2570_3 }, { USB_VENDOR_NOVATECH, USB_PRODUCT_NOVATECH_NV902 }, { USB_VENDOR_RALINK, USB_PRODUCT_RALINK_RT2570 }, { USB_VENDOR_RALINK, USB_PRODUCT_RALINK_RT2570_2 }, { USB_VENDOR_VTECH, USB_PRODUCT_VTECH_RT2570 }, { USB_VENDOR_ZINWELL, USB_PRODUCT_ZINWELL_RT2570 } }; MODULE_DEPEND(ural, wlan, 1, 1, 1); static int ural_alloc_tx_list(struct ural_softc *); static void ural_free_tx_list(struct ural_softc *); static int ural_alloc_rx_list(struct ural_softc *); static void ural_free_rx_list(struct ural_softc *); static int ural_media_change(struct ifnet *); static void ural_next_scan(void *); static void ural_task(void *); static int ural_newstate(struct ieee80211com *, enum ieee80211_state, int); static int ural_rxrate(struct ural_rx_desc *); static void ural_txeof(usbd_xfer_handle, usbd_private_handle, usbd_status); static void ural_rxeof(usbd_xfer_handle, usbd_private_handle, usbd_status); static int ural_ack_rate(struct ieee80211com *, int); static uint16_t ural_txtime(int, int, uint32_t); static uint8_t ural_plcp_signal(int); static void ural_setup_tx_desc(struct ural_softc *, struct ural_tx_desc *, uint32_t, int, int); static int ural_tx_bcn(struct ural_softc *, struct mbuf *, struct ieee80211_node *); static int ural_tx_mgt(struct ural_softc *, struct mbuf *, struct ieee80211_node *); static int ural_tx_data(struct ural_softc *, struct mbuf *, struct ieee80211_node *); static void ural_start(struct ifnet *); static void ural_watchdog(struct ifnet *); static int ural_reset(struct ifnet *); static int ural_ioctl(struct ifnet *, u_long, caddr_t); static void ural_set_testmode(struct ural_softc *); static void ural_eeprom_read(struct ural_softc *, uint16_t, void *, int); static uint16_t ural_read(struct ural_softc *, uint16_t); static void ural_read_multi(struct ural_softc *, uint16_t, void *, int); static void ural_write(struct ural_softc *, uint16_t, uint16_t); static void ural_write_multi(struct ural_softc *, uint16_t, void *, int); static void ural_bbp_write(struct ural_softc *, uint8_t, uint8_t); static uint8_t ural_bbp_read(struct ural_softc *, uint8_t); static void ural_rf_write(struct ural_softc *, uint8_t, uint32_t); static void ural_set_chan(struct ural_softc *, struct ieee80211_channel *); static void ural_disable_rf_tune(struct ural_softc *); static void ural_enable_tsf_sync(struct ural_softc *); static void ural_update_slot(struct ifnet *); static void ural_set_txpreamble(struct ural_softc *); static void ural_set_basicrates(struct ural_softc *); static void ural_set_bssid(struct ural_softc *, uint8_t *); static void ural_set_macaddr(struct ural_softc *, uint8_t *); static void ural_update_promisc(struct ural_softc *); static const char *ural_get_rf(int); static void ural_read_eeprom(struct ural_softc *); static int ural_bbp_init(struct ural_softc *); static void ural_set_txantenna(struct ural_softc *, int); static void ural_set_rxantenna(struct ural_softc *, int); static void ural_init(void *); static void ural_stop(void *); static int ural_raw_xmit(struct ieee80211_node *, struct mbuf *, const struct ieee80211_bpf_params *); static void ural_amrr_start(struct ural_softc *, struct ieee80211_node *); static void ural_amrr_timeout(void *); static void ural_amrr_update(usbd_xfer_handle, usbd_private_handle, usbd_status status); static void ural_ratectl(struct ural_amrr *, struct ieee80211_node *); /* * Supported rates for 802.11a/b/g modes (in 500Kbps unit). */ static const struct ieee80211_rateset ural_rateset_11a = { 8, { 12, 18, 24, 36, 48, 72, 96, 108 } }; static const struct ieee80211_rateset ural_rateset_11b = { 4, { 2, 4, 11, 22 } }; static const struct ieee80211_rateset ural_rateset_11g = { 12, { 2, 4, 11, 22, 12, 18, 24, 36, 48, 72, 96, 108 } }; /* * Default values for MAC registers; values taken from the reference driver. */ static const struct { uint16_t reg; uint16_t val; } ural_def_mac[] = { { RAL_TXRX_CSR5, 0x8c8d }, { RAL_TXRX_CSR6, 0x8b8a }, { RAL_TXRX_CSR7, 0x8687 }, { RAL_TXRX_CSR8, 0x0085 }, { RAL_MAC_CSR13, 0x1111 }, { RAL_MAC_CSR14, 0x1e11 }, { RAL_TXRX_CSR21, 0xe78f }, { RAL_MAC_CSR9, 0xff1d }, { RAL_MAC_CSR11, 0x0002 }, { RAL_MAC_CSR22, 0x0053 }, { RAL_MAC_CSR15, 0x0000 }, { RAL_MAC_CSR8, 0x0780 }, { RAL_TXRX_CSR19, 0x0000 }, { RAL_TXRX_CSR18, 0x005a }, { RAL_PHY_CSR2, 0x0000 }, { RAL_TXRX_CSR0, 0x1ec0 }, { RAL_PHY_CSR4, 0x000f } }; /* * Default values for BBP registers; values taken from the reference driver. */ static const struct { uint8_t reg; uint8_t val; } ural_def_bbp[] = { { 3, 0x02 }, { 4, 0x19 }, { 14, 0x1c }, { 15, 0x30 }, { 16, 0xac }, { 17, 0x48 }, { 18, 0x18 }, { 19, 0xff }, { 20, 0x1e }, { 21, 0x08 }, { 22, 0x08 }, { 23, 0x08 }, { 24, 0x80 }, { 25, 0x50 }, { 26, 0x08 }, { 27, 0x23 }, { 30, 0x10 }, { 31, 0x2b }, { 32, 0xb9 }, { 34, 0x12 }, { 35, 0x50 }, { 39, 0xc4 }, { 40, 0x02 }, { 41, 0x60 }, { 53, 0x10 }, { 54, 0x18 }, { 56, 0x08 }, { 57, 0x10 }, { 58, 0x08 }, { 61, 0x60 }, { 62, 0x10 }, { 75, 0xff } }; /* * Default values for RF register R2 indexed by channel numbers. */ static const uint32_t ural_rf2522_r2[] = { 0x307f6, 0x307fb, 0x30800, 0x30805, 0x3080a, 0x3080f, 0x30814, 0x30819, 0x3081e, 0x30823, 0x30828, 0x3082d, 0x30832, 0x3083e }; static const uint32_t ural_rf2523_r2[] = { 0x00327, 0x00328, 0x00329, 0x0032a, 0x0032b, 0x0032c, 0x0032d, 0x0032e, 0x0032f, 0x00340, 0x00341, 0x00342, 0x00343, 0x00346 }; static const uint32_t ural_rf2524_r2[] = { 0x00327, 0x00328, 0x00329, 0x0032a, 0x0032b, 0x0032c, 0x0032d, 0x0032e, 0x0032f, 0x00340, 0x00341, 0x00342, 0x00343, 0x00346 }; static const uint32_t ural_rf2525_r2[] = { 0x20327, 0x20328, 0x20329, 0x2032a, 0x2032b, 0x2032c, 0x2032d, 0x2032e, 0x2032f, 0x20340, 0x20341, 0x20342, 0x20343, 0x20346 }; static const uint32_t ural_rf2525_hi_r2[] = { 0x2032f, 0x20340, 0x20341, 0x20342, 0x20343, 0x20344, 0x20345, 0x20346, 0x20347, 0x20348, 0x20349, 0x2034a, 0x2034b, 0x2034e }; static const uint32_t ural_rf2525e_r2[] = { 0x2044d, 0x2044e, 0x2044f, 0x20460, 0x20461, 0x20462, 0x20463, 0x20464, 0x20465, 0x20466, 0x20467, 0x20468, 0x20469, 0x2046b }; static const uint32_t ural_rf2526_hi_r2[] = { 0x0022a, 0x0022b, 0x0022b, 0x0022c, 0x0022c, 0x0022d, 0x0022d, 0x0022e, 0x0022e, 0x0022f, 0x0022d, 0x00240, 0x00240, 0x00241 }; static const uint32_t ural_rf2526_r2[] = { 0x00226, 0x00227, 0x00227, 0x00228, 0x00228, 0x00229, 0x00229, 0x0022a, 0x0022a, 0x0022b, 0x0022b, 0x0022c, 0x0022c, 0x0022d }; /* * For dual-band RF, RF registers R1 and R4 also depend on channel number; * values taken from the reference driver. */ static const struct { uint8_t chan; uint32_t r1; uint32_t r2; uint32_t r4; } ural_rf5222[] = { { 1, 0x08808, 0x0044d, 0x00282 }, { 2, 0x08808, 0x0044e, 0x00282 }, { 3, 0x08808, 0x0044f, 0x00282 }, { 4, 0x08808, 0x00460, 0x00282 }, { 5, 0x08808, 0x00461, 0x00282 }, { 6, 0x08808, 0x00462, 0x00282 }, { 7, 0x08808, 0x00463, 0x00282 }, { 8, 0x08808, 0x00464, 0x00282 }, { 9, 0x08808, 0x00465, 0x00282 }, { 10, 0x08808, 0x00466, 0x00282 }, { 11, 0x08808, 0x00467, 0x00282 }, { 12, 0x08808, 0x00468, 0x00282 }, { 13, 0x08808, 0x00469, 0x00282 }, { 14, 0x08808, 0x0046b, 0x00286 }, { 36, 0x08804, 0x06225, 0x00287 }, { 40, 0x08804, 0x06226, 0x00287 }, { 44, 0x08804, 0x06227, 0x00287 }, { 48, 0x08804, 0x06228, 0x00287 }, { 52, 0x08804, 0x06229, 0x00287 }, { 56, 0x08804, 0x0622a, 0x00287 }, { 60, 0x08804, 0x0622b, 0x00287 }, { 64, 0x08804, 0x0622c, 0x00287 }, { 100, 0x08804, 0x02200, 0x00283 }, { 104, 0x08804, 0x02201, 0x00283 }, { 108, 0x08804, 0x02202, 0x00283 }, { 112, 0x08804, 0x02203, 0x00283 }, { 116, 0x08804, 0x02204, 0x00283 }, { 120, 0x08804, 0x02205, 0x00283 }, { 124, 0x08804, 0x02206, 0x00283 }, { 128, 0x08804, 0x02207, 0x00283 }, { 132, 0x08804, 0x02208, 0x00283 }, { 136, 0x08804, 0x02209, 0x00283 }, { 140, 0x08804, 0x0220a, 0x00283 }, { 149, 0x08808, 0x02429, 0x00281 }, { 153, 0x08808, 0x0242b, 0x00281 }, { 157, 0x08808, 0x0242d, 0x00281 }, { 161, 0x08808, 0x0242f, 0x00281 } }; USB_DECLARE_DRIVER(ural); USB_MATCH(ural) { USB_MATCH_START(ural, uaa); if (uaa->iface != NULL) return UMATCH_NONE; return (usb_lookup(ural_devs, uaa->vendor, uaa->product) != NULL) ? UMATCH_VENDOR_PRODUCT : UMATCH_NONE; } USB_ATTACH(ural) { USB_ATTACH_START(ural, sc, uaa); struct ifnet *ifp; struct ieee80211com *ic = &sc->sc_ic; usb_interface_descriptor_t *id; usb_endpoint_descriptor_t *ed; usbd_status error; char devinfo[1024]; int i; sc->sc_udev = uaa->device; usbd_devinfo(sc->sc_udev, 0, devinfo); USB_ATTACH_SETUP; if (usbd_set_config_no(sc->sc_udev, RAL_CONFIG_NO, 0) != 0) { printf("%s: could not set configuration no\n", device_get_nameunit(sc->sc_dev)); USB_ATTACH_ERROR_RETURN; } /* get the first interface handle */ error = usbd_device2interface_handle(sc->sc_udev, RAL_IFACE_INDEX, &sc->sc_iface); if (error != 0) { printf("%s: could not get interface handle\n", device_get_nameunit(sc->sc_dev)); USB_ATTACH_ERROR_RETURN; } /* * Find endpoints. */ id = usbd_get_interface_descriptor(sc->sc_iface); sc->sc_rx_no = sc->sc_tx_no = -1; for (i = 0; i < id->bNumEndpoints; i++) { ed = usbd_interface2endpoint_descriptor(sc->sc_iface, i); if (ed == NULL) { printf("%s: no endpoint descriptor for %d\n", device_get_nameunit(sc->sc_dev), i); USB_ATTACH_ERROR_RETURN; } if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) sc->sc_rx_no = ed->bEndpointAddress; else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT && UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) sc->sc_tx_no = ed->bEndpointAddress; } if (sc->sc_rx_no == -1 || sc->sc_tx_no == -1) { printf("%s: missing endpoint\n", device_get_nameunit(sc->sc_dev)); USB_ATTACH_ERROR_RETURN; } mtx_init(&sc->sc_mtx, device_get_nameunit(sc->sc_dev), MTX_NETWORK_LOCK, MTX_DEF | MTX_RECURSE); usb_init_task(&sc->sc_task, ural_task, sc); callout_init(&sc->scan_ch, debug_mpsafenet ? CALLOUT_MPSAFE : 0); callout_init(&sc->amrr_ch, 0); /* retrieve RT2570 rev. no */ sc->asic_rev = ural_read(sc, RAL_MAC_CSR0); /* retrieve MAC address and various other things from EEPROM */ ural_read_eeprom(sc); printf("%s: MAC/BBP RT2570 (rev 0x%02x), RF %s\n", device_get_nameunit(sc->sc_dev), sc->asic_rev, ural_get_rf(sc->rf_rev)); ifp = sc->sc_ifp = if_alloc(IFT_ETHER); if (ifp == NULL) { printf("%s: can not if_alloc()\n", device_get_nameunit(sc->sc_dev)); USB_ATTACH_ERROR_RETURN; } ifp->if_softc = sc; if_initname(ifp, "ural", device_get_unit(sc->sc_dev)); ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST | IFF_NEEDSGIANT; /* USB stack is still under Giant lock */ ifp->if_init = ural_init; ifp->if_ioctl = ural_ioctl; ifp->if_start = ural_start; ifp->if_watchdog = ural_watchdog; IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN); ifp->if_snd.ifq_drv_maxlen = IFQ_MAXLEN; IFQ_SET_READY(&ifp->if_snd); ic->ic_ifp = ifp; ic->ic_phytype = IEEE80211_T_OFDM; /* not only, but not used */ ic->ic_opmode = IEEE80211_M_STA; /* default to BSS mode */ ic->ic_state = IEEE80211_S_INIT; /* set device capabilities */ ic->ic_caps = IEEE80211_C_IBSS | /* IBSS mode supported */ IEEE80211_C_MONITOR | /* monitor mode supported */ IEEE80211_C_HOSTAP | /* HostAp mode supported */ IEEE80211_C_TXPMGT | /* tx power management */ IEEE80211_C_SHPREAMBLE | /* short preamble supported */ IEEE80211_C_SHSLOT | /* short slot time supported */ IEEE80211_C_WPA; /* 802.11i */ if (sc->rf_rev == RAL_RF_5222) { /* set supported .11a rates */ ic->ic_sup_rates[IEEE80211_MODE_11A] = ural_rateset_11a; /* set supported .11a channels */ for (i = 36; i <= 64; i += 4) { ic->ic_channels[i].ic_freq = ieee80211_ieee2mhz(i, IEEE80211_CHAN_5GHZ); ic->ic_channels[i].ic_flags = IEEE80211_CHAN_A; } for (i = 100; i <= 140; i += 4) { ic->ic_channels[i].ic_freq = ieee80211_ieee2mhz(i, IEEE80211_CHAN_5GHZ); ic->ic_channels[i].ic_flags = IEEE80211_CHAN_A; } for (i = 149; i <= 161; i += 4) { ic->ic_channels[i].ic_freq = ieee80211_ieee2mhz(i, IEEE80211_CHAN_5GHZ); ic->ic_channels[i].ic_flags = IEEE80211_CHAN_A; } } /* set supported .11b and .11g rates */ ic->ic_sup_rates[IEEE80211_MODE_11B] = ural_rateset_11b; ic->ic_sup_rates[IEEE80211_MODE_11G] = ural_rateset_11g; /* set supported .11b and .11g channels (1 through 14) */ for (i = 1; i <= 14; i++) { ic->ic_channels[i].ic_freq = ieee80211_ieee2mhz(i, IEEE80211_CHAN_2GHZ); ic->ic_channels[i].ic_flags = IEEE80211_CHAN_CCK | IEEE80211_CHAN_OFDM | IEEE80211_CHAN_DYN | IEEE80211_CHAN_2GHZ; } ieee80211_ifattach(ic); ic->ic_reset = ural_reset; /* enable s/w bmiss handling in sta mode */ ic->ic_flags_ext |= IEEE80211_FEXT_SWBMISS; /* override state transition machine */ sc->sc_newstate = ic->ic_newstate; ic->ic_newstate = ural_newstate; ic->ic_raw_xmit = ural_raw_xmit; ieee80211_media_init(ic, ural_media_change, ieee80211_media_status); bpfattach2(ifp, DLT_IEEE802_11_RADIO, sizeof (struct ieee80211_frame) + 64, &sc->sc_drvbpf); sc->sc_rxtap_len = sizeof sc->sc_rxtapu; sc->sc_rxtap.wr_ihdr.it_len = htole16(sc->sc_rxtap_len); sc->sc_rxtap.wr_ihdr.it_present = htole32(RAL_RX_RADIOTAP_PRESENT); sc->sc_txtap_len = sizeof sc->sc_txtapu; sc->sc_txtap.wt_ihdr.it_len = htole16(sc->sc_txtap_len); sc->sc_txtap.wt_ihdr.it_present = htole32(RAL_TX_RADIOTAP_PRESENT); if (bootverbose) ieee80211_announce(ic); USB_ATTACH_SUCCESS_RETURN; } USB_DETACH(ural) { USB_DETACH_START(ural, sc); struct ieee80211com *ic = &sc->sc_ic; struct ifnet *ifp = ic->ic_ifp; ural_stop(sc); usb_rem_task(sc->sc_udev, &sc->sc_task); callout_stop(&sc->scan_ch); callout_stop(&sc->amrr_ch); if (sc->amrr_xfer != NULL) { usbd_free_xfer(sc->amrr_xfer); sc->amrr_xfer = NULL; } if (sc->sc_rx_pipeh != NULL) { usbd_abort_pipe(sc->sc_rx_pipeh); usbd_close_pipe(sc->sc_rx_pipeh); } if (sc->sc_tx_pipeh != NULL) { usbd_abort_pipe(sc->sc_tx_pipeh); usbd_close_pipe(sc->sc_tx_pipeh); } ural_free_rx_list(sc); ural_free_tx_list(sc); bpfdetach(ifp); ieee80211_ifdetach(ic); if_free(ifp); mtx_destroy(&sc->sc_mtx); return 0; } static int ural_alloc_tx_list(struct ural_softc *sc) { struct ural_tx_data *data; int i, error; sc->tx_queued = 0; for (i = 0; i < RAL_TX_LIST_COUNT; i++) { data = &sc->tx_data[i]; data->sc = sc; data->xfer = usbd_alloc_xfer(sc->sc_udev); if (data->xfer == NULL) { printf("%s: could not allocate tx xfer\n", device_get_nameunit(sc->sc_dev)); error = ENOMEM; goto fail; } data->buf = usbd_alloc_buffer(data->xfer, RAL_TX_DESC_SIZE + MCLBYTES); if (data->buf == NULL) { printf("%s: could not allocate tx buffer\n", device_get_nameunit(sc->sc_dev)); error = ENOMEM; goto fail; } } return 0; fail: ural_free_tx_list(sc); return error; } static void ural_free_tx_list(struct ural_softc *sc) { struct ural_tx_data *data; int i; for (i = 0; i < RAL_TX_LIST_COUNT; i++) { data = &sc->tx_data[i]; if (data->xfer != NULL) { usbd_free_xfer(data->xfer); data->xfer = NULL; } if (data->ni != NULL) { ieee80211_free_node(data->ni); data->ni = NULL; } } } static int ural_alloc_rx_list(struct ural_softc *sc) { struct ural_rx_data *data; int i, error; for (i = 0; i < RAL_RX_LIST_COUNT; i++) { data = &sc->rx_data[i]; data->sc = sc; data->xfer = usbd_alloc_xfer(sc->sc_udev); if (data->xfer == NULL) { printf("%s: could not allocate rx xfer\n", device_get_nameunit(sc->sc_dev)); error = ENOMEM; goto fail; } if (usbd_alloc_buffer(data->xfer, MCLBYTES) == NULL) { printf("%s: could not allocate rx buffer\n", device_get_nameunit(sc->sc_dev)); error = ENOMEM; goto fail; } data->m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); if (data->m == NULL) { printf("%s: could not allocate rx mbuf\n", device_get_nameunit(sc->sc_dev)); error = ENOMEM; goto fail; } data->buf = mtod(data->m, uint8_t *); } return 0; fail: ural_free_tx_list(sc); return error; } static void ural_free_rx_list(struct ural_softc *sc) { struct ural_rx_data *data; int i; for (i = 0; i < RAL_RX_LIST_COUNT; i++) { data = &sc->rx_data[i]; if (data->xfer != NULL) { usbd_free_xfer(data->xfer); data->xfer = NULL; } if (data->m != NULL) { m_freem(data->m); data->m = NULL; } } } static int ural_media_change(struct ifnet *ifp) { struct ural_softc *sc = ifp->if_softc; int error; RAL_LOCK(sc); error = ieee80211_media_change(ifp); if (error != ENETRESET) { RAL_UNLOCK(sc); return error; } if ((ifp->if_flags & IFF_UP) && (ifp->if_drv_flags & IFF_DRV_RUNNING)) ural_init(sc); RAL_UNLOCK(sc); return 0; } /* * This function is called periodically (every 200ms) during scanning to * switch from one channel to another. */ static void ural_next_scan(void *arg) { struct ural_softc *sc = arg; struct ieee80211com *ic = &sc->sc_ic; if (ic->ic_state == IEEE80211_S_SCAN) ieee80211_next_scan(ic); } static void ural_task(void *arg) { struct ural_softc *sc = arg; struct ieee80211com *ic = &sc->sc_ic; enum ieee80211_state ostate; struct ieee80211_node *ni; struct mbuf *m; ostate = ic->ic_state; switch (sc->sc_state) { case IEEE80211_S_INIT: if (ostate == IEEE80211_S_RUN) { /* abort TSF synchronization */ ural_write(sc, RAL_TXRX_CSR19, 0); /* force tx led to stop blinking */ ural_write(sc, RAL_MAC_CSR20, 0); } break; case IEEE80211_S_SCAN: ural_set_chan(sc, ic->ic_curchan); callout_reset(&sc->scan_ch, hz / 5, ural_next_scan, sc); break; case IEEE80211_S_AUTH: ural_set_chan(sc, ic->ic_curchan); break; case IEEE80211_S_ASSOC: ural_set_chan(sc, ic->ic_curchan); break; case IEEE80211_S_RUN: ural_set_chan(sc, ic->ic_curchan); ni = ic->ic_bss; if (ic->ic_opmode != IEEE80211_M_MONITOR) { ural_update_slot(ic->ic_ifp); ural_set_txpreamble(sc); ural_set_basicrates(sc); ural_set_bssid(sc, ni->ni_bssid); } if (ic->ic_opmode == IEEE80211_M_HOSTAP || ic->ic_opmode == IEEE80211_M_IBSS) { m = ieee80211_beacon_alloc(ic, ni, &sc->sc_bo); if (m == NULL) { printf("%s: could not allocate beacon\n", device_get_nameunit(sc->sc_dev)); return; } if (ural_tx_bcn(sc, m, ni) != 0) { printf("%s: could not send beacon\n", device_get_nameunit(sc->sc_dev)); return; } } /* make tx led blink on tx (controlled by ASIC) */ ural_write(sc, RAL_MAC_CSR20, 1); if (ic->ic_opmode != IEEE80211_M_MONITOR) ural_enable_tsf_sync(sc); /* enable automatic rate adaptation in STA mode */ if (ic->ic_opmode == IEEE80211_M_STA && ic->ic_fixed_rate == IEEE80211_FIXED_RATE_NONE) ural_amrr_start(sc, ni); break; } sc->sc_newstate(ic, sc->sc_state, -1); } static int ural_newstate(struct ieee80211com *ic, enum ieee80211_state nstate, int arg) { struct ural_softc *sc = ic->ic_ifp->if_softc; usb_rem_task(sc->sc_udev, &sc->sc_task); callout_stop(&sc->scan_ch); callout_stop(&sc->amrr_ch); /* do it in a process context */ sc->sc_state = nstate; - usb_add_task(sc->sc_udev, &sc->sc_task); + usb_add_task(sc->sc_udev, &sc->sc_task, USB_TASKQ_DRIVER); return 0; } /* quickly determine if a given rate is CCK or OFDM */ #define RAL_RATE_IS_OFDM(rate) ((rate) >= 12 && (rate) != 22) #define RAL_ACK_SIZE 14 /* 10 + 4(FCS) */ #define RAL_CTS_SIZE 14 /* 10 + 4(FCS) */ #define RAL_SIFS 10 /* us */ #define RAL_RXTX_TURNAROUND 5 /* us */ /* * This function is only used by the Rx radiotap code. */ static int ural_rxrate(struct ural_rx_desc *desc) { if (le32toh(desc->flags) & RAL_RX_OFDM) { /* reverse function of ural_plcp_signal */ switch (desc->rate) { case 0xb: return 12; case 0xf: return 18; case 0xa: return 24; case 0xe: return 36; case 0x9: return 48; case 0xd: return 72; case 0x8: return 96; case 0xc: return 108; } } else { if (desc->rate == 10) return 2; if (desc->rate == 20) return 4; if (desc->rate == 55) return 11; if (desc->rate == 110) return 22; } return 2; /* should not get there */ } static void ural_txeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) { struct ural_tx_data *data = priv; struct ural_softc *sc = data->sc; struct ifnet *ifp = sc->sc_ic.ic_ifp; if (status != USBD_NORMAL_COMPLETION) { if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) return; printf("%s: could not transmit buffer: %s\n", device_get_nameunit(sc->sc_dev), usbd_errstr(status)); if (status == USBD_STALLED) usbd_clear_endpoint_stall_async(sc->sc_rx_pipeh); ifp->if_oerrors++; return; } m_freem(data->m); data->m = NULL; ieee80211_free_node(data->ni); data->ni = NULL; sc->tx_queued--; ifp->if_opackets++; DPRINTFN(10, ("tx done\n")); sc->sc_tx_timer = 0; ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; ural_start(ifp); } static void ural_rxeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) { struct ural_rx_data *data = priv; struct ural_softc *sc = data->sc; struct ieee80211com *ic = &sc->sc_ic; struct ifnet *ifp = ic->ic_ifp; struct ural_rx_desc *desc; struct ieee80211_frame *wh; struct ieee80211_node *ni; struct mbuf *mnew, *m; int len; if (status != USBD_NORMAL_COMPLETION) { if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) return; if (status == USBD_STALLED) usbd_clear_endpoint_stall_async(sc->sc_rx_pipeh); goto skip; } usbd_get_xfer_status(xfer, NULL, NULL, &len, NULL); if (len < RAL_RX_DESC_SIZE + IEEE80211_MIN_LEN) { DPRINTF(("%s: xfer too short %d\n", device_get_nameunit(sc->sc_dev), len)); ifp->if_ierrors++; goto skip; } /* rx descriptor is located at the end */ desc = (struct ural_rx_desc *)(data->buf + len - RAL_RX_DESC_SIZE); if ((le32toh(desc->flags) & RAL_RX_PHY_ERROR) || (le32toh(desc->flags) & RAL_RX_CRC_ERROR)) { /* * This should not happen since we did not request to receive * those frames when we filled RAL_TXRX_CSR2. */ DPRINTFN(5, ("PHY or CRC error\n")); ifp->if_ierrors++; goto skip; } mnew = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); if (mnew == NULL) { ifp->if_ierrors++; goto skip; } m = data->m; data->m = mnew; data->buf = mtod(data->m, uint8_t *); /* finalize mbuf */ m->m_pkthdr.rcvif = ifp; m->m_pkthdr.len = m->m_len = (le32toh(desc->flags) >> 16) & 0xfff; m->m_flags |= M_HASFCS; /* h/w leaves FCS */ if (bpf_peers_present(sc->sc_drvbpf)) { struct ural_rx_radiotap_header *tap = &sc->sc_rxtap; tap->wr_flags = IEEE80211_RADIOTAP_F_FCS; tap->wr_rate = ural_rxrate(desc); tap->wr_chan_freq = htole16(ic->ic_curchan->ic_freq); tap->wr_chan_flags = htole16(ic->ic_curchan->ic_flags); tap->wr_antenna = sc->rx_ant; tap->wr_antsignal = desc->rssi; bpf_mtap2(sc->sc_drvbpf, tap, sc->sc_rxtap_len, m); } wh = mtod(m, struct ieee80211_frame *); ni = ieee80211_find_rxnode(ic, (struct ieee80211_frame_min *)wh); /* send the frame to the 802.11 layer */ ieee80211_input(ic, m, ni, desc->rssi, 0); /* node is no longer needed */ ieee80211_free_node(ni); DPRINTFN(15, ("rx done\n")); skip: /* setup a new transfer */ usbd_setup_xfer(xfer, sc->sc_rx_pipeh, data, data->buf, MCLBYTES, USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT, ural_rxeof); usbd_transfer(xfer); } /* * Return the expected ack rate for a frame transmitted at rate `rate'. * XXX: this should depend on the destination node basic rate set. */ static int ural_ack_rate(struct ieee80211com *ic, int rate) { switch (rate) { /* CCK rates */ case 2: return 2; case 4: case 11: case 22: return (ic->ic_curmode == IEEE80211_MODE_11B) ? 4 : rate; /* OFDM rates */ case 12: case 18: return 12; case 24: case 36: return 24; case 48: case 72: case 96: case 108: return 48; } /* default to 1Mbps */ return 2; } /* * Compute the duration (in us) needed to transmit `len' bytes at rate `rate'. * The function automatically determines the operating mode depending on the * given rate. `flags' indicates whether short preamble is in use or not. */ static uint16_t ural_txtime(int len, int rate, uint32_t flags) { uint16_t txtime; if (RAL_RATE_IS_OFDM(rate)) { /* IEEE Std 802.11a-1999, pp. 37 */ txtime = (8 + 4 * len + 3 + rate - 1) / rate; txtime = 16 + 4 + 4 * txtime + 6; } else { /* IEEE Std 802.11b-1999, pp. 28 */ txtime = (16 * len + rate - 1) / rate; if (rate != 2 && (flags & IEEE80211_F_SHPREAMBLE)) txtime += 72 + 24; else txtime += 144 + 48; } return txtime; } static uint8_t ural_plcp_signal(int rate) { switch (rate) { /* CCK rates (returned values are device-dependent) */ case 2: return 0x0; case 4: return 0x1; case 11: return 0x2; case 22: return 0x3; /* OFDM rates (cf IEEE Std 802.11a-1999, pp. 14 Table 80) */ case 12: return 0xb; case 18: return 0xf; case 24: return 0xa; case 36: return 0xe; case 48: return 0x9; case 72: return 0xd; case 96: return 0x8; case 108: return 0xc; /* unsupported rates (should not get there) */ default: return 0xff; } } static void ural_setup_tx_desc(struct ural_softc *sc, struct ural_tx_desc *desc, uint32_t flags, int len, int rate) { struct ieee80211com *ic = &sc->sc_ic; uint16_t plcp_length; int remainder; desc->flags = htole32(flags); desc->flags |= htole32(RAL_TX_NEWSEQ); desc->flags |= htole32(len << 16); desc->wme = htole16(RAL_AIFSN(2) | RAL_LOGCWMIN(3) | RAL_LOGCWMAX(5)); desc->wme |= htole16(RAL_IVOFFSET(sizeof (struct ieee80211_frame))); /* setup PLCP fields */ desc->plcp_signal = ural_plcp_signal(rate); desc->plcp_service = 4; len += IEEE80211_CRC_LEN; if (RAL_RATE_IS_OFDM(rate)) { desc->flags |= htole32(RAL_TX_OFDM); plcp_length = len & 0xfff; desc->plcp_length_hi = plcp_length >> 6; desc->plcp_length_lo = plcp_length & 0x3f; } else { plcp_length = (16 * len + rate - 1) / rate; if (rate == 22) { remainder = (16 * len) % 22; if (remainder != 0 && remainder < 7) desc->plcp_service |= RAL_PLCP_LENGEXT; } desc->plcp_length_hi = plcp_length >> 8; desc->plcp_length_lo = plcp_length & 0xff; if (rate != 2 && (ic->ic_flags & IEEE80211_F_SHPREAMBLE)) desc->plcp_signal |= 0x08; } desc->iv = 0; desc->eiv = 0; } #define RAL_TX_TIMEOUT 5000 static int ural_tx_bcn(struct ural_softc *sc, struct mbuf *m0, struct ieee80211_node *ni) { struct ural_tx_desc *desc; usbd_xfer_handle xfer; uint8_t cmd = 0; usbd_status error; uint8_t *buf; int xferlen, rate; rate = IEEE80211_IS_CHAN_5GHZ(ni->ni_chan) ? 12 : 2; xfer = usbd_alloc_xfer(sc->sc_udev); if (xfer == NULL) return ENOMEM; /* xfer length needs to be a multiple of two! */ xferlen = (RAL_TX_DESC_SIZE + m0->m_pkthdr.len + 1) & ~1; buf = usbd_alloc_buffer(xfer, xferlen); if (buf == NULL) { usbd_free_xfer(xfer); return ENOMEM; } usbd_setup_xfer(xfer, sc->sc_tx_pipeh, NULL, &cmd, sizeof cmd, USBD_FORCE_SHORT_XFER, RAL_TX_TIMEOUT, NULL); error = usbd_sync_transfer(xfer); if (error != 0) { usbd_free_xfer(xfer); return error; } desc = (struct ural_tx_desc *)buf; m_copydata(m0, 0, m0->m_pkthdr.len, buf + RAL_TX_DESC_SIZE); ural_setup_tx_desc(sc, desc, RAL_TX_IFS_NEWBACKOFF | RAL_TX_TIMESTAMP, m0->m_pkthdr.len, rate); DPRINTFN(10, ("sending beacon frame len=%u rate=%u xfer len=%u\n", m0->m_pkthdr.len, rate, xferlen)); usbd_setup_xfer(xfer, sc->sc_tx_pipeh, NULL, buf, xferlen, USBD_FORCE_SHORT_XFER | USBD_NO_COPY, RAL_TX_TIMEOUT, NULL); error = usbd_sync_transfer(xfer); usbd_free_xfer(xfer); return error; } static int ural_tx_mgt(struct ural_softc *sc, struct mbuf *m0, struct ieee80211_node *ni) { struct ieee80211com *ic = &sc->sc_ic; struct ural_tx_desc *desc; struct ural_tx_data *data; struct ieee80211_frame *wh; uint32_t flags = 0; uint16_t dur; usbd_status error; int xferlen, rate; data = &sc->tx_data[0]; desc = (struct ural_tx_desc *)data->buf; rate = IEEE80211_IS_CHAN_5GHZ(ic->ic_curchan) ? 12 : 2; data->m = m0; data->ni = ni; wh = mtod(m0, struct ieee80211_frame *); if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { flags |= RAL_TX_ACK; dur = ural_txtime(RAL_ACK_SIZE, rate, ic->ic_flags) + RAL_SIFS; *(uint16_t *)wh->i_dur = htole16(dur); /* tell hardware to add timestamp for probe responses */ if ((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) == IEEE80211_FC0_TYPE_MGT && (wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK) == IEEE80211_FC0_SUBTYPE_PROBE_RESP) flags |= RAL_TX_TIMESTAMP; } if (bpf_peers_present(sc->sc_drvbpf)) { struct ural_tx_radiotap_header *tap = &sc->sc_txtap; tap->wt_flags = 0; tap->wt_rate = rate; tap->wt_chan_freq = htole16(ic->ic_curchan->ic_freq); tap->wt_chan_flags = htole16(ic->ic_curchan->ic_flags); tap->wt_antenna = sc->tx_ant; bpf_mtap2(sc->sc_drvbpf, tap, sc->sc_txtap_len, m0); } m_copydata(m0, 0, m0->m_pkthdr.len, data->buf + RAL_TX_DESC_SIZE); ural_setup_tx_desc(sc, desc, flags, m0->m_pkthdr.len, rate); /* align end on a 2-bytes boundary */ xferlen = (RAL_TX_DESC_SIZE + m0->m_pkthdr.len + 1) & ~1; /* * No space left in the last URB to store the extra 2 bytes, force * sending of another URB. */ if ((xferlen % 64) == 0) xferlen += 2; DPRINTFN(10, ("sending mgt frame len=%u rate=%u xfer len=%u\n", m0->m_pkthdr.len, rate, xferlen)); usbd_setup_xfer(data->xfer, sc->sc_tx_pipeh, data, data->buf, xferlen, USBD_FORCE_SHORT_XFER | USBD_NO_COPY, RAL_TX_TIMEOUT, ural_txeof); error = usbd_transfer(data->xfer); if (error != USBD_NORMAL_COMPLETION && error != USBD_IN_PROGRESS) return error; sc->tx_queued++; return 0; } static int ural_tx_raw(struct ural_softc *sc, struct mbuf *m0, struct ieee80211_node *ni, const struct ieee80211_bpf_params *params) { struct ieee80211com *ic = &sc->sc_ic; struct ural_tx_desc *desc; struct ural_tx_data *data; uint32_t flags; usbd_status error; int xferlen, rate; data = &sc->tx_data[0]; desc = (struct ural_tx_desc *)data->buf; rate = params->ibp_rate0 & IEEE80211_RATE_VAL; /* XXX validate */ if (rate == 0) return EINVAL; if (bpf_peers_present(sc->sc_drvbpf)) { struct ural_tx_radiotap_header *tap = &sc->sc_txtap; tap->wt_flags = 0; tap->wt_rate = rate; tap->wt_chan_freq = htole16(ic->ic_curchan->ic_freq); tap->wt_chan_flags = htole16(ic->ic_curchan->ic_flags); tap->wt_antenna = sc->tx_ant; bpf_mtap2(sc->sc_drvbpf, tap, sc->sc_txtap_len, m0); } data->m = m0; data->ni = ni; flags = 0; if ((params->ibp_flags & IEEE80211_BPF_NOACK) == 0) flags |= RAL_TX_ACK; m_copydata(m0, 0, m0->m_pkthdr.len, data->buf + RAL_TX_DESC_SIZE); /* XXX need to setup descriptor ourself */ ural_setup_tx_desc(sc, desc, flags, m0->m_pkthdr.len, rate); /* align end on a 2-bytes boundary */ xferlen = (RAL_TX_DESC_SIZE + m0->m_pkthdr.len + 1) & ~1; /* * No space left in the last URB to store the extra 2 bytes, force * sending of another URB. */ if ((xferlen % 64) == 0) xferlen += 2; DPRINTFN(10, ("sending raw frame len=%u rate=%u xfer len=%u\n", m0->m_pkthdr.len, rate, xferlen)); usbd_setup_xfer(data->xfer, sc->sc_tx_pipeh, data, data->buf, xferlen, USBD_FORCE_SHORT_XFER | USBD_NO_COPY, RAL_TX_TIMEOUT, ural_txeof); error = usbd_transfer(data->xfer); if (error != USBD_NORMAL_COMPLETION && error != USBD_IN_PROGRESS) return error; sc->tx_queued++; return 0; } static int ural_tx_data(struct ural_softc *sc, struct mbuf *m0, struct ieee80211_node *ni) { struct ieee80211com *ic = &sc->sc_ic; struct ural_tx_desc *desc; struct ural_tx_data *data; struct ieee80211_frame *wh; struct ieee80211_key *k; uint32_t flags = 0; uint16_t dur; usbd_status error; int xferlen, rate; wh = mtod(m0, struct ieee80211_frame *); if (ic->ic_fixed_rate != IEEE80211_FIXED_RATE_NONE) rate = ic->ic_bss->ni_rates.rs_rates[ic->ic_fixed_rate]; else rate = ni->ni_rates.rs_rates[ni->ni_txrate]; rate &= IEEE80211_RATE_VAL; if (wh->i_fc[1] & IEEE80211_FC1_WEP) { k = ieee80211_crypto_encap(ic, ni, m0); if (k == NULL) { m_freem(m0); return ENOBUFS; } /* packet header may have moved, reset our local pointer */ wh = mtod(m0, struct ieee80211_frame *); } data = &sc->tx_data[0]; desc = (struct ural_tx_desc *)data->buf; data->m = m0; data->ni = ni; if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { flags |= RAL_TX_ACK; flags |= RAL_TX_RETRY(7); dur = ural_txtime(RAL_ACK_SIZE, ural_ack_rate(ic, rate), ic->ic_flags) + RAL_SIFS; *(uint16_t *)wh->i_dur = htole16(dur); } if (bpf_peers_present(sc->sc_drvbpf)) { struct ural_tx_radiotap_header *tap = &sc->sc_txtap; tap->wt_flags = 0; tap->wt_rate = rate; tap->wt_chan_freq = htole16(ic->ic_curchan->ic_freq); tap->wt_chan_flags = htole16(ic->ic_curchan->ic_flags); tap->wt_antenna = sc->tx_ant; bpf_mtap2(sc->sc_drvbpf, tap, sc->sc_txtap_len, m0); } m_copydata(m0, 0, m0->m_pkthdr.len, data->buf + RAL_TX_DESC_SIZE); ural_setup_tx_desc(sc, desc, flags, m0->m_pkthdr.len, rate); /* align end on a 2-bytes boundary */ xferlen = (RAL_TX_DESC_SIZE + m0->m_pkthdr.len + 1) & ~1; /* * No space left in the last URB to store the extra 2 bytes, force * sending of another URB. */ if ((xferlen % 64) == 0) xferlen += 2; DPRINTFN(10, ("sending data frame len=%u rate=%u xfer len=%u\n", m0->m_pkthdr.len, rate, xferlen)); usbd_setup_xfer(data->xfer, sc->sc_tx_pipeh, data, data->buf, xferlen, USBD_FORCE_SHORT_XFER | USBD_NO_COPY, RAL_TX_TIMEOUT, ural_txeof); error = usbd_transfer(data->xfer); if (error != USBD_NORMAL_COMPLETION && error != USBD_IN_PROGRESS) return error; sc->tx_queued++; return 0; } static void ural_start(struct ifnet *ifp) { struct ural_softc *sc = ifp->if_softc; struct ieee80211com *ic = &sc->sc_ic; struct mbuf *m0; struct ether_header *eh; struct ieee80211_node *ni; for (;;) { IF_POLL(&ic->ic_mgtq, m0); if (m0 != NULL) { if (sc->tx_queued >= RAL_TX_LIST_COUNT) { ifp->if_drv_flags |= IFF_DRV_OACTIVE; break; } IF_DEQUEUE(&ic->ic_mgtq, m0); ni = (struct ieee80211_node *)m0->m_pkthdr.rcvif; m0->m_pkthdr.rcvif = NULL; if (bpf_peers_present(ic->ic_rawbpf)) bpf_mtap(ic->ic_rawbpf, m0); if (ural_tx_mgt(sc, m0, ni) != 0) break; } else { if (ic->ic_state != IEEE80211_S_RUN) break; IFQ_DRV_DEQUEUE(&ifp->if_snd, m0); if (m0 == NULL) break; if (sc->tx_queued >= RAL_TX_LIST_COUNT) { IFQ_DRV_PREPEND(&ifp->if_snd, m0); ifp->if_drv_flags |= IFF_DRV_OACTIVE; break; } if (m0->m_len < sizeof (struct ether_header) && !(m0 = m_pullup(m0, sizeof (struct ether_header)))) continue; eh = mtod(m0, struct ether_header *); ni = ieee80211_find_txnode(ic, eh->ether_dhost); if (ni == NULL) { m_freem(m0); continue; } BPF_MTAP(ifp, m0); m0 = ieee80211_encap(ic, m0, ni); if (m0 == NULL) { ieee80211_free_node(ni); continue; } if (bpf_peers_present(ic->ic_rawbpf)) bpf_mtap(ic->ic_rawbpf, m0); if (ural_tx_data(sc, m0, ni) != 0) { ieee80211_free_node(ni); ifp->if_oerrors++; break; } } sc->sc_tx_timer = 5; ifp->if_timer = 1; } } static void ural_watchdog(struct ifnet *ifp) { struct ural_softc *sc = ifp->if_softc; struct ieee80211com *ic = &sc->sc_ic; RAL_LOCK(sc); ifp->if_timer = 0; if (sc->sc_tx_timer > 0) { if (--sc->sc_tx_timer == 0) { device_printf(sc->sc_dev, "device timeout\n"); /*ural_init(sc); XXX needs a process context! */ ifp->if_oerrors++; RAL_UNLOCK(sc); return; } ifp->if_timer = 1; } ieee80211_watchdog(ic); RAL_UNLOCK(sc); } /* * This function allows for fast channel switching in monitor mode (used by * net-mgmt/kismet). In IBSS mode, we must explicitly reset the interface to * generate a new beacon frame. */ static int ural_reset(struct ifnet *ifp) { struct ural_softc *sc = ifp->if_softc; struct ieee80211com *ic = &sc->sc_ic; if (ic->ic_opmode != IEEE80211_M_MONITOR) return ENETRESET; ural_set_chan(sc, ic->ic_curchan); return 0; } static int ural_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) { struct ural_softc *sc = ifp->if_softc; struct ieee80211com *ic = &sc->sc_ic; int error = 0; RAL_LOCK(sc); switch (cmd) { case SIOCSIFFLAGS: if (ifp->if_flags & IFF_UP) { if (ifp->if_drv_flags & IFF_DRV_RUNNING) ural_update_promisc(sc); else ural_init(sc); } else { if (ifp->if_drv_flags & IFF_DRV_RUNNING) ural_stop(sc); } break; default: error = ieee80211_ioctl(ic, cmd, data); } if (error == ENETRESET) { if ((ifp->if_flags & IFF_UP) && (ifp->if_drv_flags & IFF_DRV_RUNNING) && (ic->ic_roaming != IEEE80211_ROAMING_MANUAL)) ural_init(sc); error = 0; } RAL_UNLOCK(sc); return error; } static void ural_set_testmode(struct ural_softc *sc) { usb_device_request_t req; usbd_status error; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = RAL_VENDOR_REQUEST; USETW(req.wValue, 4); USETW(req.wIndex, 1); USETW(req.wLength, 0); error = usbd_do_request(sc->sc_udev, &req, NULL); if (error != 0) { printf("%s: could not set test mode: %s\n", device_get_nameunit(sc->sc_dev), usbd_errstr(error)); } } static void ural_eeprom_read(struct ural_softc *sc, uint16_t addr, void *buf, int len) { usb_device_request_t req; usbd_status error; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = RAL_READ_EEPROM; USETW(req.wValue, 0); USETW(req.wIndex, addr); USETW(req.wLength, len); error = usbd_do_request(sc->sc_udev, &req, buf); if (error != 0) { printf("%s: could not read EEPROM: %s\n", device_get_nameunit(sc->sc_dev), usbd_errstr(error)); } } static uint16_t ural_read(struct ural_softc *sc, uint16_t reg) { usb_device_request_t req; usbd_status error; uint16_t val; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = RAL_READ_MAC; USETW(req.wValue, 0); USETW(req.wIndex, reg); USETW(req.wLength, sizeof (uint16_t)); error = usbd_do_request(sc->sc_udev, &req, &val); if (error != 0) { printf("%s: could not read MAC register: %s\n", device_get_nameunit(sc->sc_dev), usbd_errstr(error)); return 0; } return le16toh(val); } static void ural_read_multi(struct ural_softc *sc, uint16_t reg, void *buf, int len) { usb_device_request_t req; usbd_status error; req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = RAL_READ_MULTI_MAC; USETW(req.wValue, 0); USETW(req.wIndex, reg); USETW(req.wLength, len); error = usbd_do_request(sc->sc_udev, &req, buf); if (error != 0) { printf("%s: could not read MAC register: %s\n", device_get_nameunit(sc->sc_dev), usbd_errstr(error)); } } static void ural_write(struct ural_softc *sc, uint16_t reg, uint16_t val) { usb_device_request_t req; usbd_status error; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = RAL_WRITE_MAC; USETW(req.wValue, val); USETW(req.wIndex, reg); USETW(req.wLength, 0); error = usbd_do_request(sc->sc_udev, &req, NULL); if (error != 0) { printf("%s: could not write MAC register: %s\n", device_get_nameunit(sc->sc_dev), usbd_errstr(error)); } } static void ural_write_multi(struct ural_softc *sc, uint16_t reg, void *buf, int len) { usb_device_request_t req; usbd_status error; req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = RAL_WRITE_MULTI_MAC; USETW(req.wValue, 0); USETW(req.wIndex, reg); USETW(req.wLength, len); error = usbd_do_request(sc->sc_udev, &req, buf); if (error != 0) { printf("%s: could not write MAC register: %s\n", device_get_nameunit(sc->sc_dev), usbd_errstr(error)); } } static void ural_bbp_write(struct ural_softc *sc, uint8_t reg, uint8_t val) { uint16_t tmp; int ntries; for (ntries = 0; ntries < 5; ntries++) { if (!(ural_read(sc, RAL_PHY_CSR8) & RAL_BBP_BUSY)) break; } if (ntries == 5) { printf("%s: could not write to BBP\n", device_get_nameunit(sc->sc_dev)); return; } tmp = reg << 8 | val; ural_write(sc, RAL_PHY_CSR7, tmp); } static uint8_t ural_bbp_read(struct ural_softc *sc, uint8_t reg) { uint16_t val; int ntries; val = RAL_BBP_WRITE | reg << 8; ural_write(sc, RAL_PHY_CSR7, val); for (ntries = 0; ntries < 5; ntries++) { if (!(ural_read(sc, RAL_PHY_CSR8) & RAL_BBP_BUSY)) break; } if (ntries == 5) { printf("%s: could not read BBP\n", device_get_nameunit(sc->sc_dev)); return 0; } return ural_read(sc, RAL_PHY_CSR7) & 0xff; } static void ural_rf_write(struct ural_softc *sc, uint8_t reg, uint32_t val) { uint32_t tmp; int ntries; for (ntries = 0; ntries < 5; ntries++) { if (!(ural_read(sc, RAL_PHY_CSR10) & RAL_RF_LOBUSY)) break; } if (ntries == 5) { printf("%s: could not write to RF\n", device_get_nameunit(sc->sc_dev)); return; } tmp = RAL_RF_BUSY | RAL_RF_20BIT | (val & 0xfffff) << 2 | (reg & 0x3); ural_write(sc, RAL_PHY_CSR9, tmp & 0xffff); ural_write(sc, RAL_PHY_CSR10, tmp >> 16); /* remember last written value in sc */ sc->rf_regs[reg] = val; DPRINTFN(15, ("RF R[%u] <- 0x%05x\n", reg & 0x3, val & 0xfffff)); } static void ural_set_chan(struct ural_softc *sc, struct ieee80211_channel *c) { struct ieee80211com *ic = &sc->sc_ic; uint8_t power, tmp; u_int i, chan; chan = ieee80211_chan2ieee(ic, c); if (chan == 0 || chan == IEEE80211_CHAN_ANY) return; if (IEEE80211_IS_CHAN_2GHZ(c)) power = min(sc->txpow[chan - 1], 31); else power = 31; /* adjust txpower using ifconfig settings */ power -= (100 - ic->ic_txpowlimit) / 8; DPRINTFN(2, ("setting channel to %u, txpower to %u\n", chan, power)); switch (sc->rf_rev) { case RAL_RF_2522: ural_rf_write(sc, RAL_RF1, 0x00814); ural_rf_write(sc, RAL_RF2, ural_rf2522_r2[chan - 1]); ural_rf_write(sc, RAL_RF3, power << 7 | 0x00040); break; case RAL_RF_2523: ural_rf_write(sc, RAL_RF1, 0x08804); ural_rf_write(sc, RAL_RF2, ural_rf2523_r2[chan - 1]); ural_rf_write(sc, RAL_RF3, power << 7 | 0x38044); ural_rf_write(sc, RAL_RF4, (chan == 14) ? 0x00280 : 0x00286); break; case RAL_RF_2524: ural_rf_write(sc, RAL_RF1, 0x0c808); ural_rf_write(sc, RAL_RF2, ural_rf2524_r2[chan - 1]); ural_rf_write(sc, RAL_RF3, power << 7 | 0x00040); ural_rf_write(sc, RAL_RF4, (chan == 14) ? 0x00280 : 0x00286); break; case RAL_RF_2525: ural_rf_write(sc, RAL_RF1, 0x08808); ural_rf_write(sc, RAL_RF2, ural_rf2525_hi_r2[chan - 1]); ural_rf_write(sc, RAL_RF3, power << 7 | 0x18044); ural_rf_write(sc, RAL_RF4, (chan == 14) ? 0x00280 : 0x00286); ural_rf_write(sc, RAL_RF1, 0x08808); ural_rf_write(sc, RAL_RF2, ural_rf2525_r2[chan - 1]); ural_rf_write(sc, RAL_RF3, power << 7 | 0x18044); ural_rf_write(sc, RAL_RF4, (chan == 14) ? 0x00280 : 0x00286); break; case RAL_RF_2525E: ural_rf_write(sc, RAL_RF1, 0x08808); ural_rf_write(sc, RAL_RF2, ural_rf2525e_r2[chan - 1]); ural_rf_write(sc, RAL_RF3, power << 7 | 0x18044); ural_rf_write(sc, RAL_RF4, (chan == 14) ? 0x00286 : 0x00282); break; case RAL_RF_2526: ural_rf_write(sc, RAL_RF2, ural_rf2526_hi_r2[chan - 1]); ural_rf_write(sc, RAL_RF4, (chan & 1) ? 0x00386 : 0x00381); ural_rf_write(sc, RAL_RF1, 0x08804); ural_rf_write(sc, RAL_RF2, ural_rf2526_r2[chan - 1]); ural_rf_write(sc, RAL_RF3, power << 7 | 0x18044); ural_rf_write(sc, RAL_RF4, (chan & 1) ? 0x00386 : 0x00381); break; /* dual-band RF */ case RAL_RF_5222: for (i = 0; ural_rf5222[i].chan != chan; i++); ural_rf_write(sc, RAL_RF1, ural_rf5222[i].r1); ural_rf_write(sc, RAL_RF2, ural_rf5222[i].r2); ural_rf_write(sc, RAL_RF3, power << 7 | 0x00040); ural_rf_write(sc, RAL_RF4, ural_rf5222[i].r4); break; } if (ic->ic_opmode != IEEE80211_M_MONITOR && ic->ic_state != IEEE80211_S_SCAN) { /* set Japan filter bit for channel 14 */ tmp = ural_bbp_read(sc, 70); tmp &= ~RAL_JAPAN_FILTER; if (chan == 14) tmp |= RAL_JAPAN_FILTER; ural_bbp_write(sc, 70, tmp); /* clear CRC errors */ ural_read(sc, RAL_STA_CSR0); DELAY(10000); ural_disable_rf_tune(sc); } } /* * Disable RF auto-tuning. */ static void ural_disable_rf_tune(struct ural_softc *sc) { uint32_t tmp; if (sc->rf_rev != RAL_RF_2523) { tmp = sc->rf_regs[RAL_RF1] & ~RAL_RF1_AUTOTUNE; ural_rf_write(sc, RAL_RF1, tmp); } tmp = sc->rf_regs[RAL_RF3] & ~RAL_RF3_AUTOTUNE; ural_rf_write(sc, RAL_RF3, tmp); DPRINTFN(2, ("disabling RF autotune\n")); } /* * Refer to IEEE Std 802.11-1999 pp. 123 for more information on TSF * synchronization. */ static void ural_enable_tsf_sync(struct ural_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; uint16_t logcwmin, preload, tmp; /* first, disable TSF synchronization */ ural_write(sc, RAL_TXRX_CSR19, 0); tmp = (16 * ic->ic_bss->ni_intval) << 4; ural_write(sc, RAL_TXRX_CSR18, tmp); logcwmin = (ic->ic_opmode == IEEE80211_M_IBSS) ? 2 : 0; preload = (ic->ic_opmode == IEEE80211_M_IBSS) ? 320 : 6; tmp = logcwmin << 12 | preload; ural_write(sc, RAL_TXRX_CSR20, tmp); /* finally, enable TSF synchronization */ tmp = RAL_ENABLE_TSF | RAL_ENABLE_TBCN; if (ic->ic_opmode == IEEE80211_M_STA) tmp |= RAL_ENABLE_TSF_SYNC(1); else tmp |= RAL_ENABLE_TSF_SYNC(2) | RAL_ENABLE_BEACON_GENERATOR; ural_write(sc, RAL_TXRX_CSR19, tmp); DPRINTF(("enabling TSF synchronization\n")); } static void ural_update_slot(struct ifnet *ifp) { struct ural_softc *sc = ifp->if_softc; struct ieee80211com *ic = &sc->sc_ic; uint16_t slottime, sifs, eifs; slottime = (ic->ic_flags & IEEE80211_F_SHSLOT) ? 9 : 20; /* * These settings may sound a bit inconsistent but this is what the * reference driver does. */ if (ic->ic_curmode == IEEE80211_MODE_11B) { sifs = 16 - RAL_RXTX_TURNAROUND; eifs = 364; } else { sifs = 10 - RAL_RXTX_TURNAROUND; eifs = 64; } ural_write(sc, RAL_MAC_CSR10, slottime); ural_write(sc, RAL_MAC_CSR11, sifs); ural_write(sc, RAL_MAC_CSR12, eifs); } static void ural_set_txpreamble(struct ural_softc *sc) { uint16_t tmp; tmp = ural_read(sc, RAL_TXRX_CSR10); tmp &= ~RAL_SHORT_PREAMBLE; if (sc->sc_ic.ic_flags & IEEE80211_F_SHPREAMBLE) tmp |= RAL_SHORT_PREAMBLE; ural_write(sc, RAL_TXRX_CSR10, tmp); } static void ural_set_basicrates(struct ural_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; /* update basic rate set */ if (ic->ic_curmode == IEEE80211_MODE_11B) { /* 11b basic rates: 1, 2Mbps */ ural_write(sc, RAL_TXRX_CSR11, 0x3); } else if (IEEE80211_IS_CHAN_5GHZ(ic->ic_bss->ni_chan)) { /* 11a basic rates: 6, 12, 24Mbps */ ural_write(sc, RAL_TXRX_CSR11, 0x150); } else { /* 11g basic rates: 1, 2, 5.5, 11, 6, 12, 24Mbps */ ural_write(sc, RAL_TXRX_CSR11, 0x15f); } } static void ural_set_bssid(struct ural_softc *sc, uint8_t *bssid) { uint16_t tmp; tmp = bssid[0] | bssid[1] << 8; ural_write(sc, RAL_MAC_CSR5, tmp); tmp = bssid[2] | bssid[3] << 8; ural_write(sc, RAL_MAC_CSR6, tmp); tmp = bssid[4] | bssid[5] << 8; ural_write(sc, RAL_MAC_CSR7, tmp); DPRINTF(("setting BSSID to %6D\n", bssid, ":")); } static void ural_set_macaddr(struct ural_softc *sc, uint8_t *addr) { uint16_t tmp; tmp = addr[0] | addr[1] << 8; ural_write(sc, RAL_MAC_CSR2, tmp); tmp = addr[2] | addr[3] << 8; ural_write(sc, RAL_MAC_CSR3, tmp); tmp = addr[4] | addr[5] << 8; ural_write(sc, RAL_MAC_CSR4, tmp); DPRINTF(("setting MAC address to %6D\n", addr, ":")); } static void ural_update_promisc(struct ural_softc *sc) { struct ifnet *ifp = sc->sc_ic.ic_ifp; uint32_t tmp; tmp = ural_read(sc, RAL_TXRX_CSR2); tmp &= ~RAL_DROP_NOT_TO_ME; if (!(ifp->if_flags & IFF_PROMISC)) tmp |= RAL_DROP_NOT_TO_ME; ural_write(sc, RAL_TXRX_CSR2, tmp); DPRINTF(("%s promiscuous mode\n", (ifp->if_flags & IFF_PROMISC) ? "entering" : "leaving")); } static const char * ural_get_rf(int rev) { switch (rev) { case RAL_RF_2522: return "RT2522"; case RAL_RF_2523: return "RT2523"; case RAL_RF_2524: return "RT2524"; case RAL_RF_2525: return "RT2525"; case RAL_RF_2525E: return "RT2525e"; case RAL_RF_2526: return "RT2526"; case RAL_RF_5222: return "RT5222"; default: return "unknown"; } } static void ural_read_eeprom(struct ural_softc *sc) { struct ieee80211com *ic = &sc->sc_ic; uint16_t val; ural_eeprom_read(sc, RAL_EEPROM_CONFIG0, &val, 2); val = le16toh(val); sc->rf_rev = (val >> 11) & 0x7; sc->hw_radio = (val >> 10) & 0x1; sc->led_mode = (val >> 6) & 0x7; sc->rx_ant = (val >> 4) & 0x3; sc->tx_ant = (val >> 2) & 0x3; sc->nb_ant = val & 0x3; /* read MAC address */ ural_eeprom_read(sc, RAL_EEPROM_ADDRESS, ic->ic_myaddr, 6); /* read default values for BBP registers */ ural_eeprom_read(sc, RAL_EEPROM_BBP_BASE, sc->bbp_prom, 2 * 16); /* read Tx power for all b/g channels */ ural_eeprom_read(sc, RAL_EEPROM_TXPOWER, sc->txpow, 14); } static int ural_bbp_init(struct ural_softc *sc) { #define N(a) (sizeof (a) / sizeof ((a)[0])) int i, ntries; /* wait for BBP to be ready */ for (ntries = 0; ntries < 100; ntries++) { if (ural_bbp_read(sc, RAL_BBP_VERSION) != 0) break; DELAY(1000); } if (ntries == 100) { device_printf(sc->sc_dev, "timeout waiting for BBP\n"); return EIO; } /* initialize BBP registers to default values */ for (i = 0; i < N(ural_def_bbp); i++) ural_bbp_write(sc, ural_def_bbp[i].reg, ural_def_bbp[i].val); #if 0 /* initialize BBP registers to values stored in EEPROM */ for (i = 0; i < 16; i++) { if (sc->bbp_prom[i].reg == 0xff) continue; ural_bbp_write(sc, sc->bbp_prom[i].reg, sc->bbp_prom[i].val); } #endif return 0; #undef N } static void ural_set_txantenna(struct ural_softc *sc, int antenna) { uint16_t tmp; uint8_t tx; tx = ural_bbp_read(sc, RAL_BBP_TX) & ~RAL_BBP_ANTMASK; if (antenna == 1) tx |= RAL_BBP_ANTA; else if (antenna == 2) tx |= RAL_BBP_ANTB; else tx |= RAL_BBP_DIVERSITY; /* need to force I/Q flip for RF 2525e, 2526 and 5222 */ if (sc->rf_rev == RAL_RF_2525E || sc->rf_rev == RAL_RF_2526 || sc->rf_rev == RAL_RF_5222) tx |= RAL_BBP_FLIPIQ; ural_bbp_write(sc, RAL_BBP_TX, tx); /* update values in PHY_CSR5 and PHY_CSR6 */ tmp = ural_read(sc, RAL_PHY_CSR5) & ~0x7; ural_write(sc, RAL_PHY_CSR5, tmp | (tx & 0x7)); tmp = ural_read(sc, RAL_PHY_CSR6) & ~0x7; ural_write(sc, RAL_PHY_CSR6, tmp | (tx & 0x7)); } static void ural_set_rxantenna(struct ural_softc *sc, int antenna) { uint8_t rx; rx = ural_bbp_read(sc, RAL_BBP_RX) & ~RAL_BBP_ANTMASK; if (antenna == 1) rx |= RAL_BBP_ANTA; else if (antenna == 2) rx |= RAL_BBP_ANTB; else rx |= RAL_BBP_DIVERSITY; /* need to force no I/Q flip for RF 2525e and 2526 */ if (sc->rf_rev == RAL_RF_2525E || sc->rf_rev == RAL_RF_2526) rx &= ~RAL_BBP_FLIPIQ; ural_bbp_write(sc, RAL_BBP_RX, rx); } static void ural_init(void *priv) { #define N(a) (sizeof (a) / sizeof ((a)[0])) struct ural_softc *sc = priv; struct ieee80211com *ic = &sc->sc_ic; struct ifnet *ifp = ic->ic_ifp; struct ieee80211_key *wk; struct ural_rx_data *data; uint16_t tmp; usbd_status error; int i, ntries; ural_set_testmode(sc); ural_write(sc, 0x308, 0x00f0); /* XXX magic */ ural_stop(sc); /* initialize MAC registers to default values */ for (i = 0; i < N(ural_def_mac); i++) ural_write(sc, ural_def_mac[i].reg, ural_def_mac[i].val); /* wait for BBP and RF to wake up (this can take a long time!) */ for (ntries = 0; ntries < 100; ntries++) { tmp = ural_read(sc, RAL_MAC_CSR17); if ((tmp & (RAL_BBP_AWAKE | RAL_RF_AWAKE)) == (RAL_BBP_AWAKE | RAL_RF_AWAKE)) break; DELAY(1000); } if (ntries == 100) { printf("%s: timeout waiting for BBP/RF to wakeup\n", device_get_nameunit(sc->sc_dev)); goto fail; } /* we're ready! */ ural_write(sc, RAL_MAC_CSR1, RAL_HOST_READY); /* set basic rate set (will be updated later) */ ural_write(sc, RAL_TXRX_CSR11, 0x15f); if (ural_bbp_init(sc) != 0) goto fail; /* set default BSS channel */ ural_set_chan(sc, ic->ic_curchan); /* clear statistic registers (STA_CSR0 to STA_CSR10) */ ural_read_multi(sc, RAL_STA_CSR0, sc->sta, sizeof sc->sta); ural_set_txantenna(sc, sc->tx_ant); ural_set_rxantenna(sc, sc->rx_ant); IEEE80211_ADDR_COPY(ic->ic_myaddr, IF_LLADDR(ifp)); ural_set_macaddr(sc, ic->ic_myaddr); /* * Copy WEP keys into adapter's memory (SEC_CSR0 to SEC_CSR31). */ for (i = 0; i < IEEE80211_WEP_NKID; i++) { wk = &ic->ic_crypto.cs_nw_keys[i]; ural_write_multi(sc, wk->wk_keyix * IEEE80211_KEYBUF_SIZE + RAL_SEC_CSR0, wk->wk_key, IEEE80211_KEYBUF_SIZE); } /* * Allocate xfer for AMRR statistics requests. */ sc->amrr_xfer = usbd_alloc_xfer(sc->sc_udev); if (sc->amrr_xfer == NULL) { printf("%s: could not allocate AMRR xfer\n", device_get_nameunit(sc->sc_dev)); goto fail; } /* * Open Tx and Rx USB bulk pipes. */ error = usbd_open_pipe(sc->sc_iface, sc->sc_tx_no, USBD_EXCLUSIVE_USE, &sc->sc_tx_pipeh); if (error != 0) { printf("%s: could not open Tx pipe: %s\n", device_get_nameunit(sc->sc_dev), usbd_errstr(error)); goto fail; } error = usbd_open_pipe(sc->sc_iface, sc->sc_rx_no, USBD_EXCLUSIVE_USE, &sc->sc_rx_pipeh); if (error != 0) { printf("%s: could not open Rx pipe: %s\n", device_get_nameunit(sc->sc_dev), usbd_errstr(error)); goto fail; } /* * Allocate Tx and Rx xfer queues. */ error = ural_alloc_tx_list(sc); if (error != 0) { printf("%s: could not allocate Tx list\n", device_get_nameunit(sc->sc_dev)); goto fail; } error = ural_alloc_rx_list(sc); if (error != 0) { printf("%s: could not allocate Rx list\n", device_get_nameunit(sc->sc_dev)); goto fail; } /* * Start up the receive pipe. */ for (i = 0; i < RAL_RX_LIST_COUNT; i++) { data = &sc->rx_data[i]; usbd_setup_xfer(data->xfer, sc->sc_rx_pipeh, data, data->buf, MCLBYTES, USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT, ural_rxeof); usbd_transfer(data->xfer); } /* kick Rx */ tmp = RAL_DROP_PHY | RAL_DROP_CRC; if (ic->ic_opmode != IEEE80211_M_MONITOR) { tmp |= RAL_DROP_CTL | RAL_DROP_BAD_VERSION; if (ic->ic_opmode != IEEE80211_M_HOSTAP) tmp |= RAL_DROP_TODS; if (!(ifp->if_flags & IFF_PROMISC)) tmp |= RAL_DROP_NOT_TO_ME; } ural_write(sc, RAL_TXRX_CSR2, tmp); ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; ifp->if_drv_flags |= IFF_DRV_RUNNING; if (ic->ic_opmode != IEEE80211_M_MONITOR) { if (ic->ic_roaming != IEEE80211_ROAMING_MANUAL) ieee80211_new_state(ic, IEEE80211_S_SCAN, -1); } else ieee80211_new_state(ic, IEEE80211_S_RUN, -1); return; fail: ural_stop(sc); #undef N } static void ural_stop(void *priv) { struct ural_softc *sc = priv; struct ieee80211com *ic = &sc->sc_ic; struct ifnet *ifp = ic->ic_ifp; ieee80211_new_state(ic, IEEE80211_S_INIT, -1); sc->sc_tx_timer = 0; ifp->if_timer = 0; ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); /* disable Rx */ ural_write(sc, RAL_TXRX_CSR2, RAL_DISABLE_RX); /* reset ASIC and BBP (but won't reset MAC registers!) */ ural_write(sc, RAL_MAC_CSR1, RAL_RESET_ASIC | RAL_RESET_BBP); ural_write(sc, RAL_MAC_CSR1, 0); if (sc->amrr_xfer != NULL) { usbd_free_xfer(sc->amrr_xfer); sc->amrr_xfer = NULL; } if (sc->sc_rx_pipeh != NULL) { usbd_abort_pipe(sc->sc_rx_pipeh); usbd_close_pipe(sc->sc_rx_pipeh); sc->sc_rx_pipeh = NULL; } if (sc->sc_tx_pipeh != NULL) { usbd_abort_pipe(sc->sc_tx_pipeh); usbd_close_pipe(sc->sc_tx_pipeh); sc->sc_tx_pipeh = NULL; } ural_free_rx_list(sc); ural_free_tx_list(sc); } static int ural_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, const struct ieee80211_bpf_params *params) { struct ieee80211com *ic = ni->ni_ic; struct ifnet *ifp = ic->ic_ifp; struct ural_softc *sc = ifp->if_softc; /* prevent management frames from being sent if we're not ready */ if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) return ENETDOWN; if (sc->tx_queued >= RAL_TX_LIST_COUNT) { ifp->if_drv_flags |= IFF_DRV_OACTIVE; return EIO; } if (bpf_peers_present(ic->ic_rawbpf)) bpf_mtap(ic->ic_rawbpf, m); ifp->if_opackets++; if (params == NULL) { /* * Legacy path; interpret frame contents to decide * precisely how to send the frame. */ if (ural_tx_mgt(sc, m, ni) != 0) goto bad; } else { /* * Caller supplied explicit parameters to use in * sending the frame. */ if (ural_tx_raw(sc, m, ni, params) != 0) goto bad; } sc->sc_tx_timer = 5; ifp->if_timer = 1; return 0; bad: ifp->if_oerrors++; ieee80211_free_node(ni); return EIO; /* XXX */ } #define URAL_AMRR_MIN_SUCCESS_THRESHOLD 1 #define URAL_AMRR_MAX_SUCCESS_THRESHOLD 10 static void ural_amrr_start(struct ural_softc *sc, struct ieee80211_node *ni) { struct ural_amrr *amrr = &sc->amrr; int i; /* clear statistic registers (STA_CSR0 to STA_CSR10) */ ural_read_multi(sc, RAL_STA_CSR0, sc->sta, sizeof sc->sta); amrr->success = 0; amrr->recovery = 0; amrr->txcnt = amrr->retrycnt = 0; amrr->success_threshold = URAL_AMRR_MIN_SUCCESS_THRESHOLD; /* set rate to some reasonable initial value */ for (i = ni->ni_rates.rs_nrates - 1; i > 0 && (ni->ni_rates.rs_rates[i] & IEEE80211_RATE_VAL) > 72; i--); ni->ni_txrate = i; callout_reset(&sc->amrr_ch, hz, ural_amrr_timeout, sc); } static void ural_amrr_timeout(void *arg) { struct ural_softc *sc = (struct ural_softc *)arg; usb_device_request_t req; int s; s = splusb(); /* * Asynchronously read statistic registers (cleared by read). */ req.bmRequestType = UT_READ_VENDOR_DEVICE; req.bRequest = RAL_READ_MULTI_MAC; USETW(req.wValue, 0); USETW(req.wIndex, RAL_STA_CSR0); USETW(req.wLength, sizeof sc->sta); usbd_setup_default_xfer(sc->amrr_xfer, sc->sc_udev, sc, USBD_DEFAULT_TIMEOUT, &req, sc->sta, sizeof sc->sta, 0, ural_amrr_update); (void)usbd_transfer(sc->amrr_xfer); splx(s); } static void ural_amrr_update(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) { struct ural_softc *sc = (struct ural_softc *)priv; struct ural_amrr *amrr = &sc->amrr; struct ifnet *ifp = sc->sc_ic.ic_ifp; if (status != USBD_NORMAL_COMPLETION) { device_printf(sc->sc_dev, "could not retrieve Tx statistics - " "cancelling automatic rate control\n"); return; } /* count TX retry-fail as Tx errors */ ifp->if_oerrors += sc->sta[9]; amrr->retrycnt = sc->sta[7] + /* TX one-retry ok count */ sc->sta[8] + /* TX more-retry ok count */ sc->sta[9]; /* TX retry-fail count */ amrr->txcnt = amrr->retrycnt + sc->sta[6]; /* TX no-retry ok count */ ural_ratectl(amrr, sc->sc_ic.ic_bss); callout_reset(&sc->amrr_ch, hz, ural_amrr_timeout, sc); } /*- * Naive implementation of the Adaptive Multi Rate Retry algorithm: * "IEEE 802.11 Rate Adaptation: A Practical Approach" * Mathieu Lacage, Hossein Manshaei, Thierry Turletti * INRIA Sophia - Projet Planete * http://www-sop.inria.fr/rapports/sophia/RR-5208.html * * This algorithm is particularly well suited for ural since it does not * require per-frame retry statistics. Note however that since h/w does * not provide per-frame stats, we can't do per-node rate adaptation and * thus automatic rate adaptation is only enabled in STA operating mode. */ #define is_success(amrr) \ ((amrr)->retrycnt < (amrr)->txcnt / 10) #define is_failure(amrr) \ ((amrr)->retrycnt > (amrr)->txcnt / 3) #define is_enough(amrr) \ ((amrr)->txcnt > 10) #define is_min_rate(ni) \ ((ni)->ni_txrate == 0) #define is_max_rate(ni) \ ((ni)->ni_txrate == (ni)->ni_rates.rs_nrates - 1) #define increase_rate(ni) \ ((ni)->ni_txrate++) #define decrease_rate(ni) \ ((ni)->ni_txrate--) #define reset_cnt(amrr) \ do { (amrr)->txcnt = (amrr)->retrycnt = 0; } while (0) static void ural_ratectl(struct ural_amrr *amrr, struct ieee80211_node *ni) { int need_change = 0; if (is_success(amrr) && is_enough(amrr)) { amrr->success++; if (amrr->success >= amrr->success_threshold && !is_max_rate(ni)) { amrr->recovery = 1; amrr->success = 0; increase_rate(ni); need_change = 1; } else { amrr->recovery = 0; } } else if (is_failure(amrr)) { amrr->success = 0; if (!is_min_rate(ni)) { if (amrr->recovery) { amrr->success_threshold *= 2; if (amrr->success_threshold > URAL_AMRR_MAX_SUCCESS_THRESHOLD) amrr->success_threshold = URAL_AMRR_MAX_SUCCESS_THRESHOLD; } else { amrr->success_threshold = URAL_AMRR_MIN_SUCCESS_THRESHOLD; } decrease_rate(ni); need_change = 1; } amrr->recovery = 0; /* original paper was incorrect */ } if (is_enough(amrr) || need_change) reset_cnt(amrr); } DRIVER_MODULE(ural, uhub, ural_driver, ural_devclass, usbd_driver_load, 0); diff --git a/sys/dev/usb/ohci.c b/sys/dev/usb/ohci.c index 09c2ad3cbe20..60262796af45 100644 --- a/sys/dev/usb/ohci.c +++ b/sys/dev/usb/ohci.c @@ -1,3683 +1,3684 @@ /* $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 #if defined(__NetBSD__) || defined(__OpenBSD__) #include #include #elif defined(__FreeBSD__) #include #include #include #if defined(DIAGNOSTIC) && defined(__i386__) && defined(__FreeBSD__) #include #endif #endif #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__FreeBSD__) #define delay(d) DELAY(d) #endif #if defined(__OpenBSD__) struct cfdriver ohci_cd = { NULL, "ohci", DV_DULL }; #endif #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"); #ifndef __NetBSD__ #define bitmask_snprintf(q,f,b,l) snprintf((b), (l), "%b", (q), (f)) #endif #else #define DPRINTF(x) #define DPRINTFN(n,x) #endif /* * The OHCI controller is little endian, so on big endian machines * the data strored in memory needs to be swapped. */ #if defined(__OpenBSD__) #if BYTE_ORDER == BIG_ENDIAN #define htole32(x) (bswap32(x)) #define le32toh(x) (bswap32(x)) #else #define htole32(x) (x) #define le32toh(x) (x) #endif #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, }; #if defined(__NetBSD__) || defined(__OpenBSD__) int ohci_activate(device_t self, enum devact act) { struct ohci_softc *sc = (struct ohci_softc *)self; int rv = 0; switch (act) { case DVACT_ACTIVATE: return (EOPNOTSUPP); case DVACT_DEACTIVATE: if (sc->sc_child != NULL) rv = config_deactivate(sc->sc_child); sc->sc_dying = 1; break; } return (rv); } #endif int ohci_detach(struct ohci_softc *sc, int flags) { int i, rv = 0; #if defined(__NetBSD__) || defined(__OpenBSD__) if (sc->sc_child != NULL) rv = config_detach(sc->sc_child, flags); if (rv != 0) return (rv); #else sc->sc_dying = 1; #endif usb_uncallout(sc->sc_tmo_rhsc, ohci_rhsc_enable, sc); #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")); #if defined(__OpenBSD__) printf(","); #else printf("%s:", device_get_nameunit(sc->sc_bus.bdev)); #endif 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_control = sc->sc_intre = 0; sc->sc_powerhook = powerhook_establish(ohci_power, sc); sc->sc_shutdownhook = shutdownhook_establish(ohci_shutdown, sc); #endif usb_callout_init(sc->sc_tmo_rhsc); 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); 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); 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); 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); } 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_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); 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); 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); } #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/uhci.c b/sys/dev/usb/uhci.c index 577b3c867e86..69380d9b2f0b 100644 --- a/sys/dev/usb/uhci.c +++ b/sys/dev/usb/uhci.c @@ -1,3761 +1,3762 @@ /* $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 #if defined(__NetBSD__) || defined(__OpenBSD__) #include #include #elif defined(__FreeBSD__) #include #include #include #if defined(DIAGNOSTIC) && defined(__i386__) #include #endif #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 */ #if defined(__FreeBSD__) #define delay(d) DELAY(d) #endif #define MS_TO_TICKS(ms) ((ms) * hz / 1000) #if defined(__OpenBSD__) struct cfdriver uhci_cd = { NULL, "uhci", DV_DULL }; #endif #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"); #ifndef __NetBSD__ #define bitmask_snprintf(q,f,b,l) snprintf((b), (l), "%b", (q), (f)) #endif #else #define DPRINTF(x) #define DPRINTFN(n,x) #endif /* * The UHCI controller is little endian, so on big endian machines * the data strored in memory needs to be swapped. */ #if defined(__OpenBSD__) #if BYTE_ORDER == BIG_ENDIAN #define htole32(x) (bswap32(x)) #define le32toh(x) (bswap32(x)) #else #define htole32(x) (x) #define le32toh(x) (x) #endif #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); /* 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... */ } #if defined(__NetBSD__) || defined(__OpenBSD__) int uhci_activate(device_t self, enum devact act) { struct uhci_softc *sc = (struct uhci_softc *)self; int rv = 0; switch (act) { case DVACT_ACTIVATE: return (EOPNOTSUPP); case DVACT_DEACTIVATE: if (sc->sc_child != NULL) rv = config_deactivate(sc->sc_child); break; } return (rv); } #endif int uhci_detach(struct uhci_softc *sc, int flags) { usbd_xfer_handle xfer; int rv = 0; #if defined(__NetBSD__) || defined(__OpenBSD__) if (sc->sc_child != NULL) rv = config_detach(sc->sc_child, flags); if (rv != 0) return (rv); #else sc->sc_dying = 1; #endif 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); sc->sc_bus.use_polling++; uhci_run(sc, 0); /* stop the controller */ /* 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); #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); 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); 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_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), 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); 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); 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), 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); 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); 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); sc->sc_intr_xfer = NULL; DPRINTF(("uhci_root_intr_close\n")); } diff --git a/sys/dev/usb/usb.c b/sys/dev/usb/usb.c index 1930108d4959..0267b63bfbd8 100644 --- a/sys/dev/usb/usb.c +++ b/sys/dev/usb/usb.c @@ -1,994 +1,1022 @@ /* $NetBSD: usb.c,v 1.68 2002/02/20 20:30:12 christos Exp $ */ /* Also already merged from NetBSD: * $NetBSD: usb.c,v 1.70 2002/05/09 21:54:32 augustss Exp $ * $NetBSD: usb.c,v 1.71 2002/06/01 23:51:04 lukem Exp $ * $NetBSD: usb.c,v 1.73 2002/09/23 05:51:19 simonb Exp $ * $NetBSD: usb.c,v 1.80 2003/11/07 17:03:25 wiz 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 specifications and other documentation can be found at * http://www.usb.org/developers/docs/ and * http://www.usb.org/developers/devclass_docs/ */ #include #include #include #include #include #if __FreeBSD_version >= 500000 #include #endif #if defined(__NetBSD__) || defined(__OpenBSD__) #include #elif defined(__FreeBSD__) #include #include #include #include #include #include #endif #include #include #include #include #if __FreeBSD_version >= 500014 #include #else #include #endif #include #include #include #include #include #include #define USBUNIT(d) (minor(d)) /* usb_discover device nodes, kthread */ #define USB_DEV_MINOR 255 /* event queue device */ #if defined(__FreeBSD__) MALLOC_DEFINE(M_USB, "USB", "USB"); MALLOC_DEFINE(M_USBDEV, "USBdev", "USB device"); MALLOC_DEFINE(M_USBHC, "USBHC", "USB host controller"); #include "usb_if.h" #endif /* defined(__FreeBSD__) */ #include #include #include /* Define this unconditionally in case a kernel module is loaded that * has been compiled with debugging options. */ SYSCTL_NODE(_hw, OID_AUTO, usb, CTLFLAG_RW, 0, "USB debugging"); #ifdef USB_DEBUG #define DPRINTF(x) if (usbdebug) logprintf x #define DPRINTFN(n,x) if (usbdebug>(n)) logprintf x int usbdebug = 0; SYSCTL_INT(_hw_usb, OID_AUTO, debug, CTLFLAG_RW, &usbdebug, 0, "usb debug level"); /* * 0 - do usual exploration * 1 - do not use timeout exploration * >1 - do no exploration */ int usb_noexplore = 0; #else #define DPRINTF(x) #define DPRINTFN(n,x) #endif struct usb_softc { device_t sc_dev; /* base device */ #ifdef __FreeBSD__ struct cdev *sc_usbdev; /* /dev/usbN device */ TAILQ_ENTRY(usb_softc) sc_coldexplist; /* cold needs-explore list */ #endif usbd_bus_handle sc_bus; /* USB controller */ struct usbd_port sc_port; /* dummy port for root hub */ struct proc *sc_event_thread; char sc_dying; }; -TAILQ_HEAD(, usb_task) usb_all_tasks; +struct usb_taskq { + TAILQ_HEAD(, usb_task) tasks; + struct proc *task_thread_proc; + const char *name; + int taskcreated; /* task thread exists. */ +}; + +static struct usb_taskq usb_taskq[USB_NUM_TASKQS]; #if defined(__NetBSD__) || defined(__OpenBSD__) cdev_decl(usb); #elif defined(__FreeBSD__) d_open_t usbopen; d_close_t usbclose; d_read_t usbread; d_ioctl_t usbioctl; d_poll_t usbpoll; struct cdevsw usb_cdevsw = { .d_version = D_VERSION, .d_flags = D_NEEDGIANT, .d_open = usbopen, .d_close = usbclose, .d_read = usbread, .d_ioctl = usbioctl, .d_poll = usbpoll, .d_name = "usb", #if __FreeBSD_version < 500014 .d_bmaj = -1 #endif }; #endif static void usb_discover(void *); #ifdef __FreeBSD__ static bus_child_detached_t usb_child_detached; #endif static void usb_create_event_thread(void *); static void usb_event_thread(void *); static void usb_task_thread(void *); -static struct proc *usb_task_thread_proc = NULL; #ifdef __FreeBSD__ static struct cdev *usb_dev; /* The /dev/usb device. */ static int usb_ndevs; /* Number of /dev/usbN devices. */ -static int usb_taskcreated; /* USB task thread exists. */ /* Busses to explore at the end of boot-time device configuration. */ static TAILQ_HEAD(, usb_softc) usb_coldexplist = TAILQ_HEAD_INITIALIZER(usb_coldexplist); #endif #define USB_MAX_EVENTS 100 struct usb_event_q { struct usb_event ue; TAILQ_ENTRY(usb_event_q) next; }; static TAILQ_HEAD(, usb_event_q) usb_events = TAILQ_HEAD_INITIALIZER(usb_events); static int usb_nevents = 0; static struct selinfo usb_selevent; static struct proc *usb_async_proc; /* process that wants USB SIGIO */ static int usb_dev_open = 0; static void usb_add_event(int, struct usb_event *); static int usb_get_next_event(struct usb_event *); static const char *usbrev_str[] = USBREV_STR; USB_DECLARE_DRIVER_INIT(usb, DEVMETHOD(bus_child_detached, usb_child_detached), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown) ); #if defined(__FreeBSD__) MODULE_VERSION(usb, 1); #endif USB_MATCH(usb) { DPRINTF(("usbd_match\n")); return (UMATCH_GENERIC); } USB_ATTACH(usb) { #if defined(__NetBSD__) || defined(__OpenBSD__) struct usb_softc *sc = (struct usb_softc *)self; #elif defined(__FreeBSD__) struct usb_softc *sc = device_get_softc(self); void *aux = device_get_ivars(self); #endif usbd_device_handle dev; usbd_status err; int usbrev; int speed; struct usb_event ue; sc->sc_dev = self; DPRINTF(("usbd_attach\n")); usbd_init(); sc->sc_bus = aux; sc->sc_bus->usbctl = sc; sc->sc_port.power = USB_MAX_POWER; #if defined(__FreeBSD__) printf("%s", device_get_nameunit(sc->sc_dev)); #endif usbrev = sc->sc_bus->usbrev; printf(": USB revision %s", usbrev_str[usbrev]); switch (usbrev) { case USBREV_1_0: case USBREV_1_1: speed = USB_SPEED_FULL; break; case USBREV_2_0: speed = USB_SPEED_HIGH; break; default: printf(", not supported\n"); sc->sc_dying = 1; USB_ATTACH_ERROR_RETURN; } printf("\n"); /* Make sure not to use tsleep() if we are cold booting. */ if (cold) sc->sc_bus->use_polling++; ue.u.ue_ctrlr.ue_bus = device_get_unit(sc->sc_dev); usb_add_event(USB_EVENT_CTRLR_ATTACH, &ue); #ifdef USB_USE_SOFTINTR #ifdef __HAVE_GENERIC_SOFT_INTERRUPTS /* XXX we should have our own level */ sc->sc_bus->soft = softintr_establish(IPL_SOFTNET, sc->sc_bus->methods->soft_intr, sc->sc_bus); if (sc->sc_bus->soft == NULL) { printf("%s: can't register softintr\n", device_get_nameunit(sc->sc_dev)); sc->sc_dying = 1; USB_ATTACH_ERROR_RETURN; } #else usb_callout_init(sc->sc_bus->softi); #endif #endif err = usbd_new_device(USBDEV(sc->sc_dev), sc->sc_bus, 0, speed, 0, &sc->sc_port); if (!err) { dev = sc->sc_port.device; if (dev->hub == NULL) { sc->sc_dying = 1; printf("%s: root device is not a hub\n", device_get_nameunit(sc->sc_dev)); USB_ATTACH_ERROR_RETURN; } sc->sc_bus->root_hub = dev; #if 1 /* * Turning this code off will delay attachment of USB devices * until the USB event thread is running, which means that * the keyboard will not work until after cold boot. */ #if defined(__FreeBSD__) if (cold) { /* Explore high-speed busses before others. */ if (speed == USB_SPEED_HIGH) dev->hub->explore(sc->sc_bus->root_hub); else TAILQ_INSERT_TAIL(&usb_coldexplist, sc, sc_coldexplist); } #else if (cold && (sc->sc_dev.dv_cfdata->cf_flags & 1)) dev->hub->explore(sc->sc_bus->root_hub); #endif #endif } else { printf("%s: root hub problem, error=%d\n", device_get_nameunit(sc->sc_dev), err); sc->sc_dying = 1; } if (cold) sc->sc_bus->use_polling--; config_pending_incr(); #if defined(__NetBSD__) || defined(__OpenBSD__) usb_kthread_create(usb_create_event_thread, sc); #endif #if defined(__FreeBSD__) usb_create_event_thread(sc); /* The per controller devices (used for usb_discover) */ /* XXX This is redundant now, but old usbd's will want it */ sc->sc_usbdev = make_dev(&usb_cdevsw, device_get_unit(self), UID_ROOT, GID_OPERATOR, 0660, "usb%d", device_get_unit(self)); if (usb_ndevs++ == 0) { /* The device spitting out events */ usb_dev = make_dev(&usb_cdevsw, USB_DEV_MINOR, UID_ROOT, GID_OPERATOR, 0660, "usb"); } #endif USB_ATTACH_SUCCESS_RETURN; } +static const char *taskq_names[] = USB_TASKQ_NAMES; + void usb_create_event_thread(void *arg) { struct usb_softc *sc = arg; + struct usb_taskq *taskq; + int i; if (usb_kthread_create1(usb_event_thread, sc, &sc->sc_event_thread, "%s", device_get_nameunit(sc->sc_dev))) { printf("%s: unable to create event thread for\n", device_get_nameunit(sc->sc_dev)); panic("usb_create_event_thread"); } - if (usb_taskcreated == 0) { - usb_taskcreated = 1; - TAILQ_INIT(&usb_all_tasks); - if (usb_kthread_create2(usb_task_thread, NULL, - &usb_task_thread_proc, "usbtask")) { - printf("unable to create task thread\n"); - panic("usb_create_event_thread task"); + for (i = 0; i < USB_NUM_TASKQS; i++) { + taskq = &usb_taskq[i]; + + if (taskq->taskcreated == 0) { + taskq->taskcreated = 1; + taskq->name = taskq_names[i]; + TAILQ_INIT(&taskq->tasks); + if (usb_kthread_create2(usb_task_thread, taskq, + &taskq->task_thread_proc, taskq->name)) { + printf("unable to create task thread\n"); + panic("usb_create_event_thread task"); + } } } } /* * Add a task to be performed by the task thread. This function can be * called from any context and the task will be executed in a process * context ASAP. */ void -usb_add_task(usbd_device_handle dev, struct usb_task *task) +usb_add_task(usbd_device_handle dev, struct usb_task *task, int queue) { + struct usb_taskq *taskq; int s; s = splusb(); - if (!task->onqueue) { + taskq = &usb_taskq[queue]; + if (task->queue == -1) { DPRINTFN(2,("usb_add_task: task=%p\n", task)); - TAILQ_INSERT_TAIL(&usb_all_tasks, task, next); - task->onqueue = 1; + TAILQ_INSERT_TAIL(&taskq->tasks, task, next); + task->queue = queue; } else { DPRINTFN(3,("usb_add_task: task=%p on q\n", task)); } - wakeup(&usb_all_tasks); + wakeup(&taskq->tasks); splx(s); } void usb_rem_task(usbd_device_handle dev, struct usb_task *task) { + struct usb_taskq *taskq; int s; s = splusb(); - if (task->onqueue) { - TAILQ_REMOVE(&usb_all_tasks, task, next); - task->onqueue = 0; + if (task->queue != -1) { + taskq = &usb_taskq[task->queue]; + TAILQ_REMOVE(&taskq->tasks, task, next); + task->queue = -1; } splx(s); } void usb_event_thread(void *arg) { static int newthread_wchan; struct usb_softc *sc = arg; #if defined(__FreeBSD__) && __FreeBSD_version >= 500000 mtx_lock(&Giant); #endif DPRINTF(("usb_event_thread: start\n")); /* * In case this controller is a companion controller to an * EHCI controller we need to wait until the EHCI controller * has grabbed the port. What we do here is wait until no new * USB threads have been created in a while. XXX we actually * just want to wait for the PCI slot to be fully scanned. * * Note that when you `kldload usb' it actually attaches the * devices in order that the drivers appear in the kld, not the * normal PCI order, since the addition of each driver within * usb.ko (ohci, ehci etc.) causes a separate PCI bus re-scan. */ wakeup(&newthread_wchan); for (;;) { if (tsleep(&newthread_wchan , PWAIT, "usbets", hz * 4) != 0) break; } /* Make sure first discover does something. */ sc->sc_bus->needs_explore = 1; usb_discover(sc); config_pending_decr(); while (!sc->sc_dying) { #ifdef USB_DEBUG if (usb_noexplore < 2) #endif usb_discover(sc); #ifdef USB_DEBUG (void)tsleep(&sc->sc_bus->needs_explore, PWAIT, "usbevt", usb_noexplore ? 0 : hz * 60); #else (void)tsleep(&sc->sc_bus->needs_explore, PWAIT, "usbevt", hz * 60); #endif DPRINTFN(2,("usb_event_thread: woke up\n")); } sc->sc_event_thread = NULL; /* In case parent is waiting for us to exit. */ wakeup(sc); DPRINTF(("usb_event_thread: exit\n")); kthread_exit(0); } void usb_task_thread(void *arg) { struct usb_task *task; + struct usb_taskq *taskq; int s; #if defined(__FreeBSD__) && __FreeBSD_version >= 500000 mtx_lock(&Giant); #endif - DPRINTF(("usb_task_thread: start\n")); + taskq = arg; + DPRINTF(("usb_task_thread: start taskq %s\n", taskq->name)); s = splusb(); while (usb_ndevs > 0) { - task = TAILQ_FIRST(&usb_all_tasks); + task = TAILQ_FIRST(&taskq->tasks); if (task == NULL) { - tsleep(&usb_all_tasks, PWAIT, "usbtsk", 0); - task = TAILQ_FIRST(&usb_all_tasks); + tsleep(&taskq->tasks, PWAIT, "usbtsk", 0); + task = TAILQ_FIRST(&taskq->tasks); } DPRINTFN(2,("usb_task_thread: woke up task=%p\n", task)); if (task != NULL) { - TAILQ_REMOVE(&usb_all_tasks, task, next); - task->onqueue = 0; + TAILQ_REMOVE(&taskq->tasks, task, next); + task->queue = -1; splx(s); task->fun(task->arg); s = splusb(); } } splx(s); - usb_taskcreated = 0; - wakeup(&usb_taskcreated); + taskq->taskcreated = 0; + wakeup(&taskq->taskcreated); DPRINTF(("usb_event_thread: exit\n")); kthread_exit(0); } #if defined(__NetBSD__) || defined(__OpenBSD__) int usbctlprint(void *aux, const char *pnp) { /* only "usb"es can attach to host controllers */ if (pnp) printf("usb at %s", pnp); return (UNCONF); } #endif /* defined(__NetBSD__) || defined(__OpenBSD__) */ int usbopen(struct cdev *dev, int flag, int mode, usb_proc_ptr p) { int unit = USBUNIT(dev); struct usb_softc *sc; if (unit == USB_DEV_MINOR) { if (usb_dev_open) return (EBUSY); usb_dev_open = 1; usb_async_proc = 0; return (0); } USB_GET_SC_OPEN(usb, unit, sc); if (sc->sc_dying) return (EIO); return (0); } int usbread(struct cdev *dev, struct uio *uio, int flag) { struct usb_event ue; int unit = USBUNIT(dev); int s, error, n; if (unit != USB_DEV_MINOR) return (ENODEV); if (uio->uio_resid != sizeof(struct usb_event)) return (EINVAL); error = 0; s = splusb(); for (;;) { n = usb_get_next_event(&ue); if (n != 0) break; if (flag & O_NONBLOCK) { error = EWOULDBLOCK; break; } error = tsleep(&usb_events, PZERO | PCATCH, "usbrea", 0); if (error) break; } splx(s); if (!error) error = uiomove((void *)&ue, uio->uio_resid, uio); return (error); } int usbclose(struct cdev *dev, int flag, int mode, usb_proc_ptr p) { int unit = USBUNIT(dev); if (unit == USB_DEV_MINOR) { usb_async_proc = 0; usb_dev_open = 0; } return (0); } int usbioctl(struct cdev *devt, u_long cmd, caddr_t data, int flag, usb_proc_ptr p) { struct usb_softc *sc; int unit = USBUNIT(devt); if (unit == USB_DEV_MINOR) { switch (cmd) { case FIONBIO: /* All handled in the upper FS layer. */ return (0); case FIOASYNC: if (*(int *)data) #if __FreeBSD_version >= 500000 usb_async_proc = p->td_proc; #else usb_async_proc = p; #endif else usb_async_proc = 0; return (0); default: return (EINVAL); } } USB_GET_SC(usb, unit, sc); if (sc->sc_dying) return (EIO); switch (cmd) { #if defined(__FreeBSD__) /* This part should be deleted */ case USB_DISCOVER: break; #endif case USB_REQUEST: { struct usb_ctl_request *ur = (void *)data; int len = UGETW(ur->ucr_request.wLength); struct iovec iov; struct uio uio; void *ptr = 0; int addr = ur->ucr_addr; usbd_status err; int error = 0; DPRINTF(("usbioctl: USB_REQUEST addr=%d len=%d\n", addr, len)); if (len < 0 || len > 32768) return (EINVAL); if (addr < 0 || addr >= USB_MAX_DEVICES || sc->sc_bus->devices[addr] == 0) return (EINVAL); if (len != 0) { iov.iov_base = (caddr_t)ur->ucr_data; iov.iov_len = len; uio.uio_iov = &iov; uio.uio_iovcnt = 1; uio.uio_resid = len; uio.uio_offset = 0; uio.uio_segflg = UIO_USERSPACE; uio.uio_rw = ur->ucr_request.bmRequestType & UT_READ ? UIO_READ : UIO_WRITE; uio.uio_td = p; ptr = malloc(len, M_TEMP, M_WAITOK); if (uio.uio_rw == UIO_WRITE) { error = uiomove(ptr, len, &uio); if (error) goto ret; } } err = usbd_do_request_flags(sc->sc_bus->devices[addr], &ur->ucr_request, ptr, ur->ucr_flags, &ur->ucr_actlen, USBD_DEFAULT_TIMEOUT); if (err) { error = EIO; goto ret; } if (len != 0) { if (uio.uio_rw == UIO_READ) { error = uiomove(ptr, len, &uio); if (error) goto ret; } } ret: if (ptr) free(ptr, M_TEMP); return (error); } case USB_DEVICEINFO: { struct usb_device_info *di = (void *)data; int addr = di->udi_addr; usbd_device_handle dev; if (addr < 1 || addr >= USB_MAX_DEVICES) return (EINVAL); dev = sc->sc_bus->devices[addr]; if (dev == NULL) return (ENXIO); usbd_fill_deviceinfo(dev, di, 1); break; } case USB_DEVICESTATS: *(struct usb_device_stats *)data = sc->sc_bus->stats; break; default: return (EINVAL); } return (0); } int usbpoll(struct cdev *dev, int events, usb_proc_ptr p) { int revents, mask, s; int unit = USBUNIT(dev); if (unit == USB_DEV_MINOR) { revents = 0; mask = POLLIN | POLLRDNORM; s = splusb(); if (events & mask && usb_nevents > 0) revents |= events & mask; if (revents == 0 && events & mask) selrecord(p, &usb_selevent); splx(s); return (revents); } else { #if defined(__FreeBSD__) return (0); /* select/poll never wakes up - back compat */ #else return (ENXIO); #endif } } /* Explore device tree from the root. */ static void usb_discover(void *v) { struct usb_softc *sc = v; #if defined(__FreeBSD__) /* splxxx should be changed to mutexes for preemption safety some day */ int s; #endif DPRINTFN(2,("usb_discover\n")); #ifdef USB_DEBUG if (usb_noexplore > 1) return; #endif /* * We need mutual exclusion while traversing the device tree, * but this is guaranteed since this function is only called * from the event thread for the controller. */ #if defined(__FreeBSD__) s = splusb(); #endif while (sc->sc_bus->needs_explore && !sc->sc_dying) { sc->sc_bus->needs_explore = 0; #if defined(__FreeBSD__) splx(s); #endif sc->sc_bus->root_hub->hub->explore(sc->sc_bus->root_hub); #if defined(__FreeBSD__) s = splusb(); #endif } #if defined(__FreeBSD__) splx(s); #endif } void usb_needs_explore(usbd_device_handle dev) { DPRINTFN(2,("usb_needs_explore\n")); dev->bus->needs_explore = 1; wakeup(&dev->bus->needs_explore); } /* Called at splusb() */ int usb_get_next_event(struct usb_event *ue) { struct usb_event_q *ueq; if (usb_nevents <= 0) return (0); ueq = TAILQ_FIRST(&usb_events); #ifdef DIAGNOSTIC if (ueq == NULL) { printf("usb: usb_nevents got out of sync! %d\n", usb_nevents); usb_nevents = 0; return (0); } #endif *ue = ueq->ue; TAILQ_REMOVE(&usb_events, ueq, next); free(ueq, M_USBDEV); usb_nevents--; return (1); } void usbd_add_dev_event(int type, usbd_device_handle udev) { struct usb_event ue; usbd_fill_deviceinfo(udev, &ue.u.ue_device, USB_EVENT_IS_ATTACH(type)); usb_add_event(type, &ue); } void usbd_add_drv_event(int type, usbd_device_handle udev, device_t dev) { struct usb_event ue; ue.u.ue_driver.ue_cookie = udev->cookie; strncpy(ue.u.ue_driver.ue_devname, device_get_nameunit(dev), sizeof ue.u.ue_driver.ue_devname); usb_add_event(type, &ue); } void usb_add_event(int type, struct usb_event *uep) { struct usb_event_q *ueq; struct usb_event ue; struct timeval thetime; int s; ueq = malloc(sizeof *ueq, M_USBDEV, M_WAITOK); ueq->ue = *uep; ueq->ue.ue_type = type; microtime(&thetime); TIMEVAL_TO_TIMESPEC(&thetime, &ueq->ue.ue_time); s = splusb(); if (USB_EVENT_IS_DETACH(type)) { struct usb_event_q *ueqi, *ueqi_next; for (ueqi = TAILQ_FIRST(&usb_events); ueqi; ueqi = ueqi_next) { ueqi_next = TAILQ_NEXT(ueqi, next); if (ueqi->ue.u.ue_driver.ue_cookie.cookie == uep->u.ue_device.udi_cookie.cookie) { TAILQ_REMOVE(&usb_events, ueqi, next); free(ueqi, M_USBDEV); usb_nevents--; ueqi_next = TAILQ_FIRST(&usb_events); } } } if (usb_nevents >= USB_MAX_EVENTS) { /* Too many queued events, drop an old one. */ DPRINTF(("usb: event dropped\n")); (void)usb_get_next_event(&ue); } TAILQ_INSERT_TAIL(&usb_events, ueq, next); usb_nevents++; wakeup(&usb_events); selwakeuppri(&usb_selevent, PZERO); if (usb_async_proc != NULL) { PROC_LOCK(usb_async_proc); psignal(usb_async_proc, SIGIO); PROC_UNLOCK(usb_async_proc); } splx(s); } void usb_schedsoftintr(usbd_bus_handle bus) { DPRINTFN(10,("usb_schedsoftintr: polling=%d\n", bus->use_polling)); #ifdef USB_USE_SOFTINTR if (bus->use_polling) { bus->methods->soft_intr(bus); } else { #ifdef __HAVE_GENERIC_SOFT_INTERRUPTS softintr_schedule(bus->soft); #else if (!callout_pending(&bus->softi)) callout_reset(&bus->softi, 0, bus->methods->soft_intr, bus); #endif /* __HAVE_GENERIC_SOFT_INTERRUPTS */ } #else bus->methods->soft_intr(bus); #endif /* USB_USE_SOFTINTR */ } #if defined(__NetBSD__) || defined(__OpenBSD__) int usb_activate(device_t self, enum devact act) { struct usb_softc *sc = (struct usb_softc *)self; usbd_device_handle dev = sc->sc_port.device; int i, rv = 0; switch (act) { case DVACT_ACTIVATE: return (EOPNOTSUPP); case DVACT_DEACTIVATE: sc->sc_dying = 1; if (dev != NULL && dev->cdesc != NULL && dev->subdevs != NULL) { for (i = 0; dev->subdevs[i]; i++) rv |= config_deactivate(dev->subdevs[i]); } break; } return (rv); } #endif USB_DETACH(usb) { USB_DETACH_START(usb, sc); struct usb_event ue; + struct usb_taskq *taskq; + int i; DPRINTF(("usb_detach: start\n")); sc->sc_dying = 1; /* Make all devices disconnect. */ if (sc->sc_port.device != NULL) usb_disconnect_port(&sc->sc_port, self); /* Kill off event thread. */ if (sc->sc_event_thread != NULL) { wakeup(&sc->sc_bus->needs_explore); if (tsleep(sc, PWAIT, "usbdet", hz * 60)) printf("%s: event thread didn't die\n", device_get_nameunit(sc->sc_dev)); DPRINTF(("usb_detach: event thread dead\n")); } #ifdef __FreeBSD__ destroy_dev(sc->sc_usbdev); if (--usb_ndevs == 0) { destroy_dev(usb_dev); usb_dev = NULL; - wakeup(&usb_all_tasks); - if (tsleep(&usb_taskcreated, PWAIT, "usbtdt", hz * 60)) - printf("usb task thread didn't die\n"); + for (i = 0; i < USB_NUM_TASKQS; i++) { + taskq = &usb_taskq[i]; + wakeup(&taskq->tasks); + if (tsleep(&taskq->taskcreated, PWAIT, "usbtdt", + hz * 60)) { + printf("usb task thread %s didn't die\n", + taskq->name); + } + } } #endif usbd_finish(); #ifdef USB_USE_SOFTINTR #ifdef __HAVE_GENERIC_SOFT_INTERRUPTS if (sc->sc_bus->soft != NULL) { softintr_disestablish(sc->sc_bus->soft); sc->sc_bus->soft = NULL; } #else callout_stop(&sc->sc_bus->softi); #endif #endif ue.u.ue_ctrlr.ue_bus = device_get_unit(sc->sc_dev); usb_add_event(USB_EVENT_CTRLR_DETACH, &ue); return (0); } #if defined(__FreeBSD__) static void usb_child_detached(device_t self, device_t child) { struct usb_softc *sc = device_get_softc(self); /* XXX, should check it is the right device. */ sc->sc_port.device = NULL; } /* Explore USB busses at the end of device configuration. */ static void usb_cold_explore(void *arg) { struct usb_softc *sc; KASSERT(cold || TAILQ_EMPTY(&usb_coldexplist), ("usb_cold_explore: busses to explore when !cold")); while (!TAILQ_EMPTY(&usb_coldexplist)) { sc = TAILQ_FIRST(&usb_coldexplist); TAILQ_REMOVE(&usb_coldexplist, sc, sc_coldexplist); sc->sc_bus->use_polling++; sc->sc_port.device->hub->explore(sc->sc_bus->root_hub); sc->sc_bus->use_polling--; } } DRIVER_MODULE(usb, ohci, usb_driver, usb_devclass, 0, 0); DRIVER_MODULE(usb, uhci, usb_driver, usb_devclass, 0, 0); DRIVER_MODULE(usb, ehci, usb_driver, usb_devclass, 0, 0); DRIVER_MODULE(usb, slhci, usb_driver, usb_devclass, 0, 0); SYSINIT(usb_cold_explore, SI_SUB_CONFIGURE, SI_ORDER_MIDDLE, usb_cold_explore, NULL); #endif diff --git a/sys/dev/usb/usbdi.h b/sys/dev/usb/usbdi.h index cedb6d927aaf..fb1af43f94db 100644 --- a/sys/dev/usb/usbdi.h +++ b/sys/dev/usb/usbdi.h @@ -1,308 +1,312 @@ /* $NetBSD: usbdi.h,v 1.64 2004/10/23 13:26:34 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. */ #ifndef _USBDI_H_ #define _USBDI_H_ typedef struct usbd_bus *usbd_bus_handle; typedef struct usbd_device *usbd_device_handle; typedef struct usbd_interface *usbd_interface_handle; typedef struct usbd_pipe *usbd_pipe_handle; typedef struct usbd_xfer *usbd_xfer_handle; typedef void *usbd_private_handle; typedef enum { /* keep in sync with usbd_status_msgs */ USBD_NORMAL_COMPLETION = 0, /* must be 0 */ USBD_IN_PROGRESS, /* 1 */ /* errors */ USBD_PENDING_REQUESTS, /* 2 */ USBD_NOT_STARTED, /* 3 */ USBD_INVAL, /* 4 */ USBD_NOMEM, /* 5 */ USBD_CANCELLED, /* 6 */ USBD_BAD_ADDRESS, /* 7 */ USBD_IN_USE, /* 8 */ USBD_NO_ADDR, /* 9 */ USBD_SET_ADDR_FAILED, /* 10 */ USBD_NO_POWER, /* 11 */ USBD_TOO_DEEP, /* 12 */ USBD_IOERROR, /* 13 */ USBD_NOT_CONFIGURED, /* 14 */ USBD_TIMEOUT, /* 15 */ USBD_SHORT_XFER, /* 16 */ USBD_STALLED, /* 17 */ USBD_INTERRUPTED, /* 18 */ USBD_ERROR_MAX /* must be last */ } usbd_status; typedef void (*usbd_callback)(usbd_xfer_handle, usbd_private_handle, usbd_status); /* Open flags */ #define USBD_EXCLUSIVE_USE 0x01 /* Use default (specified by ep. desc.) interval on interrupt pipe */ #define USBD_DEFAULT_INTERVAL (-1) /* Request flags */ #define USBD_NO_COPY 0x01 /* do not copy data to DMA buffer */ #define USBD_SYNCHRONOUS 0x02 /* wait for completion */ /* in usb.h #define USBD_SHORT_XFER_OK 0x04*/ /* allow short reads */ #define USBD_FORCE_SHORT_XFER 0x08 /* force last short packet on write */ #define USBD_NO_TIMEOUT 0 #define USBD_DEFAULT_TIMEOUT 5000 /* ms = 5 s */ usbd_status usbd_open_pipe(usbd_interface_handle, u_int8_t, u_int8_t, usbd_pipe_handle *); usbd_status usbd_close_pipe(usbd_pipe_handle); usbd_status usbd_transfer(usbd_xfer_handle); usbd_xfer_handle usbd_alloc_xfer(usbd_device_handle); usbd_status usbd_free_xfer(usbd_xfer_handle); void usbd_setup_xfer(usbd_xfer_handle, usbd_pipe_handle, usbd_private_handle, void *, u_int32_t, u_int16_t, u_int32_t, usbd_callback); void usbd_setup_default_xfer(usbd_xfer_handle, usbd_device_handle, usbd_private_handle, u_int32_t, usb_device_request_t *, void *, u_int32_t, u_int16_t, usbd_callback); void usbd_setup_isoc_xfer(usbd_xfer_handle, usbd_pipe_handle, usbd_private_handle, u_int16_t *, u_int32_t, u_int16_t, usbd_callback); void usbd_get_xfer_status(usbd_xfer_handle, usbd_private_handle *, void **, u_int32_t *, usbd_status *); usb_endpoint_descriptor_t *usbd_interface2endpoint_descriptor (usbd_interface_handle, u_int8_t); usbd_status usbd_abort_pipe(usbd_pipe_handle); usbd_status usbd_abort_default_pipe(usbd_device_handle); usbd_status usbd_clear_endpoint_stall(usbd_pipe_handle); usbd_status usbd_clear_endpoint_stall_async(usbd_pipe_handle); void usbd_clear_endpoint_toggle(usbd_pipe_handle); usbd_status usbd_endpoint_count(usbd_interface_handle, u_int8_t *); usbd_status usbd_interface_count(usbd_device_handle, u_int8_t *); void usbd_interface2device_handle(usbd_interface_handle, usbd_device_handle *); usbd_status usbd_device2interface_handle(usbd_device_handle, u_int8_t, usbd_interface_handle *); usbd_device_handle usbd_pipe2device_handle(usbd_pipe_handle); void *usbd_alloc_buffer(usbd_xfer_handle, u_int32_t); void usbd_free_buffer(usbd_xfer_handle); void *usbd_get_buffer(usbd_xfer_handle); usbd_status usbd_sync_transfer(usbd_xfer_handle); usbd_status usbd_open_pipe_intr(usbd_interface_handle, u_int8_t, u_int8_t, usbd_pipe_handle *, usbd_private_handle, void *, u_int32_t, usbd_callback, int); usbd_status usbd_do_request(usbd_device_handle, usb_device_request_t *, void *); usbd_status usbd_do_request_async(usbd_device_handle, usb_device_request_t *, void *); usbd_status usbd_do_request_flags(usbd_device_handle, usb_device_request_t *, void *, u_int16_t, int*, u_int32_t); usbd_status usbd_do_request_flags_pipe(usbd_device_handle, usbd_pipe_handle, usb_device_request_t *, void *, u_int16_t, int *, u_int32_t); usb_interface_descriptor_t *usbd_get_interface_descriptor (usbd_interface_handle); usb_config_descriptor_t *usbd_get_config_descriptor(usbd_device_handle); usb_device_descriptor_t *usbd_get_device_descriptor(usbd_device_handle); int usbd_get_speed(usbd_device_handle); usbd_status usbd_set_interface(usbd_interface_handle, int); int usbd_get_no_alts(usb_config_descriptor_t *, int); usbd_status usbd_get_interface(usbd_interface_handle, u_int8_t *); void usbd_fill_deviceinfo(usbd_device_handle, struct usb_device_info *, int); int usbd_get_interface_altindex(usbd_interface_handle); usb_interface_descriptor_t *usbd_find_idesc(usb_config_descriptor_t *, int, int); usb_endpoint_descriptor_t *usbd_find_edesc(usb_config_descriptor_t *, int, int, int); void usbd_dopoll(usbd_interface_handle); void usbd_set_polling(usbd_device_handle, int); const char *usbd_errstr(usbd_status); void usbd_add_dev_event(int, usbd_device_handle); void usbd_add_drv_event(int, usbd_device_handle, device_t); void usbd_devinfo(usbd_device_handle, int, char *); const struct usbd_quirks *usbd_get_quirks(usbd_device_handle); usb_endpoint_descriptor_t *usbd_get_endpoint_descriptor (usbd_interface_handle, u_int8_t); usbd_status usbd_reload_device_desc(usbd_device_handle); int usbd_ratecheck(struct timeval *last); usbd_status usbd_get_string(usbd_device_handle dev, int si, char *buf); /* An iterator for descriptors. */ typedef struct { const uByte *cur; const uByte *end; } usbd_desc_iter_t; void usb_desc_iter_init(usbd_device_handle dev, usbd_desc_iter_t *iter); const usb_descriptor_t *usb_desc_iter_next(usbd_desc_iter_t *iter); /* * The usb_task structs form a queue of things to run in the USB event * thread. Normally this is just device discovery when a connect/disconnect * has been detected. But it may also be used by drivers that need to * perform (short) tasks that must have a process context. */ struct usb_task { TAILQ_ENTRY(usb_task) next; void (*fun)(void *); void *arg; - char onqueue; + int queue; }; +#define USB_TASKQ_HC 0 +#define USB_TASKQ_DRIVER 1 +#define USB_NUM_TASKQS 2 +#define USB_TASKQ_NAMES {"usbtask-hc", "usbtask-dr"} -void usb_add_task(usbd_device_handle, struct usb_task *); +void usb_add_task(usbd_device_handle, struct usb_task *, int queue); void usb_rem_task(usbd_device_handle, struct usb_task *); -#define usb_init_task(t, f, a) ((t)->fun = (f), (t)->arg = (a), (t)->onqueue = 0) +#define usb_init_task(t, f, a) ((t)->fun = (f), (t)->arg = (a), (t)->queue = -1) struct usb_devno { u_int16_t ud_vendor; u_int16_t ud_product; }; const struct usb_devno *usb_match_device(const struct usb_devno *, u_int, u_int, u_int16_t, u_int16_t); #define usb_lookup(tbl, vendor, product) \ usb_match_device((const struct usb_devno *)(tbl), sizeof (tbl) / sizeof ((tbl)[0]), sizeof ((tbl)[0]), (vendor), (product)) #define USB_PRODUCT_ANY 0xffff /* NetBSD attachment information */ /* Attach data */ struct usb_attach_arg { int port; int configno; int ifaceno; int vendor; int product; int release; int matchlvl; usbd_device_handle device; /* current device */ usbd_interface_handle iface; /* current interface */ int usegeneric; usbd_interface_handle *ifaces; /* all interfaces */ int nifaces; /* number of interfaces */ }; #if defined(__NetBSD__) || defined(__OpenBSD__) /* Match codes. */ /* First five codes is for a whole device. */ #define UMATCH_VENDOR_PRODUCT_REV 14 #define UMATCH_VENDOR_PRODUCT 13 #define UMATCH_VENDOR_DEVCLASS_DEVPROTO 12 #define UMATCH_DEVCLASS_DEVSUBCLASS_DEVPROTO 11 #define UMATCH_DEVCLASS_DEVSUBCLASS 10 /* Next six codes are for interfaces. */ #define UMATCH_VENDOR_PRODUCT_REV_CONF_IFACE 9 #define UMATCH_VENDOR_PRODUCT_CONF_IFACE 8 #define UMATCH_VENDOR_IFACESUBCLASS_IFACEPROTO 7 #define UMATCH_VENDOR_IFACESUBCLASS 6 #define UMATCH_IFACECLASS_IFACESUBCLASS_IFACEPROTO 5 #define UMATCH_IFACECLASS_IFACESUBCLASS 4 #define UMATCH_IFACECLASS 3 #define UMATCH_IFACECLASS_GENERIC 2 /* Generic driver */ #define UMATCH_GENERIC 1 /* No match */ #define UMATCH_NONE 0 #elif defined(__FreeBSD__) /* FreeBSD needs values less than zero */ #define UMATCH_VENDOR_PRODUCT_REV (-10) #define UMATCH_VENDOR_PRODUCT (-20) #define UMATCH_VENDOR_DEVCLASS_DEVPROTO (-30) #define UMATCH_DEVCLASS_DEVSUBCLASS_DEVPROTO (-40) #define UMATCH_DEVCLASS_DEVSUBCLASS (-50) #define UMATCH_VENDOR_PRODUCT_REV_CONF_IFACE (-60) #define UMATCH_VENDOR_PRODUCT_CONF_IFACE (-70) #define UMATCH_VENDOR_IFACESUBCLASS_IFACEPROTO (-80) #define UMATCH_VENDOR_IFACESUBCLASS (-90) #define UMATCH_IFACECLASS_IFACESUBCLASS_IFACEPROTO (-100) #define UMATCH_IFACECLASS_IFACESUBCLASS (-110) #define UMATCH_IFACECLASS (-120) #define UMATCH_IFACECLASS_GENERIC (-130) #define UMATCH_GENERIC (-140) #define UMATCH_NONE (ENXIO) #endif #define USBD_SHOW_DEVICE_CLASS 0x1 #define USBD_SHOW_INTERFACE_CLASS 0x2 #if defined(__FreeBSD__) int usbd_driver_load(module_t mod, int what, void *arg); static inline int usb_get_port(device_t dev) { struct usb_attach_arg *uap = device_get_ivars(dev); return (uap->port); } static inline struct usbd_interface * usb_get_iface(device_t dev) { struct usb_attach_arg *uap = device_get_ivars(dev); return (uap->iface); } #endif /* XXX Perhaps USB should have its own levels? */ #ifdef USB_USE_SOFTINTR #ifdef __HAVE_GENERIC_SOFT_INTERRUPTS #define splusb splsoftnet #else #define splusb splsoftclock #endif /* __HAVE_GENERIC_SOFT_INTERRUPTS */ #else #define splusb splbio #endif /* USB_USE_SOFTINTR */ #define splhardusb splbio #define IPL_USB IPL_BIO #endif /* _USBDI_H_ */