diff --git a/sys/dev/usb/controller/ohci.c b/sys/dev/usb/controller/ohci.c index 7268af06a602..880e2e347619 100644 --- a/sys/dev/usb/controller/ohci.c +++ b/sys/dev/usb/controller/ohci.c @@ -1,2687 +1,2687 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved. * Copyright (c) 1998 Lennart Augustsson. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * 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 */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USB_DEBUG_VAR ohcidebug #include #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ #include #include #define OHCI_BUS2SC(bus) \ __containerof(bus, ohci_softc_t, sc_bus) #ifdef USB_DEBUG static int ohcidebug = 0; static SYSCTL_NODE(_hw_usb, OID_AUTO, ohci, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "USB ohci"); SYSCTL_INT(_hw_usb_ohci, OID_AUTO, debug, CTLFLAG_RWTUN, &ohcidebug, 0, "ohci debug level"); static void ohci_dumpregs(ohci_softc_t *); static void ohci_dump_tds(ohci_td_t *); static uint8_t ohci_dump_td(ohci_td_t *); static void ohci_dump_ed(ohci_ed_t *); static uint8_t ohci_dump_itd(ohci_itd_t *); static void ohci_dump_itds(ohci_itd_t *); #endif #define OBARR(sc) bus_space_barrier((sc)->sc_io_tag, (sc)->sc_io_hdl, 0, (sc)->sc_io_size, \ BUS_SPACE_BARRIER_READ|BUS_SPACE_BARRIER_WRITE) #define OWRITE1(sc, r, x) \ do { OBARR(sc); bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); } while (0) #define OWRITE2(sc, r, x) \ do { OBARR(sc); bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); } while (0) #define OWRITE4(sc, r, x) \ do { OBARR(sc); bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); } while (0) #define OREAD1(sc, r) (OBARR(sc), bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (r))) #define OREAD2(sc, r) (OBARR(sc), bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (r))) #define OREAD4(sc, r) (OBARR(sc), bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (r))) #define OHCI_INTR_ENDPT 1 static const struct usb_bus_methods ohci_bus_methods; static const struct usb_pipe_methods ohci_device_bulk_methods; static const struct usb_pipe_methods ohci_device_ctrl_methods; static const struct usb_pipe_methods ohci_device_intr_methods; static const struct usb_pipe_methods ohci_device_isoc_methods; static void ohci_do_poll(struct usb_bus *bus); static void ohci_device_done(struct usb_xfer *xfer, usb_error_t error); static void ohci_timeout(void *arg); static uint8_t ohci_check_transfer(struct usb_xfer *xfer); static void ohci_root_intr(ohci_softc_t *sc); struct ohci_std_temp { struct usb_page_cache *pc; ohci_td_t *td; ohci_td_t *td_next; uint32_t average; uint32_t td_flags; uint32_t len; uint16_t max_frame_size; uint8_t shortpkt; uint8_t setup_alt_next; uint8_t last_frame; }; static struct ohci_hcca * ohci_get_hcca(ohci_softc_t *sc) { usb_pc_cpu_invalidate(&sc->sc_hw.hcca_pc); return (sc->sc_hcca_p); } void ohci_iterate_hw_softc(struct usb_bus *bus, usb_bus_mem_sub_cb_t *cb) { struct ohci_softc *sc = OHCI_BUS2SC(bus); uint32_t i; cb(bus, &sc->sc_hw.hcca_pc, &sc->sc_hw.hcca_pg, sizeof(ohci_hcca_t), OHCI_HCCA_ALIGN); cb(bus, &sc->sc_hw.ctrl_start_pc, &sc->sc_hw.ctrl_start_pg, sizeof(ohci_ed_t), OHCI_ED_ALIGN); cb(bus, &sc->sc_hw.bulk_start_pc, &sc->sc_hw.bulk_start_pg, sizeof(ohci_ed_t), OHCI_ED_ALIGN); cb(bus, &sc->sc_hw.isoc_start_pc, &sc->sc_hw.isoc_start_pg, sizeof(ohci_ed_t), OHCI_ED_ALIGN); for (i = 0; i != OHCI_NO_EDS; i++) { cb(bus, sc->sc_hw.intr_start_pc + i, sc->sc_hw.intr_start_pg + i, sizeof(ohci_ed_t), OHCI_ED_ALIGN); } } static usb_error_t ohci_controller_init(ohci_softc_t *sc, int do_suspend) { struct usb_page_search buf_res; uint32_t i; uint32_t ctl; uint32_t ival; uint32_t hcr; uint32_t fm; uint32_t per; uint32_t desca; /* Determine in what context we are running. */ ctl = OREAD4(sc, OHCI_CONTROL); if (ctl & OHCI_IR) { /* SMM active, request change */ DPRINTF("SMM active, request owner change\n"); OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_OCR); for (i = 0; (i < 100) && (ctl & OHCI_IR); i++) { usb_pause_mtx(NULL, hz / 1000); ctl = OREAD4(sc, OHCI_CONTROL); } if (ctl & OHCI_IR) { device_printf(sc->sc_bus.bdev, "SMM does not respond, resetting\n"); OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET); goto reset; } } else { DPRINTF("cold started\n"); reset: /* controller was cold started */ usb_pause_mtx(NULL, USB_MS_TO_TICKS(USB_BUS_RESET_DELAY)); } /* * This reset should not be necessary according to the OHCI spec, but * without it some controllers do not start. */ DPRINTF("%s: resetting\n", device_get_nameunit(sc->sc_bus.bdev)); OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET); usb_pause_mtx(NULL, USB_MS_TO_TICKS(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) { device_printf(sc->sc_bus.bdev, "reset timeout\n"); return (USB_ERR_IOERROR); } #ifdef USB_DEBUG if (ohcidebug > 15) { ohci_dumpregs(sc); } #endif if (do_suspend) { OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_SUSPEND); return (USB_ERR_NORMAL_COMPLETION); } /* The controller is now in SUSPEND state, we have 2ms to finish. */ /* set up HC registers */ usbd_get_page(&sc->sc_hw.hcca_pc, 0, &buf_res); OWRITE4(sc, OHCI_HCCA, buf_res.physaddr); usbd_get_page(&sc->sc_hw.ctrl_start_pc, 0, &buf_res); OWRITE4(sc, OHCI_CONTROL_HEAD_ED, buf_res.physaddr); usbd_get_page(&sc->sc_hw.bulk_start_pc, 0, &buf_res); OWRITE4(sc, OHCI_BULK_HEAD_ED, buf_res.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_pause_mtx(NULL, USB_MS_TO_TICKS(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_pause_mtx(NULL, USB_MS_TO_TICKS(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 (USB_ERR_NORMAL_COMPLETION); } static struct ohci_ed * ohci_init_ed(struct usb_page_cache *pc) { struct usb_page_search buf_res; struct ohci_ed *ed; usbd_get_page(pc, 0, &buf_res); ed = buf_res.buffer; ed->ed_self = htole32(buf_res.physaddr); ed->ed_flags = htole32(OHCI_ED_SKIP); ed->page_cache = pc; return (ed); } usb_error_t ohci_init(ohci_softc_t *sc) { struct usb_page_search buf_res; uint16_t i; uint16_t bit; uint16_t x; uint16_t y; DPRINTF("start\n"); sc->sc_eintrs = OHCI_NORMAL_INTRS; /* * Setup all ED's */ sc->sc_ctrl_p_last = ohci_init_ed(&sc->sc_hw.ctrl_start_pc); sc->sc_bulk_p_last = ohci_init_ed(&sc->sc_hw.bulk_start_pc); sc->sc_isoc_p_last = ohci_init_ed(&sc->sc_hw.isoc_start_pc); for (i = 0; i != OHCI_NO_EDS; i++) { sc->sc_intr_p_last[i] = ohci_init_ed(sc->sc_hw.intr_start_pc + i); } /* * the QHs are arranged to give poll intervals that are * powers of 2 times 1ms */ bit = OHCI_NO_EDS / 2; while (bit) { x = bit; while (x & bit) { ohci_ed_t *ed_x; ohci_ed_t *ed_y; y = (x ^ bit) | (bit / 2); /* * the next QH has half the poll interval */ ed_x = sc->sc_intr_p_last[x]; ed_y = sc->sc_intr_p_last[y]; ed_x->next = NULL; ed_x->ed_next = ed_y->ed_self; x++; } bit >>= 1; } if (1) { ohci_ed_t *ed_int; ohci_ed_t *ed_isc; ed_int = sc->sc_intr_p_last[0]; ed_isc = sc->sc_isoc_p_last; /* the last (1ms) QH */ ed_int->next = ed_isc; ed_int->ed_next = ed_isc->ed_self; } usbd_get_page(&sc->sc_hw.hcca_pc, 0, &buf_res); sc->sc_hcca_p = buf_res.buffer; /* * 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_p->hcca_interrupt_table[i] = sc->sc_intr_p_last[i | (OHCI_NO_EDS / 2)]->ed_self; } /* flush all cache into memory */ usb_bus_mem_flush_all(&sc->sc_bus, &ohci_iterate_hw_softc); /* set up the bus struct */ sc->sc_bus.methods = &ohci_bus_methods; usb_callout_init_mtx(&sc->sc_tmo_rhsc, &sc->sc_bus.bus_mtx, 0); #ifdef USB_DEBUG if (ohcidebug > 15) { for (i = 0; i != OHCI_NO_EDS; i++) { printf("ed#%d ", i); ohci_dump_ed(sc->sc_intr_p_last[i]); } printf("iso "); ohci_dump_ed(sc->sc_isoc_p_last); } #endif sc->sc_bus.usbrev = USB_REV_1_0; if (ohci_controller_init(sc, 0) != 0) return (USB_ERR_INVAL); /* catch any lost interrupts */ ohci_do_poll(&sc->sc_bus); return (USB_ERR_NORMAL_COMPLETION); } /* * shut down the controller when the system is going down */ void ohci_detach(struct ohci_softc *sc) { USB_BUS_LOCK(&sc->sc_bus); usb_callout_stop(&sc->sc_tmo_rhsc); OWRITE4(sc, OHCI_INTERRUPT_DISABLE, OHCI_ALL_INTRS); OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET); USB_BUS_UNLOCK(&sc->sc_bus); /* XXX let stray task complete */ usb_pause_mtx(NULL, hz / 20); usb_callout_drain(&sc->sc_tmo_rhsc); } static void ohci_suspend(ohci_softc_t *sc) { DPRINTF("\n"); #ifdef USB_DEBUG if (ohcidebug > 2) ohci_dumpregs(sc); #endif /* reset HC and leave it suspended */ ohci_controller_init(sc, 1); } static void ohci_resume(ohci_softc_t *sc) { DPRINTF("\n"); #ifdef USB_DEBUG if (ohcidebug > 2) ohci_dumpregs(sc); #endif /* some broken BIOSes never initialize the Controller chip */ ohci_controller_init(sc, 0); /* catch any lost interrupts */ ohci_do_poll(&sc->sc_bus); } #ifdef USB_DEBUG static void ohci_dumpregs(ohci_softc_t *sc) { struct ohci_hcca *hcca; 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))); hcca = ohci_get_hcca(sc); DPRINTF(" HCCA: frame_number=0x%04x done_head=0x%08x\n", le32toh(hcca->hcca_frame_number), le32toh(hcca->hcca_done_head)); } static void ohci_dump_tds(ohci_td_t *std) { for (; std; std = std->obj_next) { if (ohci_dump_td(std)) { break; } } } static uint8_t ohci_dump_td(ohci_td_t *std) { uint32_t td_flags; uint8_t temp; usb_pc_cpu_invalidate(std->page_cache); td_flags = le32toh(std->td_flags); temp = (std->td_next == 0); printf("TD(%p) at 0x%08x: %s%s%s%s%s delay=%d ec=%d " "cc=%d\ncbp=0x%08x next=0x%08x be=0x%08x\n", std, le32toh(std->td_self), (td_flags & OHCI_TD_R) ? "-R" : "", (td_flags & OHCI_TD_OUT) ? "-OUT" : "", (td_flags & OHCI_TD_IN) ? "-IN" : "", ((td_flags & OHCI_TD_TOGGLE_MASK) == OHCI_TD_TOGGLE_1) ? "-TOG1" : "", ((td_flags & OHCI_TD_TOGGLE_MASK) == OHCI_TD_TOGGLE_0) ? "-TOG0" : "", OHCI_TD_GET_DI(td_flags), OHCI_TD_GET_EC(td_flags), OHCI_TD_GET_CC(td_flags), le32toh(std->td_cbp), le32toh(std->td_next), le32toh(std->td_be)); return (temp); } static uint8_t ohci_dump_itd(ohci_itd_t *sitd) { uint32_t itd_flags; uint16_t i; uint8_t temp; usb_pc_cpu_invalidate(sitd->page_cache); itd_flags = le32toh(sitd->itd_flags); temp = (sitd->itd_next == 0); printf("ITD(%p) at 0x%08x: sf=%d di=%d fc=%d cc=%d\n" "bp0=0x%08x next=0x%08x be=0x%08x\n", sitd, le32toh(sitd->itd_self), OHCI_ITD_GET_SF(itd_flags), OHCI_ITD_GET_DI(itd_flags), OHCI_ITD_GET_FC(itd_flags), OHCI_ITD_GET_CC(itd_flags), le32toh(sitd->itd_bp0), le32toh(sitd->itd_next), le32toh(sitd->itd_be)); for (i = 0; i < OHCI_ITD_NOFFSET; i++) { printf("offs[%d]=0x%04x ", i, (uint32_t)le16toh(sitd->itd_offset[i])); } printf("\n"); return (temp); } static void ohci_dump_itds(ohci_itd_t *sitd) { for (; sitd; sitd = sitd->obj_next) { if (ohci_dump_itd(sitd)) { break; } } } static void ohci_dump_ed(ohci_ed_t *sed) { uint32_t ed_flags; uint32_t ed_headp; usb_pc_cpu_invalidate(sed->page_cache); ed_flags = le32toh(sed->ed_flags); ed_headp = le32toh(sed->ed_headp); printf("ED(%p) at 0x%08x: addr=%d endpt=%d maxp=%d flags=%s%s%s%s%s\n" "tailp=0x%08x headflags=%s%s headp=0x%08x nexted=0x%08x\n", sed, le32toh(sed->ed_self), OHCI_ED_GET_FA(ed_flags), OHCI_ED_GET_EN(ed_flags), OHCI_ED_GET_MAXP(ed_flags), (ed_flags & OHCI_ED_DIR_OUT) ? "-OUT" : "", (ed_flags & OHCI_ED_DIR_IN) ? "-IN" : "", (ed_flags & OHCI_ED_SPEED) ? "-LOWSPEED" : "", (ed_flags & OHCI_ED_SKIP) ? "-SKIP" : "", (ed_flags & OHCI_ED_FORMAT_ISO) ? "-ISO" : "", le32toh(sed->ed_tailp), (ed_headp & OHCI_HALTED) ? "-HALTED" : "", (ed_headp & OHCI_TOGGLECARRY) ? "-CARRY" : "", le32toh(sed->ed_headp), le32toh(sed->ed_next)); } #endif static void ohci_transfer_intr_enqueue(struct usb_xfer *xfer) { /* check for early completion */ if (ohci_check_transfer(xfer)) { return; } /* put transfer on interrupt queue */ usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer); /* start timeout, if any */ if (xfer->timeout != 0) { usbd_transfer_timeout_ms(xfer, &ohci_timeout, xfer->timeout); } } #define OHCI_APPEND_QH(sed,last) (last) = _ohci_append_qh(sed,last) static ohci_ed_t * _ohci_append_qh(ohci_ed_t *sed, ohci_ed_t *last) { DPRINTFN(11, "%p to %p\n", sed, last); if (sed->prev != NULL) { /* should not happen */ DPRINTFN(0, "ED already linked!\n"); return (last); } /* (sc->sc_bus.bus_mtx) must be locked */ sed->next = last->next; sed->ed_next = last->ed_next; sed->ed_tailp = 0; sed->prev = last; usb_pc_cpu_flush(sed->page_cache); /* * the last->next->prev is never followed: sed->next->prev = sed; */ last->next = sed; last->ed_next = sed->ed_self; usb_pc_cpu_flush(last->page_cache); return (sed); } #define OHCI_REMOVE_QH(sed,last) (last) = _ohci_remove_qh(sed,last) static ohci_ed_t * _ohci_remove_qh(ohci_ed_t *sed, ohci_ed_t *last) { DPRINTFN(11, "%p from %p\n", sed, last); /* (sc->sc_bus.bus_mtx) must be locked */ /* only remove if not removed from a queue */ if (sed->prev) { sed->prev->next = sed->next; sed->prev->ed_next = sed->ed_next; usb_pc_cpu_flush(sed->prev->page_cache); if (sed->next) { sed->next->prev = sed->prev; usb_pc_cpu_flush(sed->next->page_cache); } last = ((last == sed) ? sed->prev : last); sed->prev = 0; usb_pc_cpu_flush(sed->page_cache); } return (last); } static void ohci_isoc_done(struct usb_xfer *xfer) { uint8_t nframes; uint32_t *plen = xfer->frlengths; volatile uint16_t *olen; uint16_t len = 0; ohci_itd_t *td = xfer->td_transfer_first; while (1) { if (td == NULL) { panic("%s:%d: out of TD's\n", __FUNCTION__, __LINE__); } #ifdef USB_DEBUG if (ohcidebug > 5) { DPRINTF("isoc TD\n"); ohci_dump_itd(td); } #endif usb_pc_cpu_invalidate(td->page_cache); nframes = td->frames; olen = &td->itd_offset[0]; if (nframes > 8) { nframes = 8; } while (nframes--) { len = le16toh(*olen); if ((len >> 12) == OHCI_CC_NOT_ACCESSED) { len = 0; } else { len &= ((1 << 12) - 1); } if (len > *plen) { len = 0;/* invalid length */ } *plen = len; plen++; olen++; } if (((void *)td) == xfer->td_transfer_last) { break; } td = td->obj_next; } xfer->aframes = xfer->nframes; ohci_device_done(xfer, USB_ERR_NORMAL_COMPLETION); } #ifdef USB_DEBUG static const char *const 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 static usb_error_t ohci_non_isoc_done_sub(struct usb_xfer *xfer) { ohci_td_t *td; ohci_td_t *td_alt_next; uint32_t temp; uint32_t phy_start; uint32_t phy_end; uint32_t td_flags; uint16_t cc; td = xfer->td_transfer_cache; td_alt_next = td->alt_next; td_flags = 0; if (xfer->aframes != xfer->nframes) { usbd_xfer_set_frame_len(xfer, xfer->aframes, 0); } while (1) { usb_pc_cpu_invalidate(td->page_cache); phy_start = le32toh(td->td_cbp); td_flags = le32toh(td->td_flags); cc = OHCI_TD_GET_CC(td_flags); if (phy_start) { /* * short transfer - compute the number of remaining * bytes in the hardware buffer: */ phy_end = le32toh(td->td_be); temp = (OHCI_PAGE(phy_start ^ phy_end) ? (OHCI_PAGE_SIZE + 1) : 0x0001); temp += OHCI_PAGE_OFFSET(phy_end); temp -= OHCI_PAGE_OFFSET(phy_start); if (temp > td->len) { /* guard against corruption */ cc = OHCI_CC_STALL; } else if (xfer->aframes != xfer->nframes) { /* * Sum up total transfer length * in "frlengths[]": */ xfer->frlengths[xfer->aframes] += td->len - temp; } } else { if (xfer->aframes != xfer->nframes) { /* transfer was complete */ xfer->frlengths[xfer->aframes] += td->len; } } /* Check for last transfer */ if (((void *)td) == xfer->td_transfer_last) { td = NULL; break; } /* Check transfer status */ if (cc) { /* the transfer is finished */ td = NULL; break; } /* Check for short transfer */ if (phy_start) { if (xfer->flags_int.short_frames_ok) { /* follow alt next */ td = td->alt_next; } else { /* the transfer is finished */ td = NULL; } break; } td = td->obj_next; if (td->alt_next != td_alt_next) { /* this USB frame is complete */ break; } } /* update transfer cache */ xfer->td_transfer_cache = td; DPRINTFN(16, "error cc=%d (%s)\n", cc, ohci_cc_strs[cc]); return ((cc == 0) ? USB_ERR_NORMAL_COMPLETION : (cc == OHCI_CC_STALL) ? USB_ERR_STALLED : USB_ERR_IOERROR); } static void ohci_non_isoc_done(struct usb_xfer *xfer) { usb_error_t err = 0; DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n", xfer, xfer->endpoint); #ifdef USB_DEBUG if (ohcidebug > 10) { ohci_dump_tds(xfer->td_transfer_first); } #endif /* reset scanner */ xfer->td_transfer_cache = xfer->td_transfer_first; if (xfer->flags_int.control_xfr) { if (xfer->flags_int.control_hdr) { err = ohci_non_isoc_done_sub(xfer); } xfer->aframes = 1; if (xfer->td_transfer_cache == NULL) { goto done; } } while (xfer->aframes != xfer->nframes) { err = ohci_non_isoc_done_sub(xfer); xfer->aframes++; if (xfer->td_transfer_cache == NULL) { goto done; } } if (xfer->flags_int.control_xfr && !xfer->flags_int.control_act) { err = ohci_non_isoc_done_sub(xfer); } done: ohci_device_done(xfer, err); } /*------------------------------------------------------------------------* * ohci_check_transfer_sub *------------------------------------------------------------------------*/ static void ohci_check_transfer_sub(struct usb_xfer *xfer) { ohci_td_t *td; ohci_ed_t *ed; uint32_t phy_start; uint32_t td_flags; uint32_t td_next; uint16_t cc; td = xfer->td_transfer_cache; while (1) { usb_pc_cpu_invalidate(td->page_cache); phy_start = le32toh(td->td_cbp); td_flags = le32toh(td->td_flags); td_next = le32toh(td->td_next); /* Check for last transfer */ if (((void *)td) == xfer->td_transfer_last) { /* the transfer is finished */ td = NULL; break; } /* Check transfer status */ cc = OHCI_TD_GET_CC(td_flags); if (cc) { /* the transfer is finished */ td = NULL; break; } /* * Check if we reached the last packet * or if there is a short packet: */ if (((td_next & (~0xF)) == OHCI_TD_NEXT_END) || phy_start) { /* follow alt next */ td = td->alt_next; break; } td = td->obj_next; } /* update transfer cache */ xfer->td_transfer_cache = td; if (td) { ed = xfer->qh_start[xfer->flags_int.curr_dma_set]; ed->ed_headp = td->td_self; usb_pc_cpu_flush(ed->page_cache); DPRINTFN(13, "xfer=%p following alt next\n", xfer); /* * Make sure that the OHCI re-scans the schedule by * writing the BLF and CLF bits: */ if (xfer->xroot->udev->flags.self_suspended) { /* nothing to do */ } else if (xfer->endpoint->methods == &ohci_device_bulk_methods) { ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus); OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_BLF); } else if (xfer->endpoint->methods == &ohci_device_ctrl_methods) { ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus); OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_CLF); } } } /*------------------------------------------------------------------------* * ohci_check_transfer * * Return values: * 0: USB transfer is not finished * Else: USB transfer is finished *------------------------------------------------------------------------*/ static uint8_t ohci_check_transfer(struct usb_xfer *xfer) { ohci_ed_t *ed; uint32_t ed_headp; uint32_t ed_tailp; DPRINTFN(13, "xfer=%p checking transfer\n", xfer); ed = xfer->qh_start[xfer->flags_int.curr_dma_set]; usb_pc_cpu_invalidate(ed->page_cache); ed_headp = le32toh(ed->ed_headp); ed_tailp = le32toh(ed->ed_tailp); if ((ed_headp & OHCI_HALTED) || (((ed_headp ^ ed_tailp) & (~0xF)) == 0)) { if (xfer->endpoint->methods == &ohci_device_isoc_methods) { /* isochronous transfer */ ohci_isoc_done(xfer); } else { if (xfer->flags_int.short_frames_ok) { ohci_check_transfer_sub(xfer); if (xfer->td_transfer_cache) { /* not finished yet */ return (0); } } /* store data-toggle */ if (ed_headp & OHCI_TOGGLECARRY) { xfer->endpoint->toggle_next = 1; } else { xfer->endpoint->toggle_next = 0; } /* non-isochronous transfer */ ohci_non_isoc_done(xfer); } return (1); } DPRINTFN(13, "xfer=%p is still active\n", xfer); return (0); } static void ohci_rhsc_enable(ohci_softc_t *sc) { DPRINTFN(5, "\n"); USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); sc->sc_eintrs |= OHCI_RHSC; OWRITE4(sc, OHCI_INTERRUPT_ENABLE, OHCI_RHSC); /* acknowledge any RHSC interrupt */ OWRITE4(sc, OHCI_INTERRUPT_STATUS, OHCI_RHSC); ohci_root_intr(sc); } static void ohci_interrupt_poll(ohci_softc_t *sc) { struct usb_xfer *xfer; repeat: TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { /* * check if transfer is transferred */ if (ohci_check_transfer(xfer)) { /* queue has been modified */ goto repeat; } } } /*------------------------------------------------------------------------* * ohci_interrupt - OHCI interrupt handler * * NOTE: Do not access "sc->sc_bus.bdev" inside the interrupt handler, * hence the interrupt handler will be setup before "sc->sc_bus.bdev" * is present ! *------------------------------------------------------------------------*/ void ohci_interrupt(ohci_softc_t *sc) { struct ohci_hcca *hcca; uint32_t status; uint32_t done; USB_BUS_LOCK(&sc->sc_bus); hcca = ohci_get_hcca(sc); DPRINTFN(16, "real interrupt\n"); #ifdef USB_DEBUG if (ohcidebug > 15) { ohci_dumpregs(sc); } #endif done = le32toh(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) { status = 0; if (done & ~OHCI_DONE_INTRS) { status |= OHCI_WDH; } if (done & OHCI_DONE_INTRS) { status |= OREAD4(sc, OHCI_INTERRUPT_STATUS); } hcca->hcca_done_head = 0; usb_pc_cpu_flush(&sc->sc_hw.hcca_pc); } else { status = OREAD4(sc, OHCI_INTERRUPT_STATUS) & ~OHCI_WDH; } status &= ~OHCI_MIE; if (status == 0) { /* * nothing to be done (PCI shared * interrupt) */ goto done; } OWRITE4(sc, OHCI_INTERRUPT_STATUS, status); /* Acknowledge */ status &= sc->sc_eintrs; if (status == 0) { goto done; } if (status & (OHCI_SO | OHCI_RD | OHCI_UE | OHCI_RHSC)) { #if 0 if (status & OHCI_SO) { /* XXX do what */ } #endif if (status & OHCI_RD) { printf("%s: resume detect\n", __FUNCTION__); /* XXX process resume detect */ } if (status & OHCI_UE) { printf("%s: unrecoverable error, " "controller halted\n", __FUNCTION__); OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET); /* XXX what else */ } if (status & OHCI_RHSC) { /* * Disable RHSC interrupt for now, because it will be * on until the port has been reset. */ sc->sc_eintrs &= ~OHCI_RHSC; OWRITE4(sc, OHCI_INTERRUPT_DISABLE, OHCI_RHSC); ohci_root_intr(sc); /* do not allow RHSC interrupts > 1 per second */ usb_callout_reset(&sc->sc_tmo_rhsc, hz, (void *)&ohci_rhsc_enable, sc); } } status &= ~(OHCI_RHSC | OHCI_WDH | OHCI_SO); if (status != 0) { /* Block unprocessed interrupts. XXX */ OWRITE4(sc, OHCI_INTERRUPT_DISABLE, status); sc->sc_eintrs &= ~status; printf("%s: blocking intrs 0x%x\n", __FUNCTION__, status); } /* poll all the USB transfers */ ohci_interrupt_poll(sc); done: USB_BUS_UNLOCK(&sc->sc_bus); } /* * called when a request does not complete */ static void ohci_timeout(void *arg) { struct usb_xfer *xfer = arg; DPRINTF("xfer=%p\n", xfer); USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); /* transfer is transferred */ ohci_device_done(xfer, USB_ERR_TIMEOUT); } static void ohci_do_poll(struct usb_bus *bus) { struct ohci_softc *sc = OHCI_BUS2SC(bus); USB_BUS_LOCK(&sc->sc_bus); ohci_interrupt_poll(sc); USB_BUS_UNLOCK(&sc->sc_bus); } static void ohci_setup_standard_chain_sub(struct ohci_std_temp *temp) { struct usb_page_search buf_res; ohci_td_t *td; ohci_td_t *td_next; ohci_td_t *td_alt_next; uint32_t buf_offset; uint32_t average; uint32_t len_old; uint8_t shortpkt_old; uint8_t precompute; td_alt_next = NULL; buf_offset = 0; shortpkt_old = temp->shortpkt; len_old = temp->len; precompute = 1; /* software is used to detect short incoming transfers */ if ((temp->td_flags & htole32(OHCI_TD_DP_MASK)) == htole32(OHCI_TD_IN)) { temp->td_flags |= htole32(OHCI_TD_R); } else { temp->td_flags &= ~htole32(OHCI_TD_R); } restart: td = temp->td; td_next = temp->td_next; while (1) { if (temp->len == 0) { if (temp->shortpkt) { break; } /* send a Zero Length Packet, ZLP, last */ temp->shortpkt = 1; average = 0; } else { average = temp->average; if (temp->len < average) { if (temp->len % temp->max_frame_size) { temp->shortpkt = 1; } average = temp->len; } } if (td_next == NULL) { panic("%s: out of OHCI transfer descriptors!", __FUNCTION__); } /* get next TD */ td = td_next; td_next = td->obj_next; /* check if we are pre-computing */ if (precompute) { /* update remaining length */ temp->len -= average; continue; } /* fill out current TD */ td->td_flags = temp->td_flags; /* the next TD uses TOGGLE_CARRY */ temp->td_flags &= ~htole32(OHCI_TD_TOGGLE_MASK); if (average == 0) { /* * The buffer start and end phys addresses should be * 0x0 for a zero length packet. */ td->td_cbp = 0; td->td_be = 0; td->len = 0; } else { usbd_get_page(temp->pc, buf_offset, &buf_res); td->td_cbp = htole32(buf_res.physaddr); buf_offset += (average - 1); usbd_get_page(temp->pc, buf_offset, &buf_res); td->td_be = htole32(buf_res.physaddr); buf_offset++; td->len = average; /* update remaining length */ temp->len -= average; } if ((td_next == td_alt_next) && temp->setup_alt_next) { /* we need to receive these frames one by one ! */ td->td_flags &= htole32(~OHCI_TD_INTR_MASK); td->td_flags |= htole32(OHCI_TD_SET_DI(1)); td->td_next = htole32(OHCI_TD_NEXT_END); } else { if (td_next) { /* link the current TD with the next one */ td->td_next = td_next->td_self; } } td->alt_next = td_alt_next; usb_pc_cpu_flush(td->page_cache); } if (precompute) { precompute = 0; /* setup alt next pointer, if any */ if (temp->last_frame) { /* no alternate next */ td_alt_next = NULL; } else { /* we use this field internally */ td_alt_next = td_next; } /* restore */ temp->shortpkt = shortpkt_old; temp->len = len_old; goto restart; } temp->td = td; temp->td_next = td_next; } static void ohci_setup_standard_chain(struct usb_xfer *xfer, ohci_ed_t **ed_last) { struct ohci_std_temp temp; const struct usb_pipe_methods *methods; ohci_ed_t *ed; ohci_td_t *td; uint32_t ed_flags; uint32_t x; DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n", xfer->address, UE_GET_ADDR(xfer->endpointno), xfer->sumlen, usbd_get_speed(xfer->xroot->udev)); temp.average = xfer->max_hc_frame_size; temp.max_frame_size = xfer->max_frame_size; /* toggle the DMA set we are using */ xfer->flags_int.curr_dma_set ^= 1; /* get next DMA set */ td = xfer->td_start[xfer->flags_int.curr_dma_set]; xfer->td_transfer_first = td; xfer->td_transfer_cache = td; temp.td = NULL; temp.td_next = td; temp.last_frame = 0; temp.setup_alt_next = xfer->flags_int.short_frames_ok; methods = xfer->endpoint->methods; /* check if we should prepend a setup message */ if (xfer->flags_int.control_xfr) { if (xfer->flags_int.control_hdr) { temp.td_flags = htole32(OHCI_TD_SETUP | OHCI_TD_NOCC | OHCI_TD_TOGGLE_0 | OHCI_TD_NOINTR); temp.len = xfer->frlengths[0]; temp.pc = xfer->frbuffers + 0; temp.shortpkt = temp.len ? 1 : 0; /* check for last frame */ if (xfer->nframes == 1) { /* no STATUS stage yet, SETUP is last */ if (xfer->flags_int.control_act) { temp.last_frame = 1; temp.setup_alt_next = 0; } } ohci_setup_standard_chain_sub(&temp); /* * XXX assume that the setup message is * contained within one USB packet: */ xfer->endpoint->toggle_next = 1; } x = 1; } else { x = 0; } temp.td_flags = htole32(OHCI_TD_NOCC | OHCI_TD_NOINTR); /* set data toggle */ if (xfer->endpoint->toggle_next) { temp.td_flags |= htole32(OHCI_TD_TOGGLE_1); } else { temp.td_flags |= htole32(OHCI_TD_TOGGLE_0); } /* set endpoint direction */ if (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN) { temp.td_flags |= htole32(OHCI_TD_IN); } else { temp.td_flags |= htole32(OHCI_TD_OUT); } while (x != xfer->nframes) { /* DATA0 / DATA1 message */ temp.len = xfer->frlengths[x]; temp.pc = xfer->frbuffers + x; x++; if (x == xfer->nframes) { if (xfer->flags_int.control_xfr) { /* no STATUS stage yet, DATA is last */ if (xfer->flags_int.control_act) { temp.last_frame = 1; temp.setup_alt_next = 0; } } else { temp.last_frame = 1; temp.setup_alt_next = 0; } } if (temp.len == 0) { /* make sure that we send an USB packet */ temp.shortpkt = 0; } else { /* regular data transfer */ temp.shortpkt = (xfer->flags.force_short_xfer) ? 0 : 1; } ohci_setup_standard_chain_sub(&temp); } /* check if we should append a status stage */ if (xfer->flags_int.control_xfr && !xfer->flags_int.control_act) { /* * Send a DATA1 message and invert the current endpoint * direction. */ /* set endpoint direction and data toggle */ if (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN) { temp.td_flags = htole32(OHCI_TD_OUT | OHCI_TD_NOCC | OHCI_TD_TOGGLE_1 | OHCI_TD_SET_DI(1)); } else { temp.td_flags = htole32(OHCI_TD_IN | OHCI_TD_NOCC | OHCI_TD_TOGGLE_1 | OHCI_TD_SET_DI(1)); } temp.len = 0; temp.pc = NULL; temp.shortpkt = 0; temp.last_frame = 1; temp.setup_alt_next = 0; ohci_setup_standard_chain_sub(&temp); } td = temp.td; /* Ensure that last TD is terminating: */ td->td_next = htole32(OHCI_TD_NEXT_END); td->td_flags &= ~htole32(OHCI_TD_INTR_MASK); td->td_flags |= htole32(OHCI_TD_SET_DI(1)); usb_pc_cpu_flush(td->page_cache); /* must have at least one frame! */ xfer->td_transfer_last = td; #ifdef USB_DEBUG if (ohcidebug > 8) { DPRINTF("nexttog=%d; data before transfer:\n", xfer->endpoint->toggle_next); ohci_dump_tds(xfer->td_transfer_first); } #endif ed = xfer->qh_start[xfer->flags_int.curr_dma_set]; ed_flags = (OHCI_ED_SET_FA(xfer->address) | OHCI_ED_SET_EN(UE_GET_ADDR(xfer->endpointno)) | OHCI_ED_SET_MAXP(xfer->max_frame_size)); ed_flags |= (OHCI_ED_FORMAT_GEN | OHCI_ED_DIR_TD); if (xfer->xroot->udev->speed == USB_SPEED_LOW) { ed_flags |= OHCI_ED_SPEED; } ed->ed_flags = htole32(ed_flags); td = xfer->td_transfer_first; ed->ed_headp = td->td_self; if (xfer->xroot->udev->flags.self_suspended == 0) { /* the append function will flush the endpoint descriptor */ OHCI_APPEND_QH(ed, *ed_last); if (methods == &ohci_device_bulk_methods) { ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus); OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_BLF); } if (methods == &ohci_device_ctrl_methods) { ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus); OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_CLF); } } else { usb_pc_cpu_flush(ed->page_cache); } } static void ohci_root_intr(ohci_softc_t *sc) { - uint32_t hstatus; + uint32_t hstatus __usbdebug_used; uint16_t i; uint16_t m; USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); /* clear any old interrupt data */ memset(sc->sc_hub_idata, 0, sizeof(sc->sc_hub_idata)); hstatus = OREAD4(sc, OHCI_RH_STATUS); DPRINTF("sc=%p hstatus=0x%08x\n", sc, hstatus); /* set bits */ m = (sc->sc_noport + 1); if (m > (8 * sizeof(sc->sc_hub_idata))) { m = (8 * sizeof(sc->sc_hub_idata)); } for (i = 1; i < m; i++) { /* pick out CHANGE bits from the status register */ if (OREAD4(sc, OHCI_RH_PORT_STATUS(i)) >> 16) { sc->sc_hub_idata[i / 8] |= 1 << (i % 8); DPRINTF("port %d changed\n", i); } } uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata, sizeof(sc->sc_hub_idata)); } /* NOTE: "done" can be run two times in a row, * from close and from interrupt */ static void ohci_device_done(struct usb_xfer *xfer, usb_error_t error) { const struct usb_pipe_methods *methods = xfer->endpoint->methods; ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus); ohci_ed_t *ed; USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); DPRINTFN(2, "xfer=%p, endpoint=%p, error=%d\n", xfer, xfer->endpoint, error); ed = xfer->qh_start[xfer->flags_int.curr_dma_set]; if (ed) { usb_pc_cpu_invalidate(ed->page_cache); } if (methods == &ohci_device_bulk_methods) { OHCI_REMOVE_QH(ed, sc->sc_bulk_p_last); } if (methods == &ohci_device_ctrl_methods) { OHCI_REMOVE_QH(ed, sc->sc_ctrl_p_last); } if (methods == &ohci_device_intr_methods) { OHCI_REMOVE_QH(ed, sc->sc_intr_p_last[xfer->qh_pos]); } if (methods == &ohci_device_isoc_methods) { OHCI_REMOVE_QH(ed, sc->sc_isoc_p_last); } xfer->td_transfer_first = NULL; xfer->td_transfer_last = NULL; /* dequeue transfer and start next transfer */ usbd_transfer_done(xfer, error); } /*------------------------------------------------------------------------* * ohci bulk support *------------------------------------------------------------------------*/ static void ohci_device_bulk_open(struct usb_xfer *xfer) { return; } static void ohci_device_bulk_close(struct usb_xfer *xfer) { ohci_device_done(xfer, USB_ERR_CANCELLED); } static void ohci_device_bulk_enter(struct usb_xfer *xfer) { return; } static void ohci_device_bulk_start(struct usb_xfer *xfer) { ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus); /* setup TD's and QH */ ohci_setup_standard_chain(xfer, &sc->sc_bulk_p_last); /* put transfer on interrupt queue */ ohci_transfer_intr_enqueue(xfer); } static const struct usb_pipe_methods ohci_device_bulk_methods = { .open = ohci_device_bulk_open, .close = ohci_device_bulk_close, .enter = ohci_device_bulk_enter, .start = ohci_device_bulk_start, }; /*------------------------------------------------------------------------* * ohci control support *------------------------------------------------------------------------*/ static void ohci_device_ctrl_open(struct usb_xfer *xfer) { return; } static void ohci_device_ctrl_close(struct usb_xfer *xfer) { ohci_device_done(xfer, USB_ERR_CANCELLED); } static void ohci_device_ctrl_enter(struct usb_xfer *xfer) { return; } static void ohci_device_ctrl_start(struct usb_xfer *xfer) { ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus); /* setup TD's and QH */ ohci_setup_standard_chain(xfer, &sc->sc_ctrl_p_last); /* put transfer on interrupt queue */ ohci_transfer_intr_enqueue(xfer); } static const struct usb_pipe_methods ohci_device_ctrl_methods = { .open = ohci_device_ctrl_open, .close = ohci_device_ctrl_close, .enter = ohci_device_ctrl_enter, .start = ohci_device_ctrl_start, }; /*------------------------------------------------------------------------* * ohci interrupt support *------------------------------------------------------------------------*/ static void ohci_device_intr_open(struct usb_xfer *xfer) { ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus); uint16_t best; uint16_t bit; uint16_t x; best = 0; bit = OHCI_NO_EDS / 2; while (bit) { if (xfer->interval >= bit) { x = bit; best = bit; while (x & bit) { if (sc->sc_intr_stat[x] < sc->sc_intr_stat[best]) { best = x; } x++; } break; } bit >>= 1; } sc->sc_intr_stat[best]++; xfer->qh_pos = best; DPRINTFN(3, "best=%d interval=%d\n", best, xfer->interval); } static void ohci_device_intr_close(struct usb_xfer *xfer) { ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus); sc->sc_intr_stat[xfer->qh_pos]--; ohci_device_done(xfer, USB_ERR_CANCELLED); } static void ohci_device_intr_enter(struct usb_xfer *xfer) { return; } static void ohci_device_intr_start(struct usb_xfer *xfer) { ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus); /* setup TD's and QH */ ohci_setup_standard_chain(xfer, &sc->sc_intr_p_last[xfer->qh_pos]); /* put transfer on interrupt queue */ ohci_transfer_intr_enqueue(xfer); } static const struct usb_pipe_methods ohci_device_intr_methods = { .open = ohci_device_intr_open, .close = ohci_device_intr_close, .enter = ohci_device_intr_enter, .start = ohci_device_intr_start, }; /*------------------------------------------------------------------------* * ohci isochronous support *------------------------------------------------------------------------*/ static void ohci_device_isoc_open(struct usb_xfer *xfer) { return; } static void ohci_device_isoc_close(struct usb_xfer *xfer) { /**/ ohci_device_done(xfer, USB_ERR_CANCELLED); } static void ohci_device_isoc_enter(struct usb_xfer *xfer) { struct usb_page_search buf_res; ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus); struct ohci_hcca *hcca; uint32_t buf_offset; uint32_t nframes; uint32_t startframe; uint32_t ed_flags; uint32_t *plen; uint16_t itd_offset[OHCI_ITD_NOFFSET]; uint16_t length; uint8_t ncur; ohci_itd_t *td; ohci_itd_t *td_last = NULL; ohci_ed_t *ed; hcca = ohci_get_hcca(sc); nframes = le32toh(hcca->hcca_frame_number); DPRINTFN(6, "xfer=%p isoc_next=%u nframes=%u hcca_fn=%u\n", xfer, xfer->endpoint->isoc_next, xfer->nframes, nframes); if (usbd_xfer_get_isochronous_start_frame( xfer, nframes, 0, 1, 0xFFFF, &startframe)) DPRINTFN(3, "start next=%d\n", startframe); /* get the real number of frames */ nframes = xfer->nframes; buf_offset = 0; plen = xfer->frlengths; /* toggle the DMA set we are using */ xfer->flags_int.curr_dma_set ^= 1; /* get next DMA set */ td = xfer->td_start[xfer->flags_int.curr_dma_set]; xfer->td_transfer_first = td; ncur = 0; length = 0; while (nframes--) { if (td == NULL) { panic("%s:%d: out of TD's\n", __FUNCTION__, __LINE__); } itd_offset[ncur] = length; buf_offset += *plen; length += *plen; plen++; ncur++; if ( /* check if the ITD is full */ (ncur == OHCI_ITD_NOFFSET) || /* check if we have put more than 4K into the ITD */ (length & 0xF000) || /* check if it is the last frame */ (nframes == 0)) { /* fill current ITD */ td->itd_flags = htole32( OHCI_ITD_NOCC | OHCI_ITD_SET_SF(startframe) | OHCI_ITD_NOINTR | OHCI_ITD_SET_FC(ncur)); td->frames = ncur; startframe += ncur; if (length == 0) { /* all zero */ td->itd_bp0 = 0; td->itd_be = ~0; while (ncur--) { td->itd_offset[ncur] = htole16(OHCI_ITD_MK_OFFS(0)); } } else { usbd_get_page(xfer->frbuffers, buf_offset - length, &buf_res); length = OHCI_PAGE_MASK(buf_res.physaddr); buf_res.physaddr = OHCI_PAGE(buf_res.physaddr); td->itd_bp0 = htole32(buf_res.physaddr); usbd_get_page(xfer->frbuffers, buf_offset - 1, &buf_res); td->itd_be = htole32(buf_res.physaddr); while (ncur--) { itd_offset[ncur] += length; itd_offset[ncur] = OHCI_ITD_MK_OFFS(itd_offset[ncur]); td->itd_offset[ncur] = htole16(itd_offset[ncur]); } } ncur = 0; length = 0; td_last = td; td = td->obj_next; if (td) { /* link the last TD with the next one */ td_last->itd_next = td->itd_self; } usb_pc_cpu_flush(td_last->page_cache); } } /* update the last TD */ td_last->itd_flags &= ~htole32(OHCI_ITD_NOINTR); td_last->itd_flags |= htole32(OHCI_ITD_SET_DI(0)); td_last->itd_next = 0; usb_pc_cpu_flush(td_last->page_cache); xfer->td_transfer_last = td_last; #ifdef USB_DEBUG if (ohcidebug > 8) { DPRINTF("data before transfer:\n"); ohci_dump_itds(xfer->td_transfer_first); } #endif ed = xfer->qh_start[xfer->flags_int.curr_dma_set]; if (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN) ed_flags = (OHCI_ED_DIR_IN | OHCI_ED_FORMAT_ISO); else ed_flags = (OHCI_ED_DIR_OUT | OHCI_ED_FORMAT_ISO); ed_flags |= (OHCI_ED_SET_FA(xfer->address) | OHCI_ED_SET_EN(UE_GET_ADDR(xfer->endpointno)) | OHCI_ED_SET_MAXP(xfer->max_frame_size)); if (xfer->xroot->udev->speed == USB_SPEED_LOW) { ed_flags |= OHCI_ED_SPEED; } ed->ed_flags = htole32(ed_flags); td = xfer->td_transfer_first; ed->ed_headp = td->itd_self; /* isochronous transfers are not affected by suspend / resume */ /* the append function will flush the endpoint descriptor */ OHCI_APPEND_QH(ed, sc->sc_isoc_p_last); } static void ohci_device_isoc_start(struct usb_xfer *xfer) { /* put transfer on interrupt queue */ ohci_transfer_intr_enqueue(xfer); } static const struct usb_pipe_methods ohci_device_isoc_methods = { .open = ohci_device_isoc_open, .close = ohci_device_isoc_close, .enter = ohci_device_isoc_enter, .start = ohci_device_isoc_start, }; /*------------------------------------------------------------------------* * ohci root control support *------------------------------------------------------------------------* * Simulate a hardware hub by handling all the necessary requests. *------------------------------------------------------------------------*/ static const struct usb_device_descriptor ohci_devd = { sizeof(struct usb_device_descriptor), 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 indexes */ 1 /* # of configurations */ }; static const struct ohci_config_desc ohci_confd = { .confd = { .bLength = sizeof(struct usb_config_descriptor), .bDescriptorType = UDESC_CONFIG, .wTotalLength[0] = sizeof(ohci_confd), .bNumInterface = 1, .bConfigurationValue = 1, .iConfiguration = 0, .bmAttributes = UC_SELF_POWERED, .bMaxPower = 0, /* max power */ }, .ifcd = { .bLength = sizeof(struct usb_interface_descriptor), .bDescriptorType = UDESC_INTERFACE, .bNumEndpoints = 1, .bInterfaceClass = UICLASS_HUB, .bInterfaceSubClass = UISUBCLASS_HUB, .bInterfaceProtocol = 0, }, .endpd = { .bLength = sizeof(struct usb_endpoint_descriptor), .bDescriptorType = UDESC_ENDPOINT, .bEndpointAddress = UE_DIR_IN | OHCI_INTR_ENDPT, .bmAttributes = UE_INTERRUPT, .wMaxPacketSize[0] = 32,/* max packet (255 ports) */ .bInterval = 255, }, }; static const struct usb_hub_descriptor ohci_hubd = { .bDescLength = 0, /* dynamic length */ .bDescriptorType = UDESC_HUB, }; static usb_error_t ohci_roothub_exec(struct usb_device *udev, struct usb_device_request *req, const void **pptr, uint16_t *plength) { ohci_softc_t *sc = OHCI_BUS2SC(udev->bus); const void *ptr; const char *str_ptr; uint32_t port; uint32_t v; uint16_t len; uint16_t value; uint16_t index; uint8_t l; usb_error_t err; USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); /* buffer reset */ ptr = (const void *)&sc->sc_hub_desc.temp; len = 0; err = 0; value = UGETW(req->wValue); index = UGETW(req->wIndex); DPRINTFN(3, "type=0x%02x request=0x%02x wLen=0x%04x " "wValue=0x%04x wIndex=0x%04x\n", req->bmRequestType, req->bRequest, UGETW(req->wLength), value, index); #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): len = 1; sc->sc_hub_desc.temp[0] = sc->sc_conf; break; case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE): switch (value >> 8) { case UDESC_DEVICE: if ((value & 0xff) != 0) { err = USB_ERR_IOERROR; goto done; } len = sizeof(ohci_devd); ptr = (const void *)&ohci_devd; break; case UDESC_CONFIG: if ((value & 0xff) != 0) { err = USB_ERR_IOERROR; goto done; } len = sizeof(ohci_confd); ptr = (const void *)&ohci_confd; break; case UDESC_STRING: switch (value & 0xff) { case 0: /* Language table */ str_ptr = "\001"; break; case 1: /* Vendor */ str_ptr = sc->sc_vendor; break; case 2: /* Product */ str_ptr = "OHCI root HUB"; break; default: str_ptr = ""; break; } len = usb_make_str_desc( sc->sc_hub_desc.temp, sizeof(sc->sc_hub_desc.temp), str_ptr); break; default: err = USB_ERR_IOERROR; goto done; } break; case C(UR_GET_INTERFACE, UT_READ_INTERFACE): len = 1; sc->sc_hub_desc.temp[0] = 0; break; case C(UR_GET_STATUS, UT_READ_DEVICE): len = 2; USETW(sc->sc_hub_desc.stat.wStatus, UDS_SELF_POWERED); break; case C(UR_GET_STATUS, UT_READ_INTERFACE): case C(UR_GET_STATUS, UT_READ_ENDPOINT): len = 2; USETW(sc->sc_hub_desc.stat.wStatus, 0); break; case C(UR_SET_ADDRESS, UT_WRITE_DEVICE): if (value >= OHCI_MAX_DEVICES) { err = USB_ERR_IOERROR; goto done; } sc->sc_addr = value; break; case C(UR_SET_CONFIG, UT_WRITE_DEVICE): if ((value != 0) && (value != 1)) { err = USB_ERR_IOERROR; goto done; } 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 = USB_ERR_IOERROR; goto done; 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(9, "UR_CLEAR_PORT_FEATURE " "port=%d feature=%d\n", index, value); if ((index < 1) || (index > sc->sc_noport)) { err = USB_ERR_IOERROR; goto done; } 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 = USB_ERR_IOERROR; goto done; } 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_enable(sc); break; default: break; } break; case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE): if ((value & 0xff) != 0) { err = USB_ERR_IOERROR; goto done; } v = OREAD4(sc, OHCI_RH_DESCRIPTOR_A); sc->sc_hub_desc.hubd = ohci_hubd; sc->sc_hub_desc.hubd.bNbrPorts = sc->sc_noport; USETW(sc->sc_hub_desc.hubd.wHubCharacteristics, (v & OHCI_NPS ? UHD_PWR_NO_SWITCH : v & OHCI_PSM ? UHD_PWR_GANGED : UHD_PWR_INDIVIDUAL) /* XXX overcurrent */ ); sc->sc_hub_desc.hubd.bPwrOn2PwrGood = OHCI_GET_POTPGT(v); v = OREAD4(sc, OHCI_RH_DESCRIPTOR_B); for (l = 0; l < sc->sc_noport; l++) { if (v & 1) { sc->sc_hub_desc.hubd.DeviceRemovable[l / 8] |= (1 << (l % 8)); } v >>= 1; } sc->sc_hub_desc.hubd.bDescLength = 8 + ((sc->sc_noport + 7) / 8); len = sc->sc_hub_desc.hubd.bDescLength; break; case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE): len = 16; memset(sc->sc_hub_desc.temp, 0, 16); break; case C(UR_GET_STATUS, UT_READ_CLASS_OTHER): DPRINTFN(9, "get port status i=%d\n", index); if ((index < 1) || (index > sc->sc_noport)) { err = USB_ERR_IOERROR; goto done; } v = OREAD4(sc, OHCI_RH_PORT_STATUS(index)); DPRINTFN(9, "port status=0x%04x\n", v); v &= ~UPS_PORT_MODE_DEVICE; /* force host mode */ USETW(sc->sc_hub_desc.ps.wPortStatus, v); USETW(sc->sc_hub_desc.ps.wPortChange, v >> 16); len = sizeof(sc->sc_hub_desc.ps); break; case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE): err = USB_ERR_IOERROR; goto done; 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 = USB_ERR_IOERROR; goto done; } 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(6, "reset port %d\n", index); OWRITE4(sc, port, UPS_RESET); for (v = 0;; v++) { if (v < 12) { usb_pause_mtx(&sc->sc_bus.bus_mtx, USB_MS_TO_TICKS(usb_port_root_reset_delay)); if ((OREAD4(sc, port) & UPS_RESET) == 0) { break; } } else { err = USB_ERR_TIMEOUT; goto done; } } DPRINTFN(9, "ohci port %d reset, status = 0x%04x\n", index, OREAD4(sc, port)); break; case UHF_PORT_POWER: DPRINTFN(3, "set port power %d\n", index); OWRITE4(sc, port, UPS_PORT_POWER); break; default: err = USB_ERR_IOERROR; goto done; } break; default: err = USB_ERR_IOERROR; goto done; } done: *plength = len; *pptr = ptr; return (err); } static void ohci_xfer_setup(struct usb_setup_params *parm) { struct usb_page_search page_info; struct usb_page_cache *pc; struct usb_xfer *xfer; void *last_obj; uint32_t ntd; uint32_t nitd; uint32_t nqh; uint32_t n; xfer = parm->curr_xfer; parm->hc_max_packet_size = 0x500; parm->hc_max_packet_count = 1; parm->hc_max_frame_size = OHCI_PAGE_SIZE; /* * calculate ntd and nqh */ if (parm->methods == &ohci_device_ctrl_methods) { xfer->flags_int.bdma_enable = 1; usbd_transfer_setup_sub(parm); nitd = 0; ntd = ((2 * xfer->nframes) + 1 /* STATUS */ + (xfer->max_data_length / xfer->max_hc_frame_size)); nqh = 1; } else if (parm->methods == &ohci_device_bulk_methods) { xfer->flags_int.bdma_enable = 1; usbd_transfer_setup_sub(parm); nitd = 0; ntd = ((2 * xfer->nframes) + (xfer->max_data_length / xfer->max_hc_frame_size)); nqh = 1; } else if (parm->methods == &ohci_device_intr_methods) { xfer->flags_int.bdma_enable = 1; usbd_transfer_setup_sub(parm); nitd = 0; ntd = ((2 * xfer->nframes) + (xfer->max_data_length / xfer->max_hc_frame_size)); nqh = 1; } else if (parm->methods == &ohci_device_isoc_methods) { xfer->flags_int.bdma_enable = 1; usbd_transfer_setup_sub(parm); nitd = ((xfer->max_data_length / OHCI_PAGE_SIZE) + howmany(xfer->nframes, OHCI_ITD_NOFFSET) + 1 /* EXTRA */ ); ntd = 0; nqh = 1; } else { usbd_transfer_setup_sub(parm); nitd = 0; ntd = 0; nqh = 0; } alloc_dma_set: if (parm->err) { return; } last_obj = NULL; if (usbd_transfer_setup_sub_malloc( parm, &pc, sizeof(ohci_td_t), OHCI_TD_ALIGN, ntd)) { parm->err = USB_ERR_NOMEM; return; } if (parm->buf) { for (n = 0; n != ntd; n++) { ohci_td_t *td; usbd_get_page(pc + n, 0, &page_info); td = page_info.buffer; /* init TD */ td->td_self = htole32(page_info.physaddr); td->obj_next = last_obj; td->page_cache = pc + n; last_obj = td; usb_pc_cpu_flush(pc + n); } } if (usbd_transfer_setup_sub_malloc( parm, &pc, sizeof(ohci_itd_t), OHCI_ITD_ALIGN, nitd)) { parm->err = USB_ERR_NOMEM; return; } if (parm->buf) { for (n = 0; n != nitd; n++) { ohci_itd_t *itd; usbd_get_page(pc + n, 0, &page_info); itd = page_info.buffer; /* init TD */ itd->itd_self = htole32(page_info.physaddr); itd->obj_next = last_obj; itd->page_cache = pc + n; last_obj = itd; usb_pc_cpu_flush(pc + n); } } xfer->td_start[xfer->flags_int.curr_dma_set] = last_obj; last_obj = NULL; if (usbd_transfer_setup_sub_malloc( parm, &pc, sizeof(ohci_ed_t), OHCI_ED_ALIGN, nqh)) { parm->err = USB_ERR_NOMEM; return; } if (parm->buf) { for (n = 0; n != nqh; n++) { ohci_ed_t *ed; usbd_get_page(pc + n, 0, &page_info); ed = page_info.buffer; /* init QH */ ed->ed_self = htole32(page_info.physaddr); ed->obj_next = last_obj; ed->page_cache = pc + n; last_obj = ed; usb_pc_cpu_flush(pc + n); } } xfer->qh_start[xfer->flags_int.curr_dma_set] = last_obj; if (!xfer->flags_int.curr_dma_set) { xfer->flags_int.curr_dma_set = 1; goto alloc_dma_set; } } static void ohci_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc, struct usb_endpoint *ep) { ohci_softc_t *sc = OHCI_BUS2SC(udev->bus); DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d (%d)\n", ep, udev->address, edesc->bEndpointAddress, udev->flags.usb_mode, sc->sc_addr); if (udev->device_index != sc->sc_addr) { switch (edesc->bmAttributes & UE_XFERTYPE) { case UE_CONTROL: ep->methods = &ohci_device_ctrl_methods; break; case UE_INTERRUPT: ep->methods = &ohci_device_intr_methods; break; case UE_ISOCHRONOUS: if (udev->speed == USB_SPEED_FULL) { ep->methods = &ohci_device_isoc_methods; } break; case UE_BULK: ep->methods = &ohci_device_bulk_methods; break; default: /* do nothing */ break; } } } static void ohci_xfer_unsetup(struct usb_xfer *xfer) { return; } static void ohci_get_dma_delay(struct usb_device *udev, uint32_t *pus) { /* * Wait until hardware has finished any possible use of the * transfer descriptor(s) and QH */ *pus = (1125); /* microseconds */ } static void ohci_device_resume(struct usb_device *udev) { struct ohci_softc *sc = OHCI_BUS2SC(udev->bus); struct usb_xfer *xfer; const struct usb_pipe_methods *methods; ohci_ed_t *ed; DPRINTF("\n"); USB_BUS_LOCK(udev->bus); TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { if (xfer->xroot->udev == udev) { methods = xfer->endpoint->methods; ed = xfer->qh_start[xfer->flags_int.curr_dma_set]; if (methods == &ohci_device_bulk_methods) { OHCI_APPEND_QH(ed, sc->sc_bulk_p_last); OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_BLF); } if (methods == &ohci_device_ctrl_methods) { OHCI_APPEND_QH(ed, sc->sc_ctrl_p_last); OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_CLF); } if (methods == &ohci_device_intr_methods) { OHCI_APPEND_QH(ed, sc->sc_intr_p_last[xfer->qh_pos]); } } } USB_BUS_UNLOCK(udev->bus); return; } static void ohci_device_suspend(struct usb_device *udev) { struct ohci_softc *sc = OHCI_BUS2SC(udev->bus); struct usb_xfer *xfer; const struct usb_pipe_methods *methods; ohci_ed_t *ed; DPRINTF("\n"); USB_BUS_LOCK(udev->bus); TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) { if (xfer->xroot->udev == udev) { methods = xfer->endpoint->methods; ed = xfer->qh_start[xfer->flags_int.curr_dma_set]; if (methods == &ohci_device_bulk_methods) { OHCI_REMOVE_QH(ed, sc->sc_bulk_p_last); } if (methods == &ohci_device_ctrl_methods) { OHCI_REMOVE_QH(ed, sc->sc_ctrl_p_last); } if (methods == &ohci_device_intr_methods) { OHCI_REMOVE_QH(ed, sc->sc_intr_p_last[xfer->qh_pos]); } } } USB_BUS_UNLOCK(udev->bus); return; } static void ohci_set_hw_power_sleep(struct usb_bus *bus, uint32_t state) { struct ohci_softc *sc = OHCI_BUS2SC(bus); switch (state) { case USB_HW_POWER_SUSPEND: case USB_HW_POWER_SHUTDOWN: ohci_suspend(sc); break; case USB_HW_POWER_RESUME: ohci_resume(sc); break; default: break; } } static void ohci_set_hw_power(struct usb_bus *bus) { struct ohci_softc *sc = OHCI_BUS2SC(bus); uint32_t temp; uint32_t flags; DPRINTF("\n"); USB_BUS_LOCK(bus); flags = bus->hw_power_state; temp = OREAD4(sc, OHCI_CONTROL); temp &= ~(OHCI_PLE | OHCI_IE | OHCI_CLE | OHCI_BLE); if (flags & USB_HW_POWER_CONTROL) temp |= OHCI_CLE; if (flags & USB_HW_POWER_BULK) temp |= OHCI_BLE; if (flags & USB_HW_POWER_INTERRUPT) temp |= OHCI_PLE; if (flags & USB_HW_POWER_ISOC) temp |= OHCI_IE | OHCI_PLE; OWRITE4(sc, OHCI_CONTROL, temp); USB_BUS_UNLOCK(bus); return; } static const struct usb_bus_methods ohci_bus_methods = { .endpoint_init = ohci_ep_init, .xfer_setup = ohci_xfer_setup, .xfer_unsetup = ohci_xfer_unsetup, .get_dma_delay = ohci_get_dma_delay, .device_resume = ohci_device_resume, .device_suspend = ohci_device_suspend, .set_hw_power = ohci_set_hw_power, .set_hw_power_sleep = ohci_set_hw_power_sleep, .roothub_exec = ohci_roothub_exec, .xfer_poll = ohci_do_poll, }; diff --git a/sys/dev/usb/input/uep.c b/sys/dev/usb/input/uep.c index 5b96a31b1dee..5fece855e650 100644 --- a/sys/dev/usb/input/uep.c +++ b/sys/dev/usb/input/uep.c @@ -1,541 +1,541 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright 2010, Gleb Smirnoff * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ /* * http://www.eeti.com.tw/pdf/Software%20Programming%20Guide_v2.0.pdf */ #include "opt_evdev.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #ifdef EVDEV_SUPPORT #include #include #else #include #include #endif #define USB_DEBUG_VAR uep_debug #include #ifdef USB_DEBUG static int uep_debug = 0; static SYSCTL_NODE(_hw_usb, OID_AUTO, uep, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "USB uep"); SYSCTL_INT(_hw_usb_uep, OID_AUTO, debug, CTLFLAG_RWTUN, &uep_debug, 0, "Debug level"); #endif #define UEP_MAX_X 2047 #define UEP_MAX_Y 2047 #define UEP_DOWN 0x01 #define UEP_PACKET_LEN_MAX 16 #define UEP_PACKET_LEN_REPORT 5 #define UEP_PACKET_LEN_REPORT2 6 #define UEP_PACKET_DIAG 0x0a #define UEP_PACKET_REPORT_MASK 0xe0 #define UEP_PACKET_REPORT 0x80 #define UEP_PACKET_REPORT_PRESSURE 0xc0 #define UEP_PACKET_REPORT_PLAYER 0xa0 #define UEP_PACKET_LEN_MASK #define UEP_FIFO_BUF_SIZE 8 /* bytes */ #define UEP_FIFO_QUEUE_MAXLEN 50 /* units */ enum { UEP_INTR_DT, UEP_N_TRANSFER, }; struct uep_softc { struct mtx mtx; struct usb_xfer *xfer[UEP_N_TRANSFER]; #ifdef EVDEV_SUPPORT struct evdev_dev *evdev; #else struct usb_fifo_sc fifo; u_int pollrate; u_int state; #define UEP_ENABLED 0x01 #endif /* Reassembling buffer. */ u_char buf[UEP_PACKET_LEN_MAX]; uint8_t buf_len; }; static usb_callback_t uep_intr_callback; static device_probe_t uep_probe; static device_attach_t uep_attach; static device_detach_t uep_detach; #ifdef EVDEV_SUPPORT static evdev_open_t uep_ev_open; static evdev_close_t uep_ev_close; static const struct evdev_methods uep_evdev_methods = { .ev_open = &uep_ev_open, .ev_close = &uep_ev_close, }; #else /* !EVDEV_SUPPORT */ static usb_fifo_cmd_t uep_start_read; static usb_fifo_cmd_t uep_stop_read; static usb_fifo_open_t uep_open; static usb_fifo_close_t uep_close; static void uep_put_queue(struct uep_softc *, u_char *); static struct usb_fifo_methods uep_fifo_methods = { .f_open = &uep_open, .f_close = &uep_close, .f_start_read = &uep_start_read, .f_stop_read = &uep_stop_read, .basename[0] = "uep", }; #endif /* !EVDEV_SUPPORT */ static int get_pkt_len(u_char *buf) { if (buf[0] == UEP_PACKET_DIAG) { int len; len = buf[1] + 2; if (len > UEP_PACKET_LEN_MAX) { DPRINTF("bad packet len %u\n", len); return (UEP_PACKET_LEN_MAX); } return (len); } switch (buf[0] & UEP_PACKET_REPORT_MASK) { case UEP_PACKET_REPORT: return (UEP_PACKET_LEN_REPORT); case UEP_PACKET_REPORT_PRESSURE: case UEP_PACKET_REPORT_PLAYER: case UEP_PACKET_REPORT_PRESSURE | UEP_PACKET_REPORT_PLAYER: return (UEP_PACKET_LEN_REPORT2); default: DPRINTF("bad packet len 0\n"); return (0); } } static void uep_process_pkt(struct uep_softc *sc, u_char *buf) { - int32_t x, y; + int32_t x __usbdebug_used, y __usbdebug_used; #ifdef EVDEV_SUPPORT int touch; #endif if ((buf[0] & 0xFE) != 0x80) { DPRINTF("bad input packet format 0x%.2x\n", buf[0]); return; } /* * Packet format is 5 bytes: * * 1000000T * 0000AAAA * 0AAAAAAA * 0000BBBB * 0BBBBBBB * * T: 1=touched 0=not touched * A: bits of axis A position, MSB to LSB * B: bits of axis B position, MSB to LSB * * For the unit I have, which is CTF1020-S from CarTFT.com, * A = X and B = Y. But in NetBSD uep(4) it is other way round :) * * The controller sends a stream of T=1 events while the * panel is touched, followed by a single T=0 event. * */ x = (buf[1] << 7) | buf[2]; y = (buf[3] << 7) | buf[4]; DPRINTFN(2, "x %u y %u\n", x, y); #ifdef EVDEV_SUPPORT touch = buf[0] & (1 << 0); if (touch) { evdev_push_abs(sc->evdev, ABS_X, x); evdev_push_abs(sc->evdev, ABS_Y, y); } evdev_push_key(sc->evdev, BTN_TOUCH, touch); evdev_sync(sc->evdev); #else uep_put_queue(sc, buf); #endif } static void uep_intr_callback(struct usb_xfer *xfer, usb_error_t error) { struct uep_softc *sc = usbd_xfer_softc(xfer); int len; usbd_xfer_status(xfer, &len, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: { struct usb_page_cache *pc; u_char buf[17], *p; int pkt_len; if (len > (int)sizeof(buf)) { DPRINTF("bad input length %d\n", len); goto tr_setup; } pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, buf, len); /* * The below code mimics Linux a lot. I don't know * why NetBSD reads complete packets, but we need * to reassamble 'em like Linux does (tries?). */ if (sc->buf_len > 0) { int res; if (sc->buf_len == 1) sc->buf[1] = buf[0]; if ((pkt_len = get_pkt_len(sc->buf)) == 0) goto tr_setup; res = pkt_len - sc->buf_len; memcpy(sc->buf + sc->buf_len, buf, res); uep_process_pkt(sc, sc->buf); sc->buf_len = 0; p = buf + res; len -= res; } else p = buf; if (len == 1) { sc->buf[0] = buf[0]; sc->buf_len = 1; goto tr_setup; } while (len > 0) { if ((pkt_len = get_pkt_len(p)) == 0) goto tr_setup; /* full packet: process */ if (pkt_len <= len) { uep_process_pkt(sc, p); } else { /* incomplete packet: save in buffer */ memcpy(sc->buf, p, len); sc->buf_len = len; } p += pkt_len; len -= pkt_len; } } case USB_ST_SETUP: tr_setup: #ifndef EVDEV_SUPPORT /* check if we can put more data into the FIFO */ if (usb_fifo_put_bytes_max(sc->fifo.fp[USB_FIFO_RX]) == 0) break; #endif usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: if (error != USB_ERR_CANCELLED) { /* try clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static const struct usb_config uep_config[UEP_N_TRANSFER] = { [UEP_INTR_DT] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .bufsize = 0, /* use wMaxPacketSize */ .callback = &uep_intr_callback, }, }; static const STRUCT_USB_HOST_ID uep_devs[] = { {USB_VPI(USB_VENDOR_EGALAX, USB_PRODUCT_EGALAX_TPANEL, 0)}, {USB_VPI(USB_VENDOR_EGALAX, USB_PRODUCT_EGALAX_TPANEL2, 0)}, {USB_VPI(USB_VENDOR_EGALAX2, USB_PRODUCT_EGALAX2_TPANEL, 0)}, }; static int uep_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (uaa->info.bConfigIndex != 0) return (ENXIO); if (uaa->info.bIfaceIndex != 0) return (ENXIO); return (usbd_lookup_id_by_uaa(uep_devs, sizeof(uep_devs), uaa)); } static int uep_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct uep_softc *sc = device_get_softc(dev); int error; device_set_usb_desc(dev); mtx_init(&sc->mtx, "uep lock", NULL, MTX_DEF); error = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex, sc->xfer, uep_config, UEP_N_TRANSFER, sc, &sc->mtx); if (error) { DPRINTF("usbd_transfer_setup error=%s\n", usbd_errstr(error)); goto detach; } #ifdef EVDEV_SUPPORT sc->evdev = evdev_alloc(); evdev_set_name(sc->evdev, device_get_desc(dev)); evdev_set_phys(sc->evdev, device_get_nameunit(dev)); evdev_set_id(sc->evdev, BUS_USB, uaa->info.idVendor, uaa->info.idProduct, 0); evdev_set_serial(sc->evdev, usb_get_serial(uaa->device)); evdev_set_methods(sc->evdev, sc, &uep_evdev_methods); evdev_support_prop(sc->evdev, INPUT_PROP_DIRECT); evdev_support_event(sc->evdev, EV_SYN); evdev_support_event(sc->evdev, EV_ABS); evdev_support_event(sc->evdev, EV_KEY); evdev_support_key(sc->evdev, BTN_TOUCH); evdev_support_abs(sc->evdev, ABS_X, 0, UEP_MAX_X, 0, 0, 0); evdev_support_abs(sc->evdev, ABS_Y, 0, UEP_MAX_Y, 0, 0, 0); error = evdev_register_mtx(sc->evdev, &sc->mtx); if (error) { DPRINTF("evdev_register_mtx error=%s\n", usbd_errstr(error)); goto detach; } #else /* !EVDEV_SUPPORT */ error = usb_fifo_attach(uaa->device, sc, &sc->mtx, &uep_fifo_methods, &sc->fifo, device_get_unit(dev), -1, uaa->info.bIfaceIndex, UID_ROOT, GID_OPERATOR, 0644); if (error) { DPRINTF("usb_fifo_attach error=%s\n", usbd_errstr(error)); goto detach; } #endif /* !EVDEV_SUPPORT */ sc->buf_len = 0; return (0); detach: uep_detach(dev); return (ENOMEM); /* XXX */ } static int uep_detach(device_t dev) { struct uep_softc *sc = device_get_softc(dev); #ifdef EVDEV_SUPPORT evdev_free(sc->evdev); #else usb_fifo_detach(&sc->fifo); #endif usbd_transfer_unsetup(sc->xfer, UEP_N_TRANSFER); mtx_destroy(&sc->mtx); return (0); } #ifdef EVDEV_SUPPORT static int uep_ev_close(struct evdev_dev *evdev) { struct uep_softc *sc = evdev_get_softc(evdev); mtx_assert(&sc->mtx, MA_OWNED); usbd_transfer_stop(sc->xfer[UEP_INTR_DT]); return (0); } static int uep_ev_open(struct evdev_dev *evdev) { struct uep_softc *sc = evdev_get_softc(evdev); mtx_assert(&sc->mtx, MA_OWNED); usbd_transfer_start(sc->xfer[UEP_INTR_DT]); return (0); } #else /* !EVDEV_SUPPORT */ static void uep_start_read(struct usb_fifo *fifo) { struct uep_softc *sc = usb_fifo_softc(fifo); u_int rate; if ((rate = sc->pollrate) > 1000) rate = 1000; if (rate > 0 && sc->xfer[UEP_INTR_DT] != NULL) { usbd_transfer_stop(sc->xfer[UEP_INTR_DT]); usbd_xfer_set_interval(sc->xfer[UEP_INTR_DT], 1000 / rate); sc->pollrate = 0; } usbd_transfer_start(sc->xfer[UEP_INTR_DT]); } static void uep_stop_read(struct usb_fifo *fifo) { struct uep_softc *sc = usb_fifo_softc(fifo); usbd_transfer_stop(sc->xfer[UEP_INTR_DT]); } static void uep_put_queue(struct uep_softc *sc, u_char *buf) { usb_fifo_put_data_linear(sc->fifo.fp[USB_FIFO_RX], buf, UEP_PACKET_LEN_REPORT, 1); } static int uep_open(struct usb_fifo *fifo, int fflags) { if (fflags & FREAD) { struct uep_softc *sc = usb_fifo_softc(fifo); if (sc->state & UEP_ENABLED) return (EBUSY); if (usb_fifo_alloc_buffer(fifo, UEP_FIFO_BUF_SIZE, UEP_FIFO_QUEUE_MAXLEN)) return (ENOMEM); sc->state |= UEP_ENABLED; } return (0); } static void uep_close(struct usb_fifo *fifo, int fflags) { if (fflags & FREAD) { struct uep_softc *sc = usb_fifo_softc(fifo); sc->state &= ~(UEP_ENABLED); usb_fifo_free_buffer(fifo); } } #endif /* !EVDEV_SUPPORT */ static devclass_t uep_devclass; static device_method_t uep_methods[] = { DEVMETHOD(device_probe, uep_probe), DEVMETHOD(device_attach, uep_attach), DEVMETHOD(device_detach, uep_detach), { 0, 0 }, }; static driver_t uep_driver = { .name = "uep", .methods = uep_methods, .size = sizeof(struct uep_softc), }; DRIVER_MODULE(uep, uhub, uep_driver, uep_devclass, NULL, NULL); MODULE_DEPEND(uep, usb, 1, 1, 1); #ifdef EVDEV_SUPPORT MODULE_DEPEND(uep, evdev, 1, 1, 1); #endif MODULE_VERSION(uep, 1); USB_PNP_HOST_INFO(uep_devs); diff --git a/sys/dev/usb/net/if_cdce.c b/sys/dev/usb/net/if_cdce.c index f3c73567b7ce..b5fa4944974c 100644 --- a/sys/dev/usb/net/if_cdce.c +++ b/sys/dev/usb/net/if_cdce.c @@ -1,1748 +1,1748 @@ /* $NetBSD: if_cdce.c,v 1.4 2004/10/24 12:50:54 augustss Exp $ */ /*- * SPDX-License-Identifier: BSD-4-Clause * * Copyright (c) 1997, 1998, 1999, 2000-2003 Bill Paul * Copyright (c) 2003-2005 Craig Boston * Copyright (c) 2004 Daniel Hartmeier * Copyright (c) 2009 Hans Petter Selasky * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Bill Paul. * 4. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul, THE VOICES IN HIS HEAD OR * THE 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 Communication Device Class (Ethernet Networking Control Model) * http://www.usb.org/developers/devclass_docs/usbcdc11.pdf */ /* * USB Network Control Model (NCM) * http://www.usb.org/developers/devclass_docs/NCM10.zip */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "usbdevs.h" #define USB_DEBUG_VAR cdce_debug #include #include #include #include "usb_if.h" #include #include static device_probe_t cdce_probe; static device_attach_t cdce_attach; static device_detach_t cdce_detach; static device_suspend_t cdce_suspend; static device_resume_t cdce_resume; static usb_handle_request_t cdce_handle_request; static usb_callback_t cdce_bulk_write_callback; static usb_callback_t cdce_bulk_read_callback; static usb_callback_t cdce_intr_read_callback; static usb_callback_t cdce_intr_write_callback; #if CDCE_HAVE_NCM static usb_callback_t cdce_ncm_bulk_write_callback; static usb_callback_t cdce_ncm_bulk_read_callback; #endif static uether_fn_t cdce_attach_post; static uether_fn_t cdce_init; static uether_fn_t cdce_stop; static uether_fn_t cdce_start; static uether_fn_t cdce_setmulti; static uether_fn_t cdce_setpromisc; static int cdce_attach_post_sub(struct usb_ether *); static int cdce_ioctl(struct ifnet *, u_long, caddr_t); static int cdce_media_change_cb(struct ifnet *); static void cdce_media_status_cb(struct ifnet *, struct ifmediareq *); static uint32_t cdce_m_crc32(struct mbuf *, uint32_t, uint32_t); static void cdce_set_filter(struct usb_ether *); #ifdef USB_DEBUG static int cdce_debug = 0; static int cdce_tx_interval = 0; static SYSCTL_NODE(_hw_usb, OID_AUTO, cdce, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "USB CDC-Ethernet"); SYSCTL_INT(_hw_usb_cdce, OID_AUTO, debug, CTLFLAG_RWTUN, &cdce_debug, 0, "Debug level"); SYSCTL_INT(_hw_usb_cdce, OID_AUTO, interval, CTLFLAG_RWTUN, &cdce_tx_interval, 0, "NCM transmit interval in ms"); #else #define cdce_debug 0 #endif static const struct usb_config cdce_config[CDCE_N_TRANSFER] = { [CDCE_BULK_RX] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_RX, .if_index = 0, .frames = CDCE_FRAMES_MAX, .bufsize = (CDCE_FRAMES_MAX * MCLBYTES), .flags = {.pipe_bof = 1,.short_frames_ok = 1,.short_xfer_ok = 1,.ext_buffer = 1,}, .callback = cdce_bulk_read_callback, .timeout = 0, /* no timeout */ .usb_mode = USB_MODE_DUAL, /* both modes */ }, [CDCE_BULK_TX] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_TX, .if_index = 0, .frames = CDCE_FRAMES_MAX, .bufsize = (CDCE_FRAMES_MAX * MCLBYTES), .flags = {.pipe_bof = 1,.force_short_xfer = 1,.ext_buffer = 1,}, .callback = cdce_bulk_write_callback, .timeout = 10000, /* 10 seconds */ .usb_mode = USB_MODE_DUAL, /* both modes */ }, [CDCE_INTR_RX] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_RX, .if_index = 1, .bufsize = CDCE_IND_SIZE_MAX, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.no_pipe_ok = 1,}, .callback = cdce_intr_read_callback, .timeout = 0, .usb_mode = USB_MODE_HOST, }, [CDCE_INTR_TX] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_TX, .if_index = 1, .bufsize = CDCE_IND_SIZE_MAX, .flags = {.pipe_bof = 1,.force_short_xfer = 1,.no_pipe_ok = 1,}, .callback = cdce_intr_write_callback, .timeout = 10000, /* 10 seconds */ .usb_mode = USB_MODE_DEVICE, }, }; #if CDCE_HAVE_NCM static const struct usb_config cdce_ncm_config[CDCE_N_TRANSFER] = { [CDCE_BULK_RX] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_RX, .if_index = 0, .frames = CDCE_NCM_RX_FRAMES_MAX, .bufsize = (CDCE_NCM_RX_FRAMES_MAX * CDCE_NCM_RX_MAXLEN), .flags = {.pipe_bof = 1,.short_frames_ok = 1,.short_xfer_ok = 1,}, .callback = cdce_ncm_bulk_read_callback, .timeout = 0, /* no timeout */ .usb_mode = USB_MODE_DUAL, /* both modes */ }, [CDCE_BULK_TX] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_TX, .if_index = 0, .frames = CDCE_NCM_TX_FRAMES_MAX, .bufsize = (CDCE_NCM_TX_FRAMES_MAX * CDCE_NCM_TX_MAXLEN), .flags = {.pipe_bof = 1,}, .callback = cdce_ncm_bulk_write_callback, .timeout = 10000, /* 10 seconds */ .usb_mode = USB_MODE_DUAL, /* both modes */ }, [CDCE_INTR_RX] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_RX, .if_index = 1, .bufsize = CDCE_IND_SIZE_MAX, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.no_pipe_ok = 1,}, .callback = cdce_intr_read_callback, .timeout = 0, .usb_mode = USB_MODE_HOST, }, [CDCE_INTR_TX] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_TX, .if_index = 1, .bufsize = CDCE_IND_SIZE_MAX, .flags = {.pipe_bof = 1,.force_short_xfer = 1,.no_pipe_ok = 1,}, .callback = cdce_intr_write_callback, .timeout = 10000, /* 10 seconds */ .usb_mode = USB_MODE_DEVICE, }, }; #endif static device_method_t cdce_methods[] = { /* USB interface */ DEVMETHOD(usb_handle_request, cdce_handle_request), /* Device interface */ DEVMETHOD(device_probe, cdce_probe), DEVMETHOD(device_attach, cdce_attach), DEVMETHOD(device_detach, cdce_detach), DEVMETHOD(device_suspend, cdce_suspend), DEVMETHOD(device_resume, cdce_resume), DEVMETHOD_END }; static driver_t cdce_driver = { .name = "cdce", .methods = cdce_methods, .size = sizeof(struct cdce_softc), }; static devclass_t cdce_devclass; static eventhandler_tag cdce_etag; static int cdce_driver_loaded(struct module *, int, void *); static const STRUCT_USB_HOST_ID cdce_switch_devs[] = { {USB_VPI(USB_VENDOR_HUAWEI, USB_PRODUCT_HUAWEI_E3272_INIT, MSC_EJECT_HUAWEI2)}, }; static const STRUCT_USB_HOST_ID cdce_host_devs[] = { {USB_VPI(USB_VENDOR_ACERLABS, USB_PRODUCT_ACERLABS_M5632, CDCE_FLAG_NO_UNION)}, {USB_VPI(USB_VENDOR_AMBIT, USB_PRODUCT_AMBIT_NTL_250, CDCE_FLAG_NO_UNION)}, {USB_VPI(USB_VENDOR_COMPAQ, USB_PRODUCT_COMPAQ_IPAQLINUX, CDCE_FLAG_NO_UNION)}, {USB_VPI(USB_VENDOR_GMATE, USB_PRODUCT_GMATE_YP3X00, CDCE_FLAG_NO_UNION)}, {USB_VPI(USB_VENDOR_MOTOROLA2, USB_PRODUCT_MOTOROLA2_USBLAN, CDCE_FLAG_ZAURUS | CDCE_FLAG_NO_UNION)}, {USB_VPI(USB_VENDOR_MOTOROLA2, USB_PRODUCT_MOTOROLA2_USBLAN2, CDCE_FLAG_ZAURUS | CDCE_FLAG_NO_UNION)}, {USB_VPI(USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_PL2501, CDCE_FLAG_NO_UNION)}, {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SL5500, CDCE_FLAG_ZAURUS)}, {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SL5600, CDCE_FLAG_ZAURUS | CDCE_FLAG_NO_UNION)}, {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SLA300, CDCE_FLAG_ZAURUS | CDCE_FLAG_NO_UNION)}, {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SLC700, CDCE_FLAG_ZAURUS | CDCE_FLAG_NO_UNION)}, {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SLC750, CDCE_FLAG_ZAURUS | CDCE_FLAG_NO_UNION)}, {USB_VPI(USB_VENDOR_REALTEK, USB_PRODUCT_REALTEK_RTL8156, 0)}, {USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR), USB_IFACE_SUBCLASS(0x02), USB_IFACE_PROTOCOL(0x16), USB_DRIVER_INFO(0)}, {USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR), USB_IFACE_SUBCLASS(0x02), USB_IFACE_PROTOCOL(0x46), USB_DRIVER_INFO(0)}, {USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR), USB_IFACE_SUBCLASS(0x02), USB_IFACE_PROTOCOL(0x76), USB_DRIVER_INFO(0)}, {USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR), USB_IFACE_SUBCLASS(0x03), USB_IFACE_PROTOCOL(0x16), USB_DRIVER_INFO(0)}, }; static const STRUCT_USB_DUAL_ID cdce_dual_devs[] = { {USB_IF_CSI(UICLASS_CDC, UISUBCLASS_ETHERNET_NETWORKING_CONTROL_MODEL, 0)}, {USB_IF_CSI(UICLASS_CDC, UISUBCLASS_MOBILE_DIRECT_LINE_MODEL, 0)}, {USB_IF_CSI(UICLASS_CDC, UISUBCLASS_NETWORK_CONTROL_MODEL, 0)}, }; DRIVER_MODULE(cdce, uhub, cdce_driver, cdce_devclass, cdce_driver_loaded, 0); MODULE_VERSION(cdce, 1); MODULE_DEPEND(cdce, uether, 1, 1, 1); MODULE_DEPEND(cdce, usb, 1, 1, 1); MODULE_DEPEND(cdce, ether, 1, 1, 1); USB_PNP_DEVICE_INFO(cdce_switch_devs); USB_PNP_HOST_INFO(cdce_host_devs); USB_PNP_DUAL_INFO(cdce_dual_devs); static const struct usb_ether_methods cdce_ue_methods = { .ue_attach_post = cdce_attach_post, .ue_attach_post_sub = cdce_attach_post_sub, .ue_start = cdce_start, .ue_init = cdce_init, .ue_stop = cdce_stop, .ue_setmulti = cdce_setmulti, .ue_setpromisc = cdce_setpromisc, }; #if CDCE_HAVE_NCM /*------------------------------------------------------------------------* * cdce_ncm_init * * Return values: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ static uint8_t cdce_ncm_init(struct cdce_softc *sc) { struct usb_ncm_parameters temp; struct usb_device_request req; struct usb_ncm_func_descriptor *ufd; uint8_t value[8]; int err; ufd = usbd_find_descriptor(sc->sc_ue.ue_udev, NULL, sc->sc_ifaces_index[1], UDESC_CS_INTERFACE, 0xFF, UCDC_NCM_FUNC_DESC_SUBTYPE, 0xFF); /* verify length of NCM functional descriptor */ if (ufd != NULL) { if (ufd->bLength < sizeof(*ufd)) ufd = NULL; else DPRINTFN(1, "Found NCM functional descriptor.\n"); } req.bmRequestType = UT_READ_CLASS_INTERFACE; req.bRequest = UCDC_NCM_GET_NTB_PARAMETERS; USETW(req.wValue, 0); req.wIndex[0] = sc->sc_ifaces_index[1]; req.wIndex[1] = 0; USETW(req.wLength, sizeof(temp)); err = usbd_do_request_flags(sc->sc_ue.ue_udev, NULL, &req, &temp, 0, NULL, 1000 /* ms */); if (err) return (1); /* Read correct set of parameters according to device mode */ if (usbd_get_mode(sc->sc_ue.ue_udev) == USB_MODE_HOST) { sc->sc_ncm.rx_max = UGETDW(temp.dwNtbInMaxSize); sc->sc_ncm.tx_max = UGETDW(temp.dwNtbOutMaxSize); sc->sc_ncm.tx_remainder = UGETW(temp.wNdpOutPayloadRemainder); sc->sc_ncm.tx_modulus = UGETW(temp.wNdpOutDivisor); sc->sc_ncm.tx_struct_align = UGETW(temp.wNdpOutAlignment); sc->sc_ncm.tx_nframe = UGETW(temp.wNtbOutMaxDatagrams); } else { sc->sc_ncm.rx_max = UGETDW(temp.dwNtbOutMaxSize); sc->sc_ncm.tx_max = UGETDW(temp.dwNtbInMaxSize); sc->sc_ncm.tx_remainder = UGETW(temp.wNdpInPayloadRemainder); sc->sc_ncm.tx_modulus = UGETW(temp.wNdpInDivisor); sc->sc_ncm.tx_struct_align = UGETW(temp.wNdpInAlignment); sc->sc_ncm.tx_nframe = UGETW(temp.wNtbOutMaxDatagrams); } /* Verify maximum receive length */ if ((sc->sc_ncm.rx_max < 32) || (sc->sc_ncm.rx_max > CDCE_NCM_RX_MAXLEN)) { DPRINTFN(1, "Using default maximum receive length\n"); sc->sc_ncm.rx_max = CDCE_NCM_RX_MAXLEN; } /* Verify maximum transmit length */ if ((sc->sc_ncm.tx_max < 32) || (sc->sc_ncm.tx_max > CDCE_NCM_TX_MAXLEN)) { DPRINTFN(1, "Using default maximum transmit length\n"); sc->sc_ncm.tx_max = CDCE_NCM_TX_MAXLEN; } /* * Verify that the structure alignment is: * - power of two * - not greater than the maximum transmit length * - not less than four bytes */ if ((sc->sc_ncm.tx_struct_align < 4) || (sc->sc_ncm.tx_struct_align != ((-sc->sc_ncm.tx_struct_align) & sc->sc_ncm.tx_struct_align)) || (sc->sc_ncm.tx_struct_align >= sc->sc_ncm.tx_max)) { DPRINTFN(1, "Using default other alignment: 4 bytes\n"); sc->sc_ncm.tx_struct_align = 4; } /* * Verify that the payload alignment is: * - power of two * - not greater than the maximum transmit length * - not less than four bytes */ if ((sc->sc_ncm.tx_modulus < 4) || (sc->sc_ncm.tx_modulus != ((-sc->sc_ncm.tx_modulus) & sc->sc_ncm.tx_modulus)) || (sc->sc_ncm.tx_modulus >= sc->sc_ncm.tx_max)) { DPRINTFN(1, "Using default transmit modulus: 4 bytes\n"); sc->sc_ncm.tx_modulus = 4; } /* Verify that the payload remainder */ if ((sc->sc_ncm.tx_remainder >= sc->sc_ncm.tx_modulus)) { DPRINTFN(1, "Using default transmit remainder: 0 bytes\n"); sc->sc_ncm.tx_remainder = 0; } /* * Offset the TX remainder so that IP packet payload starts at * the tx_modulus. This is not too clear in the specification. */ sc->sc_ncm.tx_remainder = (sc->sc_ncm.tx_remainder - ETHER_HDR_LEN) & (sc->sc_ncm.tx_modulus - 1); /* Verify max datagrams */ if (sc->sc_ncm.tx_nframe == 0 || sc->sc_ncm.tx_nframe > (CDCE_NCM_SUBFRAMES_MAX - 1)) { DPRINTFN(1, "Using default max " "subframes: %u units\n", CDCE_NCM_SUBFRAMES_MAX - 1); /* need to reserve one entry for zero padding */ sc->sc_ncm.tx_nframe = (CDCE_NCM_SUBFRAMES_MAX - 1); } /* Additional configuration, will fail in device side mode, which is OK. */ req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UCDC_NCM_SET_NTB_INPUT_SIZE; USETW(req.wValue, 0); req.wIndex[0] = sc->sc_ifaces_index[1]; req.wIndex[1] = 0; if (ufd != NULL && (ufd->bmNetworkCapabilities & UCDC_NCM_CAP_MAX_DGRAM)) { USETW(req.wLength, 8); USETDW(value, sc->sc_ncm.rx_max); USETW(value + 4, (CDCE_NCM_SUBFRAMES_MAX - 1)); USETW(value + 6, 0); } else { USETW(req.wLength, 4); USETDW(value, sc->sc_ncm.rx_max); } err = usbd_do_request_flags(sc->sc_ue.ue_udev, NULL, &req, &value, 0, NULL, 1000 /* ms */); if (err) { DPRINTFN(1, "Setting input size " "to %u failed.\n", sc->sc_ncm.rx_max); } req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UCDC_NCM_SET_CRC_MODE; USETW(req.wValue, 0); /* no CRC */ req.wIndex[0] = sc->sc_ifaces_index[1]; req.wIndex[1] = 0; USETW(req.wLength, 0); err = usbd_do_request_flags(sc->sc_ue.ue_udev, NULL, &req, NULL, 0, NULL, 1000 /* ms */); if (err) { DPRINTFN(1, "Setting CRC mode to off failed.\n"); } req.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.bRequest = UCDC_NCM_SET_NTB_FORMAT; USETW(req.wValue, 0); /* NTB-16 */ req.wIndex[0] = sc->sc_ifaces_index[1]; req.wIndex[1] = 0; USETW(req.wLength, 0); err = usbd_do_request_flags(sc->sc_ue.ue_udev, NULL, &req, NULL, 0, NULL, 1000 /* ms */); if (err) { DPRINTFN(1, "Setting NTB format to 16-bit failed.\n"); } return (0); /* success */ } #endif static void cdce_test_autoinst(void *arg, struct usb_device *udev, struct usb_attach_arg *uaa) { struct usb_interface *iface; struct usb_interface_descriptor *id; if (uaa->dev_state != UAA_DEV_READY) return; iface = usbd_get_iface(udev, 0); if (iface == NULL) return; id = iface->idesc; if (id == NULL || id->bInterfaceClass != UICLASS_MASS) return; if (usbd_lookup_id_by_uaa(cdce_switch_devs, sizeof(cdce_switch_devs), uaa)) return; /* no device match */ if (usb_msc_eject(udev, 0, USB_GET_DRIVER_INFO(uaa)) == 0) { /* success, mark the udev as disappearing */ uaa->dev_state = UAA_DEV_EJECTING; } } static int cdce_driver_loaded(struct module *mod, int what, void *arg) { switch (what) { case MOD_LOAD: /* register our autoinstall handler */ cdce_etag = EVENTHANDLER_REGISTER(usb_dev_configured, cdce_test_autoinst, NULL, EVENTHANDLER_PRI_ANY); return (0); case MOD_UNLOAD: EVENTHANDLER_DEREGISTER(usb_dev_configured, cdce_etag); return (0); default: return (EOPNOTSUPP); } } static int cdce_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); int error; error = usbd_lookup_id_by_uaa(cdce_host_devs, sizeof(cdce_host_devs), uaa); if (error) error = usbd_lookup_id_by_uaa(cdce_dual_devs, sizeof(cdce_dual_devs), uaa); return (error); } static void cdce_attach_post(struct usb_ether *ue) { /* no-op */ return; } static int cdce_attach_post_sub(struct usb_ether *ue) { struct cdce_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); /* mostly copied from usb_ethernet.c */ ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_start = uether_start; ifp->if_ioctl = cdce_ioctl; ifp->if_init = uether_init; IFQ_SET_MAXLEN(&ifp->if_snd, ifqmaxlen); ifp->if_snd.ifq_drv_maxlen = ifqmaxlen; IFQ_SET_READY(&ifp->if_snd); if ((sc->sc_flags & CDCE_FLAG_VLAN) == CDCE_FLAG_VLAN) if_setcapabilitiesbit(ifp, IFCAP_VLAN_MTU, 0); if_setcapabilitiesbit(ifp, IFCAP_LINKSTATE, 0); if_setcapenable(ifp, if_getcapabilities(ifp)); ifmedia_init(&sc->sc_media, IFM_IMASK, cdce_media_change_cb, cdce_media_status_cb); ifmedia_add(&sc->sc_media, IFM_ETHER | IFM_AUTO, 0, NULL); ifmedia_set(&sc->sc_media, IFM_ETHER | IFM_AUTO); sc->sc_media.ifm_media = sc->sc_media.ifm_cur->ifm_media; CDCE_LOCK(sc); cdce_set_filter(ue); CDCE_UNLOCK(sc); return 0; } static int cdce_attach(device_t dev) { struct cdce_softc *sc = device_get_softc(dev); struct usb_ether *ue = &sc->sc_ue; struct usb_attach_arg *uaa = device_get_ivars(dev); struct usb_interface *iface; const struct usb_cdc_union_descriptor *ud; const struct usb_interface_descriptor *id; const struct usb_cdc_ethernet_descriptor *ued; const struct usb_config *pcfg; uint32_t seed; int error; uint8_t i; uint8_t data_iface_no; char eaddr_str[5 * ETHER_ADDR_LEN]; /* approx */ sc->sc_flags = USB_GET_DRIVER_INFO(uaa); sc->sc_ue.ue_udev = uaa->device; device_set_usb_desc(dev); mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF); ud = usbd_find_descriptor (uaa->device, NULL, uaa->info.bIfaceIndex, UDESC_CS_INTERFACE, 0xFF, UDESCSUB_CDC_UNION, 0xFF); if ((ud == NULL) || (ud->bLength < sizeof(*ud)) || (sc->sc_flags & CDCE_FLAG_NO_UNION)) { DPRINTFN(1, "No union descriptor!\n"); sc->sc_ifaces_index[0] = uaa->info.bIfaceIndex; sc->sc_ifaces_index[1] = uaa->info.bIfaceIndex; goto alloc_transfers; } data_iface_no = ud->bSlaveInterface[0]; for (i = 0;; i++) { iface = usbd_get_iface(uaa->device, i); if (iface) { id = usbd_get_interface_descriptor(iface); if (id && (id->bInterfaceNumber == data_iface_no)) { sc->sc_ifaces_index[0] = i; sc->sc_ifaces_index[1] = uaa->info.bIfaceIndex; usbd_set_parent_iface(uaa->device, i, uaa->info.bIfaceIndex); break; } } else { device_printf(dev, "no data interface found\n"); goto detach; } } /* * * * The Data Class interface of a networking device shall have * a minimum of two interface settings. The first setting * (the default interface setting) includes no endpoints and * therefore no networking traffic is exchanged whenever the * default interface setting is selected. One or more * additional interface settings are used for normal * operation, and therefore each includes a pair of endpoints * (one IN, and one OUT) to exchange network traffic. Select * an alternate interface setting to initialize the network * aspects of the device and to enable the exchange of * network traffic. * * * * Some devices, most notably cable modems, include interface * settings that have no IN or OUT endpoint, therefore loop * through the list of all available interface settings * looking for one with both IN and OUT endpoints. */ alloc_transfers: pcfg = cdce_config; /* Default Configuration */ for (i = 0; i != 32; i++) { error = usbd_set_alt_interface_index(uaa->device, sc->sc_ifaces_index[0], i); if (error) break; #if CDCE_HAVE_NCM if ((i == 0) && (cdce_ncm_init(sc) == 0)) pcfg = cdce_ncm_config; #endif error = usbd_transfer_setup(uaa->device, sc->sc_ifaces_index, sc->sc_xfer, pcfg, CDCE_N_TRANSFER, sc, &sc->sc_mtx); if (error == 0) break; } if (error || (i == 32)) { device_printf(dev, "No valid alternate " "setting found\n"); goto detach; } ued = usbd_find_descriptor (uaa->device, NULL, uaa->info.bIfaceIndex, UDESC_CS_INTERFACE, 0xFF, UDESCSUB_CDC_ENF, 0xFF); if ((ued == NULL) || (ued->bLength < sizeof(*ued))) { error = USB_ERR_INVAL; } else { /* * ECM 1.2 doesn't say it excludes the CRC, but states that it's * normally 1514, which excludes the CRC. */ DPRINTF("max segsize: %d\n", UGETW(ued->wMaxSegmentSize)); if (UGETW(ued->wMaxSegmentSize) >= (ETHER_MAX_LEN - ETHER_CRC_LEN + ETHER_VLAN_ENCAP_LEN)) sc->sc_flags |= CDCE_FLAG_VLAN; error = usbd_req_get_string_any(uaa->device, NULL, eaddr_str, sizeof(eaddr_str), ued->iMacAddress); } if (error) { /* fake MAC address */ device_printf(dev, "faking MAC address\n"); seed = ticks; sc->sc_ue.ue_eaddr[0] = 0x2a; memcpy(&sc->sc_ue.ue_eaddr[1], &seed, sizeof(uint32_t)); sc->sc_ue.ue_eaddr[5] = device_get_unit(dev); } else { memset(sc->sc_ue.ue_eaddr, 0, sizeof(sc->sc_ue.ue_eaddr)); for (i = 0; i != (ETHER_ADDR_LEN * 2); i++) { char c = eaddr_str[i]; if ('0' <= c && c <= '9') c -= '0'; else if (c != 0) c -= 'A' - 10; else break; c &= 0xf; if ((i & 1) == 0) c <<= 4; sc->sc_ue.ue_eaddr[i / 2] |= c; } if (uaa->usb_mode == USB_MODE_DEVICE) { /* * Do not use the same MAC address like the peer ! */ sc->sc_ue.ue_eaddr[5] ^= 0xFF; } } ue->ue_sc = sc; ue->ue_dev = dev; ue->ue_udev = uaa->device; ue->ue_mtx = &sc->sc_mtx; ue->ue_methods = &cdce_ue_methods; error = uether_ifattach(ue); if (error) { device_printf(dev, "could not attach interface\n"); goto detach; } return (0); /* success */ detach: cdce_detach(dev); return (ENXIO); /* failure */ } static int cdce_detach(device_t dev) { struct cdce_softc *sc = device_get_softc(dev); struct usb_ether *ue = &sc->sc_ue; /* stop all USB transfers first */ usbd_transfer_unsetup(sc->sc_xfer, CDCE_N_TRANSFER); uether_ifdetach(ue); mtx_destroy(&sc->sc_mtx); ifmedia_removeall(&sc->sc_media); return (0); } static void cdce_start(struct usb_ether *ue) { struct cdce_softc *sc = uether_getsc(ue); /* * Start the USB transfers, if not already started: */ usbd_transfer_start(sc->sc_xfer[CDCE_BULK_TX]); usbd_transfer_start(sc->sc_xfer[CDCE_BULK_RX]); } static int cdce_ioctl(struct ifnet *ifp, u_long command, caddr_t data) { struct usb_ether *ue = ifp->if_softc; struct cdce_softc *sc = uether_getsc(ue); struct ifreq *ifr = (struct ifreq *)data; int error; error = 0; switch(command) { case SIOCGIFMEDIA: case SIOCSIFMEDIA: error = ifmedia_ioctl(ifp, ifr, &sc->sc_media, command); break; default: error = uether_ioctl(ifp, command, data); break; } return (error); } static void cdce_free_queue(struct mbuf **ppm, uint8_t n) { uint8_t x; for (x = 0; x != n; x++) { if (ppm[x] != NULL) { m_freem(ppm[x]); ppm[x] = NULL; } } } static int cdce_media_change_cb(struct ifnet *ifp) { return (EOPNOTSUPP); } static void cdce_media_status_cb(struct ifnet *ifp, struct ifmediareq *ifmr) { if ((if_getflags(ifp) & IFF_UP) == 0) return; ifmr->ifm_active = IFM_ETHER; ifmr->ifm_status = IFM_AVALID; ifmr->ifm_status |= ifp->if_link_state == LINK_STATE_UP ? IFM_ACTIVE : 0; } static void cdce_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct cdce_softc *sc = usbd_xfer_softc(xfer); struct ifnet *ifp = uether_getifp(&sc->sc_ue); struct mbuf *m; struct mbuf *mt; uint32_t crc; uint8_t x; int actlen, aframes; usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL); DPRINTFN(1, "\n"); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTFN(11, "transfer complete: %u bytes in %u frames\n", actlen, aframes); if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); /* free all previous TX buffers */ cdce_free_queue(sc->sc_tx_buf, CDCE_FRAMES_MAX); /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: for (x = 0; x != CDCE_FRAMES_MAX; x++) { IFQ_DRV_DEQUEUE(&ifp->if_snd, m); if (m == NULL) break; if (sc->sc_flags & CDCE_FLAG_ZAURUS) { /* * Zaurus wants a 32-bit CRC appended * to every frame */ crc = cdce_m_crc32(m, 0, m->m_pkthdr.len); crc = htole32(crc); if (!m_append(m, 4, (void *)&crc)) { m_freem(m); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); continue; } } if (m->m_len != m->m_pkthdr.len) { mt = m_defrag(m, M_NOWAIT); if (mt == NULL) { m_freem(m); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); continue; } m = mt; } if (m->m_pkthdr.len > MCLBYTES) { m->m_pkthdr.len = MCLBYTES; } sc->sc_tx_buf[x] = m; usbd_xfer_set_frame_data(xfer, x, m->m_data, m->m_len); /* * If there's a BPF listener, bounce a copy of * this frame to him: */ BPF_MTAP(ifp, m); } if (x != 0) { usbd_xfer_set_frames(xfer, x); usbd_transfer_submit(xfer); } break; default: /* Error */ DPRINTFN(11, "transfer error, %s\n", usbd_errstr(error)); /* free all previous TX buffers */ cdce_free_queue(sc->sc_tx_buf, CDCE_FRAMES_MAX); /* count output errors */ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); if (error != USB_ERR_CANCELLED) { if (usbd_get_mode(sc->sc_ue.ue_udev) == USB_MODE_HOST) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); } goto tr_setup; } break; } } static int32_t cdce_m_crc32_cb(void *arg, void *src, uint32_t count) { uint32_t *p_crc = arg; *p_crc = crc32_raw(src, count, *p_crc); return (0); } static uint32_t cdce_m_crc32(struct mbuf *m, uint32_t src_offset, uint32_t src_len) { uint32_t crc = 0xFFFFFFFF; (void)m_apply(m, src_offset, src_len, cdce_m_crc32_cb, &crc); return (crc ^ 0xFFFFFFFF); } static void cdce_init(struct usb_ether *ue) { struct cdce_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); CDCE_LOCK_ASSERT(sc, MA_OWNED); ifp->if_drv_flags |= IFF_DRV_RUNNING; /* start interrupt transfer */ usbd_transfer_start(sc->sc_xfer[CDCE_INTR_RX]); usbd_transfer_start(sc->sc_xfer[CDCE_INTR_TX]); /* * Stall data write direction, which depends on USB mode. * * Some USB host stacks (e.g. Mac OS X) don't clears stall * bit as it should, so set it in our host mode only. */ if (usbd_get_mode(sc->sc_ue.ue_udev) == USB_MODE_HOST) usbd_xfer_set_stall(sc->sc_xfer[CDCE_BULK_TX]); /* start data transfers */ cdce_start(ue); } static void cdce_stop(struct usb_ether *ue) { struct cdce_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); CDCE_LOCK_ASSERT(sc, MA_OWNED); ifp->if_drv_flags &= ~IFF_DRV_RUNNING; /* * stop all the transfers, if not already stopped: */ usbd_transfer_stop(sc->sc_xfer[CDCE_BULK_RX]); usbd_transfer_stop(sc->sc_xfer[CDCE_BULK_TX]); usbd_transfer_stop(sc->sc_xfer[CDCE_INTR_RX]); usbd_transfer_stop(sc->sc_xfer[CDCE_INTR_TX]); } static void cdce_setmulti(struct usb_ether *ue) { cdce_set_filter(ue); } static void cdce_setpromisc(struct usb_ether *ue) { cdce_set_filter(ue); } static void cdce_set_filter(struct usb_ether *ue) { struct cdce_softc *sc = uether_getsc(ue); struct ifnet *ifp = uether_getifp(ue); struct usb_device_request req; uint16_t value; value = CDC_PACKET_TYPE_DIRECTED | CDC_PACKET_TYPE_BROADCAST; if (if_getflags(ifp) & IFF_PROMISC) value |= CDC_PACKET_TYPE_PROMISC; if (if_getflags(ifp) & IFF_ALLMULTI) value |= CDC_PACKET_TYPE_ALL_MULTICAST; req.bmRequestType = UT_CLASS | UT_INTERFACE; req.bRequest = CDC_SET_ETHERNET_PACKET_FILTER; USETW(req.wValue, value); req.wIndex[0] = sc->sc_ifaces_index[1]; req.wIndex[1] = 0; USETW(req.wLength, 0); /* * Function below will drop the sc mutex. * We can do that since we're called from a separate task, * that simply wraps the setpromisc/setmulti methods. */ usbd_do_request(sc->sc_ue.ue_udev, &sc->sc_mtx, &req, NULL); } static int cdce_suspend(device_t dev) { device_printf(dev, "Suspending\n"); return (0); } static int cdce_resume(device_t dev) { device_printf(dev, "Resuming\n"); return (0); } static void cdce_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct cdce_softc *sc = usbd_xfer_softc(xfer); struct mbuf *m; uint8_t x; int actlen; int aframes; int len; usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTF("received %u bytes in %u frames\n", actlen, aframes); for (x = 0; x != aframes; x++) { m = sc->sc_rx_buf[x]; sc->sc_rx_buf[x] = NULL; len = usbd_xfer_frame_len(xfer, x); /* Strip off CRC added by Zaurus, if any */ if ((sc->sc_flags & CDCE_FLAG_ZAURUS) && len >= 14) len -= 4; if (len < (int)sizeof(struct ether_header)) { m_freem(m); continue; } /* queue up mbuf */ uether_rxmbuf(&sc->sc_ue, m, len); } /* FALLTHROUGH */ case USB_ST_SETUP: /* * TODO: Implement support for multi frame transfers, * when the USB hardware supports it. */ for (x = 0; x != 1; x++) { if (sc->sc_rx_buf[x] == NULL) { m = uether_newbuf(); if (m == NULL) goto tr_stall; sc->sc_rx_buf[x] = m; } else { m = sc->sc_rx_buf[x]; } usbd_xfer_set_frame_data(xfer, x, m->m_data, m->m_len); } /* set number of frames and start hardware */ usbd_xfer_set_frames(xfer, x); usbd_transfer_submit(xfer); /* flush any received frames */ uether_rxflush(&sc->sc_ue); break; default: /* Error */ DPRINTF("error = %s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { tr_stall: if (usbd_get_mode(sc->sc_ue.ue_udev) == USB_MODE_HOST) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); usbd_xfer_set_frames(xfer, 0); usbd_transfer_submit(xfer); } break; } /* need to free the RX-mbufs when we are cancelled */ cdce_free_queue(sc->sc_rx_buf, CDCE_FRAMES_MAX); break; } } static void cdce_intr_read_callback(struct usb_xfer *xfer, usb_error_t error) { u_char buf[CDCE_IND_SIZE_MAX]; struct usb_cdc_notification ucn; struct cdce_softc *sc; struct ifnet *ifp; struct usb_page_cache *pc; int off, actlen; uint32_t downrate, uprate; sc = usbd_xfer_softc(xfer); ifp = uether_getifp(&sc->sc_ue); pc = usbd_xfer_get_frame(xfer, 0); usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: if (USB_DEBUG_VAR) usbd_copy_out(pc, 0, buf, MIN(actlen, sizeof buf)); DPRINTF("Received %d bytes: %*D\n", actlen, (int)MIN(actlen, sizeof buf), buf, ""); off = 0; while (actlen - off >= UCDC_NOTIFICATION_LENGTH) { usbd_copy_out(pc, off, &ucn, UCDC_NOTIFICATION_LENGTH); do { if (ucn.bmRequestType != 0xa1) break; switch (ucn.bNotification) { case UCDC_N_NETWORK_CONNECTION: DPRINTF("changing link state: %d\n", UGETW(ucn.wValue)); if_link_state_change(ifp, UGETW(ucn.wValue) ? LINK_STATE_UP : LINK_STATE_DOWN); break; case UCDC_N_CONNECTION_SPEED_CHANGE: if (UGETW(ucn.wLength) != 8) break; usbd_copy_out(pc, off + UCDC_NOTIFICATION_LENGTH, &ucn.data, UGETW(ucn.wLength)); downrate = UGETDW(ucn.data); uprate = UGETDW(ucn.data); if (downrate != uprate) break; /* set rate */ DPRINTF("changing baudrate: %u\n", downrate); if_setbaudrate(ifp, downrate); break; default: break; } } while (0); off += UCDC_NOTIFICATION_LENGTH + UGETW(ucn.wLength); } /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); usbd_transfer_submit(xfer); break; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* start clear stall */ if (usbd_get_mode(sc->sc_ue.ue_udev) == USB_MODE_HOST) usbd_xfer_set_stall(xfer); goto tr_setup; } break; } } static void cdce_intr_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct cdce_softc *sc = usbd_xfer_softc(xfer); struct usb_cdc_notification req; struct usb_page_cache *pc; uint32_t speed; int actlen; usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTF("Transferred %d bytes\n", actlen); switch (sc->sc_notify_state) { case CDCE_NOTIFY_NETWORK_CONNECTION: sc->sc_notify_state = CDCE_NOTIFY_SPEED_CHANGE; break; case CDCE_NOTIFY_SPEED_CHANGE: sc->sc_notify_state = CDCE_NOTIFY_DONE; break; default: break; } /* FALLTHROUGH */ case USB_ST_SETUP: tr_setup: /* * Inform host about connection. Required according to USB CDC * specification and communicating to Mac OS X USB host stack. * Some of the values seems ignored by Mac OS X though. */ if (sc->sc_notify_state == CDCE_NOTIFY_NETWORK_CONNECTION) { req.bmRequestType = UCDC_NOTIFICATION; req.bNotification = UCDC_N_NETWORK_CONNECTION; req.wIndex[0] = sc->sc_ifaces_index[1]; req.wIndex[1] = 0; USETW(req.wValue, 1); /* Connected */ USETW(req.wLength, 0); pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, &req, sizeof(req)); usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); usbd_xfer_set_frames(xfer, 1); usbd_transfer_submit(xfer); } else if (sc->sc_notify_state == CDCE_NOTIFY_SPEED_CHANGE) { req.bmRequestType = UCDC_NOTIFICATION; req.bNotification = UCDC_N_CONNECTION_SPEED_CHANGE; req.wIndex[0] = sc->sc_ifaces_index[1]; req.wIndex[1] = 0; USETW(req.wValue, 0); USETW(req.wLength, 8); /* Peak theoretical bulk trasfer rate in bits/s */ if (usbd_get_speed(sc->sc_ue.ue_udev) != USB_SPEED_FULL) speed = (13 * 512 * 8 * 1000 * 8); else speed = (19 * 64 * 1 * 1000 * 8); USETDW(req.data + 0, speed); /* Upstream bit rate */ USETDW(req.data + 4, speed); /* Downstream bit rate */ pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, &req, sizeof(req)); usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); usbd_xfer_set_frames(xfer, 1); usbd_transfer_submit(xfer); } break; default: /* Error */ if (error != USB_ERR_CANCELLED) { if (usbd_get_mode(sc->sc_ue.ue_udev) == USB_MODE_HOST) { /* start clear stall */ usbd_xfer_set_stall(xfer); } goto tr_setup; } break; } } static int cdce_handle_request(device_t dev, const void *preq, void **pptr, uint16_t *plen, uint16_t offset, uint8_t *pstate) { struct cdce_softc *sc = device_get_softc(dev); const struct usb_device_request *req = preq; uint8_t is_complete = *pstate; /* * When Mac OS X resumes after suspending it expects * to be notified again after this request. */ if (req->bmRequestType == UT_WRITE_CLASS_INTERFACE && \ req->bRequest == UCDC_NCM_SET_ETHERNET_PACKET_FILTER) { if (is_complete == 1) { mtx_lock(&sc->sc_mtx); sc->sc_notify_state = CDCE_NOTIFY_SPEED_CHANGE; usbd_transfer_start(sc->sc_xfer[CDCE_INTR_TX]); mtx_unlock(&sc->sc_mtx); } return (0); } return (ENXIO); /* use builtin handler */ } #if CDCE_HAVE_NCM static void cdce_ncm_tx_zero(struct usb_page_cache *pc, uint32_t start, uint32_t end) { if (start >= CDCE_NCM_TX_MAXLEN) return; if (end > CDCE_NCM_TX_MAXLEN) end = CDCE_NCM_TX_MAXLEN; usbd_frame_zero(pc, start, end - start); } static uint8_t cdce_ncm_fill_tx_frames(struct usb_xfer *xfer, uint8_t index) { struct cdce_softc *sc = usbd_xfer_softc(xfer); struct ifnet *ifp = uether_getifp(&sc->sc_ue); struct usb_page_cache *pc = usbd_xfer_get_frame(xfer, index); struct mbuf *m; uint32_t rem; uint32_t offset; uint32_t last_offset; uint16_t n; uint8_t retval; usbd_xfer_set_frame_offset(xfer, index * CDCE_NCM_TX_MAXLEN, index); offset = sizeof(sc->sc_ncm.hdr) + sizeof(sc->sc_ncm.dpt) + sizeof(sc->sc_ncm.dp); /* Store last valid offset before alignment */ last_offset = offset; /* Align offset */ offset = CDCE_NCM_ALIGN(sc->sc_ncm.tx_remainder, offset, sc->sc_ncm.tx_modulus); /* Zero pad */ cdce_ncm_tx_zero(pc, last_offset, offset); /* buffer full */ retval = 2; for (n = 0; n != sc->sc_ncm.tx_nframe; n++) { /* check if end of transmit buffer is reached */ if (offset >= sc->sc_ncm.tx_max) break; /* compute maximum buffer size */ rem = sc->sc_ncm.tx_max - offset; IFQ_DRV_DEQUEUE(&(ifp->if_snd), m); if (m == NULL) { /* buffer not full */ retval = 1; break; } if (m->m_pkthdr.len > (int)rem) { if (n == 0) { /* The frame won't fit in our buffer */ DPRINTFN(1, "Frame too big to be transmitted!\n"); m_freem(m); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); n--; continue; } /* Wait till next buffer becomes ready */ IFQ_DRV_PREPEND(&(ifp->if_snd), m); break; } usbd_m_copy_in(pc, offset, m, 0, m->m_pkthdr.len); USETW(sc->sc_ncm.dp[n].wFrameLength, m->m_pkthdr.len); USETW(sc->sc_ncm.dp[n].wFrameIndex, offset); /* Update offset */ offset += m->m_pkthdr.len; /* Store last valid offset before alignment */ last_offset = offset; /* Align offset */ offset = CDCE_NCM_ALIGN(sc->sc_ncm.tx_remainder, offset, sc->sc_ncm.tx_modulus); /* Zero pad */ cdce_ncm_tx_zero(pc, last_offset, offset); /* * If there's a BPF listener, bounce a copy * of this frame to him: */ BPF_MTAP(ifp, m); /* Free mbuf */ m_freem(m); /* Pre-increment interface counter */ if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1); } if (n == 0) return (0); rem = (sizeof(sc->sc_ncm.dpt) + (4 * n) + 4); USETW(sc->sc_ncm.dpt.wLength, rem); /* zero the rest of the data pointer entries */ for (; n != CDCE_NCM_SUBFRAMES_MAX; n++) { USETW(sc->sc_ncm.dp[n].wFrameLength, 0); USETW(sc->sc_ncm.dp[n].wFrameIndex, 0); } offset = last_offset; /* Align offset */ offset = CDCE_NCM_ALIGN(0, offset, CDCE_NCM_TX_MINLEN); /* Optimise, save bandwidth and force short termination */ if (offset >= sc->sc_ncm.tx_max) offset = sc->sc_ncm.tx_max; else offset ++; /* Zero pad */ cdce_ncm_tx_zero(pc, last_offset, offset); /* set frame length */ usbd_xfer_set_frame_len(xfer, index, offset); /* Fill out 16-bit header */ sc->sc_ncm.hdr.dwSignature[0] = 'N'; sc->sc_ncm.hdr.dwSignature[1] = 'C'; sc->sc_ncm.hdr.dwSignature[2] = 'M'; sc->sc_ncm.hdr.dwSignature[3] = 'H'; USETW(sc->sc_ncm.hdr.wHeaderLength, sizeof(sc->sc_ncm.hdr)); USETW(sc->sc_ncm.hdr.wBlockLength, offset); USETW(sc->sc_ncm.hdr.wSequence, sc->sc_ncm.tx_seq); USETW(sc->sc_ncm.hdr.wDptIndex, sizeof(sc->sc_ncm.hdr)); sc->sc_ncm.tx_seq++; /* Fill out 16-bit frame table header */ sc->sc_ncm.dpt.dwSignature[0] = 'N'; sc->sc_ncm.dpt.dwSignature[1] = 'C'; sc->sc_ncm.dpt.dwSignature[2] = 'M'; sc->sc_ncm.dpt.dwSignature[3] = '0'; USETW(sc->sc_ncm.dpt.wNextNdpIndex, 0); /* reserved */ usbd_copy_in(pc, 0, &(sc->sc_ncm.hdr), sizeof(sc->sc_ncm.hdr)); usbd_copy_in(pc, sizeof(sc->sc_ncm.hdr), &(sc->sc_ncm.dpt), sizeof(sc->sc_ncm.dpt)); usbd_copy_in(pc, sizeof(sc->sc_ncm.hdr) + sizeof(sc->sc_ncm.dpt), &(sc->sc_ncm.dp), sizeof(sc->sc_ncm.dp)); return (retval); } static void cdce_ncm_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct cdce_softc *sc = usbd_xfer_softc(xfer); struct ifnet *ifp = uether_getifp(&sc->sc_ue); uint16_t x; uint8_t temp; int actlen; int aframes; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL); DPRINTFN(10, "transfer complete: " "%u bytes in %u frames\n", actlen, aframes); case USB_ST_SETUP: for (x = 0; x != CDCE_NCM_TX_FRAMES_MAX; x++) { temp = cdce_ncm_fill_tx_frames(xfer, x); if (temp == 0) break; if (temp == 1) { x++; break; } } if (x != 0) { #ifdef USB_DEBUG usbd_xfer_set_interval(xfer, cdce_tx_interval); #endif usbd_xfer_set_frames(xfer, x); usbd_transfer_submit(xfer); } break; default: /* Error */ DPRINTFN(10, "Transfer error: %s\n", usbd_errstr(error)); /* update error counter */ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); if (error != USB_ERR_CANCELLED) { if (usbd_get_mode(sc->sc_ue.ue_udev) == USB_MODE_HOST) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); usbd_xfer_set_frames(xfer, 0); usbd_transfer_submit(xfer); } } break; } } static void cdce_ncm_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct cdce_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc = usbd_xfer_get_frame(xfer, 0); struct ifnet *ifp = uether_getifp(&sc->sc_ue); struct mbuf *m; - int sumdata; + int sumdata __usbdebug_used; int sumlen; int actlen; int aframes; int temp; int nframes; int x; int offset; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: usbd_xfer_status(xfer, &actlen, &sumlen, &aframes, NULL); DPRINTFN(1, "received %u bytes in %u frames\n", actlen, aframes); if (actlen < (int)(sizeof(sc->sc_ncm.hdr) + sizeof(sc->sc_ncm.dpt))) { DPRINTFN(1, "frame too short\n"); goto tr_setup; } usbd_copy_out(pc, 0, &(sc->sc_ncm.hdr), sizeof(sc->sc_ncm.hdr)); if ((sc->sc_ncm.hdr.dwSignature[0] != 'N') || (sc->sc_ncm.hdr.dwSignature[1] != 'C') || (sc->sc_ncm.hdr.dwSignature[2] != 'M') || (sc->sc_ncm.hdr.dwSignature[3] != 'H')) { DPRINTFN(1, "invalid HDR signature: " "0x%02x:0x%02x:0x%02x:0x%02x\n", sc->sc_ncm.hdr.dwSignature[0], sc->sc_ncm.hdr.dwSignature[1], sc->sc_ncm.hdr.dwSignature[2], sc->sc_ncm.hdr.dwSignature[3]); goto tr_stall; } temp = UGETW(sc->sc_ncm.hdr.wBlockLength); if (temp > sumlen) { DPRINTFN(1, "unsupported block length %u/%u\n", temp, sumlen); goto tr_stall; } temp = UGETW(sc->sc_ncm.hdr.wDptIndex); if ((int)(temp + sizeof(sc->sc_ncm.dpt)) > actlen) { DPRINTFN(1, "invalid DPT index: 0x%04x\n", temp); goto tr_stall; } usbd_copy_out(pc, temp, &(sc->sc_ncm.dpt), sizeof(sc->sc_ncm.dpt)); if ((sc->sc_ncm.dpt.dwSignature[0] != 'N') || (sc->sc_ncm.dpt.dwSignature[1] != 'C') || (sc->sc_ncm.dpt.dwSignature[2] != 'M') || (sc->sc_ncm.dpt.dwSignature[3] != '0')) { DPRINTFN(1, "invalid DPT signature" "0x%02x:0x%02x:0x%02x:0x%02x\n", sc->sc_ncm.dpt.dwSignature[0], sc->sc_ncm.dpt.dwSignature[1], sc->sc_ncm.dpt.dwSignature[2], sc->sc_ncm.dpt.dwSignature[3]); goto tr_stall; } nframes = UGETW(sc->sc_ncm.dpt.wLength) / 4; /* Subtract size of header and last zero padded entry */ if (nframes >= (2 + 1)) nframes -= (2 + 1); else nframes = 0; DPRINTFN(1, "nframes = %u\n", nframes); temp += sizeof(sc->sc_ncm.dpt); if ((temp + (4 * nframes)) > actlen) goto tr_stall; if (nframes > CDCE_NCM_SUBFRAMES_MAX) { DPRINTFN(1, "Truncating number of frames from %u to %u\n", nframes, CDCE_NCM_SUBFRAMES_MAX); nframes = CDCE_NCM_SUBFRAMES_MAX; } usbd_copy_out(pc, temp, &(sc->sc_ncm.dp), (4 * nframes)); sumdata = 0; for (x = 0; x != nframes; x++) { offset = UGETW(sc->sc_ncm.dp[x].wFrameIndex); temp = UGETW(sc->sc_ncm.dp[x].wFrameLength); if ((offset == 0) || (temp < (int)sizeof(struct ether_header)) || (temp > (MCLBYTES - ETHER_ALIGN))) { DPRINTFN(1, "NULL frame detected at %d\n", x); m = NULL; /* silently ignore this frame */ continue; } else if ((offset + temp) > actlen) { DPRINTFN(1, "invalid frame " "detected at %d\n", x); m = NULL; /* silently ignore this frame */ continue; } else if (temp > (int)(MHLEN - ETHER_ALIGN)) { m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); } else { m = m_gethdr(M_NOWAIT, MT_DATA); } DPRINTFN(16, "frame %u, offset = %u, length = %u \n", x, offset, temp); /* check if we have a buffer */ if (m) { m->m_len = m->m_pkthdr.len = temp + ETHER_ALIGN; m_adj(m, ETHER_ALIGN); usbd_copy_out(pc, offset, m->m_data, temp); /* enqueue */ uether_rxmbuf(&sc->sc_ue, m, temp); sumdata += temp; } else { if_inc_counter(ifp, IFCOUNTER_IERRORS, 1); } } DPRINTFN(1, "Efficiency: %u/%u bytes\n", sumdata, actlen); case USB_ST_SETUP: tr_setup: usbd_xfer_set_frame_len(xfer, 0, sc->sc_ncm.rx_max); usbd_xfer_set_frames(xfer, 1); usbd_transfer_submit(xfer); uether_rxflush(&sc->sc_ue); /* must be last */ break; default: /* Error */ DPRINTFN(1, "error = %s\n", usbd_errstr(error)); if (error != USB_ERR_CANCELLED) { tr_stall: if (usbd_get_mode(sc->sc_ue.ue_udev) == USB_MODE_HOST) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); usbd_xfer_set_frames(xfer, 0); usbd_transfer_submit(xfer); } } break; } } #endif diff --git a/sys/dev/usb/usb_debug.h b/sys/dev/usb/usb_debug.h index 87c0dcfe3d5f..9bcbaec70ef4 100644 --- a/sys/dev/usb/usb_debug.h +++ b/sys/dev/usb/usb_debug.h @@ -1,88 +1,90 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* This file contains various factored out debug macros. */ #ifndef _USB_DEBUG_H_ #define _USB_DEBUG_H_ /* Declare global USB debug variable. */ extern int usb_debug; /* Check if USB debugging is enabled. */ #ifdef USB_DEBUG_VAR #ifdef USB_DEBUG #define DPRINTFN(n,fmt,...) do { \ if ((USB_DEBUG_VAR) >= (n)) { \ printf("%s: " fmt, \ __FUNCTION__ ,##__VA_ARGS__); \ } \ } while (0) #define DPRINTF(...) DPRINTFN(1, __VA_ARGS__) +#define __usbdebug_used #else #define DPRINTF(...) do { } while (0) #define DPRINTFN(...) do { } while (0) +#define __usbdebug_used __unused #endif #endif struct usb_interface; struct usb_device; struct usb_endpoint; struct usb_xfer; void usb_dump_iface(struct usb_interface *iface); void usb_dump_device(struct usb_device *udev); void usb_dump_queue(struct usb_endpoint *ep); void usb_dump_endpoint(struct usb_endpoint *ep); void usb_dump_xfer(struct usb_xfer *xfer); #ifdef USB_DEBUG extern unsigned int usb_port_reset_delay; extern unsigned int usb_port_root_reset_delay; extern unsigned int usb_port_reset_recovery; extern unsigned int usb_port_powerup_delay; extern unsigned int usb_port_resume_delay; extern unsigned int usb_set_address_settle; extern unsigned int usb_resume_delay; extern unsigned int usb_resume_wait; extern unsigned int usb_resume_recovery; extern unsigned int usb_extra_power_up_time; #else #define usb_port_reset_delay USB_PORT_RESET_DELAY #define usb_port_root_reset_delay USB_PORT_ROOT_RESET_DELAY #define usb_port_reset_recovery USB_PORT_RESET_RECOVERY #define usb_port_powerup_delay USB_PORT_POWERUP_DELAY #define usb_port_resume_delay USB_PORT_RESUME_DELAY #define usb_set_address_settle USB_SET_ADDRESS_SETTLE #define usb_resume_delay USB_RESUME_DELAY #define usb_resume_wait USB_RESUME_WAIT #define usb_resume_recovery USB_RESUME_RECOVERY #define usb_extra_power_up_time USB_EXTRA_POWER_UP_TIME #endif #endif /* _USB_DEBUG_H_ */ diff --git a/sys/dev/usb/usb_msctest.c b/sys/dev/usb/usb_msctest.c index 5dcf8d151119..bf60e5852598 100644 --- a/sys/dev/usb/usb_msctest.c +++ b/sys/dev/usb/usb_msctest.c @@ -1,1140 +1,1140 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2008-2022 Hans Petter Selasky. * Copyright (c) 2021-2022 Idwer Vollering. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * The following file contains code that will detect USB autoinstall * disks. */ #ifdef USB_GLOBAL_INCLUDE_FILE #include USB_GLOBAL_INCLUDE_FILE #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USB_DEBUG_VAR usb_debug #include #include #include #include #include #include #include #include #include #endif /* USB_GLOBAL_INCLUDE_FILE */ enum { ST_COMMAND, ST_DATA_RD, ST_DATA_RD_CS, ST_DATA_WR, ST_DATA_WR_CS, ST_STATUS, ST_MAX, }; enum { DIR_IN, DIR_OUT, DIR_NONE, }; #define SCSI_MAX_LEN MAX(SCSI_FIXED_BLOCK_SIZE, USB_MSCTEST_BULK_SIZE) #define SCSI_INQ_LEN 0x24 #define SCSI_SENSE_LEN 0xFF #define SCSI_FIXED_BLOCK_SIZE 512 /* bytes */ static uint8_t scsi_test_unit_ready[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; static uint8_t scsi_inquiry[] = { 0x12, 0x00, 0x00, 0x00, SCSI_INQ_LEN, 0x00 }; static uint8_t scsi_rezero_init[] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 }; static uint8_t scsi_start_unit[] = { 0x1b, 0x00, 0x00, 0x00, 0x01, 0x00 }; static uint8_t scsi_stop_unit[] = { 0x1b, 0x00, 0x00, 0x00, 0x02, 0x00 }; static uint8_t scsi_ztestor_eject[] = { 0x85, 0x01, 0x01, 0x01, 0x18, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00 }; static uint8_t scsi_cmotech_eject[] = { 0xff, 0x52, 0x44, 0x45, 0x56, 0x43, 0x48, 0x47 }; static uint8_t scsi_huawei_eject[] = { 0x11, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; static uint8_t scsi_huawei_eject2[] = { 0x11, 0x06, 0x20, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; static uint8_t scsi_tct_eject[] = { 0x06, 0xf5, 0x04, 0x02, 0x52, 0x70 }; static uint8_t scsi_sync_cache[] = { 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; static uint8_t scsi_request_sense[] = { 0x03, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; static uint8_t scsi_read_capacity[] = { 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; static uint8_t scsi_prevent_removal[] = { 0x1e, 0, 0, 0, 1, 0 }; static uint8_t scsi_allow_removal[] = { 0x1e, 0, 0, 0, 0, 0 }; #ifndef USB_MSCTEST_BULK_SIZE #define USB_MSCTEST_BULK_SIZE 64 /* dummy */ #endif #define ERR_CSW_FAILED -1 /* Command Block Wrapper */ struct bbb_cbw { uDWord dCBWSignature; #define CBWSIGNATURE 0x43425355 uDWord dCBWTag; uDWord dCBWDataTransferLength; uByte bCBWFlags; #define CBWFLAGS_OUT 0x00 #define CBWFLAGS_IN 0x80 uByte bCBWLUN; uByte bCDBLength; #define CBWCDBLENGTH 16 uByte CBWCDB[CBWCDBLENGTH]; } __packed; /* Command Status Wrapper */ struct bbb_csw { uDWord dCSWSignature; #define CSWSIGNATURE 0x53425355 uDWord dCSWTag; uDWord dCSWDataResidue; uByte bCSWStatus; #define CSWSTATUS_GOOD 0x0 #define CSWSTATUS_FAILED 0x1 #define CSWSTATUS_PHASE 0x2 } __packed; struct bbb_transfer { struct mtx mtx; struct cv cv; struct bbb_cbw *cbw; struct bbb_csw *csw; struct usb_xfer *xfer[ST_MAX]; uint8_t *data_ptr; usb_size_t data_len; /* bytes */ usb_size_t data_rem; /* bytes */ usb_timeout_t data_timeout; /* ms */ usb_frlength_t actlen; /* bytes */ usb_frlength_t buffer_size; /* bytes */ uint8_t cmd_len; /* bytes */ uint8_t dir; uint8_t lun; uint8_t state; uint8_t status_try; int error; uint8_t *buffer; }; static usb_callback_t bbb_command_callback; static usb_callback_t bbb_data_read_callback; static usb_callback_t bbb_data_rd_cs_callback; static usb_callback_t bbb_data_write_callback; static usb_callback_t bbb_data_wr_cs_callback; static usb_callback_t bbb_status_callback; static usb_callback_t bbb_raw_write_callback; static void bbb_done(struct bbb_transfer *, int); static void bbb_transfer_start(struct bbb_transfer *, uint8_t); static void bbb_data_clear_stall_callback(struct usb_xfer *, uint8_t, uint8_t); static int bbb_command_start(struct bbb_transfer *, uint8_t, uint8_t, void *, size_t, void *, size_t, usb_timeout_t); static struct bbb_transfer *bbb_attach(struct usb_device *, uint8_t, uint8_t); static void bbb_detach(struct bbb_transfer *); static const struct usb_config bbb_config[ST_MAX] = { [ST_COMMAND] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = sizeof(struct bbb_cbw), .callback = &bbb_command_callback, .timeout = 4 * USB_MS_HZ, /* 4 seconds */ }, [ST_DATA_RD] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = SCSI_MAX_LEN, .flags = {.proxy_buffer = 1,.short_xfer_ok = 1,}, .callback = &bbb_data_read_callback, .timeout = 4 * USB_MS_HZ, /* 4 seconds */ }, [ST_DATA_RD_CS] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request), .callback = &bbb_data_rd_cs_callback, .timeout = 1 * USB_MS_HZ, /* 1 second */ }, [ST_DATA_WR] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = SCSI_MAX_LEN, .flags = {.ext_buffer = 1,.proxy_buffer = 1,}, .callback = &bbb_data_write_callback, .timeout = 4 * USB_MS_HZ, /* 4 seconds */ }, [ST_DATA_WR_CS] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .bufsize = sizeof(struct usb_device_request), .callback = &bbb_data_wr_cs_callback, .timeout = 1 * USB_MS_HZ, /* 1 second */ }, [ST_STATUS] = { .type = UE_BULK, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .bufsize = sizeof(struct bbb_csw), .flags = {.short_xfer_ok = 1,}, .callback = &bbb_status_callback, .timeout = 1 * USB_MS_HZ, /* 1 second */ }, }; static const struct usb_config bbb_raw_config[1] = { [0] = { .type = UE_BULK_INTR, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .bufsize = SCSI_MAX_LEN, .flags = {.ext_buffer = 1,.proxy_buffer = 1,}, .callback = &bbb_raw_write_callback, .timeout = 1 * USB_MS_HZ, /* 1 second */ }, }; static void bbb_done(struct bbb_transfer *sc, int error) { sc->error = error; sc->state = ST_COMMAND; sc->status_try = 1; cv_signal(&sc->cv); } static void bbb_transfer_start(struct bbb_transfer *sc, uint8_t xfer_index) { sc->state = xfer_index; usbd_transfer_start(sc->xfer[xfer_index]); } static void bbb_data_clear_stall_callback(struct usb_xfer *xfer, uint8_t next_xfer, uint8_t stall_xfer) { struct bbb_transfer *sc = usbd_xfer_softc(xfer); if (usbd_clear_stall_callback(xfer, sc->xfer[stall_xfer])) { switch (USB_GET_STATE(xfer)) { case USB_ST_SETUP: case USB_ST_TRANSFERRED: bbb_transfer_start(sc, next_xfer); break; default: bbb_done(sc, USB_ERR_STALLED); break; } } } static void bbb_command_callback(struct usb_xfer *xfer, usb_error_t error) { struct bbb_transfer *sc = usbd_xfer_softc(xfer); uint32_t tag; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: bbb_transfer_start (sc, ((sc->dir == DIR_IN) ? ST_DATA_RD : (sc->dir == DIR_OUT) ? ST_DATA_WR : ST_STATUS)); break; case USB_ST_SETUP: sc->status_try = 0; tag = UGETDW(sc->cbw->dCBWTag) + 1; USETDW(sc->cbw->dCBWSignature, CBWSIGNATURE); USETDW(sc->cbw->dCBWTag, tag); USETDW(sc->cbw->dCBWDataTransferLength, (uint32_t)sc->data_len); sc->cbw->bCBWFlags = ((sc->dir == DIR_IN) ? CBWFLAGS_IN : CBWFLAGS_OUT); sc->cbw->bCBWLUN = sc->lun; sc->cbw->bCDBLength = sc->cmd_len; if (sc->cbw->bCDBLength > sizeof(sc->cbw->CBWCDB)) { sc->cbw->bCDBLength = sizeof(sc->cbw->CBWCDB); DPRINTFN(0, "Truncating long command\n"); } usbd_xfer_set_frame_len(xfer, 0, sizeof(struct bbb_cbw)); usbd_transfer_submit(xfer); break; default: /* Error */ bbb_done(sc, error); break; } } static void bbb_data_read_callback(struct usb_xfer *xfer, usb_error_t error) { struct bbb_transfer *sc = usbd_xfer_softc(xfer); usb_frlength_t max_bulk = usbd_xfer_max_len(xfer); int actlen, sumlen; usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: sc->data_rem -= actlen; sc->data_ptr += actlen; sc->actlen += actlen; if (actlen < sumlen) { /* short transfer */ sc->data_rem = 0; } case USB_ST_SETUP: DPRINTF("max_bulk=%d, data_rem=%d\n", max_bulk, sc->data_rem); if (sc->data_rem == 0) { bbb_transfer_start(sc, ST_STATUS); break; } if (max_bulk > sc->data_rem) { max_bulk = sc->data_rem; } usbd_xfer_set_timeout(xfer, sc->data_timeout); usbd_xfer_set_frame_data(xfer, 0, sc->data_ptr, max_bulk); usbd_transfer_submit(xfer); break; default: /* Error */ if (error == USB_ERR_CANCELLED) { bbb_done(sc, error); } else { bbb_transfer_start(sc, ST_DATA_RD_CS); } break; } } static void bbb_data_rd_cs_callback(struct usb_xfer *xfer, usb_error_t error) { bbb_data_clear_stall_callback(xfer, ST_STATUS, ST_DATA_RD); } static void bbb_data_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct bbb_transfer *sc = usbd_xfer_softc(xfer); usb_frlength_t max_bulk = usbd_xfer_max_len(xfer); int actlen, sumlen; usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: sc->data_rem -= actlen; sc->data_ptr += actlen; sc->actlen += actlen; if (actlen < sumlen) { /* short transfer */ sc->data_rem = 0; } case USB_ST_SETUP: DPRINTF("max_bulk=%d, data_rem=%d\n", max_bulk, sc->data_rem); if (sc->data_rem == 0) { bbb_transfer_start(sc, ST_STATUS); break; } if (max_bulk > sc->data_rem) { max_bulk = sc->data_rem; } usbd_xfer_set_timeout(xfer, sc->data_timeout); usbd_xfer_set_frame_data(xfer, 0, sc->data_ptr, max_bulk); usbd_transfer_submit(xfer); break; default: /* Error */ if (error == USB_ERR_CANCELLED) { bbb_done(sc, error); } else { bbb_transfer_start(sc, ST_DATA_WR_CS); } break; } } static void bbb_data_wr_cs_callback(struct usb_xfer *xfer, usb_error_t error) { bbb_data_clear_stall_callback(xfer, ST_STATUS, ST_DATA_WR); } static void bbb_status_callback(struct usb_xfer *xfer, usb_error_t error) { struct bbb_transfer *sc = usbd_xfer_softc(xfer); int actlen; int sumlen; usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: /* very simple status check */ if (actlen < (int)sizeof(struct bbb_csw)) { bbb_done(sc, USB_ERR_SHORT_XFER); } else if (sc->csw->bCSWStatus == CSWSTATUS_GOOD) { bbb_done(sc, 0); /* success */ } else { bbb_done(sc, ERR_CSW_FAILED); /* error */ } break; case USB_ST_SETUP: usbd_xfer_set_frame_len(xfer, 0, sizeof(struct bbb_csw)); usbd_transfer_submit(xfer); break; default: DPRINTF("Failed to read CSW: %s, try %d\n", usbd_errstr(error), sc->status_try); if (error == USB_ERR_CANCELLED || sc->status_try) { bbb_done(sc, error); } else { sc->status_try = 1; bbb_transfer_start(sc, ST_DATA_RD_CS); } break; } } static void bbb_raw_write_callback(struct usb_xfer *xfer, usb_error_t error) { struct bbb_transfer *sc = usbd_xfer_softc(xfer); usb_frlength_t max_bulk = usbd_xfer_max_len(xfer); int actlen, sumlen; usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: sc->data_rem -= actlen; sc->data_ptr += actlen; sc->actlen += actlen; if (actlen < sumlen) { /* short transfer */ sc->data_rem = 0; } case USB_ST_SETUP: DPRINTF("max_bulk=%d, data_rem=%d\n", max_bulk, sc->data_rem); if (sc->data_rem == 0) { bbb_done(sc, 0); break; } if (max_bulk > sc->data_rem) { max_bulk = sc->data_rem; } usbd_xfer_set_timeout(xfer, sc->data_timeout); usbd_xfer_set_frame_data(xfer, 0, sc->data_ptr, max_bulk); usbd_transfer_submit(xfer); break; default: /* Error */ bbb_done(sc, error); break; } } /*------------------------------------------------------------------------* * bbb_command_start - execute a SCSI command synchronously * * Return values * 0: Success * Else: Failure *------------------------------------------------------------------------*/ static int bbb_command_start(struct bbb_transfer *sc, uint8_t dir, uint8_t lun, void *data_ptr, size_t data_len, void *cmd_ptr, size_t cmd_len, usb_timeout_t data_timeout) { sc->lun = lun; sc->dir = data_len ? dir : DIR_NONE; sc->data_ptr = data_ptr; sc->data_len = data_len; sc->data_rem = data_len; sc->data_timeout = (data_timeout + USB_MS_HZ); sc->actlen = 0; sc->error = 0; sc->cmd_len = cmd_len; memset(&sc->cbw->CBWCDB, 0, sizeof(sc->cbw->CBWCDB)); memcpy(&sc->cbw->CBWCDB, cmd_ptr, cmd_len); DPRINTFN(1, "SCSI cmd = %*D\n", (int)cmd_len, (char *)sc->cbw->CBWCDB, ":"); USB_MTX_LOCK(&sc->mtx); usbd_transfer_start(sc->xfer[sc->state]); while (usbd_transfer_pending(sc->xfer[sc->state])) { cv_wait(&sc->cv, &sc->mtx); } USB_MTX_UNLOCK(&sc->mtx); return (sc->error); } /*------------------------------------------------------------------------* * bbb_raw_write - write a raw BULK message synchronously * * Return values * 0: Success * Else: Failure *------------------------------------------------------------------------*/ static int bbb_raw_write(struct bbb_transfer *sc, const void *data_ptr, size_t data_len, usb_timeout_t data_timeout) { sc->data_ptr = __DECONST(void *, data_ptr); sc->data_len = data_len; sc->data_rem = data_len; sc->data_timeout = (data_timeout + USB_MS_HZ); sc->actlen = 0; sc->error = 0; DPRINTFN(1, "BULK DATA = %*D\n", (int)data_len, (const char *)data_ptr, ":"); USB_MTX_LOCK(&sc->mtx); usbd_transfer_start(sc->xfer[0]); while (usbd_transfer_pending(sc->xfer[0])) cv_wait(&sc->cv, &sc->mtx); USB_MTX_UNLOCK(&sc->mtx); return (sc->error); } static struct bbb_transfer * bbb_attach(struct usb_device *udev, uint8_t iface_index, uint8_t bInterfaceClass) { struct usb_interface *iface; struct usb_interface_descriptor *id; const struct usb_config *pconfig; struct bbb_transfer *sc; usb_error_t err; int nconfig; #if USB_HAVE_MSCTEST_DETACH uint8_t do_unlock; /* Prevent re-enumeration */ do_unlock = usbd_enum_lock(udev); /* * Make sure any driver which is hooked up to this interface, * like umass is gone: */ usb_detach_device(udev, iface_index, 0); if (do_unlock) usbd_enum_unlock(udev); #endif iface = usbd_get_iface(udev, iface_index); if (iface == NULL) return (NULL); id = iface->idesc; if (id == NULL || id->bInterfaceClass != bInterfaceClass) return (NULL); switch (id->bInterfaceClass) { case UICLASS_MASS: switch (id->bInterfaceSubClass) { case UISUBCLASS_SCSI: case UISUBCLASS_UFI: case UISUBCLASS_SFF8020I: case UISUBCLASS_SFF8070I: break; default: return (NULL); } switch (id->bInterfaceProtocol) { case UIPROTO_MASS_BBB_OLD: case UIPROTO_MASS_BBB: break; default: return (NULL); } pconfig = bbb_config; nconfig = ST_MAX; break; case UICLASS_HID: switch (id->bInterfaceSubClass) { case 0: break; default: return (NULL); } pconfig = bbb_raw_config; nconfig = 1; break; default: return (NULL); } sc = malloc(sizeof(*sc), M_USB, M_WAITOK | M_ZERO); mtx_init(&sc->mtx, "USB autoinstall", NULL, MTX_DEF); cv_init(&sc->cv, "WBBB"); err = usbd_transfer_setup(udev, &iface_index, sc->xfer, pconfig, nconfig, sc, &sc->mtx); if (err) { bbb_detach(sc); return (NULL); } switch (id->bInterfaceClass) { case UICLASS_MASS: /* store pointer to DMA buffers */ sc->buffer = usbd_xfer_get_frame_buffer( sc->xfer[ST_DATA_RD], 0); sc->buffer_size = usbd_xfer_max_len(sc->xfer[ST_DATA_RD]); sc->cbw = usbd_xfer_get_frame_buffer( sc->xfer[ST_COMMAND], 0); sc->csw = usbd_xfer_get_frame_buffer( sc->xfer[ST_STATUS], 0); break; default: break; } return (sc); } static void bbb_detach(struct bbb_transfer *sc) { usbd_transfer_unsetup(sc->xfer, ST_MAX); mtx_destroy(&sc->mtx); cv_destroy(&sc->cv); free(sc, M_USB); } /*------------------------------------------------------------------------* * usb_iface_is_cdrom * * Return values: * 1: This interface is an auto install disk (CD-ROM) * 0: Not an auto install disk. *------------------------------------------------------------------------*/ int usb_iface_is_cdrom(struct usb_device *udev, uint8_t iface_index) { struct bbb_transfer *sc; uint8_t timeout; uint8_t is_cdrom; uint8_t sid_type; int err; sc = bbb_attach(udev, iface_index, UICLASS_MASS); if (sc == NULL) return (0); is_cdrom = 0; timeout = 4; /* tries */ while (--timeout) { err = bbb_command_start(sc, DIR_IN, 0, sc->buffer, SCSI_INQ_LEN, &scsi_inquiry, sizeof(scsi_inquiry), USB_MS_HZ); if (err == 0 && sc->actlen > 0) { sid_type = sc->buffer[0] & 0x1F; if (sid_type == 0x05) is_cdrom = 1; break; } else if (err != ERR_CSW_FAILED) break; /* non retryable error */ usb_pause_mtx(NULL, hz); } bbb_detach(sc); return (is_cdrom); } static uint8_t usb_msc_get_max_lun(struct usb_device *udev, uint8_t iface_index) { struct usb_device_request req; usb_error_t err; uint8_t buf = 0; /* The Get Max Lun command is a class-specific request. */ req.bmRequestType = UT_READ_CLASS_INTERFACE; req.bRequest = 0xFE; /* GET_MAX_LUN */ USETW(req.wValue, 0); req.wIndex[0] = iface_index; req.wIndex[1] = 0; USETW(req.wLength, 1); err = usbd_do_request(udev, NULL, &req, &buf); if (err) buf = 0; return (buf); } #define USB_ADD_QUIRK(udev, any, which) do { \ if (usb_get_manufacturer(udev) != NULL && usb_get_product(udev) != NULL) { \ DPRINTFN(0, #which " set for USB mass storage device %s %s (0x%04x:0x%04x)\n", \ usb_get_manufacturer(udev), \ usb_get_product(udev), \ UGETW(udev->ddesc.idVendor), \ UGETW(udev->ddesc.idProduct)); \ } else { \ DPRINTFN(0, #which " set for USB mass storage device, 0x%04x:0x%04x\n", \ UGETW(udev->ddesc.idVendor), \ UGETW(udev->ddesc.idProduct)); \ } \ usbd_add_dynamic_quirk(udev, which); \ any = 1; \ } while (0) usb_error_t usb_msc_auto_quirk(struct usb_device *udev, uint8_t iface_index, const struct usb_attach_arg *uaa) { struct bbb_transfer *sc; uint8_t timeout; uint8_t is_no_direct; uint8_t sid_type; uint8_t any_quirk; int err; sc = bbb_attach(udev, iface_index, UICLASS_MASS); if (sc == NULL) return (0); any_quirk = 0; /* * Some devices need a delay after that the configuration * value is set to function properly: */ usb_pause_mtx(NULL, hz); if (usb_test_quirk(uaa, UQ_MSC_NO_GETMAXLUN) == 0 && usb_msc_get_max_lun(udev, iface_index) == 0) { DPRINTF("Device has only got one LUN.\n"); USB_ADD_QUIRK(udev, any_quirk, UQ_MSC_NO_GETMAXLUN); } is_no_direct = 1; for (timeout = 4; timeout != 0; timeout--) { err = bbb_command_start(sc, DIR_IN, 0, sc->buffer, SCSI_INQ_LEN, &scsi_inquiry, sizeof(scsi_inquiry), USB_MS_HZ); if (err == 0 && sc->actlen > 0) { sid_type = sc->buffer[0] & 0x1F; if (sid_type == 0x00) is_no_direct = 0; break; } else if (err != ERR_CSW_FAILED) { DPRINTF("Device is not responding " "properly to SCSI INQUIRY command.\n"); goto error; /* non retryable error */ } usb_pause_mtx(NULL, hz); } if (is_no_direct) { DPRINTF("Device is not direct access.\n"); goto done; } if (usb_test_quirk(uaa, UQ_MSC_NO_TEST_UNIT_READY) == 0) { err = bbb_command_start(sc, DIR_NONE, 0, NULL, 0, &scsi_test_unit_ready, sizeof(scsi_test_unit_ready), USB_MS_HZ); if (err != 0) { if (err != ERR_CSW_FAILED) goto error; USB_ADD_QUIRK(udev, any_quirk, UQ_MSC_NO_TEST_UNIT_READY); } } if (usb_test_quirk(uaa, UQ_MSC_NO_PREVENT_ALLOW) == 0) { err = bbb_command_start(sc, DIR_NONE, 0, NULL, 0, &scsi_prevent_removal, sizeof(scsi_prevent_removal), USB_MS_HZ); if (err == 0) { err = bbb_command_start(sc, DIR_NONE, 0, NULL, 0, &scsi_allow_removal, sizeof(scsi_allow_removal), USB_MS_HZ); } if (err != 0) { if (err != ERR_CSW_FAILED) goto error; USB_ADD_QUIRK(udev, any_quirk, UQ_MSC_NO_PREVENT_ALLOW); } } timeout = 1; retry_sync_cache: err = bbb_command_start(sc, DIR_NONE, 0, NULL, 0, &scsi_sync_cache, sizeof(scsi_sync_cache), USB_MS_HZ); if (err != 0) { if (err != ERR_CSW_FAILED) goto error; USB_ADD_QUIRK(udev, any_quirk, UQ_MSC_NO_SYNC_CACHE); } else { /* * Certain Kingston memory sticks fail the first * read capacity after a synchronize cache command * has been issued. Disable the synchronize cache * command for such devices. */ err = bbb_command_start(sc, DIR_IN, 0, sc->buffer, 8, &scsi_read_capacity, sizeof(scsi_read_capacity), USB_MS_HZ); if (err != 0) { if (err != ERR_CSW_FAILED) goto error; err = bbb_command_start(sc, DIR_IN, 0, sc->buffer, 8, &scsi_read_capacity, sizeof(scsi_read_capacity), USB_MS_HZ); if (err == 0) { if (timeout--) goto retry_sync_cache; USB_ADD_QUIRK(udev, any_quirk, UQ_MSC_NO_SYNC_CACHE); } else { if (err != ERR_CSW_FAILED) goto error; } } } if (usb_test_quirk(uaa, UQ_MSC_NO_START_STOP) == 0) { err = bbb_command_start(sc, DIR_NONE, 0, NULL, 0, &scsi_start_unit, sizeof(scsi_start_unit), USB_MS_HZ); if (err != 0) { if (err != ERR_CSW_FAILED) goto error; USB_ADD_QUIRK(udev, any_quirk, UQ_MSC_NO_START_STOP); } } /* clear sense status of any failed commands on the device */ err = bbb_command_start(sc, DIR_IN, 0, sc->buffer, SCSI_INQ_LEN, &scsi_inquiry, sizeof(scsi_inquiry), USB_MS_HZ); DPRINTF("Inquiry = %d\n", err); if (err != 0) { if (err != ERR_CSW_FAILED) goto error; } err = bbb_command_start(sc, DIR_IN, 0, sc->buffer, SCSI_SENSE_LEN, &scsi_request_sense, sizeof(scsi_request_sense), USB_MS_HZ); DPRINTF("Request sense = %d\n", err); if (err != 0) { if (err != ERR_CSW_FAILED) goto error; } goto done; error: /* Apply most quirks */ USB_ADD_QUIRK(udev, any_quirk, UQ_MSC_NO_SYNC_CACHE); USB_ADD_QUIRK(udev, any_quirk, UQ_MSC_NO_PREVENT_ALLOW); USB_ADD_QUIRK(udev, any_quirk, UQ_MSC_NO_TEST_UNIT_READY); USB_ADD_QUIRK(udev, any_quirk, UQ_MSC_NO_START_STOP); done: bbb_detach(sc); if (any_quirk) { /* Unconfigure device, to clear software data toggle. */ usbd_set_config_index(udev, USB_UNCONFIG_INDEX); /* Need to re-enumerate the device to clear its state. */ usbd_req_re_enumerate(udev, NULL); return (USB_ERR_STALLED); } /* No quirks were added, continue as usual. */ return (0); } usb_error_t usb_msc_eject(struct usb_device *udev, uint8_t iface_index, int method) { struct bbb_transfer *sc; - usb_error_t err; + usb_error_t err __usbdebug_used; sc = bbb_attach(udev, iface_index, UICLASS_MASS); if (sc == NULL) return (USB_ERR_INVAL); switch (method) { case MSC_EJECT_STOPUNIT: err = bbb_command_start(sc, DIR_IN, 0, NULL, 0, &scsi_test_unit_ready, sizeof(scsi_test_unit_ready), USB_MS_HZ); DPRINTF("Test unit ready status: %s\n", usbd_errstr(err)); err = bbb_command_start(sc, DIR_IN, 0, NULL, 0, &scsi_stop_unit, sizeof(scsi_stop_unit), USB_MS_HZ); break; case MSC_EJECT_REZERO: err = bbb_command_start(sc, DIR_IN, 0, NULL, 0, &scsi_rezero_init, sizeof(scsi_rezero_init), USB_MS_HZ); break; case MSC_EJECT_ZTESTOR: err = bbb_command_start(sc, DIR_IN, 0, NULL, 0, &scsi_ztestor_eject, sizeof(scsi_ztestor_eject), USB_MS_HZ); break; case MSC_EJECT_CMOTECH: err = bbb_command_start(sc, DIR_IN, 0, NULL, 0, &scsi_cmotech_eject, sizeof(scsi_cmotech_eject), USB_MS_HZ); break; case MSC_EJECT_HUAWEI: err = bbb_command_start(sc, DIR_IN, 0, NULL, 0, &scsi_huawei_eject, sizeof(scsi_huawei_eject), USB_MS_HZ); break; case MSC_EJECT_HUAWEI2: err = bbb_command_start(sc, DIR_IN, 0, NULL, 0, &scsi_huawei_eject2, sizeof(scsi_huawei_eject2), USB_MS_HZ); break; case MSC_EJECT_TCT: /* * TCTMobile needs DIR_IN flag. To get it, we * supply a dummy data with the command. */ err = bbb_command_start(sc, DIR_IN, 0, sc->buffer, sc->buffer_size, &scsi_tct_eject, sizeof(scsi_tct_eject), USB_MS_HZ); break; default: DPRINTF("Unknown eject method (%d)\n", method); bbb_detach(sc); return (USB_ERR_INVAL); } DPRINTF("Eject CD command status: %s\n", usbd_errstr(err)); bbb_detach(sc); return (0); } usb_error_t usb_dymo_eject(struct usb_device *udev, uint8_t iface_index) { static const uint8_t data[3] = { 0x1b, 0x5a, 0x01 }; struct bbb_transfer *sc; usb_error_t err; sc = bbb_attach(udev, iface_index, UICLASS_HID); if (sc == NULL) return (USB_ERR_INVAL); err = bbb_raw_write(sc, data, sizeof(data), USB_MS_HZ); bbb_detach(sc); return (err); } usb_error_t usb_msc_read_10(struct usb_device *udev, uint8_t iface_index, uint32_t lba, uint32_t blocks, void *buffer) { struct bbb_transfer *sc; uint8_t cmd[10]; usb_error_t err; cmd[0] = 0x28; /* READ_10 */ cmd[1] = 0; cmd[2] = lba >> 24; cmd[3] = lba >> 16; cmd[4] = lba >> 8; cmd[5] = lba >> 0; cmd[6] = 0; cmd[7] = blocks >> 8; cmd[8] = blocks; cmd[9] = 0; sc = bbb_attach(udev, iface_index, UICLASS_MASS); if (sc == NULL) return (USB_ERR_INVAL); err = bbb_command_start(sc, DIR_IN, 0, buffer, blocks * SCSI_FIXED_BLOCK_SIZE, cmd, 10, USB_MS_HZ); bbb_detach(sc); return (err); } usb_error_t usb_msc_write_10(struct usb_device *udev, uint8_t iface_index, uint32_t lba, uint32_t blocks, void *buffer) { struct bbb_transfer *sc; uint8_t cmd[10]; usb_error_t err; cmd[0] = 0x2a; /* WRITE_10 */ cmd[1] = 0; cmd[2] = lba >> 24; cmd[3] = lba >> 16; cmd[4] = lba >> 8; cmd[5] = lba >> 0; cmd[6] = 0; cmd[7] = blocks >> 8; cmd[8] = blocks; cmd[9] = 0; sc = bbb_attach(udev, iface_index, UICLASS_MASS); if (sc == NULL) return (USB_ERR_INVAL); err = bbb_command_start(sc, DIR_OUT, 0, buffer, blocks * SCSI_FIXED_BLOCK_SIZE, cmd, 10, USB_MS_HZ); bbb_detach(sc); return (err); } usb_error_t usb_msc_read_capacity(struct usb_device *udev, uint8_t iface_index, uint32_t *lba_last, uint32_t *block_size) { struct bbb_transfer *sc; usb_error_t err; sc = bbb_attach(udev, iface_index, UICLASS_MASS); if (sc == NULL) return (USB_ERR_INVAL); err = bbb_command_start(sc, DIR_IN, 0, sc->buffer, 8, &scsi_read_capacity, sizeof(scsi_read_capacity), USB_MS_HZ); *lba_last = (sc->buffer[0] << 24) | (sc->buffer[1] << 16) | (sc->buffer[2] << 8) | (sc->buffer[3]); *block_size = (sc->buffer[4] << 24) | (sc->buffer[5] << 16) | (sc->buffer[6] << 8) | (sc->buffer[7]); /* we currently only support one block size */ if (*block_size != SCSI_FIXED_BLOCK_SIZE) err = USB_ERR_INVAL; bbb_detach(sc); return (err); }