Index: stable/4/sys/dev/usb/ohci.c =================================================================== --- stable/4/sys/dev/usb/ohci.c (revision 145576) +++ stable/4/sys/dev/usb/ohci.c (revision 145577) @@ -1,3588 +1,3625 @@ /* $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 #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__) #include #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 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_ptr_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 rv = 0; + 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); +#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 */ - /* free data structures XXX */ + 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); } -#endif 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; ohci_physaddr_t dataphys; u_int32_t tdflags; int offset = 0; int len, curlen; usb_dma_t *dma = &xfer->dmabuf; u_int16_t flags = xfer->flags; DPRINTFN(alen < 4096,("ohci_alloc_std_chain: start len=%d\n", alen)); len = alen; cur = sp; 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_NOINTR); for (;;) { next = ohci_alloc_std(sc); if (next == NULL) goto nomem; dataphys = DMAADDR(dma, offset); /* * The OHCI hardware can handle at most one 4k crossing. * XXX - currently we only allocate contigous buffers, but * 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.) * * If/when dma has multiple segments, this will need to * properly handle fragmenting TD's. - * - * We can describe the above using maxsegsz = 4k and nsegs = 2 - * in the future. + * + * Note that if we are gathering data from multiple SMALL + * segments, e.g. mbufs, we need to do special gymnastics, + * e.g. bounce buffering or data aggregation, + * BEFORE WE GET HERE because a bulk USB transfer must + * consist of maximally sized packets right up to the end. + * A shorter than maximal packet means that it is the end + * of the transfer. If the data transfer length is a + * multiple of the packet size, then a 0 byte + * packet will be the signal of the end of transfer. + * Since packets can't cross TDs this means that + * each TD except the last one must cover an exact multiple + * of the maximal packet length. */ - if (OHCI_PAGE(dataphys) == OHCI_PAGE(DMAADDR(dma, offset + - len - 1)) || len - (OHCI_PAGE_SIZE - - OHCI_PAGE_OFFSET(dataphys)) <= OHCI_PAGE_SIZE) { - /* we can handle it in this TD */ + if (OHCI_PAGE_OFFSET(dataphys) + len <= (2 * OHCI_PAGE_SIZE)) { + /* We can handle all that remains in this TD */ curlen = len; } else { - /* XXX The calculation below is wrong and could - * result in a packet that is not a multiple of the - * MaxPacketSize in the case where the buffer does not - * start on an appropriate address (like for example in - * the case of an mbuf cluster). You'll get an early - * short packet. - */ /* must use multiple TDs, fill as much as possible. */ curlen = 2 * OHCI_PAGE_SIZE - OHCI_PAGE_OFFSET(dataphys); /* the length must be a multiple of the max size */ curlen -= curlen % UGETW(opipe->pipe.endpoint->edesc->wMaxPacketSize); -#ifdef DIAGNOSTIC - if (curlen == 0) - panic("ohci_alloc_std: curlen == 0"); -#endif + KASSERT((curlen != 0), ("ohci_alloc_std: curlen == 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(DMAADDR(dma, offset + curlen - 1)); 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) break; 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; cur = next; } if ((flags & USBD_FORCE_SHORT_XFER) && alen % UGETW(opipe->pipe.endpoint->edesc->wMaxPacketSize) == 0) { /* Force a 0 length transfer at the end. */ cur = next; 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")); } *ep = cur; 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:", USBDEVNAME(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", USBDEVNAME(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]); SIMPLEQ_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", USBDEVNAME(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", USBDEVNAME(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", USBDEVNAME(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 = SIMPLEQ_FIRST(&sc->sc_free_xfers); if (xfer != NULL) { SIMPLEQ_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; struct ohci_xfer *oxfer = (struct ohci_xfer *)xfer; ohci_soft_itd_t *sitd; if (oxfer->ohci_xfer_flags & OHCI_ISOC_DIRTY) { for (sitd = xfer->hcpriv; sitd != NULL && sitd->xfer == xfer; sitd = sitd->nextitd) ohci_free_sitd(sc, sitd); } #ifdef DIAGNOSTIC if (xfer->busy_free != XFER_BUSY) { printf("ohci_freex: xfer=%p not busy, 0x%08x\n", xfer, xfer->busy_free); return; } xfer->busy_free = XFER_FREE; #endif SIMPLEQ_INSERT_HEAD(&sc->sc_free_xfers, xfer, next); } /* * Shut down the controller when the system is going down. */ -#if defined(__NetBSD__) || defined(__OpenBSD__) 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); } -#endif #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 *); int ohci_intr(void *p) { ohci_softc_t *sc = p; 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) { #ifdef DIAGNOSTIC printf("ohci_intr: ignored interrupt while polling\n"); #endif return (0); } 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", USBDEVNAME(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", USBDEVNAME(sc->sc_bus.bdev)); /* XXX process resume detect */ } if (eintrs & OHCI_UE) { printf("%s: unrecoverable error, controller halted\n", USBDEVNAME(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", USBDEVNAME(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; 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 || (std->flags & OHCI_TD_HANDLED)) { /* * xfer == NULL: There seems to be no xfer associated * with this TD. It is tailp that happened to end up on * the done queue. * flags & OHCI_TD_HANDLED: The TD has already been * handled by process_done and should not be done again. * Shouldn't happen, but some chips are broken(?). */ continue; } if (xfer->status == USBD_CANCELLED || xfer->status == USBD_TIMEOUT) { DPRINTF(("ohci_process_done: cancel/timeout %p\n", xfer)); /* Handled by abort routine. */ continue; } usb_uncallout(xfer->timeout_handle, ohci_timeout, xfer); usb_rem_task(OXFER(xfer)->xfer.pipe->device, &OXFER(xfer)->abort_task); 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) { if (std->flags & OHCI_CALL_DONE) { xfer->status = USBD_NORMAL_COMPLETION; s = splusb(); usb_transfer_complete(xfer); splx(s); } ohci_free_std(sc, std); } else { /* * Endpoint is halted. First unlink all the TDs * belonging to the failed transfer, and then restart * the endpoint. */ ohci_soft_td_t *p, *n; 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))])); /* Mark all the TDs in the done queue for the current * xfer as handled */ for (p = stdnext; p; p = p->dnext) { if (p->xfer == xfer) p->flags |= OHCI_TD_HANDLED; } /* remove TDs */ for (p = std; 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); } } #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; } 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; ohci_soft_ed_t *sed = opipe->sed; ohci_soft_td_t *data, *tail; DPRINTFN(10,("ohci_device_intr_done: xfer=%p, actlen=%d\n", xfer, xfer->actlen)); xfer->hcpriv = NULL; if (xfer->pipe->repeat) { data = opipe->tail.td; tail = ohci_alloc_std(sc); /* XXX should reuse TD */ if (tail == NULL) { xfer->status = USBD_NOMEM; return; } 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); data->td.td_cbp = htole32(DMAADDR(&xfer->dmabuf, 0)); data->nexttd = tail; data->td.td_nexttd = htole32(tail->physaddr); data->td.td_be = htole32(le32toh(data->td.td_cbp) + xfer->length - 1); data->len = xfer->length; data->xfer = xfer; data->flags = OHCI_CALL_DONE | OHCI_ADD_LEN; xfer->hcpriv = data; xfer->actlen = 0; sed->ed.ed_tailp = htole32(tail->physaddr); opipe->tail.td = tail; } } 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 = KERNADDR(&xfer->dmabuf, 0); 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; int addr = dev->address; 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, addr, 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; - /* Update device address and length since they may have changed. */ + /* Update device address and length since they may have changed + during the setup of the control pipe in usbd_new_device(). */ /* XXX This only needs to be done once, but it's too early in open. */ /* XXXX Should not touch ED here! */ sed->ed.ed_flags = htole32( (le32toh(sed->ed.ed_flags) & ~(OHCI_ED_ADDRMASK | OHCI_ED_MAXPMASK)) | OHCI_ED_SET_FA(addr) | OHCI_ED_SET_MAXP(UGETW(opipe->pipe.endpoint->edesc->wMaxPacketSize))); 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_NOINTR); 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", USBDEVNAME(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", USBDEVNAME(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); } 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(ed->bEndpointAddress) | + 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 = 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); 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); } 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(SIMPLEQ_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 = KERNADDR(&xfer->dmabuf, 0); #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(SIMPLEQ_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(SIMPLEQ_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(SIMPLEQ_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); 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(SIMPLEQ_FIRST(&xfer->pipe->queue))); } Static usbd_status ohci_device_intr_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; ohci_soft_ed_t *sed = opipe->sed; ohci_soft_td_t *data, *tail; int len; int s; if (sc->sc_dying) return (USBD_IOERROR); DPRINTFN(3, ("ohci_device_intr_transfer: 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_transfer: a request"); #endif len = xfer->length; 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); data->td.td_cbp = htole32(DMAADDR(&xfer->dmabuf, 0)); data->nexttd = tail; data->td.td_nexttd = htole32(tail->physaddr); data->td.td_be = htole32(le32toh(data->td.td_cbp) + len - 1); data->len = len; data->xfer = xfer; data->flags = OHCI_CALL_DONE | OHCI_ADD_LEN; xfer->hcpriv = data; #ifdef USB_DEBUG if (ohcidebug > 5) { DPRINTF(("ohci_device_intr_transfer:\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; sed->ed.ed_flags &= htole32(~OHCI_ED_SKIP); #if 0 /* * This goes horribly wrong, printing thousands of descriptors, * because false references are followed due to the fact that the * TD is gone. */ if (ohcidebug > 5) { usb_delay_ms(&sc->sc_bus, 5); DPRINTF(("ohci_device_intr_transfer: status=%x\n", OREAD4(sc, OHCI_COMMAND_STATUS))); ohci_dump_ed(sed); ohci_dump_tds(data); } #endif splx(s); return (USBD_IN_PROGRESS); } /* 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", USBDEVNAME(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(SIMPLEQ_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 ohci_xfer *oxfer = (struct ohci_xfer *)xfer; ohci_soft_itd_t *sitd, *nsitd; ohci_physaddr_t buf, offs, noffs, bp0, tdphys; int i, ncur, nframes; 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)); } if (xfer->hcpriv) { for (sitd = xfer->hcpriv; sitd != NULL && sitd->xfer == xfer; sitd = sitd->nextitd) ohci_free_sitd(sc, sitd); /* Free ITDs in prev xfer*/ if (sitd == NULL) { sitd = ohci_alloc_sitd(sc); if (sitd == NULL) panic("cant alloc isoc"); opipe->tail.itd = sitd; tdphys = sitd->physaddr; sed->ed.ed_flags |= htole32(OHCI_ED_SKIP); /* Stop*/ sed->ed.ed_headp = sed->ed.ed_tailp = htole32(tdphys); sed->ed.ed_flags &= htole32(~OHCI_ED_SKIP); /* Start.*/ } } sitd = opipe->tail.itd; buf = DMAADDR(&xfer->dmabuf, 0); bp0 = OHCI_PAGE(buf); offs = OHCI_PAGE_OFFSET(buf); nframes = xfer->nframes; xfer->hcpriv = sitd; for (i = ncur = 0; i < nframes; i++, ncur++) { noffs = offs + xfer->frlengths[i]; if (ncur == OHCI_ITD_NOFFSET || /* all offsets used */ OHCI_PAGE(buf + noffs) > bp0 + OHCI_PAGE_SIZE) { /* too many page crossings */ /* Allocate next ITD */ nsitd = ohci_alloc_sitd(sc); if (nsitd == NULL) { /* XXX what now? */ printf("%s: isoc TD alloc failed\n", USBDEVNAME(sc->sc_bus.bdev)); return; } /* Fill current ITD */ 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->itd.itd_bp0 = htole32(bp0); sitd->nextitd = nsitd; sitd->itd.itd_nextitd = htole32(nsitd->physaddr); sitd->itd.itd_be = htole32(bp0 + offs - 1); sitd->xfer = xfer; sitd->flags = OHCI_ITD_ACTIVE; sitd = nsitd; iso->next = iso->next + ncur; bp0 = OHCI_PAGE(buf + offs); ncur = 0; } sitd->itd.itd_offset[ncur] = htole16(OHCI_ITD_MK_OFFS(offs)); offs = noffs; } nsitd = ohci_alloc_sitd(sc); if (nsitd == NULL) { /* XXX what now? */ printf("%s: isoc TD alloc failed\n", USBDEVNAME(sc->sc_bus.bdev)); return; } /* Fixup last used ITD */ 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->itd.itd_bp0 = htole32(bp0); sitd->nextitd = nsitd; sitd->itd.itd_nextitd = htole32(nsitd->physaddr); sitd->itd.itd_be = htole32(bp0 + offs - 1); sitd->xfer = xfer; sitd->flags = OHCI_CALL_DONE | OHCI_ITD_ACTIVE; iso->next = iso->next + ncur; iso->inuse += nframes; xfer->actlen = offs; /* XXX pretend we did it all */ xfer->status = USBD_IN_PROGRESS; oxfer->ohci_xfer_flags |= OHCI_ISOC_DIRTY; #ifdef USB_DEBUG if (ohcidebug > 5) { DPRINTF(("ohci_device_isoc_enter: frame=%d\n", le32toh(sc->sc_hcca->hcca_frame_number))); ohci_dump_itds(xfer->hcpriv); ohci_dump_ed(sed); } #endif s = splusb(); opipe->tail.itd = nsitd; sed->ed.ed_flags &= htole32(~OHCI_ED_SKIP); sed->ed.ed_tailp = htole32(nsitd->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, *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 ); s = splusb(); /* Run callback. */ usb_transfer_complete(xfer); if (sitd != NULL) /* * Only if there is a `next' sitd in next xfer... * unlink this xfer's sitds. */ sed->ed.ed_headp = htole32(sitd->physaddr); else sed->ed.ed_headp = 0; sed->ed.ed_flags &= htole32(~OHCI_ED_SKIP); /* remove hardware skip */ splx(s); } 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.*/ } Index: stable/4/sys/dev/usb/ohcireg.h =================================================================== --- stable/4/sys/dev/usb/ohcireg.h (revision 145576) +++ stable/4/sys/dev/usb/ohcireg.h (revision 145577) @@ -1,249 +1,249 @@ /* $NetBSD: ohcireg.h,v 1.17 2000/04/01 09:27:35 augustss Exp $ */ /* $FreeBSD$ */ -/* +/*- * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef _DEV_PCI_OHCIREG_H_ #define _DEV_PCI_OHCIREG_H_ /*** PCI config registers ***/ #define PCI_CBMEM 0x10 /* configuration base memory */ #define PCI_INTERFACE_OHCI 0x10 /*** OHCI registers */ #define OHCI_REVISION 0x00 /* OHCI revision # */ #define OHCI_REV_LO(rev) ((rev)&0xf) #define OHCI_REV_HI(rev) (((rev)>>4)&0xf) #define OHCI_REV_LEGACY(rev) ((rev) & 0x100) #define OHCI_CONTROL 0x04 #define OHCI_CBSR_MASK 0x00000003 /* Control/Bulk Service Ratio */ #define OHCI_RATIO_1_1 0x00000000 #define OHCI_RATIO_1_2 0x00000001 #define OHCI_RATIO_1_3 0x00000002 #define OHCI_RATIO_1_4 0x00000003 #define OHCI_PLE 0x00000004 /* Periodic List Enable */ #define OHCI_IE 0x00000008 /* Isochronous Enable */ #define OHCI_CLE 0x00000010 /* Control List Enable */ #define OHCI_BLE 0x00000020 /* Bulk List Enable */ #define OHCI_HCFS_MASK 0x000000c0 /* HostControllerFunctionalState */ #define OHCI_HCFS_RESET 0x00000000 #define OHCI_HCFS_RESUME 0x00000040 #define OHCI_HCFS_OPERATIONAL 0x00000080 #define OHCI_HCFS_SUSPEND 0x000000c0 #define OHCI_IR 0x00000100 /* Interrupt Routing */ #define OHCI_RWC 0x00000200 /* Remote Wakeup Connected */ #define OHCI_RWE 0x00000400 /* Remote Wakeup Enabled */ #define OHCI_COMMAND_STATUS 0x08 #define OHCI_HCR 0x00000001 /* Host Controller Reset */ #define OHCI_CLF 0x00000002 /* Control List Filled */ #define OHCI_BLF 0x00000004 /* Bulk List Filled */ #define OHCI_OCR 0x00000008 /* Ownership Change Request */ #define OHCI_SOC_MASK 0x00030000 /* Scheduling Overrun Count */ #define OHCI_INTERRUPT_STATUS 0x0c #define OHCI_SO 0x00000001 /* Scheduling Overrun */ #define OHCI_WDH 0x00000002 /* Writeback Done Head */ #define OHCI_SF 0x00000004 /* Start of Frame */ #define OHCI_RD 0x00000008 /* Resume Detected */ #define OHCI_UE 0x00000010 /* Unrecoverable Error */ #define OHCI_FNO 0x00000020 /* Frame Number Overflow */ #define OHCI_RHSC 0x00000040 /* Root Hub Status Change */ #define OHCI_OC 0x40000000 /* Ownership Change */ #define OHCI_MIE 0x80000000 /* Master Interrupt Enable */ #define OHCI_INTERRUPT_ENABLE 0x10 #define OHCI_INTERRUPT_DISABLE 0x14 #define OHCI_HCCA 0x18 #define OHCI_PERIOD_CURRENT_ED 0x1c #define OHCI_CONTROL_HEAD_ED 0x20 #define OHCI_CONTROL_CURRENT_ED 0x24 #define OHCI_BULK_HEAD_ED 0x28 #define OHCI_BULK_CURRENT_ED 0x2c #define OHCI_DONE_HEAD 0x30 #define OHCI_FM_INTERVAL 0x34 #define OHCI_GET_IVAL(s) ((s) & 0x3fff) #define OHCI_GET_FSMPS(s) (((s) >> 16) & 0x7fff) #define OHCI_FIT 0x80000000 #define OHCI_FM_REMAINING 0x38 #define OHCI_FM_NUMBER 0x3c #define OHCI_PERIODIC_START 0x40 #define OHCI_LS_THRESHOLD 0x44 #define OHCI_RH_DESCRIPTOR_A 0x48 #define OHCI_GET_NDP(s) ((s) & 0xff) #define OHCI_PSM 0x0100 /* Power Switching Mode */ #define OHCI_NPS 0x0200 /* No Power Switching */ #define OHCI_DT 0x0400 /* Device Type */ #define OHCI_OCPM 0x0800 /* Overcurrent Protection Mode */ #define OHCI_NOCP 0x1000 /* No Overcurrent Protection */ #define OHCI_GET_POTPGT(s) ((s) >> 24) #define OHCI_RH_DESCRIPTOR_B 0x4c #define OHCI_RH_STATUS 0x50 #define OHCI_LPS 0x00000001 /* Local Power Status */ #define OHCI_OCI 0x00000002 /* OverCurrent Indicator */ #define OHCI_DRWE 0x00008000 /* Device Remote Wakeup Enable */ #define OHCI_LPSC 0x00010000 /* Local Power Status Change */ #define OHCI_CCIC 0x00020000 /* OverCurrent Indicator Change */ #define OHCI_CRWE 0x80000000 /* Clear Remote Wakeup Enable */ #define OHCI_RH_PORT_STATUS(n) (0x50 + (n)*4) /* 1 based indexing */ #define OHCI_LES (OHCI_PLE | OHCI_IE | OHCI_CLE | OHCI_BLE) #define OHCI_ALL_INTRS (OHCI_SO | OHCI_WDH | OHCI_SF | OHCI_RD | OHCI_UE | \ OHCI_FNO | OHCI_RHSC | OHCI_OC) -#define OHCI_NORMAL_INTRS (OHCI_SO | OHCI_WDH | OHCI_RD | OHCI_UE | OHCI_RHSC) +#define OHCI_NORMAL_INTRS (OHCI_WDH | OHCI_RD | OHCI_UE | OHCI_RHSC) #define OHCI_FSMPS(i) (((i-210)*6/7) << 16) #define OHCI_PERIODIC(i) ((i)*9/10) typedef u_int32_t ohci_physaddr_t; #define OHCI_NO_INTRS 32 struct ohci_hcca { ohci_physaddr_t hcca_interrupt_table[OHCI_NO_INTRS]; u_int32_t hcca_frame_number; ohci_physaddr_t hcca_done_head; #define OHCI_DONE_INTRS 1 }; #define OHCI_HCCA_SIZE 256 #define OHCI_HCCA_ALIGN 256 #define OHCI_PAGE_SIZE 0x1000 #define OHCI_PAGE(x) ((x) &~ 0xfff) #define OHCI_PAGE_OFFSET(x) ((x) & 0xfff) #define OHCI_PAGE_MASK(x) ((x) & 0xfff) typedef struct { u_int32_t ed_flags; #define OHCI_ED_GET_FA(s) ((s) & 0x7f) #define OHCI_ED_ADDRMASK 0x0000007f #define OHCI_ED_SET_FA(s) (s) #define OHCI_ED_GET_EN(s) (((s) >> 7) & 0xf) #define OHCI_ED_SET_EN(s) ((s) << 7) #define OHCI_ED_DIR_MASK 0x00001800 #define OHCI_ED_DIR_TD 0x00000000 #define OHCI_ED_DIR_OUT 0x00000800 #define OHCI_ED_DIR_IN 0x00001000 #define OHCI_ED_SPEED 0x00002000 #define OHCI_ED_SKIP 0x00004000 #define OHCI_ED_FORMAT_GEN 0x00000000 #define OHCI_ED_FORMAT_ISO 0x00008000 #define OHCI_ED_GET_MAXP(s) (((s) >> 16) & 0x07ff) #define OHCI_ED_SET_MAXP(s) ((s) << 16) #define OHCI_ED_MAXPMASK (0x7ff << 16) ohci_physaddr_t ed_tailp; ohci_physaddr_t ed_headp; #define OHCI_HALTED 0x00000001 #define OHCI_TOGGLECARRY 0x00000002 #define OHCI_HEADMASK 0xfffffffc ohci_physaddr_t ed_nexted; } ohci_ed_t; /* #define OHCI_ED_SIZE 16 */ #define OHCI_ED_ALIGN 16 typedef struct { u_int32_t td_flags; #define OHCI_TD_R 0x00040000 /* Buffer Rounding */ #define OHCI_TD_DP_MASK 0x00180000 /* Direction / PID */ #define OHCI_TD_SETUP 0x00000000 #define OHCI_TD_OUT 0x00080000 #define OHCI_TD_IN 0x00100000 #define OHCI_TD_GET_DI(x) (((x) >> 21) & 7) /* Delay Interrupt */ #define OHCI_TD_SET_DI(x) ((x) << 21) #define OHCI_TD_NOINTR 0x00e00000 #define OHCI_TD_INTR_MASK 0x00e00000 #define OHCI_TD_TOGGLE_CARRY 0x00000000 #define OHCI_TD_TOGGLE_0 0x02000000 #define OHCI_TD_TOGGLE_1 0x03000000 #define OHCI_TD_TOGGLE_MASK 0x03000000 #define OHCI_TD_GET_EC(x) (((x) >> 26) & 3) /* Error Count */ #define OHCI_TD_GET_CC(x) ((x) >> 28) /* Condition Code */ #define OHCI_TD_NOCC 0xf0000000 ohci_physaddr_t td_cbp; /* Current Buffer Pointer */ ohci_physaddr_t td_nexttd; /* Next TD */ ohci_physaddr_t td_be; /* Buffer End */ } ohci_td_t; /* #define OHCI_TD_SIZE 16 */ #define OHCI_TD_ALIGN 16 #define OHCI_ITD_NOFFSET 8 typedef struct { u_int32_t itd_flags; #define OHCI_ITD_GET_SF(x) ((x) & 0x0000ffff) #define OHCI_ITD_SET_SF(x) ((x) & 0xffff) #define OHCI_ITD_GET_DI(x) (((x) >> 21) & 7) /* Delay Interrupt */ #define OHCI_ITD_SET_DI(x) ((x) << 21) #define OHCI_ITD_NOINTR 0x00e00000 #define OHCI_ITD_GET_FC(x) ((((x) >> 24) & 7)+1) /* Frame Count */ #define OHCI_ITD_SET_FC(x) (((x)-1) << 24) #define OHCI_ITD_GET_CC(x) ((x) >> 28) /* Condition Code */ #define OHCI_ITD_NOCC 0xf0000000 ohci_physaddr_t itd_bp0; /* Buffer Page 0 */ ohci_physaddr_t itd_nextitd; /* Next ITD */ ohci_physaddr_t itd_be; /* Buffer End */ u_int16_t itd_offset[OHCI_ITD_NOFFSET]; /* Buffer offsets */ #define itd_pswn itd_offset /* Packet Status Word*/ #define OHCI_ITD_PAGE_SELECT 0x00001000 #define OHCI_ITD_MK_OFFS(len) (0xe000 | ((len) & 0x1fff)) #define OHCI_ITD_PSW_LENGTH(x) ((x) & 0xfff) /* Transfer length */ #define OHCI_ITD_PSW_GET_CC(x) ((x) >> 12) /* Condition Code */ } ohci_itd_t; /* #define OHCI_ITD_SIZE 32 */ #define OHCI_ITD_ALIGN 32 #define OHCI_CC_NO_ERROR 0 #define OHCI_CC_CRC 1 #define OHCI_CC_BIT_STUFFING 2 #define OHCI_CC_DATA_TOGGLE_MISMATCH 3 #define OHCI_CC_STALL 4 #define OHCI_CC_DEVICE_NOT_RESPONDING 5 #define OHCI_CC_PID_CHECK_FAILURE 6 #define OHCI_CC_UNEXPECTED_PID 7 #define OHCI_CC_DATA_OVERRUN 8 #define OHCI_CC_DATA_UNDERRUN 9 #define OHCI_CC_BUFFER_OVERRUN 12 #define OHCI_CC_BUFFER_UNDERRUN 13 #define OHCI_CC_NOT_ACCESSED 15 /* Some delay needed when changing certain registers. */ #define OHCI_ENABLE_POWER_DELAY 5 #define OHCI_READ_DESC_DELAY 5 #endif /* _DEV_PCI_OHCIREG_H_ */ Index: stable/4/sys/dev/usb/ohcivar.h =================================================================== --- stable/4/sys/dev/usb/ohcivar.h (revision 145576) +++ stable/4/sys/dev/usb/ohcivar.h (revision 145577) @@ -1,167 +1,177 @@ /* $NetBSD: ohcivar.h,v 1.30 2001/12/31 12:20:35 augustss Exp $ */ /* $FreeBSD$ */ -/* +/*- * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ typedef struct ohci_soft_ed { ohci_ed_t ed; struct ohci_soft_ed *next; ohci_physaddr_t physaddr; } ohci_soft_ed_t; #define OHCI_SED_SIZE ((sizeof (struct ohci_soft_ed) + OHCI_ED_ALIGN - 1) / OHCI_ED_ALIGN * OHCI_ED_ALIGN) #define OHCI_SED_CHUNK (PAGE_SIZE / OHCI_SED_SIZE) typedef struct ohci_soft_td { ohci_td_t td; struct ohci_soft_td *nexttd; /* mirrors nexttd in TD */ struct ohci_soft_td *dnext; /* next in done list */ ohci_physaddr_t physaddr; LIST_ENTRY(ohci_soft_td) hnext; usbd_xfer_handle xfer; u_int16_t len; u_int16_t flags; #define OHCI_CALL_DONE 0x0001 #define OHCI_ADD_LEN 0x0002 #define OHCI_TD_HANDLED 0x0004 /* signal process_done has seen it */ } ohci_soft_td_t; #define OHCI_STD_SIZE ((sizeof (struct ohci_soft_td) + OHCI_TD_ALIGN - 1) / OHCI_TD_ALIGN * OHCI_TD_ALIGN) #define OHCI_STD_CHUNK (PAGE_SIZE / OHCI_STD_SIZE) typedef struct ohci_soft_itd { ohci_itd_t itd; struct ohci_soft_itd *nextitd; /* mirrors nexttd in ITD */ struct ohci_soft_itd *dnext; /* next in done list */ ohci_physaddr_t physaddr; LIST_ENTRY(ohci_soft_itd) hnext; usbd_xfer_handle xfer; u_int16_t flags; #define OHCI_ITD_ACTIVE 0x0010 /* Hardware op in progress */ #define OHCI_ITD_INTFIN 0x0020 /* Hw completion interrupt seen.*/ #ifdef DIAGNOSTIC char isdone; #endif } ohci_soft_itd_t; #define OHCI_SITD_SIZE ((sizeof (struct ohci_soft_itd) + OHCI_ITD_ALIGN - 1) / OHCI_ITD_ALIGN * OHCI_ITD_ALIGN) #define OHCI_SITD_CHUNK (PAGE_SIZE / OHCI_SITD_SIZE) #define OHCI_NO_EDS (2*OHCI_NO_INTRS-1) #define OHCI_HASH_SIZE 128 +#define OHCI_SCFLG_DONEINIT 0x0001 /* ohci_init() done. */ + typedef struct ohci_softc { struct usbd_bus sc_bus; /* base device */ + int sc_flags; bus_space_tag_t iot; bus_space_handle_t ioh; bus_size_t sc_size; #if defined(__FreeBSD__) void *ih; struct resource *io_res; struct resource *irq_res; #endif usb_dma_t sc_hccadma; struct ohci_hcca *sc_hcca; ohci_soft_ed_t *sc_eds[OHCI_NO_EDS]; u_int sc_bws[OHCI_NO_INTRS]; u_int32_t sc_eintrs; /* enabled interrupts */ ohci_soft_ed_t *sc_isoc_head; ohci_soft_ed_t *sc_ctrl_head; ohci_soft_ed_t *sc_bulk_head; LIST_HEAD(, ohci_soft_td) sc_hash_tds[OHCI_HASH_SIZE]; LIST_HEAD(, ohci_soft_itd) sc_hash_itds[OHCI_HASH_SIZE]; int sc_noport; u_int8_t sc_addr; /* device address */ u_int8_t sc_conf; /* device configuration */ #ifdef USB_USE_SOFTINTR char sc_softwake; #endif /* USB_USE_SOFTINTR */ ohci_soft_ed_t *sc_freeeds; ohci_soft_td_t *sc_freetds; ohci_soft_itd_t *sc_freeitds; SIMPLEQ_HEAD(, usbd_xfer) sc_free_xfers; /* free xfers */ usbd_xfer_handle sc_intrxfer; ohci_soft_itd_t *sc_sidone; ohci_soft_td_t *sc_sdone; char sc_vendor[16]; int sc_id_vendor; #if defined(__NetBSD__) || defined(__OpenBSD__) void *sc_powerhook; /* cookie from power hook */ void *sc_shutdownhook; /* cookie from shutdown hook */ #endif u_int32_t sc_control; /* Preserved during suspend/standby */ u_int32_t sc_intre; u_int sc_overrun_cnt; struct timeval sc_overrun_ntc; usb_callout_t sc_tmo_rhsc; +#if defined(__NetBSD__) || defined(__OpenBSD__) device_ptr_t sc_child; +#endif char sc_dying; } ohci_softc_t; struct ohci_xfer { struct usbd_xfer xfer; struct usb_task abort_task; u_int32_t ohci_xfer_flags; }; #define OHCI_ISOC_DIRTY 0x01 +#define OHCI_XFER_ABORTING 0x02 /* xfer is aborting. */ +#define OHCI_XFER_ABORTWAIT 0x04 /* abort completion is being awaited. */ #define OXFER(xfer) ((struct ohci_xfer *)(xfer)) usbd_status ohci_init(ohci_softc_t *); int ohci_intr(void *); +int ohci_detach(ohci_softc_t *, int); #if defined(__NetBSD__) || defined(__OpenBSD__) -int ohci_detach(ohci_softc_t *, int); int ohci_activate(device_ptr_t, enum devact); #endif #define MS_TO_TICKS(ms) ((ms) * hz / 1000) + +void ohci_shutdown(void *v); +void ohci_power(int state, void *priv); Index: stable/4/sys/dev/usb/uhci.c =================================================================== --- stable/4/sys/dev/usb/uhci.c (revision 145576) +++ stable/4/sys/dev/usb/uhci.c (revision 145577) @@ -1,3538 +1,3583 @@ /* $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 #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__) #include #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 *); #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, usb_dma_t *, 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_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 *); +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 * +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 = bsqh; 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); SIMPLEQ_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_ptr_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); +#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 = SIMPLEQ_FIRST(&sc->sc_free_xfers); if (xfer == NULL) break; SIMPLEQ_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); } -#endif 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 = SIMPLEQ_FIRST(&sc->sc_free_xfers); if (xfer != NULL) { SIMPLEQ_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 SIMPLEQ_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", USBDEVNAME(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; uhci_softc_t *sc = (uhci_softc_t *)pipe->device->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 = KERNADDR(&xfer->dmabuf, 0); 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(); xfer->device->bus->intr_context++; usb_transfer_complete(xfer); xfer->device->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", USBDEVNAME(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", USBDEVNAME(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", USBDEVNAME(sc->sc_bus.bdev)); #endif } if (status & UHCI_STS_HSE) { ack |= UHCI_STS_HSE; printf("%s: host system error\n", USBDEVNAME(sc->sc_bus.bdev)); } if (status & UHCI_STS_HCPE) { ack |= UHCI_STS_HCPE; printf("%s: host controller process error\n", USBDEVNAME(sc->sc_bus.bdev)); } if (status & UHCI_STS_HCH) { /* no acknowledge needed */ if (!sc->sc_dying) { printf("%s: host controller halted\n", USBDEVNAME(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", USBDEVNAME(sc->sc_bus.bdev))); return (1); } +/* not in 4.x */ +#define LIST_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = LIST_FIRST((head)); \ + (var) && ((tvar) = LIST_NEXT((var), field), 1); \ + (var) = (tvar)) + void uhci_softintr(void *v) { uhci_softc_t *sc = v; - uhci_intr_info_t *ii; + uhci_intr_info_t *ii, *nextii; DPRINTFN(10,("%s: uhci_softintr (%d)\n", USBDEVNAME(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(ii, &sc->sc_intrhead, list) + 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: usb_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_init_task(&uxfer->abort_task, uhci_timeout_task, ii->xfer); usb_add_task(uxfer->xfer.pipe->device, &uxfer->abort_task); } 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_USBINT) + 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_USBINT) + 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", USBDEVNAME(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", USBDEVNAME(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; 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 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, usb_dma_t *dma, uhci_soft_td_t **sp, uhci_soft_td_t **ep) { uhci_soft_td_t *p, *lastp; uhci_physaddr_t lastlink; int i, ntd, l, tog, maxp; 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 ((flags & USBD_FORCE_SHORT_XFER) && len % maxp == 0) ntd++; DPRINTFN(10, ("uhci_alloc_std_chain: maxp=%d ntd=%d\n", maxp, ntd)); if (ntd == 0) { *sp = *ep = 0; DPRINTFN(-1,("uhci_alloc_std_chain: ntd=0\n")); return (USBD_NORMAL_COMPLETION); } tog = upipe->nexttoggle; if (ntd % 2 == 0) tog ^= 1; upipe->nexttoggle = tog ^ 1; lastp = NULL; lastlink = UHCI_PTR_T; ntd--; 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; for (i = ntd; i >= 0; i--) { p = uhci_alloc_std(sc); if (p == NULL) { uhci_free_std_chain(sc, lastp, NULL); return (USBD_NOMEM); } p->link.std = lastp; p->td.td_link = htole32(lastlink | UHCI_PTR_VF | UHCI_PTR_TD); lastp = p; lastlink = p->physaddr; p->td.td_status = htole32(status); if (i == ntd) { /* 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)); p->td.td_buffer = htole32(DMAADDR(dma, i * maxp)); tog ^= 1; } *sp = lastp; DPRINTFN(10, ("uhci_alloc_std_chain: nexttog=%d\n", upipe->nexttoggle)); return (USBD_NORMAL_COMPLETION); } 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(SIMPLEQ_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->dmabuf, &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) { - uhci_intr_info_t *ii = &UXFER(xfer)->iinfo; + 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); usb_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. */ - xfer->hcpriv = ii; - 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); + } usb_transfer_complete(xfer); splx(s); } /* 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); } 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(SIMPLEQ_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(SIMPLEQ_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->dmabuf, &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->dmabuf, &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(SIMPLEQ_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; u_int32_t buf, len, status; int s, i, next, nframes; 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; buf = DMAADDR(&xfer->dmabuf, 0); 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]; std->td.td_buffer = htole32(buf); 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 buf += 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. */ usb_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 } 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->dmabuf, &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); } } /* 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); 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); 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 = 0; 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(SIMPLEQ_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 = KERNADDR(&xfer->dmabuf, 0); #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 != 0) { + 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(); usb_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 usb_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(SIMPLEQ_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")); } Index: stable/4/sys/dev/usb/uhcireg.h =================================================================== --- stable/4/sys/dev/usb/uhcireg.h (revision 145576) +++ stable/4/sys/dev/usb/uhcireg.h (revision 145577) @@ -1,193 +1,193 @@ /* $NetBSD: uhcireg.h,v 1.15 2002/02/11 11:41:30 augustss Exp $ */ /* $FreeBSD$ */ -/* +/*- * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifndef _DEV_PCI_UHCIREG_H_ #define _DEV_PCI_UHCIREG_H_ /*** PCI config registers ***/ #define PCI_USBREV 0x60 /* USB protocol revision */ #define PCI_USBREV_MASK 0xff #define PCI_USBREV_PRE_1_0 0x00 #define PCI_USBREV_1_0 0x10 #define PCI_USBREV_1_1 0x11 #define PCI_LEGSUP 0xc0 /* Legacy Support register */ #define PCI_LEGSUP_USBPIRQDEN 0x2000 /* USB PIRQ D Enable */ #define PCI_CBIO 0x20 /* configuration base IO */ #define PCI_INTERFACE_UHCI 0x00 /*** UHCI registers ***/ #define UHCI_CMD 0x00 #define UHCI_CMD_RS 0x0001 #define UHCI_CMD_HCRESET 0x0002 #define UHCI_CMD_GRESET 0x0004 #define UHCI_CMD_EGSM 0x0008 #define UHCI_CMD_FGR 0x0010 #define UHCI_CMD_SWDBG 0x0020 #define UHCI_CMD_CF 0x0040 #define UHCI_CMD_MAXP 0x0080 #define UHCI_STS 0x02 #define UHCI_STS_USBINT 0x0001 #define UHCI_STS_USBEI 0x0002 #define UHCI_STS_RD 0x0004 #define UHCI_STS_HSE 0x0008 #define UHCI_STS_HCPE 0x0010 #define UHCI_STS_HCH 0x0020 #define UHCI_STS_ALLINTRS 0x003f #define UHCI_INTR 0x04 #define UHCI_INTR_TOCRCIE 0x0001 #define UHCI_INTR_RIE 0x0002 #define UHCI_INTR_IOCE 0x0004 #define UHCI_INTR_SPIE 0x0008 #define UHCI_FRNUM 0x06 #define UHCI_FRNUM_MASK 0x03ff #define UHCI_FLBASEADDR 0x08 #define UHCI_SOF 0x0c #define UHCI_SOF_MASK 0x7f #define UHCI_PORTSC1 0x010 #define UHCI_PORTSC2 0x012 #define UHCI_PORTSC_CCS 0x0001 #define UHCI_PORTSC_CSC 0x0002 #define UHCI_PORTSC_PE 0x0004 #define UHCI_PORTSC_POEDC 0x0008 #define UHCI_PORTSC_LS 0x0030 #define UHCI_PORTSC_LS_SHIFT 4 #define UHCI_PORTSC_RD 0x0040 #define UHCI_PORTSC_LSDA 0x0100 #define UHCI_PORTSC_PR 0x0200 #define UHCI_PORTSC_OCI 0x0400 #define UHCI_PORTSC_OCIC 0x0800 #define UHCI_PORTSC_SUSP 0x1000 #define URWMASK(x) \ ((x) & (UHCI_PORTSC_SUSP | UHCI_PORTSC_PR | UHCI_PORTSC_RD | UHCI_PORTSC_PE)) #define UHCI_FRAMELIST_COUNT 1024 #define UHCI_FRAMELIST_ALIGN 4096 #define UHCI_TD_ALIGN 16 #define UHCI_QH_ALIGN 16 typedef u_int32_t uhci_physaddr_t; #define UHCI_PTR_T 0x00000001 #define UHCI_PTR_TD 0x00000000 #define UHCI_PTR_QH 0x00000002 #define UHCI_PTR_VF 0x00000004 /* * Wait this long after a QH has been removed. This gives that HC a * chance to stop looking at it before it's recycled. */ #define UHCI_QH_REMOVE_DELAY 5 /* * The Queue Heads and Transfer Descriptors are accessed * by both the CPU and the USB controller which run * concurrently. This means that they have to be accessed * with great care. As long as the data structures are * not linked into the controller's frame list they cannot * be accessed by it and anything goes. As soon as a * TD is accessible by the controller it "owns" the td_status * field; it will not be written by the CPU. Similarly * the controller "owns" the qh_elink field. */ typedef struct { uhci_physaddr_t td_link; u_int32_t td_status; #define UHCI_TD_GET_ACTLEN(s) (((s) + 1) & 0x3ff) #define UHCI_TD_ZERO_ACTLEN(t) ((t) | 0x3ff) #define UHCI_TD_BITSTUFF 0x00020000 #define UHCI_TD_CRCTO 0x00040000 #define UHCI_TD_NAK 0x00080000 #define UHCI_TD_BABBLE 0x00100000 #define UHCI_TD_DBUFFER 0x00200000 #define UHCI_TD_STALLED 0x00400000 #define UHCI_TD_ACTIVE 0x00800000 #define UHCI_TD_IOC 0x01000000 #define UHCI_TD_IOS 0x02000000 #define UHCI_TD_LS 0x04000000 #define UHCI_TD_GET_ERRCNT(s) (((s) >> 27) & 3) #define UHCI_TD_SET_ERRCNT(n) ((n) << 27) #define UHCI_TD_SPD 0x20000000 u_int32_t td_token; #define UHCI_TD_PID_IN 0x00000069 #define UHCI_TD_PID_OUT 0x000000e1 #define UHCI_TD_PID_SETUP 0x0000002d #define UHCI_TD_GET_PID(s) ((s) & 0xff) #define UHCI_TD_SET_DEVADDR(a) ((a) << 8) #define UHCI_TD_GET_DEVADDR(s) (((s) >> 8) & 0x7f) #define UHCI_TD_SET_ENDPT(e) (((e)&0xf) << 15) #define UHCI_TD_GET_ENDPT(s) (((s) >> 15) & 0xf) #define UHCI_TD_SET_DT(t) ((t) << 19) #define UHCI_TD_GET_DT(s) (((s) >> 19) & 1) #define UHCI_TD_SET_MAXLEN(l) (((l)-1) << 21) #define UHCI_TD_GET_MAXLEN(s) ((((s) >> 21) + 1) & 0x7ff) #define UHCI_TD_MAXLEN_MASK 0xffe00000 u_int32_t td_buffer; } uhci_td_t; #define UHCI_TD_ERROR (UHCI_TD_BITSTUFF|UHCI_TD_CRCTO|UHCI_TD_BABBLE|UHCI_TD_DBUFFER|UHCI_TD_STALLED) #define UHCI_TD_SETUP(len, endp, dev) (UHCI_TD_SET_MAXLEN(len) | \ UHCI_TD_SET_ENDPT(endp) | UHCI_TD_SET_DEVADDR(dev) | UHCI_TD_PID_SETUP) #define UHCI_TD_OUT(len, endp, dev, dt) (UHCI_TD_SET_MAXLEN(len) | \ UHCI_TD_SET_ENDPT(endp) | UHCI_TD_SET_DEVADDR(dev) | \ UHCI_TD_PID_OUT | UHCI_TD_SET_DT(dt)) #define UHCI_TD_IN(len, endp, dev, dt) (UHCI_TD_SET_MAXLEN(len) | \ UHCI_TD_SET_ENDPT(endp) | UHCI_TD_SET_DEVADDR(dev) | UHCI_TD_PID_IN | \ UHCI_TD_SET_DT(dt)) typedef struct { uhci_physaddr_t qh_hlink; uhci_physaddr_t qh_elink; } uhci_qh_t; #endif /* _DEV_PCI_UHCIREG_H_ */ Index: stable/4/sys/dev/usb/uhcivar.h =================================================================== --- stable/4/sys/dev/usb/uhcivar.h (revision 145576) +++ stable/4/sys/dev/usb/uhcivar.h (revision 145577) @@ -1,205 +1,214 @@ /* $NetBSD: uhcivar.h,v 1.33 2002/02/11 11:41:30 augustss Exp $ */ /* $FreeBSD$ */ -/* +/*- * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * To avoid having 1024 TDs for each isochronous transfer we introduce * a virtual frame list. Every UHCI_VFRAMELIST_COUNT entries in the real * frame list points to a non-active TD. These, in turn, form the * starts of the virtual frame list. This also has the advantage that it * simplifies linking in/out of TDs/QHs in the schedule. * Furthermore, initially each of the inactive TDs point to an inactive * QH that forms the start of the interrupt traffic for that slot. * Each of these QHs point to the same QH that is the start of control * traffic. This QH points at another QH which is the start of the * bulk traffic. * * UHCI_VFRAMELIST_COUNT should be a power of 2 and <= UHCI_FRAMELIST_COUNT. */ #define UHCI_VFRAMELIST_COUNT 128 typedef struct uhci_soft_qh uhci_soft_qh_t; typedef struct uhci_soft_td uhci_soft_td_t; typedef union { struct uhci_soft_qh *sqh; struct uhci_soft_td *std; } uhci_soft_td_qh_t; /* * An interrupt info struct contains the information needed to * execute a requested routine when the controller generates an * interrupt. Since we cannot know which transfer generated * the interrupt all structs are linked together so they can be * searched at interrupt time. */ typedef struct uhci_intr_info { struct uhci_softc *sc; usbd_xfer_handle xfer; uhci_soft_td_t *stdstart; uhci_soft_td_t *stdend; LIST_ENTRY(uhci_intr_info) list; #ifdef DIAGNOSTIC int isdone; #endif } uhci_intr_info_t; struct uhci_xfer { struct usbd_xfer xfer; uhci_intr_info_t iinfo; struct usb_task abort_task; int curframe; + u_int32_t uhci_xfer_flags; }; +#define UHCI_XFER_ABORTING 0x0001 /* xfer is aborting. */ +#define UHCI_XFER_ABORTWAIT 0x0002 /* abort completion is being awaited. */ + #define UXFER(xfer) ((struct uhci_xfer *)(xfer)) /* * Extra information that we need for a TD. */ struct uhci_soft_td { uhci_td_t td; /* The real TD, must be first */ uhci_soft_td_qh_t link; /* soft version of the td_link field */ uhci_physaddr_t physaddr; /* TD's physical address. */ }; /* * Make the size such that it is a multiple of UHCI_TD_ALIGN. This way * we can pack a number of soft TD together and have the real TD well * aligned. * NOTE: Minimum size is 32 bytes. */ #define UHCI_STD_SIZE ((sizeof (struct uhci_soft_td) + UHCI_TD_ALIGN - 1) / UHCI_TD_ALIGN * UHCI_TD_ALIGN) #define UHCI_STD_CHUNK (PAGE_SIZE / UHCI_STD_SIZE) /* * Extra information that we need for a QH. */ struct uhci_soft_qh { uhci_qh_t qh; /* The real QH, must be first */ uhci_soft_qh_t *hlink; /* soft version of qh_hlink */ uhci_soft_td_t *elink; /* soft version of qh_elink */ uhci_physaddr_t physaddr; /* QH's physical address. */ int pos; /* Timeslot position */ }; /* See comment about UHCI_STD_SIZE. */ #define UHCI_SQH_SIZE ((sizeof (struct uhci_soft_qh) + UHCI_QH_ALIGN - 1) / UHCI_QH_ALIGN * UHCI_QH_ALIGN) #define UHCI_SQH_CHUNK (PAGE_SIZE / UHCI_SQH_SIZE) /* * Information about an entry in the virtual frame list. */ struct uhci_vframe { uhci_soft_td_t *htd; /* pointer to dummy TD */ uhci_soft_td_t *etd; /* pointer to last TD */ uhci_soft_qh_t *hqh; /* pointer to dummy QH */ uhci_soft_qh_t *eqh; /* pointer to last QH */ u_int bandwidth; /* max bandwidth used by this frame */ }; +#define UHCI_SCFLG_DONEINIT 0x0001 /* uhci_init() done */ + typedef struct uhci_softc { struct usbd_bus sc_bus; /* base device */ + int sc_flags; bus_space_tag_t iot; bus_space_handle_t ioh; bus_size_t sc_size; #if defined(__FreeBSD__) void *ih; struct resource *io_res; struct resource *irq_res; #endif uhci_physaddr_t *sc_pframes; usb_dma_t sc_dma; struct uhci_vframe sc_vframes[UHCI_VFRAMELIST_COUNT]; uhci_soft_qh_t *sc_lctl_start; /* dummy QH for low speed control */ uhci_soft_qh_t *sc_lctl_end; /* last control QH */ uhci_soft_qh_t *sc_hctl_start; /* dummy QH for high speed control */ uhci_soft_qh_t *sc_hctl_end; /* last control QH */ uhci_soft_qh_t *sc_bulk_start; /* dummy QH for bulk */ uhci_soft_qh_t *sc_bulk_end; /* last bulk transfer */ uhci_soft_qh_t *sc_last_qh; /* dummy QH at the end */ u_int32_t sc_loops; /* number of QHs that wants looping */ uhci_soft_td_t *sc_freetds; /* TD free list */ uhci_soft_qh_t *sc_freeqhs; /* QH free list */ SIMPLEQ_HEAD(, usbd_xfer) sc_free_xfers; /* free xfers */ u_int8_t sc_addr; /* device address */ u_int8_t sc_conf; /* device configuration */ u_int8_t sc_saved_sof; u_int16_t sc_saved_frnum; #ifdef USB_USE_SOFTINTR char sc_softwake; #endif /* USB_USE_SOFTINTR */ char sc_isreset; char sc_suspend; char sc_dying; LIST_HEAD(, uhci_intr_info) sc_intrhead; /* Info for the root hub interrupt channel. */ int sc_ival; /* time between root hub intrs */ usbd_xfer_handle sc_intr_xfer; /* root hub interrupt transfer */ usb_callout_t sc_poll_handle; char sc_vendor[16]; /* vendor string for root hub */ int sc_id_vendor; /* vendor ID for root hub */ #if defined(__NetBSD__) void *sc_powerhook; /* cookie from power hook */ void *sc_shutdownhook; /* cookie from shutdown hook */ #endif +#if defined(__NetBSD__) || defined(__OpenBSD__) device_ptr_t sc_child; /* /dev/usb# device */ +#endif } uhci_softc_t; usbd_status uhci_init(uhci_softc_t *); int uhci_intr(void *); -#if defined(__NetBSD__) || defined(__OpenBSD__) int uhci_detach(uhci_softc_t *, int); +#if defined(__NetBSD__) || defined(__OpenBSD__) int uhci_activate(device_ptr_t, enum devact); #endif void uhci_shutdown(void *v); void uhci_power(int state, void *priv); Index: stable/4/sys/dev/usb/usb.c =================================================================== --- stable/4/sys/dev/usb/usb.c (revision 145576) +++ stable/4/sys/dev/usb/usb.c (revision 145577) @@ -1,922 +1,982 @@ /* $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/data/ and - * http://www.usb.org/developers/index.html . + * 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 #endif #include #include #include #include #if __FreeBSD_version >= 500014 #include #else #include #endif -#include +#include /* IO_NDELAY */ #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 { USBBASEDEVICE sc_dev; /* base device */ +#ifdef __FreeBSD__ + dev_t 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; #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_open = usbopen, .d_close = usbclose, .d_read = usbread, .d_ioctl = usbioctl, .d_poll = usbpoll, .d_name = "usb", .d_maj = USB_CDEV_MAJOR, #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 dev_t 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); - static int global_init_done = 0; #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", USBDEVNAME(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 = USBDEVUNIT(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", USBDEVNAME(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", USBDEVNAME(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) + 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)) -#endif dev->hub->explore(sc->sc_bus->root_hub); #endif +#endif } else { printf("%s: root hub problem, error=%d\n", USBDEVNAME(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 */ - make_dev(&usb_cdevsw, device_get_unit(self), UID_ROOT, GID_OPERATOR, - 0660, "usb%d", device_get_unit(self)); - if (!global_init_done) { + 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 */ - make_dev(&usb_cdevsw, USB_DEV_MINOR, UID_ROOT, GID_OPERATOR, - 0660, "usb"); - global_init_done = 1; + usb_dev = make_dev(&usb_cdevsw, USB_DEV_MINOR, UID_ROOT, + GID_OPERATOR, 0660, "usb"); } #endif USB_ATTACH_SUCCESS_RETURN; } void usb_create_event_thread(void *arg) { struct usb_softc *sc = arg; - static int created = 0; if (usb_kthread_create1(usb_event_thread, sc, &sc->sc_event_thread, "%s", USBDEVNAME(sc->sc_dev))) { printf("%s: unable to create event thread for\n", USBDEVNAME(sc->sc_dev)); panic("usb_create_event_thread"); } - if (!created) { - created = 1; + 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"); } } } /* * 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) { int s; s = splusb(); if (!task->onqueue) { DPRINTFN(2,("usb_add_task: task=%p\n", task)); TAILQ_INSERT_TAIL(&usb_all_tasks, task, next); task->onqueue = 1; } else { DPRINTFN(3,("usb_add_task: task=%p on q\n", task)); } wakeup(&usb_all_tasks); splx(s); } void usb_rem_task(usbd_device_handle dev, struct usb_task *task) { int s; s = splusb(); if (task->onqueue) { TAILQ_REMOVE(&usb_all_tasks, task, next); task->onqueue = 0; } splx(s); } void usb_event_thread(void *arg) { 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. * XXX It would be nicer to do this with a tsleep(), but I don't * know how to synchronize the creation of the threads so it * will work. */ usb_delay_ms(sc->sc_bus, 500); /* 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; int s; #if defined(__FreeBSD__) && __FreeBSD_version >= 500000 mtx_lock(&Giant); #endif DPRINTF(("usb_task_thread: start\n")); s = splusb(); - for (;;) { + while (usb_ndevs > 0) { task = TAILQ_FIRST(&usb_all_tasks); if (task == NULL) { tsleep(&usb_all_tasks, PWAIT, "usbtsk", 0); task = TAILQ_FIRST(&usb_all_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; splx(s); task->fun(task->arg); s = splusb(); } } + splx(s); + + usb_taskcreated = 0; + wakeup(&usb_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(dev_t 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(dev_t 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 & IO_NDELAY) { 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(dev_t 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(dev_t 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_procp = 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(dev_t 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_ptr_t dev) { struct usb_event ue; ue.u.ue_driver.ue_cookie = udev->cookie; strncpy(ue.u.ue_driver.ue_devname, USBDEVPTRNAME(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); selwakeup(&usb_selevent); 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_ptr_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 -int -usb_detach(device_ptr_t self, int flags) +USB_DETACH(usb) { - struct usb_softc *sc = (struct usb_softc *)self; + USB_DETACH_START(usb, sc); struct usb_event ue; 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", USBDEVNAME(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"); + } +#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 = USBDEVUNIT(sc->sc_dev); usb_add_event(USB_EVENT_CTRLR_DETACH, &ue); return (0); } -#elif defined(__FreeBSD__) -int -usb_detach(device_t self) + +#if defined(__FreeBSD__) +Static void +usb_child_detached(device_t self, device_t child) { - DPRINTF(("%s: unload, prevented\n", USBDEVNAME(self))); + struct usb_softc *sc = device_get_softc(self); - return (EINVAL); + /* XXX, should check it is the right device. */ + sc->sc_port.device = NULL; } -#endif +/* Explore USB busses at the end of device configuration. */ +Static void +usb_cold_explore(void *arg) +{ + struct usb_softc *sc; -#if defined(__FreeBSD__) + 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); +SYSINIT(usb_cold_explore, SI_SUB_INT_CONFIG_HOOKS, SI_ORDER_FIRST, + usb_cold_explore, NULL); #endif Index: stable/4/sys/pci/ohci_pci.c =================================================================== --- stable/4/sys/pci/ohci_pci.c (revision 145576) +++ stable/4/sys/pci/ohci_pci.c (revision 145577) @@ -1,327 +1,365 @@ /*- * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (augustss@carlstedt.se) 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. */ #include __FBSDID("$FreeBSD$"); /* * USB Open Host Controller driver. * * OHCI spec: http://www.intel.com/design/usb/ohci11d.pdf */ /* The low level controller code for OHCI has been split into * PCI probes and OHCI specific code. This was done to facilitate the * sharing of code between *BSD's */ #include "opt_bus.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define PCI_OHCI_VENDORID_ACERLABS 0x10b9 #define PCI_OHCI_VENDORID_AMD 0x1022 #define PCI_OHCI_VENDORID_APPLE 0x106b #define PCI_OHCI_VENDORID_CMDTECH 0x1095 #define PCI_OHCI_VENDORID_NEC 0x1033 #define PCI_OHCI_VENDORID_NVIDIA 0x12D2 +#define PCI_OHCI_VENDORID_NVIDIA2 0x10DE #define PCI_OHCI_VENDORID_OPTI 0x1045 #define PCI_OHCI_VENDORID_SIS 0x1039 #define PCI_OHCI_DEVICEID_ALADDIN_V 0x523710b9 static const char *ohci_device_aladdin_v = "AcerLabs M5237 (Aladdin-V) USB controller"; #define PCI_OHCI_DEVICEID_AMD756 0x740c1022 static const char *ohci_device_amd756 = "AMD-756 USB Controller"; #define PCI_OHCI_DEVICEID_AMD766 0x74141022 static const char *ohci_device_amd766 = "AMD-766 USB Controller"; #define PCI_OHCI_DEVICEID_FIRELINK 0xc8611045 static const char *ohci_device_firelink = "OPTi 82C861 (FireLink) USB controller"; #define PCI_OHCI_DEVICEID_NEC 0x00351033 static const char *ohci_device_nec = "NEC uPD 9210 USB controller"; #define PCI_OHCI_DEVICEID_NFORCE3 0x00d710de static const char *ohci_device_nforce3 = "nVidia nForce3 USB Controller"; #define PCI_OHCI_DEVICEID_USB0670 0x06701095 static const char *ohci_device_usb0670 = "CMD Tech 670 (USB0670) USB controller"; #define PCI_OHCI_DEVICEID_USB0673 0x06731095 static const char *ohci_device_usb0673 = "CMD Tech 673 (USB0673) USB controller"; #define PCI_OHCI_DEVICEID_SIS5571 0x70011039 static const char *ohci_device_sis5571 = "SiS 5571 USB controller"; #define PCI_OHCI_DEVICEID_KEYLARGO 0x0019106b static const char *ohci_device_keylargo = "Apple KeyLargo USB controller"; static const char *ohci_device_generic = "OHCI (generic) USB controller"; #define PCI_OHCI_BASE_REG 0x10 static int ohci_pci_attach(device_t self); static int ohci_pci_detach(device_t self); +static int ohci_pci_suspend(device_t self); +static int ohci_pci_resume(device_t self); +static int +ohci_pci_suspend(device_t self) +{ + ohci_softc_t *sc = device_get_softc(self); + int err; + + err = bus_generic_suspend(self); + if (err) + return err; + ohci_power(PWR_SUSPEND, sc); + + return 0; +} + +static int +ohci_pci_resume(device_t self) +{ + ohci_softc_t *sc = device_get_softc(self); + u_int32_t reg, int_line; + + if (pci_get_powerstate(self) != PCI_POWERSTATE_D0) { + device_printf(self, "chip is in D%d mode " + "-- setting to D0\n", pci_get_powerstate(self)); + reg = pci_read_config(self, PCI_CBMEM, 4); + int_line = pci_read_config(self, PCIR_INTLINE, 4); + pci_set_powerstate(self, PCI_POWERSTATE_D0); + pci_write_config(self, PCI_CBMEM, reg, 4); + pci_write_config(self, PCIR_INTLINE, int_line, 4); + } + + ohci_power(PWR_RESUME, sc); + bus_generic_resume(self); + + return 0; +} + static const char * ohci_pci_match(device_t self) { u_int32_t device_id = pci_get_devid(self); switch (device_id) { case PCI_OHCI_DEVICEID_ALADDIN_V: return (ohci_device_aladdin_v); case PCI_OHCI_DEVICEID_AMD756: return (ohci_device_amd756); case PCI_OHCI_DEVICEID_AMD766: return (ohci_device_amd766); case PCI_OHCI_DEVICEID_USB0670: return (ohci_device_usb0670); case PCI_OHCI_DEVICEID_USB0673: return (ohci_device_usb0673); case PCI_OHCI_DEVICEID_FIRELINK: return (ohci_device_firelink); case PCI_OHCI_DEVICEID_NEC: return (ohci_device_nec); case PCI_OHCI_DEVICEID_NFORCE3: return (ohci_device_nforce3); case PCI_OHCI_DEVICEID_SIS5571: return (ohci_device_sis5571); case PCI_OHCI_DEVICEID_KEYLARGO: return (ohci_device_keylargo); default: if (pci_get_class(self) == PCIC_SERIALBUS && pci_get_subclass(self) == PCIS_SERIALBUS_USB && pci_get_progif(self) == PCI_INTERFACE_OHCI) { return (ohci_device_generic); } } return NULL; /* dunno */ } static int ohci_pci_probe(device_t self) { const char *desc = ohci_pci_match(self); if (desc) { device_set_desc(self, desc); return 0; } else { return ENXIO; } } static int ohci_pci_attach(device_t self) { ohci_softc_t *sc = device_get_softc(self); int err; int rid; /* XXX where does it say so in the spec? */ sc->sc_bus.usbrev = USBREV_1_0; pci_enable_busmaster(self); rid = PCI_CBMEM; sc->io_res = bus_alloc_resource(self, SYS_RES_MEMORY, &rid, 0, ~0, 1, RF_ACTIVE); if (!sc->io_res) { device_printf(self, "Could not map memory\n"); return ENXIO; } sc->iot = rman_get_bustag(sc->io_res); sc->ioh = rman_get_bushandle(sc->io_res); rid = 0; sc->irq_res = bus_alloc_resource(self, SYS_RES_IRQ, &rid, 0, ~0, 1, RF_SHAREABLE | RF_ACTIVE); if (sc->irq_res == NULL) { device_printf(self, "Could not allocate irq\n"); ohci_pci_detach(self); return ENXIO; } sc->sc_bus.bdev = device_add_child(self, "usb", -1); if (!sc->sc_bus.bdev) { device_printf(self, "Could not add USB device\n"); ohci_pci_detach(self); return ENOMEM; } - device_set_ivars(sc->sc_bus.bdev, sc); + device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); /* ohci_pci_match will never return NULL if ohci_pci_probe succeeded */ device_set_desc(sc->sc_bus.bdev, ohci_pci_match(self)); switch (pci_get_vendor(self)) { case PCI_OHCI_VENDORID_ACERLABS: sprintf(sc->sc_vendor, "AcerLabs"); break; case PCI_OHCI_VENDORID_AMD: sprintf(sc->sc_vendor, "AMD"); break; case PCI_OHCI_VENDORID_APPLE: sprintf(sc->sc_vendor, "Apple"); break; case PCI_OHCI_VENDORID_CMDTECH: sprintf(sc->sc_vendor, "CMDTECH"); break; case PCI_OHCI_VENDORID_NEC: sprintf(sc->sc_vendor, "NEC"); break; case PCI_OHCI_VENDORID_NVIDIA: + case PCI_OHCI_VENDORID_NVIDIA2: sprintf(sc->sc_vendor, "nVidia"); break; case PCI_OHCI_VENDORID_OPTI: sprintf(sc->sc_vendor, "OPTi"); break; case PCI_OHCI_VENDORID_SIS: sprintf(sc->sc_vendor, "SiS"); break; default: if (bootverbose) device_printf(self, "(New OHCI DeviceId=0x%08x)\n", pci_get_devid(self)); sprintf(sc->sc_vendor, "(0x%04x)", pci_get_vendor(self)); } err = bus_setup_intr(self, sc->irq_res, INTR_TYPE_BIO, (driver_intr_t *) ohci_intr, sc, &sc->ih); if (err) { device_printf(self, "Could not setup irq, %d\n", err); sc->ih = NULL; ohci_pci_detach(self); return ENXIO; } err = ohci_init(sc); - if (!err) + if (!err) { + sc->sc_flags |= OHCI_SCFLG_DONEINIT; err = device_probe_and_attach(sc->sc_bus.bdev); + } if (err) { device_printf(self, "USB init failed\n"); ohci_pci_detach(self); return EIO; } return 0; } static int ohci_pci_detach(device_t self) { ohci_softc_t *sc = device_get_softc(self); - /* - * XXX this code is not yet fit to be used as detach for the OHCI - * controller - */ + if (sc->sc_flags & OHCI_SCFLG_DONEINIT) { + ohci_detach(sc, 0); + sc->sc_flags &= ~OHCI_SCFLG_DONEINIT; + } - /* - * disable interrupts that might have been switched on in ohci_init - */ - if (sc->iot && sc->ioh) - bus_space_write_4(sc->iot, sc->ioh, - OHCI_INTERRUPT_DISABLE, OHCI_ALL_INTRS); - if (sc->irq_res && sc->ih) { int err = bus_teardown_intr(self, sc->irq_res, sc->ih); if (err) /* XXX or should we panic? */ device_printf(self, "Could not tear down irq, %d\n", err); sc->ih = NULL; } if (sc->sc_bus.bdev) { device_delete_child(self, sc->sc_bus.bdev); sc->sc_bus.bdev = NULL; } if (sc->irq_res) { bus_release_resource(self, SYS_RES_IRQ, 0, sc->irq_res); sc->irq_res = NULL; } if (sc->io_res) { bus_release_resource(self, SYS_RES_MEMORY, PCI_CBMEM, sc->io_res); sc->io_res = NULL; sc->iot = 0; sc->ioh = 0; } return 0; } static device_method_t ohci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ohci_pci_probe), DEVMETHOD(device_attach, ohci_pci_attach), + DEVMETHOD(device_detach, ohci_pci_detach), + DEVMETHOD(device_suspend, ohci_pci_suspend), + DEVMETHOD(device_resume, ohci_pci_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), /* Bus interface */ DEVMETHOD(bus_print_child, bus_generic_print_child), {0, 0} }; static driver_t ohci_driver = { "ohci", ohci_methods, sizeof(ohci_softc_t), }; static devclass_t ohci_devclass; DRIVER_MODULE(ohci, pci, ohci_driver, ohci_devclass, 0, 0); Index: stable/4/sys/pci/uhci_pci.c =================================================================== --- stable/4/sys/pci/uhci_pci.c (revision 145576) +++ stable/4/sys/pci/uhci_pci.c (revision 145577) @@ -1,414 +1,428 @@ /*- * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (augustss@carlstedt.se) 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. */ #include __FBSDID("$FreeBSD$"); /* Universal Host Controller Interface * * UHCI spec: http://www.intel.com/ */ /* The low level controller code for UHCI has been split into * PCI probes and UHCI specific code. This was done to facilitate the * sharing of code between *BSD's */ #include "opt_bus.h" #include #include #include #include #include #include #if defined(__FreeBSD__) #include #include #include #include #endif #include #include #include #include #include #include #include #include #define PCI_UHCI_VENDORID_INTEL 0x8086 #define PCI_UHCI_VENDORID_VIA 0x1106 #define PCI_UHCI_DEVICEID_PIIX3 0x70208086 static const char *uhci_device_piix3 = "Intel 82371SB (PIIX3) USB controller"; #define PCI_UHCI_DEVICEID_PIIX4 0x71128086 #define PCI_UHCI_DEVICEID_PIIX4E 0x71128086 /* no separate stepping */ static const char *uhci_device_piix4 = "Intel 82371AB/EB (PIIX4) USB controller"; #define PCI_UHCI_DEVICEID_ICH 0x24128086 static const char *uhci_device_ich = "Intel 82801AA (ICH) USB controller"; #define PCI_UHCI_DEVICEID_ICH0 0x24228086 static const char *uhci_device_ich0 = "Intel 82801AB (ICH0) USB controller"; #define PCI_UHCI_DEVICEID_ICH2_A 0x24428086 static const char *uhci_device_ich2_a = "Intel 82801BA/BAM (ICH2) USB controller USB-A"; #define PCI_UHCI_DEVICEID_ICH2_B 0x24448086 static const char *uhci_device_ich2_b = "Intel 82801BA/BAM (ICH2) USB controller USB-B"; #define PCI_UHCI_DEVICEID_ICH3_A 0x24828086 static const char *uhci_device_ich3_a = "Intel 82801CA/CAM (ICH3) USB controller USB-A"; #define PCI_UHCI_DEVICEID_ICH3_B 0x24848086 static const char *uhci_device_ich3_b = "Intel 82801CA/CAM (ICH3) USB controller USB-B"; #define PCI_UHCI_DEVICEID_ICH3_C 0x24878086 static const char *uhci_device_ich3_c = "Intel 82801CA/CAM (ICH3) USB controller USB-C"; #define PCI_UHCI_DEVICEID_ICH4_A 0x24c28086 static const char *uhci_device_ich4_a = "Intel 82801DB (ICH4) USB controller USB-A"; #define PCI_UHCI_DEVICEID_ICH4_B 0x24c48086 static const char *uhci_device_ich4_b = "Intel 82801DB (ICH4) USB controller USB-B"; #define PCI_UHCI_DEVICEID_ICH4_C 0x24c78086 static const char *uhci_device_ich4_c = "Intel 82801DB (ICH4) USB controller USB-C"; #define PCI_UHCI_DEVICEID_ICH5_A 0x24d28086 static const char *uhci_device_ich5_a = "Intel 82801EB (ICH5) USB controller USB-A"; #define PCI_UHCI_DEVICEID_ICH5_B 0x24d48086 static const char *uhci_device_ich5_b = "Intel 82801EB (ICH5) USB controller USB-B"; #define PCI_UHCI_DEVICEID_ICH5_C 0x24d78086 static const char *uhci_device_ich5_c = "Intel 82801EB (ICH5) USB controller USB-C"; #define PCI_UHCI_DEVICEID_ICH5_D 0x24de8086 static const char *uhci_device_ich5_d = "Intel 82801EB (ICH5) USB controller USB-D"; +#define PCI_UHCI_DEVICEID_ICH6_A 0x26588086 +static const char *uhci_device_ich6_a = "Intel 82801FB/FR/FW/FRW (ICH6) USB controller USB-A"; + +#define PCI_UHCI_DEVICEID_ICH6_B 0x26598086 +static const char *uhci_device_ich6_b = "Intel 82801FB/FR/FW/FRW (ICH6) USB controller USB-B"; + +#define PCI_UHCI_DEVICEID_ICH6_C 0x265a8086 +static const char *uhci_device_ich6_c = "Intel 82801FB/FR/FW/FRW (ICH6) USB controller USB-C"; + +#define PCI_UHCI_DEVICEID_ICH6_D 0x265b8086 +static const char *uhci_device_ich6_d = "Intel 82801FB/FR/FW/FRW (ICH6) USB controller USB-D"; + #define PCI_UHCI_DEVICEID_440MX 0x719a8086 static const char *uhci_device_440mx = "Intel 82443MX USB controller"; #define PCI_UHCI_DEVICEID_460GX 0x76028086 static const char *uhci_device_460gx = "Intel 82372FB/82468GX USB controller"; #define PCI_UHCI_DEVICEID_VT83C572 0x30381106 static const char *uhci_device_vt83c572 = "VIA 83C572 USB controller"; static const char *uhci_device_generic = "UHCI (generic) USB controller"; #define PCI_UHCI_BASE_REG 0x20 static int uhci_pci_attach(device_t self); static int uhci_pci_detach(device_t self); static int uhci_pci_suspend(device_t self); static int uhci_pci_resume(device_t self); static int uhci_pci_suspend(device_t self) { uhci_softc_t *sc = device_get_softc(self); int err; err = bus_generic_suspend(self); if (err) return err; uhci_power(PWR_SUSPEND, sc); return 0; } static int uhci_pci_resume(device_t self) { uhci_softc_t *sc = device_get_softc(self); + pci_write_config(self, PCI_LEGSUP, PCI_LEGSUP_USBPIRQDEN, 2); + uhci_power(PWR_RESUME, sc); bus_generic_resume(self); return 0; } static const char * uhci_pci_match(device_t self) { u_int32_t device_id = pci_get_devid(self); if (device_id == PCI_UHCI_DEVICEID_PIIX3) { return (uhci_device_piix3); } else if (device_id == PCI_UHCI_DEVICEID_PIIX4) { return (uhci_device_piix4); } else if (device_id == PCI_UHCI_DEVICEID_ICH) { return (uhci_device_ich); } else if (device_id == PCI_UHCI_DEVICEID_ICH0) { return (uhci_device_ich0); } else if (device_id == PCI_UHCI_DEVICEID_ICH2_A) { return (uhci_device_ich2_a); } else if (device_id == PCI_UHCI_DEVICEID_ICH2_B) { return (uhci_device_ich2_b); } else if (device_id == PCI_UHCI_DEVICEID_ICH3_A) { return (uhci_device_ich3_a); } else if (device_id == PCI_UHCI_DEVICEID_ICH3_B) { return (uhci_device_ich3_b); } else if (device_id == PCI_UHCI_DEVICEID_ICH3_C) { return (uhci_device_ich3_c); } else if (device_id == PCI_UHCI_DEVICEID_ICH4_A) { return (uhci_device_ich4_a); } else if (device_id == PCI_UHCI_DEVICEID_ICH4_B) { return (uhci_device_ich4_b); } else if (device_id == PCI_UHCI_DEVICEID_ICH4_C) { return (uhci_device_ich4_c); } else if (device_id == PCI_UHCI_DEVICEID_ICH5_A) { return (uhci_device_ich5_a); } else if (device_id == PCI_UHCI_DEVICEID_ICH5_B) { return (uhci_device_ich5_b); } else if (device_id == PCI_UHCI_DEVICEID_ICH5_C) { return (uhci_device_ich5_c); } else if (device_id == PCI_UHCI_DEVICEID_ICH5_D) { return (uhci_device_ich5_d); + } else if (device_id == PCI_UHCI_DEVICEID_ICH6_A) { + return (uhci_device_ich6_a); + } else if (device_id == PCI_UHCI_DEVICEID_ICH6_B) { + return (uhci_device_ich6_b); + } else if (device_id == PCI_UHCI_DEVICEID_ICH6_C) { + return (uhci_device_ich6_c); + } else if (device_id == PCI_UHCI_DEVICEID_ICH6_D) { + return (uhci_device_ich6_d); } else if (device_id == PCI_UHCI_DEVICEID_440MX) { return (uhci_device_440mx); } else if (device_id == PCI_UHCI_DEVICEID_460GX) { return (uhci_device_460gx); } else if (device_id == PCI_UHCI_DEVICEID_VT83C572) { return (uhci_device_vt83c572); } else { if (pci_get_class(self) == PCIC_SERIALBUS && pci_get_subclass(self) == PCIS_SERIALBUS_USB && pci_get_progif(self) == PCI_INTERFACE_UHCI) { return (uhci_device_generic); } } return NULL; /* dunno... */ } static int uhci_pci_probe(device_t self) { const char *desc = uhci_pci_match(self); if (desc) { device_set_desc(self, desc); return 0; } else { return ENXIO; } } static int uhci_pci_attach(device_t self) { uhci_softc_t *sc = device_get_softc(self); int rid; int err; pci_enable_busmaster(self); rid = PCI_UHCI_BASE_REG; sc->io_res = bus_alloc_resource(self, SYS_RES_IOPORT, &rid, 0, ~0, 1, RF_ACTIVE); if (!sc->io_res) { device_printf(self, "Could not map ports\n"); return ENXIO; } sc->iot = rman_get_bustag(sc->io_res); sc->ioh = rman_get_bushandle(sc->io_res); /* disable interrupts */ bus_space_write_2(sc->iot, sc->ioh, UHCI_INTR, 0); rid = 0; sc->irq_res = bus_alloc_resource(self, SYS_RES_IRQ, &rid, 0, ~0, 1, RF_SHAREABLE | RF_ACTIVE); if (sc->irq_res == NULL) { device_printf(self, "Could not allocate irq\n"); uhci_pci_detach(self); return ENXIO; } sc->sc_bus.bdev = device_add_child(self, "usb", -1); if (!sc->sc_bus.bdev) { device_printf(self, "Could not add USB device\n"); uhci_pci_detach(self); return ENOMEM; } - device_set_ivars(sc->sc_bus.bdev, sc); + device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); /* uhci_pci_match must never return NULL if uhci_pci_probe succeeded */ device_set_desc(sc->sc_bus.bdev, uhci_pci_match(self)); switch (pci_get_vendor(self)) { case PCI_UHCI_VENDORID_INTEL: sprintf(sc->sc_vendor, "Intel"); break; case PCI_UHCI_VENDORID_VIA: sprintf(sc->sc_vendor, "VIA"); break; default: if (bootverbose) device_printf(self, "(New UHCI DeviceId=0x%08x)\n", pci_get_devid(self)); sprintf(sc->sc_vendor, "(0x%04x)", pci_get_vendor(self)); } switch (pci_read_config(self, PCI_USBREV, 1) & PCI_USBREV_MASK) { case PCI_USBREV_PRE_1_0: sc->sc_bus.usbrev = USBREV_PRE_1_0; break; case PCI_USBREV_1_0: sc->sc_bus.usbrev = USBREV_1_0; break; default: sc->sc_bus.usbrev = USBREV_UNKNOWN; break; } err = bus_setup_intr(self, sc->irq_res, INTR_TYPE_BIO, (driver_intr_t *) uhci_intr, sc, &sc->ih); if (err) { device_printf(self, "Could not setup irq, %d\n", err); sc->ih = NULL; uhci_pci_detach(self); return ENXIO; } /* * Set the PIRQD enable bit and switch off all the others. We don't * want legacy support to interfere with us XXX Does this also mean * that the BIOS won't touch the keyboard anymore if it is connected * to the ports of the root hub? */ #ifdef USB_DEBUG if (pci_read_config(self, PCI_LEGSUP, 2) != PCI_LEGSUP_USBPIRQDEN) device_printf(self, "LegSup = 0x%04x\n", pci_read_config(self, PCI_LEGSUP, 2)); #endif pci_write_config(self, PCI_LEGSUP, PCI_LEGSUP_USBPIRQDEN, 2); err = uhci_init(sc); - if (!err) + if (!err) { + sc->sc_flags |= UHCI_SCFLG_DONEINIT; err = device_probe_and_attach(sc->sc_bus.bdev); + } if (err) { device_printf(self, "USB init failed\n"); uhci_pci_detach(self); return EIO; } return 0; /* success */ } int uhci_pci_detach(device_t self) { uhci_softc_t *sc = device_get_softc(self); - /* - * XXX This function is not yet complete and should not be added - * method list. - */ -#if 0 - if uhci_init - was successful - we should call something like uhci_deinit -#endif + if (sc->sc_flags & UHCI_SCFLG_DONEINIT) { + uhci_detach(sc, 0); + sc->sc_flags &= ~UHCI_SCFLG_DONEINIT; + } - /* - * disable interrupts that might have been switched on in - * uhci_init. - */ - if (sc->iot && sc->ioh) - bus_space_write_2(sc->iot, sc->ioh, UHCI_INTR, 0); if (sc->irq_res && sc->ih) { int err = bus_teardown_intr(self, sc->irq_res, sc->ih); if (err) /* XXX or should we panic? */ device_printf(self, "Could not tear down irq, %d\n", err); sc->ih = NULL; } if (sc->sc_bus.bdev) { device_delete_child(self, sc->sc_bus.bdev); sc->sc_bus.bdev = NULL; } if (sc->irq_res) { bus_release_resource(self, SYS_RES_IRQ, 0, sc->irq_res); sc->irq_res = NULL; } if (sc->io_res) { bus_release_resource(self, SYS_RES_IOPORT, PCI_UHCI_BASE_REG, sc->io_res); sc->io_res = NULL; sc->iot = 0; sc->ioh = 0; } return 0; } static device_method_t uhci_methods[] = { /* Device interface */ DEVMETHOD(device_probe, uhci_pci_probe), DEVMETHOD(device_attach, uhci_pci_attach), + DEVMETHOD(device_detach, uhci_pci_detach), DEVMETHOD(device_suspend, uhci_pci_suspend), DEVMETHOD(device_resume, uhci_pci_resume), DEVMETHOD(device_shutdown, bus_generic_shutdown), /* Bus interface */ DEVMETHOD(bus_print_child, bus_generic_print_child), {0, 0} }; static driver_t uhci_driver = { "uhci", uhci_methods, sizeof(uhci_softc_t), }; static devclass_t uhci_devclass; DRIVER_MODULE(uhci, pci, uhci_driver, uhci_devclass, 0, 0);