diff options
author | Sebastian Huber <sebastian.huber@embedded-brains.de> | 2013-10-09 22:42:09 +0200 |
---|---|---|
committer | Sebastian Huber <sebastian.huber@embedded-brains.de> | 2013-10-10 09:06:58 +0200 |
commit | bceabc95c1c85d793200446fa85f1ddc6313ea29 (patch) | |
tree | 973c8bd8deca9fd69913f2895cc91e0e6114d46c /freebsd/sys/dev/usb | |
parent | Add FreeBSD sources as a submodule (diff) | |
download | rtems-libbsd-bceabc95c1c85d793200446fa85f1ddc6313ea29.tar.bz2 |
Move files to match FreeBSD layout
Diffstat (limited to 'freebsd/sys/dev/usb')
54 files changed, 36813 insertions, 0 deletions
diff --git a/freebsd/sys/dev/usb/controller/ehci.c b/freebsd/sys/dev/usb/controller/ehci.c new file mode 100644 index 00000000..39221b7a --- /dev/null +++ b/freebsd/sys/dev/usb/controller/ehci.c @@ -0,0 +1,3939 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/*- + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * Copyright (c) 2004 The NetBSD Foundation, Inc. All rights reserved. + * Copyright (c) 2004 Lennart Augustsson. All rights reserved. + * Copyright (c) 2004 Charles M. Hannum. 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 Enhanced Host Controller Driver, a.k.a. USB 2.0 controller. + * + * The EHCI 0.96 spec can be found at + * http://developer.intel.com/technology/usb/download/ehci-r096.pdf + * The EHCI 1.0 spec can be found at + * http://developer.intel.com/technology/usb/download/ehci-r10.pdf + * and the USB 2.0 spec at + * http://www.usb.org/developers/docs/usb_20.zip + * + */ + +/* + * TODO: + * 1) command failures are not recovered correctly + */ + +#include <freebsd/sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <freebsd/sys/stdint.h> +#include <freebsd/sys/stddef.h> +#include <freebsd/sys/param.h> +#include <freebsd/sys/queue.h> +#include <freebsd/sys/types.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/bus.h> +#include <freebsd/sys/linker_set.h> +#include <freebsd/sys/module.h> +#include <freebsd/sys/lock.h> +#include <freebsd/sys/mutex.h> +#include <freebsd/sys/condvar.h> +#include <freebsd/sys/sysctl.h> +#include <freebsd/sys/sx.h> +#include <freebsd/sys/unistd.h> +#include <freebsd/sys/callout.h> +#include <freebsd/sys/malloc.h> +#include <freebsd/sys/priv.h> + +#include <freebsd/dev/usb/usb.h> +#include <freebsd/dev/usb/usbdi.h> + +#define USB_DEBUG_VAR ehcidebug + +#include <freebsd/dev/usb/usb_core.h> +#include <freebsd/dev/usb/usb_debug.h> +#include <freebsd/dev/usb/usb_busdma.h> +#include <freebsd/dev/usb/usb_process.h> +#include <freebsd/dev/usb/usb_transfer.h> +#include <freebsd/dev/usb/usb_device.h> +#include <freebsd/dev/usb/usb_hub.h> +#include <freebsd/dev/usb/usb_util.h> + +#include <freebsd/dev/usb/usb_controller.h> +#include <freebsd/dev/usb/usb_bus.h> +#include <freebsd/dev/usb/controller/ehci.h> +#include <freebsd/dev/usb/controller/ehcireg.h> + +#define EHCI_BUS2SC(bus) \ + ((ehci_softc_t *)(((uint8_t *)(bus)) - \ + ((uint8_t *)&(((ehci_softc_t *)0)->sc_bus)))) + +#ifdef USB_DEBUG +static int ehcidebug = 0; +static int ehcinohighspeed = 0; +static int ehciiaadbug = 0; +static int ehcilostintrbug = 0; + +SYSCTL_NODE(_hw_usb, OID_AUTO, ehci, CTLFLAG_RW, 0, "USB ehci"); +SYSCTL_INT(_hw_usb_ehci, OID_AUTO, debug, CTLFLAG_RW, + &ehcidebug, 0, "Debug level"); +SYSCTL_INT(_hw_usb_ehci, OID_AUTO, no_hs, CTLFLAG_RW, + &ehcinohighspeed, 0, "Disable High Speed USB"); +SYSCTL_INT(_hw_usb_ehci, OID_AUTO, iaadbug, CTLFLAG_RW, + &ehciiaadbug, 0, "Enable doorbell bug workaround"); +SYSCTL_INT(_hw_usb_ehci, OID_AUTO, lostintrbug, CTLFLAG_RW, + &ehcilostintrbug, 0, "Enable lost interrupt bug workaround"); + +TUNABLE_INT("hw.usb.ehci.debug", &ehcidebug); +TUNABLE_INT("hw.usb.ehci.no_hs", &ehcinohighspeed); +TUNABLE_INT("hw.usb.ehci.iaadbug", &ehciiaadbug); +TUNABLE_INT("hw.usb.ehci.lostintrbug", &ehcilostintrbug); + +static void ehci_dump_regs(ehci_softc_t *sc); +static void ehci_dump_sqh(ehci_softc_t *sc, ehci_qh_t *sqh); + +#endif + +#define EHCI_INTR_ENDPT 1 + +extern struct usb_bus_methods ehci_bus_methods; +extern struct usb_pipe_methods ehci_device_bulk_methods; +extern struct usb_pipe_methods ehci_device_ctrl_methods; +extern struct usb_pipe_methods ehci_device_intr_methods; +extern struct usb_pipe_methods ehci_device_isoc_fs_methods; +extern struct usb_pipe_methods ehci_device_isoc_hs_methods; + +static void ehci_do_poll(struct usb_bus *); +static void ehci_device_done(struct usb_xfer *, usb_error_t); +static uint8_t ehci_check_transfer(struct usb_xfer *); +static void ehci_timeout(void *); +static void ehci_poll_timeout(void *); + +static void ehci_root_intr(ehci_softc_t *sc); + +struct ehci_std_temp { + ehci_softc_t *sc; + struct usb_page_cache *pc; + ehci_qtd_t *td; + ehci_qtd_t *td_next; + uint32_t average; + uint32_t qtd_status; + uint32_t len; + uint16_t max_frame_size; + uint8_t shortpkt; + uint8_t auto_data_toggle; + uint8_t setup_alt_next; + uint8_t last_frame; +}; + +void +ehci_iterate_hw_softc(struct usb_bus *bus, usb_bus_mem_sub_cb_t *cb) +{ + ehci_softc_t *sc = EHCI_BUS2SC(bus); + uint32_t i; + + cb(bus, &sc->sc_hw.pframes_pc, &sc->sc_hw.pframes_pg, + sizeof(uint32_t) * EHCI_FRAMELIST_COUNT, EHCI_FRAMELIST_ALIGN); + + cb(bus, &sc->sc_hw.terminate_pc, &sc->sc_hw.terminate_pg, + sizeof(struct ehci_qh_sub), EHCI_QH_ALIGN); + + cb(bus, &sc->sc_hw.async_start_pc, &sc->sc_hw.async_start_pg, + sizeof(ehci_qh_t), EHCI_QH_ALIGN); + + for (i = 0; i != EHCI_VIRTUAL_FRAMELIST_COUNT; i++) { + cb(bus, sc->sc_hw.intr_start_pc + i, + sc->sc_hw.intr_start_pg + i, + sizeof(ehci_qh_t), EHCI_QH_ALIGN); + } + + for (i = 0; i != EHCI_VIRTUAL_FRAMELIST_COUNT; i++) { + cb(bus, sc->sc_hw.isoc_hs_start_pc + i, + sc->sc_hw.isoc_hs_start_pg + i, + sizeof(ehci_itd_t), EHCI_ITD_ALIGN); + } + + for (i = 0; i != EHCI_VIRTUAL_FRAMELIST_COUNT; i++) { + cb(bus, sc->sc_hw.isoc_fs_start_pc + i, + sc->sc_hw.isoc_fs_start_pg + i, + sizeof(ehci_sitd_t), EHCI_SITD_ALIGN); + } +} + +usb_error_t +ehci_reset(ehci_softc_t *sc) +{ + uint32_t hcr; + int i; + + EOWRITE4(sc, EHCI_USBCMD, EHCI_CMD_HCRESET); + for (i = 0; i < 100; i++) { + usb_pause_mtx(NULL, hz / 1000); + hcr = EOREAD4(sc, EHCI_USBCMD) & EHCI_CMD_HCRESET; + if (!hcr) { + if (sc->sc_flags & (EHCI_SCFLG_SETMODE | EHCI_SCFLG_BIGEMMIO)) { + /* + * Force USBMODE as requested. Controllers + * may have multiple operating modes. + */ + uint32_t usbmode = EOREAD4(sc, EHCI_USBMODE); + if (sc->sc_flags & EHCI_SCFLG_SETMODE) { + usbmode = (usbmode &~ EHCI_UM_CM) | EHCI_UM_CM_HOST; + device_printf(sc->sc_bus.bdev, + "set host controller mode\n"); + } + if (sc->sc_flags & EHCI_SCFLG_BIGEMMIO) { + usbmode = (usbmode &~ EHCI_UM_ES) | EHCI_UM_ES_BE; + device_printf(sc->sc_bus.bdev, + "set big-endian mode\n"); + } + EOWRITE4(sc, EHCI_USBMODE, usbmode); + } + return (0); + } + } + device_printf(sc->sc_bus.bdev, "reset timeout\n"); + return (USB_ERR_IOERROR); +} + +static usb_error_t +ehci_hcreset(ehci_softc_t *sc) +{ + uint32_t hcr; + int i; + + EOWRITE4(sc, EHCI_USBCMD, 0); /* Halt controller */ + for (i = 0; i < 100; i++) { + usb_pause_mtx(NULL, hz / 1000); + hcr = EOREAD4(sc, EHCI_USBSTS) & EHCI_STS_HCH; + if (hcr) + break; + } + if (!hcr) + /* + * Fall through and try reset anyway even though + * Table 2-9 in the EHCI spec says this will result + * in undefined behavior. + */ + device_printf(sc->sc_bus.bdev, "stop timeout\n"); + + return ehci_reset(sc); +} + +usb_error_t +ehci_init(ehci_softc_t *sc) +{ + struct usb_page_search buf_res; + uint32_t version; + uint32_t sparams; + uint32_t cparams; + uint32_t hcr; + uint16_t i; + uint16_t x; + uint16_t y; + uint16_t bit; + usb_error_t err = 0; + + DPRINTF("start\n"); + + usb_callout_init_mtx(&sc->sc_tmo_pcd, &sc->sc_bus.bus_mtx, 0); + usb_callout_init_mtx(&sc->sc_tmo_poll, &sc->sc_bus.bus_mtx, 0); + +#ifdef USB_DEBUG + if (ehciiaadbug) + sc->sc_flags |= EHCI_SCFLG_IAADBUG; + if (ehcilostintrbug) + sc->sc_flags |= EHCI_SCFLG_LOSTINTRBUG; + if (ehcidebug > 2) { + ehci_dump_regs(sc); + } +#endif + + sc->sc_offs = EHCI_CAPLENGTH(EREAD4(sc, EHCI_CAPLEN_HCIVERSION)); + + version = EHCI_HCIVERSION(EREAD4(sc, EHCI_CAPLEN_HCIVERSION)); + device_printf(sc->sc_bus.bdev, "EHCI version %x.%x\n", + version >> 8, version & 0xff); + + sparams = EREAD4(sc, EHCI_HCSPARAMS); + DPRINTF("sparams=0x%x\n", sparams); + + sc->sc_noport = EHCI_HCS_N_PORTS(sparams); + cparams = EREAD4(sc, EHCI_HCCPARAMS); + DPRINTF("cparams=0x%x\n", cparams); + + if (EHCI_HCC_64BIT(cparams)) { + DPRINTF("HCC uses 64-bit structures\n"); + + /* MUST clear segment register if 64 bit capable */ + EWRITE4(sc, EHCI_CTRLDSSEGMENT, 0); + } + sc->sc_bus.usbrev = USB_REV_2_0; + + /* Reset the controller */ + DPRINTF("%s: resetting\n", device_get_nameunit(sc->sc_bus.bdev)); + + err = ehci_hcreset(sc); + if (err) { + device_printf(sc->sc_bus.bdev, "reset timeout\n"); + return (err); + } + /* + * use current frame-list-size selection 0: 1024*4 bytes 1: 512*4 + * bytes 2: 256*4 bytes 3: unknown + */ + if (EHCI_CMD_FLS(EOREAD4(sc, EHCI_USBCMD)) == 3) { + device_printf(sc->sc_bus.bdev, "invalid frame-list-size\n"); + return (USB_ERR_IOERROR); + } + /* set up the bus struct */ + sc->sc_bus.methods = &ehci_bus_methods; + + sc->sc_eintrs = EHCI_NORMAL_INTRS; + + if (1) { + struct ehci_qh_sub *qh; + + usbd_get_page(&sc->sc_hw.terminate_pc, 0, &buf_res); + + qh = buf_res.buffer; + + sc->sc_terminate_self = htohc32(sc, buf_res.physaddr); + + /* init terminate TD */ + qh->qtd_next = + htohc32(sc, EHCI_LINK_TERMINATE); + qh->qtd_altnext = + htohc32(sc, EHCI_LINK_TERMINATE); + qh->qtd_status = + htohc32(sc, EHCI_QTD_HALTED); + } + + for (i = 0; i < EHCI_VIRTUAL_FRAMELIST_COUNT; i++) { + ehci_qh_t *qh; + + usbd_get_page(sc->sc_hw.intr_start_pc + i, 0, &buf_res); + + qh = buf_res.buffer; + + /* initialize page cache pointer */ + + qh->page_cache = sc->sc_hw.intr_start_pc + i; + + /* store a pointer to queue head */ + + sc->sc_intr_p_last[i] = qh; + + qh->qh_self = + htohc32(sc, buf_res.physaddr) | + htohc32(sc, EHCI_LINK_QH); + + qh->qh_endp = + htohc32(sc, EHCI_QH_SET_EPS(EHCI_QH_SPEED_HIGH)); + qh->qh_endphub = + htohc32(sc, EHCI_QH_SET_MULT(1)); + qh->qh_curqtd = 0; + + qh->qh_qtd.qtd_next = + htohc32(sc, EHCI_LINK_TERMINATE); + qh->qh_qtd.qtd_altnext = + htohc32(sc, EHCI_LINK_TERMINATE); + qh->qh_qtd.qtd_status = + htohc32(sc, EHCI_QTD_HALTED); + } + + /* + * the QHs are arranged to give poll intervals that are + * powers of 2 times 1ms + */ + bit = EHCI_VIRTUAL_FRAMELIST_COUNT / 2; + while (bit) { + x = bit; + while (x & bit) { + ehci_qh_t *qh_x; + ehci_qh_t *qh_y; + + y = (x ^ bit) | (bit / 2); + + qh_x = sc->sc_intr_p_last[x]; + qh_y = sc->sc_intr_p_last[y]; + + /* + * the next QH has half the poll interval + */ + qh_x->qh_link = qh_y->qh_self; + + x++; + } + bit >>= 1; + } + + if (1) { + ehci_qh_t *qh; + + qh = sc->sc_intr_p_last[0]; + + /* the last (1ms) QH terminates */ + qh->qh_link = htohc32(sc, EHCI_LINK_TERMINATE); + } + for (i = 0; i < EHCI_VIRTUAL_FRAMELIST_COUNT; i++) { + ehci_sitd_t *sitd; + ehci_itd_t *itd; + + usbd_get_page(sc->sc_hw.isoc_fs_start_pc + i, 0, &buf_res); + + sitd = buf_res.buffer; + + /* initialize page cache pointer */ + + sitd->page_cache = sc->sc_hw.isoc_fs_start_pc + i; + + /* store a pointer to the transfer descriptor */ + + sc->sc_isoc_fs_p_last[i] = sitd; + + /* initialize full speed isochronous */ + + sitd->sitd_self = + htohc32(sc, buf_res.physaddr) | + htohc32(sc, EHCI_LINK_SITD); + + sitd->sitd_back = + htohc32(sc, EHCI_LINK_TERMINATE); + + sitd->sitd_next = + sc->sc_intr_p_last[i | (EHCI_VIRTUAL_FRAMELIST_COUNT / 2)]->qh_self; + + + usbd_get_page(sc->sc_hw.isoc_hs_start_pc + i, 0, &buf_res); + + itd = buf_res.buffer; + + /* initialize page cache pointer */ + + itd->page_cache = sc->sc_hw.isoc_hs_start_pc + i; + + /* store a pointer to the transfer descriptor */ + + sc->sc_isoc_hs_p_last[i] = itd; + + /* initialize high speed isochronous */ + + itd->itd_self = + htohc32(sc, buf_res.physaddr) | + htohc32(sc, EHCI_LINK_ITD); + + itd->itd_next = + sitd->sitd_self; + } + + usbd_get_page(&sc->sc_hw.pframes_pc, 0, &buf_res); + + if (1) { + uint32_t *pframes; + + pframes = buf_res.buffer; + + /* + * execution order: + * pframes -> high speed isochronous -> + * full speed isochronous -> interrupt QH's + */ + for (i = 0; i < EHCI_FRAMELIST_COUNT; i++) { + pframes[i] = sc->sc_isoc_hs_p_last + [i & (EHCI_VIRTUAL_FRAMELIST_COUNT - 1)]->itd_self; + } + } + /* setup sync list pointer */ + EOWRITE4(sc, EHCI_PERIODICLISTBASE, buf_res.physaddr); + + usbd_get_page(&sc->sc_hw.async_start_pc, 0, &buf_res); + + if (1) { + + ehci_qh_t *qh; + + qh = buf_res.buffer; + + /* initialize page cache pointer */ + + qh->page_cache = &sc->sc_hw.async_start_pc; + + /* store a pointer to the queue head */ + + sc->sc_async_p_last = qh; + + /* init dummy QH that starts the async list */ + + qh->qh_self = + htohc32(sc, buf_res.physaddr) | + htohc32(sc, EHCI_LINK_QH); + + /* fill the QH */ + qh->qh_endp = + htohc32(sc, EHCI_QH_SET_EPS(EHCI_QH_SPEED_HIGH) | EHCI_QH_HRECL); + qh->qh_endphub = htohc32(sc, EHCI_QH_SET_MULT(1)); + qh->qh_link = qh->qh_self; + qh->qh_curqtd = 0; + + /* fill the overlay qTD */ + qh->qh_qtd.qtd_next = htohc32(sc, EHCI_LINK_TERMINATE); + qh->qh_qtd.qtd_altnext = htohc32(sc, EHCI_LINK_TERMINATE); + qh->qh_qtd.qtd_status = htohc32(sc, EHCI_QTD_HALTED); + } + /* flush all cache into memory */ + + usb_bus_mem_flush_all(&sc->sc_bus, &ehci_iterate_hw_softc); + +#ifdef USB_DEBUG + if (ehcidebug) { + ehci_dump_sqh(sc, sc->sc_async_p_last); + } +#endif + + /* setup async list pointer */ + EOWRITE4(sc, EHCI_ASYNCLISTADDR, buf_res.physaddr | EHCI_LINK_QH); + + + /* enable interrupts */ + EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs); + + /* turn on controller */ + EOWRITE4(sc, EHCI_USBCMD, + EHCI_CMD_ITC_1 | /* 1 microframes interrupt delay */ + (EOREAD4(sc, EHCI_USBCMD) & EHCI_CMD_FLS_M) | + EHCI_CMD_ASE | + EHCI_CMD_PSE | + EHCI_CMD_RS); + + /* Take over port ownership */ + EOWRITE4(sc, EHCI_CONFIGFLAG, EHCI_CONF_CF); + + for (i = 0; i < 100; i++) { + usb_pause_mtx(NULL, hz / 1000); + hcr = EOREAD4(sc, EHCI_USBSTS) & EHCI_STS_HCH; + if (!hcr) { + break; + } + } + if (hcr) { + device_printf(sc->sc_bus.bdev, "run timeout\n"); + return (USB_ERR_IOERROR); + } + + if (!err) { + /* catch any lost interrupts */ + ehci_do_poll(&sc->sc_bus); + } + return (err); +} + +/* + * shut down the controller when the system is going down + */ +void +ehci_detach(ehci_softc_t *sc) +{ + USB_BUS_LOCK(&sc->sc_bus); + + usb_callout_stop(&sc->sc_tmo_pcd); + usb_callout_stop(&sc->sc_tmo_poll); + + EOWRITE4(sc, EHCI_USBINTR, 0); + USB_BUS_UNLOCK(&sc->sc_bus); + + if (ehci_hcreset(sc)) { + DPRINTF("reset failed!\n"); + } + + /* XXX let stray task complete */ + usb_pause_mtx(NULL, hz / 20); + + usb_callout_drain(&sc->sc_tmo_pcd); + usb_callout_drain(&sc->sc_tmo_poll); +} + +void +ehci_suspend(ehci_softc_t *sc) +{ + uint32_t cmd; + uint32_t hcr; + uint8_t i; + + USB_BUS_LOCK(&sc->sc_bus); + + for (i = 1; i <= sc->sc_noport; i++) { + cmd = EOREAD4(sc, EHCI_PORTSC(i)); + if (((cmd & EHCI_PS_PO) == 0) && + ((cmd & EHCI_PS_PE) == EHCI_PS_PE)) { + EOWRITE4(sc, EHCI_PORTSC(i), + cmd | EHCI_PS_SUSP); + } + } + + sc->sc_cmd = EOREAD4(sc, EHCI_USBCMD); + + cmd = sc->sc_cmd & ~(EHCI_CMD_ASE | EHCI_CMD_PSE); + EOWRITE4(sc, EHCI_USBCMD, cmd); + + for (i = 0; i < 100; i++) { + hcr = EOREAD4(sc, EHCI_USBSTS) & + (EHCI_STS_ASS | EHCI_STS_PSS); + + if (hcr == 0) { + break; + } + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000); + } + + if (hcr != 0) { + device_printf(sc->sc_bus.bdev, "reset timeout\n"); + } + cmd &= ~EHCI_CMD_RS; + EOWRITE4(sc, EHCI_USBCMD, cmd); + + for (i = 0; i < 100; i++) { + hcr = EOREAD4(sc, EHCI_USBSTS) & EHCI_STS_HCH; + if (hcr == EHCI_STS_HCH) { + break; + } + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000); + } + + if (hcr != EHCI_STS_HCH) { + device_printf(sc->sc_bus.bdev, + "config timeout\n"); + } + USB_BUS_UNLOCK(&sc->sc_bus); +} + +void +ehci_resume(ehci_softc_t *sc) +{ + struct usb_page_search buf_res; + uint32_t cmd; + uint32_t hcr; + uint8_t i; + + USB_BUS_LOCK(&sc->sc_bus); + + /* restore things in case the bios doesn't */ + EOWRITE4(sc, EHCI_CTRLDSSEGMENT, 0); + + usbd_get_page(&sc->sc_hw.pframes_pc, 0, &buf_res); + EOWRITE4(sc, EHCI_PERIODICLISTBASE, buf_res.physaddr); + + usbd_get_page(&sc->sc_hw.async_start_pc, 0, &buf_res); + EOWRITE4(sc, EHCI_ASYNCLISTADDR, buf_res.physaddr | EHCI_LINK_QH); + + EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs); + + hcr = 0; + for (i = 1; i <= sc->sc_noport; i++) { + cmd = EOREAD4(sc, EHCI_PORTSC(i)); + if (((cmd & EHCI_PS_PO) == 0) && + ((cmd & EHCI_PS_SUSP) == EHCI_PS_SUSP)) { + EOWRITE4(sc, EHCI_PORTSC(i), + cmd | EHCI_PS_FPR); + hcr = 1; + } + } + + if (hcr) { + usb_pause_mtx(&sc->sc_bus.bus_mtx, + USB_MS_TO_TICKS(USB_RESUME_WAIT)); + + for (i = 1; i <= sc->sc_noport; i++) { + cmd = EOREAD4(sc, EHCI_PORTSC(i)); + if (((cmd & EHCI_PS_PO) == 0) && + ((cmd & EHCI_PS_SUSP) == EHCI_PS_SUSP)) { + EOWRITE4(sc, EHCI_PORTSC(i), + cmd & ~EHCI_PS_FPR); + } + } + } + EOWRITE4(sc, EHCI_USBCMD, sc->sc_cmd); + + for (i = 0; i < 100; i++) { + hcr = EOREAD4(sc, EHCI_USBSTS) & EHCI_STS_HCH; + if (hcr != EHCI_STS_HCH) { + break; + } + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000); + } + if (hcr == EHCI_STS_HCH) { + device_printf(sc->sc_bus.bdev, "config timeout\n"); + } + + USB_BUS_UNLOCK(&sc->sc_bus); + + usb_pause_mtx(NULL, + USB_MS_TO_TICKS(USB_RESUME_WAIT)); + + /* catch any lost interrupts */ + ehci_do_poll(&sc->sc_bus); +} + +void +ehci_shutdown(ehci_softc_t *sc) +{ + DPRINTF("stopping the HC\n"); + + if (ehci_hcreset(sc)) { + DPRINTF("reset failed!\n"); + } +} + +#ifdef USB_DEBUG +static void +ehci_dump_regs(ehci_softc_t *sc) +{ + uint32_t i; + + i = EOREAD4(sc, EHCI_USBCMD); + printf("cmd=0x%08x\n", i); + + if (i & EHCI_CMD_ITC_1) + printf(" EHCI_CMD_ITC_1\n"); + if (i & EHCI_CMD_ITC_2) + printf(" EHCI_CMD_ITC_2\n"); + if (i & EHCI_CMD_ITC_4) + printf(" EHCI_CMD_ITC_4\n"); + if (i & EHCI_CMD_ITC_8) + printf(" EHCI_CMD_ITC_8\n"); + if (i & EHCI_CMD_ITC_16) + printf(" EHCI_CMD_ITC_16\n"); + if (i & EHCI_CMD_ITC_32) + printf(" EHCI_CMD_ITC_32\n"); + if (i & EHCI_CMD_ITC_64) + printf(" EHCI_CMD_ITC_64\n"); + if (i & EHCI_CMD_ASPME) + printf(" EHCI_CMD_ASPME\n"); + if (i & EHCI_CMD_ASPMC) + printf(" EHCI_CMD_ASPMC\n"); + if (i & EHCI_CMD_LHCR) + printf(" EHCI_CMD_LHCR\n"); + if (i & EHCI_CMD_IAAD) + printf(" EHCI_CMD_IAAD\n"); + if (i & EHCI_CMD_ASE) + printf(" EHCI_CMD_ASE\n"); + if (i & EHCI_CMD_PSE) + printf(" EHCI_CMD_PSE\n"); + if (i & EHCI_CMD_FLS_M) + printf(" EHCI_CMD_FLS_M\n"); + if (i & EHCI_CMD_HCRESET) + printf(" EHCI_CMD_HCRESET\n"); + if (i & EHCI_CMD_RS) + printf(" EHCI_CMD_RS\n"); + + i = EOREAD4(sc, EHCI_USBSTS); + + printf("sts=0x%08x\n", i); + + if (i & EHCI_STS_ASS) + printf(" EHCI_STS_ASS\n"); + if (i & EHCI_STS_PSS) + printf(" EHCI_STS_PSS\n"); + if (i & EHCI_STS_REC) + printf(" EHCI_STS_REC\n"); + if (i & EHCI_STS_HCH) + printf(" EHCI_STS_HCH\n"); + if (i & EHCI_STS_IAA) + printf(" EHCI_STS_IAA\n"); + if (i & EHCI_STS_HSE) + printf(" EHCI_STS_HSE\n"); + if (i & EHCI_STS_FLR) + printf(" EHCI_STS_FLR\n"); + if (i & EHCI_STS_PCD) + printf(" EHCI_STS_PCD\n"); + if (i & EHCI_STS_ERRINT) + printf(" EHCI_STS_ERRINT\n"); + if (i & EHCI_STS_INT) + printf(" EHCI_STS_INT\n"); + + printf("ien=0x%08x\n", + EOREAD4(sc, EHCI_USBINTR)); + printf("frindex=0x%08x ctrdsegm=0x%08x periodic=0x%08x async=0x%08x\n", + EOREAD4(sc, EHCI_FRINDEX), + EOREAD4(sc, EHCI_CTRLDSSEGMENT), + EOREAD4(sc, EHCI_PERIODICLISTBASE), + EOREAD4(sc, EHCI_ASYNCLISTADDR)); + for (i = 1; i <= sc->sc_noport; i++) { + printf("port %d status=0x%08x\n", i, + EOREAD4(sc, EHCI_PORTSC(i))); + } +} + +static void +ehci_dump_link(ehci_softc_t *sc, uint32_t link, int type) +{ + link = hc32toh(sc, link); + printf("0x%08x", link); + if (link & EHCI_LINK_TERMINATE) + printf("<T>"); + else { + printf("<"); + if (type) { + switch (EHCI_LINK_TYPE(link)) { + case EHCI_LINK_ITD: + printf("ITD"); + break; + case EHCI_LINK_QH: + printf("QH"); + break; + case EHCI_LINK_SITD: + printf("SITD"); + break; + case EHCI_LINK_FSTN: + printf("FSTN"); + break; + } + } + printf(">"); + } +} + +static void +ehci_dump_qtd(ehci_softc_t *sc, ehci_qtd_t *qtd) +{ + uint32_t s; + + printf(" next="); + ehci_dump_link(sc, qtd->qtd_next, 0); + printf(" altnext="); + ehci_dump_link(sc, qtd->qtd_altnext, 0); + printf("\n"); + s = hc32toh(sc, qtd->qtd_status); + printf(" status=0x%08x: toggle=%d bytes=0x%x ioc=%d c_page=0x%x\n", + s, EHCI_QTD_GET_TOGGLE(s), EHCI_QTD_GET_BYTES(s), + EHCI_QTD_GET_IOC(s), EHCI_QTD_GET_C_PAGE(s)); + printf(" cerr=%d pid=%d stat=%s%s%s%s%s%s%s%s\n", + EHCI_QTD_GET_CERR(s), EHCI_QTD_GET_PID(s), + (s & EHCI_QTD_ACTIVE) ? "ACTIVE" : "NOT_ACTIVE", + (s & EHCI_QTD_HALTED) ? "-HALTED" : "", + (s & EHCI_QTD_BUFERR) ? "-BUFERR" : "", + (s & EHCI_QTD_BABBLE) ? "-BABBLE" : "", + (s & EHCI_QTD_XACTERR) ? "-XACTERR" : "", + (s & EHCI_QTD_MISSEDMICRO) ? "-MISSED" : "", + (s & EHCI_QTD_SPLITXSTATE) ? "-SPLIT" : "", + (s & EHCI_QTD_PINGSTATE) ? "-PING" : ""); + + for (s = 0; s < 5; s++) { + printf(" buffer[%d]=0x%08x\n", s, + hc32toh(sc, qtd->qtd_buffer[s])); + } + for (s = 0; s < 5; s++) { + printf(" buffer_hi[%d]=0x%08x\n", s, + hc32toh(sc, qtd->qtd_buffer_hi[s])); + } +} + +static uint8_t +ehci_dump_sqtd(ehci_softc_t *sc, ehci_qtd_t *sqtd) +{ + uint8_t temp; + + usb_pc_cpu_invalidate(sqtd->page_cache); + printf("QTD(%p) at 0x%08x:\n", sqtd, hc32toh(sc, sqtd->qtd_self)); + ehci_dump_qtd(sc, sqtd); + temp = (sqtd->qtd_next & htohc32(sc, EHCI_LINK_TERMINATE)) ? 1 : 0; + return (temp); +} + +static void +ehci_dump_sqtds(ehci_softc_t *sc, ehci_qtd_t *sqtd) +{ + uint16_t i; + uint8_t stop; + + stop = 0; + for (i = 0; sqtd && (i < 20) && !stop; sqtd = sqtd->obj_next, i++) { + stop = ehci_dump_sqtd(sc, sqtd); + } + if (sqtd) { + printf("dump aborted, too many TDs\n"); + } +} + +static void +ehci_dump_sqh(ehci_softc_t *sc, ehci_qh_t *qh) +{ + uint32_t endp; + uint32_t endphub; + + usb_pc_cpu_invalidate(qh->page_cache); + printf("QH(%p) at 0x%08x:\n", qh, hc32toh(sc, qh->qh_self) & ~0x1F); + printf(" link="); + ehci_dump_link(sc, qh->qh_link, 1); + printf("\n"); + endp = hc32toh(sc, qh->qh_endp); + printf(" endp=0x%08x\n", endp); + printf(" addr=0x%02x inact=%d endpt=%d eps=%d dtc=%d hrecl=%d\n", + EHCI_QH_GET_ADDR(endp), EHCI_QH_GET_INACT(endp), + EHCI_QH_GET_ENDPT(endp), EHCI_QH_GET_EPS(endp), + EHCI_QH_GET_DTC(endp), EHCI_QH_GET_HRECL(endp)); + printf(" mpl=0x%x ctl=%d nrl=%d\n", + EHCI_QH_GET_MPL(endp), EHCI_QH_GET_CTL(endp), + EHCI_QH_GET_NRL(endp)); + endphub = hc32toh(sc, qh->qh_endphub); + printf(" endphub=0x%08x\n", endphub); + printf(" smask=0x%02x cmask=0x%02x huba=0x%02x port=%d mult=%d\n", + EHCI_QH_GET_SMASK(endphub), EHCI_QH_GET_CMASK(endphub), + EHCI_QH_GET_HUBA(endphub), EHCI_QH_GET_PORT(endphub), + EHCI_QH_GET_MULT(endphub)); + printf(" curqtd="); + ehci_dump_link(sc, qh->qh_curqtd, 0); + printf("\n"); + printf("Overlay qTD:\n"); + ehci_dump_qtd(sc, (void *)&qh->qh_qtd); +} + +static void +ehci_dump_sitd(ehci_softc_t *sc, ehci_sitd_t *sitd) +{ + usb_pc_cpu_invalidate(sitd->page_cache); + printf("SITD(%p) at 0x%08x\n", sitd, hc32toh(sc, sitd->sitd_self) & ~0x1F); + printf(" next=0x%08x\n", hc32toh(sc, sitd->sitd_next)); + printf(" portaddr=0x%08x dir=%s addr=%d endpt=0x%x port=0x%x huba=0x%x\n", + hc32toh(sc, sitd->sitd_portaddr), + (sitd->sitd_portaddr & htohc32(sc, EHCI_SITD_SET_DIR_IN)) + ? "in" : "out", + EHCI_SITD_GET_ADDR(hc32toh(sc, sitd->sitd_portaddr)), + EHCI_SITD_GET_ENDPT(hc32toh(sc, sitd->sitd_portaddr)), + EHCI_SITD_GET_PORT(hc32toh(sc, sitd->sitd_portaddr)), + EHCI_SITD_GET_HUBA(hc32toh(sc, sitd->sitd_portaddr))); + printf(" mask=0x%08x\n", hc32toh(sc, sitd->sitd_mask)); + printf(" status=0x%08x <%s> len=0x%x\n", hc32toh(sc, sitd->sitd_status), + (sitd->sitd_status & htohc32(sc, EHCI_SITD_ACTIVE)) ? "ACTIVE" : "", + EHCI_SITD_GET_LEN(hc32toh(sc, sitd->sitd_status))); + printf(" back=0x%08x, bp=0x%08x,0x%08x,0x%08x,0x%08x\n", + hc32toh(sc, sitd->sitd_back), + hc32toh(sc, sitd->sitd_bp[0]), + hc32toh(sc, sitd->sitd_bp[1]), + hc32toh(sc, sitd->sitd_bp_hi[0]), + hc32toh(sc, sitd->sitd_bp_hi[1])); +} + +static void +ehci_dump_itd(ehci_softc_t *sc, ehci_itd_t *itd) +{ + usb_pc_cpu_invalidate(itd->page_cache); + printf("ITD(%p) at 0x%08x\n", itd, hc32toh(sc, itd->itd_self) & ~0x1F); + printf(" next=0x%08x\n", hc32toh(sc, itd->itd_next)); + printf(" status[0]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[0]), + (itd->itd_status[0] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); + printf(" status[1]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[1]), + (itd->itd_status[1] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); + printf(" status[2]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[2]), + (itd->itd_status[2] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); + printf(" status[3]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[3]), + (itd->itd_status[3] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); + printf(" status[4]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[4]), + (itd->itd_status[4] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); + printf(" status[5]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[5]), + (itd->itd_status[5] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); + printf(" status[6]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[6]), + (itd->itd_status[6] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); + printf(" status[7]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[7]), + (itd->itd_status[7] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : ""); + printf(" bp[0]=0x%08x\n", hc32toh(sc, itd->itd_bp[0])); + printf(" addr=0x%02x; endpt=0x%01x\n", + EHCI_ITD_GET_ADDR(hc32toh(sc, itd->itd_bp[0])), + EHCI_ITD_GET_ENDPT(hc32toh(sc, itd->itd_bp[0]))); + printf(" bp[1]=0x%08x\n", hc32toh(sc, itd->itd_bp[1])); + printf(" dir=%s; mpl=0x%02x\n", + (hc32toh(sc, itd->itd_bp[1]) & EHCI_ITD_SET_DIR_IN) ? "in" : "out", + EHCI_ITD_GET_MPL(hc32toh(sc, itd->itd_bp[1]))); + printf(" bp[2..6]=0x%08x,0x%08x,0x%08x,0x%08x,0x%08x\n", + hc32toh(sc, itd->itd_bp[2]), + hc32toh(sc, itd->itd_bp[3]), + hc32toh(sc, itd->itd_bp[4]), + hc32toh(sc, itd->itd_bp[5]), + hc32toh(sc, itd->itd_bp[6])); + printf(" bp_hi=0x%08x,0x%08x,0x%08x,0x%08x,\n" + " 0x%08x,0x%08x,0x%08x\n", + hc32toh(sc, itd->itd_bp_hi[0]), + hc32toh(sc, itd->itd_bp_hi[1]), + hc32toh(sc, itd->itd_bp_hi[2]), + hc32toh(sc, itd->itd_bp_hi[3]), + hc32toh(sc, itd->itd_bp_hi[4]), + hc32toh(sc, itd->itd_bp_hi[5]), + hc32toh(sc, itd->itd_bp_hi[6])); +} + +static void +ehci_dump_isoc(ehci_softc_t *sc) +{ + ehci_itd_t *itd; + ehci_sitd_t *sitd; + uint16_t max = 1000; + uint16_t pos; + + pos = (EOREAD4(sc, EHCI_FRINDEX) / 8) & + (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); + + printf("%s: isochronous dump from frame 0x%03x:\n", + __FUNCTION__, pos); + + itd = sc->sc_isoc_hs_p_last[pos]; + sitd = sc->sc_isoc_fs_p_last[pos]; + + while (itd && max && max--) { + ehci_dump_itd(sc, itd); + itd = itd->prev; + } + + while (sitd && max && max--) { + ehci_dump_sitd(sc, sitd); + sitd = sitd->prev; + } +} + +#endif + +static void +ehci_transfer_intr_enqueue(struct usb_xfer *xfer) +{ + /* check for early completion */ + if (ehci_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, &ehci_timeout, xfer->timeout); + } +} + +#define EHCI_APPEND_FS_TD(std,last) (last) = _ehci_append_fs_td(std,last) +static ehci_sitd_t * +_ehci_append_fs_td(ehci_sitd_t *std, ehci_sitd_t *last) +{ + DPRINTFN(11, "%p to %p\n", std, last); + + /* (sc->sc_bus.mtx) must be locked */ + + std->next = last->next; + std->sitd_next = last->sitd_next; + + std->prev = last; + + usb_pc_cpu_flush(std->page_cache); + + /* + * the last->next->prev is never followed: std->next->prev = std; + */ + last->next = std; + last->sitd_next = std->sitd_self; + + usb_pc_cpu_flush(last->page_cache); + + return (std); +} + +#define EHCI_APPEND_HS_TD(std,last) (last) = _ehci_append_hs_td(std,last) +static ehci_itd_t * +_ehci_append_hs_td(ehci_itd_t *std, ehci_itd_t *last) +{ + DPRINTFN(11, "%p to %p\n", std, last); + + /* (sc->sc_bus.mtx) must be locked */ + + std->next = last->next; + std->itd_next = last->itd_next; + + std->prev = last; + + usb_pc_cpu_flush(std->page_cache); + + /* + * the last->next->prev is never followed: std->next->prev = std; + */ + last->next = std; + last->itd_next = std->itd_self; + + usb_pc_cpu_flush(last->page_cache); + + return (std); +} + +#define EHCI_APPEND_QH(sqh,last) (last) = _ehci_append_qh(sqh,last) +static ehci_qh_t * +_ehci_append_qh(ehci_qh_t *sqh, ehci_qh_t *last) +{ + DPRINTFN(11, "%p to %p\n", sqh, last); + + if (sqh->prev != NULL) { + /* should not happen */ + DPRINTFN(0, "QH already linked!\n"); + return (last); + } + /* (sc->sc_bus.mtx) must be locked */ + + sqh->next = last->next; + sqh->qh_link = last->qh_link; + + sqh->prev = last; + + usb_pc_cpu_flush(sqh->page_cache); + + /* + * the last->next->prev is never followed: sqh->next->prev = sqh; + */ + + last->next = sqh; + last->qh_link = sqh->qh_self; + + usb_pc_cpu_flush(last->page_cache); + + return (sqh); +} + +#define EHCI_REMOVE_FS_TD(std,last) (last) = _ehci_remove_fs_td(std,last) +static ehci_sitd_t * +_ehci_remove_fs_td(ehci_sitd_t *std, ehci_sitd_t *last) +{ + DPRINTFN(11, "%p from %p\n", std, last); + + /* (sc->sc_bus.mtx) must be locked */ + + std->prev->next = std->next; + std->prev->sitd_next = std->sitd_next; + + usb_pc_cpu_flush(std->prev->page_cache); + + if (std->next) { + std->next->prev = std->prev; + usb_pc_cpu_flush(std->next->page_cache); + } + return ((last == std) ? std->prev : last); +} + +#define EHCI_REMOVE_HS_TD(std,last) (last) = _ehci_remove_hs_td(std,last) +static ehci_itd_t * +_ehci_remove_hs_td(ehci_itd_t *std, ehci_itd_t *last) +{ + DPRINTFN(11, "%p from %p\n", std, last); + + /* (sc->sc_bus.mtx) must be locked */ + + std->prev->next = std->next; + std->prev->itd_next = std->itd_next; + + usb_pc_cpu_flush(std->prev->page_cache); + + if (std->next) { + std->next->prev = std->prev; + usb_pc_cpu_flush(std->next->page_cache); + } + return ((last == std) ? std->prev : last); +} + +#define EHCI_REMOVE_QH(sqh,last) (last) = _ehci_remove_qh(sqh,last) +static ehci_qh_t * +_ehci_remove_qh(ehci_qh_t *sqh, ehci_qh_t *last) +{ + DPRINTFN(11, "%p from %p\n", sqh, last); + + /* (sc->sc_bus.mtx) must be locked */ + + /* only remove if not removed from a queue */ + if (sqh->prev) { + + sqh->prev->next = sqh->next; + sqh->prev->qh_link = sqh->qh_link; + + usb_pc_cpu_flush(sqh->prev->page_cache); + + if (sqh->next) { + sqh->next->prev = sqh->prev; + usb_pc_cpu_flush(sqh->next->page_cache); + } + last = ((last == sqh) ? sqh->prev : last); + + sqh->prev = 0; + + usb_pc_cpu_flush(sqh->page_cache); + } + return (last); +} + +static usb_error_t +ehci_non_isoc_done_sub(struct usb_xfer *xfer) +{ + ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); + ehci_qtd_t *td; + ehci_qtd_t *td_alt_next; + uint32_t status; + uint16_t len; + + td = xfer->td_transfer_cache; + td_alt_next = td->alt_next; + + if (xfer->aframes != xfer->nframes) { + usbd_xfer_set_frame_len(xfer, xfer->aframes, 0); + } + while (1) { + + usb_pc_cpu_invalidate(td->page_cache); + status = hc32toh(sc, td->qtd_status); + + len = EHCI_QTD_GET_BYTES(status); + + /* + * Verify the status length and + * add the length to "frlengths[]": + */ + if (len > td->len) { + /* should not happen */ + DPRINTF("Invalid status length, " + "0x%04x/0x%04x bytes\n", len, td->len); + status |= EHCI_QTD_HALTED; + } else if (xfer->aframes != xfer->nframes) { + xfer->frlengths[xfer->aframes] += td->len - len; + } + /* Check for last transfer */ + if (((void *)td) == xfer->td_transfer_last) { + td = NULL; + break; + } + /* Check for transfer error */ + if (status & EHCI_QTD_HALTED) { + /* the transfer is finished */ + td = NULL; + break; + } + /* Check for short transfer */ + if (len > 0) { + 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; + +#ifdef USB_DEBUG + if (status & EHCI_QTD_STATERRS) { + DPRINTFN(11, "error, addr=%d, endpt=0x%02x, frame=0x%02x" + "status=%s%s%s%s%s%s%s%s\n", + xfer->address, xfer->endpointno, xfer->aframes, + (status & EHCI_QTD_ACTIVE) ? "[ACTIVE]" : "[NOT_ACTIVE]", + (status & EHCI_QTD_HALTED) ? "[HALTED]" : "", + (status & EHCI_QTD_BUFERR) ? "[BUFERR]" : "", + (status & EHCI_QTD_BABBLE) ? "[BABBLE]" : "", + (status & EHCI_QTD_XACTERR) ? "[XACTERR]" : "", + (status & EHCI_QTD_MISSEDMICRO) ? "[MISSED]" : "", + (status & EHCI_QTD_SPLITXSTATE) ? "[SPLIT]" : "", + (status & EHCI_QTD_PINGSTATE) ? "[PING]" : ""); + } +#endif + + return ((status & EHCI_QTD_HALTED) ? + USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION); +} + +static void +ehci_non_isoc_done(struct usb_xfer *xfer) +{ + ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); + ehci_qh_t *qh; + uint32_t status; + usb_error_t err = 0; + + DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n", + xfer, xfer->endpoint); + +#ifdef USB_DEBUG + if (ehcidebug > 10) { + ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); + + ehci_dump_sqtds(sc, xfer->td_transfer_first); + } +#endif + + /* extract data toggle directly from the QH's overlay area */ + + qh = xfer->qh_start[xfer->flags_int.curr_dma_set]; + + usb_pc_cpu_invalidate(qh->page_cache); + + status = hc32toh(sc, qh->qh_qtd.qtd_status); + + xfer->endpoint->toggle_next = + (status & EHCI_QTD_TOGGLE_MASK) ? 1 : 0; + + /* reset scanner */ + + xfer->td_transfer_cache = xfer->td_transfer_first; + + if (xfer->flags_int.control_xfr) { + + if (xfer->flags_int.control_hdr) { + + err = ehci_non_isoc_done_sub(xfer); + } + xfer->aframes = 1; + + if (xfer->td_transfer_cache == NULL) { + goto done; + } + } + while (xfer->aframes != xfer->nframes) { + + err = ehci_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 = ehci_non_isoc_done_sub(xfer); + } +done: + ehci_device_done(xfer, err); +} + +/*------------------------------------------------------------------------* + * ehci_check_transfer + * + * Return values: + * 0: USB transfer is not finished + * Else: USB transfer is finished + *------------------------------------------------------------------------*/ +static uint8_t +ehci_check_transfer(struct usb_xfer *xfer) +{ + struct usb_pipe_methods *methods = xfer->endpoint->methods; + ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); + + uint32_t status; + + DPRINTFN(13, "xfer=%p checking transfer\n", xfer); + + if (methods == &ehci_device_isoc_fs_methods) { + ehci_sitd_t *td; + + /* isochronous full speed transfer */ + + td = xfer->td_transfer_last; + usb_pc_cpu_invalidate(td->page_cache); + status = hc32toh(sc, td->sitd_status); + + /* also check if first is complete */ + + td = xfer->td_transfer_first; + usb_pc_cpu_invalidate(td->page_cache); + status |= hc32toh(sc, td->sitd_status); + + if (!(status & EHCI_SITD_ACTIVE)) { + ehci_device_done(xfer, USB_ERR_NORMAL_COMPLETION); + goto transferred; + } + } else if (methods == &ehci_device_isoc_hs_methods) { + ehci_itd_t *td; + + /* isochronous high speed transfer */ + + /* check last transfer */ + td = xfer->td_transfer_last; + usb_pc_cpu_invalidate(td->page_cache); + status = td->itd_status[0]; + status |= td->itd_status[1]; + status |= td->itd_status[2]; + status |= td->itd_status[3]; + status |= td->itd_status[4]; + status |= td->itd_status[5]; + status |= td->itd_status[6]; + status |= td->itd_status[7]; + + /* also check first transfer */ + td = xfer->td_transfer_first; + usb_pc_cpu_invalidate(td->page_cache); + status |= td->itd_status[0]; + status |= td->itd_status[1]; + status |= td->itd_status[2]; + status |= td->itd_status[3]; + status |= td->itd_status[4]; + status |= td->itd_status[5]; + status |= td->itd_status[6]; + status |= td->itd_status[7]; + + /* if no transactions are active we continue */ + if (!(status & htohc32(sc, EHCI_ITD_ACTIVE))) { + ehci_device_done(xfer, USB_ERR_NORMAL_COMPLETION); + goto transferred; + } + } else { + ehci_qtd_t *td; + ehci_qh_t *qh; + + /* non-isochronous transfer */ + + /* + * check whether there is an error somewhere in the middle, + * or whether there was a short packet (SPD and not ACTIVE) + */ + td = xfer->td_transfer_cache; + + qh = xfer->qh_start[xfer->flags_int.curr_dma_set]; + + usb_pc_cpu_invalidate(qh->page_cache); + + status = hc32toh(sc, qh->qh_qtd.qtd_status); + if (status & EHCI_QTD_ACTIVE) { + /* transfer is pending */ + goto done; + } + + while (1) { + usb_pc_cpu_invalidate(td->page_cache); + status = hc32toh(sc, td->qtd_status); + + /* + * Check if there is an active TD which + * indicates that the transfer isn't done. + */ + if (status & EHCI_QTD_ACTIVE) { + /* update cache */ + xfer->td_transfer_cache = td; + goto done; + } + /* + * last transfer descriptor makes the transfer done + */ + if (((void *)td) == xfer->td_transfer_last) { + break; + } + /* + * any kind of error makes the transfer done + */ + if (status & EHCI_QTD_HALTED) { + break; + } + /* + * if there is no alternate next transfer, a short + * packet also makes the transfer done + */ + if (EHCI_QTD_GET_BYTES(status)) { + if (xfer->flags_int.short_frames_ok) { + /* follow alt next */ + if (td->alt_next) { + td = td->alt_next; + continue; + } + } + /* transfer is done */ + break; + } + td = td->obj_next; + } + ehci_non_isoc_done(xfer); + goto transferred; + } + +done: + DPRINTFN(13, "xfer=%p is still active\n", xfer); + return (0); + +transferred: + return (1); +} + +static void +ehci_pcd_enable(ehci_softc_t *sc) +{ + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + sc->sc_eintrs |= EHCI_STS_PCD; + EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs); + + /* acknowledge any PCD interrupt */ + EOWRITE4(sc, EHCI_USBSTS, EHCI_STS_PCD); + + ehci_root_intr(sc); +} + +static void +ehci_interrupt_poll(ehci_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 (ehci_check_transfer(xfer)) { + /* queue has been modified */ + goto repeat; + } + } +} + +/* + * Some EHCI chips from VIA / ATI seem to trigger interrupts before + * writing back the qTD status, or miss signalling occasionally under + * heavy load. If the host machine is too fast, we can miss + * transaction completion - when we scan the active list the + * transaction still seems to be active. This generally exhibits + * itself as a umass stall that never recovers. + * + * We work around this behaviour by setting up this callback after any + * softintr that completes with transactions still pending, giving us + * another chance to check for completion after the writeback has + * taken place. + */ +static void +ehci_poll_timeout(void *arg) +{ + ehci_softc_t *sc = arg; + + DPRINTFN(3, "\n"); + ehci_interrupt_poll(sc); +} + +/*------------------------------------------------------------------------* + * ehci_interrupt - EHCI 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 +ehci_interrupt(ehci_softc_t *sc) +{ + uint32_t status; + + USB_BUS_LOCK(&sc->sc_bus); + + DPRINTFN(16, "real interrupt\n"); + +#ifdef USB_DEBUG + if (ehcidebug > 15) { + ehci_dump_regs(sc); + } +#endif + + status = EHCI_STS_INTRS(EOREAD4(sc, EHCI_USBSTS)); + if (status == 0) { + /* the interrupt was not for us */ + goto done; + } + if (!(status & sc->sc_eintrs)) { + goto done; + } + EOWRITE4(sc, EHCI_USBSTS, status); /* acknowledge */ + + status &= sc->sc_eintrs; + + if (status & EHCI_STS_HSE) { + printf("%s: unrecoverable error, " + "controller halted\n", __FUNCTION__); +#ifdef USB_DEBUG + ehci_dump_regs(sc); + ehci_dump_isoc(sc); +#endif + } + if (status & EHCI_STS_PCD) { + /* + * Disable PCD interrupt for now, because it will be + * on until the port has been reset. + */ + sc->sc_eintrs &= ~EHCI_STS_PCD; + EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs); + + ehci_root_intr(sc); + + /* do not allow RHSC interrupts > 1 per second */ + usb_callout_reset(&sc->sc_tmo_pcd, hz, + (void *)&ehci_pcd_enable, sc); + } + status &= ~(EHCI_STS_INT | EHCI_STS_ERRINT | EHCI_STS_PCD | EHCI_STS_IAA); + + if (status != 0) { + /* block unprocessed interrupts */ + sc->sc_eintrs &= ~status; + EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs); + printf("%s: blocking interrupts 0x%x\n", __FUNCTION__, status); + } + /* poll all the USB transfers */ + ehci_interrupt_poll(sc); + + if (sc->sc_flags & EHCI_SCFLG_LOSTINTRBUG) { + usb_callout_reset(&sc->sc_tmo_poll, hz / 128, + (void *)&ehci_poll_timeout, sc); + } + +done: + USB_BUS_UNLOCK(&sc->sc_bus); +} + +/* + * called when a request does not complete + */ +static void +ehci_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 */ + ehci_device_done(xfer, USB_ERR_TIMEOUT); +} + +static void +ehci_do_poll(struct usb_bus *bus) +{ + ehci_softc_t *sc = EHCI_BUS2SC(bus); + + USB_BUS_LOCK(&sc->sc_bus); + ehci_interrupt_poll(sc); + USB_BUS_UNLOCK(&sc->sc_bus); +} + +static void +ehci_setup_standard_chain_sub(struct ehci_std_temp *temp) +{ + struct usb_page_search buf_res; + ehci_qtd_t *td; + ehci_qtd_t *td_next; + ehci_qtd_t *td_alt_next; + uint32_t buf_offset; + uint32_t average; + uint32_t len_old; + uint32_t terminate; + uint32_t qtd_altnext; + uint8_t shortpkt_old; + uint8_t precompute; + + terminate = temp->sc->sc_terminate_self; + qtd_altnext = temp->sc->sc_terminate_self; + td_alt_next = NULL; + buf_offset = 0; + shortpkt_old = temp->shortpkt; + len_old = temp->len; + precompute = 1; + +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 EHCI 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->qtd_status = + temp->qtd_status | + htohc32(temp->sc, EHCI_QTD_IOC | + EHCI_QTD_SET_BYTES(average)); + + if (average == 0) { + + if (temp->auto_data_toggle == 0) { + + /* update data toggle, ZLP case */ + + temp->qtd_status ^= + htohc32(temp->sc, EHCI_QTD_TOGGLE_MASK); + } + td->len = 0; + + td->qtd_buffer[0] = 0; + td->qtd_buffer_hi[0] = 0; + + td->qtd_buffer[1] = 0; + td->qtd_buffer_hi[1] = 0; + + } else { + + uint8_t x; + + if (temp->auto_data_toggle == 0) { + + /* update data toggle */ + + if (((average + temp->max_frame_size - 1) / + temp->max_frame_size) & 1) { + temp->qtd_status ^= + htohc32(temp->sc, EHCI_QTD_TOGGLE_MASK); + } + } + td->len = average; + + /* update remaining length */ + + temp->len -= average; + + /* fill out buffer pointers */ + + usbd_get_page(temp->pc, buf_offset, &buf_res); + td->qtd_buffer[0] = + htohc32(temp->sc, buf_res.physaddr); + td->qtd_buffer_hi[0] = 0; + + x = 1; + + while (average > EHCI_PAGE_SIZE) { + average -= EHCI_PAGE_SIZE; + buf_offset += EHCI_PAGE_SIZE; + usbd_get_page(temp->pc, buf_offset, &buf_res); + td->qtd_buffer[x] = + htohc32(temp->sc, + buf_res.physaddr & (~0xFFF)); + td->qtd_buffer_hi[x] = 0; + x++; + } + + /* + * NOTE: The "average" variable is never zero after + * exiting the loop above ! + * + * NOTE: We have to subtract one from the offset to + * ensure that we are computing the physical address + * of a valid page ! + */ + buf_offset += average; + usbd_get_page(temp->pc, buf_offset - 1, &buf_res); + td->qtd_buffer[x] = + htohc32(temp->sc, + buf_res.physaddr & (~0xFFF)); + td->qtd_buffer_hi[x] = 0; + } + + if (td_next) { + /* link the current TD with the next one */ + td->qtd_next = td_next->qtd_self; + } + td->qtd_altnext = qtd_altnext; + 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) { + td_alt_next = NULL; + qtd_altnext = terminate; + } else { + /* we use this field internally */ + td_alt_next = td_next; + if (temp->setup_alt_next) { + qtd_altnext = td_next->qtd_self; + } else { + qtd_altnext = terminate; + } + } + + /* restore */ + temp->shortpkt = shortpkt_old; + temp->len = len_old; + goto restart; + } + temp->td = td; + temp->td_next = td_next; +} + +static void +ehci_setup_standard_chain(struct usb_xfer *xfer, ehci_qh_t **qh_last) +{ + struct ehci_std_temp temp; + struct usb_pipe_methods *methods; + ehci_qh_t *qh; + ehci_qtd_t *td; + uint32_t qh_endp; + uint32_t qh_endphub; + 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; + temp.sc = EHCI_BUS2SC(xfer->xroot->bus); + + /* 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.qtd_status = 0; + temp.last_frame = 0; + temp.setup_alt_next = xfer->flags_int.short_frames_ok; + + if (xfer->flags_int.control_xfr) { + if (xfer->endpoint->toggle_next) { + /* DATA1 is next */ + temp.qtd_status |= + htohc32(temp.sc, EHCI_QTD_SET_TOGGLE(1)); + } + temp.auto_data_toggle = 0; + } else { + temp.auto_data_toggle = 1; + } + + if ((xfer->xroot->udev->parent_hs_hub != NULL) || + (xfer->xroot->udev->address != 0)) { + /* max 3 retries */ + temp.qtd_status |= + htohc32(temp.sc, EHCI_QTD_SET_CERR(3)); + } + /* check if we should prepend a setup message */ + + if (xfer->flags_int.control_xfr) { + if (xfer->flags_int.control_hdr) { + + temp.qtd_status &= + htohc32(temp.sc, EHCI_QTD_SET_CERR(3)); + temp.qtd_status |= htohc32(temp.sc, + EHCI_QTD_ACTIVE | + EHCI_QTD_SET_PID(EHCI_QTD_PID_SETUP) | + EHCI_QTD_SET_TOGGLE(0)); + + 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; + } + } + ehci_setup_standard_chain_sub(&temp); + } + x = 1; + } else { + x = 0; + } + + 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; + } + } + /* keep previous data toggle and error count */ + + temp.qtd_status &= + htohc32(temp.sc, EHCI_QTD_SET_CERR(3) | + EHCI_QTD_SET_TOGGLE(1)); + + 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; + } + + /* set endpoint direction */ + + temp.qtd_status |= + (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN) ? + htohc32(temp.sc, EHCI_QTD_ACTIVE | + EHCI_QTD_SET_PID(EHCI_QTD_PID_IN)) : + htohc32(temp.sc, EHCI_QTD_ACTIVE | + EHCI_QTD_SET_PID(EHCI_QTD_PID_OUT)); + + ehci_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. + */ + + temp.qtd_status &= htohc32(temp.sc, EHCI_QTD_SET_CERR(3) | + EHCI_QTD_SET_TOGGLE(1)); + temp.qtd_status |= + (UE_GET_DIR(xfer->endpointno) == UE_DIR_OUT) ? + htohc32(temp.sc, EHCI_QTD_ACTIVE | + EHCI_QTD_SET_PID(EHCI_QTD_PID_IN) | + EHCI_QTD_SET_TOGGLE(1)) : + htohc32(temp.sc, EHCI_QTD_ACTIVE | + EHCI_QTD_SET_PID(EHCI_QTD_PID_OUT) | + EHCI_QTD_SET_TOGGLE(1)); + + temp.len = 0; + temp.pc = NULL; + temp.shortpkt = 0; + temp.last_frame = 1; + temp.setup_alt_next = 0; + + ehci_setup_standard_chain_sub(&temp); + } + td = temp.td; + + /* the last TD terminates the transfer: */ + td->qtd_next = htohc32(temp.sc, EHCI_LINK_TERMINATE); + td->qtd_altnext = htohc32(temp.sc, EHCI_LINK_TERMINATE); + + usb_pc_cpu_flush(td->page_cache); + + /* must have at least one frame! */ + + xfer->td_transfer_last = td; + +#ifdef USB_DEBUG + if (ehcidebug > 8) { + DPRINTF("nexttog=%d; data before transfer:\n", + xfer->endpoint->toggle_next); + ehci_dump_sqtds(temp.sc, + xfer->td_transfer_first); + } +#endif + + methods = xfer->endpoint->methods; + + qh = xfer->qh_start[xfer->flags_int.curr_dma_set]; + + /* the "qh_link" field is filled when the QH is added */ + + qh_endp = + (EHCI_QH_SET_ADDR(xfer->address) | + EHCI_QH_SET_ENDPT(UE_GET_ADDR(xfer->endpointno)) | + EHCI_QH_SET_MPL(xfer->max_packet_size)); + + if (usbd_get_speed(xfer->xroot->udev) == USB_SPEED_HIGH) { + qh_endp |= EHCI_QH_SET_EPS(EHCI_QH_SPEED_HIGH); + if (methods != &ehci_device_intr_methods) + qh_endp |= EHCI_QH_SET_NRL(8); + } else { + + if (usbd_get_speed(xfer->xroot->udev) == USB_SPEED_FULL) { + qh_endp |= EHCI_QH_SET_EPS(EHCI_QH_SPEED_FULL); + } else { + qh_endp |= EHCI_QH_SET_EPS(EHCI_QH_SPEED_LOW); + } + + if (methods == &ehci_device_ctrl_methods) { + qh_endp |= EHCI_QH_CTL; + } + if (methods != &ehci_device_intr_methods) { + /* Only try one time per microframe! */ + qh_endp |= EHCI_QH_SET_NRL(1); + } + } + + if (temp.auto_data_toggle == 0) { + /* software computes the data toggle */ + qh_endp |= EHCI_QH_DTC; + } + + qh->qh_endp = htohc32(temp.sc, qh_endp); + + qh_endphub = + (EHCI_QH_SET_MULT(xfer->max_packet_count & 3) | + EHCI_QH_SET_CMASK(xfer->endpoint->usb_cmask) | + EHCI_QH_SET_SMASK(xfer->endpoint->usb_smask) | + EHCI_QH_SET_HUBA(xfer->xroot->udev->hs_hub_addr) | + EHCI_QH_SET_PORT(xfer->xroot->udev->hs_port_no)); + + qh->qh_endphub = htohc32(temp.sc, qh_endphub); + qh->qh_curqtd = 0; + + /* fill the overlay qTD */ + + if (temp.auto_data_toggle && xfer->endpoint->toggle_next) { + /* DATA1 is next */ + qh->qh_qtd.qtd_status = htohc32(temp.sc, EHCI_QTD_SET_TOGGLE(1)); + } else { + qh->qh_qtd.qtd_status = 0; + } + + td = xfer->td_transfer_first; + + qh->qh_qtd.qtd_next = td->qtd_self; + qh->qh_qtd.qtd_altnext = + htohc32(temp.sc, EHCI_LINK_TERMINATE); + + usb_pc_cpu_flush(qh->page_cache); + + if (xfer->xroot->udev->flags.self_suspended == 0) { + EHCI_APPEND_QH(qh, *qh_last); + } +} + +static void +ehci_root_intr(ehci_softc_t *sc) +{ + 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)); + + /* 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 (EOREAD4(sc, EHCI_PORTSC(i)) & EHCI_PS_CLEAR) { + 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)); +} + +static void +ehci_isoc_fs_done(ehci_softc_t *sc, struct usb_xfer *xfer) +{ + uint32_t nframes = xfer->nframes; + uint32_t status; + uint32_t *plen = xfer->frlengths; + uint16_t len = 0; + ehci_sitd_t *td = xfer->td_transfer_first; + ehci_sitd_t **pp_last = &sc->sc_isoc_fs_p_last[xfer->qh_pos]; + + DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n", + xfer, xfer->endpoint); + + while (nframes--) { + if (td == NULL) { + panic("%s:%d: out of TD's\n", + __FUNCTION__, __LINE__); + } + if (pp_last >= &sc->sc_isoc_fs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]) { + pp_last = &sc->sc_isoc_fs_p_last[0]; + } +#ifdef USB_DEBUG + if (ehcidebug > 15) { + DPRINTF("isoc FS-TD\n"); + ehci_dump_sitd(sc, td); + } +#endif + usb_pc_cpu_invalidate(td->page_cache); + status = hc32toh(sc, td->sitd_status); + + len = EHCI_SITD_GET_LEN(status); + + DPRINTFN(2, "status=0x%08x, rem=%u\n", status, len); + + if (*plen >= len) { + len = *plen - len; + } else { + len = 0; + } + + *plen = len; + + /* remove FS-TD from schedule */ + EHCI_REMOVE_FS_TD(td, *pp_last); + + pp_last++; + plen++; + td = td->obj_next; + } + + xfer->aframes = xfer->nframes; +} + +static void +ehci_isoc_hs_done(ehci_softc_t *sc, struct usb_xfer *xfer) +{ + uint32_t nframes = xfer->nframes; + uint32_t status; + uint32_t *plen = xfer->frlengths; + uint16_t len = 0; + uint8_t td_no = 0; + ehci_itd_t *td = xfer->td_transfer_first; + ehci_itd_t **pp_last = &sc->sc_isoc_hs_p_last[xfer->qh_pos]; + + DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n", + xfer, xfer->endpoint); + + while (nframes) { + if (td == NULL) { + panic("%s:%d: out of TD's\n", + __FUNCTION__, __LINE__); + } + if (pp_last >= &sc->sc_isoc_hs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]) { + pp_last = &sc->sc_isoc_hs_p_last[0]; + } +#ifdef USB_DEBUG + if (ehcidebug > 15) { + DPRINTF("isoc HS-TD\n"); + ehci_dump_itd(sc, td); + } +#endif + + usb_pc_cpu_invalidate(td->page_cache); + status = hc32toh(sc, td->itd_status[td_no]); + + len = EHCI_ITD_GET_LEN(status); + + DPRINTFN(2, "status=0x%08x, len=%u\n", status, len); + + if (xfer->endpoint->usb_smask & (1 << td_no)) { + + if (*plen >= len) { + /* + * The length is valid. NOTE: The + * complete length is written back + * into the status field, and not the + * remainder like with other transfer + * descriptor types. + */ + } else { + /* Invalid length - truncate */ + len = 0; + } + + *plen = len; + plen++; + nframes--; + } + + td_no++; + + if ((td_no == 8) || (nframes == 0)) { + /* remove HS-TD from schedule */ + EHCI_REMOVE_HS_TD(td, *pp_last); + pp_last++; + + td_no = 0; + td = td->obj_next; + } + } + xfer->aframes = xfer->nframes; +} + +/* NOTE: "done" can be run two times in a row, + * from close and from interrupt + */ +static void +ehci_device_done(struct usb_xfer *xfer, usb_error_t error) +{ + struct usb_pipe_methods *methods = xfer->endpoint->methods; + ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + DPRINTFN(2, "xfer=%p, endpoint=%p, error=%d\n", + xfer, xfer->endpoint, error); + + if ((methods == &ehci_device_bulk_methods) || + (methods == &ehci_device_ctrl_methods)) { +#ifdef USB_DEBUG + if (ehcidebug > 8) { + DPRINTF("nexttog=%d; data after transfer:\n", + xfer->endpoint->toggle_next); + ehci_dump_sqtds(sc, + xfer->td_transfer_first); + } +#endif + + EHCI_REMOVE_QH(xfer->qh_start[xfer->flags_int.curr_dma_set], + sc->sc_async_p_last); + } + if (methods == &ehci_device_intr_methods) { + EHCI_REMOVE_QH(xfer->qh_start[xfer->flags_int.curr_dma_set], + sc->sc_intr_p_last[xfer->qh_pos]); + } + /* + * Only finish isochronous transfers once which will update + * "xfer->frlengths". + */ + if (xfer->td_transfer_first && + xfer->td_transfer_last) { + if (methods == &ehci_device_isoc_fs_methods) { + ehci_isoc_fs_done(sc, xfer); + } + if (methods == &ehci_device_isoc_hs_methods) { + ehci_isoc_hs_done(sc, xfer); + } + xfer->td_transfer_first = NULL; + xfer->td_transfer_last = NULL; + } + /* dequeue transfer and start next transfer */ + usbd_transfer_done(xfer, error); +} + +/*------------------------------------------------------------------------* + * ehci bulk support + *------------------------------------------------------------------------*/ +static void +ehci_device_bulk_open(struct usb_xfer *xfer) +{ + return; +} + +static void +ehci_device_bulk_close(struct usb_xfer *xfer) +{ + ehci_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +ehci_device_bulk_enter(struct usb_xfer *xfer) +{ + return; +} + +static void +ehci_device_bulk_start(struct usb_xfer *xfer) +{ + ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); + uint32_t temp; + + /* setup TD's and QH */ + ehci_setup_standard_chain(xfer, &sc->sc_async_p_last); + + /* put transfer on interrupt queue */ + ehci_transfer_intr_enqueue(xfer); + + /* + * XXX Certain nVidia chipsets choke when using the IAAD + * feature too frequently. + */ + if (sc->sc_flags & EHCI_SCFLG_IAADBUG) + return; + + /* XXX Performance quirk: Some Host Controllers have a too low + * interrupt rate. Issue an IAAD to stimulate the Host + * Controller after queueing the BULK transfer. + */ + temp = EOREAD4(sc, EHCI_USBCMD); + if (!(temp & EHCI_CMD_IAAD)) + EOWRITE4(sc, EHCI_USBCMD, temp | EHCI_CMD_IAAD); +} + +struct usb_pipe_methods ehci_device_bulk_methods = +{ + .open = ehci_device_bulk_open, + .close = ehci_device_bulk_close, + .enter = ehci_device_bulk_enter, + .start = ehci_device_bulk_start, +}; + +/*------------------------------------------------------------------------* + * ehci control support + *------------------------------------------------------------------------*/ +static void +ehci_device_ctrl_open(struct usb_xfer *xfer) +{ + return; +} + +static void +ehci_device_ctrl_close(struct usb_xfer *xfer) +{ + ehci_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +ehci_device_ctrl_enter(struct usb_xfer *xfer) +{ + return; +} + +static void +ehci_device_ctrl_start(struct usb_xfer *xfer) +{ + ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); + + /* setup TD's and QH */ + ehci_setup_standard_chain(xfer, &sc->sc_async_p_last); + + /* put transfer on interrupt queue */ + ehci_transfer_intr_enqueue(xfer); +} + +struct usb_pipe_methods ehci_device_ctrl_methods = +{ + .open = ehci_device_ctrl_open, + .close = ehci_device_ctrl_close, + .enter = ehci_device_ctrl_enter, + .start = ehci_device_ctrl_start, +}; + +/*------------------------------------------------------------------------* + * ehci interrupt support + *------------------------------------------------------------------------*/ +static void +ehci_device_intr_open(struct usb_xfer *xfer) +{ + ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); + uint16_t best; + uint16_t bit; + uint16_t x; + + usb_hs_bandwidth_alloc(xfer); + + /* + * Find the best QH position corresponding to the given interval: + */ + + best = 0; + bit = EHCI_VIRTUAL_FRAMELIST_COUNT / 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 +ehci_device_intr_close(struct usb_xfer *xfer) +{ + ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); + + sc->sc_intr_stat[xfer->qh_pos]--; + + ehci_device_done(xfer, USB_ERR_CANCELLED); + + /* bandwidth must be freed after device done */ + usb_hs_bandwidth_free(xfer); +} + +static void +ehci_device_intr_enter(struct usb_xfer *xfer) +{ + return; +} + +static void +ehci_device_intr_start(struct usb_xfer *xfer) +{ + ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); + + /* setup TD's and QH */ + ehci_setup_standard_chain(xfer, &sc->sc_intr_p_last[xfer->qh_pos]); + + /* put transfer on interrupt queue */ + ehci_transfer_intr_enqueue(xfer); +} + +struct usb_pipe_methods ehci_device_intr_methods = +{ + .open = ehci_device_intr_open, + .close = ehci_device_intr_close, + .enter = ehci_device_intr_enter, + .start = ehci_device_intr_start, +}; + +/*------------------------------------------------------------------------* + * ehci full speed isochronous support + *------------------------------------------------------------------------*/ +static void +ehci_device_isoc_fs_open(struct usb_xfer *xfer) +{ + ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); + ehci_sitd_t *td; + uint32_t sitd_portaddr; + uint8_t ds; + + sitd_portaddr = + EHCI_SITD_SET_ADDR(xfer->address) | + EHCI_SITD_SET_ENDPT(UE_GET_ADDR(xfer->endpointno)) | + EHCI_SITD_SET_HUBA(xfer->xroot->udev->hs_hub_addr) | + EHCI_SITD_SET_PORT(xfer->xroot->udev->hs_port_no); + + if (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN) { + sitd_portaddr |= EHCI_SITD_SET_DIR_IN; + } + sitd_portaddr = htohc32(sc, sitd_portaddr); + + /* initialize all TD's */ + + for (ds = 0; ds != 2; ds++) { + + for (td = xfer->td_start[ds]; td; td = td->obj_next) { + + td->sitd_portaddr = sitd_portaddr; + + /* + * TODO: make some kind of automatic + * SMASK/CMASK selection based on micro-frame + * usage + * + * micro-frame usage (8 microframes per 1ms) + */ + td->sitd_back = htohc32(sc, EHCI_LINK_TERMINATE); + + usb_pc_cpu_flush(td->page_cache); + } + } +} + +static void +ehci_device_isoc_fs_close(struct usb_xfer *xfer) +{ + ehci_device_done(xfer, USB_ERR_CANCELLED); +} + +static void +ehci_device_isoc_fs_enter(struct usb_xfer *xfer) +{ + struct usb_page_search buf_res; + ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); + struct usb_fs_isoc_schedule *fss_start; + struct usb_fs_isoc_schedule *fss_end; + struct usb_fs_isoc_schedule *fss; + ehci_sitd_t *td; + ehci_sitd_t *td_last = NULL; + ehci_sitd_t **pp_last; + uint32_t *plen; + uint32_t buf_offset; + uint32_t nframes; + uint32_t temp; + uint32_t sitd_mask; + uint16_t tlen; + uint8_t sa; + uint8_t sb; + uint8_t error; + +#ifdef USB_DEBUG + uint8_t once = 1; + +#endif + + DPRINTFN(6, "xfer=%p next=%d nframes=%d\n", + xfer, xfer->endpoint->isoc_next, xfer->nframes); + + /* get the current frame index */ + + nframes = EOREAD4(sc, EHCI_FRINDEX) / 8; + + /* + * check if the frame index is within the window where the frames + * will be inserted + */ + buf_offset = (nframes - xfer->endpoint->isoc_next) & + (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); + + if ((xfer->endpoint->is_synced == 0) || + (buf_offset < xfer->nframes)) { + /* + * If there is data underflow or the pipe queue is empty we + * schedule the transfer a few frames ahead of the current + * frame position. Else two isochronous transfers might + * overlap. + */ + xfer->endpoint->isoc_next = (nframes + 3) & + (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); + xfer->endpoint->is_synced = 1; + DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next); + } + /* + * compute how many milliseconds the insertion is ahead of the + * current frame position: + */ + buf_offset = (xfer->endpoint->isoc_next - nframes) & + (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); + + /* + * pre-compute when the isochronous transfer will be finished: + */ + xfer->isoc_time_complete = + usbd_fs_isoc_schedule_isoc_time_expand + (xfer->xroot->udev, &fss_start, &fss_end, nframes) + buf_offset + + xfer->nframes; + + /* 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; + + pp_last = &sc->sc_isoc_fs_p_last[xfer->endpoint->isoc_next]; + + /* store starting position */ + + xfer->qh_pos = xfer->endpoint->isoc_next; + + fss = fss_start + (xfer->qh_pos % USB_ISOC_TIME_MAX); + + while (nframes--) { + if (td == NULL) { + panic("%s:%d: out of TD's\n", + __FUNCTION__, __LINE__); + } + if (pp_last >= &sc->sc_isoc_fs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]) { + pp_last = &sc->sc_isoc_fs_p_last[0]; + } + if (fss >= fss_end) { + fss = fss_start; + } + /* reuse sitd_portaddr and sitd_back from last transfer */ + + if (*plen > xfer->max_frame_size) { +#ifdef USB_DEBUG + if (once) { + once = 0; + printf("%s: frame length(%d) exceeds %d " + "bytes (frame truncated)\n", + __FUNCTION__, *plen, + xfer->max_frame_size); + } +#endif + *plen = xfer->max_frame_size; + } + /* + * We currently don't care if the ISOCHRONOUS schedule is + * full! + */ + error = usbd_fs_isoc_schedule_alloc(fss, &sa, *plen); + if (error) { + /* + * The FULL speed schedule is FULL! Set length + * to zero. + */ + *plen = 0; + } + if (*plen) { + /* + * only call "usbd_get_page()" when we have a + * non-zero length + */ + usbd_get_page(xfer->frbuffers, buf_offset, &buf_res); + td->sitd_bp[0] = htohc32(sc, buf_res.physaddr); + buf_offset += *plen; + /* + * NOTE: We need to subtract one from the offset so + * that we are on a valid page! + */ + usbd_get_page(xfer->frbuffers, buf_offset - 1, + &buf_res); + temp = buf_res.physaddr & ~0xFFF; + } else { + td->sitd_bp[0] = 0; + temp = 0; + } + + if (UE_GET_DIR(xfer->endpointno) == UE_DIR_OUT) { + tlen = *plen; + if (tlen <= 188) { + temp |= 1; /* T-count = 1, TP = ALL */ + tlen = 1; + } else { + tlen += 187; + tlen /= 188; + temp |= tlen; /* T-count = [1..6] */ + temp |= 8; /* TP = Begin */ + } + + tlen += sa; + + if (tlen >= 8) { + sb = 0; + } else { + sb = (1 << tlen); + } + + sa = (1 << sa); + sa = (sb - sa) & 0x3F; + sb = 0; + } else { + sb = (-(4 << sa)) & 0xFE; + sa = (1 << sa) & 0x3F; + } + + sitd_mask = (EHCI_SITD_SET_SMASK(sa) | + EHCI_SITD_SET_CMASK(sb)); + + td->sitd_bp[1] = htohc32(sc, temp); + + td->sitd_mask = htohc32(sc, sitd_mask); + + if (nframes == 0) { + td->sitd_status = htohc32(sc, + EHCI_SITD_IOC | + EHCI_SITD_ACTIVE | + EHCI_SITD_SET_LEN(*plen)); + } else { + td->sitd_status = htohc32(sc, + EHCI_SITD_ACTIVE | + EHCI_SITD_SET_LEN(*plen)); + } + usb_pc_cpu_flush(td->page_cache); + +#ifdef USB_DEBUG + if (ehcidebug > 15) { + DPRINTF("FS-TD %d\n", nframes); + ehci_dump_sitd(sc, td); + } +#endif + /* insert TD into schedule */ + EHCI_APPEND_FS_TD(td, *pp_last); + pp_last++; + + plen++; + fss++; + td_last = td; + td = td->obj_next; + } + + xfer->td_transfer_last = td_last; + + /* update isoc_next */ + xfer->endpoint->isoc_next = (pp_last - &sc->sc_isoc_fs_p_last[0]) & + (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); +} + +static void +ehci_device_isoc_fs_start(struct usb_xfer *xfer) +{ + /* put transfer on interrupt queue */ + ehci_transfer_intr_enqueue(xfer); +} + +struct usb_pipe_methods ehci_device_isoc_fs_methods = +{ + .open = ehci_device_isoc_fs_open, + .close = ehci_device_isoc_fs_close, + .enter = ehci_device_isoc_fs_enter, + .start = ehci_device_isoc_fs_start, +}; + +/*------------------------------------------------------------------------* + * ehci high speed isochronous support + *------------------------------------------------------------------------*/ +static void +ehci_device_isoc_hs_open(struct usb_xfer *xfer) +{ + ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); + ehci_itd_t *td; + uint32_t temp; + uint8_t ds; + + usb_hs_bandwidth_alloc(xfer); + + /* initialize all TD's */ + + for (ds = 0; ds != 2; ds++) { + + for (td = xfer->td_start[ds]; td; td = td->obj_next) { + + /* set TD inactive */ + td->itd_status[0] = 0; + td->itd_status[1] = 0; + td->itd_status[2] = 0; + td->itd_status[3] = 0; + td->itd_status[4] = 0; + td->itd_status[5] = 0; + td->itd_status[6] = 0; + td->itd_status[7] = 0; + + /* set endpoint and address */ + td->itd_bp[0] = htohc32(sc, + EHCI_ITD_SET_ADDR(xfer->address) | + EHCI_ITD_SET_ENDPT(UE_GET_ADDR(xfer->endpointno))); + + temp = + EHCI_ITD_SET_MPL(xfer->max_packet_size & 0x7FF); + + /* set direction */ + if (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN) { + temp |= EHCI_ITD_SET_DIR_IN; + } + /* set maximum packet size */ + td->itd_bp[1] = htohc32(sc, temp); + + /* set transfer multiplier */ + td->itd_bp[2] = htohc32(sc, xfer->max_packet_count & 3); + + usb_pc_cpu_flush(td->page_cache); + } + } +} + +static void +ehci_device_isoc_hs_close(struct usb_xfer *xfer) +{ + ehci_device_done(xfer, USB_ERR_CANCELLED); + + /* bandwidth must be freed after device done */ + usb_hs_bandwidth_free(xfer); +} + +static void +ehci_device_isoc_hs_enter(struct usb_xfer *xfer) +{ + struct usb_page_search buf_res; + ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus); + ehci_itd_t *td; + ehci_itd_t *td_last = NULL; + ehci_itd_t **pp_last; + bus_size_t page_addr; + uint32_t *plen; + uint32_t status; + uint32_t buf_offset; + uint32_t nframes; + uint32_t itd_offset[8 + 1]; + uint8_t x; + uint8_t td_no; + uint8_t page_no; + uint8_t shift = usbd_xfer_get_fps_shift(xfer); + +#ifdef USB_DEBUG + uint8_t once = 1; + +#endif + + DPRINTFN(6, "xfer=%p next=%d nframes=%d shift=%d\n", + xfer, xfer->endpoint->isoc_next, xfer->nframes, (int)shift); + + /* get the current frame index */ + + nframes = EOREAD4(sc, EHCI_FRINDEX) / 8; + + /* + * check if the frame index is within the window where the frames + * will be inserted + */ + buf_offset = (nframes - xfer->endpoint->isoc_next) & + (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); + + if ((xfer->endpoint->is_synced == 0) || + (buf_offset < (((xfer->nframes << shift) + 7) / 8))) { + /* + * If there is data underflow or the pipe queue is empty we + * schedule the transfer a few frames ahead of the current + * frame position. Else two isochronous transfers might + * overlap. + */ + xfer->endpoint->isoc_next = (nframes + 3) & + (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); + xfer->endpoint->is_synced = 1; + DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next); + } + /* + * compute how many milliseconds the insertion is ahead of the + * current frame position: + */ + buf_offset = (xfer->endpoint->isoc_next - nframes) & + (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); + + /* + * pre-compute when the isochronous transfer will be finished: + */ + xfer->isoc_time_complete = + usb_isoc_time_expand(&sc->sc_bus, nframes) + buf_offset + + (((xfer->nframes << shift) + 7) / 8); + + /* get the real number of frames */ + + nframes = xfer->nframes; + + buf_offset = 0; + td_no = 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; + + pp_last = &sc->sc_isoc_hs_p_last[xfer->endpoint->isoc_next]; + + /* store starting position */ + + xfer->qh_pos = xfer->endpoint->isoc_next; + + while (nframes) { + if (td == NULL) { + panic("%s:%d: out of TD's\n", + __FUNCTION__, __LINE__); + } + if (pp_last >= &sc->sc_isoc_hs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]) { + pp_last = &sc->sc_isoc_hs_p_last[0]; + } + /* range check */ + if (*plen > xfer->max_frame_size) { +#ifdef USB_DEBUG + if (once) { + once = 0; + printf("%s: frame length(%d) exceeds %d bytes " + "(frame truncated)\n", + __FUNCTION__, *plen, xfer->max_frame_size); + } +#endif + *plen = xfer->max_frame_size; + } + + if (xfer->endpoint->usb_smask & (1 << td_no)) { + status = (EHCI_ITD_SET_LEN(*plen) | + EHCI_ITD_ACTIVE | + EHCI_ITD_SET_PG(0)); + td->itd_status[td_no] = htohc32(sc, status); + itd_offset[td_no] = buf_offset; + buf_offset += *plen; + plen++; + nframes --; + } else { + td->itd_status[td_no] = 0; /* not active */ + itd_offset[td_no] = buf_offset; + } + + td_no++; + + if ((td_no == 8) || (nframes == 0)) { + + /* the rest of the transfers are not active, if any */ + for (x = td_no; x != 8; x++) { + td->itd_status[x] = 0; /* not active */ + } + + /* check if there is any data to be transferred */ + if (itd_offset[0] != buf_offset) { + page_no = 0; + itd_offset[td_no] = buf_offset; + + /* get first page offset */ + usbd_get_page(xfer->frbuffers, itd_offset[0], &buf_res); + /* get page address */ + page_addr = buf_res.physaddr & ~0xFFF; + /* update page address */ + td->itd_bp[0] &= htohc32(sc, 0xFFF); + td->itd_bp[0] |= htohc32(sc, page_addr); + + for (x = 0; x != td_no; x++) { + /* set page number and page offset */ + status = (EHCI_ITD_SET_PG(page_no) | + (buf_res.physaddr & 0xFFF)); + td->itd_status[x] |= htohc32(sc, status); + + /* get next page offset */ + if (itd_offset[x + 1] == buf_offset) { + /* + * We subtract one so that + * we don't go off the last + * page! + */ + usbd_get_page(xfer->frbuffers, buf_offset - 1, &buf_res); + } else { + usbd_get_page(xfer->frbuffers, itd_offset[x + 1], &buf_res); + } + + /* check if we need a new page */ + if ((buf_res.physaddr ^ page_addr) & ~0xFFF) { + /* new page needed */ + page_addr = buf_res.physaddr & ~0xFFF; + if (page_no == 6) { + panic("%s: too many pages\n", __FUNCTION__); + } + page_no++; + /* update page address */ + td->itd_bp[page_no] &= htohc32(sc, 0xFFF); + td->itd_bp[page_no] |= htohc32(sc, page_addr); + } + } + } + /* set IOC bit if we are complete */ + if (nframes == 0) { + td->itd_status[td_no - 1] |= htohc32(sc, EHCI_ITD_IOC); + } + usb_pc_cpu_flush(td->page_cache); +#ifdef USB_DEBUG + if (ehcidebug > 15) { + DPRINTF("HS-TD %d\n", nframes); + ehci_dump_itd(sc, td); + } +#endif + /* insert TD into schedule */ + EHCI_APPEND_HS_TD(td, *pp_last); + pp_last++; + + td_no = 0; + td_last = td; + td = td->obj_next; + } + } + + xfer->td_transfer_last = td_last; + + /* update isoc_next */ + xfer->endpoint->isoc_next = (pp_last - &sc->sc_isoc_hs_p_last[0]) & + (EHCI_VIRTUAL_FRAMELIST_COUNT - 1); +} + +static void +ehci_device_isoc_hs_start(struct usb_xfer *xfer) +{ + /* put transfer on interrupt queue */ + ehci_transfer_intr_enqueue(xfer); +} + +struct usb_pipe_methods ehci_device_isoc_hs_methods = +{ + .open = ehci_device_isoc_hs_open, + .close = ehci_device_isoc_hs_close, + .enter = ehci_device_isoc_hs_enter, + .start = ehci_device_isoc_hs_start, +}; + +/*------------------------------------------------------------------------* + * ehci root control support + *------------------------------------------------------------------------* + * Simulate a hardware hub by handling all the necessary requests. + *------------------------------------------------------------------------*/ + +static const +struct usb_device_descriptor ehci_devd = +{ + sizeof(struct usb_device_descriptor), + UDESC_DEVICE, /* type */ + {0x00, 0x02}, /* USB version */ + UDCLASS_HUB, /* class */ + UDSUBCLASS_HUB, /* subclass */ + UDPROTO_HSHUBSTT, /* protocol */ + 64, /* max packet */ + {0}, {0}, {0x00, 0x01}, /* device id */ + 1, 2, 0, /* string indicies */ + 1 /* # of configurations */ +}; + +static const +struct usb_device_qualifier ehci_odevd = +{ + sizeof(struct usb_device_qualifier), + UDESC_DEVICE_QUALIFIER, /* type */ + {0x00, 0x02}, /* USB version */ + UDCLASS_HUB, /* class */ + UDSUBCLASS_HUB, /* subclass */ + UDPROTO_FSHUB, /* protocol */ + 0, /* max packet */ + 0, /* # of configurations */ + 0 +}; + +static const struct ehci_config_desc ehci_confd = { + .confd = { + .bLength = sizeof(struct usb_config_descriptor), + .bDescriptorType = UDESC_CONFIG, + .wTotalLength[0] = sizeof(ehci_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 | EHCI_INTR_ENDPT, + .bmAttributes = UE_INTERRUPT, + .wMaxPacketSize[0] = 8, /* max packet (63 ports) */ + .bInterval = 255, + }, +}; + +static const +struct usb_hub_descriptor ehci_hubd = +{ + 0, /* dynamic length */ + UDESC_HUB, + 0, + {0, 0}, + 0, + 0, + {0}, +}; + +static void +ehci_disown(ehci_softc_t *sc, uint16_t index, uint8_t lowspeed) +{ + uint32_t port; + uint32_t v; + + DPRINTF("index=%d lowspeed=%d\n", index, lowspeed); + + port = EHCI_PORTSC(index); + v = EOREAD4(sc, port) & ~EHCI_PS_CLEAR; + EOWRITE4(sc, port, v | EHCI_PS_PO); +} + +static usb_error_t +ehci_roothub_exec(struct usb_device *udev, + struct usb_device_request *req, const void **pptr, uint16_t *plength) +{ + ehci_softc_t *sc = EHCI_BUS2SC(udev->bus); + const char *str_ptr; + const void *ptr; + uint32_t port; + uint32_t v; + uint16_t len; + uint16_t i; + uint16_t value; + uint16_t index; + usb_error_t err; + + USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED); + + /* buffer reset */ + ptr = (const void *)&sc->sc_hub_desc; + 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(ehci_devd); + ptr = (const void *)&ehci_devd; + break; + /* + * We can't really operate at another speed, + * but the specification says we need this + * descriptor: + */ + case UDESC_DEVICE_QUALIFIER: + if ((value & 0xff) != 0) { + err = USB_ERR_IOERROR; + goto done; + } + len = sizeof(ehci_odevd); + ptr = (const void *)&ehci_odevd; + break; + + case UDESC_CONFIG: + if ((value & 0xff) != 0) { + err = USB_ERR_IOERROR; + goto done; + } + len = sizeof(ehci_confd); + ptr = (const void *)&ehci_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 = "EHCI 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 >= EHCI_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\n"); + + if ((index < 1) || + (index > sc->sc_noport)) { + err = USB_ERR_IOERROR; + goto done; + } + port = EHCI_PORTSC(index); + v = EOREAD4(sc, port) & ~EHCI_PS_CLEAR; + switch (value) { + case UHF_PORT_ENABLE: + EOWRITE4(sc, port, v & ~EHCI_PS_PE); + break; + case UHF_PORT_SUSPEND: + if ((v & EHCI_PS_SUSP) && (!(v & EHCI_PS_FPR))) { + + /* + * waking up a High Speed device is rather + * complicated if + */ + EOWRITE4(sc, port, v | EHCI_PS_FPR); + } + /* wait 20ms for resume sequence to complete */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 50); + + EOWRITE4(sc, port, v & ~(EHCI_PS_SUSP | + EHCI_PS_FPR | (3 << 10) /* High Speed */ )); + + /* 4ms settle time */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 250); + break; + case UHF_PORT_POWER: + EOWRITE4(sc, port, v & ~EHCI_PS_PP); + break; + case UHF_PORT_TEST: + DPRINTFN(3, "clear port test " + "%d\n", index); + break; + case UHF_PORT_INDICATOR: + DPRINTFN(3, "clear port ind " + "%d\n", index); + EOWRITE4(sc, port, v & ~EHCI_PS_PIC); + break; + case UHF_C_PORT_CONNECTION: + EOWRITE4(sc, port, v | EHCI_PS_CSC); + break; + case UHF_C_PORT_ENABLE: + EOWRITE4(sc, port, v | EHCI_PS_PEC); + break; + case UHF_C_PORT_SUSPEND: + EOWRITE4(sc, port, v | EHCI_PS_SUSP); + break; + case UHF_C_PORT_OVER_CURRENT: + EOWRITE4(sc, port, v | EHCI_PS_OCC); + break; + case UHF_C_PORT_RESET: + sc->sc_isreset = 0; + break; + default: + err = USB_ERR_IOERROR; + goto done; + } + break; + case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE): + if ((value & 0xff) != 0) { + err = USB_ERR_IOERROR; + goto done; + } + v = EREAD4(sc, EHCI_HCSPARAMS); + + sc->sc_hub_desc.hubd = ehci_hubd; + sc->sc_hub_desc.hubd.bNbrPorts = sc->sc_noport; + + if (EHCI_HCS_PPC(v)) + i = UHD_PWR_INDIVIDUAL; + else + i = UHD_PWR_NO_SWITCH; + + if (EHCI_HCS_P_INDICATOR(v)) + i |= UHD_PORT_IND; + + USETW(sc->sc_hub_desc.hubd.wHubCharacteristics, i); + /* XXX can't find out? */ + sc->sc_hub_desc.hubd.bPwrOn2PwrGood = 200; + /* XXX don't know if ports are removable or not */ + 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; + bzero(sc->sc_hub_desc.temp, 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 = EOREAD4(sc, EHCI_PORTSC(index)); + DPRINTFN(9, "port status=0x%04x\n", v); + if (sc->sc_flags & (EHCI_SCFLG_FORCESPEED | EHCI_SCFLG_TT)) { + if ((v & 0xc000000) == 0x8000000) + i = UPS_HIGH_SPEED; + else if ((v & 0xc000000) == 0x4000000) + i = UPS_LOW_SPEED; + else + i = 0; + } else { + i = UPS_HIGH_SPEED; + } + if (v & EHCI_PS_CS) + i |= UPS_CURRENT_CONNECT_STATUS; + if (v & EHCI_PS_PE) + i |= UPS_PORT_ENABLED; + if ((v & EHCI_PS_SUSP) && !(v & EHCI_PS_FPR)) + i |= UPS_SUSPEND; + if (v & EHCI_PS_OCA) + i |= UPS_OVERCURRENT_INDICATOR; + if (v & EHCI_PS_PR) + i |= UPS_RESET; + if (v & EHCI_PS_PP) + i |= UPS_PORT_POWER; + USETW(sc->sc_hub_desc.ps.wPortStatus, i); + i = 0; + if (v & EHCI_PS_CSC) + i |= UPS_C_CONNECT_STATUS; + if (v & EHCI_PS_PEC) + i |= UPS_C_PORT_ENABLED; + if (v & EHCI_PS_OCC) + i |= UPS_C_OVERCURRENT_INDICATOR; + if (v & EHCI_PS_FPR) + i |= UPS_C_SUSPEND; + if (sc->sc_isreset) + i |= UPS_C_PORT_RESET; + USETW(sc->sc_hub_desc.ps.wPortChange, i); + 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 = EHCI_PORTSC(index); + v = EOREAD4(sc, port) & ~EHCI_PS_CLEAR; + switch (value) { + case UHF_PORT_ENABLE: + EOWRITE4(sc, port, v | EHCI_PS_PE); + break; + case UHF_PORT_SUSPEND: + EOWRITE4(sc, port, v | EHCI_PS_SUSP); + break; + case UHF_PORT_RESET: + DPRINTFN(6, "reset port %d\n", index); +#ifdef USB_DEBUG + if (ehcinohighspeed) { + /* + * Connect USB device to companion + * controller. + */ + ehci_disown(sc, index, 1); + break; + } +#endif + if (EHCI_PS_IS_LOWSPEED(v) && + (sc->sc_flags & EHCI_SCFLG_TT) == 0) { + /* Low speed device, give up ownership. */ + ehci_disown(sc, index, 1); + break; + } + /* Start reset sequence. */ + v &= ~(EHCI_PS_PE | EHCI_PS_PR); + EOWRITE4(sc, port, v | EHCI_PS_PR); + + /* Wait for reset to complete. */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, + USB_MS_TO_TICKS(USB_PORT_ROOT_RESET_DELAY)); + + /* Terminate reset sequence. */ + if (!(sc->sc_flags & EHCI_SCFLG_NORESTERM)) + EOWRITE4(sc, port, v); + + /* Wait for HC to complete reset. */ + usb_pause_mtx(&sc->sc_bus.bus_mtx, + USB_MS_TO_TICKS(EHCI_PORT_RESET_COMPLETE)); + + v = EOREAD4(sc, port); + DPRINTF("ehci after reset, status=0x%08x\n", v); + if (v & EHCI_PS_PR) { + device_printf(sc->sc_bus.bdev, + "port reset timeout\n"); + err = USB_ERR_TIMEOUT; + goto done; + } + if (!(v & EHCI_PS_PE) && + (sc->sc_flags & EHCI_SCFLG_TT) == 0) { + /* Not a high speed device, give up ownership.*/ + ehci_disown(sc, index, 0); + break; + } + sc->sc_isreset = 1; + DPRINTF("ehci port %d reset, status = 0x%08x\n", + index, v); + break; + + case UHF_PORT_POWER: + DPRINTFN(3, "set port power %d\n", index); + EOWRITE4(sc, port, v | EHCI_PS_PP); + break; + + case UHF_PORT_TEST: + DPRINTFN(3, "set port test %d\n", index); + break; + + case UHF_PORT_INDICATOR: + DPRINTFN(3, "set port ind %d\n", index); + EOWRITE4(sc, port, v | EHCI_PS_PIC); + break; + + default: + err = USB_ERR_IOERROR; + goto done; + } + break; + case C(UR_CLEAR_TT_BUFFER, UT_WRITE_CLASS_OTHER): + case C(UR_RESET_TT, UT_WRITE_CLASS_OTHER): + case C(UR_GET_TT_STATE, UT_READ_CLASS_OTHER): + case C(UR_STOP_TT, UT_WRITE_CLASS_OTHER): + break; + default: + err = USB_ERR_IOERROR; + goto done; + } +done: + *plength = len; + *pptr = ptr; + return (err); +} + +static void +ehci_xfer_setup(struct usb_setup_params *parm) +{ + struct usb_page_search page_info; + struct usb_page_cache *pc; + ehci_softc_t *sc; + struct usb_xfer *xfer; + void *last_obj; + uint32_t nqtd; + uint32_t nqh; + uint32_t nsitd; + uint32_t nitd; + uint32_t n; + + sc = EHCI_BUS2SC(parm->udev->bus); + xfer = parm->curr_xfer; + + nqtd = 0; + nqh = 0; + nsitd = 0; + nitd = 0; + + /* + * compute maximum number of some structures + */ + if (parm->methods == &ehci_device_ctrl_methods) { + + /* + * The proof for the "nqtd" formula is illustrated like + * this: + * + * +------------------------------------+ + * | | + * | |remainder -> | + * | +-----+---+ | + * | | xxx | x | frm 0 | + * | +-----+---++ | + * | | xxx | xx | frm 1 | + * | +-----+----+ | + * | ... | + * +------------------------------------+ + * + * "xxx" means a completely full USB transfer descriptor + * + * "x" and "xx" means a short USB packet + * + * For the remainder of an USB transfer modulo + * "max_data_length" we need two USB transfer descriptors. + * One to transfer the remaining data and one to finalise + * with a zero length packet in case the "force_short_xfer" + * flag is set. We only need two USB transfer descriptors in + * the case where the transfer length of the first one is a + * factor of "max_frame_size". The rest of the needed USB + * transfer descriptors is given by the buffer size divided + * by the maximum data payload. + */ + parm->hc_max_packet_size = 0x400; + parm->hc_max_packet_count = 1; + parm->hc_max_frame_size = EHCI_QTD_PAYLOAD_MAX; + xfer->flags_int.bdma_enable = 1; + + usbd_transfer_setup_sub(parm); + + nqh = 1; + nqtd = ((2 * xfer->nframes) + 1 /* STATUS */ + + (xfer->max_data_length / xfer->max_hc_frame_size)); + + } else if (parm->methods == &ehci_device_bulk_methods) { + + parm->hc_max_packet_size = 0x400; + parm->hc_max_packet_count = 1; + parm->hc_max_frame_size = EHCI_QTD_PAYLOAD_MAX; + xfer->flags_int.bdma_enable = 1; + + usbd_transfer_setup_sub(parm); + + nqh = 1; + nqtd = ((2 * xfer->nframes) + + (xfer->max_data_length / xfer->max_hc_frame_size)); + + } else if (parm->methods == &ehci_device_intr_methods) { + + if (parm->speed == USB_SPEED_HIGH) { + parm->hc_max_packet_size = 0x400; + parm->hc_max_packet_count = 3; + } else if (parm->speed == USB_SPEED_FULL) { + parm->hc_max_packet_size = USB_FS_BYTES_PER_HS_UFRAME; + parm->hc_max_packet_count = 1; + } else { + parm->hc_max_packet_size = USB_FS_BYTES_PER_HS_UFRAME / 8; + parm->hc_max_packet_count = 1; + } + + parm->hc_max_frame_size = EHCI_QTD_PAYLOAD_MAX; + xfer->flags_int.bdma_enable = 1; + + usbd_transfer_setup_sub(parm); + + nqh = 1; + nqtd = ((2 * xfer->nframes) + + (xfer->max_data_length / xfer->max_hc_frame_size)); + + } else if (parm->methods == &ehci_device_isoc_fs_methods) { + + parm->hc_max_packet_size = 0x3FF; + parm->hc_max_packet_count = 1; + parm->hc_max_frame_size = 0x3FF; + xfer->flags_int.bdma_enable = 1; + + usbd_transfer_setup_sub(parm); + + nsitd = xfer->nframes; + + } else if (parm->methods == &ehci_device_isoc_hs_methods) { + + parm->hc_max_packet_size = 0x400; + parm->hc_max_packet_count = 3; + parm->hc_max_frame_size = 0xC00; + xfer->flags_int.bdma_enable = 1; + + usbd_transfer_setup_sub(parm); + + nitd = ((xfer->nframes + 7) / 8) << + usbd_xfer_get_fps_shift(xfer); + + } else { + + parm->hc_max_packet_size = 0x400; + parm->hc_max_packet_count = 1; + parm->hc_max_frame_size = 0x400; + + usbd_transfer_setup_sub(parm); + } + +alloc_dma_set: + + if (parm->err) { + return; + } + /* + * Allocate queue heads and transfer descriptors + */ + last_obj = NULL; + + if (usbd_transfer_setup_sub_malloc( + parm, &pc, sizeof(ehci_itd_t), + EHCI_ITD_ALIGN, nitd)) { + parm->err = USB_ERR_NOMEM; + return; + } + if (parm->buf) { + for (n = 0; n != nitd; n++) { + ehci_itd_t *td; + + usbd_get_page(pc + n, 0, &page_info); + + td = page_info.buffer; + + /* init TD */ + td->itd_self = htohc32(sc, page_info.physaddr | EHCI_LINK_ITD); + 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(ehci_sitd_t), + EHCI_SITD_ALIGN, nsitd)) { + parm->err = USB_ERR_NOMEM; + return; + } + if (parm->buf) { + for (n = 0; n != nsitd; n++) { + ehci_sitd_t *td; + + usbd_get_page(pc + n, 0, &page_info); + + td = page_info.buffer; + + /* init TD */ + td->sitd_self = htohc32(sc, page_info.physaddr | EHCI_LINK_SITD); + 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(ehci_qtd_t), + EHCI_QTD_ALIGN, nqtd)) { + parm->err = USB_ERR_NOMEM; + return; + } + if (parm->buf) { + for (n = 0; n != nqtd; n++) { + ehci_qtd_t *qtd; + + usbd_get_page(pc + n, 0, &page_info); + + qtd = page_info.buffer; + + /* init TD */ + qtd->qtd_self = htohc32(sc, page_info.physaddr); + qtd->obj_next = last_obj; + qtd->page_cache = pc + n; + + last_obj = qtd; + + 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(ehci_qh_t), + EHCI_QH_ALIGN, nqh)) { + parm->err = USB_ERR_NOMEM; + return; + } + if (parm->buf) { + for (n = 0; n != nqh; n++) { + ehci_qh_t *qh; + + usbd_get_page(pc + n, 0, &page_info); + + qh = page_info.buffer; + + /* init QH */ + qh->qh_self = htohc32(sc, page_info.physaddr | EHCI_LINK_QH); + qh->obj_next = last_obj; + qh->page_cache = pc + n; + + last_obj = qh; + + 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 +ehci_xfer_unsetup(struct usb_xfer *xfer) +{ + return; +} + +static void +ehci_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc, + struct usb_endpoint *ep) +{ + ehci_softc_t *sc = EHCI_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->flags.usb_mode != USB_MODE_HOST) { + /* not supported */ + return; + } + if (udev->device_index != sc->sc_addr) { + + if ((udev->speed != USB_SPEED_HIGH) && + ((udev->hs_hub_addr == 0) || + (udev->hs_port_no == 0) || + (udev->parent_hs_hub == NULL) || + (udev->parent_hs_hub->hub == NULL))) { + /* We need a transaction translator */ + goto done; + } + switch (edesc->bmAttributes & UE_XFERTYPE) { + case UE_CONTROL: + ep->methods = &ehci_device_ctrl_methods; + break; + case UE_INTERRUPT: + ep->methods = &ehci_device_intr_methods; + break; + case UE_ISOCHRONOUS: + if (udev->speed == USB_SPEED_HIGH) { + ep->methods = &ehci_device_isoc_hs_methods; + } else if (udev->speed == USB_SPEED_FULL) { + ep->methods = &ehci_device_isoc_fs_methods; + } + break; + case UE_BULK: + ep->methods = &ehci_device_bulk_methods; + break; + default: + /* do nothing */ + break; + } + } +done: + return; +} + +static void +ehci_get_dma_delay(struct usb_device *udev, uint32_t *pus) +{ + /* + * Wait until the hardware has finished any possible use of + * the transfer descriptor(s) and QH + */ + *pus = (188); /* microseconds */ +} + +static void +ehci_device_resume(struct usb_device *udev) +{ + ehci_softc_t *sc = EHCI_BUS2SC(udev->bus); + struct usb_xfer *xfer; + struct usb_pipe_methods *methods; + + 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; + + if ((methods == &ehci_device_bulk_methods) || + (methods == &ehci_device_ctrl_methods)) { + EHCI_APPEND_QH(xfer->qh_start[xfer->flags_int.curr_dma_set], + sc->sc_async_p_last); + } + if (methods == &ehci_device_intr_methods) { + EHCI_APPEND_QH(xfer->qh_start[xfer->flags_int.curr_dma_set], + sc->sc_intr_p_last[xfer->qh_pos]); + } + } + } + + USB_BUS_UNLOCK(udev->bus); + + return; +} + +static void +ehci_device_suspend(struct usb_device *udev) +{ + ehci_softc_t *sc = EHCI_BUS2SC(udev->bus); + struct usb_xfer *xfer; + struct usb_pipe_methods *methods; + + 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; + + if ((methods == &ehci_device_bulk_methods) || + (methods == &ehci_device_ctrl_methods)) { + EHCI_REMOVE_QH(xfer->qh_start[xfer->flags_int.curr_dma_set], + sc->sc_async_p_last); + } + if (methods == &ehci_device_intr_methods) { + EHCI_REMOVE_QH(xfer->qh_start[xfer->flags_int.curr_dma_set], + sc->sc_intr_p_last[xfer->qh_pos]); + } + } + } + + USB_BUS_UNLOCK(udev->bus); + + return; +} + +static void +ehci_set_hw_power(struct usb_bus *bus) +{ + ehci_softc_t *sc = EHCI_BUS2SC(bus); + uint32_t temp; + uint32_t flags; + + DPRINTF("\n"); + + USB_BUS_LOCK(bus); + + flags = bus->hw_power_state; + + temp = EOREAD4(sc, EHCI_USBCMD); + + temp &= ~(EHCI_CMD_ASE | EHCI_CMD_PSE); + + if (flags & (USB_HW_POWER_CONTROL | + USB_HW_POWER_BULK)) { + DPRINTF("Async is active\n"); + temp |= EHCI_CMD_ASE; + } + if (flags & (USB_HW_POWER_INTERRUPT | + USB_HW_POWER_ISOC)) { + DPRINTF("Periodic is active\n"); + temp |= EHCI_CMD_PSE; + } + EOWRITE4(sc, EHCI_USBCMD, temp); + + USB_BUS_UNLOCK(bus); + + return; +} + +struct usb_bus_methods ehci_bus_methods = +{ + .endpoint_init = ehci_ep_init, + .xfer_setup = ehci_xfer_setup, + .xfer_unsetup = ehci_xfer_unsetup, + .get_dma_delay = ehci_get_dma_delay, + .device_resume = ehci_device_resume, + .device_suspend = ehci_device_suspend, + .set_hw_power = ehci_set_hw_power, + .roothub_exec = ehci_roothub_exec, + .xfer_poll = ehci_do_poll, +}; diff --git a/freebsd/sys/dev/usb/controller/ehci.h b/freebsd/sys/dev/usb/controller/ehci.h new file mode 100644 index 00000000..26ba336e --- /dev/null +++ b/freebsd/sys/dev/usb/controller/ehci.h @@ -0,0 +1,486 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2001 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _EHCI_HH_ +#define _EHCI_HH_ + +#define EHCI_MAX_DEVICES MIN(USB_MAX_DEVICES, 128) + +/* + * Alignment NOTE: structures must be aligned so that the hardware can index + * without performing addition. + */ +#define EHCI_FRAMELIST_ALIGN 0x1000 /* bytes */ +#define EHCI_FRAMELIST_COUNT 1024 /* units */ +#define EHCI_VIRTUAL_FRAMELIST_COUNT 128 /* units */ + +#if ((8*EHCI_VIRTUAL_FRAMELIST_COUNT) < USB_MAX_HS_ISOC_FRAMES_PER_XFER) +#error "maximum number of high-speed isochronous frames is higher than supported!" +#endif + +#if (EHCI_VIRTUAL_FRAMELIST_COUNT < USB_MAX_FS_ISOC_FRAMES_PER_XFER) +#error "maximum number of full-speed isochronous frames is higher than supported!" +#endif + +/* Link types */ +#define EHCI_LINK_TERMINATE 0x00000001 +#define EHCI_LINK_TYPE(x) ((x) & 0x00000006) +#define EHCI_LINK_ITD 0x0 +#define EHCI_LINK_QH 0x2 +#define EHCI_LINK_SITD 0x4 +#define EHCI_LINK_FSTN 0x6 +#define EHCI_LINK_ADDR(x) ((x) &~ 0x1f) + +/* Structures alignment (bytes) */ +#define EHCI_ITD_ALIGN 128 +#define EHCI_SITD_ALIGN 64 +#define EHCI_QTD_ALIGN 64 +#define EHCI_QH_ALIGN 128 +#define EHCI_FSTN_ALIGN 32 +/* Data buffers are divided into one or more pages */ +#define EHCI_PAGE_SIZE 0x1000 +#if ((USB_PAGE_SIZE < EHCI_PAGE_SIZE) || (EHCI_PAGE_SIZE == 0) || \ + (USB_PAGE_SIZE < EHCI_ITD_ALIGN) || (EHCI_ITD_ALIGN == 0) || \ + (USB_PAGE_SIZE < EHCI_SITD_ALIGN) || (EHCI_SITD_ALIGN == 0) || \ + (USB_PAGE_SIZE < EHCI_QTD_ALIGN) || (EHCI_QTD_ALIGN == 0) || \ + (USB_PAGE_SIZE < EHCI_QH_ALIGN) || (EHCI_QH_ALIGN == 0) || \ + (USB_PAGE_SIZE < EHCI_FSTN_ALIGN) || (EHCI_FSTN_ALIGN == 0)) +#error "Invalid USB page size!" +#endif + + +/* + * Isochronous Transfer Descriptor. This descriptor is used for high speed + * transfers only. + */ +struct ehci_itd { + volatile uint32_t itd_next; + volatile uint32_t itd_status[8]; +#define EHCI_ITD_SET_LEN(x) ((x) << 16) +#define EHCI_ITD_GET_LEN(x) (((x) >> 16) & 0xFFF) +#define EHCI_ITD_IOC (1 << 15) +#define EHCI_ITD_SET_PG(x) ((x) << 12) +#define EHCI_ITD_GET_PG(x) (((x) >> 12) & 0x7) +#define EHCI_ITD_SET_OFFS(x) (x) +#define EHCI_ITD_GET_OFFS(x) (((x) >> 0) & 0xFFF) +#define EHCI_ITD_ACTIVE (1 << 31) +#define EHCI_ITD_DATABUFERR (1 << 30) +#define EHCI_ITD_BABBLE (1 << 29) +#define EHCI_ITD_XACTERR (1 << 28) + volatile uint32_t itd_bp[7]; + /* itd_bp[0] */ +#define EHCI_ITD_SET_ADDR(x) (x) +#define EHCI_ITD_GET_ADDR(x) (((x) >> 0) & 0x7F) +#define EHCI_ITD_SET_ENDPT(x) ((x) << 8) +#define EHCI_ITD_GET_ENDPT(x) (((x) >> 8) & 0xF) + /* itd_bp[1] */ +#define EHCI_ITD_SET_DIR_IN (1 << 11) +#define EHCI_ITD_SET_DIR_OUT (0 << 11) +#define EHCI_ITD_SET_MPL(x) (x) +#define EHCI_ITD_GET_MPL(x) (((x) >> 0) & 0x7FF) + volatile uint32_t itd_bp_hi[7]; +/* + * Extra information needed: + */ + uint32_t itd_self; + struct ehci_itd *next; + struct ehci_itd *prev; + struct ehci_itd *obj_next; + struct usb_page_cache *page_cache; +} __aligned(EHCI_ITD_ALIGN); + +typedef struct ehci_itd ehci_itd_t; + +/* + * Split Transaction Isochronous Transfer Descriptor. This descriptor is used + * for full speed transfers only. + */ +struct ehci_sitd { + volatile uint32_t sitd_next; + volatile uint32_t sitd_portaddr; +#define EHCI_SITD_SET_DIR_OUT (0 << 31) +#define EHCI_SITD_SET_DIR_IN (1 << 31) +#define EHCI_SITD_SET_ADDR(x) (x) +#define EHCI_SITD_GET_ADDR(x) ((x) & 0x7F) +#define EHCI_SITD_SET_ENDPT(x) ((x) << 8) +#define EHCI_SITD_GET_ENDPT(x) (((x) >> 8) & 0xF) +#define EHCI_SITD_GET_DIR(x) ((x) >> 31) +#define EHCI_SITD_SET_PORT(x) ((x) << 24) +#define EHCI_SITD_GET_PORT(x) (((x) >> 24) & 0x7F) +#define EHCI_SITD_SET_HUBA(x) ((x) << 16) +#define EHCI_SITD_GET_HUBA(x) (((x) >> 16) & 0x7F) + volatile uint32_t sitd_mask; +#define EHCI_SITD_SET_SMASK(x) (x) +#define EHCI_SITD_SET_CMASK(x) ((x) << 8) + volatile uint32_t sitd_status; +#define EHCI_SITD_COMPLETE_SPLIT (1<<1) +#define EHCI_SITD_START_SPLIT (0<<1) +#define EHCI_SITD_MISSED_MICRO_FRAME (1<<2) +#define EHCI_SITD_XACTERR (1<<3) +#define EHCI_SITD_BABBLE (1<<4) +#define EHCI_SITD_DATABUFERR (1<<5) +#define EHCI_SITD_ERROR (1<<6) +#define EHCI_SITD_ACTIVE (1<<7) +#define EHCI_SITD_IOC (1<<31) +#define EHCI_SITD_SET_LEN(len) ((len)<<16) +#define EHCI_SITD_GET_LEN(x) (((x)>>16) & 0x3FF) + volatile uint32_t sitd_bp[2]; + volatile uint32_t sitd_back; + volatile uint32_t sitd_bp_hi[2]; +/* + * Extra information needed: + */ + uint32_t sitd_self; + struct ehci_sitd *next; + struct ehci_sitd *prev; + struct ehci_sitd *obj_next; + struct usb_page_cache *page_cache; +} __aligned(EHCI_SITD_ALIGN); + +typedef struct ehci_sitd ehci_sitd_t; + +/* Queue Element Transfer Descriptor */ +struct ehci_qtd { + volatile uint32_t qtd_next; + volatile uint32_t qtd_altnext; + volatile uint32_t qtd_status; +#define EHCI_QTD_GET_STATUS(x) (((x) >> 0) & 0xff) +#define EHCI_QTD_SET_STATUS(x) ((x) << 0) +#define EHCI_QTD_ACTIVE 0x80 +#define EHCI_QTD_HALTED 0x40 +#define EHCI_QTD_BUFERR 0x20 +#define EHCI_QTD_BABBLE 0x10 +#define EHCI_QTD_XACTERR 0x08 +#define EHCI_QTD_MISSEDMICRO 0x04 +#define EHCI_QTD_SPLITXSTATE 0x02 +#define EHCI_QTD_PINGSTATE 0x01 +#define EHCI_QTD_STATERRS 0x74 +#define EHCI_QTD_GET_PID(x) (((x) >> 8) & 0x3) +#define EHCI_QTD_SET_PID(x) ((x) << 8) +#define EHCI_QTD_PID_OUT 0x0 +#define EHCI_QTD_PID_IN 0x1 +#define EHCI_QTD_PID_SETUP 0x2 +#define EHCI_QTD_GET_CERR(x) (((x) >> 10) & 0x3) +#define EHCI_QTD_SET_CERR(x) ((x) << 10) +#define EHCI_QTD_GET_C_PAGE(x) (((x) >> 12) & 0x7) +#define EHCI_QTD_SET_C_PAGE(x) ((x) << 12) +#define EHCI_QTD_GET_IOC(x) (((x) >> 15) & 0x1) +#define EHCI_QTD_IOC 0x00008000 +#define EHCI_QTD_GET_BYTES(x) (((x) >> 16) & 0x7fff) +#define EHCI_QTD_SET_BYTES(x) ((x) << 16) +#define EHCI_QTD_GET_TOGGLE(x) (((x) >> 31) & 0x1) +#define EHCI_QTD_SET_TOGGLE(x) ((x) << 31) +#define EHCI_QTD_TOGGLE_MASK 0x80000000 +#define EHCI_QTD_NBUFFERS 5 +#define EHCI_QTD_PAYLOAD_MAX ((EHCI_QTD_NBUFFERS-1)*EHCI_PAGE_SIZE) + volatile uint32_t qtd_buffer[EHCI_QTD_NBUFFERS]; + volatile uint32_t qtd_buffer_hi[EHCI_QTD_NBUFFERS]; +/* + * Extra information needed: + */ + struct ehci_qtd *alt_next; + struct ehci_qtd *obj_next; + struct usb_page_cache *page_cache; + uint32_t qtd_self; + uint16_t len; +} __aligned(EHCI_QTD_ALIGN); + +typedef struct ehci_qtd ehci_qtd_t; + +/* Queue Head Sub Structure */ +struct ehci_qh_sub { + volatile uint32_t qtd_next; + volatile uint32_t qtd_altnext; + volatile uint32_t qtd_status; + volatile uint32_t qtd_buffer[EHCI_QTD_NBUFFERS]; + volatile uint32_t qtd_buffer_hi[EHCI_QTD_NBUFFERS]; +} __aligned(4); + +/* Queue Head */ +struct ehci_qh { + volatile uint32_t qh_link; + volatile uint32_t qh_endp; +#define EHCI_QH_GET_ADDR(x) (((x) >> 0) & 0x7f) /* endpoint addr */ +#define EHCI_QH_SET_ADDR(x) (x) +#define EHCI_QH_ADDRMASK 0x0000007f +#define EHCI_QH_GET_INACT(x) (((x) >> 7) & 0x01) /* inactivate on next */ +#define EHCI_QH_INACT 0x00000080 +#define EHCI_QH_GET_ENDPT(x) (((x) >> 8) & 0x0f) /* endpoint no */ +#define EHCI_QH_SET_ENDPT(x) ((x) << 8) +#define EHCI_QH_GET_EPS(x) (((x) >> 12) & 0x03) /* endpoint speed */ +#define EHCI_QH_SET_EPS(x) ((x) << 12) +#define EHCI_QH_SPEED_FULL 0x0 +#define EHCI_QH_SPEED_LOW 0x1 +#define EHCI_QH_SPEED_HIGH 0x2 +#define EHCI_QH_GET_DTC(x) (((x) >> 14) & 0x01) /* data toggle control */ +#define EHCI_QH_DTC 0x00004000 +#define EHCI_QH_GET_HRECL(x) (((x) >> 15) & 0x01) /* head of reclamation */ +#define EHCI_QH_HRECL 0x00008000 +#define EHCI_QH_GET_MPL(x) (((x) >> 16) & 0x7ff) /* max packet len */ +#define EHCI_QH_SET_MPL(x) ((x) << 16) +#define EHCI_QH_MPLMASK 0x07ff0000 +#define EHCI_QH_GET_CTL(x) (((x) >> 27) & 0x01) /* control endpoint */ +#define EHCI_QH_CTL 0x08000000 +#define EHCI_QH_GET_NRL(x) (((x) >> 28) & 0x0f) /* NAK reload */ +#define EHCI_QH_SET_NRL(x) ((x) << 28) + volatile uint32_t qh_endphub; +#define EHCI_QH_GET_SMASK(x) (((x) >> 0) & 0xff) /* intr sched mask */ +#define EHCI_QH_SET_SMASK(x) ((x) << 0) +#define EHCI_QH_GET_CMASK(x) (((x) >> 8) & 0xff) /* split completion mask */ +#define EHCI_QH_SET_CMASK(x) ((x) << 8) +#define EHCI_QH_GET_HUBA(x) (((x) >> 16) & 0x7f) /* hub address */ +#define EHCI_QH_SET_HUBA(x) ((x) << 16) +#define EHCI_QH_GET_PORT(x) (((x) >> 23) & 0x7f) /* hub port */ +#define EHCI_QH_SET_PORT(x) ((x) << 23) +#define EHCI_QH_GET_MULT(x) (((x) >> 30) & 0x03) /* pipe multiplier */ +#define EHCI_QH_SET_MULT(x) ((x) << 30) + volatile uint32_t qh_curqtd; + struct ehci_qh_sub qh_qtd; +/* + * Extra information needed: + */ + struct ehci_qh *next; + struct ehci_qh *prev; + struct ehci_qh *obj_next; + struct usb_page_cache *page_cache; + uint32_t qh_self; +} __aligned(EHCI_QH_ALIGN); + +typedef struct ehci_qh ehci_qh_t; + +/* Periodic Frame Span Traversal Node */ +struct ehci_fstn { + volatile uint32_t fstn_link; + volatile uint32_t fstn_back; +} __aligned(EHCI_FSTN_ALIGN); + +typedef struct ehci_fstn ehci_fstn_t; + +struct ehci_hw_softc { + struct usb_page_cache pframes_pc; + struct usb_page_cache terminate_pc; + struct usb_page_cache async_start_pc; + struct usb_page_cache intr_start_pc[EHCI_VIRTUAL_FRAMELIST_COUNT]; + struct usb_page_cache isoc_hs_start_pc[EHCI_VIRTUAL_FRAMELIST_COUNT]; + struct usb_page_cache isoc_fs_start_pc[EHCI_VIRTUAL_FRAMELIST_COUNT]; + + struct usb_page pframes_pg; + struct usb_page terminate_pg; + struct usb_page async_start_pg; + struct usb_page intr_start_pg[EHCI_VIRTUAL_FRAMELIST_COUNT]; + struct usb_page isoc_hs_start_pg[EHCI_VIRTUAL_FRAMELIST_COUNT]; + struct usb_page isoc_fs_start_pg[EHCI_VIRTUAL_FRAMELIST_COUNT]; +}; + +struct ehci_config_desc { + struct usb_config_descriptor confd; + struct usb_interface_descriptor ifcd; + struct usb_endpoint_descriptor endpd; +} __packed; + +union ehci_hub_desc { + struct usb_status stat; + struct usb_port_status ps; + struct usb_hub_descriptor hubd; + uint8_t temp[128]; +}; + +typedef struct ehci_softc { + struct ehci_hw_softc sc_hw; + struct usb_bus sc_bus; /* base device */ + struct usb_callout sc_tmo_pcd; + struct usb_callout sc_tmo_poll; + union ehci_hub_desc sc_hub_desc; + + struct usb_device *sc_devices[EHCI_MAX_DEVICES]; + struct resource *sc_io_res; + struct resource *sc_irq_res; + struct ehci_qh *sc_async_p_last; + struct ehci_qh *sc_intr_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]; + struct ehci_sitd *sc_isoc_fs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]; + struct ehci_itd *sc_isoc_hs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]; + void *sc_intr_hdl; + bus_size_t sc_io_size; + bus_space_tag_t sc_io_tag; + bus_space_handle_t sc_io_hdl; + + uint32_t sc_terminate_self; /* TD short packet termination pointer */ + uint32_t sc_eintrs; + uint32_t sc_cmd; /* shadow of cmd register during + * suspend */ + + uint16_t sc_intr_stat[EHCI_VIRTUAL_FRAMELIST_COUNT]; + uint16_t sc_id_vendor; /* vendor ID for root hub */ + uint16_t sc_flags; /* chip specific flags */ +#define EHCI_SCFLG_SETMODE 0x0001 /* set bridge mode again after init */ +#define EHCI_SCFLG_FORCESPEED 0x0002 /* force speed */ +#define EHCI_SCFLG_NORESTERM 0x0004 /* don't terminate reset sequence */ +#define EHCI_SCFLG_BIGEDESC 0x0008 /* big-endian byte order descriptors */ +#define EHCI_SCFLG_BIGEMMIO 0x0010 /* big-endian byte order MMIO */ +#define EHCI_SCFLG_TT 0x0020 /* transaction translator present */ +#define EHCI_SCFLG_LOSTINTRBUG 0x0040 /* workaround for VIA / ATI chipsets */ +#define EHCI_SCFLG_IAADBUG 0x0080 /* workaround for nVidia chipsets */ + + uint8_t sc_offs; /* offset to operational registers */ + uint8_t sc_doorbell_disable; /* set on doorbell failure */ + uint8_t sc_noport; + uint8_t sc_addr; /* device address */ + uint8_t sc_conf; /* device configuration */ + uint8_t sc_isreset; + uint8_t sc_hub_idata[8]; + + char sc_vendor[16]; /* vendor string for root hub */ + +} ehci_softc_t; + +#define EREAD1(sc, a) bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (a)) +#ifndef __rtems__ +#define EREAD2(sc, a) bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (a)) +#define EREAD4(sc, a) bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (a)) +#else /* __rtems__ */ +#define EREAD2(sc, a) le16toh(bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (a))) +#define EREAD4(sc, a) le32toh(bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (a))) +#endif /* __rtems__ */ +#define EWRITE1(sc, a, x) \ + bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (a), (x)) +#ifndef __rtems__ +#define EWRITE2(sc, a, x) \ + bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (a), (x)) +#define EWRITE4(sc, a, x) \ + bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (a), (x)) +#else /* __rtems__ */ +#define EWRITE2(sc, a, x) \ + bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (a), htole16(x)) +#define EWRITE4(sc, a, x) \ + bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (a), htole32(x)) +#endif /* __rtems__ */ +#define EOREAD1(sc, a) \ + bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a)) +#ifndef __rtems__ +#define EOREAD2(sc, a) \ + bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a)) +#define EOREAD4(sc, a) \ + bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a)) +#else /* __rtems__ */ +#define EOREAD2(sc, a) \ + le16toh(bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a))) +#define EOREAD4(sc, a) \ + le32toh(bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a))) +#endif /* __rtems__ */ +#define EOWRITE1(sc, a, x) \ + bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a), (x)) +#ifndef __rtems__ +#define EOWRITE2(sc, a, x) \ + bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a), (x)) +#define EOWRITE4(sc, a, x) \ + bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a), (x)) +#else /* __rtems__ */ +#define EOWRITE2(sc, a, x) \ + bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a), htole16(x)) +#define EOWRITE4(sc, a, x) \ + bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a), htole32(x)) +#endif /* __rtems__ */ + +#ifdef USB_EHCI_BIG_ENDIAN_DESC +/* + * Handle byte order conversion between host and ``host controller''. + * Typically the latter is little-endian but some controllers require + * big-endian in which case we may need to manually swap. + */ +static __inline uint32_t +htohc32(const struct ehci_softc *sc, const uint32_t v) +{ + return sc->sc_flags & EHCI_SCFLG_BIGEDESC ? htobe32(v) : htole32(v); +} + +static __inline uint16_t +htohc16(const struct ehci_softc *sc, const uint16_t v) +{ + return sc->sc_flags & EHCI_SCFLG_BIGEDESC ? htobe16(v) : htole16(v); +} + +static __inline uint32_t +hc32toh(const struct ehci_softc *sc, const uint32_t v) +{ + return sc->sc_flags & EHCI_SCFLG_BIGEDESC ? be32toh(v) : le32toh(v); +} + +static __inline uint16_t +hc16toh(const struct ehci_softc *sc, const uint16_t v) +{ + return sc->sc_flags & EHCI_SCFLG_BIGEDESC ? be16toh(v) : le16toh(v); +} +#else +/* + * Normal little-endian only conversion routines. + */ +static __inline uint32_t +htohc32(const struct ehci_softc *sc, const uint32_t v) +{ + return htole32(v); +} + +static __inline uint16_t +htohc16(const struct ehci_softc *sc, const uint16_t v) +{ + return htole16(v); +} + +static __inline uint32_t +hc32toh(const struct ehci_softc *sc, const uint32_t v) +{ + return le32toh(v); +} + +static __inline uint16_t +hc16toh(const struct ehci_softc *sc, const uint16_t v) +{ + return le16toh(v); +} +#endif + +usb_bus_mem_cb_t ehci_iterate_hw_softc; + +usb_error_t ehci_reset(ehci_softc_t *sc); +usb_error_t ehci_init(ehci_softc_t *sc); +void ehci_detach(struct ehci_softc *sc); +void ehci_suspend(struct ehci_softc *sc); +void ehci_resume(struct ehci_softc *sc); +void ehci_shutdown(ehci_softc_t *sc); +void ehci_interrupt(ehci_softc_t *sc); + +#endif /* _EHCI_HH_ */ diff --git a/freebsd/sys/dev/usb/controller/ehcireg.h b/freebsd/sys/dev/usb/controller/ehcireg.h new file mode 100644 index 00000000..7677dfad --- /dev/null +++ b/freebsd/sys/dev/usb/controller/ehcireg.h @@ -0,0 +1,171 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2001 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _EHCIREG_HH_ +#define _EHCIREG_HH_ + +/* PCI config registers */ +#define PCI_CBMEM 0x10 /* configuration base MEM */ +#define PCI_INTERFACE_EHCI 0x20 +#define PCI_USBREV 0x60 /* RO USB protocol revision */ +#define PCI_USB_REV_MASK 0xff +#define PCI_USB_REV_PRE_1_0 0x00 +#define PCI_USB_REV_1_0 0x10 +#define PCI_USB_REV_1_1 0x11 +#define PCI_USB_REV_2_0 0x20 +#define PCI_EHCI_FLADJ 0x61 /* RW Frame len adj, SOF=59488+6*fladj */ +#define PCI_EHCI_PORTWAKECAP 0x62 /* RW Port wake caps (opt) */ + +/* EHCI Extended Capabilities */ +#define EHCI_EC_LEGSUP 0x01 +#define EHCI_EECP_NEXT(x) (((x) >> 8) & 0xff) +#define EHCI_EECP_ID(x) ((x) & 0xff) + +/* Legacy support extended capability */ +#define EHCI_LEGSUP_BIOS_SEM 0x02 +#define EHCI_LEGSUP_OS_SEM 0x03 +#define EHCI_LEGSUP_USBLEGCTLSTS 0x04 + +/* EHCI capability registers */ +#define EHCI_CAPLEN_HCIVERSION 0x00 /* RO Capability register length + * (least-significant byte) and + * interface version number (two + * most significant) + */ +#define EHCI_CAPLENGTH(x) ((x) & 0xff) +#define EHCI_HCIVERSION(x) (((x) >> 16) & 0xffff) +#define EHCI_HCSPARAMS 0x04 /* RO Structural parameters */ +#define EHCI_HCS_DEBUGPORT(x) (((x) >> 20) & 0xf) +#define EHCI_HCS_P_INDICATOR(x) ((x) & 0x10000) +#define EHCI_HCS_N_CC(x) (((x) >> 12) & 0xf) /* # of companion ctlrs */ +#define EHCI_HCS_N_PCC(x) (((x) >> 8) & 0xf) /* # of ports per comp. */ +#define EHCI_HCS_PPC(x) ((x) & 0x10) /* port power control */ +#define EHCI_HCS_N_PORTS(x) ((x) & 0xf) /* # of ports */ +#define EHCI_HCCPARAMS 0x08 /* RO Capability parameters */ +#define EHCI_HCC_EECP(x) (((x) >> 8) & 0xff) /* extended ports caps */ +#define EHCI_HCC_IST(x) (((x) >> 4) & 0xf) /* isoc sched threshold */ +#define EHCI_HCC_ASPC(x) ((x) & 0x4) /* async sched park cap */ +#define EHCI_HCC_PFLF(x) ((x) & 0x2) /* prog frame list flag */ +#define EHCI_HCC_64BIT(x) ((x) & 0x1) /* 64 bit address cap */ +#define EHCI_HCSP_PORTROUTE 0x0c /* RO Companion port route description */ + +/* EHCI operational registers. Offset given by EHCI_CAPLENGTH register */ +#define EHCI_USBCMD 0x00 /* RO, RW, WO Command register */ +#define EHCI_CMD_ITC_M 0x00ff0000 /* RW interrupt threshold ctrl */ +#define EHCI_CMD_ITC_1 0x00010000 +#define EHCI_CMD_ITC_2 0x00020000 +#define EHCI_CMD_ITC_4 0x00040000 +#define EHCI_CMD_ITC_8 0x00080000 +#define EHCI_CMD_ITC_16 0x00100000 +#define EHCI_CMD_ITC_32 0x00200000 +#define EHCI_CMD_ITC_64 0x00400000 +#define EHCI_CMD_ASPME 0x00000800 /* RW/RO async park enable */ +#define EHCI_CMD_ASPMC 0x00000300 /* RW/RO async park count */ +#define EHCI_CMD_LHCR 0x00000080 /* RW light host ctrl reset */ +#define EHCI_CMD_IAAD 0x00000040 /* RW intr on async adv door + * bell */ +#define EHCI_CMD_ASE 0x00000020 /* RW async sched enable */ +#define EHCI_CMD_PSE 0x00000010 /* RW periodic sched enable */ +#define EHCI_CMD_FLS_M 0x0000000c /* RW/RO frame list size */ +#define EHCI_CMD_FLS(x) (((x) >> 2) & 3) /* RW/RO frame list size */ +#define EHCI_CMD_HCRESET 0x00000002 /* RW reset */ +#define EHCI_CMD_RS 0x00000001 /* RW run/stop */ +#define EHCI_USBSTS 0x04 /* RO, RW, RWC Status register */ +#define EHCI_STS_ASS 0x00008000 /* RO async sched status */ +#define EHCI_STS_PSS 0x00004000 /* RO periodic sched status */ +#define EHCI_STS_REC 0x00002000 /* RO reclamation */ +#define EHCI_STS_HCH 0x00001000 /* RO host controller halted */ +#define EHCI_STS_IAA 0x00000020 /* RWC interrupt on async adv */ +#define EHCI_STS_HSE 0x00000010 /* RWC host system error */ +#define EHCI_STS_FLR 0x00000008 /* RWC frame list rollover */ +#define EHCI_STS_PCD 0x00000004 /* RWC port change detect */ +#define EHCI_STS_ERRINT 0x00000002 /* RWC error interrupt */ +#define EHCI_STS_INT 0x00000001 /* RWC interrupt */ +#define EHCI_STS_INTRS(x) ((x) & 0x3f) + +/* + * NOTE: the doorbell interrupt is enabled, but the doorbell is never + * used! SiS chipsets require this. + */ +#define EHCI_NORMAL_INTRS (EHCI_STS_IAA | EHCI_STS_HSE | \ + EHCI_STS_PCD | EHCI_STS_ERRINT | EHCI_STS_INT) + +#define EHCI_USBINTR 0x08 /* RW Interrupt register */ +#define EHCI_INTR_IAAE 0x00000020 /* interrupt on async advance + * ena */ +#define EHCI_INTR_HSEE 0x00000010 /* host system error ena */ +#define EHCI_INTR_FLRE 0x00000008 /* frame list rollover ena */ +#define EHCI_INTR_PCIE 0x00000004 /* port change ena */ +#define EHCI_INTR_UEIE 0x00000002 /* USB error intr ena */ +#define EHCI_INTR_UIE 0x00000001 /* USB intr ena */ + +#define EHCI_FRINDEX 0x0c /* RW Frame Index register */ + +#define EHCI_CTRLDSSEGMENT 0x10 /* RW Control Data Structure Segment */ + +#define EHCI_PERIODICLISTBASE 0x14 /* RW Periodic List Base */ +#define EHCI_ASYNCLISTADDR 0x18 /* RW Async List Base */ + +#define EHCI_CONFIGFLAG 0x40 /* RW Configure Flag register */ +#define EHCI_CONF_CF 0x00000001 /* RW configure flag */ + +#define EHCI_PORTSC(n) (0x40+(4*(n))) /* RO, RW, RWC Port Status reg */ +#define EHCI_PS_WKOC_E 0x00400000 /* RW wake on over current ena */ +#define EHCI_PS_WKDSCNNT_E 0x00200000 /* RW wake on disconnect ena */ +#define EHCI_PS_WKCNNT_E 0x00100000 /* RW wake on connect ena */ +#define EHCI_PS_PTC 0x000f0000 /* RW port test control */ +#define EHCI_PS_PIC 0x0000c000 /* RW port indicator control */ +#define EHCI_PS_PO 0x00002000 /* RW port owner */ +#define EHCI_PS_PP 0x00001000 /* RW,RO port power */ +#define EHCI_PS_LS 0x00000c00 /* RO line status */ +#define EHCI_PS_IS_LOWSPEED(x) (((x) & EHCI_PS_LS) == 0x00000400) +#define EHCI_PS_PR 0x00000100 /* RW port reset */ +#define EHCI_PS_SUSP 0x00000080 /* RW suspend */ +#define EHCI_PS_FPR 0x00000040 /* RW force port resume */ +#define EHCI_PS_OCC 0x00000020 /* RWC over current change */ +#define EHCI_PS_OCA 0x00000010 /* RO over current active */ +#define EHCI_PS_PEC 0x00000008 /* RWC port enable change */ +#define EHCI_PS_PE 0x00000004 /* RW port enable */ +#define EHCI_PS_CSC 0x00000002 /* RWC connect status change */ +#define EHCI_PS_CS 0x00000001 /* RO connect status */ +#define EHCI_PS_CLEAR (EHCI_PS_OCC | EHCI_PS_PEC | EHCI_PS_CSC) + +#define EHCI_USBMODE 0x68 /* RW USB Device mode register */ +#define EHCI_UM_CM 0x00000003 /* R/WO Controller Mode */ +#define EHCI_UM_CM_IDLE 0x0 /* Idle */ +#define EHCI_UM_CM_HOST 0x3 /* Host Controller */ +#define EHCI_UM_ES 0x00000004 /* R/WO Endian Select */ +#define EHCI_UM_ES_LE 0x0 /* Little-endian byte alignment */ +#define EHCI_UM_ES_BE 0x4 /* Big-endian byte alignment */ +#define EHCI_UM_SDIS 0x00000010 /* R/WO Stream Disable Mode */ + +#define EHCI_PORT_RESET_COMPLETE 2 /* ms */ + +#endif /* _EHCIREG_HH_ */ diff --git a/freebsd/sys/dev/usb/controller/ohci.c b/freebsd/sys/dev/usb/controller/ohci.c new file mode 100644 index 00000000..28261d80 --- /dev/null +++ b/freebsd/sys/dev/usb/controller/ohci.c @@ -0,0 +1,2764 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/*- + * 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. + */ + +#include <freebsd/sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * USB Open Host Controller driver. + * + * OHCI spec: http://www.compaq.com/productinfo/development/openhci.html + * USB spec: http://www.usb.org/developers/docs/usbspec.zip + */ + +#include <freebsd/sys/stdint.h> +#include <freebsd/sys/stddef.h> +#include <freebsd/sys/param.h> +#include <freebsd/sys/queue.h> +#include <freebsd/sys/types.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/bus.h> +#include <freebsd/sys/linker_set.h> +#include <freebsd/sys/module.h> +#include <freebsd/sys/lock.h> +#include <freebsd/sys/mutex.h> +#include <freebsd/sys/condvar.h> +#include <freebsd/sys/sysctl.h> +#include <freebsd/sys/sx.h> +#include <freebsd/sys/unistd.h> +#include <freebsd/sys/callout.h> +#include <freebsd/sys/malloc.h> +#include <freebsd/sys/priv.h> + +#include <freebsd/dev/usb/usb.h> +#include <freebsd/dev/usb/usbdi.h> + +#define USB_DEBUG_VAR ohcidebug + +#include <freebsd/dev/usb/usb_core.h> +#include <freebsd/dev/usb/usb_debug.h> +#include <freebsd/dev/usb/usb_busdma.h> +#include <freebsd/dev/usb/usb_process.h> +#include <freebsd/dev/usb/usb_transfer.h> +#include <freebsd/dev/usb/usb_device.h> +#include <freebsd/dev/usb/usb_hub.h> +#include <freebsd/dev/usb/usb_util.h> + +#include <freebsd/dev/usb/usb_controller.h> +#include <freebsd/dev/usb/usb_bus.h> +#include <freebsd/dev/usb/controller/ohci.h> +#include <freebsd/dev/usb/controller/ohcireg.h> + +#define OHCI_BUS2SC(bus) \ + ((ohci_softc_t *)(((uint8_t *)(bus)) - \ + ((uint8_t *)&(((ohci_softc_t *)0)->sc_bus)))) + +#ifdef USB_DEBUG +static int ohcidebug = 0; + +SYSCTL_NODE(_hw_usb, OID_AUTO, ohci, CTLFLAG_RW, 0, "USB ohci"); +SYSCTL_INT(_hw_usb_ohci, OID_AUTO, debug, CTLFLAG_RW, + &ohcidebug, 0, "ohci debug level"); + +TUNABLE_INT("hw.usb.ohci.debug", &ohcidebug); + +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 + +extern struct usb_bus_methods ohci_bus_methods; +extern struct usb_pipe_methods ohci_device_bulk_methods; +extern struct usb_pipe_methods ohci_device_ctrl_methods; +extern struct usb_pipe_methods ohci_device_intr_methods; +extern 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) +{ + 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 + + /* 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)) { + return (USB_ERR_INVAL); + } else { + /* 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); +} + +/* NOTE: suspend/resume is called from + * interrupt context and cannot sleep! + */ +void +ohci_suspend(ohci_softc_t *sc) +{ + uint32_t ctl; + + USB_BUS_LOCK(&sc->sc_bus); + +#ifdef USB_DEBUG + DPRINTF("\n"); + if (ohcidebug > 2) { + ohci_dumpregs(sc); + } +#endif + + ctl = OREAD4(sc, OHCI_CONTROL) & ~OHCI_HCFS_MASK; + if (sc->sc_control == 0) { + /* + * Preserve register values, in case that APM BIOS + * does not recover them. + */ + sc->sc_control = ctl; + sc->sc_intre = OREAD4(sc, OHCI_INTERRUPT_ENABLE); + } + ctl |= OHCI_HCFS_SUSPEND; + OWRITE4(sc, OHCI_CONTROL, ctl); + + usb_pause_mtx(&sc->sc_bus.bus_mtx, + USB_MS_TO_TICKS(USB_RESUME_WAIT)); + + USB_BUS_UNLOCK(&sc->sc_bus); +} + +void +ohci_resume(ohci_softc_t *sc) +{ + uint32_t ctl; + +#ifdef USB_DEBUG + DPRINTF("\n"); + if (ohcidebug > 2) { + ohci_dumpregs(sc); + } +#endif + /* some broken BIOSes never initialize the Controller chip */ + ohci_controller_init(sc); + + USB_BUS_LOCK(&sc->sc_bus); + if (sc->sc_intre) { + OWRITE4(sc, OHCI_INTERRUPT_ENABLE, + sc->sc_intre & (OHCI_ALL_INTRS | OHCI_MIE)); + } + if (sc->sc_control) + ctl = sc->sc_control; + else + ctl = OREAD4(sc, OHCI_CONTROL); + ctl |= OHCI_HCFS_RESUME; + OWRITE4(sc, OHCI_CONTROL, ctl); + usb_pause_mtx(&sc->sc_bus.bus_mtx, + USB_MS_TO_TICKS(USB_RESUME_DELAY)); + ctl = (ctl & ~OHCI_HCFS_MASK) | OHCI_HCFS_OPERATIONAL; + OWRITE4(sc, OHCI_CONTROL, ctl); + usb_pause_mtx(&sc->sc_bus.bus_mtx, + USB_MS_TO_TICKS(USB_RESUME_RECOVERY)); + sc->sc_control = sc->sc_intre = 0; + + USB_BUS_UNLOCK(&sc->sc_bus); + + /* 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; + 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; + 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) +{ + 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); +} + +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); +} + +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); +} + +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 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 ((xfer->endpoint->is_synced == 0) || + (((nframes - xfer->endpoint->isoc_next) & 0xFFFF) < xfer->nframes) || + (((xfer->endpoint->isoc_next - nframes) & 0xFFFF) >= 128)) { + /* + * If there is data underflow or the pipe queue is empty we + * schedule the transfer a few frames ahead of the current + * frame position. Else two isochronous transfers might + * overlap. + */ + xfer->endpoint->isoc_next = (nframes + 3) & 0xFFFF; + xfer->endpoint->is_synced = 1; + DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next); + } + /* + * compute how many milliseconds the insertion is ahead of the + * current frame position: + */ + buf_offset = ((xfer->endpoint->isoc_next - nframes) & 0xFFFF); + + /* + * pre-compute when the isochronous transfer will be finished: + */ + xfer->isoc_time_complete = + (usb_isoc_time_expand(&sc->sc_bus, nframes) + buf_offset + + xfer->nframes); + + /* 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(xfer->endpoint->isoc_next) | + OHCI_ITD_NOINTR | + OHCI_ITD_SET_FC(ncur)); + + td->frames = ncur; + xfer->endpoint->isoc_next += 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); +} + +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 indicies */ + 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 = +{ + 0, /* dynamic length */ + UDESC_HUB, + 0, + {0, 0}, + 0, + 0, + {0}, +}; + +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; + bzero(sc->sc_hub_desc.temp, 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); + 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; + ohci_softc_t *sc; + struct usb_xfer *xfer; + void *last_obj; + uint32_t ntd; + uint32_t nitd; + uint32_t nqh; + uint32_t n; + + sc = OHCI_BUS2SC(parm->udev->bus); + 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) + + ((xfer->nframes + OHCI_ITD_NOFFSET - 1) / 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->flags.usb_mode != USB_MODE_HOST) { + /* not supported */ + return; + } + 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; + 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; + 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(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; +} + +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, + .roothub_exec = ohci_roothub_exec, + .xfer_poll = ohci_do_poll, +}; diff --git a/freebsd/sys/dev/usb/controller/ohci.h b/freebsd/sys/dev/usb/controller/ohci.h new file mode 100644 index 00000000..1affa420 --- /dev/null +++ b/freebsd/sys/dev/usb/controller/ohci.h @@ -0,0 +1,276 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _OHCI_HH_ +#define _OHCI_HH_ + +#define OHCI_MAX_DEVICES MIN(USB_MAX_DEVICES, 128) + +#define OHCI_NO_INTRS 32 +#define OHCI_HCCA_SIZE 256 + +/* Structures alignment (bytes) */ +#define OHCI_HCCA_ALIGN 256 +#define OHCI_ED_ALIGN 16 +#define OHCI_TD_ALIGN 16 +#define OHCI_ITD_ALIGN 32 + +#define OHCI_PAGE_SIZE 0x1000 +#define OHCI_PAGE(x) ((x) &~ 0xfff) +#define OHCI_PAGE_OFFSET(x) ((x) & 0xfff) +#define OHCI_PAGE_MASK(x) ((x) & 0xfff) + +#if ((USB_PAGE_SIZE < OHCI_ED_ALIGN) || (OHCI_ED_ALIGN == 0) || \ + (USB_PAGE_SIZE < OHCI_TD_ALIGN) || (OHCI_TD_ALIGN == 0) || \ + (USB_PAGE_SIZE < OHCI_ITD_ALIGN) || (OHCI_ITD_ALIGN == 0) || \ + (USB_PAGE_SIZE < OHCI_PAGE_SIZE) || (OHCI_PAGE_SIZE == 0)) +#error "Invalid USB page size!" +#endif + +#define OHCI_VIRTUAL_FRAMELIST_COUNT 128/* dummy */ + +#if (OHCI_VIRTUAL_FRAMELIST_COUNT < USB_MAX_FS_ISOC_FRAMES_PER_XFER) +#error "maximum number of full-speed isochronous frames is higher than supported!" +#endif + +struct ohci_hcca { + volatile uint32_t hcca_interrupt_table[OHCI_NO_INTRS]; + volatile uint32_t hcca_frame_number; + volatile uint32_t hcca_done_head; +#define OHCI_DONE_INTRS 1 +} __aligned(OHCI_HCCA_ALIGN); + +typedef struct ohci_hcca ohci_hcca_t; + +struct ohci_ed { + volatile uint32_t ed_flags; +#define OHCI_ED_GET_FA(s) ((s) & 0x7f) +#define OHCI_ED_ADDRMASK 0x0000007f +#define OHCI_ED_SET_FA(s) (s) +#define OHCI_ED_GET_EN(s) (((s) >> 7) & 0xf) +#define OHCI_ED_SET_EN(s) ((s) << 7) +#define OHCI_ED_DIR_MASK 0x00001800 +#define OHCI_ED_DIR_TD 0x00000000 +#define OHCI_ED_DIR_OUT 0x00000800 +#define OHCI_ED_DIR_IN 0x00001000 +#define OHCI_ED_SPEED 0x00002000 +#define OHCI_ED_SKIP 0x00004000 +#define OHCI_ED_FORMAT_GEN 0x00000000 +#define OHCI_ED_FORMAT_ISO 0x00008000 +#define OHCI_ED_GET_MAXP(s) (((s) >> 16) & 0x07ff) +#define OHCI_ED_SET_MAXP(s) ((s) << 16) +#define OHCI_ED_MAXPMASK (0x7ff << 16) + volatile uint32_t ed_tailp; + volatile uint32_t ed_headp; +#define OHCI_HALTED 0x00000001 +#define OHCI_TOGGLECARRY 0x00000002 +#define OHCI_HEADMASK 0xfffffffc + volatile uint32_t ed_next; +/* + * Extra information needed: + */ + struct ohci_ed *next; + struct ohci_ed *prev; + struct ohci_ed *obj_next; + struct usb_page_cache *page_cache; + uint32_t ed_self; +} __aligned(OHCI_ED_ALIGN); + +typedef struct ohci_ed ohci_ed_t; + +struct ohci_td { + volatile uint32_t td_flags; +#define OHCI_TD_R 0x00040000 /* Buffer Rounding */ +#define OHCI_TD_DP_MASK 0x00180000 /* Direction / PID */ +#define OHCI_TD_SETUP 0x00000000 +#define OHCI_TD_OUT 0x00080000 +#define OHCI_TD_IN 0x00100000 +#define OHCI_TD_GET_DI(x) (((x) >> 21) & 7) /* Delay Interrupt */ +#define OHCI_TD_SET_DI(x) ((x) << 21) +#define OHCI_TD_NOINTR 0x00e00000 +#define OHCI_TD_INTR_MASK 0x00e00000 +#define OHCI_TD_TOGGLE_CARRY 0x00000000 +#define OHCI_TD_TOGGLE_0 0x02000000 +#define OHCI_TD_TOGGLE_1 0x03000000 +#define OHCI_TD_TOGGLE_MASK 0x03000000 +#define OHCI_TD_GET_EC(x) (((x) >> 26) & 3) /* Error Count */ +#define OHCI_TD_GET_CC(x) ((x) >> 28) /* Condition Code */ +#define OHCI_TD_SET_CC(x) ((x) << 28) +#define OHCI_TD_NOCC 0xf0000000 + volatile uint32_t td_cbp; /* Current Buffer Pointer */ + volatile uint32_t td_next; /* Next TD */ +#define OHCI_TD_NEXT_END 0 + volatile uint32_t td_be; /* Buffer End */ +/* + * Extra information needed: + */ + struct ohci_td *obj_next; + struct ohci_td *alt_next; + struct usb_page_cache *page_cache; + uint32_t td_self; + uint16_t len; +} __aligned(OHCI_TD_ALIGN); + +typedef struct ohci_td ohci_td_t; + +struct ohci_itd { + volatile uint32_t itd_flags; +#define OHCI_ITD_GET_SF(x) ((x) & 0x0000ffff) +#define OHCI_ITD_SET_SF(x) ((x) & 0xffff) +#define OHCI_ITD_GET_DI(x) (((x) >> 21) & 7) /* Delay Interrupt */ +#define OHCI_ITD_SET_DI(x) ((x) << 21) +#define OHCI_ITD_NOINTR 0x00e00000 +#define OHCI_ITD_GET_FC(x) ((((x) >> 24) & 7)+1) /* Frame Count */ +#define OHCI_ITD_SET_FC(x) (((x)-1) << 24) +#define OHCI_ITD_GET_CC(x) ((x) >> 28) /* Condition Code */ +#define OHCI_ITD_NOCC 0xf0000000 +#define OHCI_ITD_NOFFSET 8 + volatile uint32_t itd_bp0; /* Buffer Page 0 */ + volatile uint32_t itd_next; /* Next ITD */ + volatile uint32_t itd_be; /* Buffer End */ + volatile uint16_t itd_offset[OHCI_ITD_NOFFSET]; /* Buffer offsets and + * Status */ +#define OHCI_ITD_PAGE_SELECT 0x00001000 +#define OHCI_ITD_MK_OFFS(len) (0xe000 | ((len) & 0x1fff)) +#define OHCI_ITD_PSW_LENGTH(x) ((x) & 0xfff) /* Transfer length */ +#define OHCI_ITD_PSW_GET_CC(x) ((x) >> 12) /* Condition Code */ +/* + * Extra information needed: + */ + struct ohci_itd *obj_next; + struct usb_page_cache *page_cache; + uint32_t itd_self; + uint8_t frames; +} __aligned(OHCI_ITD_ALIGN); + +typedef struct ohci_itd ohci_itd_t; + +#define OHCI_CC_NO_ERROR 0 +#define OHCI_CC_CRC 1 +#define OHCI_CC_BIT_STUFFING 2 +#define OHCI_CC_DATA_TOGGLE_MISMATCH 3 +#define OHCI_CC_STALL 4 +#define OHCI_CC_DEVICE_NOT_RESPONDING 5 +#define OHCI_CC_PID_CHECK_FAILURE 6 +#define OHCI_CC_UNEXPECTED_PID 7 +#define OHCI_CC_DATA_OVERRUN 8 +#define OHCI_CC_DATA_UNDERRUN 9 +#define OHCI_CC_BUFFER_OVERRUN 12 +#define OHCI_CC_BUFFER_UNDERRUN 13 +#define OHCI_CC_NOT_ACCESSED 15 + +/* Some delay needed when changing certain registers. */ +#define OHCI_ENABLE_POWER_DELAY 5 +#define OHCI_READ_DESC_DELAY 5 + +#define OHCI_NO_EDS (2*OHCI_NO_INTRS) + +struct ohci_hw_softc { + struct usb_page_cache hcca_pc; + struct usb_page_cache ctrl_start_pc; + struct usb_page_cache bulk_start_pc; + struct usb_page_cache isoc_start_pc; + struct usb_page_cache intr_start_pc[OHCI_NO_EDS]; + + struct usb_page hcca_pg; + struct usb_page ctrl_start_pg; + struct usb_page bulk_start_pg; + struct usb_page isoc_start_pg; + struct usb_page intr_start_pg[OHCI_NO_EDS]; +}; + +struct ohci_config_desc { + struct usb_config_descriptor confd; + struct usb_interface_descriptor ifcd; + struct usb_endpoint_descriptor endpd; +} __packed; + +union ohci_hub_desc { + struct usb_status stat; + struct usb_port_status ps; + struct usb_hub_descriptor hubd; + uint8_t temp[128]; +}; + +typedef struct ohci_softc { + struct ohci_hw_softc sc_hw; + struct usb_bus sc_bus; /* base device */ + struct usb_callout sc_tmo_rhsc; + union ohci_hub_desc sc_hub_desc; + + struct usb_device *sc_devices[OHCI_MAX_DEVICES]; + struct resource *sc_io_res; + struct resource *sc_irq_res; + struct ohci_hcca *sc_hcca_p; + struct ohci_ed *sc_ctrl_p_last; + struct ohci_ed *sc_bulk_p_last; + struct ohci_ed *sc_isoc_p_last; + struct ohci_ed *sc_intr_p_last[OHCI_NO_EDS]; +#ifndef __rtems__ + void *sc_intr_hdl; +#endif /* __rtems__ */ + device_t sc_dev; + bus_size_t sc_io_size; + bus_space_tag_t sc_io_tag; + bus_space_handle_t sc_io_hdl; + + uint32_t sc_eintrs; /* enabled interrupts */ + uint32_t sc_control; /* Preserved during suspend/standby */ + uint32_t sc_intre; + + uint16_t sc_intr_stat[OHCI_NO_EDS]; + uint16_t sc_id_vendor; + + uint8_t sc_noport; + uint8_t sc_addr; /* device address */ + uint8_t sc_conf; /* device configuration */ + uint8_t sc_hub_idata[32]; + + char sc_vendor[16]; + +} ohci_softc_t; + +usb_bus_mem_cb_t ohci_iterate_hw_softc; + +usb_error_t ohci_init(ohci_softc_t *sc); +void ohci_detach(struct ohci_softc *sc); +void ohci_suspend(ohci_softc_t *sc); +void ohci_resume(ohci_softc_t *sc); +void ohci_interrupt(ohci_softc_t *sc); + +#endif /* _OHCI_HH_ */ diff --git a/freebsd/sys/dev/usb/controller/ohcireg.h b/freebsd/sys/dev/usb/controller/ohcireg.h new file mode 100644 index 00000000..b3acb69b --- /dev/null +++ b/freebsd/sys/dev/usb/controller/ohcireg.h @@ -0,0 +1,131 @@ +/* $FreeBSD$ */ +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _OHCIREG_HH_ +#define _OHCIREG_HH_ + +/* PCI config registers */ +#define PCI_CBMEM 0x10 /* configuration base memory */ +#define PCI_INTERFACE_OHCI 0x10 + +/* OHCI registers */ +#define OHCI_REVISION 0x00 /* OHCI revision */ +#define OHCI_REV_LO(rev) ((rev) & 0xf) +#define OHCI_REV_HI(rev) (((rev)>>4) & 0xf) +#define OHCI_REV_LEGACY(rev) ((rev) & 0x100) +#define OHCI_CONTROL 0x04 +#define OHCI_CBSR_MASK 0x00000003 /* Control/Bulk Service Ratio */ +#define OHCI_RATIO_1_1 0x00000000 +#define OHCI_RATIO_1_2 0x00000001 +#define OHCI_RATIO_1_3 0x00000002 +#define OHCI_RATIO_1_4 0x00000003 +#define OHCI_PLE 0x00000004 /* Periodic List Enable */ +#define OHCI_IE 0x00000008 /* Isochronous Enable */ +#define OHCI_CLE 0x00000010 /* Control List Enable */ +#define OHCI_BLE 0x00000020 /* Bulk List Enable */ +#define OHCI_HCFS_MASK 0x000000c0 /* HostControllerFunctionalStat + * e */ +#define OHCI_HCFS_RESET 0x00000000 +#define OHCI_HCFS_RESUME 0x00000040 +#define OHCI_HCFS_OPERATIONAL 0x00000080 +#define OHCI_HCFS_SUSPEND 0x000000c0 +#define OHCI_IR 0x00000100 /* Interrupt Routing */ +#define OHCI_RWC 0x00000200 /* Remote Wakeup Connected */ +#define OHCI_RWE 0x00000400 /* Remote Wakeup Enabled */ +#define OHCI_COMMAND_STATUS 0x08 +#define OHCI_HCR 0x00000001 /* Host Controller Reset */ +#define OHCI_CLF 0x00000002 /* Control List Filled */ +#define OHCI_BLF 0x00000004 /* Bulk List Filled */ +#define OHCI_OCR 0x00000008 /* Ownership Change Request */ +#define OHCI_SOC_MASK 0x00030000 /* Scheduling Overrun Count */ +#define OHCI_INTERRUPT_STATUS 0x0c +#define OHCI_SO 0x00000001 /* Scheduling Overrun */ +#define OHCI_WDH 0x00000002 /* Writeback Done Head */ +#define OHCI_SF 0x00000004 /* Start of Frame */ +#define OHCI_RD 0x00000008 /* Resume Detected */ +#define OHCI_UE 0x00000010 /* Unrecoverable Error */ +#define OHCI_FNO 0x00000020 /* Frame Number Overflow */ +#define OHCI_RHSC 0x00000040 /* Root Hub Status Change */ +#define OHCI_OC 0x40000000 /* Ownership Change */ +#define OHCI_MIE 0x80000000 /* Master Interrupt Enable */ +#define OHCI_INTERRUPT_ENABLE 0x10 +#define OHCI_INTERRUPT_DISABLE 0x14 +#define OHCI_HCCA 0x18 +#define OHCI_PERIOD_CURRENT_ED 0x1c +#define OHCI_CONTROL_HEAD_ED 0x20 +#define OHCI_CONTROL_CURRENT_ED 0x24 +#define OHCI_BULK_HEAD_ED 0x28 +#define OHCI_BULK_CURRENT_ED 0x2c +#define OHCI_DONE_HEAD 0x30 +#define OHCI_FM_INTERVAL 0x34 +#define OHCI_GET_IVAL(s) ((s) & 0x3fff) +#define OHCI_GET_FSMPS(s) (((s) >> 16) & 0x7fff) +#define OHCI_FIT 0x80000000 +#define OHCI_FM_REMAINING 0x38 +#define OHCI_FM_NUMBER 0x3c +#define OHCI_PERIODIC_START 0x40 +#define OHCI_LS_THRESHOLD 0x44 +#define OHCI_RH_DESCRIPTOR_A 0x48 +#define OHCI_GET_NDP(s) ((s) & 0xff) +#define OHCI_PSM 0x0100 /* Power Switching Mode */ +#define OHCI_NPS 0x0200 /* No Power Switching */ +#define OHCI_DT 0x0400 /* Device Type */ +#define OHCI_OCPM 0x0800 /* Overcurrent Protection Mode */ +#define OHCI_NOCP 0x1000 /* No Overcurrent Protection */ +#define OHCI_GET_POTPGT(s) ((s) >> 24) +#define OHCI_RH_DESCRIPTOR_B 0x4c +#define OHCI_RH_STATUS 0x50 +#define OHCI_LPS 0x00000001 /* Local Power Status */ +#define OHCI_OCI 0x00000002 /* OverCurrent Indicator */ +#define OHCI_DRWE 0x00008000 /* Device Remote Wakeup Enable */ +#define OHCI_LPSC 0x00010000 /* Local Power Status Change */ +#define OHCI_CCIC 0x00020000 /* OverCurrent Indicator + * Change */ +#define OHCI_CRWE 0x80000000 /* Clear Remote Wakeup Enable */ +#define OHCI_RH_PORT_STATUS(n) (0x50 + ((n)*4)) /* 1 based indexing */ + +#define OHCI_LES (OHCI_PLE | OHCI_IE | OHCI_CLE | OHCI_BLE) +#define OHCI_ALL_INTRS (OHCI_SO | OHCI_WDH | OHCI_SF | \ + OHCI_RD | OHCI_UE | OHCI_FNO | \ + OHCI_RHSC | OHCI_OC) +#define OHCI_NORMAL_INTRS (OHCI_WDH | OHCI_RD | OHCI_UE | OHCI_RHSC) + +#define OHCI_FSMPS(i) (((i-210)*6/7) << 16) +#define OHCI_PERIODIC(i) ((i)*9/10) + +#endif /* _OHCIREG_HH_ */ diff --git a/freebsd/sys/dev/usb/controller/usb_controller.c b/freebsd/sys/dev/usb/controller/usb_controller.c new file mode 100644 index 00000000..ee37c265 --- /dev/null +++ b/freebsd/sys/dev/usb/controller/usb_controller.c @@ -0,0 +1,606 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/* $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. + */ + +#include <freebsd/local/opt_ddb.h> + +#include <freebsd/sys/stdint.h> +#include <freebsd/sys/stddef.h> +#include <freebsd/sys/param.h> +#include <freebsd/sys/queue.h> +#include <freebsd/sys/types.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/bus.h> +#include <freebsd/sys/linker_set.h> +#include <freebsd/sys/module.h> +#include <freebsd/sys/lock.h> +#include <freebsd/sys/mutex.h> +#include <freebsd/sys/condvar.h> +#include <freebsd/sys/sysctl.h> +#include <freebsd/sys/sx.h> +#include <freebsd/sys/unistd.h> +#include <freebsd/sys/callout.h> +#include <freebsd/sys/malloc.h> +#include <freebsd/sys/priv.h> + +#include <freebsd/dev/usb/usb.h> +#include <freebsd/dev/usb/usbdi.h> + +#define USB_DEBUG_VAR usb_ctrl_debug + +#include <freebsd/dev/usb/usb_core.h> +#include <freebsd/dev/usb/usb_debug.h> +#include <freebsd/dev/usb/usb_process.h> +#include <freebsd/dev/usb/usb_busdma.h> +#include <freebsd/dev/usb/usb_dynamic.h> +#include <freebsd/dev/usb/usb_device.h> +#include <freebsd/dev/usb/usb_hub.h> + +#include <freebsd/dev/usb/usb_controller.h> +#include <freebsd/dev/usb/usb_bus.h> + +/* function prototypes */ + +static device_probe_t usb_probe; +static device_attach_t usb_attach; +static device_detach_t usb_detach; + +static void usb_attach_sub(device_t, struct usb_bus *); + +/* static variables */ + +#ifdef USB_DEBUG +static int usb_ctrl_debug = 0; + +SYSCTL_NODE(_hw_usb, OID_AUTO, ctrl, CTLFLAG_RW, 0, "USB controller"); +SYSCTL_INT(_hw_usb_ctrl, OID_AUTO, debug, CTLFLAG_RW, &usb_ctrl_debug, 0, + "Debug level"); +#endif + +#ifndef __rtems__ +static int usb_no_boot_wait = 0; +TUNABLE_INT("hw.usb.no_boot_wait", &usb_no_boot_wait); +SYSCTL_INT(_hw_usb, OID_AUTO, no_boot_wait, CTLFLAG_RDTUN, &usb_no_boot_wait, 0, + "No device enumerate waiting at boot."); +#endif /* __rtems__ */ + +static devclass_t usb_devclass; + +static device_method_t usb_methods[] = { + DEVMETHOD(device_probe, usb_probe), + DEVMETHOD(device_attach, usb_attach), + DEVMETHOD(device_detach, usb_detach), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + {0, 0} +}; + +static driver_t usb_driver = { + .name = "usbus", + .methods = usb_methods, + .size = 0, +}; + +/* Host Only Drivers */ +DRIVER_MODULE(usbus, ohci, usb_driver, usb_devclass, 0, 0); +DRIVER_MODULE(usbus, uhci, usb_driver, usb_devclass, 0, 0); +DRIVER_MODULE(usbus, ehci, usb_driver, usb_devclass, 0, 0); +DRIVER_MODULE(usbus, xhci, usb_driver, usb_devclass, 0, 0); + +/* Device Only Drivers */ +DRIVER_MODULE(usbus, at91_udp, usb_driver, usb_devclass, 0, 0); +DRIVER_MODULE(usbus, musbotg, usb_driver, usb_devclass, 0, 0); +DRIVER_MODULE(usbus, uss820, usb_driver, usb_devclass, 0, 0); + +/*------------------------------------------------------------------------* + * usb_probe + * + * This function is called from "{ehci,ohci,uhci}_pci_attach()". + *------------------------------------------------------------------------*/ +static int +usb_probe(device_t dev) +{ + DPRINTF("\n"); + return (0); +} + +static void +usb_root_mount_rel(struct usb_bus *bus) +{ +#ifndef __rtems__ + if (bus->bus_roothold != NULL) { + DPRINTF("Releasing root mount hold %p\n", bus->bus_roothold); + root_mount_rel(bus->bus_roothold); + bus->bus_roothold = NULL; + } +#endif /* __rtems__ */ +} + +/*------------------------------------------------------------------------* + * usb_attach + *------------------------------------------------------------------------*/ +static int +usb_attach(device_t dev) +{ + struct usb_bus *bus = device_get_ivars(dev); + + DPRINTF("\n"); + + if (bus == NULL) { + device_printf(dev, "USB device has no ivars\n"); + return (ENXIO); + } + +#ifndef __rtems__ + if (usb_no_boot_wait == 0) { + /* delay vfs_mountroot until the bus is explored */ + bus->bus_roothold = root_mount_hold(device_get_nameunit(dev)); + } +#endif /* __rtems__ */ + + usb_attach_sub(dev, bus); + + return (0); /* return success */ +} + +/*------------------------------------------------------------------------* + * usb_detach + *------------------------------------------------------------------------*/ +static int +usb_detach(device_t dev) +{ + struct usb_bus *bus = device_get_softc(dev); + + DPRINTF("\n"); + + if (bus == NULL) { + /* was never setup properly */ + return (0); + } + /* Stop power watchdog */ + usb_callout_drain(&bus->power_wdog); + + /* Let the USB explore process detach all devices. */ + usb_root_mount_rel(bus); + + USB_BUS_LOCK(bus); + if (usb_proc_msignal(&bus->explore_proc, + &bus->detach_msg[0], &bus->detach_msg[1])) { + /* ignore */ + } + /* Wait for detach to complete */ + + usb_proc_mwait(&bus->explore_proc, + &bus->detach_msg[0], &bus->detach_msg[1]); + + USB_BUS_UNLOCK(bus); + + /* Get rid of USB callback processes */ + + usb_proc_free(&bus->giant_callback_proc); + usb_proc_free(&bus->non_giant_callback_proc); + + /* Get rid of USB explore process */ + + usb_proc_free(&bus->explore_proc); + + /* Get rid of control transfer process */ + + usb_proc_free(&bus->control_xfer_proc); + + return (0); +} + +/*------------------------------------------------------------------------* + * usb_bus_explore + * + * This function is used to explore the device tree from the root. + *------------------------------------------------------------------------*/ +static void +usb_bus_explore(struct usb_proc_msg *pm) +{ + struct usb_bus *bus; + struct usb_device *udev; + + bus = ((struct usb_bus_msg *)pm)->bus; + udev = bus->devices[USB_ROOT_HUB_ADDR]; + + if (udev && udev->hub) { + + if (bus->do_probe) { + bus->do_probe = 0; + bus->driver_added_refcount++; + } + if (bus->driver_added_refcount == 0) { + /* avoid zero, hence that is memory default */ + bus->driver_added_refcount = 1; + } + +#ifdef DDB + /* + * The following three lines of code are only here to + * recover from DDB: + */ + usb_proc_rewakeup(&bus->control_xfer_proc); + usb_proc_rewakeup(&bus->giant_callback_proc); + usb_proc_rewakeup(&bus->non_giant_callback_proc); +#endif + + USB_BUS_UNLOCK(bus); + +#if USB_HAVE_POWERD + /* + * First update the USB power state! + */ + usb_bus_powerd(bus); +#endif + /* Explore the Root USB HUB. */ + (udev->hub->explore) (udev); + USB_BUS_LOCK(bus); + } + usb_root_mount_rel(bus); +} + +/*------------------------------------------------------------------------* + * usb_bus_detach + * + * This function is used to detach the device tree from the root. + *------------------------------------------------------------------------*/ +static void +usb_bus_detach(struct usb_proc_msg *pm) +{ + struct usb_bus *bus; + struct usb_device *udev; + device_t dev; + + bus = ((struct usb_bus_msg *)pm)->bus; + udev = bus->devices[USB_ROOT_HUB_ADDR]; + dev = bus->bdev; + /* clear the softc */ + device_set_softc(dev, NULL); + USB_BUS_UNLOCK(bus); + + /* detach children first */ + mtx_lock(&Giant); + bus_generic_detach(dev); + mtx_unlock(&Giant); + + /* + * Free USB device and all subdevices, if any. + */ + usb_free_device(udev, 0); + + USB_BUS_LOCK(bus); + /* clear bdev variable last */ + bus->bdev = NULL; +} + +static void +usb_power_wdog(void *arg) +{ + struct usb_bus *bus = arg; + + USB_BUS_LOCK_ASSERT(bus, MA_OWNED); + + usb_callout_reset(&bus->power_wdog, + 4 * hz, usb_power_wdog, arg); + +#ifdef DDB + /* + * The following line of code is only here to recover from + * DDB: + */ + usb_proc_rewakeup(&bus->explore_proc); /* recover from DDB */ +#endif + +#if USB_HAVE_POWERD + USB_BUS_UNLOCK(bus); + + usb_bus_power_update(bus); + + USB_BUS_LOCK(bus); +#endif +} + +/*------------------------------------------------------------------------* + * usb_bus_attach + * + * This function attaches USB in context of the explore thread. + *------------------------------------------------------------------------*/ +static void +usb_bus_attach(struct usb_proc_msg *pm) +{ + struct usb_bus *bus; + struct usb_device *child; + device_t dev; + usb_error_t err; + enum usb_dev_speed speed; + + bus = ((struct usb_bus_msg *)pm)->bus; + dev = bus->bdev; + + DPRINTF("\n"); + + switch (bus->usbrev) { + case USB_REV_1_0: + speed = USB_SPEED_FULL; + device_printf(bus->bdev, "12Mbps Full Speed USB v1.0\n"); + break; + + case USB_REV_1_1: + speed = USB_SPEED_FULL; + device_printf(bus->bdev, "12Mbps Full Speed USB v1.1\n"); + break; + + case USB_REV_2_0: + speed = USB_SPEED_HIGH; + device_printf(bus->bdev, "480Mbps High Speed USB v2.0\n"); + break; + + case USB_REV_2_5: + speed = USB_SPEED_VARIABLE; + device_printf(bus->bdev, "480Mbps Wireless USB v2.5\n"); + break; + + case USB_REV_3_0: + speed = USB_SPEED_SUPER; + device_printf(bus->bdev, "4.8Gbps Super Speed USB v3.0\n"); + break; + + default: + device_printf(bus->bdev, "Unsupported USB revision\n"); + usb_root_mount_rel(bus); + return; + } + + USB_BUS_UNLOCK(bus); + + /* default power_mask value */ + bus->hw_power_state = + USB_HW_POWER_CONTROL | + USB_HW_POWER_BULK | + USB_HW_POWER_INTERRUPT | + USB_HW_POWER_ISOC | + USB_HW_POWER_NON_ROOT_HUB; + + /* make sure power is set at least once */ + + if (bus->methods->set_hw_power != NULL) { + (bus->methods->set_hw_power) (bus); + } + + /* Allocate the Root USB device */ + + child = usb_alloc_device(bus->bdev, bus, NULL, 0, 0, 1, + speed, USB_MODE_HOST); + if (child) { + err = usb_probe_and_attach(child, + USB_IFACE_INDEX_ANY); + if (!err) { + if ((bus->devices[USB_ROOT_HUB_ADDR] == NULL) || + (bus->devices[USB_ROOT_HUB_ADDR]->hub == NULL)) { + err = USB_ERR_NO_ROOT_HUB; + } + } + } else { + err = USB_ERR_NOMEM; + } + + USB_BUS_LOCK(bus); + + if (err) { + device_printf(bus->bdev, "Root HUB problem, error=%s\n", + usbd_errstr(err)); + usb_root_mount_rel(bus); + } + + /* set softc - we are ready */ + device_set_softc(dev, bus); + + /* start watchdog */ + usb_power_wdog(bus); +} + +/*------------------------------------------------------------------------* + * usb_attach_sub + * + * This function creates a thread which runs the USB attach code. + *------------------------------------------------------------------------*/ +static void +usb_attach_sub(device_t dev, struct usb_bus *bus) +{ + const char *pname = device_get_nameunit(dev); + + mtx_lock(&Giant); + if (usb_devclass_ptr == NULL) + usb_devclass_ptr = devclass_find("usbus"); + mtx_unlock(&Giant); + + /* Initialise USB process messages */ + bus->explore_msg[0].hdr.pm_callback = &usb_bus_explore; + bus->explore_msg[0].bus = bus; + bus->explore_msg[1].hdr.pm_callback = &usb_bus_explore; + bus->explore_msg[1].bus = bus; + + bus->detach_msg[0].hdr.pm_callback = &usb_bus_detach; + bus->detach_msg[0].bus = bus; + bus->detach_msg[1].hdr.pm_callback = &usb_bus_detach; + bus->detach_msg[1].bus = bus; + + bus->attach_msg[0].hdr.pm_callback = &usb_bus_attach; + bus->attach_msg[0].bus = bus; + bus->attach_msg[1].hdr.pm_callback = &usb_bus_attach; + bus->attach_msg[1].bus = bus; + + /* Create USB explore and callback processes */ + + if (usb_proc_create(&bus->giant_callback_proc, + &bus->bus_mtx, pname, USB_PRI_MED)) { + printf("WARNING: Creation of USB Giant " + "callback process failed.\n"); + } else if (usb_proc_create(&bus->non_giant_callback_proc, + &bus->bus_mtx, pname, USB_PRI_HIGH)) { + printf("WARNING: Creation of USB non-Giant " + "callback process failed.\n"); + } else if (usb_proc_create(&bus->explore_proc, + &bus->bus_mtx, pname, USB_PRI_MED)) { + printf("WARNING: Creation of USB explore " + "process failed.\n"); + } else if (usb_proc_create(&bus->control_xfer_proc, + &bus->bus_mtx, pname, USB_PRI_MED)) { + printf("WARNING: Creation of USB control transfer " + "process failed.\n"); + } else { + /* Get final attach going */ + USB_BUS_LOCK(bus); + if (usb_proc_msignal(&bus->explore_proc, + &bus->attach_msg[0], &bus->attach_msg[1])) { + /* ignore */ + } + USB_BUS_UNLOCK(bus); + + /* Do initial explore */ + usb_needs_explore(bus, 1); + } +} + +SYSUNINIT(usb_bus_unload, SI_SUB_KLD, SI_ORDER_ANY, usb_bus_unload, NULL); + +/*------------------------------------------------------------------------* + * usb_bus_mem_flush_all_cb + *------------------------------------------------------------------------*/ +#if USB_HAVE_BUSDMA +static void +usb_bus_mem_flush_all_cb(struct usb_bus *bus, struct usb_page_cache *pc, + struct usb_page *pg, usb_size_t size, usb_size_t align) +{ + usb_pc_cpu_flush(pc); +} +#endif + +/*------------------------------------------------------------------------* + * usb_bus_mem_flush_all - factored out code + *------------------------------------------------------------------------*/ +#if USB_HAVE_BUSDMA +void +usb_bus_mem_flush_all(struct usb_bus *bus, usb_bus_mem_cb_t *cb) +{ + if (cb) { + cb(bus, &usb_bus_mem_flush_all_cb); + } +} +#endif + +/*------------------------------------------------------------------------* + * usb_bus_mem_alloc_all_cb + *------------------------------------------------------------------------*/ +#if USB_HAVE_BUSDMA +static void +usb_bus_mem_alloc_all_cb(struct usb_bus *bus, struct usb_page_cache *pc, + struct usb_page *pg, usb_size_t size, usb_size_t align) +{ + /* need to initialize the page cache */ + pc->tag_parent = bus->dma_parent_tag; + + if (usb_pc_alloc_mem(pc, pg, size, align)) { + bus->alloc_failed = 1; + } +} +#endif + +/*------------------------------------------------------------------------* + * usb_bus_mem_alloc_all - factored out code + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +uint8_t +usb_bus_mem_alloc_all(struct usb_bus *bus, bus_dma_tag_t dmat, + usb_bus_mem_cb_t *cb) +{ + bus->alloc_failed = 0; + + mtx_init(&bus->bus_mtx, device_get_nameunit(bus->parent), + NULL, MTX_DEF | MTX_RECURSE); + + usb_callout_init_mtx(&bus->power_wdog, + &bus->bus_mtx, 0); + + TAILQ_INIT(&bus->intr_q.head); + +#if USB_HAVE_BUSDMA + usb_dma_tag_setup(bus->dma_parent_tag, bus->dma_tags, + dmat, &bus->bus_mtx, NULL, 32, USB_BUS_DMA_TAG_MAX); +#endif + if ((bus->devices_max > USB_MAX_DEVICES) || + (bus->devices_max < USB_MIN_DEVICES) || + (bus->devices == NULL)) { + DPRINTFN(0, "Devices field has not been " + "initialised properly\n"); + bus->alloc_failed = 1; /* failure */ + } +#if USB_HAVE_BUSDMA + if (cb) { + cb(bus, &usb_bus_mem_alloc_all_cb); + } +#endif + if (bus->alloc_failed) { + usb_bus_mem_free_all(bus, cb); + } + return (bus->alloc_failed); +} + +/*------------------------------------------------------------------------* + * usb_bus_mem_free_all_cb + *------------------------------------------------------------------------*/ +#if USB_HAVE_BUSDMA +static void +usb_bus_mem_free_all_cb(struct usb_bus *bus, struct usb_page_cache *pc, + struct usb_page *pg, usb_size_t size, usb_size_t align) +{ + usb_pc_free_mem(pc); +} +#endif + +/*------------------------------------------------------------------------* + * usb_bus_mem_free_all - factored out code + *------------------------------------------------------------------------*/ +void +usb_bus_mem_free_all(struct usb_bus *bus, usb_bus_mem_cb_t *cb) +{ +#if USB_HAVE_BUSDMA + if (cb) { + cb(bus, &usb_bus_mem_free_all_cb); + } + usb_dma_tag_unsetup(bus->dma_parent_tag); +#endif + + mtx_destroy(&bus->bus_mtx); +} diff --git a/freebsd/sys/dev/usb/quirk/usb_quirk.c b/freebsd/sys/dev/usb/quirk/usb_quirk.c new file mode 100644 index 00000000..8e597dea --- /dev/null +++ b/freebsd/sys/dev/usb/quirk/usb_quirk.c @@ -0,0 +1,807 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/* $FreeBSD$ */ +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved. + * Copyright (c) 1998 Lennart Augustsson. All rights reserved. + * 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. + */ + +#include <freebsd/sys/stdint.h> +#include <freebsd/sys/stddef.h> +#include <freebsd/sys/param.h> +#include <freebsd/sys/queue.h> +#include <freebsd/sys/types.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/bus.h> +#include <freebsd/sys/linker_set.h> +#include <freebsd/sys/module.h> +#include <freebsd/sys/lock.h> +#include <freebsd/sys/mutex.h> +#include <freebsd/sys/condvar.h> +#include <freebsd/sys/sysctl.h> +#include <freebsd/sys/sx.h> +#include <freebsd/sys/unistd.h> +#include <freebsd/sys/callout.h> +#include <freebsd/sys/malloc.h> +#include <freebsd/sys/priv.h> + +#include <freebsd/dev/usb/usb.h> +#include <freebsd/dev/usb/usb_ioctl.h> +#include <freebsd/dev/usb/usbdi.h> +#include <freebsd/local/usbdevs.h> + +#define USB_DEBUG_VAR usb_debug +#include <freebsd/dev/usb/usb_debug.h> +#include <freebsd/dev/usb/usb_dynamic.h> + +#include <freebsd/dev/usb/quirk/usb_quirk.h> + +MODULE_DEPEND(usb_quirk, usb, 1, 1, 1); +MODULE_VERSION(usb_quirk, 1); + +#define USB_DEV_QUIRKS_MAX 256 +#define USB_SUB_QUIRKS_MAX 8 + +struct usb_quirk_entry { + uint16_t vid; + uint16_t pid; + uint16_t lo_rev; + uint16_t hi_rev; + uint16_t quirks[USB_SUB_QUIRKS_MAX]; +}; + +static struct mtx usb_quirk_mtx; + +#define USB_QUIRK_VP(v,p,l,h,...) \ + { .vid = (v), .pid = (p), .lo_rev = (l), .hi_rev = (h), \ + .quirks = { __VA_ARGS__ } } +#define USB_QUIRK(v,p,l,h,...) \ + USB_QUIRK_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, l, h, __VA_ARGS__) + +static struct usb_quirk_entry usb_quirks[USB_DEV_QUIRKS_MAX] = { + USB_QUIRK(ASUS, LCM, 0x0000, 0xffff, UQ_HID_IGNORE), + USB_QUIRK(INSIDEOUT, EDGEPORT4, 0x094, 0x094, UQ_SWAP_UNICODE), + USB_QUIRK(DALLAS, J6502, 0x0a2, 0x0a2, UQ_BAD_ADC), + USB_QUIRK(DALLAS, J6502, 0x0a2, 0x0a2, UQ_AU_NO_XU), + USB_QUIRK(ALTEC, ADA70, 0x103, 0x103, UQ_BAD_ADC), + USB_QUIRK(ALTEC, ASC495, 0x000, 0x000, UQ_BAD_AUDIO), + USB_QUIRK(QTRONIX, 980N, 0x110, 0x110, UQ_SPUR_BUT_UP), + USB_QUIRK(ALCOR2, KBD_HUB, 0x001, 0x001, UQ_SPUR_BUT_UP), + USB_QUIRK(MCT, HUB0100, 0x102, 0x102, UQ_BUS_POWERED), + USB_QUIRK(MCT, USB232, 0x102, 0x102, UQ_BUS_POWERED), + USB_QUIRK(TI, UTUSB41, 0x110, 0x110, UQ_POWER_CLAIM), + USB_QUIRK(TELEX, MIC1, 0x009, 0x009, UQ_AU_NO_FRAC), + USB_QUIRK(SILICONPORTALS, YAPPHONE, 0x100, 0x100, UQ_AU_INP_ASYNC), + USB_QUIRK(LOGITECH, UN53B, 0x0000, 0xffff, UQ_NO_STRINGS), + USB_QUIRK(ELSA, MODEM1, 0x0000, 0xffff, UQ_CFG_INDEX_1), + + /* + * XXX The following quirks should have a more specific revision + * number: + */ + USB_QUIRK(HP, 895C, 0x0000, 0xffff, UQ_BROKEN_BIDIR), + USB_QUIRK(HP, 880C, 0x0000, 0xffff, UQ_BROKEN_BIDIR), + USB_QUIRK(HP, 815C, 0x0000, 0xffff, UQ_BROKEN_BIDIR), + USB_QUIRK(HP, 810C, 0x0000, 0xffff, UQ_BROKEN_BIDIR), + USB_QUIRK(HP, 830C, 0x0000, 0xffff, UQ_BROKEN_BIDIR), + USB_QUIRK(HP, 1220C, 0x0000, 0xffff, UQ_BROKEN_BIDIR), + USB_QUIRK(XEROX, WCM15, 0x0000, 0xffff, UQ_BROKEN_BIDIR), + /* Devices which should be ignored by uhid */ + USB_QUIRK(APC, UPS, 0x0000, 0xffff, UQ_HID_IGNORE), + USB_QUIRK(BELKIN, F6C550AVR, 0x0000, 0xffff, UQ_HID_IGNORE), + USB_QUIRK(CYBERPOWER, 1500CAVRLCD, 0x0000, 0xffff, UQ_HID_IGNORE), + USB_QUIRK(CYPRESS, SILVERSHIELD, 0x0000, 0xffff, UQ_HID_IGNORE), + USB_QUIRK(DELORME, EARTHMATE, 0x0000, 0xffff, UQ_HID_IGNORE), + USB_QUIRK(ITUNERNET, USBLCD2X20, 0x0000, 0xffff, UQ_HID_IGNORE), + USB_QUIRK(ITUNERNET, USBLCD4X20, 0x0000, 0xffff, UQ_HID_IGNORE), + USB_QUIRK(LIEBERT, POWERSURE_PXT, 0x0000, 0xffff, UQ_HID_IGNORE), + USB_QUIRK(MGE, UPS1, 0x0000, 0xffff, UQ_HID_IGNORE), + USB_QUIRK(MGE, UPS2, 0x0000, 0xffff, UQ_HID_IGNORE), + USB_QUIRK(APPLE, IPHONE, 0x0000, 0xffff, UQ_HID_IGNORE), + USB_QUIRK(APPLE, IPHONE_3G, 0x0000, 0xffff, UQ_HID_IGNORE), + USB_QUIRK(MEGATEC, UPS, 0x0000, 0xffff, UQ_HID_IGNORE), + /* Devices which should be ignored by both ukbd and uhid */ + USB_QUIRK(CYPRESS, WISPY1A, 0x0000, 0xffff, UQ_KBD_IGNORE, UQ_HID_IGNORE), + USB_QUIRK(METAGEEK, WISPY1B, 0x0000, 0xffff, UQ_KBD_IGNORE, UQ_HID_IGNORE), + USB_QUIRK(METAGEEK, WISPY24X, 0x0000, 0xffff, UQ_KBD_IGNORE, UQ_HID_IGNORE), + USB_QUIRK(METAGEEK2, WISPYDBX, 0x0000, 0xffff, UQ_KBD_IGNORE, UQ_HID_IGNORE), + USB_QUIRK(TENX, UAUDIO0, 0x0101, 0x0101, UQ_AUDIO_SWAP_LR), + /* MS keyboards do weird things */ + USB_QUIRK(MICROSOFT, WLINTELLIMOUSE, 0x0000, 0xffff, UQ_MS_LEADING_BYTE), + /* umodem(4) device quirks */ + USB_QUIRK(METRICOM, RICOCHET_GS, 0x100, 0x100, UQ_ASSUME_CM_OVER_DATA), + USB_QUIRK(SANYO, SCP4900, 0x000, 0x000, UQ_ASSUME_CM_OVER_DATA), + USB_QUIRK(MOTOROLA2, T720C, 0x001, 0x001, UQ_ASSUME_CM_OVER_DATA), + USB_QUIRK(EICON, DIVA852, 0x100, 0x100, UQ_ASSUME_CM_OVER_DATA), + USB_QUIRK(SIEMENS2, ES75, 0x000, 0x000, UQ_ASSUME_CM_OVER_DATA), + USB_QUIRK(QUALCOMM, CDMA_MSM, 0x0000, 0xffff, UQ_ASSUME_CM_OVER_DATA), + USB_QUIRK(QUALCOMM2, CDMA_MSM, 0x0000, 0xffff, UQ_ASSUME_CM_OVER_DATA), + USB_QUIRK(CURITEL, UM175, 0x0000, 0xffff, UQ_ASSUME_CM_OVER_DATA), + + /* USB Mass Storage Class Quirks */ + USB_QUIRK_VP(USB_VENDOR_ASAHIOPTICAL, 0, UQ_MSC_NO_RS_CLEAR_UA, + UQ_MATCH_VENDOR_ONLY), + USB_QUIRK(ADDON, ATTACHE, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_IGNORE_RESIDUE), + USB_QUIRK(ADDON, A256MB, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_IGNORE_RESIDUE), + USB_QUIRK(ADDON, DISKPRO512, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_IGNORE_RESIDUE), + USB_QUIRK(ADDONICS2, CABLE_205, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(AIPTEK, POCKETCAM3M, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(AIPTEK2, SUNPLUS_TECH, 0x0000, 0xffff, UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(ALCOR, SDCR_6335, 0x0000, 0xffff, UQ_MSC_NO_TEST_UNIT_READY, + UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(ALCOR, SDCR_6362, 0x0000, 0xffff, UQ_MSC_NO_TEST_UNIT_READY, + UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(ALCOR, AU6390, 0x0000, 0xffff, UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(ALCOR, UMCR_9361, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(ALCOR, TRANSCEND, 0x0142, 0x0142, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_GETMAXLUN, UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(ALCOR, TRANSCEND, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(APACER, HT202, 0x0000, 0xffff, UQ_MSC_NO_TEST_UNIT_READY, + UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(ASAHIOPTICAL, OPTIO230, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(ASAHIOPTICAL, OPTIO330, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(BELKIN, USB2SCSI, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(CASIO, QV_DIGICAM, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(CCYU, ED1064, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(CENTURY, EX35QUAT, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_FORCE_SHORT_INQ, + UQ_MSC_NO_START_STOP, UQ_MSC_IGNORE_RESIDUE), + USB_QUIRK(CENTURY, EX35SW4_SB4, 0x0000, 0xffff, UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(CYPRESS, XX6830XX, 0x0000, 0xffff, UQ_MSC_NO_GETMAXLUN, + UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(DESKNOTE, UCR_61S2B, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(DMI, CFSM_RW, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_SCSI, + UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(DMI, DISK, 0x000, 0xffff, UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(EPSON, STYLUS_875DC, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(EPSON, STYLUS_895, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(FEIYA, 5IN1, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(FREECOM, DVD, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(FREECOM, HDD, 0x0000, 0xffff, UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(FUJIPHOTO, MASS0100, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI_I, + UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_NO_RS_CLEAR_UA, UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(GENESYS, GL641USB2IDE, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_FORCE_SHORT_INQ, + UQ_MSC_NO_START_STOP, UQ_MSC_IGNORE_RESIDUE, UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(GENESYS, GL641USB2IDE_2, 0x0000, 0xffff, + UQ_MSC_FORCE_WIRE_BBB, UQ_MSC_FORCE_PROTO_ATAPI, + UQ_MSC_FORCE_SHORT_INQ, UQ_MSC_NO_START_STOP, + UQ_MSC_IGNORE_RESIDUE), + USB_QUIRK(GENESYS, GL641USB, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_FORCE_SHORT_INQ, + UQ_MSC_NO_START_STOP, UQ_MSC_IGNORE_RESIDUE), + USB_QUIRK(GENESYS, GL641USB_2, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_WRONG_CSWSIG), + USB_QUIRK(HAGIWARA, FG, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(HAGIWARA, FGSM, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(HITACHI, DVDCAM_DZ_MV100A, 0x0000, 0xffff, + UQ_MSC_FORCE_WIRE_CBI, UQ_MSC_FORCE_PROTO_SCSI, + UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(HITACHI, DVDCAM_USB, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI_I, + UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(HP, CDW4E, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_ATAPI), + USB_QUIRK(HP, CDW8200, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI_I, + UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_NO_TEST_UNIT_READY, + UQ_MSC_NO_START_STOP), + USB_QUIRK(IMAGINATION, DBX1, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_WRONG_CSWSIG), + USB_QUIRK(INSYSTEM, USBCABLE, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_NO_TEST_UNIT_READY, + UQ_MSC_NO_START_STOP, UQ_MSC_ALT_IFACE_1), + USB_QUIRK(INSYSTEM, ATAPI, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_RBC), + USB_QUIRK(INSYSTEM, STORAGE_V2, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_RBC), + USB_QUIRK(IODATA, IU_CD2, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(IODATA, DVR_UEH8, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(IOMEGA, ZIP100, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, + UQ_MSC_NO_TEST_UNIT_READY), /* XXX ZIP drives can also use ATAPI */ + USB_QUIRK(JMICRON, JM20336, 0x0000, 0xffff, UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(JMICRON, JM20337, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, + UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(KYOCERA, FINECAM_L3, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(KYOCERA, FINECAM_S3X, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(KYOCERA, FINECAM_S4, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(KYOCERA, FINECAM_S5, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(LACIE, HD, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_RBC), + USB_QUIRK(LEXAR, CF_READER, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(LEXAR, JUMPSHOT, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(LOGITEC, LDR_H443SU2, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(LOGITEC, LDR_H443U2, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI,), + USB_QUIRK(MELCO, DUBPXXG, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_FORCE_SHORT_INQ, + UQ_MSC_NO_START_STOP, UQ_MSC_IGNORE_RESIDUE), + USB_QUIRK(MICROTECH, DPCM, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_TEST_UNIT_READY, + UQ_MSC_NO_START_STOP), + USB_QUIRK(MICROTECH, SCSIDB25, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(MICROTECH, SCSIHD50, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(MINOLTA, E223, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(MINOLTA, F300, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(MITSUMI, CDRRW, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI | + UQ_MSC_FORCE_PROTO_ATAPI), + USB_QUIRK(MOTOROLA2, E398, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_FORCE_SHORT_INQ, + UQ_MSC_NO_INQUIRY_EVPD, UQ_MSC_NO_GETMAXLUN), + USB_QUIRK_VP(USB_VENDOR_MPMAN, 0, UQ_MSC_NO_SYNC_CACHE, + UQ_MATCH_VENDOR_ONLY), + USB_QUIRK(MSYSTEMS, DISKONKEY, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_IGNORE_RESIDUE, UQ_MSC_NO_GETMAXLUN, + UQ_MSC_NO_RS_CLEAR_UA), + USB_QUIRK(MSYSTEMS, DISKONKEY2, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_ATAPI), + USB_QUIRK(MYSON, HEDEN, 0x0000, 0xffff, UQ_MSC_IGNORE_RESIDUE, + UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(MYSON, HEDEN_8813, 0x0000, 0xffff, UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(MYSON, STARREADER, 0x0000, 0xffff, UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(NEODIO, ND3260, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_FORCE_SHORT_INQ), + USB_QUIRK(NETAC, CF_CARD, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(NETAC, ONLYDISK, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_IGNORE_RESIDUE), + USB_QUIRK(NETCHIP, CLIK_40, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_ATAPI, + UQ_MSC_NO_INQUIRY), + USB_QUIRK(NIKON, D300, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(OLYMPUS, C1, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_WRONG_CSWSIG), + USB_QUIRK(OLYMPUS, C700, 0x0000, 0xffff, UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(ONSPEC, SDS_HOTFIND_D, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_GETMAXLUN, UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(ONSPEC, CFMS_RW, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(ONSPEC, CFSM_COMBO, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(ONSPEC, CFSM_READER, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(ONSPEC, CFSM_READER2, 0x0000, 0xffff, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(ONSPEC, MDCFE_B_CF_READER, 0x0000, 0xffff, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(ONSPEC, MDSM_B_READER, 0x0000, 0xffff, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(ONSPEC, READER, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(ONSPEC, UCF100, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_NO_INQUIRY | UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(ONSPEC2, IMAGEMATE_SDDR55, 0x0000, 0xffff, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(PANASONIC, KXL840AN, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(PANASONIC, KXLCB20AN, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(PANASONIC, KXLCB35AN, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(PANASONIC, LS120CAM, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_UFI), + USB_QUIRK(PHILIPS, SPE3030CC, 0x0000, 0xffff, UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(PLEXTOR, 40_12_40U, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_TEST_UNIT_READY), + USB_QUIRK(PNY, ATTACHE2, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_IGNORE_RESIDUE, + UQ_MSC_NO_START_STOP), + USB_QUIRK(PROLIFIC, PL2506, 0x0000, 0xffff, + UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK_VP(USB_VENDOR_SAMSUNG_TECHWIN, + USB_PRODUCT_SAMSUNG_TECHWIN_DIGIMAX_410, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(SAMSUNG, YP_U4, 0x0000, 0xffff, UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(SANDISK, SDDR05A, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_READ_CAP_OFFBY1, + UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(SANDISK, SDDR09, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_SCSI, + UQ_MSC_READ_CAP_OFFBY1, UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(SANDISK, SDDR12, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_READ_CAP_OFFBY1, + UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(SANDISK, SDCZ2_256, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_IGNORE_RESIDUE), + USB_QUIRK(SANDISK, SDCZ4_128, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_IGNORE_RESIDUE), + USB_QUIRK(SANDISK, SDCZ4_256, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_IGNORE_RESIDUE), + USB_QUIRK(SANDISK, SDDR31, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_READ_CAP_OFFBY1), + USB_QUIRK(SCANLOGIC, SL11R, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(SHUTTLE, EUSB, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI_I, + UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_NO_TEST_UNIT_READY, + UQ_MSC_NO_START_STOP, UQ_MSC_SHUTTLE_INIT), + USB_QUIRK(SHUTTLE, CDRW, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_ATAPI), + USB_QUIRK(SHUTTLE, CF, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_ATAPI), + USB_QUIRK(SHUTTLE, EUSBATAPI, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_ATAPI), + USB_QUIRK(SHUTTLE, EUSBCFSM, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(SHUTTLE, EUSCSI, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(SHUTTLE, HIFD, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(SHUTTLE, SDDR09, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_SCSI, + UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(SHUTTLE, ZIOMMC, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(SIGMATEL, I_BEAD100, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_SHUTTLE_INIT), + USB_QUIRK(SIIG, WINTERREADER, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_IGNORE_RESIDUE), + USB_QUIRK(SKANHEX, MD_7425, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(SKANHEX, SX_520Z, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(SONY, HANDYCAM, 0x0500, 0x0500, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_RBC, UQ_MSC_RBC_PAD_TO_12), + USB_QUIRK(SONY, CLIE_40_MS, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(SONY, DSC, 0x0500, 0x0500, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_RBC, UQ_MSC_RBC_PAD_TO_12), + USB_QUIRK(SONY, DSC, 0x0600, 0x0600, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_RBC, UQ_MSC_RBC_PAD_TO_12), + USB_QUIRK(SONY, DSC, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_RBC), + USB_QUIRK(SONY, HANDYCAM, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_RBC), + USB_QUIRK(SONY, MSC, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_RBC), + USB_QUIRK(SONY, MS_MSC_U03, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_UFI, UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(SONY, MS_NW_MS7, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(SONY, MS_PEG_N760C, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(SONY, MSACUS1, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(SONY, PORTABLE_HDD_V2, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(SUPERTOP, IDE, 0x0000, 0xffff, UQ_MSC_IGNORE_RESIDUE, + UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(TAUGA, CAMERAMATE, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(TEAC, FD05PUB, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_UFI), + USB_QUIRK(TECLAST, TLC300, 0x0000, 0xffff, UQ_MSC_NO_TEST_UNIT_READY, + UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(TREK, MEMKEY, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(TREK, THUMBDRIVE_8MB, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_IGNORE_RESIDUE), + USB_QUIRK(TRUMPION, C3310, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_UFI), + USB_QUIRK(TRUMPION, MP3, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_RBC), + USB_QUIRK(TRUMPION, T33520, 0x0000, 0xffff, UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(TWINMOS, MDIV, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI), + USB_QUIRK(VIA, USB2IDEBRIDGE, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(VIVITAR, 35XX, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(WESTERN, COMBO, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_FORCE_SHORT_INQ, + UQ_MSC_NO_START_STOP, UQ_MSC_IGNORE_RESIDUE), + USB_QUIRK(WESTERN, EXTHDD, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_FORCE_SHORT_INQ, + UQ_MSC_NO_START_STOP, UQ_MSC_IGNORE_RESIDUE), + USB_QUIRK(WESTERN, MYBOOK, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY_EVPD, + UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(WESTERN, MYPASSWORD, 0x0000, 0xffff, UQ_MSC_FORCE_SHORT_INQ), + USB_QUIRK(WINMAXGROUP, FLASH64MC, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY), + USB_QUIRK(YANO, FW800HD, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_FORCE_SHORT_INQ, + UQ_MSC_NO_START_STOP, UQ_MSC_IGNORE_RESIDUE), + USB_QUIRK(YANO, U640MO, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI_I, + UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_FORCE_SHORT_INQ), + USB_QUIRK(YEDATA, FLASHBUSTERU, 0x0000, 0x007F, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_UFI, UQ_MSC_NO_RS_CLEAR_UA, UQ_MSC_FLOPPY_SPEED, + UQ_MSC_NO_TEST_UNIT_READY, UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(YEDATA, FLASHBUSTERU, 0x0080, 0x0080, UQ_MSC_FORCE_WIRE_CBI_I, + UQ_MSC_FORCE_PROTO_UFI, UQ_MSC_NO_RS_CLEAR_UA, UQ_MSC_FLOPPY_SPEED, + UQ_MSC_NO_TEST_UNIT_READY, UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(YEDATA, FLASHBUSTERU, 0x0081, 0xFFFF, UQ_MSC_FORCE_WIRE_CBI_I, + UQ_MSC_FORCE_PROTO_UFI, UQ_MSC_NO_RS_CLEAR_UA, UQ_MSC_FLOPPY_SPEED, + UQ_MSC_NO_GETMAXLUN), + USB_QUIRK(ZORAN, EX20DSC, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_CBI, + UQ_MSC_FORCE_PROTO_ATAPI), + USB_QUIRK(MEIZU, M6_SL, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY, UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(ACTIONS, MP4, 0x0000, 0xffff, UQ_MSC_FORCE_WIRE_BBB, + UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(ASUS, GMSC, 0x0000, 0xffff, UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(CHIPSBANK, USBMEMSTICK, 0x0000, 0xffff, UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(CHIPSBANK, USBMEMSTICK1, 0x0000, 0xffff, UQ_MSC_NO_SYNC_CACHE), + USB_QUIRK(NEWLINK, USB2IDEBRIDGE, 0x0000, 0xffff, UQ_MSC_NO_SYNC_CACHE), +}; +#undef USB_QUIRK_VP +#undef USB_QUIRK + +static const char *usb_quirk_str[USB_QUIRK_MAX] = { + [UQ_NONE] = "UQ_NONE", + [UQ_MATCH_VENDOR_ONLY] = "UQ_MATCH_VENDOR_ONLY", + [UQ_AUDIO_SWAP_LR] = "UQ_AUDIO_SWAP_LR", + [UQ_AU_INP_ASYNC] = "UQ_AU_INP_ASYNC", + [UQ_AU_NO_FRAC] = "UQ_AU_NO_FRAC", + [UQ_AU_NO_XU] = "UQ_AU_NO_XU", + [UQ_BAD_ADC] = "UQ_BAD_ADC", + [UQ_BAD_AUDIO] = "UQ_BAD_AUDIO", + [UQ_BROKEN_BIDIR] = "UQ_BROKEN_BIDIR", + [UQ_BUS_POWERED] = "UQ_BUS_POWERED", + [UQ_HID_IGNORE] = "UQ_HID_IGNORE", + [UQ_KBD_IGNORE] = "UQ_KBD_IGNORE", + [UQ_KBD_BOOTPROTO] = "UQ_KBD_BOOTPROTO", + [UQ_MS_BAD_CLASS] = "UQ_MS_BAD_CLASS", + [UQ_MS_LEADING_BYTE] = "UQ_MS_LEADING_BYTE", + [UQ_MS_REVZ] = "UQ_MS_REVZ", + [UQ_NO_STRINGS] = "UQ_NO_STRINGS", + [UQ_OPEN_CLEARSTALL] = "UQ_OPEN_CLEARSTALL", + [UQ_POWER_CLAIM] = "UQ_POWER_CLAIM", + [UQ_SPUR_BUT_UP] = "UQ_SPUR_BUT_UP", + [UQ_SWAP_UNICODE] = "UQ_SWAP_UNICODE", + [UQ_CFG_INDEX_1] = "UQ_CFG_INDEX_1", + [UQ_CFG_INDEX_2] = "UQ_CFG_INDEX_2", + [UQ_CFG_INDEX_3] = "UQ_CFG_INDEX_3", + [UQ_CFG_INDEX_4] = "UQ_CFG_INDEX_4", + [UQ_CFG_INDEX_0] = "UQ_CFG_INDEX_0", + [UQ_ASSUME_CM_OVER_DATA] = "UQ_ASSUME_CM_OVER_DATA", + [UQ_MSC_NO_TEST_UNIT_READY] = "UQ_MSC_NO_TEST_UNIT_READY", + [UQ_MSC_NO_RS_CLEAR_UA] = "UQ_MSC_NO_RS_CLEAR_UA", + [UQ_MSC_NO_START_STOP] = "UQ_MSC_NO_START_STOP", + [UQ_MSC_NO_GETMAXLUN] = "UQ_MSC_NO_GETMAXLUN", + [UQ_MSC_NO_INQUIRY] = "UQ_MSC_NO_INQUIRY", + [UQ_MSC_NO_INQUIRY_EVPD] = "UQ_MSC_NO_INQUIRY_EVPD", + [UQ_MSC_NO_SYNC_CACHE] = "UQ_MSC_NO_SYNC_CACHE", + [UQ_MSC_SHUTTLE_INIT] = "UQ_MSC_SHUTTLE_INIT", + [UQ_MSC_ALT_IFACE_1] = "UQ_MSC_ALT_IFACE_1", + [UQ_MSC_FLOPPY_SPEED] = "UQ_MSC_FLOPPY_SPEED", + [UQ_MSC_IGNORE_RESIDUE] = "UQ_MSC_IGNORE_RESIDUE", + [UQ_MSC_WRONG_CSWSIG] = "UQ_MSC_WRONG_CSWSIG", + [UQ_MSC_RBC_PAD_TO_12] = "UQ_MSC_RBC_PAD_TO_12", + [UQ_MSC_READ_CAP_OFFBY1] = "UQ_MSC_READ_CAP_OFFBY1", + [UQ_MSC_FORCE_SHORT_INQ] = "UQ_MSC_FORCE_SHORT_INQ", + [UQ_MSC_FORCE_WIRE_BBB] = "UQ_MSC_FORCE_WIRE_BBB", + [UQ_MSC_FORCE_WIRE_CBI] = "UQ_MSC_FORCE_WIRE_CBI", + [UQ_MSC_FORCE_WIRE_CBI_I] = "UQ_MSC_FORCE_WIRE_CBI_I", + [UQ_MSC_FORCE_PROTO_SCSI] = "UQ_MSC_FORCE_PROTO_SCSI", + [UQ_MSC_FORCE_PROTO_ATAPI] = "UQ_MSC_FORCE_PROTO_ATAPI", + [UQ_MSC_FORCE_PROTO_UFI] = "UQ_MSC_FORCE_PROTO_UFI", + [UQ_MSC_FORCE_PROTO_RBC] = "UQ_MSC_FORCE_PROTO_RBC", + [UQ_MSC_EJECT_HUAWEI] = "UQ_MSC_EJECT_HUAWEI", + [UQ_MSC_EJECT_SIERRA] = "UQ_MSC_EJECT_SIERRA", + [UQ_MSC_EJECT_SCSIEJECT] = "UQ_MSC_EJECT_SCSIEJECT", + [UQ_MSC_EJECT_REZERO] = "UQ_MSC_EJECT_REZERO", + [UQ_MSC_EJECT_ZTESTOR] = "UQ_MSC_EJECT_ZTESTOR", + [UQ_MSC_EJECT_CMOTECH] = "UQ_MSC_EJECT_CMOTECH", + [UQ_MSC_EJECT_WAIT] = "UQ_MSC_EJECT_WAIT", + [UQ_MSC_EJECT_SAEL_M460] = "UQ_MSC_EJECT_SAEL_M460", + [UQ_MSC_EJECT_HUAWEISCSI] = "UQ_MSC_EJECT_HUAWEISCSI", + [UQ_MSC_EJECT_TCT] = "UQ_MSC_EJECT_TCT", +}; + +/*------------------------------------------------------------------------* + * usb_quirkstr + * + * This function converts an USB quirk code into a string. + *------------------------------------------------------------------------*/ +static const char * +usb_quirkstr(uint16_t quirk) +{ + return ((quirk < USB_QUIRK_MAX) ? + usb_quirk_str[quirk] : "USB_QUIRK_UNKNOWN"); +} + +/*------------------------------------------------------------------------* + * usb_test_quirk_by_info + * + * Returns: + * 0: Quirk not found + * Else: Quirk found + *------------------------------------------------------------------------*/ +static uint8_t +usb_test_quirk_by_info(const struct usbd_lookup_info *info, uint16_t quirk) +{ + uint16_t x; + uint16_t y; + + if (quirk == UQ_NONE) { + return (0); + } + mtx_lock(&usb_quirk_mtx); + + for (x = 0; x != USB_DEV_QUIRKS_MAX; x++) { + /* see if quirk information does not match */ + if ((usb_quirks[x].vid != info->idVendor) || + (usb_quirks[x].lo_rev > info->bcdDevice) || + (usb_quirks[x].hi_rev < info->bcdDevice)) { + continue; + } + /* see if quirk only should match vendor ID */ + if (usb_quirks[x].pid != info->idProduct) { + if (usb_quirks[x].pid != 0) + continue; + + for (y = 0; y != USB_SUB_QUIRKS_MAX; y++) { + if (usb_quirks[x].quirks[y] == UQ_MATCH_VENDOR_ONLY) + break; + } + if (y == USB_SUB_QUIRKS_MAX) + continue; + } + /* lookup quirk */ + for (y = 0; y != USB_SUB_QUIRKS_MAX; y++) { + if (usb_quirks[x].quirks[y] == quirk) { + mtx_unlock(&usb_quirk_mtx); + DPRINTF("Found quirk '%s'.\n", usb_quirkstr(quirk)); + return (1); + } + } + /* no quirk found */ + break; + } + mtx_unlock(&usb_quirk_mtx); + return (0); +} + +static struct usb_quirk_entry * +usb_quirk_get_entry(uint16_t vid, uint16_t pid, + uint16_t lo_rev, uint16_t hi_rev, uint8_t do_alloc) +{ + uint16_t x; + + mtx_assert(&usb_quirk_mtx, MA_OWNED); + + if ((vid | pid | lo_rev | hi_rev) == 0) { + /* all zero - special case */ + return (usb_quirks + USB_DEV_QUIRKS_MAX - 1); + } + /* search for an existing entry */ + for (x = 0; x != USB_DEV_QUIRKS_MAX; x++) { + /* see if quirk information does not match */ + if ((usb_quirks[x].vid != vid) || + (usb_quirks[x].pid != pid) || + (usb_quirks[x].lo_rev != lo_rev) || + (usb_quirks[x].hi_rev != hi_rev)) { + continue; + } + return (usb_quirks + x); + } + + if (do_alloc == 0) { + /* no match */ + return (NULL); + } + /* search for a free entry */ + for (x = 0; x != USB_DEV_QUIRKS_MAX; x++) { + /* see if quirk information does not match */ + if ((usb_quirks[x].vid | + usb_quirks[x].pid | + usb_quirks[x].lo_rev | + usb_quirks[x].hi_rev) != 0) { + continue; + } + usb_quirks[x].vid = vid; + usb_quirks[x].pid = pid; + usb_quirks[x].lo_rev = lo_rev; + usb_quirks[x].hi_rev = hi_rev; + + return (usb_quirks + x); + } + + /* no entry found */ + return (NULL); +} + +/*------------------------------------------------------------------------* + * usb_quirk_ioctl - handle quirk IOCTLs + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static int +usb_quirk_ioctl(unsigned long cmd, caddr_t data, + int fflag, struct thread *td) +{ + struct usb_gen_quirk *pgq; + struct usb_quirk_entry *pqe; + uint32_t x; + uint32_t y; + int err; + + switch (cmd) { + case USB_DEV_QUIRK_GET: + pgq = (void *)data; + x = pgq->index % USB_SUB_QUIRKS_MAX; + y = pgq->index / USB_SUB_QUIRKS_MAX; + if (y >= USB_DEV_QUIRKS_MAX) { + return (EINVAL); + } + mtx_lock(&usb_quirk_mtx); + /* copy out data */ + pgq->vid = usb_quirks[y].vid; + pgq->pid = usb_quirks[y].pid; + pgq->bcdDeviceLow = usb_quirks[y].lo_rev; + pgq->bcdDeviceHigh = usb_quirks[y].hi_rev; + strlcpy(pgq->quirkname, + usb_quirkstr(usb_quirks[y].quirks[x]), + sizeof(pgq->quirkname)); + mtx_unlock(&usb_quirk_mtx); + return (0); /* success */ + + case USB_QUIRK_NAME_GET: + pgq = (void *)data; + x = pgq->index; + if (x >= USB_QUIRK_MAX) { + return (EINVAL); + } + strlcpy(pgq->quirkname, + usb_quirkstr(x), sizeof(pgq->quirkname)); + return (0); /* success */ + + case USB_DEV_QUIRK_ADD: + pgq = (void *)data; + + /* check privileges */ + err = priv_check(curthread, PRIV_DRIVER); + if (err) { + return (err); + } + /* convert quirk string into numerical */ + for (y = 0; y != USB_DEV_QUIRKS_MAX; y++) { + if (strcmp(pgq->quirkname, usb_quirkstr(y)) == 0) { + break; + } + } + if (y == USB_DEV_QUIRKS_MAX) { + return (EINVAL); + } + if (y == UQ_NONE) { + return (EINVAL); + } + mtx_lock(&usb_quirk_mtx); + pqe = usb_quirk_get_entry(pgq->vid, pgq->pid, + pgq->bcdDeviceLow, pgq->bcdDeviceHigh, 1); + if (pqe == NULL) { + mtx_unlock(&usb_quirk_mtx); + return (EINVAL); + } + for (x = 0; x != USB_SUB_QUIRKS_MAX; x++) { + if (pqe->quirks[x] == UQ_NONE) { + pqe->quirks[x] = y; + break; + } + } + mtx_unlock(&usb_quirk_mtx); + if (x == USB_SUB_QUIRKS_MAX) { + return (ENOMEM); + } + return (0); /* success */ + + case USB_DEV_QUIRK_REMOVE: + pgq = (void *)data; + /* check privileges */ + err = priv_check(curthread, PRIV_DRIVER); + if (err) { + return (err); + } + /* convert quirk string into numerical */ + for (y = 0; y != USB_DEV_QUIRKS_MAX; y++) { + if (strcmp(pgq->quirkname, usb_quirkstr(y)) == 0) { + break; + } + } + if (y == USB_DEV_QUIRKS_MAX) { + return (EINVAL); + } + if (y == UQ_NONE) { + return (EINVAL); + } + mtx_lock(&usb_quirk_mtx); + pqe = usb_quirk_get_entry(pgq->vid, pgq->pid, + pgq->bcdDeviceLow, pgq->bcdDeviceHigh, 0); + if (pqe == NULL) { + mtx_unlock(&usb_quirk_mtx); + return (EINVAL); + } + for (x = 0; x != USB_SUB_QUIRKS_MAX; x++) { + if (pqe->quirks[x] == y) { + pqe->quirks[x] = UQ_NONE; + break; + } + } + if (x == USB_SUB_QUIRKS_MAX) { + mtx_unlock(&usb_quirk_mtx); + return (ENOMEM); + } + for (x = 0; x != USB_SUB_QUIRKS_MAX; x++) { + if (pqe->quirks[x] != UQ_NONE) { + break; + } + } + if (x == USB_SUB_QUIRKS_MAX) { + /* all quirk entries are unused - release */ + memset(pqe, 0, sizeof(pqe)); + } + mtx_unlock(&usb_quirk_mtx); + return (0); /* success */ + + default: + break; + } + return (ENOIOCTL); +} + +static void +usb_quirk_init(void *arg) +{ + /* initialize mutex */ + mtx_init(&usb_quirk_mtx, "USB quirk", NULL, MTX_DEF); + + /* register our function */ + usb_test_quirk_p = &usb_test_quirk_by_info; + usb_quirk_ioctl_p = &usb_quirk_ioctl; +} + +#ifndef __rtems__ +static void +usb_quirk_uninit(void *arg) +{ + usb_quirk_unload(arg); + + /* destroy mutex */ + mtx_destroy(&usb_quirk_mtx); +} +#endif /* __rtems__ */ + +SYSINIT(usb_quirk_init, SI_SUB_LOCK, SI_ORDER_FIRST, usb_quirk_init, NULL); +SYSUNINIT(usb_quirk_uninit, SI_SUB_LOCK, SI_ORDER_ANY, usb_quirk_uninit, NULL); diff --git a/freebsd/sys/dev/usb/quirk/usb_quirk.h b/freebsd/sys/dev/usb/quirk/usb_quirk.h new file mode 100644 index 00000000..e8cc0dbf --- /dev/null +++ b/freebsd/sys/dev/usb/quirk/usb_quirk.h @@ -0,0 +1,108 @@ +/* $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. + */ + +#ifndef _USB_QUIRK_HH_ +#define _USB_QUIRK_HH_ + +enum { + /* + * Keep in sync with theusb_quirk_str usb_quirk.c, and with the + * share/man/man4/usb_quirk.4 + */ + UQ_NONE, /* not a valid quirk */ + + UQ_MATCH_VENDOR_ONLY, /* match quirk on vendor only */ + + /* Various quirks */ + + UQ_AUDIO_SWAP_LR, /* left and right sound channels are swapped */ + UQ_AU_INP_ASYNC, /* input is async despite claim of adaptive */ + UQ_AU_NO_FRAC, /* don't adjust for fractional samples */ + UQ_AU_NO_XU, /* audio device has broken extension unit */ + UQ_BAD_ADC, /* bad audio spec version number */ + UQ_BAD_AUDIO, /* device claims audio class, but isn't */ + UQ_BROKEN_BIDIR, /* printer has broken bidir mode */ + UQ_BUS_POWERED, /* device is bus powered, despite claim */ + UQ_HID_IGNORE, /* device should be ignored by hid class */ + UQ_KBD_IGNORE, /* device should be ignored by kbd class */ + UQ_KBD_BOOTPROTO, /* device should set the boot protocol */ + UQ_MS_BAD_CLASS, /* doesn't identify properly */ + UQ_MS_LEADING_BYTE, /* mouse sends an unknown leading byte */ + UQ_MS_REVZ, /* mouse has Z-axis reversed */ + UQ_NO_STRINGS, /* string descriptors are broken */ + UQ_OPEN_CLEARSTALL, /* device needs clear endpoint stall */ + UQ_POWER_CLAIM, /* hub lies about power status */ + UQ_SPUR_BUT_UP, /* spurious mouse button up events */ + UQ_SWAP_UNICODE, /* has some Unicode strings swapped */ + UQ_CFG_INDEX_1, /* select configuration index 1 by default */ + UQ_CFG_INDEX_2, /* select configuration index 2 by default */ + UQ_CFG_INDEX_3, /* select configuration index 3 by default */ + UQ_CFG_INDEX_4, /* select configuration index 4 by default */ + UQ_CFG_INDEX_0, /* select configuration index 0 by default */ + UQ_ASSUME_CM_OVER_DATA, /* assume cm over data feature */ + + /* USB Mass Storage Quirks. See "storage/umass.c" for a detailed description. */ + UQ_MSC_NO_TEST_UNIT_READY, /* send start/stop instead of TUR */ + UQ_MSC_NO_RS_CLEAR_UA, /* does not reset Unit Att. */ + UQ_MSC_NO_START_STOP, /* does not support start/stop */ + UQ_MSC_NO_GETMAXLUN, /* does not support get max LUN */ + UQ_MSC_NO_INQUIRY, /* fake generic inq response */ + UQ_MSC_NO_INQUIRY_EVPD, /* does not support inq EVPD */ + UQ_MSC_NO_SYNC_CACHE, /* does not support sync cache */ + UQ_MSC_SHUTTLE_INIT, /* requires Shuttle init sequence */ + UQ_MSC_ALT_IFACE_1, /* switch to alternate interface 1 */ + UQ_MSC_FLOPPY_SPEED, /* does floppy speeds (20kb/s) */ + UQ_MSC_IGNORE_RESIDUE, /* gets residue wrong */ + UQ_MSC_WRONG_CSWSIG, /* uses wrong CSW signature */ + UQ_MSC_RBC_PAD_TO_12, /* pad RBC requests to 12 bytes */ + UQ_MSC_READ_CAP_OFFBY1, /* reports sector count, not max sec. */ + UQ_MSC_FORCE_SHORT_INQ, /* does not support full inq. */ + UQ_MSC_FORCE_WIRE_BBB, /* force BBB wire protocol */ + UQ_MSC_FORCE_WIRE_CBI, /* force CBI wire protocol */ + UQ_MSC_FORCE_WIRE_CBI_I, /* force CBI with int. wire protocol */ + UQ_MSC_FORCE_PROTO_SCSI, /* force SCSI command protocol */ + UQ_MSC_FORCE_PROTO_ATAPI, /* force ATAPI command protocol */ + UQ_MSC_FORCE_PROTO_UFI, /* force UFI command protocol */ + UQ_MSC_FORCE_PROTO_RBC, /* force RBC command protocol */ + + /* Ejection of mass storage (driver disk) */ + UQ_MSC_EJECT_HUAWEI, /* ejects after Huawei USB command */ + UQ_MSC_EJECT_SIERRA, /* ejects after Sierra USB command */ + UQ_MSC_EJECT_SCSIEJECT, /* ejects after SCSI eject command */ + UQ_MSC_EJECT_REZERO, /* ejects after SCSI rezero command */ + UQ_MSC_EJECT_ZTESTOR, /* ejects after ZTE SCSI command */ + UQ_MSC_EJECT_CMOTECH, /* ejects after C-motech SCSI cmd */ + UQ_MSC_EJECT_WAIT, /* wait for the device to eject */ + UQ_MSC_EJECT_SAEL_M460, /* ejects after Sael USB commands */ + UQ_MSC_EJECT_HUAWEISCSI, /* ejects after Huawei SCSI command */ + UQ_MSC_EJECT_TCT, /* ejects after TCT SCSI command */ + + USB_QUIRK_MAX +}; + +uint8_t usb_test_quirk(const struct usb_attach_arg *uaa, uint16_t quirk); + +#endif /* _USB_QUIRK_HH_ */ diff --git a/freebsd/sys/dev/usb/storage/umass.c b/freebsd/sys/dev/usb/storage/umass.c new file mode 100644 index 00000000..b1867dc2 --- /dev/null +++ b/freebsd/sys/dev/usb/storage/umass.c @@ -0,0 +1,3119 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +#include <freebsd/sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 1999 MAEKAWA Masahide <bishop@rr.iij4u.or.jp>, + * Nick Hibma <n_hibma@FreeBSD.org> + * 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$ + * $NetBSD: umass.c,v 1.28 2000/04/02 23:46:53 augustss Exp $ + */ + +/* Also already merged from NetBSD: + * $NetBSD: umass.c,v 1.67 2001/11/25 19:05:22 augustss Exp $ + * $NetBSD: umass.c,v 1.90 2002/11/04 19:17:33 pooka Exp $ + * $NetBSD: umass.c,v 1.108 2003/11/07 17:03:25 wiz Exp $ + * $NetBSD: umass.c,v 1.109 2003/12/04 13:57:31 keihan Exp $ + */ + +/* + * Universal Serial Bus Mass Storage Class specs: + * http://www.usb.org/developers/devclass_docs/usb_msc_overview_1.2.pdf + * http://www.usb.org/developers/devclass_docs/usbmassbulk_10.pdf + * http://www.usb.org/developers/devclass_docs/usb_msc_cbi_1.1.pdf + * http://www.usb.org/developers/devclass_docs/usbmass-ufi10.pdf + */ + +/* + * Ported to NetBSD by Lennart Augustsson <augustss@NetBSD.org>. + * Parts of the code written by Jason R. Thorpe <thorpej@shagadelic.org>. + */ + +/* + * The driver handles 3 Wire Protocols + * - Command/Bulk/Interrupt (CBI) + * - Command/Bulk/Interrupt with Command Completion Interrupt (CBI with CCI) + * - Mass Storage Bulk-Only (BBB) + * (BBB refers Bulk/Bulk/Bulk for Command/Data/Status phases) + * + * Over these wire protocols it handles the following command protocols + * - SCSI + * - UFI (floppy command set) + * - 8070i (ATAPI) + * + * UFI and 8070i (ATAPI) are transformed versions of the SCSI command set. The + * sc->sc_transform method is used to convert the commands into the appropriate + * format (if at all necessary). For example, UFI requires all commands to be + * 12 bytes in length amongst other things. + * + * The source code below is marked and can be split into a number of pieces + * (in this order): + * + * - probe/attach/detach + * - generic transfer routines + * - BBB + * - CBI + * - CBI_I (in addition to functions from CBI) + * - CAM (Common Access Method) + * - SCSI + * - UFI + * - 8070i (ATAPI) + * + * The protocols are implemented using a state machine, for the transfers as + * well as for the resets. The state machine is contained in umass_t_*_callback. + * The state machine is started through either umass_command_start() or + * umass_reset(). + * + * The reason for doing this is a) CAM performs a lot better this way and b) it + * avoids using tsleep from interrupt context (for example after a failed + * transfer). + */ + +/* + * The SCSI related part of this driver has been derived from the + * dev/ppbus/vpo.c driver, by Nicolas Souchu (nsouch@FreeBSD.org). + * + * The CAM layer uses so called actions which are messages sent to the host + * adapter for completion. The actions come in through umass_cam_action. The + * appropriate block of routines is called depending on the transport protocol + * in use. When the transfer has finished, these routines call + * umass_cam_cb again to complete the CAM command. + */ + +#include <freebsd/sys/stdint.h> +#include <freebsd/sys/stddef.h> +#include <freebsd/sys/param.h> +#include <freebsd/sys/queue.h> +#include <freebsd/sys/types.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/bus.h> +#include <freebsd/sys/linker_set.h> +#include <freebsd/sys/module.h> +#include <freebsd/sys/lock.h> +#include <freebsd/sys/mutex.h> +#include <freebsd/sys/condvar.h> +#include <freebsd/sys/sysctl.h> +#include <freebsd/sys/sx.h> +#include <freebsd/sys/unistd.h> +#include <freebsd/sys/callout.h> +#include <freebsd/sys/malloc.h> +#include <freebsd/sys/priv.h> + +#include <freebsd/dev/usb/usb.h> +#include <freebsd/dev/usb/usbdi.h> +#include <freebsd/dev/usb/usbdi_util.h> +#include <freebsd/local/usbdevs.h> + +#include <freebsd/dev/usb/quirk/usb_quirk.h> + +#include <freebsd/cam/cam.h> +#include <freebsd/cam/cam_ccb.h> +#include <freebsd/cam/cam_sim.h> +#include <freebsd/cam/cam_xpt_sim.h> +#include <freebsd/cam/scsi/scsi_all.h> +#include <freebsd/cam/scsi/scsi_da.h> + +#include <freebsd/cam/cam_periph.h> + +#define UMASS_EXT_BUFFER +#ifdef UMASS_EXT_BUFFER +/* this enables loading of virtual buffers into DMA */ +#define UMASS_USB_FLAGS .ext_buffer=1, +#else +#define UMASS_USB_FLAGS +#endif + +#ifdef USB_DEBUG +#define DIF(m, x) \ + do { \ + if (umass_debug & (m)) { x ; } \ + } while (0) + +#define DPRINTF(sc, m, fmt, ...) \ + do { \ + if (umass_debug & (m)) { \ + printf("%s:%s: " fmt, \ + (sc) ? (const char *)(sc)->sc_name : \ + (const char *)"umassX", \ + __FUNCTION__ ,## __VA_ARGS__); \ + } \ + } while (0) + +#define UDMASS_GEN 0x00010000 /* general */ +#define UDMASS_SCSI 0x00020000 /* scsi */ +#define UDMASS_UFI 0x00040000 /* ufi command set */ +#define UDMASS_ATAPI 0x00080000 /* 8070i command set */ +#define UDMASS_CMD (UDMASS_SCSI|UDMASS_UFI|UDMASS_ATAPI) +#define UDMASS_USB 0x00100000 /* USB general */ +#define UDMASS_BBB 0x00200000 /* Bulk-Only transfers */ +#define UDMASS_CBI 0x00400000 /* CBI transfers */ +#define UDMASS_WIRE (UDMASS_BBB|UDMASS_CBI) +#define UDMASS_ALL 0xffff0000 /* all of the above */ +static int umass_debug = 0; + +SYSCTL_NODE(_hw_usb, OID_AUTO, umass, CTLFLAG_RW, 0, "USB umass"); +SYSCTL_INT(_hw_usb_umass, OID_AUTO, debug, CTLFLAG_RW, + &umass_debug, 0, "umass debug level"); + +TUNABLE_INT("hw.usb.umass.debug", &umass_debug); +#else +#define DIF(...) do { } while (0) +#define DPRINTF(...) do { } while (0) +#endif + +#define UMASS_GONE ((struct umass_softc *)1) + +#define UMASS_BULK_SIZE (1 << 17) +#define UMASS_CBI_DIAGNOSTIC_CMDLEN 12 /* bytes */ +#define UMASS_MAX_CMDLEN MAX(12, CAM_MAX_CDBLEN) /* bytes */ + +/* USB transfer definitions */ + +#define UMASS_T_BBB_RESET1 0 /* Bulk-Only */ +#define UMASS_T_BBB_RESET2 1 +#define UMASS_T_BBB_RESET3 2 +#define UMASS_T_BBB_COMMAND 3 +#define UMASS_T_BBB_DATA_READ 4 +#define UMASS_T_BBB_DATA_RD_CS 5 +#define UMASS_T_BBB_DATA_WRITE 6 +#define UMASS_T_BBB_DATA_WR_CS 7 +#define UMASS_T_BBB_STATUS 8 +#define UMASS_T_BBB_MAX 9 + +#define UMASS_T_CBI_RESET1 0 /* CBI */ +#define UMASS_T_CBI_RESET2 1 +#define UMASS_T_CBI_RESET3 2 +#define UMASS_T_CBI_COMMAND 3 +#define UMASS_T_CBI_DATA_READ 4 +#define UMASS_T_CBI_DATA_RD_CS 5 +#define UMASS_T_CBI_DATA_WRITE 6 +#define UMASS_T_CBI_DATA_WR_CS 7 +#define UMASS_T_CBI_STATUS 8 +#define UMASS_T_CBI_RESET4 9 +#define UMASS_T_CBI_MAX 10 + +#define UMASS_T_MAX MAX(UMASS_T_CBI_MAX, UMASS_T_BBB_MAX) + +/* Generic definitions */ + +/* Direction for transfer */ +#define DIR_NONE 0 +#define DIR_IN 1 +#define DIR_OUT 2 + +/* device name */ +#define DEVNAME "umass" +#define DEVNAME_SIM "umass-sim" + +/* Approximate maximum transfer speeds (assumes 33% overhead). */ +#define UMASS_FULL_TRANSFER_SPEED 1000 +#define UMASS_HIGH_TRANSFER_SPEED 40000 +#define UMASS_SUPER_TRANSFER_SPEED 400000 +#define UMASS_FLOPPY_TRANSFER_SPEED 20 + +#define UMASS_TIMEOUT 5000 /* ms */ + +/* CAM specific definitions */ + +#define UMASS_SCSIID_MAX 1 /* maximum number of drives expected */ +#define UMASS_SCSIID_HOST UMASS_SCSIID_MAX + +/* Bulk-Only features */ + +#define UR_BBB_RESET 0xff /* Bulk-Only reset */ +#define UR_BBB_GET_MAX_LUN 0xfe /* Get maximum lun */ + +/* Command Block Wrapper */ +typedef struct { + 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 umass_bbb_cbw_t; + +#define UMASS_BBB_CBW_SIZE 31 + +/* Command Status Wrapper */ +typedef struct { + uDWord dCSWSignature; +#define CSWSIGNATURE 0x53425355 +#define CSWSIGNATURE_IMAGINATION_DBX1 0x43425355 +#define CSWSIGNATURE_OLYMPUS_C1 0x55425355 + uDWord dCSWTag; + uDWord dCSWDataResidue; + uByte bCSWStatus; +#define CSWSTATUS_GOOD 0x0 +#define CSWSTATUS_FAILED 0x1 +#define CSWSTATUS_PHASE 0x2 +} __packed umass_bbb_csw_t; + +#define UMASS_BBB_CSW_SIZE 13 + +/* CBI features */ + +#define UR_CBI_ADSC 0x00 + +typedef union { + struct { + uint8_t type; +#define IDB_TYPE_CCI 0x00 + uint8_t value; +#define IDB_VALUE_PASS 0x00 +#define IDB_VALUE_FAIL 0x01 +#define IDB_VALUE_PHASE 0x02 +#define IDB_VALUE_PERSISTENT 0x03 +#define IDB_VALUE_STATUS_MASK 0x03 + } __packed common; + + struct { + uint8_t asc; + uint8_t ascq; + } __packed ufi; +} __packed umass_cbi_sbl_t; + +struct umass_softc; /* see below */ + +typedef void (umass_callback_t)(struct umass_softc *sc, union ccb *ccb, + uint32_t residue, uint8_t status); + +#define STATUS_CMD_OK 0 /* everything ok */ +#define STATUS_CMD_UNKNOWN 1 /* will have to fetch sense */ +#define STATUS_CMD_FAILED 2 /* transfer was ok, command failed */ +#define STATUS_WIRE_FAILED 3 /* couldn't even get command across */ + +typedef uint8_t (umass_transform_t)(struct umass_softc *sc, uint8_t *cmd_ptr, + uint8_t cmd_len); + +/* Wire and command protocol */ +#define UMASS_PROTO_BBB 0x0001 /* USB wire protocol */ +#define UMASS_PROTO_CBI 0x0002 +#define UMASS_PROTO_CBI_I 0x0004 +#define UMASS_PROTO_WIRE 0x00ff /* USB wire protocol mask */ +#define UMASS_PROTO_SCSI 0x0100 /* command protocol */ +#define UMASS_PROTO_ATAPI 0x0200 +#define UMASS_PROTO_UFI 0x0400 +#define UMASS_PROTO_RBC 0x0800 +#define UMASS_PROTO_COMMAND 0xff00 /* command protocol mask */ + +/* Device specific quirks */ +#define NO_QUIRKS 0x0000 + /* + * The drive does not support Test Unit Ready. Convert to Start Unit + */ +#define NO_TEST_UNIT_READY 0x0001 + /* + * The drive does not reset the Unit Attention state after REQUEST + * SENSE has been sent. The INQUIRY command does not reset the UA + * either, and so CAM runs in circles trying to retrieve the initial + * INQUIRY data. + */ +#define RS_NO_CLEAR_UA 0x0002 + /* The drive does not support START STOP. */ +#define NO_START_STOP 0x0004 + /* Don't ask for full inquiry data (255b). */ +#define FORCE_SHORT_INQUIRY 0x0008 + /* Needs to be initialised the Shuttle way */ +#define SHUTTLE_INIT 0x0010 + /* Drive needs to be switched to alternate iface 1 */ +#define ALT_IFACE_1 0x0020 + /* Drive does not do 1Mb/s, but just floppy speeds (20kb/s) */ +#define FLOPPY_SPEED 0x0040 + /* The device can't count and gets the residue of transfers wrong */ +#define IGNORE_RESIDUE 0x0080 + /* No GetMaxLun call */ +#define NO_GETMAXLUN 0x0100 + /* The device uses a weird CSWSIGNATURE. */ +#define WRONG_CSWSIG 0x0200 + /* Device cannot handle INQUIRY so fake a generic response */ +#define NO_INQUIRY 0x0400 + /* Device cannot handle INQUIRY EVPD, return CHECK CONDITION */ +#define NO_INQUIRY_EVPD 0x0800 + /* Pad all RBC requests to 12 bytes. */ +#define RBC_PAD_TO_12 0x1000 + /* + * Device reports number of sectors from READ_CAPACITY, not max + * sector number. + */ +#define READ_CAPACITY_OFFBY1 0x2000 + /* + * Device cannot handle a SCSI synchronize cache command. Normally + * this quirk would be handled in the cam layer, but for IDE bridges + * we need to associate the quirk with the bridge and not the + * underlying disk device. This is handled by faking a success + * result. + */ +#define NO_SYNCHRONIZE_CACHE 0x4000 + +struct umass_softc { + + struct scsi_sense cam_scsi_sense; + struct scsi_test_unit_ready cam_scsi_test_unit_ready; + struct mtx sc_mtx; + struct { + uint8_t *data_ptr; + union ccb *ccb; + umass_callback_t *callback; + + uint32_t data_len; /* bytes */ + uint32_t data_rem; /* bytes */ + uint32_t data_timeout; /* ms */ + uint32_t actlen; /* bytes */ + + uint8_t cmd_data[UMASS_MAX_CMDLEN]; + uint8_t cmd_len; /* bytes */ + uint8_t dir; + uint8_t lun; + } sc_transfer; + + /* Bulk specific variables for transfers in progress */ + umass_bbb_cbw_t cbw; /* command block wrapper */ + umass_bbb_csw_t csw; /* command status wrapper */ + + /* CBI specific variables for transfers in progress */ + umass_cbi_sbl_t sbl; /* status block */ + + device_t sc_dev; + struct usb_device *sc_udev; + struct cam_sim *sc_sim; /* SCSI Interface Module */ + struct usb_xfer *sc_xfer[UMASS_T_MAX]; + + /* + * The command transform function is used to convert the SCSI + * commands into their derivatives, like UFI, ATAPI, and friends. + */ + umass_transform_t *sc_transform; + + uint32_t sc_unit; + uint32_t sc_quirks; /* they got it almost right */ + uint32_t sc_proto; /* wire and cmd protocol */ + + uint8_t sc_name[16]; + uint8_t sc_iface_no; /* interface number */ + uint8_t sc_maxlun; /* maximum LUN number, inclusive */ + uint8_t sc_last_xfer_index; + uint8_t sc_status_try; +}; + +struct umass_probe_proto { + uint32_t quirks; + uint32_t proto; + + int error; +}; + +/* prototypes */ + +static device_probe_t umass_probe; +static device_attach_t umass_attach; +static device_detach_t umass_detach; + +static usb_callback_t umass_tr_error; +static usb_callback_t umass_t_bbb_reset1_callback; +static usb_callback_t umass_t_bbb_reset2_callback; +static usb_callback_t umass_t_bbb_reset3_callback; +static usb_callback_t umass_t_bbb_command_callback; +static usb_callback_t umass_t_bbb_data_read_callback; +static usb_callback_t umass_t_bbb_data_rd_cs_callback; +static usb_callback_t umass_t_bbb_data_write_callback; +static usb_callback_t umass_t_bbb_data_wr_cs_callback; +static usb_callback_t umass_t_bbb_status_callback; +static usb_callback_t umass_t_cbi_reset1_callback; +static usb_callback_t umass_t_cbi_reset2_callback; +static usb_callback_t umass_t_cbi_reset3_callback; +static usb_callback_t umass_t_cbi_reset4_callback; +static usb_callback_t umass_t_cbi_command_callback; +static usb_callback_t umass_t_cbi_data_read_callback; +static usb_callback_t umass_t_cbi_data_rd_cs_callback; +static usb_callback_t umass_t_cbi_data_write_callback; +static usb_callback_t umass_t_cbi_data_wr_cs_callback; +static usb_callback_t umass_t_cbi_status_callback; + +static void umass_cancel_ccb(struct umass_softc *); +static void umass_init_shuttle(struct umass_softc *); +static void umass_reset(struct umass_softc *); +static void umass_t_bbb_data_clear_stall_callback(struct usb_xfer *, + uint8_t, uint8_t, usb_error_t); +static void umass_command_start(struct umass_softc *, uint8_t, void *, + uint32_t, uint32_t, umass_callback_t *, union ccb *); +static uint8_t umass_bbb_get_max_lun(struct umass_softc *); +static void umass_cbi_start_status(struct umass_softc *); +static void umass_t_cbi_data_clear_stall_callback(struct usb_xfer *, + uint8_t, uint8_t, usb_error_t); +static int umass_cam_attach_sim(struct umass_softc *); +#ifndef __rtems__ +static void umass_cam_attach(struct umass_softc *); +#endif /* __rtems__ */ +static void umass_cam_detach_sim(struct umass_softc *); +static void umass_cam_action(struct cam_sim *, union ccb *); +static void umass_cam_poll(struct cam_sim *); +static void umass_cam_cb(struct umass_softc *, union ccb *, uint32_t, + uint8_t); +static void umass_cam_sense_cb(struct umass_softc *, union ccb *, uint32_t, + uint8_t); +static void umass_cam_quirk_cb(struct umass_softc *, union ccb *, uint32_t, + uint8_t); +static uint8_t umass_scsi_transform(struct umass_softc *, uint8_t *, uint8_t); +static uint8_t umass_rbc_transform(struct umass_softc *, uint8_t *, uint8_t); +static uint8_t umass_ufi_transform(struct umass_softc *, uint8_t *, uint8_t); +static uint8_t umass_atapi_transform(struct umass_softc *, uint8_t *, + uint8_t); +static uint8_t umass_no_transform(struct umass_softc *, uint8_t *, uint8_t); +static uint8_t umass_std_transform(struct umass_softc *, union ccb *, uint8_t + *, uint8_t); + +#ifdef USB_DEBUG +static void umass_bbb_dump_cbw(struct umass_softc *, umass_bbb_cbw_t *); +static void umass_bbb_dump_csw(struct umass_softc *, umass_bbb_csw_t *); +static void umass_cbi_dump_cmd(struct umass_softc *, void *, uint8_t); +static void umass_dump_buffer(struct umass_softc *, uint8_t *, uint32_t, + uint32_t); +#endif + +static struct usb_config umass_bbb_config[UMASS_T_BBB_MAX] = { + + [UMASS_T_BBB_RESET1] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request), + .callback = &umass_t_bbb_reset1_callback, + .timeout = 5000, /* 5 seconds */ + .interval = 500, /* 500 milliseconds */ + }, + + [UMASS_T_BBB_RESET2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request), + .callback = &umass_t_bbb_reset2_callback, + .timeout = 5000, /* 5 seconds */ + .interval = 50, /* 50 milliseconds */ + }, + + [UMASS_T_BBB_RESET3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request), + .callback = &umass_t_bbb_reset3_callback, + .timeout = 5000, /* 5 seconds */ + .interval = 50, /* 50 milliseconds */ + }, + + [UMASS_T_BBB_COMMAND] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = sizeof(umass_bbb_cbw_t), + .callback = &umass_t_bbb_command_callback, + .timeout = 5000, /* 5 seconds */ + }, + + [UMASS_T_BBB_DATA_READ] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = UMASS_BULK_SIZE, + .flags = {.proxy_buffer = 1,.short_xfer_ok = 1, UMASS_USB_FLAGS}, + .callback = &umass_t_bbb_data_read_callback, + .timeout = 0, /* overwritten later */ + }, + + [UMASS_T_BBB_DATA_RD_CS] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request), + .callback = &umass_t_bbb_data_rd_cs_callback, + .timeout = 5000, /* 5 seconds */ + }, + + [UMASS_T_BBB_DATA_WRITE] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = UMASS_BULK_SIZE, + .flags = {.proxy_buffer = 1,.short_xfer_ok = 1, UMASS_USB_FLAGS}, + .callback = &umass_t_bbb_data_write_callback, + .timeout = 0, /* overwritten later */ + }, + + [UMASS_T_BBB_DATA_WR_CS] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request), + .callback = &umass_t_bbb_data_wr_cs_callback, + .timeout = 5000, /* 5 seconds */ + }, + + [UMASS_T_BBB_STATUS] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = sizeof(umass_bbb_csw_t), + .flags = {.short_xfer_ok = 1,}, + .callback = &umass_t_bbb_status_callback, + .timeout = 5000, /* ms */ + }, +}; + +static struct usb_config umass_cbi_config[UMASS_T_CBI_MAX] = { + + [UMASS_T_CBI_RESET1] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = (sizeof(struct usb_device_request) + + UMASS_CBI_DIAGNOSTIC_CMDLEN), + .callback = &umass_t_cbi_reset1_callback, + .timeout = 5000, /* 5 seconds */ + .interval = 500, /* 500 milliseconds */ + }, + + [UMASS_T_CBI_RESET2] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request), + .callback = &umass_t_cbi_reset2_callback, + .timeout = 5000, /* 5 seconds */ + .interval = 50, /* 50 milliseconds */ + }, + + [UMASS_T_CBI_RESET3] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request), + .callback = &umass_t_cbi_reset3_callback, + .timeout = 5000, /* 5 seconds */ + .interval = 50, /* 50 milliseconds */ + }, + + [UMASS_T_CBI_COMMAND] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = (sizeof(struct usb_device_request) + + UMASS_MAX_CMDLEN), + .callback = &umass_t_cbi_command_callback, + .timeout = 5000, /* 5 seconds */ + }, + + [UMASS_T_CBI_DATA_READ] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .bufsize = UMASS_BULK_SIZE, + .flags = {.proxy_buffer = 1,.short_xfer_ok = 1, UMASS_USB_FLAGS}, + .callback = &umass_t_cbi_data_read_callback, + .timeout = 0, /* overwritten later */ + }, + + [UMASS_T_CBI_DATA_RD_CS] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request), + .callback = &umass_t_cbi_data_rd_cs_callback, + .timeout = 5000, /* 5 seconds */ + }, + + [UMASS_T_CBI_DATA_WRITE] = { + .type = UE_BULK, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .bufsize = UMASS_BULK_SIZE, + .flags = {.proxy_buffer = 1,.short_xfer_ok = 1, UMASS_USB_FLAGS}, + .callback = &umass_t_cbi_data_write_callback, + .timeout = 0, /* overwritten later */ + }, + + [UMASS_T_CBI_DATA_WR_CS] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request), + .callback = &umass_t_cbi_data_wr_cs_callback, + .timeout = 5000, /* 5 seconds */ + }, + + [UMASS_T_CBI_STATUS] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = {.short_xfer_ok = 1,.no_pipe_ok = 1,}, + .bufsize = sizeof(umass_cbi_sbl_t), + .callback = &umass_t_cbi_status_callback, + .timeout = 5000, /* ms */ + }, + + [UMASS_T_CBI_RESET4] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request), + .callback = &umass_t_cbi_reset4_callback, + .timeout = 5000, /* ms */ + }, +}; + +/* If device cannot return valid inquiry data, fake it */ +static const uint8_t fake_inq_data[SHORT_INQUIRY_LENGTH] = { + 0, /* removable */ 0x80, SCSI_REV_2, SCSI_REV_2, + /* additional_length */ 31, 0, 0, 0 +}; + +#define UFI_COMMAND_LENGTH 12 /* UFI commands are always 12 bytes */ +#define ATAPI_COMMAND_LENGTH 12 /* ATAPI commands are always 12 bytes */ + +static devclass_t umass_devclass; + +static device_method_t umass_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, umass_probe), + DEVMETHOD(device_attach, umass_attach), + DEVMETHOD(device_detach, umass_detach), + {0, 0} +}; + +static driver_t umass_driver = { + .name = "umass", + .methods = umass_methods, + .size = sizeof(struct umass_softc), +}; + +DRIVER_MODULE(umass, uhub, umass_driver, umass_devclass, NULL, 0); +MODULE_DEPEND(umass, usb, 1, 1, 1); +MODULE_DEPEND(umass, cam, 1, 1, 1); +MODULE_VERSION(umass, 1); + +/* + * USB device probe/attach/detach + */ + +static uint16_t +umass_get_proto(struct usb_interface *iface) +{ + struct usb_interface_descriptor *id; + uint16_t retval; + + retval = 0; + + /* Check for a standards compliant device */ + id = usbd_get_interface_descriptor(iface); + if ((id == NULL) || + (id->bInterfaceClass != UICLASS_MASS)) { + goto done; + } + switch (id->bInterfaceSubClass) { + case UISUBCLASS_SCSI: + retval |= UMASS_PROTO_SCSI; + break; + case UISUBCLASS_UFI: + retval |= UMASS_PROTO_UFI; + break; + case UISUBCLASS_RBC: + retval |= UMASS_PROTO_RBC; + break; + case UISUBCLASS_SFF8020I: + case UISUBCLASS_SFF8070I: + retval |= UMASS_PROTO_ATAPI; + break; + default: + goto done; + } + + switch (id->bInterfaceProtocol) { + case UIPROTO_MASS_CBI: + retval |= UMASS_PROTO_CBI; + break; + case UIPROTO_MASS_CBI_I: + retval |= UMASS_PROTO_CBI_I; + break; + case UIPROTO_MASS_BBB_OLD: + case UIPROTO_MASS_BBB: + retval |= UMASS_PROTO_BBB; + break; + default: + goto done; + } +done: + return (retval); +} + +/* + * Match the device we are seeing with the devices supported. + */ +static struct umass_probe_proto +umass_probe_proto(device_t dev, struct usb_attach_arg *uaa) +{ + struct umass_probe_proto ret; + uint32_t quirks = NO_QUIRKS; + uint32_t proto = umass_get_proto(uaa->iface); + + memset(&ret, 0, sizeof(ret)); + + /* Search for protocol enforcement */ + + if (usb_test_quirk(uaa, UQ_MSC_FORCE_WIRE_BBB)) { + proto &= ~UMASS_PROTO_WIRE; + proto |= UMASS_PROTO_BBB; + } else if (usb_test_quirk(uaa, UQ_MSC_FORCE_WIRE_CBI)) { + proto &= ~UMASS_PROTO_WIRE; + proto |= UMASS_PROTO_CBI; + } else if (usb_test_quirk(uaa, UQ_MSC_FORCE_WIRE_CBI_I)) { + proto &= ~UMASS_PROTO_WIRE; + proto |= UMASS_PROTO_CBI_I; + } + + if (usb_test_quirk(uaa, UQ_MSC_FORCE_PROTO_SCSI)) { + proto &= ~UMASS_PROTO_COMMAND; + proto |= UMASS_PROTO_SCSI; + } else if (usb_test_quirk(uaa, UQ_MSC_FORCE_PROTO_ATAPI)) { + proto &= ~UMASS_PROTO_COMMAND; + proto |= UMASS_PROTO_ATAPI; + } else if (usb_test_quirk(uaa, UQ_MSC_FORCE_PROTO_UFI)) { + proto &= ~UMASS_PROTO_COMMAND; + proto |= UMASS_PROTO_UFI; + } else if (usb_test_quirk(uaa, UQ_MSC_FORCE_PROTO_RBC)) { + proto &= ~UMASS_PROTO_COMMAND; + proto |= UMASS_PROTO_RBC; + } + + /* Check if the protocol is invalid */ + + if ((proto & UMASS_PROTO_COMMAND) == 0) { + ret.error = ENXIO; + goto done; + } + + if ((proto & UMASS_PROTO_WIRE) == 0) { + ret.error = ENXIO; + goto done; + } + + /* Search for quirks */ + + if (usb_test_quirk(uaa, UQ_MSC_NO_TEST_UNIT_READY)) + quirks |= NO_TEST_UNIT_READY; + if (usb_test_quirk(uaa, UQ_MSC_NO_RS_CLEAR_UA)) + quirks |= RS_NO_CLEAR_UA; + if (usb_test_quirk(uaa, UQ_MSC_NO_START_STOP)) + quirks |= NO_START_STOP; + if (usb_test_quirk(uaa, UQ_MSC_NO_GETMAXLUN)) + quirks |= NO_GETMAXLUN; + if (usb_test_quirk(uaa, UQ_MSC_NO_INQUIRY)) + quirks |= NO_INQUIRY; + if (usb_test_quirk(uaa, UQ_MSC_NO_INQUIRY_EVPD)) + quirks |= NO_INQUIRY_EVPD; + if (usb_test_quirk(uaa, UQ_MSC_NO_SYNC_CACHE)) + quirks |= NO_SYNCHRONIZE_CACHE; + if (usb_test_quirk(uaa, UQ_MSC_SHUTTLE_INIT)) + quirks |= SHUTTLE_INIT; + if (usb_test_quirk(uaa, UQ_MSC_ALT_IFACE_1)) + quirks |= ALT_IFACE_1; + if (usb_test_quirk(uaa, UQ_MSC_FLOPPY_SPEED)) + quirks |= FLOPPY_SPEED; + if (usb_test_quirk(uaa, UQ_MSC_IGNORE_RESIDUE)) + quirks |= IGNORE_RESIDUE; + if (usb_test_quirk(uaa, UQ_MSC_WRONG_CSWSIG)) + quirks |= WRONG_CSWSIG; + if (usb_test_quirk(uaa, UQ_MSC_RBC_PAD_TO_12)) + quirks |= RBC_PAD_TO_12; + if (usb_test_quirk(uaa, UQ_MSC_READ_CAP_OFFBY1)) + quirks |= READ_CAPACITY_OFFBY1; + if (usb_test_quirk(uaa, UQ_MSC_FORCE_SHORT_INQ)) + quirks |= FORCE_SHORT_INQUIRY; + +done: + ret.quirks = quirks; + ret.proto = proto; + return (ret); +} + +static int +umass_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct umass_probe_proto temp; + + if (uaa->usb_mode != USB_MODE_HOST) { + return (ENXIO); + } + if (uaa->use_generic == 0) { + /* give other drivers a try first */ + return (ENXIO); + } + temp = umass_probe_proto(dev, uaa); + + return (temp.error); +} + +static int +umass_attach(device_t dev) +{ + struct umass_softc *sc = device_get_softc(dev); + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct umass_probe_proto temp = umass_probe_proto(dev, uaa); + struct usb_interface_descriptor *id; + int32_t err; + + /* + * NOTE: the softc struct is bzero-ed in device_set_driver. + * We can safely call umass_detach without specifically + * initializing the struct. + */ + + sc->sc_dev = dev; + sc->sc_udev = uaa->device; + sc->sc_proto = temp.proto; + sc->sc_quirks = temp.quirks; + sc->sc_unit = device_get_unit(dev); + + snprintf(sc->sc_name, sizeof(sc->sc_name), + "%s", device_get_nameunit(dev)); + + device_set_usb_desc(dev); + + mtx_init(&sc->sc_mtx, device_get_nameunit(dev), + NULL, MTX_DEF | MTX_RECURSE); + + /* get interface index */ + + id = usbd_get_interface_descriptor(uaa->iface); + if (id == NULL) { + device_printf(dev, "failed to get " + "interface number\n"); + goto detach; + } + sc->sc_iface_no = id->bInterfaceNumber; + +#ifdef USB_DEBUG + device_printf(dev, " "); + + switch (sc->sc_proto & UMASS_PROTO_COMMAND) { + case UMASS_PROTO_SCSI: + printf("SCSI"); + break; + case UMASS_PROTO_ATAPI: + printf("8070i (ATAPI)"); + break; + case UMASS_PROTO_UFI: + printf("UFI"); + break; + case UMASS_PROTO_RBC: + printf("RBC"); + break; + default: + printf("(unknown 0x%02x)", + sc->sc_proto & UMASS_PROTO_COMMAND); + break; + } + + printf(" over "); + + switch (sc->sc_proto & UMASS_PROTO_WIRE) { + case UMASS_PROTO_BBB: + printf("Bulk-Only"); + break; + case UMASS_PROTO_CBI: /* uses Comand/Bulk pipes */ + printf("CBI"); + break; + case UMASS_PROTO_CBI_I: /* uses Comand/Bulk/Interrupt pipes */ + printf("CBI with CCI"); + break; + default: + printf("(unknown 0x%02x)", + sc->sc_proto & UMASS_PROTO_WIRE); + } + + printf("; quirks = 0x%04x\n", sc->sc_quirks); +#endif + + if (sc->sc_quirks & ALT_IFACE_1) { + err = usbd_set_alt_interface_index + (uaa->device, uaa->info.bIfaceIndex, 1); + + if (err) { + DPRINTF(sc, UDMASS_USB, "could not switch to " + "Alt Interface 1\n"); + goto detach; + } + } + /* allocate all required USB transfers */ + + if (sc->sc_proto & UMASS_PROTO_BBB) { + + err = usbd_transfer_setup(uaa->device, + &uaa->info.bIfaceIndex, sc->sc_xfer, umass_bbb_config, + UMASS_T_BBB_MAX, sc, &sc->sc_mtx); + + /* skip reset first time */ + sc->sc_last_xfer_index = UMASS_T_BBB_COMMAND; + + } else if (sc->sc_proto & (UMASS_PROTO_CBI | UMASS_PROTO_CBI_I)) { + + err = usbd_transfer_setup(uaa->device, + &uaa->info.bIfaceIndex, sc->sc_xfer, umass_cbi_config, + UMASS_T_CBI_MAX, sc, &sc->sc_mtx); + + /* skip reset first time */ + sc->sc_last_xfer_index = UMASS_T_CBI_COMMAND; + + } else { + err = USB_ERR_INVAL; + } + + if (err) { + device_printf(dev, "could not setup required " + "transfers, %s\n", usbd_errstr(err)); + goto detach; + } + sc->sc_transform = + (sc->sc_proto & UMASS_PROTO_SCSI) ? &umass_scsi_transform : + (sc->sc_proto & UMASS_PROTO_UFI) ? &umass_ufi_transform : + (sc->sc_proto & UMASS_PROTO_ATAPI) ? &umass_atapi_transform : + (sc->sc_proto & UMASS_PROTO_RBC) ? &umass_rbc_transform : + &umass_no_transform; + + /* from here onwards the device can be used. */ + + if (sc->sc_quirks & SHUTTLE_INIT) { + umass_init_shuttle(sc); + } + /* get the maximum LUN supported by the device */ + + if (((sc->sc_proto & UMASS_PROTO_WIRE) == UMASS_PROTO_BBB) && + !(sc->sc_quirks & NO_GETMAXLUN)) + sc->sc_maxlun = umass_bbb_get_max_lun(sc); + else + sc->sc_maxlun = 0; + + /* Prepare the SCSI command block */ + sc->cam_scsi_sense.opcode = REQUEST_SENSE; + sc->cam_scsi_test_unit_ready.opcode = TEST_UNIT_READY; + + /* + * some devices need a delay after that the configuration value is + * set to function properly: + */ + usb_pause_mtx(NULL, hz); + + /* register the SIM */ + err = umass_cam_attach_sim(sc); + if (err) { + goto detach; + } +#ifndef __rtems__ + /* scan the SIM */ + umass_cam_attach(sc); +#endif /* __rtems__ */ + + DPRINTF(sc, UDMASS_GEN, "Attach finished\n"); + + return (0); /* success */ + +detach: + umass_detach(dev); + return (ENXIO); /* failure */ +} + +static int +umass_detach(device_t dev) +{ + struct umass_softc *sc = device_get_softc(dev); + + DPRINTF(sc, UDMASS_USB, "\n"); + + /* teardown our statemachine */ + + usbd_transfer_unsetup(sc->sc_xfer, UMASS_T_MAX); + +#if (__FreeBSD_version >= 700037) + mtx_lock(&sc->sc_mtx); +#endif + umass_cam_detach_sim(sc); + +#if (__FreeBSD_version >= 700037) + mtx_unlock(&sc->sc_mtx); +#endif + mtx_destroy(&sc->sc_mtx); + + return (0); /* success */ +} + +static void +umass_init_shuttle(struct umass_softc *sc) +{ + struct usb_device_request req; + usb_error_t err; + uint8_t status[2] = {0, 0}; + + /* + * The Linux driver does this, but no one can tell us what the + * command does. + */ + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = 1; /* XXX unknown command */ + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, sizeof(status)); + err = usbd_do_request(sc->sc_udev, NULL, &req, &status); + + DPRINTF(sc, UDMASS_GEN, "Shuttle init returned 0x%02x%02x\n", + status[0], status[1]); +} + +/* + * Generic functions to handle transfers + */ + +static void +umass_transfer_start(struct umass_softc *sc, uint8_t xfer_index) +{ + DPRINTF(sc, UDMASS_GEN, "transfer index = " + "%d\n", xfer_index); + + if (sc->sc_xfer[xfer_index]) { + sc->sc_last_xfer_index = xfer_index; + usbd_transfer_start(sc->sc_xfer[xfer_index]); + } else { + umass_cancel_ccb(sc); + } +} + +static void +umass_reset(struct umass_softc *sc) +{ + DPRINTF(sc, UDMASS_GEN, "resetting device\n"); + + /* + * stop the last transfer, if not already stopped: + */ + usbd_transfer_stop(sc->sc_xfer[sc->sc_last_xfer_index]); + umass_transfer_start(sc, 0); +} + +static void +umass_cancel_ccb(struct umass_softc *sc) +{ + union ccb *ccb; + + mtx_assert(&sc->sc_mtx, MA_OWNED); + + ccb = sc->sc_transfer.ccb; + sc->sc_transfer.ccb = NULL; + sc->sc_last_xfer_index = 0; + + if (ccb) { + (sc->sc_transfer.callback) + (sc, ccb, (sc->sc_transfer.data_len - + sc->sc_transfer.actlen), STATUS_WIRE_FAILED); + } +} + +static void +umass_tr_error(struct usb_xfer *xfer, usb_error_t error) +{ + struct umass_softc *sc = usbd_xfer_softc(xfer); + + if (error != USB_ERR_CANCELLED) { + + DPRINTF(sc, UDMASS_GEN, "transfer error, %s -> " + "reset\n", usbd_errstr(error)); + } + umass_cancel_ccb(sc); +} + +/* + * BBB protocol specific functions + */ + +static void +umass_t_bbb_reset1_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umass_softc *sc = usbd_xfer_softc(xfer); + struct usb_device_request req; + struct usb_page_cache *pc; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + umass_transfer_start(sc, UMASS_T_BBB_RESET2); + return; + + case USB_ST_SETUP: + /* + * Reset recovery (5.3.4 in Universal Serial Bus Mass Storage Class) + * + * For Reset Recovery the host shall issue in the following order: + * a) a Bulk-Only Mass Storage Reset + * b) a Clear Feature HALT to the Bulk-In endpoint + * c) a Clear Feature HALT to the Bulk-Out endpoint + * + * This is done in 3 steps, using 3 transfers: + * UMASS_T_BBB_RESET1 + * UMASS_T_BBB_RESET2 + * UMASS_T_BBB_RESET3 + */ + + DPRINTF(sc, UDMASS_BBB, "BBB reset!\n"); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_BBB_RESET; /* bulk only reset */ + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + 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); + return; + + default: /* Error */ + umass_tr_error(xfer, error); + return; + + } +} + +static void +umass_t_bbb_reset2_callback(struct usb_xfer *xfer, usb_error_t error) +{ + umass_t_bbb_data_clear_stall_callback(xfer, UMASS_T_BBB_RESET3, + UMASS_T_BBB_DATA_READ, error); +} + +static void +umass_t_bbb_reset3_callback(struct usb_xfer *xfer, usb_error_t error) +{ + umass_t_bbb_data_clear_stall_callback(xfer, UMASS_T_BBB_COMMAND, + UMASS_T_BBB_DATA_WRITE, error); +} + +static void +umass_t_bbb_data_clear_stall_callback(struct usb_xfer *xfer, + uint8_t next_xfer, uint8_t stall_xfer, usb_error_t error) +{ + struct umass_softc *sc = usbd_xfer_softc(xfer); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: +tr_transferred: + umass_transfer_start(sc, next_xfer); + return; + + case USB_ST_SETUP: + if (usbd_clear_stall_callback(xfer, sc->sc_xfer[stall_xfer])) { + goto tr_transferred; + } + return; + + default: /* Error */ + umass_tr_error(xfer, error); + return; + + } +} + +static void +umass_t_bbb_command_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umass_softc *sc = usbd_xfer_softc(xfer); + union ccb *ccb = sc->sc_transfer.ccb; + struct usb_page_cache *pc; + uint32_t tag; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + umass_transfer_start + (sc, ((sc->sc_transfer.dir == DIR_IN) ? UMASS_T_BBB_DATA_READ : + (sc->sc_transfer.dir == DIR_OUT) ? UMASS_T_BBB_DATA_WRITE : + UMASS_T_BBB_STATUS)); + return; + + case USB_ST_SETUP: + + sc->sc_status_try = 0; + + if (ccb) { + + /* + * the initial value is not important, + * as long as the values are unique: + */ + tag = UGETDW(sc->cbw.dCBWTag) + 1; + + USETDW(sc->cbw.dCBWSignature, CBWSIGNATURE); + USETDW(sc->cbw.dCBWTag, tag); + + /* + * dCBWDataTransferLength: + * This field indicates the number of bytes of data that the host + * intends to transfer on the IN or OUT Bulk endpoint(as indicated by + * the Direction bit) during the execution of this command. If this + * field is set to 0, the device will expect that no data will be + * transferred IN or OUT during this command, regardless of the value + * of the Direction bit defined in dCBWFlags. + */ + USETDW(sc->cbw.dCBWDataTransferLength, sc->sc_transfer.data_len); + + /* + * dCBWFlags: + * The bits of the Flags field are defined as follows: + * Bits 0-6 reserved + * Bit 7 Direction - this bit shall be ignored if the + * dCBWDataTransferLength field is zero. + * 0 = data Out from host to device + * 1 = data In from device to host + */ + sc->cbw.bCBWFlags = ((sc->sc_transfer.dir == DIR_IN) ? + CBWFLAGS_IN : CBWFLAGS_OUT); + sc->cbw.bCBWLUN = sc->sc_transfer.lun; + + if (sc->sc_transfer.cmd_len > sizeof(sc->cbw.CBWCDB)) { + sc->sc_transfer.cmd_len = sizeof(sc->cbw.CBWCDB); + DPRINTF(sc, UDMASS_BBB, "Truncating long command!\n"); + } + sc->cbw.bCDBLength = sc->sc_transfer.cmd_len; + + bcopy(sc->sc_transfer.cmd_data, sc->cbw.CBWCDB, + sc->sc_transfer.cmd_len); + + bzero(sc->sc_transfer.cmd_data + sc->sc_transfer.cmd_len, + sizeof(sc->cbw.CBWCDB) - sc->sc_transfer.cmd_len); + + DIF(UDMASS_BBB, umass_bbb_dump_cbw(sc, &sc->cbw)); + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_in(pc, 0, &sc->cbw, sizeof(sc->cbw)); + usbd_xfer_set_frame_len(xfer, 0, sizeof(sc->cbw)); + + usbd_transfer_submit(xfer); + } + return; + + default: /* Error */ + umass_tr_error(xfer, error); + return; + + } +} + +static void +umass_t_bbb_data_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umass_softc *sc = usbd_xfer_softc(xfer); + uint32_t max_bulk = usbd_xfer_max_len(xfer); +#ifndef UMASS_EXT_BUFFER + struct usb_page_cache *pc; +#endif + int actlen, sumlen; + + usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: +#ifndef UMASS_EXT_BUFFER + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, sc->sc_transfer.data_ptr, actlen); +#endif + sc->sc_transfer.data_rem -= actlen; + sc->sc_transfer.data_ptr += actlen; + sc->sc_transfer.actlen += actlen; + + if (actlen < sumlen) { + /* short transfer */ + sc->sc_transfer.data_rem = 0; + } + case USB_ST_SETUP: + DPRINTF(sc, UDMASS_BBB, "max_bulk=%d, data_rem=%d\n", + max_bulk, sc->sc_transfer.data_rem); + + if (sc->sc_transfer.data_rem == 0) { + umass_transfer_start(sc, UMASS_T_BBB_STATUS); + return; + } + if (max_bulk > sc->sc_transfer.data_rem) { + max_bulk = sc->sc_transfer.data_rem; + } + usbd_xfer_set_timeout(xfer, sc->sc_transfer.data_timeout); + +#ifdef UMASS_EXT_BUFFER + usbd_xfer_set_frame_data(xfer, 0, sc->sc_transfer.data_ptr, + max_bulk); +#else + usbd_xfer_set_frame_len(xfer, 0, max_bulk); +#endif + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error == USB_ERR_CANCELLED) { + umass_tr_error(xfer, error); + } else { + umass_transfer_start(sc, UMASS_T_BBB_DATA_RD_CS); + } + return; + + } +} + +static void +umass_t_bbb_data_rd_cs_callback(struct usb_xfer *xfer, usb_error_t error) +{ + umass_t_bbb_data_clear_stall_callback(xfer, UMASS_T_BBB_STATUS, + UMASS_T_BBB_DATA_READ, error); +} + +static void +umass_t_bbb_data_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umass_softc *sc = usbd_xfer_softc(xfer); + uint32_t max_bulk = usbd_xfer_max_len(xfer); +#ifndef UMASS_EXT_BUFFER + struct usb_page_cache *pc; +#endif + int actlen, sumlen; + + usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + sc->sc_transfer.data_rem -= actlen; + sc->sc_transfer.data_ptr += actlen; + sc->sc_transfer.actlen += actlen; + + if (actlen < sumlen) { + /* short transfer */ + sc->sc_transfer.data_rem = 0; + } + case USB_ST_SETUP: + DPRINTF(sc, UDMASS_BBB, "max_bulk=%d, data_rem=%d\n", + max_bulk, sc->sc_transfer.data_rem); + + if (sc->sc_transfer.data_rem == 0) { + umass_transfer_start(sc, UMASS_T_BBB_STATUS); + return; + } + if (max_bulk > sc->sc_transfer.data_rem) { + max_bulk = sc->sc_transfer.data_rem; + } + usbd_xfer_set_timeout(xfer, sc->sc_transfer.data_timeout); + +#ifdef UMASS_EXT_BUFFER + usbd_xfer_set_frame_data(xfer, 0, sc->sc_transfer.data_ptr, + max_bulk); +#else + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_in(pc, 0, sc->sc_transfer.data_ptr, max_bulk); + usbd_xfer_set_frame_len(xfer, 0, max_bulk); +#endif + + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error == USB_ERR_CANCELLED) { + umass_tr_error(xfer, error); + } else { + umass_transfer_start(sc, UMASS_T_BBB_DATA_WR_CS); + } + return; + + } +} + +static void +umass_t_bbb_data_wr_cs_callback(struct usb_xfer *xfer, usb_error_t error) +{ + umass_t_bbb_data_clear_stall_callback(xfer, UMASS_T_BBB_STATUS, + UMASS_T_BBB_DATA_WRITE, error); +} + +static void +umass_t_bbb_status_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umass_softc *sc = usbd_xfer_softc(xfer); + union ccb *ccb = sc->sc_transfer.ccb; + struct usb_page_cache *pc; + uint32_t residue; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + /* + * Do a full reset if there is something wrong with the CSW: + */ + sc->sc_status_try = 1; + + /* Zero missing parts of the CSW: */ + + if (actlen < sizeof(sc->csw)) { + bzero(&sc->csw, sizeof(sc->csw)); + } + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, &sc->csw, actlen); + + DIF(UDMASS_BBB, umass_bbb_dump_csw(sc, &sc->csw)); + + residue = UGETDW(sc->csw.dCSWDataResidue); + + if ((!residue) || (sc->sc_quirks & IGNORE_RESIDUE)) { + residue = (sc->sc_transfer.data_len - + sc->sc_transfer.actlen); + } + if (residue > sc->sc_transfer.data_len) { + DPRINTF(sc, UDMASS_BBB, "truncating residue from %d " + "to %d bytes\n", residue, sc->sc_transfer.data_len); + residue = sc->sc_transfer.data_len; + } + /* translate weird command-status signatures: */ + if (sc->sc_quirks & WRONG_CSWSIG) { + + uint32_t temp = UGETDW(sc->csw.dCSWSignature); + + if ((temp == CSWSIGNATURE_OLYMPUS_C1) || + (temp == CSWSIGNATURE_IMAGINATION_DBX1)) { + USETDW(sc->csw.dCSWSignature, CSWSIGNATURE); + } + } + /* check CSW and handle eventual error */ + if (UGETDW(sc->csw.dCSWSignature) != CSWSIGNATURE) { + DPRINTF(sc, UDMASS_BBB, "bad CSW signature 0x%08x != 0x%08x\n", + UGETDW(sc->csw.dCSWSignature), CSWSIGNATURE); + /* + * Invalid CSW: Wrong signature or wrong tag might + * indicate that we lost synchronization. Reset the + * device. + */ + goto tr_error; + } else if (UGETDW(sc->csw.dCSWTag) != UGETDW(sc->cbw.dCBWTag)) { + DPRINTF(sc, UDMASS_BBB, "Invalid CSW: tag 0x%08x should be " + "0x%08x\n", UGETDW(sc->csw.dCSWTag), + UGETDW(sc->cbw.dCBWTag)); + goto tr_error; + } else if (sc->csw.bCSWStatus > CSWSTATUS_PHASE) { + DPRINTF(sc, UDMASS_BBB, "Invalid CSW: status %d > %d\n", + sc->csw.bCSWStatus, CSWSTATUS_PHASE); + goto tr_error; + } else if (sc->csw.bCSWStatus == CSWSTATUS_PHASE) { + DPRINTF(sc, UDMASS_BBB, "Phase error, residue = " + "%d\n", residue); + goto tr_error; + } else if (sc->sc_transfer.actlen > sc->sc_transfer.data_len) { + DPRINTF(sc, UDMASS_BBB, "Buffer overrun %d > %d\n", + sc->sc_transfer.actlen, sc->sc_transfer.data_len); + goto tr_error; + } else if (sc->csw.bCSWStatus == CSWSTATUS_FAILED) { + DPRINTF(sc, UDMASS_BBB, "Command failed, residue = " + "%d\n", residue); + + sc->sc_transfer.ccb = NULL; + + sc->sc_last_xfer_index = UMASS_T_BBB_COMMAND; + + (sc->sc_transfer.callback) + (sc, ccb, residue, STATUS_CMD_FAILED); + } else { + sc->sc_transfer.ccb = NULL; + + sc->sc_last_xfer_index = UMASS_T_BBB_COMMAND; + + (sc->sc_transfer.callback) + (sc, ccb, residue, STATUS_CMD_OK); + } + return; + + case USB_ST_SETUP: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + return; + + default: +tr_error: + DPRINTF(sc, UDMASS_BBB, "Failed to read CSW: %s, try %d\n", + usbd_errstr(error), sc->sc_status_try); + + if ((error == USB_ERR_CANCELLED) || + (sc->sc_status_try)) { + umass_tr_error(xfer, error); + } else { + sc->sc_status_try = 1; + umass_transfer_start(sc, UMASS_T_BBB_DATA_RD_CS); + } + return; + + } +} + +static void +umass_command_start(struct umass_softc *sc, uint8_t dir, + void *data_ptr, uint32_t data_len, + uint32_t data_timeout, umass_callback_t *callback, + union ccb *ccb) +{ + sc->sc_transfer.lun = ccb->ccb_h.target_lun; + + /* + * NOTE: assumes that "sc->sc_transfer.cmd_data" and + * "sc->sc_transfer.cmd_len" has been properly + * initialized. + */ + + sc->sc_transfer.dir = data_len ? dir : DIR_NONE; + sc->sc_transfer.data_ptr = data_ptr; + sc->sc_transfer.data_len = data_len; + sc->sc_transfer.data_rem = data_len; + sc->sc_transfer.data_timeout = (data_timeout + UMASS_TIMEOUT); + + sc->sc_transfer.actlen = 0; + sc->sc_transfer.callback = callback; + sc->sc_transfer.ccb = ccb; + + if (sc->sc_xfer[sc->sc_last_xfer_index]) { + usbd_transfer_start(sc->sc_xfer[sc->sc_last_xfer_index]); + } else { + ccb->ccb_h.status = CAM_TID_INVALID; + xpt_done(ccb); + } +} + +static uint8_t +umass_bbb_get_max_lun(struct umass_softc *sc) +{ + 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 = UR_BBB_GET_MAX_LUN; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, 1); + + err = usbd_do_request(sc->sc_udev, NULL, &req, &buf); + if (err) { + buf = 0; + + /* Device doesn't support Get Max Lun request. */ + printf("%s: Get Max Lun not supported (%s)\n", + sc->sc_name, usbd_errstr(err)); + } + return (buf); +} + +/* + * Command/Bulk/Interrupt (CBI) specific functions + */ + +static void +umass_cbi_start_status(struct umass_softc *sc) +{ + if (sc->sc_xfer[UMASS_T_CBI_STATUS]) { + umass_transfer_start(sc, UMASS_T_CBI_STATUS); + } else { + union ccb *ccb = sc->sc_transfer.ccb; + + sc->sc_transfer.ccb = NULL; + + sc->sc_last_xfer_index = UMASS_T_CBI_COMMAND; + + (sc->sc_transfer.callback) + (sc, ccb, (sc->sc_transfer.data_len - + sc->sc_transfer.actlen), STATUS_CMD_UNKNOWN); + } +} + +static void +umass_t_cbi_reset1_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umass_softc *sc = usbd_xfer_softc(xfer); + struct usb_device_request req; + struct usb_page_cache *pc; + uint8_t buf[UMASS_CBI_DIAGNOSTIC_CMDLEN]; + + uint8_t i; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + umass_transfer_start(sc, UMASS_T_CBI_RESET2); + break; + + case USB_ST_SETUP: + /* + * Command Block Reset Protocol + * + * First send a reset request to the device. Then clear + * any possibly stalled bulk endpoints. + * + * This is done in 3 steps, using 3 transfers: + * UMASS_T_CBI_RESET1 + * UMASS_T_CBI_RESET2 + * UMASS_T_CBI_RESET3 + * UMASS_T_CBI_RESET4 (only if there is an interrupt endpoint) + */ + + DPRINTF(sc, UDMASS_CBI, "CBI reset!\n"); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_CBI_ADSC; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + USETW(req.wLength, UMASS_CBI_DIAGNOSTIC_CMDLEN); + + /* + * The 0x1d code is the SEND DIAGNOSTIC command. To + * distinguish between the two, the last 10 bytes of the CBL + * is filled with 0xff (section 2.2 of the CBI + * specification) + */ + buf[0] = 0x1d; /* Command Block Reset */ + buf[1] = 0x04; + + for (i = 2; i < UMASS_CBI_DIAGNOSTIC_CMDLEN; i++) { + buf[i] = 0xff; + } + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_in(pc, 0, &req, sizeof(req)); + pc = usbd_xfer_get_frame(xfer, 1); + usbd_copy_in(pc, 0, buf, sizeof(buf)); + + usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); + usbd_xfer_set_frame_len(xfer, 1, sizeof(buf)); + usbd_xfer_set_frames(xfer, 2); + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + if (error == USB_ERR_CANCELLED) + umass_tr_error(xfer, error); + else + umass_transfer_start(sc, UMASS_T_CBI_RESET2); + break; + + } +} + +static void +umass_t_cbi_reset2_callback(struct usb_xfer *xfer, usb_error_t error) +{ + umass_t_cbi_data_clear_stall_callback(xfer, UMASS_T_CBI_RESET3, + UMASS_T_CBI_DATA_READ, error); +} + +static void +umass_t_cbi_reset3_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umass_softc *sc = usbd_xfer_softc(xfer); + + umass_t_cbi_data_clear_stall_callback + (xfer, (sc->sc_xfer[UMASS_T_CBI_RESET4] && + sc->sc_xfer[UMASS_T_CBI_STATUS]) ? + UMASS_T_CBI_RESET4 : UMASS_T_CBI_COMMAND, + UMASS_T_CBI_DATA_WRITE, error); +} + +static void +umass_t_cbi_reset4_callback(struct usb_xfer *xfer, usb_error_t error) +{ + umass_t_cbi_data_clear_stall_callback(xfer, UMASS_T_CBI_COMMAND, + UMASS_T_CBI_STATUS, error); +} + +static void +umass_t_cbi_data_clear_stall_callback(struct usb_xfer *xfer, + uint8_t next_xfer, uint8_t stall_xfer, usb_error_t error) +{ + struct umass_softc *sc = usbd_xfer_softc(xfer); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: +tr_transferred: + if (next_xfer == UMASS_T_CBI_STATUS) { + umass_cbi_start_status(sc); + } else { + umass_transfer_start(sc, next_xfer); + } + break; + + case USB_ST_SETUP: + if (usbd_clear_stall_callback(xfer, sc->sc_xfer[stall_xfer])) { + goto tr_transferred; /* should not happen */ + } + break; + + default: /* Error */ + umass_tr_error(xfer, error); + break; + + } +} + +static void +umass_t_cbi_command_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umass_softc *sc = usbd_xfer_softc(xfer); + union ccb *ccb = sc->sc_transfer.ccb; + struct usb_device_request req; + struct usb_page_cache *pc; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (sc->sc_transfer.dir == DIR_NONE) { + umass_cbi_start_status(sc); + } else { + umass_transfer_start + (sc, (sc->sc_transfer.dir == DIR_IN) ? + UMASS_T_CBI_DATA_READ : UMASS_T_CBI_DATA_WRITE); + } + break; + + case USB_ST_SETUP: + + if (ccb) { + + /* + * do a CBI transfer with cmd_len bytes from + * cmd_data, possibly a data phase of data_len + * bytes from/to the device and finally a status + * read phase. + */ + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_CBI_ADSC; + USETW(req.wValue, 0); + req.wIndex[0] = sc->sc_iface_no; + req.wIndex[1] = 0; + req.wLength[0] = sc->sc_transfer.cmd_len; + req.wLength[1] = 0; + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_in(pc, 0, &req, sizeof(req)); + pc = usbd_xfer_get_frame(xfer, 1); + usbd_copy_in(pc, 0, sc->sc_transfer.cmd_data, + sc->sc_transfer.cmd_len); + + usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); + usbd_xfer_set_frame_len(xfer, 1, sc->sc_transfer.cmd_len); + usbd_xfer_set_frames(xfer, + sc->sc_transfer.cmd_len ? 2 : 1); + + DIF(UDMASS_CBI, + umass_cbi_dump_cmd(sc, + sc->sc_transfer.cmd_data, + sc->sc_transfer.cmd_len)); + + usbd_transfer_submit(xfer); + } + break; + + default: /* Error */ + umass_tr_error(xfer, error); + /* skip reset */ + sc->sc_last_xfer_index = UMASS_T_CBI_COMMAND; + break; + } +} + +static void +umass_t_cbi_data_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umass_softc *sc = usbd_xfer_softc(xfer); + uint32_t max_bulk = usbd_xfer_max_len(xfer); +#ifndef UMASS_EXT_BUFFER + struct usb_page_cache *pc; +#endif + int actlen, sumlen; + + usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: +#ifndef UMASS_EXT_BUFFER + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, sc->sc_transfer.data_ptr, actlen); +#endif + sc->sc_transfer.data_rem -= actlen; + sc->sc_transfer.data_ptr += actlen; + sc->sc_transfer.actlen += actlen; + + if (actlen < sumlen) { + /* short transfer */ + sc->sc_transfer.data_rem = 0; + } + case USB_ST_SETUP: + DPRINTF(sc, UDMASS_CBI, "max_bulk=%d, data_rem=%d\n", + max_bulk, sc->sc_transfer.data_rem); + + if (sc->sc_transfer.data_rem == 0) { + umass_cbi_start_status(sc); + break; + } + if (max_bulk > sc->sc_transfer.data_rem) { + max_bulk = sc->sc_transfer.data_rem; + } + usbd_xfer_set_timeout(xfer, sc->sc_transfer.data_timeout); + +#ifdef UMASS_EXT_BUFFER + usbd_xfer_set_frame_data(xfer, 0, sc->sc_transfer.data_ptr, + max_bulk); +#else + usbd_xfer_set_frame_len(xfer, 0, max_bulk); +#endif + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + if ((error == USB_ERR_CANCELLED) || + (sc->sc_transfer.callback != &umass_cam_cb)) { + umass_tr_error(xfer, error); + } else { + umass_transfer_start(sc, UMASS_T_CBI_DATA_RD_CS); + } + break; + + } +} + +static void +umass_t_cbi_data_rd_cs_callback(struct usb_xfer *xfer, usb_error_t error) +{ + umass_t_cbi_data_clear_stall_callback(xfer, UMASS_T_CBI_STATUS, + UMASS_T_CBI_DATA_READ, error); +} + +static void +umass_t_cbi_data_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umass_softc *sc = usbd_xfer_softc(xfer); + uint32_t max_bulk = usbd_xfer_max_len(xfer); +#ifndef UMASS_EXT_BUFFER + struct usb_page_cache *pc; +#endif + int actlen, sumlen; + + usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + sc->sc_transfer.data_rem -= actlen; + sc->sc_transfer.data_ptr += actlen; + sc->sc_transfer.actlen += actlen; + + if (actlen < sumlen) { + /* short transfer */ + sc->sc_transfer.data_rem = 0; + } + case USB_ST_SETUP: + DPRINTF(sc, UDMASS_CBI, "max_bulk=%d, data_rem=%d\n", + max_bulk, sc->sc_transfer.data_rem); + + if (sc->sc_transfer.data_rem == 0) { + umass_cbi_start_status(sc); + break; + } + if (max_bulk > sc->sc_transfer.data_rem) { + max_bulk = sc->sc_transfer.data_rem; + } + usbd_xfer_set_timeout(xfer, sc->sc_transfer.data_timeout); + +#ifdef UMASS_EXT_BUFFER + usbd_xfer_set_frame_data(xfer, 0, sc->sc_transfer.data_ptr, + max_bulk); +#else + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_in(pc, 0, sc->sc_transfer.data_ptr, max_bulk); + usbd_xfer_set_frame_len(xfer, 0, max_bulk); +#endif + + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + if ((error == USB_ERR_CANCELLED) || + (sc->sc_transfer.callback != &umass_cam_cb)) { + umass_tr_error(xfer, error); + } else { + umass_transfer_start(sc, UMASS_T_CBI_DATA_WR_CS); + } + break; + + } +} + +static void +umass_t_cbi_data_wr_cs_callback(struct usb_xfer *xfer, usb_error_t error) +{ + umass_t_cbi_data_clear_stall_callback(xfer, UMASS_T_CBI_STATUS, + UMASS_T_CBI_DATA_WRITE, error); +} + +static void +umass_t_cbi_status_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct umass_softc *sc = usbd_xfer_softc(xfer); + union ccb *ccb = sc->sc_transfer.ccb; + struct usb_page_cache *pc; + uint32_t residue; + uint8_t status; + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + if (actlen < sizeof(sc->sbl)) { + goto tr_setup; + } + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, &sc->sbl, sizeof(sc->sbl)); + + residue = (sc->sc_transfer.data_len - + sc->sc_transfer.actlen); + + /* dissect the information in the buffer */ + + if (sc->sc_proto & UMASS_PROTO_UFI) { + + /* + * Section 3.4.3.1.3 specifies that the UFI command + * protocol returns an ASC and ASCQ in the interrupt + * data block. + */ + + DPRINTF(sc, UDMASS_CBI, "UFI CCI, ASC = 0x%02x, " + "ASCQ = 0x%02x\n", sc->sbl.ufi.asc, + sc->sbl.ufi.ascq); + + status = (((sc->sbl.ufi.asc == 0) && + (sc->sbl.ufi.ascq == 0)) ? + STATUS_CMD_OK : STATUS_CMD_FAILED); + + sc->sc_transfer.ccb = NULL; + + sc->sc_last_xfer_index = UMASS_T_CBI_COMMAND; + + (sc->sc_transfer.callback) + (sc, ccb, residue, status); + + break; + + } else { + + /* Command Interrupt Data Block */ + + DPRINTF(sc, UDMASS_CBI, "type=0x%02x, value=0x%02x\n", + sc->sbl.common.type, sc->sbl.common.value); + + if (sc->sbl.common.type == IDB_TYPE_CCI) { + + status = (sc->sbl.common.value & IDB_VALUE_STATUS_MASK); + + status = ((status == IDB_VALUE_PASS) ? STATUS_CMD_OK : + (status == IDB_VALUE_FAIL) ? STATUS_CMD_FAILED : + (status == IDB_VALUE_PERSISTENT) ? STATUS_CMD_FAILED : + STATUS_WIRE_FAILED); + + sc->sc_transfer.ccb = NULL; + + sc->sc_last_xfer_index = UMASS_T_CBI_COMMAND; + + (sc->sc_transfer.callback) + (sc, ccb, residue, status); + + break; + } + } + + /* 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 */ + DPRINTF(sc, UDMASS_CBI, "Failed to read CSW: %s\n", + usbd_errstr(error)); + umass_tr_error(xfer, error); + break; + + } +} + +/* + * CAM specific functions (used by SCSI, UFI, 8070i (ATAPI)) + */ + +static int +umass_cam_attach_sim(struct umass_softc *sc) +{ + struct cam_devq *devq; /* Per device Queue */ + + /* + * A HBA is attached to the CAM layer. + * + * The CAM layer will then after a while start probing for devices on + * the bus. The number of SIMs is limited to one. + */ + + devq = cam_simq_alloc(1 /* maximum openings */ ); + if (devq == NULL) { + return (ENOMEM); + } + sc->sc_sim = cam_sim_alloc + (&umass_cam_action, &umass_cam_poll, + DEVNAME_SIM, + sc /* priv */ , + sc->sc_unit /* unit number */ , +#if (__FreeBSD_version >= 700037) + &sc->sc_mtx /* mutex */ , +#endif + 1 /* maximum device openings */ , + 0 /* maximum tagged device openings */ , + devq); + + if (sc->sc_sim == NULL) { + cam_simq_free(devq); + return (ENOMEM); + } + +#if (__FreeBSD_version >= 700037) + mtx_lock(&sc->sc_mtx); +#endif + +#if (__FreeBSD_version >= 700048) + if (xpt_bus_register(sc->sc_sim, sc->sc_dev, sc->sc_unit) != CAM_SUCCESS) { + mtx_unlock(&sc->sc_mtx); + return (ENOMEM); + } +#else + if (xpt_bus_register(sc->sc_sim, sc->sc_unit) != CAM_SUCCESS) { +#if (__FreeBSD_version >= 700037) + mtx_unlock(&sc->sc_mtx); +#endif + return (ENOMEM); + } +#endif + +#if (__FreeBSD_version >= 700037) + mtx_unlock(&sc->sc_mtx); +#endif + return (0); +} + +#ifndef __rtems__ +static void +umass_cam_attach(struct umass_softc *sc) +{ +#ifndef USB_DEBUG + if (bootverbose) +#endif + printf("%s:%d:%d:%d: Attached to scbus%d\n", + sc->sc_name, cam_sim_path(sc->sc_sim), + sc->sc_unit, CAM_LUN_WILDCARD, + cam_sim_path(sc->sc_sim)); +} +#endif /* __rtems__ */ + +/* umass_cam_detach + * detach from the CAM layer + */ + +static void +umass_cam_detach_sim(struct umass_softc *sc) +{ + if (sc->sc_sim != NULL) { + if (xpt_bus_deregister(cam_sim_path(sc->sc_sim))) { + /* accessing the softc is not possible after this */ + sc->sc_sim->softc = UMASS_GONE; + cam_sim_free(sc->sc_sim, /* free_devq */ TRUE); + } else { + panic("%s: CAM layer is busy\n", + sc->sc_name); + } + sc->sc_sim = NULL; + } +} + +/* umass_cam_action + * CAM requests for action come through here + */ + +static void +umass_cam_action(struct cam_sim *sim, union ccb *ccb) +{ + struct umass_softc *sc = (struct umass_softc *)sim->softc; + + if (sc == UMASS_GONE || + (sc != NULL && !usbd_device_attached(sc->sc_udev))) { + ccb->ccb_h.status = CAM_SEL_TIMEOUT; + xpt_done(ccb); + return; + } + if (sc) { +#if (__FreeBSD_version < 700037) + mtx_lock(&sc->sc_mtx); +#endif + } + /* + * Verify, depending on the operation to perform, that we either got + * a valid sc, because an existing target was referenced, or + * otherwise the SIM is addressed. + * + * This avoids bombing out at a printf and does give the CAM layer some + * sensible feedback on errors. + */ + switch (ccb->ccb_h.func_code) { + case XPT_SCSI_IO: + case XPT_RESET_DEV: + case XPT_GET_TRAN_SETTINGS: + case XPT_SET_TRAN_SETTINGS: + case XPT_CALC_GEOMETRY: + /* the opcodes requiring a target. These should never occur. */ + if (sc == NULL) { + DPRINTF(sc, UDMASS_GEN, "%s:%d:%d:%d:func_code 0x%04x: " + "Invalid target (target needed)\n", + DEVNAME_SIM, cam_sim_path(sc->sc_sim), + ccb->ccb_h.target_id, ccb->ccb_h.target_lun, + ccb->ccb_h.func_code); + + ccb->ccb_h.status = CAM_TID_INVALID; + xpt_done(ccb); + goto done; + } + break; + case XPT_PATH_INQ: + case XPT_NOOP: + /* + * The opcodes sometimes aimed at a target (sc is valid), + * sometimes aimed at the SIM (sc is invalid and target is + * CAM_TARGET_WILDCARD) + */ + if ((sc == NULL) && + (ccb->ccb_h.target_id != CAM_TARGET_WILDCARD)) { + DPRINTF(sc, UDMASS_SCSI, "%s:%d:%d:%d:func_code 0x%04x: " + "Invalid target (no wildcard)\n", + DEVNAME_SIM, cam_sim_path(sc->sc_sim), + ccb->ccb_h.target_id, ccb->ccb_h.target_lun, + ccb->ccb_h.func_code); + + ccb->ccb_h.status = CAM_TID_INVALID; + xpt_done(ccb); + goto done; + } + break; + default: + /* XXX Hm, we should check the input parameters */ + break; + } + + /* Perform the requested action */ + switch (ccb->ccb_h.func_code) { + case XPT_SCSI_IO: + { + uint8_t *cmd; + uint8_t dir; + + if (ccb->csio.ccb_h.flags & CAM_CDB_POINTER) { + cmd = (uint8_t *)(ccb->csio.cdb_io.cdb_ptr); + } else { + cmd = (uint8_t *)(ccb->csio.cdb_io.cdb_bytes); + } + + DPRINTF(sc, UDMASS_SCSI, "%d:%d:%d:XPT_SCSI_IO: " + "cmd: 0x%02x, flags: 0x%02x, " + "%db cmd/%db data/%db sense\n", + cam_sim_path(sc->sc_sim), ccb->ccb_h.target_id, + ccb->ccb_h.target_lun, cmd[0], + ccb->ccb_h.flags & CAM_DIR_MASK, ccb->csio.cdb_len, + ccb->csio.dxfer_len, ccb->csio.sense_len); + + if (sc->sc_transfer.ccb) { + DPRINTF(sc, UDMASS_SCSI, "%d:%d:%d:XPT_SCSI_IO: " + "I/O in progress, deferring\n", + cam_sim_path(sc->sc_sim), ccb->ccb_h.target_id, + ccb->ccb_h.target_lun); + ccb->ccb_h.status = CAM_SCSI_BUSY; + xpt_done(ccb); + goto done; + } + switch (ccb->ccb_h.flags & CAM_DIR_MASK) { + case CAM_DIR_IN: + dir = DIR_IN; + break; + case CAM_DIR_OUT: + dir = DIR_OUT; + DIF(UDMASS_SCSI, + umass_dump_buffer(sc, ccb->csio.data_ptr, + ccb->csio.dxfer_len, 48)); + break; + default: + dir = DIR_NONE; + } + + ccb->ccb_h.status = CAM_REQ_INPROG | CAM_SIM_QUEUED; + + /* + * sc->sc_transform will convert the command to the + * command format needed by the specific command set + * and return the converted command in + * "sc->sc_transfer.cmd_data" + */ + if (umass_std_transform(sc, ccb, cmd, ccb->csio.cdb_len)) { + + if (sc->sc_transfer.cmd_data[0] == INQUIRY) { + const char *pserial; + + pserial = usb_get_serial(sc->sc_udev); + + /* + * Umass devices don't generally report their serial numbers + * in the usual SCSI way. Emulate it here. + */ + if ((sc->sc_transfer.cmd_data[1] & SI_EVPD) && + (sc->sc_transfer.cmd_data[2] == SVPD_UNIT_SERIAL_NUMBER) && + (pserial[0] != '\0')) { + struct scsi_vpd_unit_serial_number *vpd_serial; + + vpd_serial = (struct scsi_vpd_unit_serial_number *)ccb->csio.data_ptr; + vpd_serial->length = strlen(pserial); + if (vpd_serial->length > sizeof(vpd_serial->serial_num)) + vpd_serial->length = sizeof(vpd_serial->serial_num); + memcpy(vpd_serial->serial_num, pserial, vpd_serial->length); + ccb->csio.scsi_status = SCSI_STATUS_OK; + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + goto done; + } + + /* + * Handle EVPD inquiry for broken devices first + * NO_INQUIRY also implies NO_INQUIRY_EVPD + */ + if ((sc->sc_quirks & (NO_INQUIRY_EVPD | NO_INQUIRY)) && + (sc->sc_transfer.cmd_data[1] & SI_EVPD)) { + struct scsi_sense_data *sense; + + sense = &ccb->csio.sense_data; + bzero(sense, sizeof(*sense)); + sense->error_code = SSD_CURRENT_ERROR; + sense->flags = SSD_KEY_ILLEGAL_REQUEST; + sense->add_sense_code = 0x24; + sense->extra_len = 10; + ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND; + ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR | + CAM_AUTOSNS_VALID; + xpt_done(ccb); + goto done; + } + /* + * Return fake inquiry data for + * broken devices + */ + if (sc->sc_quirks & NO_INQUIRY) { + memcpy(ccb->csio.data_ptr, &fake_inq_data, + sizeof(fake_inq_data)); + ccb->csio.scsi_status = SCSI_STATUS_OK; + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + goto done; + } + if (sc->sc_quirks & FORCE_SHORT_INQUIRY) { + ccb->csio.dxfer_len = SHORT_INQUIRY_LENGTH; + } + } else if (sc->sc_transfer.cmd_data[0] == SYNCHRONIZE_CACHE) { + if (sc->sc_quirks & NO_SYNCHRONIZE_CACHE) { + ccb->csio.scsi_status = SCSI_STATUS_OK; + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + goto done; + } + } + umass_command_start(sc, dir, ccb->csio.data_ptr, + ccb->csio.dxfer_len, + ccb->ccb_h.timeout, + &umass_cam_cb, ccb); + } + break; + } + case XPT_PATH_INQ: + { + struct ccb_pathinq *cpi = &ccb->cpi; + + DPRINTF(sc, UDMASS_SCSI, "%d:%d:%d:XPT_PATH_INQ:.\n", + sc ? cam_sim_path(sc->sc_sim) : -1, ccb->ccb_h.target_id, + ccb->ccb_h.target_lun); + + /* host specific information */ + cpi->version_num = 1; + cpi->hba_inquiry = 0; + cpi->target_sprt = 0; + cpi->hba_misc = PIM_NO_6_BYTE; + cpi->hba_eng_cnt = 0; + cpi->max_target = UMASS_SCSIID_MAX; /* one target */ + cpi->initiator_id = UMASS_SCSIID_HOST; + strlcpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); + strlcpy(cpi->hba_vid, "USB SCSI", HBA_IDLEN); + strlcpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); + cpi->unit_number = cam_sim_unit(sim); + cpi->bus_id = sc->sc_unit; +#if (__FreeBSD_version >= 700025) + cpi->protocol = PROTO_SCSI; + cpi->protocol_version = SCSI_REV_2; + cpi->transport = XPORT_USB; + cpi->transport_version = 0; +#endif + if (sc == NULL) { + cpi->base_transfer_speed = 0; + cpi->max_lun = 0; + } else { + if (sc->sc_quirks & FLOPPY_SPEED) { + cpi->base_transfer_speed = + UMASS_FLOPPY_TRANSFER_SPEED; + } else { + switch (usbd_get_speed(sc->sc_udev)) { + case USB_SPEED_SUPER: + cpi->base_transfer_speed = + UMASS_SUPER_TRANSFER_SPEED; + cpi->maxio = MAXPHYS; + break; + case USB_SPEED_HIGH: + cpi->base_transfer_speed = + UMASS_HIGH_TRANSFER_SPEED; + break; + default: + cpi->base_transfer_speed = + UMASS_FULL_TRANSFER_SPEED; + break; + } + } + cpi->max_lun = sc->sc_maxlun; + } + + cpi->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + case XPT_RESET_DEV: + { + DPRINTF(sc, UDMASS_SCSI, "%d:%d:%d:XPT_RESET_DEV:.\n", + cam_sim_path(sc->sc_sim), ccb->ccb_h.target_id, + ccb->ccb_h.target_lun); + + umass_reset(sc); + + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + case XPT_GET_TRAN_SETTINGS: + { + struct ccb_trans_settings *cts = &ccb->cts; + + DPRINTF(sc, UDMASS_SCSI, "%d:%d:%d:XPT_GET_TRAN_SETTINGS:.\n", + cam_sim_path(sc->sc_sim), ccb->ccb_h.target_id, + ccb->ccb_h.target_lun); + +#if (__FreeBSD_version >= 700025) + cts->protocol = PROTO_SCSI; + cts->protocol_version = SCSI_REV_2; + cts->transport = XPORT_USB; + cts->transport_version = 0; + cts->xport_specific.valid = 0; +#else + cts->valid = 0; + cts->flags = 0; /* no disconnection, tagging */ +#endif + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + case XPT_SET_TRAN_SETTINGS: + { + DPRINTF(sc, UDMASS_SCSI, "%d:%d:%d:XPT_SET_TRAN_SETTINGS:.\n", + cam_sim_path(sc->sc_sim), ccb->ccb_h.target_id, + ccb->ccb_h.target_lun); + + ccb->ccb_h.status = CAM_FUNC_NOTAVAIL; + xpt_done(ccb); + break; + } +#ifndef __rtems__ + case XPT_CALC_GEOMETRY: + { + cam_calc_geometry(&ccb->ccg, /* extended */ 1); + xpt_done(ccb); + break; + } +#endif /* __rtems__ */ + case XPT_NOOP: + { + DPRINTF(sc, UDMASS_SCSI, "%d:%d:%d:XPT_NOOP:.\n", + sc ? cam_sim_path(sc->sc_sim) : -1, ccb->ccb_h.target_id, + ccb->ccb_h.target_lun); + + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + default: + DPRINTF(sc, UDMASS_SCSI, "%d:%d:%d:func_code 0x%04x: " + "Not implemented\n", + sc ? cam_sim_path(sc->sc_sim) : -1, ccb->ccb_h.target_id, + ccb->ccb_h.target_lun, ccb->ccb_h.func_code); + + ccb->ccb_h.status = CAM_FUNC_NOTAVAIL; + xpt_done(ccb); + break; + } + +done: +#if (__FreeBSD_version < 700037) + if (sc) { + mtx_unlock(&sc->sc_mtx); + } +#endif + return; +} + +static void +umass_cam_poll(struct cam_sim *sim) +{ + struct umass_softc *sc = (struct umass_softc *)sim->softc; + + if (sc == UMASS_GONE) + return; + + DPRINTF(sc, UDMASS_SCSI, "CAM poll\n"); + + usbd_transfer_poll(sc->sc_xfer, UMASS_T_MAX); +} + + +/* umass_cam_cb + * finalise a completed CAM command + */ + +static void +umass_cam_cb(struct umass_softc *sc, union ccb *ccb, uint32_t residue, + uint8_t status) +{ + ccb->csio.resid = residue; + + switch (status) { + case STATUS_CMD_OK: + ccb->ccb_h.status = CAM_REQ_CMP; + if ((sc->sc_quirks & READ_CAPACITY_OFFBY1) && + (ccb->ccb_h.func_code == XPT_SCSI_IO) && + (ccb->csio.cdb_io.cdb_bytes[0] == READ_CAPACITY)) { + struct scsi_read_capacity_data *rcap; + uint32_t maxsector; + + rcap = (void *)(ccb->csio.data_ptr); + maxsector = scsi_4btoul(rcap->addr) - 1; + scsi_ulto4b(maxsector, rcap->addr); + } + /* + * We have to add SVPD_UNIT_SERIAL_NUMBER to the list + * of pages supported by the device - otherwise, CAM + * will never ask us for the serial number if the + * device cannot handle that by itself. + */ + if (ccb->ccb_h.func_code == XPT_SCSI_IO && + sc->sc_transfer.cmd_data[0] == INQUIRY && + (sc->sc_transfer.cmd_data[1] & SI_EVPD) && + sc->sc_transfer.cmd_data[2] == SVPD_SUPPORTED_PAGE_LIST && + (usb_get_serial(sc->sc_udev)[0] != '\0')) { + struct ccb_scsiio *csio; + struct scsi_vpd_supported_page_list *page_list; + + csio = &ccb->csio; + page_list = (struct scsi_vpd_supported_page_list *)csio->data_ptr; + if (page_list->length + 1 < SVPD_SUPPORTED_PAGES_SIZE) { + page_list->list[page_list->length] = SVPD_UNIT_SERIAL_NUMBER; + page_list->length++; + } + } + xpt_done(ccb); + break; + + case STATUS_CMD_UNKNOWN: + case STATUS_CMD_FAILED: + + /* fetch sense data */ + + /* the rest of the command was filled in at attach */ + sc->cam_scsi_sense.length = ccb->csio.sense_len; + + DPRINTF(sc, UDMASS_SCSI, "Fetching %d bytes of " + "sense data\n", ccb->csio.sense_len); + + if (umass_std_transform(sc, ccb, &sc->cam_scsi_sense.opcode, + sizeof(sc->cam_scsi_sense))) { + + if ((sc->sc_quirks & FORCE_SHORT_INQUIRY) && + (sc->sc_transfer.cmd_data[0] == INQUIRY)) { + ccb->csio.sense_len = SHORT_INQUIRY_LENGTH; + } + umass_command_start(sc, DIR_IN, &ccb->csio.sense_data.error_code, + ccb->csio.sense_len, ccb->ccb_h.timeout, + &umass_cam_sense_cb, ccb); + } + break; + + default: + /* + * The wire protocol failed and will hopefully have + * recovered. We return an error to CAM and let CAM + * retry the command if necessary. In case of SCSI IO + * commands we ask the CAM layer to check the + * condition first. This is a quick hack to make + * certain devices work. + */ + if (ccb->ccb_h.func_code == XPT_SCSI_IO) { + ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR; + ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND; + } else { + ccb->ccb_h.status = CAM_REQ_CMP_ERR; + } + xpt_done(ccb); + break; + } +} + +/* + * Finalise a completed autosense operation + */ +static void +umass_cam_sense_cb(struct umass_softc *sc, union ccb *ccb, uint32_t residue, + uint8_t status) +{ + uint8_t *cmd; + uint8_t key; + + switch (status) { + case STATUS_CMD_OK: + case STATUS_CMD_UNKNOWN: + case STATUS_CMD_FAILED: + + if (ccb->csio.ccb_h.flags & CAM_CDB_POINTER) { + cmd = (uint8_t *)(ccb->csio.cdb_io.cdb_ptr); + } else { + cmd = (uint8_t *)(ccb->csio.cdb_io.cdb_bytes); + } + + key = (ccb->csio.sense_data.flags & SSD_KEY); + + /* + * Getting sense data always succeeds (apart from wire + * failures): + */ + if ((sc->sc_quirks & RS_NO_CLEAR_UA) && + (cmd[0] == INQUIRY) && + (key == SSD_KEY_UNIT_ATTENTION)) { + /* + * Ignore unit attention errors in the case where + * the Unit Attention state is not cleared on + * REQUEST SENSE. They will appear again at the next + * command. + */ + ccb->ccb_h.status = CAM_REQ_CMP; + } else if (key == SSD_KEY_NO_SENSE) { + /* + * No problem after all (in the case of CBI without + * CCI) + */ + ccb->ccb_h.status = CAM_REQ_CMP; + } else if ((sc->sc_quirks & RS_NO_CLEAR_UA) && + (cmd[0] == READ_CAPACITY) && + (key == SSD_KEY_UNIT_ATTENTION)) { + /* + * Some devices do not clear the unit attention error + * on request sense. We insert a test unit ready + * command to make sure we clear the unit attention + * condition, then allow the retry to proceed as + * usual. + */ + + ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR + | CAM_AUTOSNS_VALID; + ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND; + +#if 0 + DELAY(300000); +#endif + DPRINTF(sc, UDMASS_SCSI, "Doing a sneaky" + "TEST_UNIT_READY\n"); + + /* the rest of the command was filled in at attach */ + + if (umass_std_transform(sc, ccb, + &sc->cam_scsi_test_unit_ready.opcode, + sizeof(sc->cam_scsi_test_unit_ready))) { + umass_command_start(sc, DIR_NONE, NULL, 0, + ccb->ccb_h.timeout, + &umass_cam_quirk_cb, ccb); + } + break; + } else { + ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR + | CAM_AUTOSNS_VALID; + ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND; + } + xpt_done(ccb); + break; + + default: + DPRINTF(sc, UDMASS_SCSI, "Autosense failed, " + "status %d\n", status); + ccb->ccb_h.status = CAM_AUTOSENSE_FAIL; + xpt_done(ccb); + } +} + +/* + * This completion code just handles the fact that we sent a test-unit-ready + * after having previously failed a READ CAPACITY with CHECK_COND. Even + * though this command succeeded, we have to tell CAM to retry. + */ +static void +umass_cam_quirk_cb(struct umass_softc *sc, union ccb *ccb, uint32_t residue, + uint8_t status) +{ + DPRINTF(sc, UDMASS_SCSI, "Test unit ready " + "returned status %d\n", status); + + ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR + | CAM_AUTOSNS_VALID; + ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND; + xpt_done(ccb); +} + +/* + * SCSI specific functions + */ + +static uint8_t +umass_scsi_transform(struct umass_softc *sc, uint8_t *cmd_ptr, + uint8_t cmd_len) +{ + if ((cmd_len == 0) || + (cmd_len > sizeof(sc->sc_transfer.cmd_data))) { + DPRINTF(sc, UDMASS_SCSI, "Invalid command " + "length: %d bytes\n", cmd_len); + return (0); /* failure */ + } + sc->sc_transfer.cmd_len = cmd_len; + + switch (cmd_ptr[0]) { + case TEST_UNIT_READY: + if (sc->sc_quirks & NO_TEST_UNIT_READY) { + DPRINTF(sc, UDMASS_SCSI, "Converted TEST_UNIT_READY " + "to START_UNIT\n"); + bzero(sc->sc_transfer.cmd_data, cmd_len); + sc->sc_transfer.cmd_data[0] = START_STOP_UNIT; + sc->sc_transfer.cmd_data[4] = SSS_START; + return (1); + } + break; + + case INQUIRY: + /* + * some drives wedge when asked for full inquiry + * information. + */ + if (sc->sc_quirks & FORCE_SHORT_INQUIRY) { + bcopy(cmd_ptr, sc->sc_transfer.cmd_data, cmd_len); + sc->sc_transfer.cmd_data[4] = SHORT_INQUIRY_LENGTH; + return (1); + } + break; + } + + bcopy(cmd_ptr, sc->sc_transfer.cmd_data, cmd_len); + return (1); +} + +static uint8_t +umass_rbc_transform(struct umass_softc *sc, uint8_t *cmd_ptr, uint8_t cmd_len) +{ + if ((cmd_len == 0) || + (cmd_len > sizeof(sc->sc_transfer.cmd_data))) { + DPRINTF(sc, UDMASS_SCSI, "Invalid command " + "length: %d bytes\n", cmd_len); + return (0); /* failure */ + } + switch (cmd_ptr[0]) { + /* these commands are defined in RBC: */ + case READ_10: + case READ_CAPACITY: + case START_STOP_UNIT: + case SYNCHRONIZE_CACHE: + case WRITE_10: + case 0x2f: /* VERIFY_10 is absent from + * scsi_all.h??? */ + case INQUIRY: + case MODE_SELECT_10: + case MODE_SENSE_10: + case TEST_UNIT_READY: + case WRITE_BUFFER: + /* + * The following commands are not listed in my copy of the + * RBC specs. CAM however seems to want those, and at least + * the Sony DSC device appears to support those as well + */ + case REQUEST_SENSE: + case PREVENT_ALLOW: + + bcopy(cmd_ptr, sc->sc_transfer.cmd_data, cmd_len); + + if ((sc->sc_quirks & RBC_PAD_TO_12) && (cmd_len < 12)) { + bzero(sc->sc_transfer.cmd_data + cmd_len, 12 - cmd_len); + cmd_len = 12; + } + sc->sc_transfer.cmd_len = cmd_len; + return (1); /* sucess */ + + /* All other commands are not legal in RBC */ + default: + DPRINTF(sc, UDMASS_SCSI, "Unsupported RBC " + "command 0x%02x\n", cmd_ptr[0]); + return (0); /* failure */ + } +} + +static uint8_t +umass_ufi_transform(struct umass_softc *sc, uint8_t *cmd_ptr, + uint8_t cmd_len) +{ + if ((cmd_len == 0) || + (cmd_len > sizeof(sc->sc_transfer.cmd_data))) { + DPRINTF(sc, UDMASS_SCSI, "Invalid command " + "length: %d bytes\n", cmd_len); + return (0); /* failure */ + } + /* An UFI command is always 12 bytes in length */ + sc->sc_transfer.cmd_len = UFI_COMMAND_LENGTH; + + /* Zero the command data */ + bzero(sc->sc_transfer.cmd_data, UFI_COMMAND_LENGTH); + + switch (cmd_ptr[0]) { + /* + * Commands of which the format has been verified. They + * should work. Copy the command into the (zeroed out) + * destination buffer. + */ + case TEST_UNIT_READY: + if (sc->sc_quirks & NO_TEST_UNIT_READY) { + /* + * Some devices do not support this command. Start + * Stop Unit should give the same results + */ + DPRINTF(sc, UDMASS_UFI, "Converted TEST_UNIT_READY " + "to START_UNIT\n"); + + sc->sc_transfer.cmd_data[0] = START_STOP_UNIT; + sc->sc_transfer.cmd_data[4] = SSS_START; + return (1); + } + break; + + case REZERO_UNIT: + case REQUEST_SENSE: + case FORMAT_UNIT: + case INQUIRY: + case START_STOP_UNIT: + case SEND_DIAGNOSTIC: + case PREVENT_ALLOW: + case READ_CAPACITY: + case READ_10: + case WRITE_10: + case POSITION_TO_ELEMENT: /* SEEK_10 */ + case WRITE_AND_VERIFY: + case VERIFY: + case MODE_SELECT_10: + case MODE_SENSE_10: + case READ_12: + case WRITE_12: + case READ_FORMAT_CAPACITIES: + break; + + /* + * SYNCHRONIZE_CACHE isn't supported by UFI, nor should it be + * required for UFI devices, so it is appropriate to fake + * success. + */ + case SYNCHRONIZE_CACHE: + return (2); + + default: + DPRINTF(sc, UDMASS_SCSI, "Unsupported UFI " + "command 0x%02x\n", cmd_ptr[0]); + return (0); /* failure */ + } + + bcopy(cmd_ptr, sc->sc_transfer.cmd_data, cmd_len); + return (1); /* success */ +} + +/* + * 8070i (ATAPI) specific functions + */ +static uint8_t +umass_atapi_transform(struct umass_softc *sc, uint8_t *cmd_ptr, + uint8_t cmd_len) +{ + if ((cmd_len == 0) || + (cmd_len > sizeof(sc->sc_transfer.cmd_data))) { + DPRINTF(sc, UDMASS_SCSI, "Invalid command " + "length: %d bytes\n", cmd_len); + return (0); /* failure */ + } + /* An ATAPI command is always 12 bytes in length. */ + sc->sc_transfer.cmd_len = ATAPI_COMMAND_LENGTH; + + /* Zero the command data */ + bzero(sc->sc_transfer.cmd_data, ATAPI_COMMAND_LENGTH); + + switch (cmd_ptr[0]) { + /* + * Commands of which the format has been verified. They + * should work. Copy the command into the destination + * buffer. + */ + case INQUIRY: + /* + * some drives wedge when asked for full inquiry + * information. + */ + if (sc->sc_quirks & FORCE_SHORT_INQUIRY) { + bcopy(cmd_ptr, sc->sc_transfer.cmd_data, cmd_len); + + sc->sc_transfer.cmd_data[4] = SHORT_INQUIRY_LENGTH; + return (1); + } + break; + + case TEST_UNIT_READY: + if (sc->sc_quirks & NO_TEST_UNIT_READY) { + DPRINTF(sc, UDMASS_SCSI, "Converted TEST_UNIT_READY " + "to START_UNIT\n"); + sc->sc_transfer.cmd_data[0] = START_STOP_UNIT; + sc->sc_transfer.cmd_data[4] = SSS_START; + return (1); + } + break; + + case REZERO_UNIT: + case REQUEST_SENSE: + case START_STOP_UNIT: + case SEND_DIAGNOSTIC: + case PREVENT_ALLOW: + case READ_CAPACITY: + case READ_10: + case WRITE_10: + case POSITION_TO_ELEMENT: /* SEEK_10 */ + case SYNCHRONIZE_CACHE: + case MODE_SELECT_10: + case MODE_SENSE_10: + case READ_BUFFER: + case 0x42: /* READ_SUBCHANNEL */ + case 0x43: /* READ_TOC */ + case 0x44: /* READ_HEADER */ + case 0x47: /* PLAY_MSF (Play Minute/Second/Frame) */ + case 0x48: /* PLAY_TRACK */ + case 0x49: /* PLAY_TRACK_REL */ + case 0x4b: /* PAUSE */ + case 0x51: /* READ_DISK_INFO */ + case 0x52: /* READ_TRACK_INFO */ + case 0x54: /* SEND_OPC */ + case 0x59: /* READ_MASTER_CUE */ + case 0x5b: /* CLOSE_TR_SESSION */ + case 0x5c: /* READ_BUFFER_CAP */ + case 0x5d: /* SEND_CUE_SHEET */ + case 0xa1: /* BLANK */ + case 0xa5: /* PLAY_12 */ + case 0xa6: /* EXCHANGE_MEDIUM */ + case 0xad: /* READ_DVD_STRUCTURE */ + case 0xbb: /* SET_CD_SPEED */ + case 0xe5: /* READ_TRACK_INFO_PHILIPS */ + break; + + case READ_12: + case WRITE_12: + default: + DPRINTF(sc, UDMASS_SCSI, "Unsupported ATAPI " + "command 0x%02x - trying anyway\n", + cmd_ptr[0]); + break; + } + + bcopy(cmd_ptr, sc->sc_transfer.cmd_data, cmd_len); + return (1); /* success */ +} + +static uint8_t +umass_no_transform(struct umass_softc *sc, uint8_t *cmd, + uint8_t cmdlen) +{ + return (0); /* failure */ +} + +static uint8_t +umass_std_transform(struct umass_softc *sc, union ccb *ccb, + uint8_t *cmd, uint8_t cmdlen) +{ + uint8_t retval; + + retval = (sc->sc_transform) (sc, cmd, cmdlen); + + if (retval == 2) { + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + return (0); + } else if (retval == 0) { + ccb->ccb_h.status = CAM_REQ_INVALID; + xpt_done(ccb); + return (0); + } + /* Command should be executed */ + return (1); +} + +#ifdef USB_DEBUG +static void +umass_bbb_dump_cbw(struct umass_softc *sc, umass_bbb_cbw_t *cbw) +{ + uint8_t *c = cbw->CBWCDB; + + uint32_t dlen = UGETDW(cbw->dCBWDataTransferLength); + uint32_t tag = UGETDW(cbw->dCBWTag); + + uint8_t clen = cbw->bCDBLength; + uint8_t flags = cbw->bCBWFlags; + uint8_t lun = cbw->bCBWLUN; + + DPRINTF(sc, UDMASS_BBB, "CBW %d: cmd = %db " + "(0x%02x%02x%02x%02x%02x%02x%s), " + "data = %db, lun = %d, dir = %s\n", + tag, clen, + c[0], c[1], c[2], c[3], c[4], c[5], (clen > 6 ? "..." : ""), + dlen, lun, (flags == CBWFLAGS_IN ? "in" : + (flags == CBWFLAGS_OUT ? "out" : "<invalid>"))); +} + +static void +umass_bbb_dump_csw(struct umass_softc *sc, umass_bbb_csw_t *csw) +{ + uint32_t sig = UGETDW(csw->dCSWSignature); + uint32_t tag = UGETDW(csw->dCSWTag); + uint32_t res = UGETDW(csw->dCSWDataResidue); + uint8_t status = csw->bCSWStatus; + + DPRINTF(sc, UDMASS_BBB, "CSW %d: sig = 0x%08x (%s), tag = 0x%08x, " + "res = %d, status = 0x%02x (%s)\n", + tag, sig, (sig == CSWSIGNATURE ? "valid" : "invalid"), + tag, res, + status, (status == CSWSTATUS_GOOD ? "good" : + (status == CSWSTATUS_FAILED ? "failed" : + (status == CSWSTATUS_PHASE ? "phase" : "<invalid>")))); +} + +static void +umass_cbi_dump_cmd(struct umass_softc *sc, void *cmd, uint8_t cmdlen) +{ + uint8_t *c = cmd; + uint8_t dir = sc->sc_transfer.dir; + + DPRINTF(sc, UDMASS_BBB, "cmd = %db " + "(0x%02x%02x%02x%02x%02x%02x%s), " + "data = %db, dir = %s\n", + cmdlen, + c[0], c[1], c[2], c[3], c[4], c[5], (cmdlen > 6 ? "..." : ""), + sc->sc_transfer.data_len, + (dir == DIR_IN ? "in" : + (dir == DIR_OUT ? "out" : + (dir == DIR_NONE ? "no data phase" : "<invalid>")))); +} + +static void +umass_dump_buffer(struct umass_softc *sc, uint8_t *buffer, uint32_t buflen, + uint32_t printlen) +{ + uint32_t i, j; + char s1[40]; + char s2[40]; + char s3[5]; + + s1[0] = '\0'; + s3[0] = '\0'; + + sprintf(s2, " buffer=%p, buflen=%d", buffer, buflen); + for (i = 0; (i < buflen) && (i < printlen); i++) { + j = i % 16; + if (j == 0 && i != 0) { + DPRINTF(sc, UDMASS_GEN, "0x %s%s\n", + s1, s2); + s2[0] = '\0'; + } + sprintf(&s1[j * 2], "%02x", buffer[i] & 0xff); + } + if (buflen > printlen) + sprintf(s3, " ..."); + DPRINTF(sc, UDMASS_GEN, "0x %s%s%s\n", + s1, s2, s3); +} + +#endif diff --git a/freebsd/sys/dev/usb/ufm_ioctl.h b/freebsd/sys/dev/usb/ufm_ioctl.h new file mode 100644 index 00000000..20a0b96e --- /dev/null +++ b/freebsd/sys/dev/usb/ufm_ioctl.h @@ -0,0 +1,39 @@ +/*- + * Copyright (c) 2001 M. Warner Losh + * 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 code is based on ugen.c and ulpt.c developed by Lennart Augustsson. + * This code includes software developed by the NetBSD Foundation, Inc. and + * its contributors. + */ + +/* $FreeBSD$ */ + +#include <freebsd/sys/ioccom.h> + +#define FM_SET_FREQ _IOWR('U', 200, int) +#define FM_GET_FREQ _IOWR('U', 201, int) +#define FM_START _IOWR('U', 202, int) +#define FM_STOP _IOWR('U', 203, int) +#define FM_GET_STAT _IOWR('U', 204, int) diff --git a/freebsd/sys/dev/usb/usb.h b/freebsd/sys/dev/usb/usb.h new file mode 100644 index 00000000..9a6cb8f7 --- /dev/null +++ b/freebsd/sys/dev/usb/usb.h @@ -0,0 +1,755 @@ +/* $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. + */ + +/* + * This file contains standard definitions for the following USB + * protocol versions: + * + * USB v1.0 + * USB v1.1 + * USB v2.0 + * USB v3.0 + */ + +#ifndef _USB_STANDARD_HH_ +#define _USB_STANDARD_HH_ + +#if defined(_KERNEL) +#include <freebsd/local/opt_usb.h> + +/* Declare parent SYSCTL USB node. */ +#ifdef SYSCTL_DECL +SYSCTL_DECL(_hw_usb); +#endif + +#include <freebsd/sys/malloc.h> + +MALLOC_DECLARE(M_USB); +MALLOC_DECLARE(M_USBDEV); +MALLOC_DECLARE(M_USBHC); +#endif /* _KERNEL */ + +#include <freebsd/dev/usb/usb_endian.h> +#include <freebsd/dev/usb/usb_freebsd.h> + +#define USB_STACK_VERSION 2000 /* 2.0 */ + +/* Definition of some hardcoded USB constants. */ + +#define USB_MAX_IPACKET 8 /* initial USB packet size */ +#define USB_EP_MAX (2*16) /* hardcoded */ +#define USB_ROOT_HUB_ADDR 1 /* index */ +#define USB_MIN_DEVICES 2 /* unused + root HUB */ +#define USB_UNCONFIG_INDEX 0xFF /* internal use only */ +#define USB_IFACE_INDEX_ANY 0xFF /* internal use only */ +#define USB_START_ADDR 0 /* default USB device BUS address + * after USB bus reset */ +#define USB_CONTROL_ENDPOINT 0 /* default control endpoint */ + +#define USB_FRAMES_PER_SECOND_FS 1000 /* full speed */ +#define USB_FRAMES_PER_SECOND_HS 8000 /* high speed */ + +#define USB_FS_BYTES_PER_HS_UFRAME 188 /* bytes */ +#define USB_HS_MICRO_FRAMES_MAX 8 /* units */ + +#define USB_ISOC_TIME_MAX 128 /* ms */ + +/* + * Minimum time a device needs to be powered down to go through a + * power cycle. These values are not in the USB specification. + */ +#define USB_POWER_DOWN_TIME 200 /* ms */ +#define USB_PORT_POWER_DOWN_TIME 100 /* ms */ + +/* Definition of software USB power modes */ +#define USB_POWER_MODE_OFF 0 /* turn off device */ +#define USB_POWER_MODE_ON 1 /* always on */ +#define USB_POWER_MODE_SAVE 2 /* automatic suspend and resume */ +#define USB_POWER_MODE_SUSPEND 3 /* force suspend */ +#define USB_POWER_MODE_RESUME 4 /* force resume */ + +#if 0 +/* These are the values from the USB specification. */ +#define USB_PORT_RESET_DELAY 10 /* ms */ +#define USB_PORT_ROOT_RESET_DELAY 50 /* ms */ +#define USB_PORT_RESET_RECOVERY 10 /* ms */ +#define USB_PORT_POWERUP_DELAY 100 /* ms */ +#define USB_PORT_RESUME_DELAY 20 /* ms */ +#define USB_SET_ADDRESS_SETTLE 2 /* ms */ +#define USB_RESUME_DELAY (20*5) /* ms */ +#define USB_RESUME_WAIT 10 /* ms */ +#define USB_RESUME_RECOVERY 10 /* ms */ +#define USB_EXTRA_POWER_UP_TIME 0 /* ms */ +#else +/* Allow for marginal and non-conforming devices. */ +#define USB_PORT_RESET_DELAY 50 /* ms */ +#define USB_PORT_ROOT_RESET_DELAY 250 /* ms */ +#define USB_PORT_RESET_RECOVERY 250 /* ms */ +#define USB_PORT_POWERUP_DELAY 300 /* ms */ +#define USB_PORT_RESUME_DELAY (20*2) /* ms */ +#define USB_SET_ADDRESS_SETTLE 10 /* ms */ +#define USB_RESUME_DELAY (50*5) /* ms */ +#define USB_RESUME_WAIT 50 /* ms */ +#define USB_RESUME_RECOVERY 50 /* ms */ +#define USB_EXTRA_POWER_UP_TIME 20 /* ms */ +#endif + +#define USB_MIN_POWER 100 /* mA */ +#define USB_MAX_POWER 500 /* mA */ + +#define USB_BUS_RESET_DELAY 100 /* ms */ + +/* + * USB record layout in memory: + * + * - USB config 0 + * - USB interfaces + * - USB alternative interfaces + * - USB endpoints + * + * - USB config 1 + * - USB interfaces + * - USB alternative interfaces + * - USB endpoints + */ + +/* Declaration of USB records */ + +struct usb_device_request { + uByte bmRequestType; + uByte bRequest; + uWord wValue; + uWord wIndex; + uWord wLength; +} __packed; +typedef struct usb_device_request usb_device_request_t; + +#define UT_WRITE 0x00 +#define UT_READ 0x80 +#define UT_STANDARD 0x00 +#define UT_CLASS 0x20 +#define UT_VENDOR 0x40 +#define UT_DEVICE 0x00 +#define UT_INTERFACE 0x01 +#define UT_ENDPOINT 0x02 +#define UT_OTHER 0x03 + +#define UT_READ_DEVICE (UT_READ | UT_STANDARD | UT_DEVICE) +#define UT_READ_INTERFACE (UT_READ | UT_STANDARD | UT_INTERFACE) +#define UT_READ_ENDPOINT (UT_READ | UT_STANDARD | UT_ENDPOINT) +#define UT_WRITE_DEVICE (UT_WRITE | UT_STANDARD | UT_DEVICE) +#define UT_WRITE_INTERFACE (UT_WRITE | UT_STANDARD | UT_INTERFACE) +#define UT_WRITE_ENDPOINT (UT_WRITE | UT_STANDARD | UT_ENDPOINT) +#define UT_READ_CLASS_DEVICE (UT_READ | UT_CLASS | UT_DEVICE) +#define UT_READ_CLASS_INTERFACE (UT_READ | UT_CLASS | UT_INTERFACE) +#define UT_READ_CLASS_OTHER (UT_READ | UT_CLASS | UT_OTHER) +#define UT_READ_CLASS_ENDPOINT (UT_READ | UT_CLASS | UT_ENDPOINT) +#define UT_WRITE_CLASS_DEVICE (UT_WRITE | UT_CLASS | UT_DEVICE) +#define UT_WRITE_CLASS_INTERFACE (UT_WRITE | UT_CLASS | UT_INTERFACE) +#define UT_WRITE_CLASS_OTHER (UT_WRITE | UT_CLASS | UT_OTHER) +#define UT_WRITE_CLASS_ENDPOINT (UT_WRITE | UT_CLASS | UT_ENDPOINT) +#define UT_READ_VENDOR_DEVICE (UT_READ | UT_VENDOR | UT_DEVICE) +#define UT_READ_VENDOR_INTERFACE (UT_READ | UT_VENDOR | UT_INTERFACE) +#define UT_READ_VENDOR_OTHER (UT_READ | UT_VENDOR | UT_OTHER) +#define UT_READ_VENDOR_ENDPOINT (UT_READ | UT_VENDOR | UT_ENDPOINT) +#define UT_WRITE_VENDOR_DEVICE (UT_WRITE | UT_VENDOR | UT_DEVICE) +#define UT_WRITE_VENDOR_INTERFACE (UT_WRITE | UT_VENDOR | UT_INTERFACE) +#define UT_WRITE_VENDOR_OTHER (UT_WRITE | UT_VENDOR | UT_OTHER) +#define UT_WRITE_VENDOR_ENDPOINT (UT_WRITE | UT_VENDOR | UT_ENDPOINT) + +/* Requests */ +#define UR_GET_STATUS 0x00 +#define UR_CLEAR_FEATURE 0x01 +#define UR_SET_FEATURE 0x03 +#define UR_SET_ADDRESS 0x05 +#define UR_GET_DESCRIPTOR 0x06 +#define UDESC_DEVICE 0x01 +#define UDESC_CONFIG 0x02 +#define UDESC_STRING 0x03 +#define USB_LANGUAGE_TABLE 0x00 /* language ID string index */ +#define UDESC_INTERFACE 0x04 +#define UDESC_ENDPOINT 0x05 +#define UDESC_DEVICE_QUALIFIER 0x06 +#define UDESC_OTHER_SPEED_CONFIGURATION 0x07 +#define UDESC_INTERFACE_POWER 0x08 +#define UDESC_OTG 0x09 +#define UDESC_DEBUG 0x0A +#define UDESC_IFACE_ASSOC 0x0B /* interface association */ +#define UDESC_BOS 0x0F /* binary object store */ +#define UDESC_DEVICE_CAPABILITY 0x10 +#define UDESC_CS_DEVICE 0x21 /* class specific */ +#define UDESC_CS_CONFIG 0x22 +#define UDESC_CS_STRING 0x23 +#define UDESC_CS_INTERFACE 0x24 +#define UDESC_CS_ENDPOINT 0x25 +#define UDESC_HUB 0x29 +#define UDESC_SS_HUB 0x2A /* super speed */ +#define UDESC_ENDPOINT_SS_COMP 0x30 /* super speed */ +#define UR_SET_DESCRIPTOR 0x07 +#define UR_GET_CONFIG 0x08 +#define UR_SET_CONFIG 0x09 +#define UR_GET_INTERFACE 0x0a +#define UR_SET_INTERFACE 0x0b +#define UR_SYNCH_FRAME 0x0c +#define UR_SET_SEL 0x30 +#define UR_ISOCH_DELAY 0x31 + +/* HUB specific request */ +#define UR_GET_BUS_STATE 0x02 +#define UR_CLEAR_TT_BUFFER 0x08 +#define UR_RESET_TT 0x09 +#define UR_GET_TT_STATE 0x0a +#define UR_STOP_TT 0x0b +#define UR_SET_HUB_DEPTH 0x0c +#define USB_SS_HUB_DEPTH_MAX 5 +#define UR_GET_PORT_ERR_COUNT 0x0d + +/* Feature numbers */ +#define UF_ENDPOINT_HALT 0 +#define UF_DEVICE_REMOTE_WAKEUP 1 +#define UF_TEST_MODE 2 +#define UF_U1_ENABLE 0x30 +#define UF_U2_ENABLE 0x31 +#define UF_LTM_ENABLE 0x32 + +/* HUB specific features */ +#define UHF_C_HUB_LOCAL_POWER 0 +#define UHF_C_HUB_OVER_CURRENT 1 +#define UHF_PORT_CONNECTION 0 +#define UHF_PORT_ENABLE 1 +#define UHF_PORT_SUSPEND 2 +#define UHF_PORT_OVER_CURRENT 3 +#define UHF_PORT_RESET 4 +#define UHF_PORT_LINK_STATE 5 +#define UHF_PORT_POWER 8 +#define UHF_PORT_LOW_SPEED 9 +#define UHF_C_PORT_CONNECTION 16 +#define UHF_C_PORT_ENABLE 17 +#define UHF_C_PORT_SUSPEND 18 +#define UHF_C_PORT_OVER_CURRENT 19 +#define UHF_C_PORT_RESET 20 +#define UHF_PORT_TEST 21 +#define UHF_PORT_INDICATOR 22 + +/* SuperSpeed HUB specific features */ +#define UHF_PORT_U1_TIMEOUT 23 +#define UHF_PORT_U2_TIMEOUT 24 +#define UHF_C_PORT_LINK_STATE 25 +#define UHF_C_PORT_CONFIG_ERROR 26 +#define UHF_PORT_REMOTE_WAKE_MASK 27 +#define UHF_BH_PORT_RESET 28 +#define UHF_C_BH_PORT_RESET 29 +#define UHF_FORCE_LINKPM_ACCEPT 30 + +struct usb_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; +} __packed; +typedef struct usb_descriptor usb_descriptor_t; + +struct usb_device_descriptor { + uByte bLength; + uByte bDescriptorType; + uWord bcdUSB; +#define UD_USB_2_0 0x0200 +#define UD_USB_3_0 0x0300 +#define UD_IS_USB2(d) ((d)->bcdUSB[1] == 0x02) +#define UD_IS_USB3(d) ((d)->bcdUSB[1] == 0x03) + uByte bDeviceClass; + uByte bDeviceSubClass; + uByte bDeviceProtocol; + uByte bMaxPacketSize; + /* The fields below are not part of the initial descriptor. */ + uWord idVendor; + uWord idProduct; + uWord bcdDevice; + uByte iManufacturer; + uByte iProduct; + uByte iSerialNumber; + uByte bNumConfigurations; +} __packed; +typedef struct usb_device_descriptor usb_device_descriptor_t; + +/* Binary Device Object Store (BOS) */ +struct usb_bos_descriptor { + uByte bLength; + uByte bDescriptorType; + uWord wTotalLength; + uByte bNumDeviceCaps; +} __packed; +typedef struct usb_bos_descriptor usb_bos_descriptor_t; + +/* Binary Device Object Store Capability */ +struct usb_bos_cap_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDevCapabilityType; +#define USB_DEVCAP_RESERVED 0x00 +#define USB_DEVCAP_WUSB 0x01 +#define USB_DEVCAP_USB2EXT 0x02 +#define USB_DEVCAP_SUPER_SPEED 0x03 +#define USB_DEVCAP_CONTAINER_ID 0x04 + /* data ... */ +} __packed; +typedef struct usb_bos_cap_descriptor usb_bos_cap_descriptor_t; + +struct usb_devcap_usb2ext_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDevCapabilityType; + uByte bmAttributes; +#define USB_V2EXT_LPM 0x02 +} __packed; +typedef struct usb_devcap_usb2ext_descriptor usb_devcap_usb2ext_descriptor_t; + +struct usb_devcap_ss_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDevCapabilityType; + uByte bmAttributes; + uWord wSpeedsSupported; + uByte bFunctionalitySupport; + uByte bU1DevExitLat; + uByte bU2DevExitLat; +} __packed; +typedef struct usb_devcap_ss_descriptor usb_devcap_ss_descriptor_t; + +struct usb_devcap_container_id_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDevCapabilityType; + uByte bReserved; + uByte bContainerID; +} __packed; +typedef struct usb_devcap_container_id_descriptor + usb_devcap_container_id_descriptor_t; + +/* Device class codes */ +#define UDCLASS_IN_INTERFACE 0x00 +#define UDCLASS_COMM 0x02 +#define UDCLASS_HUB 0x09 +#define UDSUBCLASS_HUB 0x00 +#define UDPROTO_FSHUB 0x00 +#define UDPROTO_HSHUBSTT 0x01 +#define UDPROTO_HSHUBMTT 0x02 +#define UDPROTO_SSHUB 0x03 +#define UDCLASS_DIAGNOSTIC 0xdc +#define UDCLASS_WIRELESS 0xe0 +#define UDSUBCLASS_RF 0x01 +#define UDPROTO_BLUETOOTH 0x01 +#define UDCLASS_VENDOR 0xff + +struct usb_config_descriptor { + uByte bLength; + uByte bDescriptorType; + uWord wTotalLength; + uByte bNumInterface; + uByte bConfigurationValue; +#define USB_UNCONFIG_NO 0 + uByte iConfiguration; + uByte bmAttributes; +#define UC_BUS_POWERED 0x80 +#define UC_SELF_POWERED 0x40 +#define UC_REMOTE_WAKEUP 0x20 + uByte bMaxPower; /* max current in 2 mA units */ +#define UC_POWER_FACTOR 2 +} __packed; +typedef struct usb_config_descriptor usb_config_descriptor_t; + +struct usb_interface_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bInterfaceNumber; + uByte bAlternateSetting; + uByte bNumEndpoints; + uByte bInterfaceClass; + uByte bInterfaceSubClass; + uByte bInterfaceProtocol; + uByte iInterface; +} __packed; +typedef struct usb_interface_descriptor usb_interface_descriptor_t; + +struct usb_interface_assoc_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bFirstInterface; + uByte bInterfaceCount; + uByte bFunctionClass; + uByte bFunctionSubClass; + uByte bFunctionProtocol; + uByte iFunction; +} __packed; +typedef struct usb_interface_assoc_descriptor usb_interface_assoc_descriptor_t; + +/* Interface class codes */ +#define UICLASS_UNSPEC 0x00 +#define UICLASS_AUDIO 0x01 /* audio */ +#define UISUBCLASS_AUDIOCONTROL 1 +#define UISUBCLASS_AUDIOSTREAM 2 +#define UISUBCLASS_MIDISTREAM 3 + +#define UICLASS_CDC 0x02 /* communication */ +#define UISUBCLASS_DIRECT_LINE_CONTROL_MODEL 1 +#define UISUBCLASS_ABSTRACT_CONTROL_MODEL 2 +#define UISUBCLASS_TELEPHONE_CONTROL_MODEL 3 +#define UISUBCLASS_MULTICHANNEL_CONTROL_MODEL 4 +#define UISUBCLASS_CAPI_CONTROLMODEL 5 +#define UISUBCLASS_ETHERNET_NETWORKING_CONTROL_MODEL 6 +#define UISUBCLASS_ATM_NETWORKING_CONTROL_MODEL 7 +#define UISUBCLASS_WIRELESS_HANDSET_CM 8 +#define UISUBCLASS_DEVICE_MGMT 9 +#define UISUBCLASS_MOBILE_DIRECT_LINE_MODEL 10 +#define UISUBCLASS_OBEX 11 +#define UISUBCLASS_ETHERNET_EMULATION_MODEL 12 +#define UISUBCLASS_NETWORK_CONTROL_MODEL 13 + +#define UIPROTO_CDC_AT 1 + +#define UICLASS_HID 0x03 +#define UISUBCLASS_BOOT 1 +#define UIPROTO_BOOT_KEYBOARD 1 +#define UIPROTO_MOUSE 2 + +#define UICLASS_PHYSICAL 0x05 +#define UICLASS_IMAGE 0x06 +#define UISUBCLASS_SIC 1 /* still image class */ +#define UICLASS_PRINTER 0x07 +#define UISUBCLASS_PRINTER 1 +#define UIPROTO_PRINTER_UNI 1 +#define UIPROTO_PRINTER_BI 2 +#define UIPROTO_PRINTER_1284 3 + +#define UICLASS_MASS 0x08 +#define UISUBCLASS_RBC 1 +#define UISUBCLASS_SFF8020I 2 +#define UISUBCLASS_QIC157 3 +#define UISUBCLASS_UFI 4 +#define UISUBCLASS_SFF8070I 5 +#define UISUBCLASS_SCSI 6 +#define UIPROTO_MASS_CBI_I 0 +#define UIPROTO_MASS_CBI 1 +#define UIPROTO_MASS_BBB_OLD 2 /* Not in the spec anymore */ +#define UIPROTO_MASS_BBB 80 /* 'P' for the Iomega Zip drive */ + +#define UICLASS_HUB 0x09 +#define UISUBCLASS_HUB 0 +#define UIPROTO_FSHUB 0 +#define UIPROTO_HSHUBSTT 0 /* Yes, same as previous */ +#define UIPROTO_HSHUBMTT 1 + +#define UICLASS_CDC_DATA 0x0a +#define UISUBCLASS_DATA 0x00 +#define UIPROTO_DATA_ISDNBRI 0x30 /* Physical iface */ +#define UIPROTO_DATA_HDLC 0x31 /* HDLC */ +#define UIPROTO_DATA_TRANSPARENT 0x32 /* Transparent */ +#define UIPROTO_DATA_Q921M 0x50 /* Management for Q921 */ +#define UIPROTO_DATA_Q921 0x51 /* Data for Q921 */ +#define UIPROTO_DATA_Q921TM 0x52 /* TEI multiplexer for Q921 */ +#define UIPROTO_DATA_V42BIS 0x90 /* Data compression */ +#define UIPROTO_DATA_Q931 0x91 /* Euro-ISDN */ +#define UIPROTO_DATA_V120 0x92 /* V.24 rate adaption */ +#define UIPROTO_DATA_CAPI 0x93 /* CAPI 2.0 commands */ +#define UIPROTO_DATA_HOST_BASED 0xfd /* Host based driver */ +#define UIPROTO_DATA_PUF 0xfe /* see Prot. Unit Func. Desc. */ +#define UIPROTO_DATA_VENDOR 0xff /* Vendor specific */ +#define UIPROTO_DATA_NCM 0x01 /* Network Control Model */ + +#define UICLASS_SMARTCARD 0x0b +#define UICLASS_FIRM_UPD 0x0c +#define UICLASS_SECURITY 0x0d +#define UICLASS_DIAGNOSTIC 0xdc +#define UICLASS_WIRELESS 0xe0 +#define UISUBCLASS_RF 0x01 +#define UIPROTO_BLUETOOTH 0x01 + +#define UICLASS_IAD 0xEF /* Interface Association Descriptor */ + +#define UICLASS_APPL_SPEC 0xfe +#define UISUBCLASS_FIRMWARE_DOWNLOAD 1 +#define UISUBCLASS_IRDA 2 +#define UIPROTO_IRDA 0 + +#define UICLASS_VENDOR 0xff +#define UISUBCLASS_XBOX360_CONTROLLER 0x5d +#define UIPROTO_XBOX360_GAMEPAD 0x01 + +struct usb_endpoint_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bEndpointAddress; +#define UE_GET_DIR(a) ((a) & 0x80) +#define UE_SET_DIR(a,d) ((a) | (((d)&1) << 7)) +#define UE_DIR_IN 0x80 /* IN-token endpoint, fixed */ +#define UE_DIR_OUT 0x00 /* OUT-token endpoint, fixed */ +#define UE_DIR_RX 0xfd /* for internal use only! */ +#define UE_DIR_TX 0xfe /* for internal use only! */ +#define UE_DIR_ANY 0xff /* for internal use only! */ +#define UE_ADDR 0x0f +#define UE_ADDR_ANY 0xff /* for internal use only! */ +#define UE_GET_ADDR(a) ((a) & UE_ADDR) + uByte bmAttributes; +#define UE_XFERTYPE 0x03 +#define UE_CONTROL 0x00 +#define UE_ISOCHRONOUS 0x01 +#define UE_BULK 0x02 +#define UE_INTERRUPT 0x03 +#define UE_BULK_INTR 0xfe /* for internal use only! */ +#define UE_TYPE_ANY 0xff /* for internal use only! */ +#define UE_GET_XFERTYPE(a) ((a) & UE_XFERTYPE) +#define UE_ISO_TYPE 0x0c +#define UE_ISO_ASYNC 0x04 +#define UE_ISO_ADAPT 0x08 +#define UE_ISO_SYNC 0x0c +#define UE_GET_ISO_TYPE(a) ((a) & UE_ISO_TYPE) + uWord wMaxPacketSize; +#define UE_ZERO_MPS 0xFFFF /* for internal use only */ + uByte bInterval; +} __packed; +typedef struct usb_endpoint_descriptor usb_endpoint_descriptor_t; + +struct usb_endpoint_ss_comp_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bMaxBurst; + uByte bmAttributes; + uWord wBytesPerInterval; +} __packed; +typedef struct usb_endpoint_ss_comp_descriptor + usb_endpoint_ss_comp_descriptor_t; + +struct usb_string_descriptor { + uByte bLength; + uByte bDescriptorType; + uWord bString[126]; + uByte bUnused; +} __packed; +typedef struct usb_string_descriptor usb_string_descriptor_t; + +#define USB_MAKE_STRING_DESC(m,name) \ +struct name { \ + uByte bLength; \ + uByte bDescriptorType; \ + uByte bData[sizeof((uint8_t []){m})]; \ +} __packed; \ +static const struct name name = { \ + .bLength = sizeof(struct name), \ + .bDescriptorType = UDESC_STRING, \ + .bData = { m }, \ +} + +struct usb_hub_descriptor { + uByte bDescLength; + uByte bDescriptorType; + uByte bNbrPorts; + uWord wHubCharacteristics; +#define UHD_PWR 0x0003 +#define UHD_PWR_GANGED 0x0000 +#define UHD_PWR_INDIVIDUAL 0x0001 +#define UHD_PWR_NO_SWITCH 0x0002 +#define UHD_COMPOUND 0x0004 +#define UHD_OC 0x0018 +#define UHD_OC_GLOBAL 0x0000 +#define UHD_OC_INDIVIDUAL 0x0008 +#define UHD_OC_NONE 0x0010 +#define UHD_TT_THINK 0x0060 +#define UHD_TT_THINK_8 0x0000 +#define UHD_TT_THINK_16 0x0020 +#define UHD_TT_THINK_24 0x0040 +#define UHD_TT_THINK_32 0x0060 +#define UHD_PORT_IND 0x0080 + uByte bPwrOn2PwrGood; /* delay in 2 ms units */ +#define UHD_PWRON_FACTOR 2 + uByte bHubContrCurrent; + uByte DeviceRemovable[32]; /* max 255 ports */ +#define UHD_NOT_REMOV(desc, i) \ + (((desc)->DeviceRemovable[(i)/8] >> ((i) % 8)) & 1) + uByte PortPowerCtrlMask[1]; /* deprecated */ +} __packed; +typedef struct usb_hub_descriptor usb_hub_descriptor_t; + +struct usb_hub_ss_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bNbrPorts; + uWord wHubCharacteristics; + uByte bPwrOn2PwrGood; /* delay in 2 ms units */ + uByte bHubContrCurrent; + uByte bHubHdrDecLat; + uWord wHubDelay; + uByte DeviceRemovable[32]; /* max 255 ports */ +} __packed; +typedef struct usb_hub_ss_descriptor usb_hub_ss_descriptor_t; + +/* minimum HUB descriptor (8-ports maximum) */ +struct usb_hub_descriptor_min { + uByte bDescLength; + uByte bDescriptorType; + uByte bNbrPorts; + uWord wHubCharacteristics; + uByte bPwrOn2PwrGood; + uByte bHubContrCurrent; + uByte DeviceRemovable[1]; + uByte PortPowerCtrlMask[1]; +} __packed; +typedef struct usb_hub_descriptor_min usb_hub_descriptor_min_t; + +struct usb_device_qualifier { + uByte bLength; + uByte bDescriptorType; + uWord bcdUSB; + uByte bDeviceClass; + uByte bDeviceSubClass; + uByte bDeviceProtocol; + uByte bMaxPacketSize0; + uByte bNumConfigurations; + uByte bReserved; +} __packed; +typedef struct usb_device_qualifier usb_device_qualifier_t; + +struct usb_otg_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bmAttributes; +#define UOTG_SRP 0x01 +#define UOTG_HNP 0x02 +} __packed; +typedef struct usb_otg_descriptor usb_otg_descriptor_t; + +/* OTG feature selectors */ +#define UOTG_B_HNP_ENABLE 3 +#define UOTG_A_HNP_SUPPORT 4 +#define UOTG_A_ALT_HNP_SUPPORT 5 + +struct usb_status { + uWord wStatus; +/* Device status flags */ +#define UDS_SELF_POWERED 0x0001 +#define UDS_REMOTE_WAKEUP 0x0002 +/* Endpoint status flags */ +#define UES_HALT 0x0001 +} __packed; +typedef struct usb_status usb_status_t; + +struct usb_hub_status { + uWord wHubStatus; +#define UHS_LOCAL_POWER 0x0001 +#define UHS_OVER_CURRENT 0x0002 + uWord wHubChange; +} __packed; +typedef struct usb_hub_status usb_hub_status_t; + +struct usb_port_status { + uWord wPortStatus; +#define UPS_CURRENT_CONNECT_STATUS 0x0001 +#define UPS_PORT_ENABLED 0x0002 +#define UPS_SUSPEND 0x0004 +#define UPS_OVERCURRENT_INDICATOR 0x0008 +#define UPS_RESET 0x0010 +/* The link-state bits are valid for Super-Speed USB HUBs */ +#define UPS_PORT_LINK_STATE_GET(x) (((x) >> 5) & 0xF) +#define UPS_PORT_LINK_STATE_SET(x) (((x) & 0xF) << 5) +#define UPS_PORT_LS_U0 0x00 +#define UPS_PORT_LS_U1 0x01 +#define UPS_PORT_LS_U2 0x02 +#define UPS_PORT_LS_U3 0x03 +#define UPS_PORT_LS_SS_DIS 0x04 +#define UPS_PORT_LS_RX_DET 0x05 +#define UPS_PORT_LS_SS_INA 0x06 +#define UPS_PORT_LS_POLL 0x07 +#define UPS_PORT_LS_RECOVER 0x08 +#define UPS_PORT_LS_HOT_RST 0x09 +#define UPS_PORT_LS_COMP_MODE 0x0A +#define UPS_PORT_LS_LOOPBACK 0x0B +#define UPS_PORT_POWER 0x0100 +#define UPS_LOW_SPEED 0x0200 +#define UPS_HIGH_SPEED 0x0400 +#define UPS_OTHER_SPEED 0x0600 /* currently FreeBSD specific */ +#define UPS_PORT_TEST 0x0800 +#define UPS_PORT_INDICATOR 0x1000 +#define UPS_PORT_MODE_DEVICE 0x8000 /* currently FreeBSD specific */ + uWord wPortChange; +#define UPS_C_CONNECT_STATUS 0x0001 +#define UPS_C_PORT_ENABLED 0x0002 +#define UPS_C_SUSPEND 0x0004 +#define UPS_C_OVERCURRENT_INDICATOR 0x0008 +#define UPS_C_PORT_RESET 0x0010 +#define UPS_C_BH_PORT_RESET 0x0020 +#define UPS_C_PORT_LINK_STATE 0x0040 +#define UPS_C_PORT_CONFIG_ERROR 0x0080 +} __packed; +typedef struct usb_port_status usb_port_status_t; + +/* + * The "USB_SPEED" macros defines all the supported USB speeds. + */ +enum usb_dev_speed { + USB_SPEED_VARIABLE, + USB_SPEED_LOW, + USB_SPEED_FULL, + USB_SPEED_HIGH, + USB_SPEED_SUPER, +}; +#define USB_SPEED_MAX (USB_SPEED_SUPER+1) + +/* + * The "USB_REV" macros defines all the supported USB revisions. + */ +enum usb_revision { + USB_REV_UNKNOWN, + USB_REV_PRE_1_0, + USB_REV_1_0, + USB_REV_1_1, + USB_REV_2_0, + USB_REV_2_5, + USB_REV_3_0 +}; +#define USB_REV_MAX (USB_REV_3_0+1) + +/* + * Supported host contoller modes. + */ +enum usb_hc_mode { + USB_MODE_HOST, /* initiates transfers */ + USB_MODE_DEVICE, /* bus transfer target */ + USB_MODE_DUAL /* can be host or device */ +}; +#define USB_MODE_MAX (USB_MODE_DUAL+1) + +/* + * The "USB_MODE" macros defines all the supported device states. + */ +enum usb_dev_state { + USB_STATE_DETACHED, + USB_STATE_ATTACHED, + USB_STATE_POWERED, + USB_STATE_ADDRESSED, + USB_STATE_CONFIGURED, +}; +#define USB_STATE_MAX (USB_STATE_CONFIGURED+1) +#endif /* _USB_STANDARD_HH_ */ diff --git a/freebsd/sys/dev/usb/usb_bus.h b/freebsd/sys/dev/usb/usb_bus.h new file mode 100644 index 00000000..b437fac0 --- /dev/null +++ b/freebsd/sys/dev/usb/usb_bus.h @@ -0,0 +1,114 @@ +/* $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. + */ + +#ifndef _USB_BUS_HH_ +#define _USB_BUS_HH_ + +/* + * The following structure defines the USB explore message sent to the USB + * explore process. + */ + +struct usb_bus_msg { + struct usb_proc_msg hdr; + struct usb_bus *bus; +}; + +/* + * The following structure defines the USB statistics structure. + */ +struct usb_bus_stat { + uint32_t uds_requests[4]; +}; + +/* + * The following structure defines an USB BUS. There is one USB BUS + * for every Host or Device controller. + */ +struct usb_bus { + struct usb_bus_stat stats_err; + struct usb_bus_stat stats_ok; +#ifndef __rtems__ + struct root_hold_token *bus_roothold; +#endif /* __rtems__ */ + /* + * There are two callback processes. One for Giant locked + * callbacks. One for non-Giant locked callbacks. This should + * avoid congestion and reduce response time in most cases. + */ + struct usb_process giant_callback_proc; + struct usb_process non_giant_callback_proc; + + /* Explore process */ + struct usb_process explore_proc; + + /* Control request process */ + struct usb_process control_xfer_proc; + + struct usb_bus_msg explore_msg[2]; + struct usb_bus_msg detach_msg[2]; + struct usb_bus_msg attach_msg[2]; + /* + * This mutex protects the USB hardware: + */ + struct mtx bus_mtx; + struct usb_xfer_queue intr_q; + struct usb_callout power_wdog; /* power management */ + + device_t parent; + device_t bdev; /* filled by HC driver */ + +#if USB_HAVE_BUSDMA + struct usb_dma_parent_tag dma_parent_tag[1]; + struct usb_dma_tag dma_tags[USB_BUS_DMA_TAG_MAX]; +#endif + struct usb_bus_methods *methods; /* filled by HC driver */ + struct usb_device **devices; + + usb_power_mask_t hw_power_state; /* see USB_HW_POWER_XXX */ + usb_size_t uframe_usage[USB_HS_MICRO_FRAMES_MAX]; + + uint16_t isoc_time_last; /* in milliseconds */ + + uint8_t alloc_failed; /* Set if memory allocation failed. */ + uint8_t driver_added_refcount; /* Current driver generation count */ + enum usb_revision usbrev; /* USB revision. See "USB_REV_XXX". */ + + uint8_t devices_max; /* maximum number of USB devices */ + uint8_t do_probe; /* set if USB BUS should be re-probed */ + + /* + * The scratch area can only be used inside the explore thread + * belonging to the give serial bus. + */ + union { + struct usb_hw_ep_scratch hw_ep_scratch[1]; + struct usb_temp_setup temp_setup[1]; + uint8_t data[255]; + } scratch[1]; +}; + +#endif /* _USB_BUS_HH_ */ diff --git a/freebsd/sys/dev/usb/usb_busdma.c b/freebsd/sys/dev/usb/usb_busdma.c new file mode 100644 index 00000000..979136b8 --- /dev/null +++ b/freebsd/sys/dev/usb/usb_busdma.c @@ -0,0 +1,1071 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/* $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. + */ + +#include <freebsd/sys/stdint.h> +#include <freebsd/sys/stddef.h> +#include <freebsd/sys/param.h> +#include <freebsd/sys/queue.h> +#include <freebsd/sys/types.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/bus.h> +#include <freebsd/sys/linker_set.h> +#include <freebsd/sys/module.h> +#include <freebsd/sys/lock.h> +#include <freebsd/sys/mutex.h> +#include <freebsd/sys/condvar.h> +#include <freebsd/sys/sysctl.h> +#include <freebsd/sys/sx.h> +#include <freebsd/sys/unistd.h> +#include <freebsd/sys/callout.h> +#include <freebsd/sys/malloc.h> +#include <freebsd/sys/priv.h> + +#include <freebsd/dev/usb/usb.h> +#include <freebsd/dev/usb/usbdi.h> +#include <freebsd/dev/usb/usbdi_util.h> + +#define USB_DEBUG_VAR usb_debug + +#include <freebsd/dev/usb/usb_core.h> +#include <freebsd/dev/usb/usb_busdma.h> +#include <freebsd/dev/usb/usb_process.h> +#include <freebsd/dev/usb/usb_transfer.h> +#include <freebsd/dev/usb/usb_device.h> +#include <freebsd/dev/usb/usb_util.h> +#include <freebsd/dev/usb/usb_debug.h> + +#include <freebsd/dev/usb/usb_controller.h> +#include <freebsd/dev/usb/usb_bus.h> + +#if USB_HAVE_BUSDMA +static void usb_dma_tag_create(struct usb_dma_tag *, usb_size_t, usb_size_t); +static void usb_dma_tag_destroy(struct usb_dma_tag *); +static void usb_dma_lock_cb(void *, bus_dma_lock_op_t); +static void usb_pc_alloc_mem_cb(void *, bus_dma_segment_t *, int, int); +static void usb_pc_load_mem_cb(void *, bus_dma_segment_t *, int, int); +static void usb_pc_common_mem_cb(void *, bus_dma_segment_t *, int, int, + uint8_t); +#endif + +/*------------------------------------------------------------------------* + * usbd_get_page - lookup DMA-able memory for the given offset + * + * NOTE: Only call this function when the "page_cache" structure has + * been properly initialized ! + *------------------------------------------------------------------------*/ +void +usbd_get_page(struct usb_page_cache *pc, usb_frlength_t offset, + struct usb_page_search *res) +{ + struct usb_page *page; + +#if USB_HAVE_BUSDMA + if (pc->page_start) { + + /* Case 1 - something has been loaded into DMA */ + + if (pc->buffer) { + + /* Case 1a - Kernel Virtual Address */ + + res->buffer = USB_ADD_BYTES(pc->buffer, offset); + } + offset += pc->page_offset_buf; + + /* compute destination page */ + + page = pc->page_start; + + if (pc->ismultiseg) { + + page += (offset / USB_PAGE_SIZE); + + offset %= USB_PAGE_SIZE; + + res->length = USB_PAGE_SIZE - offset; + res->physaddr = page->physaddr + offset; + } else { + res->length = 0 - 1; + res->physaddr = page->physaddr + offset; + } + if (!pc->buffer) { + + /* Case 1b - Non Kernel Virtual Address */ + + res->buffer = USB_ADD_BYTES(page->buffer, offset); + } + return; + } +#endif + /* Case 2 - Plain PIO */ + + res->buffer = USB_ADD_BYTES(pc->buffer, offset); + res->length = 0 - 1; +#if USB_HAVE_BUSDMA + res->physaddr = 0; +#endif +} + +/*------------------------------------------------------------------------* + * usbd_copy_in - copy directly to DMA-able memory + *------------------------------------------------------------------------*/ +void +usbd_copy_in(struct usb_page_cache *cache, usb_frlength_t offset, + const void *ptr, usb_frlength_t len) +{ + struct usb_page_search buf_res; + + while (len != 0) { + + usbd_get_page(cache, offset, &buf_res); + + if (buf_res.length > len) { + buf_res.length = len; + } + bcopy(ptr, buf_res.buffer, buf_res.length); + + offset += buf_res.length; + len -= buf_res.length; + ptr = USB_ADD_BYTES(ptr, buf_res.length); + } +} + +/*------------------------------------------------------------------------* + * usbd_copy_in_user - copy directly to DMA-able memory from userland + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +#if USB_HAVE_USER_IO +int +usbd_copy_in_user(struct usb_page_cache *cache, usb_frlength_t offset, + const void *ptr, usb_frlength_t len) +{ + struct usb_page_search buf_res; + int error; + + while (len != 0) { + + usbd_get_page(cache, offset, &buf_res); + + if (buf_res.length > len) { + buf_res.length = len; + } + error = copyin(ptr, buf_res.buffer, buf_res.length); + if (error) + return (error); + + offset += buf_res.length; + len -= buf_res.length; + ptr = USB_ADD_BYTES(ptr, buf_res.length); + } + return (0); /* success */ +} +#endif + +/*------------------------------------------------------------------------* + * usbd_m_copy_in - copy a mbuf chain directly into DMA-able memory + *------------------------------------------------------------------------*/ +#if USB_HAVE_MBUF +struct usb_m_copy_in_arg { + struct usb_page_cache *cache; + usb_frlength_t dst_offset; +}; + +static int +usbd_m_copy_in_cb(void *arg, void *src, uint32_t count) +{ + register struct usb_m_copy_in_arg *ua = arg; + + usbd_copy_in(ua->cache, ua->dst_offset, src, count); + ua->dst_offset += count; + return (0); +} + +void +usbd_m_copy_in(struct usb_page_cache *cache, usb_frlength_t dst_offset, + struct mbuf *m, usb_size_t src_offset, usb_frlength_t src_len) +{ + struct usb_m_copy_in_arg arg = {cache, dst_offset}; + int error; + + error = m_apply(m, src_offset, src_len, &usbd_m_copy_in_cb, &arg); +} +#endif + +/*------------------------------------------------------------------------* + * usb_uiomove - factored out code + *------------------------------------------------------------------------*/ +#if USB_HAVE_USER_IO +int +usb_uiomove(struct usb_page_cache *pc, struct uio *uio, + usb_frlength_t pc_offset, usb_frlength_t len) +{ + struct usb_page_search res; + int error = 0; + + while (len != 0) { + + usbd_get_page(pc, pc_offset, &res); + + if (res.length > len) { + res.length = len; + } + /* + * "uiomove()" can sleep so one needs to make a wrapper, + * exiting the mutex and checking things + */ + error = uiomove(res.buffer, res.length, uio); + + if (error) { + break; + } + pc_offset += res.length; + len -= res.length; + } + return (error); +} +#endif + +/*------------------------------------------------------------------------* + * usbd_copy_out - copy directly from DMA-able memory + *------------------------------------------------------------------------*/ +void +usbd_copy_out(struct usb_page_cache *cache, usb_frlength_t offset, + void *ptr, usb_frlength_t len) +{ + struct usb_page_search res; + + while (len != 0) { + + usbd_get_page(cache, offset, &res); + + if (res.length > len) { + res.length = len; + } + bcopy(res.buffer, ptr, res.length); + + offset += res.length; + len -= res.length; + ptr = USB_ADD_BYTES(ptr, res.length); + } +} + +/*------------------------------------------------------------------------* + * usbd_copy_out_user - copy directly from DMA-able memory to userland + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +#if USB_HAVE_USER_IO +int +usbd_copy_out_user(struct usb_page_cache *cache, usb_frlength_t offset, + void *ptr, usb_frlength_t len) +{ + struct usb_page_search res; + int error; + + while (len != 0) { + + usbd_get_page(cache, offset, &res); + + if (res.length > len) { + res.length = len; + } + error = copyout(res.buffer, ptr, res.length); + if (error) + return (error); + + offset += res.length; + len -= res.length; + ptr = USB_ADD_BYTES(ptr, res.length); + } + return (0); /* success */ +} +#endif + +/*------------------------------------------------------------------------* + * usbd_frame_zero - zero DMA-able memory + *------------------------------------------------------------------------*/ +void +usbd_frame_zero(struct usb_page_cache *cache, usb_frlength_t offset, + usb_frlength_t len) +{ + struct usb_page_search res; + + while (len != 0) { + + usbd_get_page(cache, offset, &res); + + if (res.length > len) { + res.length = len; + } + bzero(res.buffer, res.length); + + offset += res.length; + len -= res.length; + } +} + +#if USB_HAVE_BUSDMA + +/*------------------------------------------------------------------------* + * usb_dma_lock_cb - dummy callback + *------------------------------------------------------------------------*/ +static void +usb_dma_lock_cb(void *arg, bus_dma_lock_op_t op) +{ + /* we use "mtx_owned()" instead of this function */ +} + +/*------------------------------------------------------------------------* + * usb_dma_tag_create - allocate a DMA tag + * + * NOTE: If the "align" parameter has a value of 1 the DMA-tag will + * allow multi-segment mappings. Else all mappings are single-segment. + *------------------------------------------------------------------------*/ +static void +usb_dma_tag_create(struct usb_dma_tag *udt, + usb_size_t size, usb_size_t align) +{ + bus_dma_tag_t tag; + + if (bus_dma_tag_create + ( /* parent */ udt->tag_parent->tag, + /* alignment */ align, + /* boundary */ (align == 1) ? + USB_PAGE_SIZE : 0, + /* lowaddr */ (2ULL << (udt->tag_parent->dma_bits - 1)) - 1, + /* highaddr */ BUS_SPACE_MAXADDR, + /* filter */ NULL, + /* filterarg */ NULL, + /* maxsize */ size, + /* nsegments */ (align == 1 && size > 1) ? + (2 + (size / USB_PAGE_SIZE)) : 1, + /* maxsegsz */ (align == 1 && size > USB_PAGE_SIZE) ? + USB_PAGE_SIZE : size, + /* flags */ BUS_DMA_KEEP_PG_OFFSET, + /* lockfn */ &usb_dma_lock_cb, + /* lockarg */ NULL, + &tag)) { + tag = NULL; + } + udt->tag = tag; +} + +/*------------------------------------------------------------------------* + * usb_dma_tag_free - free a DMA tag + *------------------------------------------------------------------------*/ +static void +usb_dma_tag_destroy(struct usb_dma_tag *udt) +{ + bus_dma_tag_destroy(udt->tag); +} + +/*------------------------------------------------------------------------* + * usb_pc_alloc_mem_cb - BUS-DMA callback function + *------------------------------------------------------------------------*/ +static void +usb_pc_alloc_mem_cb(void *arg, bus_dma_segment_t *segs, + int nseg, int error) +{ + usb_pc_common_mem_cb(arg, segs, nseg, error, 0); +} + +/*------------------------------------------------------------------------* + * usb_pc_load_mem_cb - BUS-DMA callback function + *------------------------------------------------------------------------*/ +static void +usb_pc_load_mem_cb(void *arg, bus_dma_segment_t *segs, + int nseg, int error) +{ + usb_pc_common_mem_cb(arg, segs, nseg, error, 1); +} + +/*------------------------------------------------------------------------* + * usb_pc_common_mem_cb - BUS-DMA callback function + *------------------------------------------------------------------------*/ +static void +usb_pc_common_mem_cb(void *arg, bus_dma_segment_t *segs, + int nseg, int error, uint8_t isload) +{ + struct usb_dma_parent_tag *uptag; + struct usb_page_cache *pc; + struct usb_page *pg; + usb_size_t rem; + uint8_t owned; + + pc = arg; + uptag = pc->tag_parent; + + /* + * XXX There is sometimes recursive locking here. + * XXX We should try to find a better solution. + * XXX Until further the "owned" variable does + * XXX the trick. + */ + + if (error) { + goto done; + } + pg = pc->page_start; + pg->physaddr = segs->ds_addr & ~(USB_PAGE_SIZE - 1); + rem = segs->ds_addr & (USB_PAGE_SIZE - 1); + pc->page_offset_buf = rem; + pc->page_offset_end += rem; + nseg--; +#ifdef USB_DEBUG + if (rem != (USB_P2U(pc->buffer) & (USB_PAGE_SIZE - 1))) { + /* + * This check verifies that the physical address is correct: + */ + DPRINTFN(0, "Page offset was not preserved\n"); + error = 1; + goto done; + } +#endif + while (nseg > 0) { + nseg--; + segs++; + pg++; + pg->physaddr = segs->ds_addr & ~(USB_PAGE_SIZE - 1); + } + +done: + owned = mtx_owned(uptag->mtx); + if (!owned) + mtx_lock(uptag->mtx); + + uptag->dma_error = (error ? 1 : 0); + if (isload) { + (uptag->func) (uptag); + } else { + cv_broadcast(uptag->cv); + } + if (!owned) + mtx_unlock(uptag->mtx); +} + +/*------------------------------------------------------------------------* + * usb_pc_alloc_mem - allocate DMA'able memory + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +uint8_t +usb_pc_alloc_mem(struct usb_page_cache *pc, struct usb_page *pg, + usb_size_t size, usb_size_t align) +{ + struct usb_dma_parent_tag *uptag; + struct usb_dma_tag *utag; + bus_dmamap_t map; + void *ptr; + int err; + + uptag = pc->tag_parent; + + if (align != 1) { + /* + * The alignment must be greater or equal to the + * "size" else the object can be split between two + * memory pages and we get a problem! + */ + while (align < size) { + align *= 2; + if (align == 0) { + goto error; + } + } +#if 1 + /* + * XXX BUS-DMA workaround - FIXME later: + * + * We assume that that the aligment at this point of + * the code is greater than or equal to the size and + * less than two times the size, so that if we double + * the size, the size will be greater than the + * alignment. + * + * The bus-dma system has a check for "alignment" + * being less than "size". If that check fails we end + * up using contigmalloc which is page based even for + * small allocations. Try to avoid that to save + * memory, hence we sometimes to a large number of + * small allocations! + */ + if (size <= (USB_PAGE_SIZE / 2)) { + size *= 2; + } +#endif + } + /* get the correct DMA tag */ + utag = usb_dma_tag_find(uptag, size, align); + if (utag == NULL) { + goto error; + } + /* allocate memory */ + if (bus_dmamem_alloc( + utag->tag, &ptr, (BUS_DMA_WAITOK | BUS_DMA_COHERENT), &map)) { + goto error; + } + /* setup page cache */ + pc->buffer = ptr; + pc->page_start = pg; + pc->page_offset_buf = 0; + pc->page_offset_end = size; + pc->map = map; + pc->tag = utag->tag; + pc->ismultiseg = (align == 1); + + mtx_lock(uptag->mtx); + + /* load memory into DMA */ + err = bus_dmamap_load( + utag->tag, map, ptr, size, &usb_pc_alloc_mem_cb, + pc, (BUS_DMA_WAITOK | BUS_DMA_COHERENT)); + + if (err == EINPROGRESS) { + cv_wait(uptag->cv, uptag->mtx); + err = 0; + } + mtx_unlock(uptag->mtx); + + if (err || uptag->dma_error) { + bus_dmamem_free(utag->tag, ptr, map); + goto error; + } + bzero(ptr, size); + + usb_pc_cpu_flush(pc); + + return (0); + +error: + /* reset most of the page cache */ + pc->buffer = NULL; + pc->page_start = NULL; + pc->page_offset_buf = 0; + pc->page_offset_end = 0; + pc->map = NULL; + pc->tag = NULL; + return (1); +} + +/*------------------------------------------------------------------------* + * usb_pc_free_mem - free DMA memory + * + * This function is NULL safe. + *------------------------------------------------------------------------*/ +void +usb_pc_free_mem(struct usb_page_cache *pc) +{ + if (pc && pc->buffer) { + + bus_dmamap_unload(pc->tag, pc->map); + + bus_dmamem_free(pc->tag, pc->buffer, pc->map); + + pc->buffer = NULL; + } +} + +/*------------------------------------------------------------------------* + * usb_pc_load_mem - load virtual memory into DMA + * + * Return values: + * 0: Success + * Else: Error + *------------------------------------------------------------------------*/ +uint8_t +usb_pc_load_mem(struct usb_page_cache *pc, usb_size_t size, uint8_t sync) +{ + /* setup page cache */ + pc->page_offset_buf = 0; + pc->page_offset_end = size; + pc->ismultiseg = 1; + + mtx_assert(pc->tag_parent->mtx, MA_OWNED); + + if (size > 0) { + if (sync) { + struct usb_dma_parent_tag *uptag; + int err; + + uptag = pc->tag_parent; + + /* + * We have to unload the previous loaded DMA + * pages before trying to load a new one! + */ + bus_dmamap_unload(pc->tag, pc->map); + + /* + * Try to load memory into DMA. + */ + err = bus_dmamap_load( + pc->tag, pc->map, pc->buffer, size, + &usb_pc_alloc_mem_cb, pc, BUS_DMA_WAITOK); + if (err == EINPROGRESS) { + cv_wait(uptag->cv, uptag->mtx); + err = 0; + } + if (err || uptag->dma_error) { + return (1); + } + } else { + + /* + * We have to unload the previous loaded DMA + * pages before trying to load a new one! + */ + bus_dmamap_unload(pc->tag, pc->map); + + /* + * Try to load memory into DMA. The callback + * will be called in all cases: + */ + if (bus_dmamap_load( + pc->tag, pc->map, pc->buffer, size, + &usb_pc_load_mem_cb, pc, BUS_DMA_WAITOK)) { + } + } + } else { + if (!sync) { + /* + * Call callback so that refcount is decremented + * properly: + */ + pc->tag_parent->dma_error = 0; + (pc->tag_parent->func) (pc->tag_parent); + } + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb_pc_cpu_invalidate - invalidate CPU cache + *------------------------------------------------------------------------*/ +void +usb_pc_cpu_invalidate(struct usb_page_cache *pc) +{ + if (pc->page_offset_end == pc->page_offset_buf) { + /* nothing has been loaded into this page cache! */ + return; + } + + /* + * TODO: We currently do XXX_POSTREAD and XXX_PREREAD at the + * same time, but in the future we should try to isolate the + * different cases to optimise the code. --HPS + */ + bus_dmamap_sync(pc->tag, pc->map, BUS_DMASYNC_POSTREAD); + bus_dmamap_sync(pc->tag, pc->map, BUS_DMASYNC_PREREAD); +} + +/*------------------------------------------------------------------------* + * usb_pc_cpu_flush - flush CPU cache + *------------------------------------------------------------------------*/ +void +usb_pc_cpu_flush(struct usb_page_cache *pc) +{ + if (pc->page_offset_end == pc->page_offset_buf) { + /* nothing has been loaded into this page cache! */ + return; + } + bus_dmamap_sync(pc->tag, pc->map, BUS_DMASYNC_PREWRITE); +} + +/*------------------------------------------------------------------------* + * usb_pc_dmamap_create - create a DMA map + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +uint8_t +usb_pc_dmamap_create(struct usb_page_cache *pc, usb_size_t size) +{ + struct usb_xfer_root *info; + struct usb_dma_tag *utag; + + /* get info */ + info = USB_DMATAG_TO_XROOT(pc->tag_parent); + + /* sanity check */ + if (info == NULL) { + goto error; + } + utag = usb_dma_tag_find(pc->tag_parent, size, 1); + if (utag == NULL) { + goto error; + } + /* create DMA map */ + if (bus_dmamap_create(utag->tag, 0, &pc->map)) { + goto error; + } + pc->tag = utag->tag; + return 0; /* success */ + +error: + pc->map = NULL; + pc->tag = NULL; + return 1; /* failure */ +} + +/*------------------------------------------------------------------------* + * usb_pc_dmamap_destroy + * + * This function is NULL safe. + *------------------------------------------------------------------------*/ +void +usb_pc_dmamap_destroy(struct usb_page_cache *pc) +{ + if (pc && pc->tag) { + bus_dmamap_destroy(pc->tag, pc->map); + pc->tag = NULL; + pc->map = NULL; + } +} + +/*------------------------------------------------------------------------* + * usb_dma_tag_find - factored out code + *------------------------------------------------------------------------*/ +struct usb_dma_tag * +usb_dma_tag_find(struct usb_dma_parent_tag *udpt, + usb_size_t size, usb_size_t align) +{ + struct usb_dma_tag *udt; + uint8_t nudt; + + USB_ASSERT(align > 0, ("Invalid parameter align = 0\n")); + USB_ASSERT(size > 0, ("Invalid parameter size = 0\n")); + + udt = udpt->utag_first; + nudt = udpt->utag_max; + + while (nudt--) { + + if (udt->align == 0) { + usb_dma_tag_create(udt, size, align); + if (udt->tag == NULL) { + return (NULL); + } + udt->align = align; + udt->size = size; + return (udt); + } + if ((udt->align == align) && (udt->size == size)) { + return (udt); + } + udt++; + } + return (NULL); +} + +/*------------------------------------------------------------------------* + * usb_dma_tag_setup - initialise USB DMA tags + *------------------------------------------------------------------------*/ +void +usb_dma_tag_setup(struct usb_dma_parent_tag *udpt, + struct usb_dma_tag *udt, bus_dma_tag_t dmat, + struct mtx *mtx, usb_dma_callback_t *func, + uint8_t ndmabits, uint8_t nudt) +{ + bzero(udpt, sizeof(*udpt)); + + /* sanity checking */ + if ((nudt == 0) || + (ndmabits == 0) || + (mtx == NULL)) { + /* something is corrupt */ + return; + } + /* initialise condition variable */ + cv_init(udpt->cv, "USB DMA CV"); + + /* store some information */ + udpt->mtx = mtx; + udpt->func = func; + udpt->tag = dmat; + udpt->utag_first = udt; + udpt->utag_max = nudt; + udpt->dma_bits = ndmabits; + + while (nudt--) { + bzero(udt, sizeof(*udt)); + udt->tag_parent = udpt; + udt++; + } +} + +/*------------------------------------------------------------------------* + * usb_bus_tag_unsetup - factored out code + *------------------------------------------------------------------------*/ +void +usb_dma_tag_unsetup(struct usb_dma_parent_tag *udpt) +{ + struct usb_dma_tag *udt; + uint8_t nudt; + + udt = udpt->utag_first; + nudt = udpt->utag_max; + + while (nudt--) { + + if (udt->align) { + /* destroy the USB DMA tag */ + usb_dma_tag_destroy(udt); + udt->align = 0; + } + udt++; + } + + if (udpt->utag_max) { + /* destroy the condition variable */ + cv_destroy(udpt->cv); + } +} + +/*------------------------------------------------------------------------* + * usb_bdma_work_loop + * + * This function handles loading of virtual buffers into DMA and is + * only called when "dma_refcount" is zero. + *------------------------------------------------------------------------*/ +void +usb_bdma_work_loop(struct usb_xfer_queue *pq) +{ + struct usb_xfer_root *info; + struct usb_xfer *xfer; + usb_frcount_t nframes; + + xfer = pq->curr; + info = xfer->xroot; + + mtx_assert(info->xfer_mtx, MA_OWNED); + + if (xfer->error) { + /* some error happened */ + USB_BUS_LOCK(info->bus); + usbd_transfer_done(xfer, 0); + USB_BUS_UNLOCK(info->bus); + return; + } + if (!xfer->flags_int.bdma_setup) { + struct usb_page *pg; + usb_frlength_t frlength_0; + uint8_t isread; + + xfer->flags_int.bdma_setup = 1; + + /* reset BUS-DMA load state */ + + info->dma_error = 0; + + if (xfer->flags_int.isochronous_xfr) { + /* only one frame buffer */ + nframes = 1; + frlength_0 = xfer->sumlen; + } else { + /* can be multiple frame buffers */ + nframes = xfer->nframes; + frlength_0 = xfer->frlengths[0]; + } + + /* + * Set DMA direction first. This is needed to + * select the correct cache invalidate and cache + * flush operations. + */ + isread = USB_GET_DATA_ISREAD(xfer); + pg = xfer->dma_page_ptr; + + if (xfer->flags_int.control_xfr && + xfer->flags_int.control_hdr) { + /* special case */ + if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) { + /* The device controller writes to memory */ + xfer->frbuffers[0].isread = 1; + } else { + /* The host controller reads from memory */ + xfer->frbuffers[0].isread = 0; + } + } else { + /* default case */ + xfer->frbuffers[0].isread = isread; + } + + /* + * Setup the "page_start" pointer which points to an array of + * USB pages where information about the physical address of a + * page will be stored. Also initialise the "isread" field of + * the USB page caches. + */ + xfer->frbuffers[0].page_start = pg; + + info->dma_nframes = nframes; + info->dma_currframe = 0; + info->dma_frlength_0 = frlength_0; + + pg += (frlength_0 / USB_PAGE_SIZE); + pg += 2; + + while (--nframes > 0) { + xfer->frbuffers[nframes].isread = isread; + xfer->frbuffers[nframes].page_start = pg; + + pg += (xfer->frlengths[nframes] / USB_PAGE_SIZE); + pg += 2; + } + + } + if (info->dma_error) { + USB_BUS_LOCK(info->bus); + usbd_transfer_done(xfer, USB_ERR_DMA_LOAD_FAILED); + USB_BUS_UNLOCK(info->bus); + return; + } + if (info->dma_currframe != info->dma_nframes) { + + if (info->dma_currframe == 0) { + /* special case */ + usb_pc_load_mem(xfer->frbuffers, + info->dma_frlength_0, 0); + } else { + /* default case */ + nframes = info->dma_currframe; + usb_pc_load_mem(xfer->frbuffers + nframes, + xfer->frlengths[nframes], 0); + } + + /* advance frame index */ + info->dma_currframe++; + + return; + } + /* go ahead */ + usb_bdma_pre_sync(xfer); + + /* start loading next USB transfer, if any */ + usb_command_wrapper(pq, NULL); + + /* finally start the hardware */ + usbd_pipe_enter(xfer); +} + +/*------------------------------------------------------------------------* + * usb_bdma_done_event + * + * This function is called when the BUS-DMA has loaded virtual memory + * into DMA, if any. + *------------------------------------------------------------------------*/ +void +usb_bdma_done_event(struct usb_dma_parent_tag *udpt) +{ + struct usb_xfer_root *info; + + info = USB_DMATAG_TO_XROOT(udpt); + + mtx_assert(info->xfer_mtx, MA_OWNED); + + /* copy error */ + info->dma_error = udpt->dma_error; + + /* enter workloop again */ + usb_command_wrapper(&info->dma_q, + info->dma_q.curr); +} + +/*------------------------------------------------------------------------* + * usb_bdma_pre_sync + * + * This function handles DMA synchronisation that must be done before + * an USB transfer is started. + *------------------------------------------------------------------------*/ +void +usb_bdma_pre_sync(struct usb_xfer *xfer) +{ + struct usb_page_cache *pc; + usb_frcount_t nframes; + + if (xfer->flags_int.isochronous_xfr) { + /* only one frame buffer */ + nframes = 1; + } else { + /* can be multiple frame buffers */ + nframes = xfer->nframes; + } + + pc = xfer->frbuffers; + + while (nframes--) { + + if (pc->isread) { + usb_pc_cpu_invalidate(pc); + } else { + usb_pc_cpu_flush(pc); + } + pc++; + } +} + +/*------------------------------------------------------------------------* + * usb_bdma_post_sync + * + * This function handles DMA synchronisation that must be done after + * an USB transfer is complete. + *------------------------------------------------------------------------*/ +void +usb_bdma_post_sync(struct usb_xfer *xfer) +{ + struct usb_page_cache *pc; + usb_frcount_t nframes; + + if (xfer->flags_int.isochronous_xfr) { + /* only one frame buffer */ + nframes = 1; + } else { + /* can be multiple frame buffers */ + nframes = xfer->nframes; + } + + pc = xfer->frbuffers; + + while (nframes--) { + if (pc->isread) { + usb_pc_cpu_invalidate(pc); + } + pc++; + } +} + +#endif diff --git a/freebsd/sys/dev/usb/usb_busdma.h b/freebsd/sys/dev/usb/usb_busdma.h new file mode 100644 index 00000000..bd761819 --- /dev/null +++ b/freebsd/sys/dev/usb/usb_busdma.h @@ -0,0 +1,161 @@ +/* $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. + */ + +#ifndef _USB_BUSDMA_HH_ +#define _USB_BUSDMA_HH_ + +#include <freebsd/sys/uio.h> +#include <freebsd/sys/mbuf.h> + +#include <freebsd/machine/bus.h> + +/* defines */ + +#define USB_PAGE_SIZE PAGE_SIZE /* use system PAGE_SIZE */ + +#if (__FreeBSD_version >= 700020) +#define USB_GET_DMA_TAG(dev) bus_get_dma_tag(dev) +#else +#define USB_GET_DMA_TAG(dev) NULL /* XXX */ +#endif + +/* structure prototypes */ + +struct usb_xfer_root; +struct usb_dma_parent_tag; +struct usb_dma_tag; + +/* + * The following typedef defines the USB DMA load done callback. + */ + +typedef void (usb_dma_callback_t)(struct usb_dma_parent_tag *udpt); + +/* + * The following structure defines physical and non kernel virtual + * address of a memory page having size USB_PAGE_SIZE. + */ +struct usb_page { +#if USB_HAVE_BUSDMA + bus_size_t physaddr; + void *buffer; /* non Kernel Virtual Address */ +#endif +}; + +/* + * The following structure is used when needing the kernel virtual + * pointer and the physical address belonging to an offset in an USB + * page cache. + */ +struct usb_page_search { + void *buffer; +#if USB_HAVE_BUSDMA + bus_size_t physaddr; +#endif + usb_size_t length; +}; + +/* + * The following structure is used to keep information about a DMA + * memory allocation. + */ +struct usb_page_cache { + +#if USB_HAVE_BUSDMA + bus_dma_tag_t tag; + bus_dmamap_t map; + struct usb_page *page_start; +#endif + struct usb_dma_parent_tag *tag_parent; /* always set */ + void *buffer; /* virtual buffer pointer */ +#if USB_HAVE_BUSDMA + usb_size_t page_offset_buf; + usb_size_t page_offset_end; + uint8_t isread:1; /* set if we are currently reading + * from the memory. Else write. */ + uint8_t ismultiseg:1; /* set if we can have multiple + * segments */ +#endif +}; + +/* + * The following structure describes the parent USB DMA tag. + */ +#if USB_HAVE_BUSDMA +struct usb_dma_parent_tag { + struct cv cv[1]; /* internal condition variable */ + bus_dma_tag_t tag; /* always set */ + + struct mtx *mtx; /* private mutex, always set */ + usb_dma_callback_t *func; /* load complete callback function */ + struct usb_dma_tag *utag_first;/* pointer to first USB DMA tag */ + uint8_t dma_error; /* set if DMA load operation failed */ + uint8_t dma_bits; /* number of DMA address lines */ + uint8_t utag_max; /* number of USB DMA tags */ +}; +#else +struct usb_dma_parent_tag {}; /* empty struct */ +#endif + +/* + * The following structure describes an USB DMA tag. + */ +#if USB_HAVE_BUSDMA +struct usb_dma_tag { + struct usb_dma_parent_tag *tag_parent; + bus_dma_tag_t tag; + usb_size_t align; + usb_size_t size; +}; +#else +struct usb_dma_tag {}; /* empty struct */ +#endif + +/* function prototypes */ + +int usb_uiomove(struct usb_page_cache *pc, struct uio *uio, + usb_frlength_t pc_offset, usb_frlength_t len); +struct usb_dma_tag *usb_dma_tag_find(struct usb_dma_parent_tag *udpt, + usb_size_t size, usb_size_t align); +uint8_t usb_pc_alloc_mem(struct usb_page_cache *pc, struct usb_page *pg, + usb_size_t size, usb_size_t align); +uint8_t usb_pc_dmamap_create(struct usb_page_cache *pc, usb_size_t size); +uint8_t usb_pc_load_mem(struct usb_page_cache *pc, usb_size_t size, + uint8_t sync); +void usb_bdma_done_event(struct usb_dma_parent_tag *udpt); +void usb_bdma_post_sync(struct usb_xfer *xfer); +void usb_bdma_pre_sync(struct usb_xfer *xfer); +void usb_bdma_work_loop(struct usb_xfer_queue *pq); +void usb_dma_tag_setup(struct usb_dma_parent_tag *udpt, + struct usb_dma_tag *udt, bus_dma_tag_t dmat, struct mtx *mtx, + usb_dma_callback_t *func, uint8_t ndmabits, uint8_t nudt); +void usb_dma_tag_unsetup(struct usb_dma_parent_tag *udpt); +void usb_pc_cpu_flush(struct usb_page_cache *pc); +void usb_pc_cpu_invalidate(struct usb_page_cache *pc); +void usb_pc_dmamap_destroy(struct usb_page_cache *pc); +void usb_pc_free_mem(struct usb_page_cache *pc); + +#endif /* _USB_BUSDMA_HH_ */ diff --git a/freebsd/sys/dev/usb/usb_cdc.h b/freebsd/sys/dev/usb/usb_cdc.h new file mode 100644 index 00000000..632cfe9c --- /dev/null +++ b/freebsd/sys/dev/usb/usb_cdc.h @@ -0,0 +1,295 @@ +/* $NetBSD: usbcdc.h,v 1.9 2004/10/23 13:24:24 augustss Exp $ */ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _USB_CDC_HH_ +#define _USB_CDC_HH_ + +#define UDESCSUB_CDC_HEADER 0 +#define UDESCSUB_CDC_CM 1 /* Call Management */ +#define UDESCSUB_CDC_ACM 2 /* Abstract Control Model */ +#define UDESCSUB_CDC_DLM 3 /* Direct Line Management */ +#define UDESCSUB_CDC_TRF 4 /* Telephone Ringer */ +#define UDESCSUB_CDC_TCLSR 5 /* Telephone Call */ +#define UDESCSUB_CDC_UNION 6 +#define UDESCSUB_CDC_CS 7 /* Country Selection */ +#define UDESCSUB_CDC_TOM 8 /* Telephone Operational Modes */ +#define UDESCSUB_CDC_USBT 9 /* USB Terminal */ +#define UDESCSUB_CDC_NCT 10 +#define UDESCSUB_CDC_PUF 11 +#define UDESCSUB_CDC_EUF 12 +#define UDESCSUB_CDC_MCMF 13 +#define UDESCSUB_CDC_CCMF 14 +#define UDESCSUB_CDC_ENF 15 +#define UDESCSUB_CDC_ANF 16 + +struct usb_cdc_header_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uWord bcdCDC; +} __packed; + +struct usb_cdc_cm_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bmCapabilities; +#define USB_CDC_CM_DOES_CM 0x01 +#define USB_CDC_CM_OVER_DATA 0x02 + uByte bDataInterface; +} __packed; + +struct usb_cdc_acm_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bmCapabilities; +#define USB_CDC_ACM_HAS_FEATURE 0x01 +#define USB_CDC_ACM_HAS_LINE 0x02 +#define USB_CDC_ACM_HAS_BREAK 0x04 +#define USB_CDC_ACM_HAS_NETWORK_CONN 0x08 +} __packed; + +struct usb_cdc_union_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bMasterInterface; + uByte bSlaveInterface[1]; +} __packed; + +struct usb_cdc_ethernet_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte iMacAddress; + uDWord bmEthernetStatistics; + uWord wMaxSegmentSize; + uWord wNumberMCFilters; + uByte bNumberPowerFilters; +} __packed; + +#define UCDC_SEND_ENCAPSULATED_COMMAND 0x00 +#define UCDC_GET_ENCAPSULATED_RESPONSE 0x01 +#define UCDC_SET_COMM_FEATURE 0x02 +#define UCDC_GET_COMM_FEATURE 0x03 +#define UCDC_ABSTRACT_STATE 0x01 +#define UCDC_COUNTRY_SETTING 0x02 +#define UCDC_CLEAR_COMM_FEATURE 0x04 +#define UCDC_SET_LINE_CODING 0x20 +#define UCDC_GET_LINE_CODING 0x21 +#define UCDC_SET_CONTROL_LINE_STATE 0x22 +#define UCDC_LINE_DTR 0x0001 +#define UCDC_LINE_RTS 0x0002 +#define UCDC_SEND_BREAK 0x23 +#define UCDC_BREAK_ON 0xffff +#define UCDC_BREAK_OFF 0x0000 + +struct usb_cdc_abstract_state { + uWord wState; +#define UCDC_IDLE_SETTING 0x0001 +#define UCDC_DATA_MULTIPLEXED 0x0002 +} __packed; + +#define UCDC_ABSTRACT_STATE_LENGTH 2 + +struct usb_cdc_line_state { + uDWord dwDTERate; + uByte bCharFormat; +#define UCDC_STOP_BIT_1 0 +#define UCDC_STOP_BIT_1_5 1 +#define UCDC_STOP_BIT_2 2 + uByte bParityType; +#define UCDC_PARITY_NONE 0 +#define UCDC_PARITY_ODD 1 +#define UCDC_PARITY_EVEN 2 +#define UCDC_PARITY_MARK 3 +#define UCDC_PARITY_SPACE 4 + uByte bDataBits; +} __packed; + +#define UCDC_LINE_STATE_LENGTH 7 + +struct usb_cdc_notification { + uByte bmRequestType; +#define UCDC_NOTIFICATION 0xa1 + uByte bNotification; +#define UCDC_N_NETWORK_CONNECTION 0x00 +#define UCDC_N_RESPONSE_AVAILABLE 0x01 +#define UCDC_N_AUX_JACK_HOOK_STATE 0x08 +#define UCDC_N_RING_DETECT 0x09 +#define UCDC_N_SERIAL_STATE 0x20 +#define UCDC_N_CALL_STATE_CHANGED 0x28 +#define UCDC_N_LINE_STATE_CHANGED 0x29 +#define UCDC_N_CONNECTION_SPEED_CHANGE 0x2a + uWord wValue; + uWord wIndex; + uWord wLength; + uByte data[16]; +} __packed; + +#define UCDC_NOTIFICATION_LENGTH 8 + +/* + * Bits set in the SERIAL STATE notifcation (first byte of data) + */ + +#define UCDC_N_SERIAL_OVERRUN 0x40 +#define UCDC_N_SERIAL_PARITY 0x20 +#define UCDC_N_SERIAL_FRAMING 0x10 +#define UCDC_N_SERIAL_RI 0x08 +#define UCDC_N_SERIAL_BREAK 0x04 +#define UCDC_N_SERIAL_DSR 0x02 +#define UCDC_N_SERIAL_DCD 0x01 + +/* Serial state bit masks */ +#define UCDC_MDM_RXCARRIER 0x01 +#define UCDC_MDM_TXCARRIER 0x02 +#define UCDC_MDM_BREAK 0x04 +#define UCDC_MDM_RING 0x08 +#define UCDC_MDM_FRAMING_ERR 0x10 +#define UCDC_MDM_PARITY_ERR 0x20 +#define UCDC_MDM_OVERRUN_ERR 0x40 + +/* + * Network Control Model, NCM16 + NCM32, protocol definitions + */ +struct usb_ncm16_hdr { + uDWord dwSignature; + uWord wHeaderLength; + uWord wSequence; + uWord wBlockLength; + uWord wDptIndex; +} __packed; + +struct usb_ncm16_dp { + uWord wFrameIndex; + uWord wFrameLength; +} __packed; + +struct usb_ncm16_dpt { + uDWord dwSignature; + uWord wLength; + uWord wNextNdpIndex; + struct usb_ncm16_dp dp[0]; +} __packed; + +struct usb_ncm32_hdr { + uDWord dwSignature; + uWord wHeaderLength; + uWord wSequence; + uDWord dwBlockLength; + uDWord dwDptIndex; +} __packed; + +struct usb_ncm32_dp { + uDWord dwFrameIndex; + uDWord dwFrameLength; +} __packed; + +struct usb_ncm32_dpt { + uDWord dwSignature; + uWord wLength; + uWord wReserved6; + uDWord dwNextNdpIndex; + uDWord dwReserved12; + struct usb_ncm32_dp dp[0]; +} __packed; + +/* Communications interface class specific descriptors */ + +#define UCDC_NCM_FUNC_DESC_SUBTYPE 0x1A + +struct usb_ncm_func_descriptor { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bcdNcmVersion[2]; + uByte bmNetworkCapabilities; +#define UCDC_NCM_CAP_FILTER 0x01 +#define UCDC_NCM_CAP_MAC_ADDR 0x02 +#define UCDC_NCM_CAP_ENCAP 0x04 +#define UCDC_NCM_CAP_MAX_DATA 0x08 +#define UCDC_NCM_CAP_CRCMODE 0x10 +#define UCDC_NCM_CAP_MAX_DGRAM 0x20 +} __packed; + +/* Communications interface specific class request codes */ + +#define UCDC_NCM_SET_ETHERNET_MULTICAST_FILTERS 0x40 +#define UCDC_NCM_SET_ETHERNET_POWER_MGMT_PATTERN_FILTER 0x41 +#define UCDC_NCM_GET_ETHERNET_POWER_MGMT_PATTERN_FILTER 0x42 +#define UCDC_NCM_SET_ETHERNET_PACKET_FILTER 0x43 +#define UCDC_NCM_GET_ETHERNET_STATISTIC 0x44 +#define UCDC_NCM_GET_NTB_PARAMETERS 0x80 +#define UCDC_NCM_GET_NET_ADDRESS 0x81 +#define UCDC_NCM_SET_NET_ADDRESS 0x82 +#define UCDC_NCM_GET_NTB_FORMAT 0x83 +#define UCDC_NCM_SET_NTB_FORMAT 0x84 +#define UCDC_NCM_GET_NTB_INPUT_SIZE 0x85 +#define UCDC_NCM_SET_NTB_INPUT_SIZE 0x86 +#define UCDC_NCM_GET_MAX_DATAGRAM_SIZE 0x87 +#define UCDC_NCM_SET_MAX_DATAGRAM_SIZE 0x88 +#define UCDC_NCM_GET_CRC_MODE 0x89 +#define UCDC_NCM_SET_CRC_MODE 0x8A + +struct usb_ncm_parameters { + uWord wLength; + uWord bmNtbFormatsSupported; +#define UCDC_NCM_FORMAT_NTB16 0x0001 +#define UCDC_NCM_FORMAT_NTB32 0x0002 + uDWord dwNtbInMaxSize; + uWord wNdpInDivisor; + uWord wNdpInPayloadRemainder; + uWord wNdpInAlignment; + uWord wReserved14; + uDWord dwNtbOutMaxSize; + uWord wNdpOutDivisor; + uWord wNdpOutPayloadRemainder; + uWord wNdpOutAlignment; + uWord wNtbOutMaxDatagrams; +} __packed; + +/* Communications interface specific class notification codes */ +#define UCDC_NCM_NOTIF_NETWORK_CONNECTION 0x00 +#define UCDC_NCM_NOTIF_RESPONSE_AVAILABLE 0x01 +#define UCDC_NCM_NOTIF_CONNECTION_SPEED_CHANGE 0x2A + +#endif /* _USB_CDC_HH_ */ diff --git a/freebsd/sys/dev/usb/usb_controller.h b/freebsd/sys/dev/usb/usb_controller.h new file mode 100644 index 00000000..8f3f3de4 --- /dev/null +++ b/freebsd/sys/dev/usb/usb_controller.h @@ -0,0 +1,225 @@ +/* $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. + */ + +#ifndef _USB_CONTROLLER_HH_ +#define _USB_CONTROLLER_HH_ + +/* defines */ + +#define USB_BUS_DMA_TAG_MAX 8 + +/* structure prototypes */ + +struct usb_bus; +struct usb_page; +struct usb_endpoint; +struct usb_page_cache; +struct usb_setup_params; +struct usb_hw_ep_profile; +struct usb_fs_isoc_schedule; +struct usb_config_descriptor; +struct usb_endpoint_descriptor; + +/* typedefs */ + +typedef void (usb_bus_mem_sub_cb_t)(struct usb_bus *bus, struct usb_page_cache *pc, struct usb_page *pg, usb_size_t size, usb_size_t align); +typedef void (usb_bus_mem_cb_t)(struct usb_bus *bus, usb_bus_mem_sub_cb_t *scb); + +/* + * The following structure is used to define all the USB BUS + * callbacks. + */ +struct usb_bus_methods { + + /* USB Device and Host mode - Mandatory */ + + usb_handle_req_t *roothub_exec; + + void (*endpoint_init) (struct usb_device *, + struct usb_endpoint_descriptor *, struct usb_endpoint *); + void (*xfer_setup) (struct usb_setup_params *); + void (*xfer_unsetup) (struct usb_xfer *); + void (*get_dma_delay) (struct usb_device *, uint32_t *); + void (*device_suspend) (struct usb_device *); + void (*device_resume) (struct usb_device *); + void (*set_hw_power) (struct usb_bus *); + + /* + * The following flag is set if one or more control transfers are + * active: + */ +#define USB_HW_POWER_CONTROL 0x01 + /* + * The following flag is set if one or more bulk transfers are + * active: + */ +#define USB_HW_POWER_BULK 0x02 + /* + * The following flag is set if one or more interrupt transfers are + * active: + */ +#define USB_HW_POWER_INTERRUPT 0x04 + /* + * The following flag is set if one or more isochronous transfers + * are active: + */ +#define USB_HW_POWER_ISOC 0x08 + /* + * The following flag is set if one or more non-root-HUB devices + * are present on the given USB bus: + */ +#define USB_HW_POWER_NON_ROOT_HUB 0x10 + + /* USB Device mode only - Mandatory */ + + void (*get_hw_ep_profile) (struct usb_device *udev, const struct usb_hw_ep_profile **ppf, uint8_t ep_addr); + void (*set_stall) (struct usb_device *udev, struct usb_xfer *xfer, struct usb_endpoint *ep, uint8_t *did_stall); + + /* USB Device mode mandatory. USB Host mode optional. */ + + void (*clear_stall) (struct usb_device *udev, struct usb_endpoint *ep); + + /* Optional transfer polling support */ + + void (*xfer_poll) (struct usb_bus *); + + /* Optional fixed power mode support */ + + void (*get_power_mode) (struct usb_device *udev, int8_t *pmode); + + /* Optional endpoint uninit */ + + void (*endpoint_uninit) (struct usb_device *, struct usb_endpoint *); + + /* Optional device init */ + + usb_error_t (*device_init) (struct usb_device *); + + /* Optional device uninit */ + + void (*device_uninit) (struct usb_device *); + + /* Optional for device and host mode */ + + void (*start_dma_delay) (struct usb_xfer *); + + void (*device_state_change) (struct usb_device *); + + /* Optional for host mode */ + + usb_error_t (*set_address) (struct usb_device *, struct mtx *, uint16_t); +}; + +/* + * The following structure is used to define all the USB pipe + * callbacks. + */ +struct usb_pipe_methods { + + /* Mandatory USB Device and Host mode callbacks: */ + + void (*open)(struct usb_xfer *); + void (*close)(struct usb_xfer *); + + void (*enter)(struct usb_xfer *); + void (*start)(struct usb_xfer *); + + /* Optional */ + + void *info; +}; + +/* + * The following structure keeps information about what a hardware USB + * endpoint supports. + */ +struct usb_hw_ep_profile { + uint16_t max_in_frame_size; /* IN-token direction */ + uint16_t max_out_frame_size; /* OUT-token direction */ + uint8_t is_simplex:1; + uint8_t support_multi_buffer:1; + uint8_t support_bulk:1; + uint8_t support_control:1; + uint8_t support_interrupt:1; + uint8_t support_isochronous:1; + uint8_t support_in:1; /* IN-token is supported */ + uint8_t support_out:1; /* OUT-token is supported */ +}; + +/* + * The following structure is used when trying to allocate hardware + * endpoints for an USB configuration in USB device side mode. + */ +struct usb_hw_ep_scratch_sub { + const struct usb_hw_ep_profile *pf; + uint16_t max_frame_size; + uint8_t hw_endpoint_out; + uint8_t hw_endpoint_in; + uint8_t needs_ep_type; + uint8_t needs_in:1; + uint8_t needs_out:1; +}; + +/* + * The following structure is used when trying to allocate hardware + * endpoints for an USB configuration in USB device side mode. + */ +struct usb_hw_ep_scratch { + struct usb_hw_ep_scratch_sub ep[USB_EP_MAX]; + struct usb_hw_ep_scratch_sub *ep_max; + struct usb_config_descriptor *cd; + struct usb_device *udev; + struct usb_bus_methods *methods; + uint8_t bmOutAlloc[(USB_EP_MAX + 15) / 16]; + uint8_t bmInAlloc[(USB_EP_MAX + 15) / 16]; +}; + +/* + * The following structure is used when generating USB descriptors + * from USB templates. + */ +struct usb_temp_setup { + void *buf; + usb_size_t size; + enum usb_dev_speed usb_speed; + uint8_t self_powered; + uint8_t bNumEndpoints; + uint8_t bInterfaceNumber; + uint8_t bAlternateSetting; + uint8_t bConfigurationValue; + usb_error_t err; +}; + +/* prototypes */ + +void usb_bus_mem_flush_all(struct usb_bus *bus, usb_bus_mem_cb_t *cb); +uint8_t usb_bus_mem_alloc_all(struct usb_bus *bus, bus_dma_tag_t dmat, usb_bus_mem_cb_t *cb); +void usb_bus_mem_free_all(struct usb_bus *bus, usb_bus_mem_cb_t *cb); +uint16_t usb_isoc_time_expand(struct usb_bus *bus, uint16_t isoc_time_curr); +uint16_t usbd_fs_isoc_schedule_isoc_time_expand(struct usb_device *udev, struct usb_fs_isoc_schedule **pp_start, struct usb_fs_isoc_schedule **pp_end, uint16_t isoc_time); +uint8_t usbd_fs_isoc_schedule_alloc(struct usb_fs_isoc_schedule *fss, uint8_t *pstart, uint16_t len); + +#endif /* _USB_CONTROLLER_HH_ */ diff --git a/freebsd/sys/dev/usb/usb_core.c b/freebsd/sys/dev/usb/usb_core.c new file mode 100644 index 00000000..17f9d903 --- /dev/null +++ b/freebsd/sys/dev/usb/usb_core.c @@ -0,0 +1,62 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/* $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. + */ + +/* + * USB specifications and other documentation can be found at + * http://www.usb.org/developers/docs/ and + * http://www.usb.org/developers/devclass_docs/ + */ + +#include <freebsd/sys/stdint.h> +#include <freebsd/sys/stddef.h> +#include <freebsd/sys/param.h> +#include <freebsd/sys/queue.h> +#include <freebsd/sys/types.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/bus.h> +#include <freebsd/sys/linker_set.h> +#include <freebsd/sys/module.h> +#include <freebsd/sys/lock.h> +#include <freebsd/sys/mutex.h> +#include <freebsd/sys/condvar.h> +#include <freebsd/sys/sysctl.h> +#include <freebsd/sys/sx.h> +#include <freebsd/sys/unistd.h> +#include <freebsd/sys/callout.h> +#include <freebsd/sys/malloc.h> +#include <freebsd/sys/priv.h> + +#include <freebsd/dev/usb/usb.h> +#include <freebsd/dev/usb/usbdi.h> + +MALLOC_DEFINE(M_USB, "USB", "USB"); +MALLOC_DEFINE(M_USBDEV, "USBdev", "USB device"); +MALLOC_DEFINE(M_USBHC, "USBHC", "USB host controller"); + +MODULE_VERSION(usb, 1); diff --git a/freebsd/sys/dev/usb/usb_core.h b/freebsd/sys/dev/usb/usb_core.h new file mode 100644 index 00000000..9d724171 --- /dev/null +++ b/freebsd/sys/dev/usb/usb_core.h @@ -0,0 +1,183 @@ +/* $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. + */ + +/* + * Including this file is mandatory for all USB related c-files in the kernel. + */ + +#ifndef _USB_CORE_HH_ +#define _USB_CORE_HH_ + +/* + * The following macro will tell if an USB transfer is currently + * receiving or transferring data. + */ +#define USB_GET_DATA_ISREAD(xfer) ((xfer)->flags_int.usb_mode == \ + USB_MODE_DEVICE ? (((xfer)->endpointno & UE_DIR_IN) ? 0 : 1) : \ + (((xfer)->endpointno & UE_DIR_IN) ? 1 : 0)) + +/* macros */ + +#define USB_BUS_LOCK(_b) mtx_lock(&(_b)->bus_mtx) +#define USB_BUS_UNLOCK(_b) mtx_unlock(&(_b)->bus_mtx) +#define USB_BUS_LOCK_ASSERT(_b, _t) mtx_assert(&(_b)->bus_mtx, _t) +#define USB_XFER_LOCK(_x) mtx_lock((_x)->xroot->xfer_mtx) +#define USB_XFER_UNLOCK(_x) mtx_unlock((_x)->xroot->xfer_mtx) +#define USB_XFER_LOCK_ASSERT(_x, _t) mtx_assert((_x)->xroot->xfer_mtx, _t) + +/* helper for converting pointers to integers */ +#define USB_P2U(ptr) \ + (((const uint8_t *)(ptr)) - ((const uint8_t *)0)) + +/* helper for computing offsets */ +#define USB_ADD_BYTES(ptr,size) \ + ((void *)(USB_P2U(ptr) + (size))) + +/* debug macro */ +#define USB_ASSERT KASSERT + +/* structure prototypes */ + +struct file; +struct usb_bus; +struct usb_device; +struct usb_device_request; +struct usb_page; +struct usb_page_cache; +struct usb_xfer; +struct usb_xfer_root; + +/* typedefs */ + +/* structures */ + +/* + * The following structure defines a set of internal USB transfer + * flags. + */ +struct usb_xfer_flags_int { + + enum usb_hc_mode usb_mode; /* shadow copy of "udev->usb_mode" */ + uint16_t control_rem; /* remainder in bytes */ + + uint8_t open:1; /* set if USB pipe has been opened */ + uint8_t transferring:1; /* set if an USB transfer is in + * progress */ + uint8_t did_dma_delay:1; /* set if we waited for HW DMA */ + uint8_t did_close:1; /* set if we closed the USB transfer */ + uint8_t draining:1; /* set if we are draining an USB + * transfer */ + uint8_t started:1; /* keeps track of started or stopped */ + uint8_t bandwidth_reclaimed:1; + uint8_t control_xfr:1; /* set if control transfer */ + uint8_t control_hdr:1; /* set if control header should be + * sent */ + uint8_t control_act:1; /* set if control transfer is active */ + uint8_t control_stall:1; /* set if control transfer should be stalled */ + + uint8_t short_frames_ok:1; /* filtered version */ + uint8_t short_xfer_ok:1; /* filtered version */ +#if USB_HAVE_BUSDMA + uint8_t bdma_enable:1; /* filtered version (only set if + * hardware supports DMA) */ + uint8_t bdma_no_post_sync:1; /* set if the USB callback wrapper + * should not do the BUS-DMA post sync + * operation */ + uint8_t bdma_setup:1; /* set if BUS-DMA has been setup */ +#endif + uint8_t isochronous_xfr:1; /* set if isochronous transfer */ + uint8_t curr_dma_set:1; /* used by USB HC/DC driver */ + uint8_t can_cancel_immed:1; /* set if USB transfer can be + * cancelled immediately */ + uint8_t doing_callback:1; /* set if executing the callback */ +}; + +/* + * The following structure defines an USB transfer. + */ +struct usb_xfer { + struct usb_callout timeout_handle; + TAILQ_ENTRY(usb_xfer) wait_entry; /* used at various places */ + + struct usb_page_cache *buf_fixup; /* fixup buffer(s) */ + struct usb_xfer_queue *wait_queue; /* pointer to queue that we + * are waiting on */ + struct usb_page *dma_page_ptr; + struct usb_endpoint *endpoint; /* our USB endpoint */ + struct usb_xfer_root *xroot; /* used by HC driver */ + void *qh_start[2]; /* used by HC driver */ + void *td_start[2]; /* used by HC driver */ + void *td_transfer_first; /* used by HC driver */ + void *td_transfer_last; /* used by HC driver */ + void *td_transfer_cache; /* used by HC driver */ + void *priv_sc; /* device driver data pointer 1 */ + void *priv_fifo; /* device driver data pointer 2 */ + void *local_buffer; + usb_frlength_t *frlengths; + struct usb_page_cache *frbuffers; + usb_callback_t *callback; + + usb_frlength_t max_hc_frame_size; + usb_frlength_t max_data_length; + usb_frlength_t sumlen; /* sum of all lengths in bytes */ + usb_frlength_t actlen; /* actual length in bytes */ + usb_timeout_t timeout; /* milliseconds */ + + usb_frcount_t max_frame_count; /* initial value of "nframes" after + * setup */ + usb_frcount_t nframes; /* number of USB frames to transfer */ + usb_frcount_t aframes; /* actual number of USB frames + * transferred */ + + uint16_t max_packet_size; + uint16_t max_frame_size; + uint16_t qh_pos; + uint16_t isoc_time_complete; /* in ms */ + usb_timeout_t interval; /* milliseconds */ + + uint8_t address; /* physical USB address */ + uint8_t endpointno; /* physical USB endpoint */ + uint8_t max_packet_count; + uint8_t usb_state; + uint8_t fps_shift; /* down shift of FPS, 0..3 */ + + usb_error_t error; + + struct usb_xfer_flags flags; + struct usb_xfer_flags_int flags_int; +}; + +/* external variables */ + +extern struct mtx usb_ref_lock; + +/* typedefs */ + +typedef struct malloc_type *usb_malloc_type; + +/* prototypes */ + +#endif /* _USB_CORE_HH_ */ diff --git a/freebsd/sys/dev/usb/usb_debug.c b/freebsd/sys/dev/usb/usb_debug.c new file mode 100644 index 00000000..907d31e1 --- /dev/null +++ b/freebsd/sys/dev/usb/usb_debug.c @@ -0,0 +1,179 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/* $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. + */ + +#include <freebsd/sys/stdint.h> +#include <freebsd/sys/stddef.h> +#include <freebsd/sys/param.h> +#include <freebsd/sys/queue.h> +#include <freebsd/sys/types.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/bus.h> +#include <freebsd/sys/linker_set.h> +#include <freebsd/sys/module.h> +#include <freebsd/sys/lock.h> +#include <freebsd/sys/mutex.h> +#include <freebsd/sys/condvar.h> +#include <freebsd/sys/sysctl.h> +#include <freebsd/sys/sx.h> +#include <freebsd/sys/unistd.h> +#include <freebsd/sys/callout.h> +#include <freebsd/sys/malloc.h> +#include <freebsd/sys/priv.h> + +#include <freebsd/dev/usb/usb.h> +#include <freebsd/dev/usb/usbdi.h> + +#include <freebsd/dev/usb/usb_core.h> +#include <freebsd/dev/usb/usb_debug.h> +#include <freebsd/dev/usb/usb_process.h> +#include <freebsd/dev/usb/usb_device.h> +#include <freebsd/dev/usb/usb_busdma.h> +#include <freebsd/dev/usb/usb_transfer.h> + +#include <freebsd/ddb/ddb.h> +#include <freebsd/ddb/db_sym.h> + +/* + * Define this unconditionally in case a kernel module is loaded that + * has been compiled with debugging options. + */ +int usb_debug = 0; + +SYSCTL_NODE(_hw, OID_AUTO, usb, CTLFLAG_RW, 0, "USB debugging"); +SYSCTL_INT(_hw_usb, OID_AUTO, debug, CTLFLAG_RW, + &usb_debug, 0, "Debug level"); + +TUNABLE_INT("hw.usb.debug", &usb_debug); + +/*------------------------------------------------------------------------* + * usb_dump_iface + * + * This function dumps information about an USB interface. + *------------------------------------------------------------------------*/ +void +usb_dump_iface(struct usb_interface *iface) +{ + printf("usb_dump_iface: iface=%p\n", iface); + if (iface == NULL) { + return; + } + printf(" iface=%p idesc=%p altindex=%d\n", + iface, iface->idesc, iface->alt_index); +} + +/*------------------------------------------------------------------------* + * usb_dump_device + * + * This function dumps information about an USB device. + *------------------------------------------------------------------------*/ +void +usb_dump_device(struct usb_device *udev) +{ + printf("usb_dump_device: dev=%p\n", udev); + if (udev == NULL) { + return; + } + printf(" bus=%p \n" + " address=%d config=%d depth=%d speed=%d self_powered=%d\n" + " power=%d langid=%d\n", + udev->bus, + udev->address, udev->curr_config_no, udev->depth, udev->speed, + udev->flags.self_powered, udev->power, udev->langid); +} + +/*------------------------------------------------------------------------* + * usb_dump_queue + * + * This function dumps the USB transfer that are queued up on an USB endpoint. + *------------------------------------------------------------------------*/ +void +usb_dump_queue(struct usb_endpoint *ep) +{ + struct usb_xfer *xfer; + + printf("usb_dump_queue: endpoint=%p xfer: ", ep); + TAILQ_FOREACH(xfer, &ep->endpoint_q.head, wait_entry) { + printf(" %p", xfer); + } + printf("\n"); +} + +/*------------------------------------------------------------------------* + * usb_dump_endpoint + * + * This function dumps information about an USB endpoint. + *------------------------------------------------------------------------*/ +void +usb_dump_endpoint(struct usb_endpoint *ep) +{ + if (ep) { + printf("usb_dump_endpoint: endpoint=%p", ep); + + printf(" edesc=%p isoc_next=%d toggle_next=%d", + ep->edesc, ep->isoc_next, ep->toggle_next); + + if (ep->edesc) { + printf(" bEndpointAddress=0x%02x", + ep->edesc->bEndpointAddress); + } + printf("\n"); + usb_dump_queue(ep); + } else { + printf("usb_dump_endpoint: endpoint=NULL\n"); + } +} + +/*------------------------------------------------------------------------* + * usb_dump_xfer + * + * This function dumps information about an USB transfer. + *------------------------------------------------------------------------*/ +void +usb_dump_xfer(struct usb_xfer *xfer) +{ + struct usb_device *udev; + printf("usb_dump_xfer: xfer=%p\n", xfer); + if (xfer == NULL) { + return; + } + if (xfer->endpoint == NULL) { + printf("xfer %p: endpoint=NULL\n", + xfer); + return; + } + udev = xfer->xroot->udev; + printf("xfer %p: udev=%p vid=0x%04x pid=0x%04x addr=%d " + "endpoint=%p ep=0x%02x attr=0x%02x\n", + xfer, udev, + UGETW(udev->ddesc.idVendor), + UGETW(udev->ddesc.idProduct), + udev->address, xfer->endpoint, + xfer->endpoint->edesc->bEndpointAddress, + xfer->endpoint->edesc->bmAttributes); +} diff --git a/freebsd/sys/dev/usb/usb_debug.h b/freebsd/sys/dev/usb/usb_debug.h new file mode 100644 index 00000000..aa3a5a49 --- /dev/null +++ b/freebsd/sys/dev/usb/usb_debug.h @@ -0,0 +1,62 @@ +/* $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_HH_ +#define _USB_DEBUG_HH_ + +/* 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__) +#else +#define DPRINTF(...) do { } while (0) +#define DPRINTFN(...) do { } while (0) +#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); + +#endif /* _USB_DEBUG_HH_ */ diff --git a/freebsd/sys/dev/usb/usb_dev.c b/freebsd/sys/dev/usb/usb_dev.c new file mode 100644 index 00000000..3517f7cc --- /dev/null +++ b/freebsd/sys/dev/usb/usb_dev.c @@ -0,0 +1,2309 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/* $FreeBSD$ */ +/*- + * Copyright (c) 2006-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. + * + * + * usb_dev.c - An abstraction layer for creating devices under /dev/... + */ + +#include <freebsd/sys/stdint.h> +#include <freebsd/sys/stddef.h> +#include <freebsd/sys/param.h> +#include <freebsd/sys/queue.h> +#include <freebsd/sys/types.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/bus.h> +#include <freebsd/sys/linker_set.h> +#include <freebsd/sys/module.h> +#include <freebsd/sys/lock.h> +#include <freebsd/sys/mutex.h> +#include <freebsd/sys/condvar.h> +#include <freebsd/sys/sysctl.h> +#include <freebsd/sys/sx.h> +#include <freebsd/sys/unistd.h> +#include <freebsd/sys/callout.h> +#include <freebsd/sys/malloc.h> +#include <freebsd/sys/priv.h> +#ifndef __rtems__ +#include <freebsd/sys/vnode.h> +#endif +#include <freebsd/sys/conf.h> +#include <freebsd/sys/fcntl.h> + +#include <freebsd/dev/usb/usb.h> +#include <freebsd/dev/usb/usb_ioctl.h> +#include <freebsd/dev/usb/usbdi.h> +#include <freebsd/dev/usb/usbdi_util.h> + +#define USB_DEBUG_VAR usb_fifo_debug + +#include <freebsd/dev/usb/usb_core.h> +#include <freebsd/dev/usb/usb_dev.h> +#include <freebsd/dev/usb/usb_mbuf.h> +#include <freebsd/dev/usb/usb_process.h> +#include <freebsd/dev/usb/usb_device.h> +#include <freebsd/dev/usb/usb_debug.h> +#include <freebsd/dev/usb/usb_busdma.h> +#include <freebsd/dev/usb/usb_generic.h> +#include <freebsd/dev/usb/usb_dynamic.h> +#include <freebsd/dev/usb/usb_util.h> + +#include <freebsd/dev/usb/usb_controller.h> +#include <freebsd/dev/usb/usb_bus.h> + +#include <freebsd/sys/filio.h> +#include <freebsd/sys/ttycom.h> +#include <freebsd/sys/syscallsubr.h> + +#include <freebsd/machine/stdarg.h> + +#if USB_HAVE_UGEN + +#ifdef USB_DEBUG +static int usb_fifo_debug = 0; + +SYSCTL_NODE(_hw_usb, OID_AUTO, dev, CTLFLAG_RW, 0, "USB device"); +SYSCTL_INT(_hw_usb_dev, OID_AUTO, debug, CTLFLAG_RW, + &usb_fifo_debug, 0, "Debug Level"); + +TUNABLE_INT("hw.usb.dev.debug", &usb_fifo_debug); +#endif + +#if ((__FreeBSD_version >= 700001) || (__FreeBSD_version == 0) || \ + ((__FreeBSD_version >= 600034) && (__FreeBSD_version < 700000))) +#define USB_UCRED struct ucred *ucred, +#else +#define USB_UCRED +#endif + +/* prototypes */ + +static int usb_fifo_open(struct usb_cdev_privdata *, + struct usb_fifo *, int); +static void usb_fifo_close(struct usb_fifo *, int); +static void usb_dev_init(void *); +static void usb_dev_init_post(void *); +static void usb_dev_uninit(void *); +static int usb_fifo_uiomove(struct usb_fifo *, void *, int, + struct uio *); +static void usb_fifo_check_methods(struct usb_fifo_methods *); +static struct usb_fifo *usb_fifo_alloc(void); +static struct usb_endpoint *usb_dev_get_ep(struct usb_device *, uint8_t, + uint8_t); +static void usb_loc_fill(struct usb_fs_privdata *, + struct usb_cdev_privdata *); +static void usb_close(void *); +static usb_error_t usb_ref_device(struct usb_cdev_privdata *, struct usb_cdev_refdata *, int); +static usb_error_t usb_usb_ref_device(struct usb_cdev_privdata *, struct usb_cdev_refdata *); +static void usb_unref_device(struct usb_cdev_privdata *, struct usb_cdev_refdata *); + +static d_open_t usb_open; +static d_ioctl_t usb_ioctl; +static d_read_t usb_read; +static d_write_t usb_write; +static d_poll_t usb_poll; + +static d_ioctl_t usb_static_ioctl; + +static usb_fifo_open_t usb_fifo_dummy_open; +static usb_fifo_close_t usb_fifo_dummy_close; +static usb_fifo_ioctl_t usb_fifo_dummy_ioctl; +static usb_fifo_cmd_t usb_fifo_dummy_cmd; + +/* character device structure used for devices (/dev/ugenX.Y and /dev/uXXX) */ +struct cdevsw usb_devsw = { + .d_version = D_VERSION, + .d_open = usb_open, + .d_ioctl = usb_ioctl, + .d_name = "usbdev", + .d_flags = D_TRACKCLOSE, + .d_read = usb_read, + .d_write = usb_write, + .d_poll = usb_poll +}; + +static struct cdev* usb_dev = NULL; + +/* character device structure used for /dev/usb */ +static struct cdevsw usb_static_devsw = { + .d_version = D_VERSION, + .d_ioctl = usb_static_ioctl, + .d_name = "usb" +}; + +static TAILQ_HEAD(, usb_symlink) usb_sym_head; +static struct sx usb_sym_lock; + +struct mtx usb_ref_lock; + +/*------------------------------------------------------------------------* + * usb_loc_fill + * + * This is used to fill out a usb_cdev_privdata structure based on the + * device's address as contained in usb_fs_privdata. + *------------------------------------------------------------------------*/ +static void +usb_loc_fill(struct usb_fs_privdata* pd, struct usb_cdev_privdata *cpd) +{ + cpd->bus_index = pd->bus_index; + cpd->dev_index = pd->dev_index; + cpd->ep_addr = pd->ep_addr; + cpd->fifo_index = pd->fifo_index; +} + +/*------------------------------------------------------------------------* + * usb_ref_device + * + * This function is used to atomically refer an USB device by its + * device location. If this function returns success the USB device + * will not dissappear until the USB device is unreferenced. + * + * Return values: + * 0: Success, refcount incremented on the given USB device. + * Else: Failure. + *------------------------------------------------------------------------*/ +static usb_error_t +usb_ref_device(struct usb_cdev_privdata *cpd, + struct usb_cdev_refdata *crd, int need_uref) +{ + struct usb_fifo **ppf; + struct usb_fifo *f; + + DPRINTFN(2, "cpd=%p need uref=%d\n", cpd, need_uref); + + /* clear all refs */ + memset(crd, 0, sizeof(*crd)); + + mtx_lock(&usb_ref_lock); + cpd->bus = devclass_get_softc(usb_devclass_ptr, cpd->bus_index); + if (cpd->bus == NULL) { + DPRINTFN(2, "no bus at %u\n", cpd->bus_index); + goto error; + } + cpd->udev = cpd->bus->devices[cpd->dev_index]; + if (cpd->udev == NULL) { + DPRINTFN(2, "no device at %u\n", cpd->dev_index); + goto error; + } + if (cpd->udev->refcount == USB_DEV_REF_MAX) { + DPRINTFN(2, "no dev ref\n"); + goto error; + } + if (need_uref) { + DPRINTFN(2, "ref udev - needed\n"); + cpd->udev->refcount++; + + mtx_unlock(&usb_ref_lock); + + /* + * We need to grab the sx-lock before grabbing the + * FIFO refs to avoid deadlock at detach! + */ + usbd_enum_lock(cpd->udev); + + mtx_lock(&usb_ref_lock); + + /* + * Set "is_uref" after grabbing the default SX lock + */ + crd->is_uref = 1; + } + + /* check if we are doing an open */ + if (cpd->fflags == 0) { + /* use zero defaults */ + } else { + /* check for write */ + if (cpd->fflags & FWRITE) { + ppf = cpd->udev->fifo; + f = ppf[cpd->fifo_index + USB_FIFO_TX]; + crd->txfifo = f; + crd->is_write = 1; /* ref */ + if (f == NULL || f->refcount == USB_FIFO_REF_MAX) + goto error; + if (f->curr_cpd != cpd) + goto error; + /* check if USB-FS is active */ + if (f->fs_ep_max != 0) { + crd->is_usbfs = 1; + } + } + + /* check for read */ + if (cpd->fflags & FREAD) { + ppf = cpd->udev->fifo; + f = ppf[cpd->fifo_index + USB_FIFO_RX]; + crd->rxfifo = f; + crd->is_read = 1; /* ref */ + if (f == NULL || f->refcount == USB_FIFO_REF_MAX) + goto error; + if (f->curr_cpd != cpd) + goto error; + /* check if USB-FS is active */ + if (f->fs_ep_max != 0) { + crd->is_usbfs = 1; + } + } + } + + /* when everything is OK we increment the refcounts */ + if (crd->is_write) { + DPRINTFN(2, "ref write\n"); + crd->txfifo->refcount++; + } + if (crd->is_read) { + DPRINTFN(2, "ref read\n"); + crd->rxfifo->refcount++; + } + mtx_unlock(&usb_ref_lock); + + return (0); + +error: + if (crd->is_uref) { + usbd_enum_unlock(cpd->udev); + + if (--(cpd->udev->refcount) == 0) { + cv_signal(&cpd->udev->ref_cv); + } + } + mtx_unlock(&usb_ref_lock); + DPRINTFN(2, "fail\n"); + return (USB_ERR_INVAL); +} + +/*------------------------------------------------------------------------* + * usb_usb_ref_device + * + * This function is used to upgrade an USB reference to include the + * USB device reference on a USB location. + * + * Return values: + * 0: Success, refcount incremented on the given USB device. + * Else: Failure. + *------------------------------------------------------------------------*/ +static usb_error_t +usb_usb_ref_device(struct usb_cdev_privdata *cpd, + struct usb_cdev_refdata *crd) +{ + /* + * Check if we already got an USB reference on this location: + */ + if (crd->is_uref) + return (0); /* success */ + + /* + * To avoid deadlock at detach we need to drop the FIFO ref + * and re-acquire a new ref! + */ + usb_unref_device(cpd, crd); + + return (usb_ref_device(cpd, crd, 1 /* need uref */)); +} + +/*------------------------------------------------------------------------* + * usb_unref_device + * + * This function will release the reference count by one unit for the + * given USB device. + *------------------------------------------------------------------------*/ +static void +usb_unref_device(struct usb_cdev_privdata *cpd, + struct usb_cdev_refdata *crd) +{ + + DPRINTFN(2, "cpd=%p is_uref=%d\n", cpd, crd->is_uref); + + if (crd->is_uref) + usbd_enum_unlock(cpd->udev); + + mtx_lock(&usb_ref_lock); + if (crd->is_read) { + if (--(crd->rxfifo->refcount) == 0) { + cv_signal(&crd->rxfifo->cv_drain); + } + crd->is_read = 0; + } + if (crd->is_write) { + if (--(crd->txfifo->refcount) == 0) { + cv_signal(&crd->txfifo->cv_drain); + } + crd->is_write = 0; + } + if (crd->is_uref) { + if (--(cpd->udev->refcount) == 0) { + cv_signal(&cpd->udev->ref_cv); + } + crd->is_uref = 0; + } + mtx_unlock(&usb_ref_lock); +} + +static struct usb_fifo * +usb_fifo_alloc(void) +{ + struct usb_fifo *f; + + f = malloc(sizeof(*f), M_USBDEV, M_WAITOK | M_ZERO); + if (f) { + cv_init(&f->cv_io, "FIFO-IO"); + cv_init(&f->cv_drain, "FIFO-DRAIN"); + f->refcount = 1; + } + return (f); +} + +/*------------------------------------------------------------------------* + * usb_fifo_create + *------------------------------------------------------------------------*/ +static int +usb_fifo_create(struct usb_cdev_privdata *cpd, + struct usb_cdev_refdata *crd) +{ + struct usb_device *udev = cpd->udev; + struct usb_fifo *f; + struct usb_endpoint *ep; + uint8_t n; + uint8_t is_tx; + uint8_t is_rx; + uint8_t no_null; + uint8_t is_busy; + int e = cpd->ep_addr; + + is_tx = (cpd->fflags & FWRITE) ? 1 : 0; + is_rx = (cpd->fflags & FREAD) ? 1 : 0; + no_null = 1; + is_busy = 0; + + /* Preallocated FIFO */ + if (e < 0) { + DPRINTFN(5, "Preallocated FIFO\n"); + if (is_tx) { + f = udev->fifo[cpd->fifo_index + USB_FIFO_TX]; + if (f == NULL) + return (EINVAL); + crd->txfifo = f; + } + if (is_rx) { + f = udev->fifo[cpd->fifo_index + USB_FIFO_RX]; + if (f == NULL) + return (EINVAL); + crd->rxfifo = f; + } + return (0); + } + + KASSERT(e >= 0 && e <= 15, ("endpoint %d out of range", e)); + + /* search for a free FIFO slot */ + DPRINTFN(5, "Endpoint device, searching for 0x%02x\n", e); + for (n = 0;; n += 2) { + + if (n == USB_FIFO_MAX) { + if (no_null) { + no_null = 0; + n = 0; + } else { + /* end of FIFOs reached */ + DPRINTFN(5, "out of FIFOs\n"); + return (ENOMEM); + } + } + /* Check for TX FIFO */ + if (is_tx) { + f = udev->fifo[n + USB_FIFO_TX]; + if (f != NULL) { + if (f->dev_ep_index != e) { + /* wrong endpoint index */ + continue; + } + if (f->curr_cpd != NULL) { + /* FIFO is opened */ + is_busy = 1; + continue; + } + } else if (no_null) { + continue; + } + } + /* Check for RX FIFO */ + if (is_rx) { + f = udev->fifo[n + USB_FIFO_RX]; + if (f != NULL) { + if (f->dev_ep_index != e) { + /* wrong endpoint index */ + continue; + } + if (f->curr_cpd != NULL) { + /* FIFO is opened */ + is_busy = 1; + continue; + } + } else if (no_null) { + continue; + } + } + break; + } + + if (no_null == 0) { + if (e >= (USB_EP_MAX / 2)) { + /* we don't create any endpoints in this range */ + DPRINTFN(5, "ep out of range\n"); + return (is_busy ? EBUSY : EINVAL); + } + } + + if ((e != 0) && is_busy) { + /* + * Only the default control endpoint is allowed to be + * opened multiple times! + */ + DPRINTFN(5, "busy\n"); + return (EBUSY); + } + + /* Check TX FIFO */ + if (is_tx && + (udev->fifo[n + USB_FIFO_TX] == NULL)) { + ep = usb_dev_get_ep(udev, e, USB_FIFO_TX); + DPRINTFN(5, "dev_get_endpoint(%d, 0x%x)\n", e, USB_FIFO_TX); + if (ep == NULL) { + DPRINTFN(5, "dev_get_endpoint returned NULL\n"); + return (EINVAL); + } + f = usb_fifo_alloc(); + if (f == NULL) { + DPRINTFN(5, "could not alloc tx fifo\n"); + return (ENOMEM); + } + /* update some fields */ + f->fifo_index = n + USB_FIFO_TX; + f->dev_ep_index = e; + f->priv_mtx = &udev->device_mtx; + f->priv_sc0 = ep; + f->methods = &usb_ugen_methods; + f->iface_index = ep->iface_index; + f->udev = udev; + mtx_lock(&usb_ref_lock); + udev->fifo[n + USB_FIFO_TX] = f; + mtx_unlock(&usb_ref_lock); + } + /* Check RX FIFO */ + if (is_rx && + (udev->fifo[n + USB_FIFO_RX] == NULL)) { + + ep = usb_dev_get_ep(udev, e, USB_FIFO_RX); + DPRINTFN(5, "dev_get_endpoint(%d, 0x%x)\n", e, USB_FIFO_RX); + if (ep == NULL) { + DPRINTFN(5, "dev_get_endpoint returned NULL\n"); + return (EINVAL); + } + f = usb_fifo_alloc(); + if (f == NULL) { + DPRINTFN(5, "could not alloc rx fifo\n"); + return (ENOMEM); + } + /* update some fields */ + f->fifo_index = n + USB_FIFO_RX; + f->dev_ep_index = e; + f->priv_mtx = &udev->device_mtx; + f->priv_sc0 = ep; + f->methods = &usb_ugen_methods; + f->iface_index = ep->iface_index; + f->udev = udev; + mtx_lock(&usb_ref_lock); + udev->fifo[n + USB_FIFO_RX] = f; + mtx_unlock(&usb_ref_lock); + } + if (is_tx) { + crd->txfifo = udev->fifo[n + USB_FIFO_TX]; + } + if (is_rx) { + crd->rxfifo = udev->fifo[n + USB_FIFO_RX]; + } + /* fill out fifo index */ + DPRINTFN(5, "fifo index = %d\n", n); + cpd->fifo_index = n; + + /* complete */ + + return (0); +} + +void +usb_fifo_free(struct usb_fifo *f) +{ + uint8_t n; + + if (f == NULL) { + /* be NULL safe */ + return; + } + /* destroy symlink devices, if any */ + for (n = 0; n != 2; n++) { + if (f->symlink[n]) { + usb_free_symlink(f->symlink[n]); + f->symlink[n] = NULL; + } + } + mtx_lock(&usb_ref_lock); + + /* delink ourselves to stop calls from userland */ + if ((f->fifo_index < USB_FIFO_MAX) && + (f->udev != NULL) && + (f->udev->fifo[f->fifo_index] == f)) { + f->udev->fifo[f->fifo_index] = NULL; + } else { + DPRINTFN(0, "USB FIFO %p has not been linked\n", f); + } + + /* decrease refcount */ + f->refcount--; + /* prevent any write flush */ + f->flag_iserror = 1; + /* need to wait until all callers have exited */ + while (f->refcount != 0) { + mtx_unlock(&usb_ref_lock); /* avoid LOR */ + mtx_lock(f->priv_mtx); + /* get I/O thread out of any sleep state */ + if (f->flag_sleeping) { + f->flag_sleeping = 0; + cv_broadcast(&f->cv_io); + } + mtx_unlock(f->priv_mtx); + mtx_lock(&usb_ref_lock); + + /* wait for sync */ + cv_wait(&f->cv_drain, &usb_ref_lock); + } + mtx_unlock(&usb_ref_lock); + + /* take care of closing the device here, if any */ + usb_fifo_close(f, 0); + + cv_destroy(&f->cv_io); + cv_destroy(&f->cv_drain); + + free(f, M_USBDEV); +} + +static struct usb_endpoint * +usb_dev_get_ep(struct usb_device *udev, uint8_t ep_index, uint8_t dir) +{ + struct usb_endpoint *ep; + uint8_t ep_dir; + + if (ep_index == 0) { + ep = &udev->ctrl_ep; + } else { + if (dir == USB_FIFO_RX) { + if (udev->flags.usb_mode == USB_MODE_HOST) { + ep_dir = UE_DIR_IN; + } else { + ep_dir = UE_DIR_OUT; + } + } else { + if (udev->flags.usb_mode == USB_MODE_HOST) { + ep_dir = UE_DIR_OUT; + } else { + ep_dir = UE_DIR_IN; + } + } + ep = usbd_get_ep_by_addr(udev, ep_index | ep_dir); + } + + if (ep == NULL) { + /* if the endpoint does not exist then return */ + return (NULL); + } + if (ep->edesc == NULL) { + /* invalid endpoint */ + return (NULL); + } + return (ep); /* success */ +} + +/*------------------------------------------------------------------------* + * usb_fifo_open + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static int +usb_fifo_open(struct usb_cdev_privdata *cpd, + struct usb_fifo *f, int fflags) +{ + int err; + + if (f == NULL) { + /* no FIFO there */ + DPRINTFN(2, "no FIFO\n"); + return (ENXIO); + } + /* remove FWRITE and FREAD flags */ + fflags &= ~(FWRITE | FREAD); + + /* set correct file flags */ + if ((f->fifo_index & 1) == USB_FIFO_TX) { + fflags |= FWRITE; + } else { + fflags |= FREAD; + } + + /* check if we are already opened */ + /* we don't need any locks when checking this variable */ + if (f->curr_cpd != NULL) { + err = EBUSY; + goto done; + } + + /* reset short flag before open */ + f->flag_short = 0; + + /* call open method */ + err = (f->methods->f_open) (f, fflags); + if (err) { + goto done; + } + mtx_lock(f->priv_mtx); + + /* reset sleep flag */ + f->flag_sleeping = 0; + + /* reset error flag */ + f->flag_iserror = 0; + + /* reset complete flag */ + f->flag_iscomplete = 0; + + /* reset select flag */ + f->flag_isselect = 0; + + /* reset flushing flag */ + f->flag_flushing = 0; + + /* reset ASYNC proc flag */ + f->async_p = NULL; + + mtx_lock(&usb_ref_lock); + /* flag the fifo as opened to prevent others */ + f->curr_cpd = cpd; + mtx_unlock(&usb_ref_lock); + + /* reset queue */ + usb_fifo_reset(f); + + mtx_unlock(f->priv_mtx); +done: + return (err); +} + +/*------------------------------------------------------------------------* + * usb_fifo_reset + *------------------------------------------------------------------------*/ +void +usb_fifo_reset(struct usb_fifo *f) +{ + struct usb_mbuf *m; + + if (f == NULL) { + return; + } + while (1) { + USB_IF_DEQUEUE(&f->used_q, m); + if (m) { + USB_IF_ENQUEUE(&f->free_q, m); + } else { + break; + } + } + /* reset have fragment flag */ + f->flag_have_fragment = 0; +} + +/*------------------------------------------------------------------------* + * usb_fifo_close + *------------------------------------------------------------------------*/ +static void +usb_fifo_close(struct usb_fifo *f, int fflags) +{ + int err; + + /* check if we are not opened */ + if (f->curr_cpd == NULL) { + /* nothing to do - already closed */ + return; + } + mtx_lock(f->priv_mtx); + + /* clear current cdev private data pointer */ + f->curr_cpd = NULL; + + /* check if we are selected */ + if (f->flag_isselect) { + selwakeup(&f->selinfo); + f->flag_isselect = 0; + } + /* check if a thread wants SIGIO */ + if (f->async_p != NULL) { + PROC_LOCK(f->async_p); + psignal(f->async_p, SIGIO); + PROC_UNLOCK(f->async_p); + f->async_p = NULL; + } + /* remove FWRITE and FREAD flags */ + fflags &= ~(FWRITE | FREAD); + + /* flush written data, if any */ + if ((f->fifo_index & 1) == USB_FIFO_TX) { + + if (!f->flag_iserror) { + + /* set flushing flag */ + f->flag_flushing = 1; + + /* get the last packet in */ + if (f->flag_have_fragment) { + struct usb_mbuf *m; + f->flag_have_fragment = 0; + USB_IF_DEQUEUE(&f->free_q, m); + if (m) { + USB_IF_ENQUEUE(&f->used_q, m); + } + } + + /* start write transfer, if not already started */ + (f->methods->f_start_write) (f); + + /* check if flushed already */ + while (f->flag_flushing && + (!f->flag_iserror)) { + /* wait until all data has been written */ + f->flag_sleeping = 1; + err = cv_wait_sig(&f->cv_io, f->priv_mtx); + if (err) { + DPRINTF("signal received\n"); + break; + } + } + } + fflags |= FWRITE; + + /* stop write transfer, if not already stopped */ + (f->methods->f_stop_write) (f); + } else { + fflags |= FREAD; + + /* stop write transfer, if not already stopped */ + (f->methods->f_stop_read) (f); + } + + /* check if we are sleeping */ + if (f->flag_sleeping) { + DPRINTFN(2, "Sleeping at close!\n"); + } + mtx_unlock(f->priv_mtx); + + /* call close method */ + (f->methods->f_close) (f, fflags); + + DPRINTF("closed\n"); +} + +/*------------------------------------------------------------------------* + * usb_open - cdev callback + *------------------------------------------------------------------------*/ +static int +usb_open(struct cdev *dev, int fflags, int devtype, struct thread *td) +{ + struct usb_fs_privdata* pd = (struct usb_fs_privdata*)dev->si_drv1; + struct usb_cdev_refdata refs; + struct usb_cdev_privdata *cpd; + int err, ep; + + DPRINTFN(2, "%s fflags=0x%08x\n", dev->si_name, fflags); + + KASSERT(fflags & (FREAD|FWRITE), ("invalid open flags")); + if (((fflags & FREAD) && !(pd->mode & FREAD)) || + ((fflags & FWRITE) && !(pd->mode & FWRITE))) { + DPRINTFN(2, "access mode not supported\n"); + return (EPERM); + } + + cpd = malloc(sizeof(*cpd), M_USBDEV, M_WAITOK | M_ZERO); + ep = cpd->ep_addr = pd->ep_addr; + + usb_loc_fill(pd, cpd); + err = usb_ref_device(cpd, &refs, 1); + if (err) { + DPRINTFN(2, "cannot ref device\n"); + free(cpd, M_USBDEV); + return (ENXIO); + } + cpd->fflags = fflags; /* access mode for open lifetime */ + + /* create FIFOs, if any */ + err = usb_fifo_create(cpd, &refs); + /* check for error */ + if (err) { + DPRINTFN(2, "cannot create fifo\n"); + usb_unref_device(cpd, &refs); + free(cpd, M_USBDEV); + return (err); + } + if (fflags & FREAD) { + err = usb_fifo_open(cpd, refs.rxfifo, fflags); + if (err) { + DPRINTFN(2, "read open failed\n"); + usb_unref_device(cpd, &refs); + free(cpd, M_USBDEV); + return (err); + } + } + if (fflags & FWRITE) { + err = usb_fifo_open(cpd, refs.txfifo, fflags); + if (err) { + DPRINTFN(2, "write open failed\n"); + if (fflags & FREAD) { + usb_fifo_close(refs.rxfifo, fflags); + } + usb_unref_device(cpd, &refs); + free(cpd, M_USBDEV); + return (err); + } + } + usb_unref_device(cpd, &refs); + devfs_set_cdevpriv(cpd, usb_close); + + return (0); +} + +/*------------------------------------------------------------------------* + * usb_close - cdev callback + *------------------------------------------------------------------------*/ +static void +usb_close(void *arg) +{ + struct usb_cdev_refdata refs; + struct usb_cdev_privdata *cpd = arg; + int err; + + DPRINTFN(2, "cpd=%p\n", cpd); + + err = usb_ref_device(cpd, &refs, 1); + if (err) { + free(cpd, M_USBDEV); + return; + } + if (cpd->fflags & FREAD) { + usb_fifo_close(refs.rxfifo, cpd->fflags); + } + if (cpd->fflags & FWRITE) { + usb_fifo_close(refs.txfifo, cpd->fflags); + } + + usb_unref_device(cpd, &refs); + free(cpd, M_USBDEV); + return; +} + +static void +usb_dev_init(void *arg) +{ + mtx_init(&usb_ref_lock, "USB ref mutex", NULL, MTX_DEF); + sx_init(&usb_sym_lock, "USB sym mutex"); + TAILQ_INIT(&usb_sym_head); + + /* check the UGEN methods */ + usb_fifo_check_methods(&usb_ugen_methods); +} + +SYSINIT(usb_dev_init, SI_SUB_KLD, SI_ORDER_FIRST, usb_dev_init, NULL); + +static void +usb_dev_init_post(void *arg) +{ + /* + * Create /dev/usb - this is needed for usbconfig(8), which + * needs a well-known device name to access. + */ + usb_dev = make_dev(&usb_static_devsw, 0, UID_ROOT, GID_OPERATOR, + 0644, USB_DEVICE_NAME); + if (usb_dev == NULL) { + DPRINTFN(0, "Could not create usb bus device\n"); + } +} + +SYSINIT(usb_dev_init_post, SI_SUB_KICK_SCHEDULER, SI_ORDER_FIRST, usb_dev_init_post, NULL); + +static void +usb_dev_uninit(void *arg) +{ + if (usb_dev != NULL) { + destroy_dev(usb_dev); + usb_dev = NULL; + } + mtx_destroy(&usb_ref_lock); + sx_destroy(&usb_sym_lock); +} + +SYSUNINIT(usb_dev_uninit, SI_SUB_KICK_SCHEDULER, SI_ORDER_ANY, usb_dev_uninit, NULL); + +static int +usb_ioctl_f_sub(struct usb_fifo *f, u_long cmd, void *addr, + struct thread *td) +{ + int error = 0; + + switch (cmd) { + case FIODTYPE: + *(int *)addr = 0; /* character device */ + break; + + case FIONBIO: + /* handled by upper FS layer */ + break; + + case FIOASYNC: + if (*(int *)addr) { + if (f->async_p != NULL) { + error = EBUSY; + break; + } + f->async_p = USB_TD_GET_PROC(td); + } else { + f->async_p = NULL; + } + break; + + /* XXX this is not the most general solution */ + case TIOCSPGRP: + if (f->async_p == NULL) { + error = EINVAL; + break; + } + if (*(int *)addr != USB_PROC_GET_GID(f->async_p)) { + error = EPERM; + break; + } + break; + default: + return (ENOIOCTL); + } + DPRINTFN(3, "cmd 0x%lx = %d\n", cmd, error); + return (error); +} + +/*------------------------------------------------------------------------* + * usb_ioctl - cdev callback + *------------------------------------------------------------------------*/ +static int +usb_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int fflag, struct thread* td) +{ + struct usb_cdev_refdata refs; + struct usb_cdev_privdata* cpd; + struct usb_fifo *f; + int fflags; + int err; + + DPRINTFN(2, "cmd=0x%lx\n", cmd); + + err = devfs_get_cdevpriv((void **)&cpd); + if (err != 0) + return (err); + + /* + * Performance optimisation: We try to check for IOCTL's that + * don't need the USB reference first. Then we grab the USB + * reference if we need it! + */ + err = usb_ref_device(cpd, &refs, 0 /* no uref */ ); + if (err) + return (ENXIO); + + fflags = cpd->fflags; + + f = NULL; /* set default value */ + err = ENOIOCTL; /* set default value */ + + if (fflags & FWRITE) { + f = refs.txfifo; + err = usb_ioctl_f_sub(f, cmd, addr, td); + } + if (fflags & FREAD) { + f = refs.rxfifo; + err = usb_ioctl_f_sub(f, cmd, addr, td); + } + KASSERT(f != NULL, ("fifo not found")); + if (err != ENOIOCTL) + goto done; + + err = (f->methods->f_ioctl) (f, cmd, addr, fflags); + + DPRINTFN(2, "f_ioctl cmd 0x%lx = %d\n", cmd, err); + + if (err != ENOIOCTL) + goto done; + + if (usb_usb_ref_device(cpd, &refs)) { + err = ENXIO; + goto done; + } + + err = (f->methods->f_ioctl_post) (f, cmd, addr, fflags); + + DPRINTFN(2, "f_ioctl_post cmd 0x%lx = %d\n", cmd, err); + + if (err == ENOIOCTL) + err = ENOTTY; + + if (err) + goto done; + + /* Wait for re-enumeration, if any */ + + while (f->udev->re_enumerate_wait != 0) { + + usb_unref_device(cpd, &refs); + + usb_pause_mtx(NULL, hz / 128); + + if (usb_ref_device(cpd, &refs, 1 /* need uref */)) { + err = ENXIO; + goto done; + } + } + +done: + usb_unref_device(cpd, &refs); + return (err); +} + +/* ARGSUSED */ +static int +usb_poll(struct cdev* dev, int events, struct thread* td) +{ + struct usb_cdev_refdata refs; + struct usb_cdev_privdata* cpd; + struct usb_fifo *f; + struct usb_mbuf *m; + int fflags, revents; + + if (devfs_get_cdevpriv((void **)&cpd) != 0 || + usb_ref_device(cpd, &refs, 0) != 0) + return (events & + (POLLHUP|POLLIN|POLLRDNORM|POLLOUT|POLLWRNORM)); + + fflags = cpd->fflags; + + /* Figure out who needs service */ + revents = 0; + if ((events & (POLLOUT | POLLWRNORM)) && + (fflags & FWRITE)) { + + f = refs.txfifo; + + mtx_lock(f->priv_mtx); + + if (!refs.is_usbfs) { + if (f->flag_iserror) { + /* we got an error */ + m = (void *)1; + } else { + if (f->queue_data == NULL) { + /* + * start write transfer, if not + * already started + */ + (f->methods->f_start_write) (f); + } + /* check if any packets are available */ + USB_IF_POLL(&f->free_q, m); + } + } else { + if (f->flag_iscomplete) { + m = (void *)1; + } else { + m = NULL; + } + } + + if (m) { + revents |= events & (POLLOUT | POLLWRNORM); + } else { + f->flag_isselect = 1; + selrecord(td, &f->selinfo); + } + + mtx_unlock(f->priv_mtx); + } + if ((events & (POLLIN | POLLRDNORM)) && + (fflags & FREAD)) { + + f = refs.rxfifo; + + mtx_lock(f->priv_mtx); + + if (!refs.is_usbfs) { + if (f->flag_iserror) { + /* we have and error */ + m = (void *)1; + } else { + if (f->queue_data == NULL) { + /* + * start read transfer, if not + * already started + */ + (f->methods->f_start_read) (f); + } + /* check if any packets are available */ + USB_IF_POLL(&f->used_q, m); + } + } else { + if (f->flag_iscomplete) { + m = (void *)1; + } else { + m = NULL; + } + } + + if (m) { + revents |= events & (POLLIN | POLLRDNORM); + } else { + f->flag_isselect = 1; + selrecord(td, &f->selinfo); + + if (!refs.is_usbfs) { + /* start reading data */ + (f->methods->f_start_read) (f); + } + } + + mtx_unlock(f->priv_mtx); + } + usb_unref_device(cpd, &refs); + return (revents); +} + +static int +usb_read(struct cdev *dev, struct uio *uio, int ioflag) +{ + struct usb_cdev_refdata refs; + struct usb_cdev_privdata* cpd; + struct usb_fifo *f; + struct usb_mbuf *m; + int fflags; + int resid; + int io_len; + int err; + uint8_t tr_data = 0; + + err = devfs_get_cdevpriv((void **)&cpd); + if (err != 0) + return (err); + + err = usb_ref_device(cpd, &refs, 0 /* no uref */ ); + if (err) { + return (ENXIO); + } + fflags = cpd->fflags; + + f = refs.rxfifo; + if (f == NULL) { + /* should not happen */ + usb_unref_device(cpd, &refs); + return (EPERM); + } + + resid = uio->uio_resid; + + mtx_lock(f->priv_mtx); + + /* check for permanent read error */ + if (f->flag_iserror) { + err = EIO; + goto done; + } + /* check if USB-FS interface is active */ + if (refs.is_usbfs) { + /* + * The queue is used for events that should be + * retrieved using the "USB_FS_COMPLETE" ioctl. + */ + err = EINVAL; + goto done; + } + while (uio->uio_resid > 0) { + + USB_IF_DEQUEUE(&f->used_q, m); + + if (m == NULL) { + + /* start read transfer, if not already started */ + + (f->methods->f_start_read) (f); + + if (ioflag & IO_NDELAY) { + if (tr_data) { + /* return length before error */ + break; + } + err = EWOULDBLOCK; + break; + } + DPRINTF("sleeping\n"); + + err = usb_fifo_wait(f); + if (err) { + break; + } + continue; + } + if (f->methods->f_filter_read) { + /* + * Sometimes it is convenient to process data at the + * expense of a userland process instead of a kernel + * process. + */ + (f->methods->f_filter_read) (f, m); + } + tr_data = 1; + + io_len = MIN(m->cur_data_len, uio->uio_resid); + + DPRINTFN(2, "transfer %d bytes from %p\n", + io_len, m->cur_data_ptr); + + err = usb_fifo_uiomove(f, + m->cur_data_ptr, io_len, uio); + + m->cur_data_len -= io_len; + m->cur_data_ptr += io_len; + + if (m->cur_data_len == 0) { + + uint8_t last_packet; + + last_packet = m->last_packet; + + USB_IF_ENQUEUE(&f->free_q, m); + + if (last_packet) { + /* keep framing */ + break; + } + } else { + USB_IF_PREPEND(&f->used_q, m); + } + + if (err) { + break; + } + } +done: + mtx_unlock(f->priv_mtx); + + usb_unref_device(cpd, &refs); + + return (err); +} + +static int +usb_write(struct cdev *dev, struct uio *uio, int ioflag) +{ + struct usb_cdev_refdata refs; + struct usb_cdev_privdata* cpd; + struct usb_fifo *f; + struct usb_mbuf *m; + uint8_t *pdata; + int fflags; + int resid; + int io_len; + int err; + uint8_t tr_data = 0; + + DPRINTFN(2, "\n"); + + err = devfs_get_cdevpriv((void **)&cpd); + if (err != 0) + return (err); + + err = usb_ref_device(cpd, &refs, 0 /* no uref */ ); + if (err) { + return (ENXIO); + } + fflags = cpd->fflags; + + f = refs.txfifo; + if (f == NULL) { + /* should not happen */ + usb_unref_device(cpd, &refs); + return (EPERM); + } + resid = uio->uio_resid; + + mtx_lock(f->priv_mtx); + + /* check for permanent write error */ + if (f->flag_iserror) { + err = EIO; + goto done; + } + /* check if USB-FS interface is active */ + if (refs.is_usbfs) { + /* + * The queue is used for events that should be + * retrieved using the "USB_FS_COMPLETE" ioctl. + */ + err = EINVAL; + goto done; + } + if (f->queue_data == NULL) { + /* start write transfer, if not already started */ + (f->methods->f_start_write) (f); + } + /* we allow writing zero length data */ + do { + USB_IF_DEQUEUE(&f->free_q, m); + + if (m == NULL) { + + if (ioflag & IO_NDELAY) { + if (tr_data) { + /* return length before error */ + break; + } + err = EWOULDBLOCK; + break; + } + DPRINTF("sleeping\n"); + + err = usb_fifo_wait(f); + if (err) { + break; + } + continue; + } + tr_data = 1; + + if (f->flag_have_fragment == 0) { + USB_MBUF_RESET(m); + io_len = m->cur_data_len; + pdata = m->cur_data_ptr; + if (io_len > uio->uio_resid) + io_len = uio->uio_resid; + m->cur_data_len = io_len; + } else { + io_len = m->max_data_len - m->cur_data_len; + pdata = m->cur_data_ptr + m->cur_data_len; + if (io_len > uio->uio_resid) + io_len = uio->uio_resid; + m->cur_data_len += io_len; + } + + DPRINTFN(2, "transfer %d bytes to %p\n", + io_len, pdata); + + err = usb_fifo_uiomove(f, pdata, io_len, uio); + + if (err) { + f->flag_have_fragment = 0; + USB_IF_ENQUEUE(&f->free_q, m); + break; + } + + /* check if the buffer is ready to be transmitted */ + + if ((f->flag_write_defrag == 0) || + (m->cur_data_len == m->max_data_len)) { + f->flag_have_fragment = 0; + + /* + * Check for write filter: + * + * Sometimes it is convenient to process data + * at the expense of a userland process + * instead of a kernel process. + */ + if (f->methods->f_filter_write) { + (f->methods->f_filter_write) (f, m); + } + + /* Put USB mbuf in the used queue */ + USB_IF_ENQUEUE(&f->used_q, m); + + /* Start writing data, if not already started */ + (f->methods->f_start_write) (f); + } else { + /* Wait for more data or close */ + f->flag_have_fragment = 1; + USB_IF_PREPEND(&f->free_q, m); + } + + } while (uio->uio_resid > 0); +done: + mtx_unlock(f->priv_mtx); + + usb_unref_device(cpd, &refs); + + return (err); +} + +int +usb_static_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, + struct thread *td) +{ + union { + struct usb_read_dir *urd; + void* data; + } u; + int err; + + u.data = data; + switch (cmd) { + case USB_READ_DIR: + err = usb_read_symlink(u.urd->urd_data, + u.urd->urd_startentry, u.urd->urd_maxlen); + break; + case USB_DEV_QUIRK_GET: + case USB_QUIRK_NAME_GET: + case USB_DEV_QUIRK_ADD: + case USB_DEV_QUIRK_REMOVE: + err = usb_quirk_ioctl_p(cmd, data, fflag, td); + break; + case USB_GET_TEMPLATE: + *(int *)data = usb_template; + err = 0; + break; + case USB_SET_TEMPLATE: + err = priv_check(curthread, PRIV_DRIVER); + if (err) + break; + usb_template = *(int *)data; + break; + default: + err = ENOTTY; + break; + } + return (err); +} + +static int +usb_fifo_uiomove(struct usb_fifo *f, void *cp, + int n, struct uio *uio) +{ + int error; + + mtx_unlock(f->priv_mtx); + + /* + * "uiomove()" can sleep so one needs to make a wrapper, + * exiting the mutex and checking things: + */ + error = uiomove(cp, n, uio); + + mtx_lock(f->priv_mtx); + + return (error); +} + +int +usb_fifo_wait(struct usb_fifo *f) +{ + int err; + + mtx_assert(f->priv_mtx, MA_OWNED); + + if (f->flag_iserror) { + /* we are gone */ + return (EIO); + } + f->flag_sleeping = 1; + + err = cv_wait_sig(&f->cv_io, f->priv_mtx); + + if (f->flag_iserror) { + /* we are gone */ + err = EIO; + } + return (err); +} + +void +usb_fifo_signal(struct usb_fifo *f) +{ + if (f->flag_sleeping) { + f->flag_sleeping = 0; + cv_broadcast(&f->cv_io); + } +} + +void +usb_fifo_wakeup(struct usb_fifo *f) +{ + usb_fifo_signal(f); + + if (f->flag_isselect) { + selwakeup(&f->selinfo); + f->flag_isselect = 0; + } + if (f->async_p != NULL) { + PROC_LOCK(f->async_p); + psignal(f->async_p, SIGIO); + PROC_UNLOCK(f->async_p); + } +} + +static int +usb_fifo_dummy_open(struct usb_fifo *fifo, int fflags) +{ + return (0); +} + +static void +usb_fifo_dummy_close(struct usb_fifo *fifo, int fflags) +{ + return; +} + +static int +usb_fifo_dummy_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr, int fflags) +{ + return (ENOIOCTL); +} + +static void +usb_fifo_dummy_cmd(struct usb_fifo *fifo) +{ + fifo->flag_flushing = 0; /* not flushing */ +} + +static void +usb_fifo_check_methods(struct usb_fifo_methods *pm) +{ + /* check that all callback functions are OK */ + + if (pm->f_open == NULL) + pm->f_open = &usb_fifo_dummy_open; + + if (pm->f_close == NULL) + pm->f_close = &usb_fifo_dummy_close; + + if (pm->f_ioctl == NULL) + pm->f_ioctl = &usb_fifo_dummy_ioctl; + + if (pm->f_ioctl_post == NULL) + pm->f_ioctl_post = &usb_fifo_dummy_ioctl; + + if (pm->f_start_read == NULL) + pm->f_start_read = &usb_fifo_dummy_cmd; + + if (pm->f_stop_read == NULL) + pm->f_stop_read = &usb_fifo_dummy_cmd; + + if (pm->f_start_write == NULL) + pm->f_start_write = &usb_fifo_dummy_cmd; + + if (pm->f_stop_write == NULL) + pm->f_stop_write = &usb_fifo_dummy_cmd; +} + +/*------------------------------------------------------------------------* + * usb_fifo_attach + * + * The following function will create a duplex FIFO. + * + * Return values: + * 0: Success. + * Else: Failure. + *------------------------------------------------------------------------*/ +int +usb_fifo_attach(struct usb_device *udev, void *priv_sc, + struct mtx *priv_mtx, struct usb_fifo_methods *pm, + struct usb_fifo_sc *f_sc, uint16_t unit, uint16_t subunit, + uint8_t iface_index, uid_t uid, gid_t gid, int mode) +{ + struct usb_fifo *f_tx; + struct usb_fifo *f_rx; + char devname[32]; + uint8_t n; + struct usb_fs_privdata* pd; + + f_sc->fp[USB_FIFO_TX] = NULL; + f_sc->fp[USB_FIFO_RX] = NULL; + + if (pm == NULL) + return (EINVAL); + + /* check the methods */ + usb_fifo_check_methods(pm); + + if (priv_mtx == NULL) + priv_mtx = &Giant; + + /* search for a free FIFO slot */ + for (n = 0;; n += 2) { + + if (n == USB_FIFO_MAX) { + /* end of FIFOs reached */ + return (ENOMEM); + } + /* Check for TX FIFO */ + if (udev->fifo[n + USB_FIFO_TX] != NULL) { + continue; + } + /* Check for RX FIFO */ + if (udev->fifo[n + USB_FIFO_RX] != NULL) { + continue; + } + break; + } + + f_tx = usb_fifo_alloc(); + f_rx = usb_fifo_alloc(); + + if ((f_tx == NULL) || (f_rx == NULL)) { + usb_fifo_free(f_tx); + usb_fifo_free(f_rx); + return (ENOMEM); + } + /* initialise FIFO structures */ + + f_tx->fifo_index = n + USB_FIFO_TX; + f_tx->dev_ep_index = -1; + f_tx->priv_mtx = priv_mtx; + f_tx->priv_sc0 = priv_sc; + f_tx->methods = pm; + f_tx->iface_index = iface_index; + f_tx->udev = udev; + + f_rx->fifo_index = n + USB_FIFO_RX; + f_rx->dev_ep_index = -1; + f_rx->priv_mtx = priv_mtx; + f_rx->priv_sc0 = priv_sc; + f_rx->methods = pm; + f_rx->iface_index = iface_index; + f_rx->udev = udev; + + f_sc->fp[USB_FIFO_TX] = f_tx; + f_sc->fp[USB_FIFO_RX] = f_rx; + + mtx_lock(&usb_ref_lock); + udev->fifo[f_tx->fifo_index] = f_tx; + udev->fifo[f_rx->fifo_index] = f_rx; + mtx_unlock(&usb_ref_lock); + + for (n = 0; n != 4; n++) { + + if (pm->basename[n] == NULL) { + continue; + } + if (subunit == 0xFFFF) { + if (snprintf(devname, sizeof(devname), + "%s%u%s", pm->basename[n], + unit, pm->postfix[n] ? + pm->postfix[n] : "")) { + /* ignore */ + } + } else { + if (snprintf(devname, sizeof(devname), + "%s%u.%u%s", pm->basename[n], + unit, subunit, pm->postfix[n] ? + pm->postfix[n] : "")) { + /* ignore */ + } + } + + /* + * Distribute the symbolic links into two FIFO structures: + */ + if (n & 1) { + f_rx->symlink[n / 2] = + usb_alloc_symlink(devname); + } else { + f_tx->symlink[n / 2] = + usb_alloc_symlink(devname); + } + + /* + * Initialize device private data - this is used to find the + * actual USB device itself. + */ + pd = malloc(sizeof(struct usb_fs_privdata), M_USBDEV, M_WAITOK | M_ZERO); + pd->bus_index = device_get_unit(udev->bus->bdev); + pd->dev_index = udev->device_index; + pd->ep_addr = -1; /* not an endpoint */ + pd->fifo_index = f_tx->fifo_index & f_rx->fifo_index; + pd->mode = FREAD|FWRITE; + + /* Now, create the device itself */ + f_sc->dev = make_dev(&usb_devsw, 0, uid, gid, mode, + "%s", devname); + /* XXX setting si_drv1 and creating the device is not atomic! */ + f_sc->dev->si_drv1 = pd; + } + + DPRINTFN(2, "attached %p/%p\n", f_tx, f_rx); + return (0); +} + +/*------------------------------------------------------------------------* + * usb_fifo_alloc_buffer + * + * Return values: + * 0: Success + * Else failure + *------------------------------------------------------------------------*/ +int +usb_fifo_alloc_buffer(struct usb_fifo *f, usb_size_t bufsize, + uint16_t nbuf) +{ + usb_fifo_free_buffer(f); + + /* allocate an endpoint */ + f->free_q.ifq_maxlen = nbuf; + f->used_q.ifq_maxlen = nbuf; + + f->queue_data = usb_alloc_mbufs( + M_USBDEV, &f->free_q, bufsize, nbuf); + + if ((f->queue_data == NULL) && bufsize && nbuf) { + return (ENOMEM); + } + return (0); /* success */ +} + +/*------------------------------------------------------------------------* + * usb_fifo_free_buffer + * + * This function will free the buffers associated with a FIFO. This + * function can be called multiple times in a row. + *------------------------------------------------------------------------*/ +void +usb_fifo_free_buffer(struct usb_fifo *f) +{ + if (f->queue_data) { + /* free old buffer */ + free(f->queue_data, M_USBDEV); + f->queue_data = NULL; + } + /* reset queues */ + + bzero(&f->free_q, sizeof(f->free_q)); + bzero(&f->used_q, sizeof(f->used_q)); +} + +static void +usb_fifo_cleanup(void* ptr) +{ + free(ptr, M_USBDEV); +} + +void +usb_fifo_detach(struct usb_fifo_sc *f_sc) +{ + if (f_sc == NULL) { + return; + } + usb_fifo_free(f_sc->fp[USB_FIFO_TX]); + usb_fifo_free(f_sc->fp[USB_FIFO_RX]); + + f_sc->fp[USB_FIFO_TX] = NULL; + f_sc->fp[USB_FIFO_RX] = NULL; + + if (f_sc->dev != NULL) { + destroy_dev_sched_cb(f_sc->dev, + usb_fifo_cleanup, f_sc->dev->si_drv1); + f_sc->dev = NULL; + } + + DPRINTFN(2, "detached %p\n", f_sc); +} + +usb_size_t +usb_fifo_put_bytes_max(struct usb_fifo *f) +{ + struct usb_mbuf *m; + usb_size_t len; + + USB_IF_POLL(&f->free_q, m); + + if (m) { + len = m->max_data_len; + } else { + len = 0; + } + return (len); +} + +/*------------------------------------------------------------------------* + * usb_fifo_put_data + * + * what: + * 0 - normal operation + * 1 - set last packet flag to enforce framing + *------------------------------------------------------------------------*/ +void +usb_fifo_put_data(struct usb_fifo *f, struct usb_page_cache *pc, + usb_frlength_t offset, usb_frlength_t len, uint8_t what) +{ + struct usb_mbuf *m; + usb_frlength_t io_len; + + while (len || (what == 1)) { + + USB_IF_DEQUEUE(&f->free_q, m); + + if (m) { + USB_MBUF_RESET(m); + + io_len = MIN(len, m->cur_data_len); + + usbd_copy_out(pc, offset, m->cur_data_ptr, io_len); + + m->cur_data_len = io_len; + offset += io_len; + len -= io_len; + + if ((len == 0) && (what == 1)) { + m->last_packet = 1; + } + USB_IF_ENQUEUE(&f->used_q, m); + + usb_fifo_wakeup(f); + + if ((len == 0) || (what == 1)) { + break; + } + } else { + break; + } + } +} + +void +usb_fifo_put_data_linear(struct usb_fifo *f, void *ptr, + usb_size_t len, uint8_t what) +{ + struct usb_mbuf *m; + usb_size_t io_len; + + while (len || (what == 1)) { + + USB_IF_DEQUEUE(&f->free_q, m); + + if (m) { + USB_MBUF_RESET(m); + + io_len = MIN(len, m->cur_data_len); + + bcopy(ptr, m->cur_data_ptr, io_len); + + m->cur_data_len = io_len; + ptr = USB_ADD_BYTES(ptr, io_len); + len -= io_len; + + if ((len == 0) && (what == 1)) { + m->last_packet = 1; + } + USB_IF_ENQUEUE(&f->used_q, m); + + usb_fifo_wakeup(f); + + if ((len == 0) || (what == 1)) { + break; + } + } else { + break; + } + } +} + +uint8_t +usb_fifo_put_data_buffer(struct usb_fifo *f, void *ptr, usb_size_t len) +{ + struct usb_mbuf *m; + + USB_IF_DEQUEUE(&f->free_q, m); + + if (m) { + m->cur_data_len = len; + m->cur_data_ptr = ptr; + USB_IF_ENQUEUE(&f->used_q, m); + usb_fifo_wakeup(f); + return (1); + } + return (0); +} + +void +usb_fifo_put_data_error(struct usb_fifo *f) +{ + f->flag_iserror = 1; + usb_fifo_wakeup(f); +} + +/*------------------------------------------------------------------------* + * usb_fifo_get_data + * + * what: + * 0 - normal operation + * 1 - only get one "usb_mbuf" + * + * returns: + * 0 - no more data + * 1 - data in buffer + *------------------------------------------------------------------------*/ +uint8_t +usb_fifo_get_data(struct usb_fifo *f, struct usb_page_cache *pc, + usb_frlength_t offset, usb_frlength_t len, usb_frlength_t *actlen, + uint8_t what) +{ + struct usb_mbuf *m; + usb_frlength_t io_len; + uint8_t tr_data = 0; + + actlen[0] = 0; + + while (1) { + + USB_IF_DEQUEUE(&f->used_q, m); + + if (m) { + + tr_data = 1; + + io_len = MIN(len, m->cur_data_len); + + usbd_copy_in(pc, offset, m->cur_data_ptr, io_len); + + len -= io_len; + offset += io_len; + actlen[0] += io_len; + m->cur_data_ptr += io_len; + m->cur_data_len -= io_len; + + if ((m->cur_data_len == 0) || (what == 1)) { + USB_IF_ENQUEUE(&f->free_q, m); + + usb_fifo_wakeup(f); + + if (what == 1) { + break; + } + } else { + USB_IF_PREPEND(&f->used_q, m); + } + } else { + + if (tr_data) { + /* wait for data to be written out */ + break; + } + if (f->flag_flushing) { + /* check if we should send a short packet */ + if (f->flag_short != 0) { + f->flag_short = 0; + tr_data = 1; + break; + } + /* flushing complete */ + f->flag_flushing = 0; + usb_fifo_wakeup(f); + } + break; + } + if (len == 0) { + break; + } + } + return (tr_data); +} + +uint8_t +usb_fifo_get_data_linear(struct usb_fifo *f, void *ptr, + usb_size_t len, usb_size_t *actlen, uint8_t what) +{ + struct usb_mbuf *m; + usb_size_t io_len; + uint8_t tr_data = 0; + + actlen[0] = 0; + + while (1) { + + USB_IF_DEQUEUE(&f->used_q, m); + + if (m) { + + tr_data = 1; + + io_len = MIN(len, m->cur_data_len); + + bcopy(m->cur_data_ptr, ptr, io_len); + + len -= io_len; + ptr = USB_ADD_BYTES(ptr, io_len); + actlen[0] += io_len; + m->cur_data_ptr += io_len; + m->cur_data_len -= io_len; + + if ((m->cur_data_len == 0) || (what == 1)) { + USB_IF_ENQUEUE(&f->free_q, m); + + usb_fifo_wakeup(f); + + if (what == 1) { + break; + } + } else { + USB_IF_PREPEND(&f->used_q, m); + } + } else { + + if (tr_data) { + /* wait for data to be written out */ + break; + } + if (f->flag_flushing) { + /* check if we should send a short packet */ + if (f->flag_short != 0) { + f->flag_short = 0; + tr_data = 1; + break; + } + /* flushing complete */ + f->flag_flushing = 0; + usb_fifo_wakeup(f); + } + break; + } + if (len == 0) { + break; + } + } + return (tr_data); +} + +uint8_t +usb_fifo_get_data_buffer(struct usb_fifo *f, void **pptr, usb_size_t *plen) +{ + struct usb_mbuf *m; + + USB_IF_POLL(&f->used_q, m); + + if (m) { + *plen = m->cur_data_len; + *pptr = m->cur_data_ptr; + + return (1); + } + return (0); +} + +void +usb_fifo_get_data_error(struct usb_fifo *f) +{ + f->flag_iserror = 1; + usb_fifo_wakeup(f); +} + +/*------------------------------------------------------------------------* + * usb_alloc_symlink + * + * Return values: + * NULL: Failure + * Else: Pointer to symlink entry + *------------------------------------------------------------------------*/ +struct usb_symlink * +usb_alloc_symlink(const char *target) +{ + struct usb_symlink *ps; + + ps = malloc(sizeof(*ps), M_USBDEV, M_WAITOK); + if (ps == NULL) { + return (ps); + } + /* XXX no longer needed */ + strlcpy(ps->src_path, target, sizeof(ps->src_path)); + ps->src_len = strlen(ps->src_path); + strlcpy(ps->dst_path, target, sizeof(ps->dst_path)); + ps->dst_len = strlen(ps->dst_path); + + sx_xlock(&usb_sym_lock); + TAILQ_INSERT_TAIL(&usb_sym_head, ps, sym_entry); + sx_unlock(&usb_sym_lock); + return (ps); +} + +/*------------------------------------------------------------------------* + * usb_free_symlink + *------------------------------------------------------------------------*/ +void +usb_free_symlink(struct usb_symlink *ps) +{ + if (ps == NULL) { + return; + } + sx_xlock(&usb_sym_lock); + TAILQ_REMOVE(&usb_sym_head, ps, sym_entry); + sx_unlock(&usb_sym_lock); + + free(ps, M_USBDEV); +} + +/*------------------------------------------------------------------------* + * usb_read_symlink + * + * Return value: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +int +usb_read_symlink(uint8_t *user_ptr, uint32_t startentry, uint32_t user_len) +{ + struct usb_symlink *ps; + uint32_t temp; + uint32_t delta = 0; + uint8_t len; + int error = 0; + + sx_xlock(&usb_sym_lock); + + TAILQ_FOREACH(ps, &usb_sym_head, sym_entry) { + + /* + * Compute total length of source and destination symlink + * strings pluss one length byte and two NUL bytes: + */ + temp = ps->src_len + ps->dst_len + 3; + + if (temp > 255) { + /* + * Skip entry because this length cannot fit + * into one byte: + */ + continue; + } + if (startentry != 0) { + /* decrement read offset */ + startentry--; + continue; + } + if (temp > user_len) { + /* out of buffer space */ + break; + } + len = temp; + + /* copy out total length */ + + error = copyout(&len, + USB_ADD_BYTES(user_ptr, delta), 1); + if (error) { + break; + } + delta += 1; + + /* copy out source string */ + + error = copyout(ps->src_path, + USB_ADD_BYTES(user_ptr, delta), ps->src_len); + if (error) { + break; + } + len = 0; + delta += ps->src_len; + error = copyout(&len, + USB_ADD_BYTES(user_ptr, delta), 1); + if (error) { + break; + } + delta += 1; + + /* copy out destination string */ + + error = copyout(ps->dst_path, + USB_ADD_BYTES(user_ptr, delta), ps->dst_len); + if (error) { + break; + } + len = 0; + delta += ps->dst_len; + error = copyout(&len, + USB_ADD_BYTES(user_ptr, delta), 1); + if (error) { + break; + } + delta += 1; + + user_len -= temp; + } + + /* a zero length entry indicates the end */ + + if ((user_len != 0) && (error == 0)) { + + len = 0; + + error = copyout(&len, + USB_ADD_BYTES(user_ptr, delta), 1); + } + sx_unlock(&usb_sym_lock); + return (error); +} + +void +usb_fifo_set_close_zlp(struct usb_fifo *f, uint8_t onoff) +{ + if (f == NULL) + return; + + /* send a Zero Length Packet, ZLP, before close */ + f->flag_short = onoff; +} + +void +usb_fifo_set_write_defrag(struct usb_fifo *f, uint8_t onoff) +{ + if (f == NULL) + return; + + /* defrag written data */ + f->flag_write_defrag = onoff; + /* reset defrag state */ + f->flag_have_fragment = 0; +} + +void * +usb_fifo_softc(struct usb_fifo *f) +{ + return (f->priv_sc0); +} +#endif /* USB_HAVE_UGEN */ diff --git a/freebsd/sys/dev/usb/usb_dev.h b/freebsd/sys/dev/usb/usb_dev.h new file mode 100644 index 00000000..ccf873bd --- /dev/null +++ b/freebsd/sys/dev/usb/usb_dev.h @@ -0,0 +1,154 @@ +/* $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. + */ + +#ifndef _USB_DEV_HH_ +#define _USB_DEV_HH_ + +#include <freebsd/sys/file.h> +#include <freebsd/sys/selinfo.h> +#include <freebsd/sys/poll.h> +#include <freebsd/sys/signalvar.h> +#include <freebsd/sys/proc.h> + +struct usb_fifo; +struct usb_mbuf; + +struct usb_symlink { + TAILQ_ENTRY(usb_symlink) sym_entry; + char src_path[32]; /* Source path - including terminating + * zero */ + char dst_path[32]; /* Destination path - including + * terminating zero */ + uint8_t src_len; /* String length */ + uint8_t dst_len; /* String length */ +}; + +/* + * Private per-device information. + */ +struct usb_cdev_privdata { + struct usb_bus *bus; + struct usb_device *udev; + struct usb_interface *iface; + int bus_index; /* bus index */ + int dev_index; /* device index */ + int ep_addr; /* endpoint address */ + int fflags; + uint8_t fifo_index; /* FIFO index */ +}; + +/* + * The following structure defines a minimum re-implementation of the + * ifqueue structure in the kernel. + */ +struct usb_ifqueue { + struct usb_mbuf *ifq_head; + struct usb_mbuf *ifq_tail; + + usb_size_t ifq_len; + usb_size_t ifq_maxlen; +}; + +/* + * Private per-device and per-thread reference information + */ +struct usb_cdev_refdata { + struct usb_fifo *rxfifo; + struct usb_fifo *txfifo; + uint8_t is_read; /* location has read access */ + uint8_t is_write; /* location has write access */ + uint8_t is_uref; /* USB refcount decr. needed */ + uint8_t is_usbfs; /* USB-FS is active */ +}; + +struct usb_fs_privdata { + int bus_index; + int dev_index; + int ep_addr; + int mode; + int fifo_index; + struct cdev *cdev; + + LIST_ENTRY(usb_fs_privdata) pd_next; +}; + +/* + * Most of the fields in the "usb_fifo" structure are used by the + * generic USB access layer. + */ +struct usb_fifo { + struct usb_ifqueue free_q; + struct usb_ifqueue used_q; + struct selinfo selinfo; + struct cv cv_io; + struct cv cv_drain; + struct usb_fifo_methods *methods; + struct usb_symlink *symlink[2];/* our symlinks */ + struct proc *async_p; /* process that wants SIGIO */ + struct usb_fs_endpoint *fs_ep_ptr; + struct usb_device *udev; + struct usb_xfer *xfer[2]; + struct usb_xfer **fs_xfer; + struct mtx *priv_mtx; /* client data */ + /* set if FIFO is opened by a FILE: */ + struct usb_cdev_privdata *curr_cpd; + void *priv_sc0; /* client data */ + void *priv_sc1; /* client data */ + void *queue_data; + usb_timeout_t timeout; /* timeout in milliseconds */ + usb_frlength_t bufsize; /* BULK and INTERRUPT buffer size */ + usb_frcount_t nframes; /* for isochronous mode */ + uint16_t dev_ep_index; /* our device endpoint index */ + uint8_t flag_sleeping; /* set if FIFO is sleeping */ + uint8_t flag_iscomplete; /* set if a USB transfer is complete */ + uint8_t flag_iserror; /* set if FIFO error happened */ + uint8_t flag_isselect; /* set if FIFO is selected */ + uint8_t flag_flushing; /* set if FIFO is flushing data */ + uint8_t flag_short; /* set if short_ok or force_short + * transfer flags should be set */ + uint8_t flag_stall; /* set if clear stall should be run */ + uint8_t flag_write_defrag; /* set to defrag written data */ + uint8_t flag_have_fragment; /* set if defragging */ + uint8_t iface_index; /* set to the interface we belong to */ + uint8_t fifo_index; /* set to the FIFO index in "struct + * usb_device" */ + uint8_t fs_ep_max; + uint8_t fifo_zlp; /* zero length packet count */ + uint8_t refcount; +#define USB_FIFO_REF_MAX 0xFF +}; + +extern struct cdevsw usb_devsw; + +int usb_fifo_wait(struct usb_fifo *fifo); +void usb_fifo_signal(struct usb_fifo *fifo); +uint8_t usb_fifo_opened(struct usb_fifo *fifo); +struct usb_symlink *usb_alloc_symlink(const char *target); +void usb_free_symlink(struct usb_symlink *ps); +int usb_read_symlink(uint8_t *user_ptr, uint32_t startentry, + uint32_t user_len); + +#endif /* _USB_DEV_HH_ */ diff --git a/freebsd/sys/dev/usb/usb_device.c b/freebsd/sys/dev/usb/usb_device.c new file mode 100644 index 00000000..1d5b5182 --- /dev/null +++ b/freebsd/sys/dev/usb/usb_device.c @@ -0,0 +1,2693 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/* $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. + */ + +#include <freebsd/sys/stdint.h> +#include <freebsd/sys/stddef.h> +#include <freebsd/sys/param.h> +#include <freebsd/sys/queue.h> +#include <freebsd/sys/types.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/bus.h> +#include <freebsd/sys/linker_set.h> +#include <freebsd/sys/module.h> +#include <freebsd/sys/lock.h> +#include <freebsd/sys/mutex.h> +#include <freebsd/sys/condvar.h> +#include <freebsd/sys/sysctl.h> +#include <freebsd/sys/sx.h> +#include <freebsd/sys/unistd.h> +#include <freebsd/sys/callout.h> +#include <freebsd/sys/malloc.h> +#include <freebsd/sys/priv.h> +#include <freebsd/sys/conf.h> +#include <freebsd/sys/fcntl.h> + +#include <freebsd/dev/usb/usb.h> +#include <freebsd/dev/usb/usbdi.h> +#include <freebsd/dev/usb/usbdi_util.h> +#include <freebsd/dev/usb/usb_ioctl.h> + +#if USB_HAVE_UGEN +#include <freebsd/sys/sbuf.h> +#endif + +#include <freebsd/local/usbdevs.h> + +#define USB_DEBUG_VAR usb_debug + +#include <freebsd/dev/usb/usb_core.h> +#include <freebsd/dev/usb/usb_debug.h> +#include <freebsd/dev/usb/usb_process.h> +#include <freebsd/dev/usb/usb_device.h> +#include <freebsd/dev/usb/usb_busdma.h> +#include <freebsd/dev/usb/usb_transfer.h> +#include <freebsd/dev/usb/usb_request.h> +#include <freebsd/dev/usb/usb_dynamic.h> +#include <freebsd/dev/usb/usb_hub.h> +#include <freebsd/dev/usb/usb_util.h> +#include <freebsd/dev/usb/usb_msctest.h> +#if USB_HAVE_UGEN +#include <freebsd/dev/usb/usb_dev.h> +#include <freebsd/dev/usb/usb_generic.h> +#endif + +#include <freebsd/dev/usb/quirk/usb_quirk.h> + +#include <freebsd/dev/usb/usb_controller.h> +#include <freebsd/dev/usb/usb_bus.h> + +/* function prototypes */ + +static void usb_init_endpoint(struct usb_device *, uint8_t, + struct usb_endpoint_descriptor *, + struct usb_endpoint_ss_comp_descriptor *, + struct usb_endpoint *); +static void usb_unconfigure(struct usb_device *, uint8_t); +static void usb_detach_device_sub(struct usb_device *, device_t *, + uint8_t); +static uint8_t usb_probe_and_attach_sub(struct usb_device *, + struct usb_attach_arg *); +static void usb_init_attach_arg(struct usb_device *, + struct usb_attach_arg *); +static void usb_suspend_resume_sub(struct usb_device *, device_t, + uint8_t); +static void usbd_clear_stall_proc(struct usb_proc_msg *_pm); +static usb_error_t usb_config_parse(struct usb_device *, uint8_t, uint8_t); +static void usbd_set_device_strings(struct usb_device *); +#if USB_HAVE_UGEN +static void usb_notify_addq(const char *type, struct usb_device *); +static void usb_fifo_free_wrap(struct usb_device *, uint8_t, uint8_t); +static struct cdev *usb_make_dev(struct usb_device *, int, int); +static void usb_cdev_create(struct usb_device *); +static void usb_cdev_free(struct usb_device *); +static void usb_cdev_cleanup(void *); +#endif + +/* This variable is global to allow easy access to it: */ + +int usb_template = 0; + +#ifndef __rtems__ +TUNABLE_INT("hw.usb.usb_template", &usb_template); +SYSCTL_INT(_hw_usb, OID_AUTO, template, CTLFLAG_RW, + &usb_template, 0, "Selected USB device side template"); +#endif /* __rtems__ */ + +/* English is default language */ + +static int usb_lang_id = 0x0009; +static int usb_lang_mask = 0x00FF; + +#ifndef __rtems__ +TUNABLE_INT("hw.usb.usb_lang_id", &usb_lang_id); +SYSCTL_INT(_hw_usb, OID_AUTO, usb_lang_id, CTLFLAG_RW, + &usb_lang_id, 0, "Preferred USB language ID"); + +TUNABLE_INT("hw.usb.usb_lang_mask", &usb_lang_mask); +SYSCTL_INT(_hw_usb, OID_AUTO, usb_lang_mask, CTLFLAG_RW, + &usb_lang_mask, 0, "Preferred USB language mask"); +#endif /* __rtems__ */ + +static const char* statestr[USB_STATE_MAX] = { + [USB_STATE_DETACHED] = "DETACHED", + [USB_STATE_ATTACHED] = "ATTACHED", + [USB_STATE_POWERED] = "POWERED", + [USB_STATE_ADDRESSED] = "ADDRESSED", + [USB_STATE_CONFIGURED] = "CONFIGURED", +}; + +const char * +usb_statestr(enum usb_dev_state state) +{ + return ((state < USB_STATE_MAX) ? statestr[state] : "UNKNOWN"); +} + +const char * +usb_get_manufacturer(struct usb_device *udev) +{ + return (udev->manufacturer ? udev->manufacturer : "Unknown"); +} + +const char * +usb_get_product(struct usb_device *udev) +{ + return (udev->product ? udev->product : ""); +} + +const char * +usb_get_serial(struct usb_device *udev) +{ + return (udev->serial ? udev->serial : ""); +} + +/*------------------------------------------------------------------------* + * usbd_get_ep_by_addr + * + * This function searches for an USB ep by endpoint address and + * direction. + * + * Returns: + * NULL: Failure + * Else: Success + *------------------------------------------------------------------------*/ +struct usb_endpoint * +usbd_get_ep_by_addr(struct usb_device *udev, uint8_t ea_val) +{ + struct usb_endpoint *ep = udev->endpoints; + struct usb_endpoint *ep_end = udev->endpoints + udev->endpoints_max; + enum { + EA_MASK = (UE_DIR_IN | UE_DIR_OUT | UE_ADDR), + }; + + /* + * According to the USB specification not all bits are used + * for the endpoint address. Keep defined bits only: + */ + ea_val &= EA_MASK; + + /* + * Iterate accross all the USB endpoints searching for a match + * based on the endpoint address: + */ + for (; ep != ep_end; ep++) { + + if (ep->edesc == NULL) { + continue; + } + /* do the mask and check the value */ + if ((ep->edesc->bEndpointAddress & EA_MASK) == ea_val) { + goto found; + } + } + + /* + * The default endpoint is always present and is checked separately: + */ + if ((udev->ctrl_ep.edesc) && + ((udev->ctrl_ep.edesc->bEndpointAddress & EA_MASK) == ea_val)) { + ep = &udev->ctrl_ep; + goto found; + } + return (NULL); + +found: + return (ep); +} + +/*------------------------------------------------------------------------* + * usbd_get_endpoint + * + * This function searches for an USB endpoint based on the information + * given by the passed "struct usb_config" pointer. + * + * Return values: + * NULL: No match. + * Else: Pointer to "struct usb_endpoint". + *------------------------------------------------------------------------*/ +struct usb_endpoint * +usbd_get_endpoint(struct usb_device *udev, uint8_t iface_index, + const struct usb_config *setup) +{ + struct usb_endpoint *ep = udev->endpoints; + struct usb_endpoint *ep_end = udev->endpoints + udev->endpoints_max; + uint8_t index = setup->ep_index; + uint8_t ea_mask; + uint8_t ea_val; + uint8_t type_mask; + uint8_t type_val; + + DPRINTFN(10, "udev=%p iface_index=%d address=0x%x " + "type=0x%x dir=0x%x index=%d\n", + udev, iface_index, setup->endpoint, + setup->type, setup->direction, setup->ep_index); + + /* check USB mode */ + + if (setup->usb_mode != USB_MODE_DUAL && + udev->flags.usb_mode != setup->usb_mode) { + /* wrong mode - no endpoint */ + return (NULL); + } + + /* setup expected endpoint direction mask and value */ + + if (setup->direction == UE_DIR_RX) { + ea_mask = (UE_DIR_IN | UE_DIR_OUT); + ea_val = (udev->flags.usb_mode == USB_MODE_DEVICE) ? + UE_DIR_OUT : UE_DIR_IN; + } else if (setup->direction == UE_DIR_TX) { + ea_mask = (UE_DIR_IN | UE_DIR_OUT); + ea_val = (udev->flags.usb_mode == USB_MODE_DEVICE) ? + UE_DIR_IN : UE_DIR_OUT; + } else if (setup->direction == UE_DIR_ANY) { + /* match any endpoint direction */ + ea_mask = 0; + ea_val = 0; + } else { + /* match the given endpoint direction */ + ea_mask = (UE_DIR_IN | UE_DIR_OUT); + ea_val = (setup->direction & (UE_DIR_IN | UE_DIR_OUT)); + } + + /* setup expected endpoint address */ + + if (setup->endpoint == UE_ADDR_ANY) { + /* match any endpoint address */ + } else { + /* match the given endpoint address */ + ea_mask |= UE_ADDR; + ea_val |= (setup->endpoint & UE_ADDR); + } + + /* setup expected endpoint type */ + + if (setup->type == UE_BULK_INTR) { + /* this will match BULK and INTERRUPT endpoints */ + type_mask = 2; + type_val = 2; + } else if (setup->type == UE_TYPE_ANY) { + /* match any endpoint type */ + type_mask = 0; + type_val = 0; + } else { + /* match the given endpoint type */ + type_mask = UE_XFERTYPE; + type_val = (setup->type & UE_XFERTYPE); + } + + /* + * Iterate accross all the USB endpoints searching for a match + * based on the endpoint address. Note that we are searching + * the endpoints from the beginning of the "udev->endpoints" array. + */ + for (; ep != ep_end; ep++) { + + if ((ep->edesc == NULL) || + (ep->iface_index != iface_index)) { + continue; + } + /* do the masks and check the values */ + + if (((ep->edesc->bEndpointAddress & ea_mask) == ea_val) && + ((ep->edesc->bmAttributes & type_mask) == type_val)) { + if (!index--) { + goto found; + } + } + } + + /* + * Match against default endpoint last, so that "any endpoint", "any + * address" and "any direction" returns the first endpoint of the + * interface. "iface_index" and "direction" is ignored: + */ + if ((udev->ctrl_ep.edesc) && + ((udev->ctrl_ep.edesc->bEndpointAddress & ea_mask) == ea_val) && + ((udev->ctrl_ep.edesc->bmAttributes & type_mask) == type_val) && + (!index)) { + ep = &udev->ctrl_ep; + goto found; + } + return (NULL); + +found: + return (ep); +} + +/*------------------------------------------------------------------------* + * usbd_interface_count + * + * This function stores the number of USB interfaces excluding + * alternate settings, which the USB config descriptor reports into + * the unsigned 8-bit integer pointed to by "count". + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_interface_count(struct usb_device *udev, uint8_t *count) +{ + if (udev->cdesc == NULL) { + *count = 0; + return (USB_ERR_NOT_CONFIGURED); + } + *count = udev->ifaces_max; + return (USB_ERR_NORMAL_COMPLETION); +} + + +/*------------------------------------------------------------------------* + * usb_init_endpoint + * + * This function will initialise the USB endpoint structure pointed to by + * the "endpoint" argument. The structure pointed to by "endpoint" must be + * zeroed before calling this function. + *------------------------------------------------------------------------*/ +static void +usb_init_endpoint(struct usb_device *udev, uint8_t iface_index, + struct usb_endpoint_descriptor *edesc, + struct usb_endpoint_ss_comp_descriptor *ecomp, + struct usb_endpoint *ep) +{ + struct usb_bus_methods *methods; + + methods = udev->bus->methods; + + (methods->endpoint_init) (udev, edesc, ep); + + /* initialise USB endpoint structure */ + ep->edesc = edesc; + ep->ecomp = ecomp; + ep->iface_index = iface_index; + TAILQ_INIT(&ep->endpoint_q.head); + ep->endpoint_q.command = &usbd_pipe_start; + + /* the pipe is not supported by the hardware */ + if (ep->methods == NULL) + return; + + /* clear stall, if any */ + if (methods->clear_stall != NULL) { + USB_BUS_LOCK(udev->bus); + (methods->clear_stall) (udev, ep); + USB_BUS_UNLOCK(udev->bus); + } +} + +/*-----------------------------------------------------------------------* + * usb_endpoint_foreach + * + * This function will iterate all the USB endpoints except the control + * endpoint. This function is NULL safe. + * + * Return values: + * NULL: End of USB endpoints + * Else: Pointer to next USB endpoint + *------------------------------------------------------------------------*/ +struct usb_endpoint * +usb_endpoint_foreach(struct usb_device *udev, struct usb_endpoint *ep) +{ + struct usb_endpoint *ep_end; + + /* be NULL safe */ + if (udev == NULL) + return (NULL); + + ep_end = udev->endpoints + udev->endpoints_max; + + /* get next endpoint */ + if (ep == NULL) + ep = udev->endpoints; + else + ep++; + + /* find next allocated ep */ + while (ep != ep_end) { + if (ep->edesc != NULL) + return (ep); + ep++; + } + return (NULL); +} + +/*------------------------------------------------------------------------* + * usb_unconfigure + * + * This function will free all USB interfaces and USB endpoints belonging + * to an USB device. + * + * Flag values, see "USB_UNCFG_FLAG_XXX". + *------------------------------------------------------------------------*/ +static void +usb_unconfigure(struct usb_device *udev, uint8_t flag) +{ + uint8_t do_unlock; + + /* automatic locking */ + if (usbd_enum_is_locked(udev)) { + do_unlock = 0; + } else { + do_unlock = 1; + usbd_enum_lock(udev); + } + + /* detach all interface drivers */ + usb_detach_device(udev, USB_IFACE_INDEX_ANY, flag); + +#if USB_HAVE_UGEN + /* free all FIFOs except control endpoint FIFOs */ + usb_fifo_free_wrap(udev, USB_IFACE_INDEX_ANY, flag); + + /* + * Free all cdev's, if any. + */ + usb_cdev_free(udev); +#endif + +#if USB_HAVE_COMPAT_LINUX + /* free Linux compat device, if any */ + if (udev->linux_endpoint_start) { + usb_linux_free_device(udev); + udev->linux_endpoint_start = NULL; + } +#endif + + usb_config_parse(udev, USB_IFACE_INDEX_ANY, USB_CFG_FREE); + + /* free "cdesc" after "ifaces" and "endpoints", if any */ + if (udev->cdesc != NULL) { + if (udev->flags.usb_mode != USB_MODE_DEVICE) + free(udev->cdesc, M_USB); + udev->cdesc = NULL; + } + /* set unconfigured state */ + udev->curr_config_no = USB_UNCONFIG_NO; + udev->curr_config_index = USB_UNCONFIG_INDEX; + + if (do_unlock) + usbd_enum_unlock(udev); +} + +/*------------------------------------------------------------------------* + * usbd_set_config_index + * + * This function selects configuration by index, independent of the + * actual configuration number. This function should not be used by + * USB drivers. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_set_config_index(struct usb_device *udev, uint8_t index) +{ + struct usb_status ds; + struct usb_config_descriptor *cdp; + uint16_t power; + uint16_t max_power; + uint8_t selfpowered; + uint8_t do_unlock; + usb_error_t err; + + DPRINTFN(6, "udev=%p index=%d\n", udev, index); + + /* automatic locking */ + if (usbd_enum_is_locked(udev)) { + do_unlock = 0; + } else { + do_unlock = 1; + usbd_enum_lock(udev); + } + + usb_unconfigure(udev, 0); + + if (index == USB_UNCONFIG_INDEX) { + /* + * Leave unallocated when unconfiguring the + * device. "usb_unconfigure()" will also reset + * the current config number and index. + */ + err = usbd_req_set_config(udev, NULL, USB_UNCONFIG_NO); + if (udev->state == USB_STATE_CONFIGURED) + usb_set_device_state(udev, USB_STATE_ADDRESSED); + goto done; + } + /* get the full config descriptor */ + if (udev->flags.usb_mode == USB_MODE_DEVICE) { + /* save some memory */ + err = usbd_req_get_descriptor_ptr(udev, &cdp, + (UDESC_CONFIG << 8) | index); + } else { + /* normal request */ + err = usbd_req_get_config_desc_full(udev, + NULL, &cdp, M_USB, index); + } + if (err) { + goto done; + } + /* set the new config descriptor */ + + udev->cdesc = cdp; + + /* Figure out if the device is self or bus powered. */ + selfpowered = 0; + if ((!udev->flags.uq_bus_powered) && + (cdp->bmAttributes & UC_SELF_POWERED) && + (udev->flags.usb_mode == USB_MODE_HOST)) { + /* May be self powered. */ + if (cdp->bmAttributes & UC_BUS_POWERED) { + /* Must ask device. */ + err = usbd_req_get_device_status(udev, NULL, &ds); + if (err) { + DPRINTFN(0, "could not read " + "device status: %s\n", + usbd_errstr(err)); + } else if (UGETW(ds.wStatus) & UDS_SELF_POWERED) { + selfpowered = 1; + } + DPRINTF("status=0x%04x \n", + UGETW(ds.wStatus)); + } else + selfpowered = 1; + } + DPRINTF("udev=%p cdesc=%p (addr %d) cno=%d attr=0x%02x, " + "selfpowered=%d, power=%d\n", + udev, cdp, + udev->address, cdp->bConfigurationValue, cdp->bmAttributes, + selfpowered, cdp->bMaxPower * 2); + + /* Check if we have enough power. */ + power = cdp->bMaxPower * 2; + + if (udev->parent_hub) { + max_power = udev->parent_hub->hub->portpower; + } else { + max_power = USB_MAX_POWER; + } + + if (power > max_power) { + DPRINTFN(0, "power exceeded %d > %d\n", power, max_power); + err = USB_ERR_NO_POWER; + goto done; + } + /* Only update "self_powered" in USB Host Mode */ + if (udev->flags.usb_mode == USB_MODE_HOST) { + udev->flags.self_powered = selfpowered; + } + udev->power = power; + udev->curr_config_no = cdp->bConfigurationValue; + udev->curr_config_index = index; + usb_set_device_state(udev, USB_STATE_CONFIGURED); + + /* Set the actual configuration value. */ + err = usbd_req_set_config(udev, NULL, cdp->bConfigurationValue); + if (err) { + goto done; + } + + err = usb_config_parse(udev, USB_IFACE_INDEX_ANY, USB_CFG_ALLOC); + if (err) { + goto done; + } + + err = usb_config_parse(udev, USB_IFACE_INDEX_ANY, USB_CFG_INIT); + if (err) { + goto done; + } + +#if USB_HAVE_UGEN + /* create device nodes for each endpoint */ + usb_cdev_create(udev); +#endif + +done: + DPRINTF("error=%s\n", usbd_errstr(err)); + if (err) { + usb_unconfigure(udev, 0); + } + if (do_unlock) + usbd_enum_unlock(udev); + return (err); +} + +/*------------------------------------------------------------------------* + * usb_config_parse + * + * This function will allocate and free USB interfaces and USB endpoints, + * parse the USB configuration structure and initialise the USB endpoints + * and interfaces. If "iface_index" is not equal to + * "USB_IFACE_INDEX_ANY" then the "cmd" parameter is the + * alternate_setting to be selected for the given interface. Else the + * "cmd" parameter is defined by "USB_CFG_XXX". "iface_index" can be + * "USB_IFACE_INDEX_ANY" or a valid USB interface index. This function + * is typically called when setting the configuration or when setting + * an alternate interface. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static usb_error_t +usb_config_parse(struct usb_device *udev, uint8_t iface_index, uint8_t cmd) +{ + struct usb_idesc_parse_state ips; + struct usb_interface_descriptor *id; + struct usb_endpoint_descriptor *ed; + struct usb_interface *iface; + struct usb_endpoint *ep; + usb_error_t err; + uint8_t ep_curr; + uint8_t ep_max; + uint8_t temp; + uint8_t do_init; + uint8_t alt_index; + + if (iface_index != USB_IFACE_INDEX_ANY) { + /* parameter overload */ + alt_index = cmd; + cmd = USB_CFG_INIT; + } else { + /* not used */ + alt_index = 0; + } + + err = 0; + + DPRINTFN(5, "iface_index=%d cmd=%d\n", + iface_index, cmd); + + if (cmd == USB_CFG_FREE) + goto cleanup; + + if (cmd == USB_CFG_INIT) { + sx_assert(&udev->enum_sx, SA_LOCKED); + + /* check for in-use endpoints */ + + ep = udev->endpoints; + ep_max = udev->endpoints_max; + while (ep_max--) { + /* look for matching endpoints */ + if ((iface_index == USB_IFACE_INDEX_ANY) || + (iface_index == ep->iface_index)) { + if (ep->refcount_alloc != 0) { + /* + * This typically indicates a + * more serious error. + */ + err = USB_ERR_IN_USE; + } else { + /* reset endpoint */ + memset(ep, 0, sizeof(*ep)); + /* make sure we don't zero the endpoint again */ + ep->iface_index = USB_IFACE_INDEX_ANY; + } + } + ep++; + } + + if (err) + return (err); + } + + memset(&ips, 0, sizeof(ips)); + + ep_curr = 0; + ep_max = 0; + + while ((id = usb_idesc_foreach(udev->cdesc, &ips))) { + + /* check for interface overflow */ + if (ips.iface_index == USB_IFACE_MAX) + break; /* crazy */ + + iface = udev->ifaces + ips.iface_index; + + /* check for specific interface match */ + + if (cmd == USB_CFG_INIT) { + if ((iface_index != USB_IFACE_INDEX_ANY) && + (iface_index != ips.iface_index)) { + /* wrong interface */ + do_init = 0; + } else if (alt_index != ips.iface_index_alt) { + /* wrong alternate setting */ + do_init = 0; + } else { + /* initialise interface */ + do_init = 1; + } + } else + do_init = 0; + + /* check for new interface */ + if (ips.iface_index_alt == 0) { + /* update current number of endpoints */ + ep_curr = ep_max; + } + /* check for init */ + if (do_init) { + /* setup the USB interface structure */ + iface->idesc = id; + /* default setting */ + iface->parent_iface_index = USB_IFACE_INDEX_ANY; + /* set alternate index */ + iface->alt_index = alt_index; + } + + DPRINTFN(5, "found idesc nendpt=%d\n", id->bNumEndpoints); + + ed = (struct usb_endpoint_descriptor *)id; + + temp = ep_curr; + + /* iterate all the endpoint descriptors */ + while ((ed = usb_edesc_foreach(udev->cdesc, ed))) { + + if (temp == USB_EP_MAX) + break; /* crazy */ + + ep = udev->endpoints + temp; + + if (do_init) { + void *ecomp; + + ecomp = usb_ed_comp_foreach(udev->cdesc, (void *)ed); + if (ecomp != NULL) + DPRINTFN(5, "Found endpoint companion descriptor\n"); + + usb_init_endpoint(udev, + ips.iface_index, ed, ecomp, ep); + } + + temp ++; + + /* find maximum number of endpoints */ + if (ep_max < temp) + ep_max = temp; + + /* optimalisation */ + id = (struct usb_interface_descriptor *)ed; + } + } + + /* NOTE: It is valid to have no interfaces and no endpoints! */ + + if (cmd == USB_CFG_ALLOC) { + udev->ifaces_max = ips.iface_index; + udev->ifaces = NULL; + if (udev->ifaces_max != 0) { + udev->ifaces = malloc(sizeof(*iface) * udev->ifaces_max, + M_USB, M_WAITOK | M_ZERO); + if (udev->ifaces == NULL) { + err = USB_ERR_NOMEM; + goto done; + } + } + if (ep_max != 0) { + udev->endpoints = malloc(sizeof(*ep) * ep_max, + M_USB, M_WAITOK | M_ZERO); + if (udev->endpoints == NULL) { + err = USB_ERR_NOMEM; + goto done; + } + } else { + udev->endpoints = NULL; + } + USB_BUS_LOCK(udev->bus); + udev->endpoints_max = ep_max; + /* reset any ongoing clear-stall */ + udev->ep_curr = NULL; + USB_BUS_UNLOCK(udev->bus); + } + +done: + if (err) { + if (cmd == USB_CFG_ALLOC) { +cleanup: + USB_BUS_LOCK(udev->bus); + udev->endpoints_max = 0; + /* reset any ongoing clear-stall */ + udev->ep_curr = NULL; + USB_BUS_UNLOCK(udev->bus); + + /* cleanup */ + if (udev->ifaces != NULL) + free(udev->ifaces, M_USB); + if (udev->endpoints != NULL) + free(udev->endpoints, M_USB); + + udev->ifaces = NULL; + udev->endpoints = NULL; + udev->ifaces_max = 0; + } + } + return (err); +} + +/*------------------------------------------------------------------------* + * usbd_set_alt_interface_index + * + * This function will select an alternate interface index for the + * given interface index. The interface should not be in use when this + * function is called. That means there should not be any open USB + * transfers. Else an error is returned. If the alternate setting is + * already set this function will simply return success. This function + * is called in Host mode and Device mode! + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_set_alt_interface_index(struct usb_device *udev, + uint8_t iface_index, uint8_t alt_index) +{ + struct usb_interface *iface = usbd_get_iface(udev, iface_index); + usb_error_t err; + uint8_t do_unlock; + + /* automatic locking */ + if (usbd_enum_is_locked(udev)) { + do_unlock = 0; + } else { + do_unlock = 1; + usbd_enum_lock(udev); + } + if (iface == NULL) { + err = USB_ERR_INVAL; + goto done; + } + if (iface->alt_index == alt_index) { + /* + * Optimise away duplicate setting of + * alternate setting in USB Host Mode! + */ + err = 0; + goto done; + } +#if USB_HAVE_UGEN + /* + * Free all generic FIFOs for this interface, except control + * endpoint FIFOs: + */ + usb_fifo_free_wrap(udev, iface_index, 0); +#endif + + err = usb_config_parse(udev, iface_index, alt_index); + if (err) { + goto done; + } + if (iface->alt_index != alt_index) { + /* the alternate setting does not exist */ + err = USB_ERR_INVAL; + goto done; + } + + err = usbd_req_set_alt_interface_no(udev, NULL, iface_index, + iface->idesc->bAlternateSetting); + +done: + if (do_unlock) + usbd_enum_unlock(udev); + + return (err); +} + +/*------------------------------------------------------------------------* + * usbd_set_endpoint_stall + * + * This function is used to make a BULK or INTERRUPT endpoint send + * STALL tokens in USB device mode. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_set_endpoint_stall(struct usb_device *udev, struct usb_endpoint *ep, + uint8_t do_stall) +{ + struct usb_xfer *xfer; + uint8_t et; + uint8_t was_stalled; + + if (ep == NULL) { + /* nothing to do */ + DPRINTF("Cannot find endpoint\n"); + /* + * Pretend that the clear or set stall request is + * successful else some USB host stacks can do + * strange things, especially when a control endpoint + * stalls. + */ + return (0); + } + et = (ep->edesc->bmAttributes & UE_XFERTYPE); + + if ((et != UE_BULK) && + (et != UE_INTERRUPT)) { + /* + * Should not stall control + * nor isochronous endpoints. + */ + DPRINTF("Invalid endpoint\n"); + return (0); + } + USB_BUS_LOCK(udev->bus); + + /* store current stall state */ + was_stalled = ep->is_stalled; + + /* check for no change */ + if (was_stalled && do_stall) { + /* if the endpoint is already stalled do nothing */ + USB_BUS_UNLOCK(udev->bus); + DPRINTF("No change\n"); + return (0); + } + /* set stalled state */ + ep->is_stalled = 1; + + if (do_stall || (!was_stalled)) { + if (!was_stalled) { + /* lookup the current USB transfer, if any */ + xfer = ep->endpoint_q.curr; + } else { + xfer = NULL; + } + + /* + * If "xfer" is non-NULL the "set_stall" method will + * complete the USB transfer like in case of a timeout + * setting the error code "USB_ERR_STALLED". + */ + (udev->bus->methods->set_stall) (udev, xfer, ep, &do_stall); + } + if (!do_stall) { + ep->toggle_next = 0; /* reset data toggle */ + ep->is_stalled = 0; /* clear stalled state */ + + (udev->bus->methods->clear_stall) (udev, ep); + + /* start up the current or next transfer, if any */ + usb_command_wrapper(&ep->endpoint_q, ep->endpoint_q.curr); + } + USB_BUS_UNLOCK(udev->bus); + return (0); +} + +/*------------------------------------------------------------------------* + * usb_reset_iface_endpoints - used in USB device side mode + *------------------------------------------------------------------------*/ +usb_error_t +usb_reset_iface_endpoints(struct usb_device *udev, uint8_t iface_index) +{ + struct usb_endpoint *ep; + struct usb_endpoint *ep_end; + + ep = udev->endpoints; + ep_end = udev->endpoints + udev->endpoints_max; + + for (; ep != ep_end; ep++) { + + if ((ep->edesc == NULL) || + (ep->iface_index != iface_index)) { + continue; + } + /* simulate a clear stall from the peer */ + usbd_set_endpoint_stall(udev, ep, 0); + } + return (0); +} + +/*------------------------------------------------------------------------* + * usb_detach_device_sub + * + * This function will try to detach an USB device. If it fails a panic + * will result. + * + * Flag values, see "USB_UNCFG_FLAG_XXX". + *------------------------------------------------------------------------*/ +static void +usb_detach_device_sub(struct usb_device *udev, device_t *ppdev, + uint8_t flag) +{ + device_t dev; + int err; + + dev = *ppdev; + if (dev) { + /* + * NOTE: It is important to clear "*ppdev" before deleting + * the child due to some device methods being called late + * during the delete process ! + */ + *ppdev = NULL; + + device_printf(dev, "at %s, port %d, addr %d " + "(disconnected)\n", + device_get_nameunit(udev->parent_dev), + udev->port_no, udev->address); + + if (device_is_attached(dev)) { + if (udev->flags.peer_suspended) { + err = DEVICE_RESUME(dev); + if (err) { + device_printf(dev, "Resume failed\n"); + } + } + if (device_detach(dev)) { + goto error; + } + } + if (device_delete_child(udev->parent_dev, dev)) { + goto error; + } + } + return; + +error: + /* Detach is not allowed to fail in the USB world */ + panic("A USB driver would not detach\n"); +} + +/*------------------------------------------------------------------------* + * usb_detach_device + * + * The following function will detach the matching interfaces. + * This function is NULL safe. + * + * Flag values, see "USB_UNCFG_FLAG_XXX". + *------------------------------------------------------------------------*/ +void +usb_detach_device(struct usb_device *udev, uint8_t iface_index, + uint8_t flag) +{ + struct usb_interface *iface; + uint8_t i; + + if (udev == NULL) { + /* nothing to do */ + return; + } + DPRINTFN(4, "udev=%p\n", udev); + + sx_assert(&udev->enum_sx, SA_LOCKED); + + /* + * First detach the child to give the child's detach routine a + * chance to detach the sub-devices in the correct order. + * Then delete the child using "device_delete_child()" which + * will detach all sub-devices from the bottom and upwards! + */ + if (iface_index != USB_IFACE_INDEX_ANY) { + i = iface_index; + iface_index = i + 1; + } else { + i = 0; + iface_index = USB_IFACE_MAX; + } + + /* do the detach */ + + for (; i != iface_index; i++) { + + iface = usbd_get_iface(udev, i); + if (iface == NULL) { + /* looks like the end of the USB interfaces */ + break; + } + usb_detach_device_sub(udev, &iface->subdev, flag); + } +} + +/*------------------------------------------------------------------------* + * usb_probe_and_attach_sub + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +usb_probe_and_attach_sub(struct usb_device *udev, + struct usb_attach_arg *uaa) +{ + struct usb_interface *iface; + device_t dev; + int err; + + iface = uaa->iface; + if (iface->parent_iface_index != USB_IFACE_INDEX_ANY) { + /* leave interface alone */ + return (0); + } + dev = iface->subdev; + if (dev) { + + /* clean up after module unload */ + + if (device_is_attached(dev)) { + /* already a device there */ + return (0); + } + /* clear "iface->subdev" as early as possible */ + + iface->subdev = NULL; + + if (device_delete_child(udev->parent_dev, dev)) { + + /* + * Panic here, else one can get a double call + * to device_detach(). USB devices should + * never fail on detach! + */ + panic("device_delete_child() failed\n"); + } + } + if (uaa->temp_dev == NULL) { + + /* create a new child */ + uaa->temp_dev = device_add_child(udev->parent_dev, NULL, -1); + if (uaa->temp_dev == NULL) { + device_printf(udev->parent_dev, + "Device creation failed\n"); + return (1); /* failure */ + } + device_set_ivars(uaa->temp_dev, uaa); + device_quiet(uaa->temp_dev); + } + /* + * Set "subdev" before probe and attach so that "devd" gets + * the information it needs. + */ + iface->subdev = uaa->temp_dev; + + if (device_probe_and_attach(iface->subdev) == 0) { + /* + * The USB attach arguments are only available during probe + * and attach ! + */ + uaa->temp_dev = NULL; + device_set_ivars(iface->subdev, NULL); + + if (udev->flags.peer_suspended) { + err = DEVICE_SUSPEND(iface->subdev); + if (err) + device_printf(iface->subdev, "Suspend failed\n"); + } + return (0); /* success */ + } else { + /* No USB driver found */ + iface->subdev = NULL; + } + return (1); /* failure */ +} + +/*------------------------------------------------------------------------* + * usbd_set_parent_iface + * + * Using this function will lock the alternate interface setting on an + * interface. It is typically used for multi interface drivers. In USB + * device side mode it is assumed that the alternate interfaces all + * have the same endpoint descriptors. The default parent index value + * is "USB_IFACE_INDEX_ANY". Then the alternate setting value is not + * locked. + *------------------------------------------------------------------------*/ +void +usbd_set_parent_iface(struct usb_device *udev, uint8_t iface_index, + uint8_t parent_index) +{ + struct usb_interface *iface; + + iface = usbd_get_iface(udev, iface_index); + if (iface) { + iface->parent_iface_index = parent_index; + } +} + +static void +usb_init_attach_arg(struct usb_device *udev, + struct usb_attach_arg *uaa) +{ + bzero(uaa, sizeof(*uaa)); + + uaa->device = udev; + uaa->usb_mode = udev->flags.usb_mode; + uaa->port = udev->port_no; + uaa->dev_state = UAA_DEV_READY; + + uaa->info.idVendor = UGETW(udev->ddesc.idVendor); + uaa->info.idProduct = UGETW(udev->ddesc.idProduct); + uaa->info.bcdDevice = UGETW(udev->ddesc.bcdDevice); + uaa->info.bDeviceClass = udev->ddesc.bDeviceClass; + uaa->info.bDeviceSubClass = udev->ddesc.bDeviceSubClass; + uaa->info.bDeviceProtocol = udev->ddesc.bDeviceProtocol; + uaa->info.bConfigIndex = udev->curr_config_index; + uaa->info.bConfigNum = udev->curr_config_no; +} + +/*------------------------------------------------------------------------* + * usb_probe_and_attach + * + * This function is called from "uhub_explore_sub()", + * "usb_handle_set_config()" and "usb_handle_request()". + * + * Returns: + * 0: Success + * Else: A control transfer failed + *------------------------------------------------------------------------*/ +usb_error_t +usb_probe_and_attach(struct usb_device *udev, uint8_t iface_index) +{ + struct usb_attach_arg uaa; + struct usb_interface *iface; + uint8_t i; + uint8_t j; + uint8_t do_unlock; + + if (udev == NULL) { + DPRINTF("udev == NULL\n"); + return (USB_ERR_INVAL); + } + /* automatic locking */ + if (usbd_enum_is_locked(udev)) { + do_unlock = 0; + } else { + do_unlock = 1; + usbd_enum_lock(udev); + } + + if (udev->curr_config_index == USB_UNCONFIG_INDEX) { + /* do nothing - no configuration has been set */ + goto done; + } + /* setup USB attach arguments */ + + usb_init_attach_arg(udev, &uaa); + + /* Check if only one interface should be probed: */ + if (iface_index != USB_IFACE_INDEX_ANY) { + i = iface_index; + j = i + 1; + } else { + i = 0; + j = USB_IFACE_MAX; + } + + /* Do the probe and attach */ + for (; i != j; i++) { + + iface = usbd_get_iface(udev, i); + if (iface == NULL) { + /* + * Looks like the end of the USB + * interfaces ! + */ + DPRINTFN(2, "end of interfaces " + "at %u\n", i); + break; + } + if (iface->idesc == NULL) { + /* no interface descriptor */ + continue; + } + uaa.iface = iface; + + uaa.info.bInterfaceClass = + iface->idesc->bInterfaceClass; + uaa.info.bInterfaceSubClass = + iface->idesc->bInterfaceSubClass; + uaa.info.bInterfaceProtocol = + iface->idesc->bInterfaceProtocol; + uaa.info.bIfaceIndex = i; + uaa.info.bIfaceNum = + iface->idesc->bInterfaceNumber; + uaa.use_generic = 0; + uaa.driver_info = 0; /* reset driver_info */ + + DPRINTFN(2, "iclass=%u/%u/%u iindex=%u/%u\n", + uaa.info.bInterfaceClass, + uaa.info.bInterfaceSubClass, + uaa.info.bInterfaceProtocol, + uaa.info.bIfaceIndex, + uaa.info.bIfaceNum); + + /* try specific interface drivers first */ + + if (usb_probe_and_attach_sub(udev, &uaa)) { + /* ignore */ + } + /* try generic interface drivers last */ + + uaa.use_generic = 1; + uaa.driver_info = 0; /* reset driver_info */ + + if (usb_probe_and_attach_sub(udev, &uaa)) { + /* ignore */ + } + } + + if (uaa.temp_dev) { + /* remove the last created child; it is unused */ + + if (device_delete_child(udev->parent_dev, uaa.temp_dev)) { + DPRINTFN(0, "device delete child failed\n"); + } + } +done: + if (do_unlock) + usbd_enum_unlock(udev); + + return (0); +} + +/*------------------------------------------------------------------------* + * usb_suspend_resume_sub + * + * This function is called when the suspend or resume methods should + * be executed on an USB device. + *------------------------------------------------------------------------*/ +static void +usb_suspend_resume_sub(struct usb_device *udev, device_t dev, uint8_t do_suspend) +{ + int err; + + if (dev == NULL) { + return; + } + if (!device_is_attached(dev)) { + return; + } + if (do_suspend) { + err = DEVICE_SUSPEND(dev); + } else { + err = DEVICE_RESUME(dev); + } + if (err) { + device_printf(dev, "%s failed\n", + do_suspend ? "Suspend" : "Resume"); + } +} + +/*------------------------------------------------------------------------* + * usb_suspend_resume + * + * The following function will suspend or resume the USB device. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usb_suspend_resume(struct usb_device *udev, uint8_t do_suspend) +{ + struct usb_interface *iface; + uint8_t i; + + if (udev == NULL) { + /* nothing to do */ + return (0); + } + DPRINTFN(4, "udev=%p do_suspend=%d\n", udev, do_suspend); + + sx_assert(&udev->sr_sx, SA_LOCKED); + + USB_BUS_LOCK(udev->bus); + /* filter the suspend events */ + if (udev->flags.peer_suspended == do_suspend) { + USB_BUS_UNLOCK(udev->bus); + /* nothing to do */ + return (0); + } + udev->flags.peer_suspended = do_suspend; + USB_BUS_UNLOCK(udev->bus); + + /* do the suspend or resume */ + + for (i = 0; i != USB_IFACE_MAX; i++) { + + iface = usbd_get_iface(udev, i); + if (iface == NULL) { + /* looks like the end of the USB interfaces */ + break; + } + usb_suspend_resume_sub(udev, iface->subdev, do_suspend); + } + return (0); +} + +/*------------------------------------------------------------------------* + * usbd_clear_stall_proc + * + * This function performs generic USB clear stall operations. + *------------------------------------------------------------------------*/ +static void +usbd_clear_stall_proc(struct usb_proc_msg *_pm) +{ + struct usb_clear_stall_msg *pm = (void *)_pm; + struct usb_device *udev = pm->udev; + + /* Change lock */ + USB_BUS_UNLOCK(udev->bus); + mtx_lock(&udev->device_mtx); + + /* Start clear stall callback */ + usbd_transfer_start(udev->ctrl_xfer[1]); + + /* Change lock */ + mtx_unlock(&udev->device_mtx); + USB_BUS_LOCK(udev->bus); +} + +/*------------------------------------------------------------------------* + * usb_alloc_device + * + * This function allocates a new USB device. This function is called + * when a new device has been put in the powered state, but not yet in + * the addressed state. Get initial descriptor, set the address, get + * full descriptor and get strings. + * + * Return values: + * 0: Failure + * Else: Success + *------------------------------------------------------------------------*/ +struct usb_device * +usb_alloc_device(device_t parent_dev, struct usb_bus *bus, + struct usb_device *parent_hub, uint8_t depth, uint8_t port_index, + uint8_t port_no, enum usb_dev_speed speed, enum usb_hc_mode mode) +{ + struct usb_attach_arg uaa; + struct usb_device *udev; + struct usb_device *adev; + struct usb_device *hub; + uint8_t *scratch_ptr; + size_t scratch_size; + usb_error_t err; + uint8_t device_index; + uint8_t config_index; + uint8_t config_quirk; + uint8_t set_config_failed; + + DPRINTF("parent_dev=%p, bus=%p, parent_hub=%p, depth=%u, " + "port_index=%u, port_no=%u, speed=%u, usb_mode=%u\n", + parent_dev, bus, parent_hub, depth, port_index, port_no, + speed, mode); + + /* + * Find an unused device index. In USB Host mode this is the + * same as the device address. + * + * Device index zero is not used and device index 1 should + * always be the root hub. + */ + for (device_index = USB_ROOT_HUB_ADDR; + (device_index != bus->devices_max) && + (bus->devices[device_index] != NULL); + device_index++) /* nop */; + + if (device_index == bus->devices_max) { + device_printf(bus->bdev, + "No free USB device index for new device\n"); + return (NULL); + } + + if (depth > 0x10) { + device_printf(bus->bdev, + "Invalid device depth\n"); + return (NULL); + } + udev = malloc(sizeof(*udev), M_USB, M_WAITOK | M_ZERO); + if (udev == NULL) { + return (NULL); + } + /* initialise our SX-lock */ + sx_init_flags(&udev->ctrl_sx, "USB device SX lock", SX_DUPOK); + + /* initialise our SX-lock */ + sx_init_flags(&udev->enum_sx, "USB config SX lock", SX_DUPOK); + sx_init_flags(&udev->sr_sx, "USB suspend and resume SX lock", SX_DUPOK); + + cv_init(&udev->ctrlreq_cv, "WCTRL"); + cv_init(&udev->ref_cv, "UGONE"); + + /* initialise our mutex */ + mtx_init(&udev->device_mtx, "USB device mutex", NULL, MTX_DEF); + + /* initialise generic clear stall */ + udev->cs_msg[0].hdr.pm_callback = &usbd_clear_stall_proc; + udev->cs_msg[0].udev = udev; + udev->cs_msg[1].hdr.pm_callback = &usbd_clear_stall_proc; + udev->cs_msg[1].udev = udev; + + /* initialise some USB device fields */ + udev->parent_hub = parent_hub; + udev->parent_dev = parent_dev; + udev->port_index = port_index; + udev->port_no = port_no; + udev->depth = depth; + udev->bus = bus; + udev->address = USB_START_ADDR; /* default value */ + udev->plugtime = (usb_ticks_t)ticks; + /* + * We need to force the power mode to "on" because there are plenty + * of USB devices out there that do not work very well with + * automatic suspend and resume! + */ + udev->power_mode = usbd_filter_power_mode(udev, USB_POWER_MODE_ON); + udev->pwr_save.last_xfer_time = ticks; + /* we are not ready yet */ + udev->refcount = 1; + + /* set up default endpoint descriptor */ + udev->ctrl_ep_desc.bLength = sizeof(udev->ctrl_ep_desc); + udev->ctrl_ep_desc.bDescriptorType = UDESC_ENDPOINT; + udev->ctrl_ep_desc.bEndpointAddress = USB_CONTROL_ENDPOINT; + udev->ctrl_ep_desc.bmAttributes = UE_CONTROL; + udev->ctrl_ep_desc.wMaxPacketSize[0] = USB_MAX_IPACKET; + udev->ctrl_ep_desc.wMaxPacketSize[1] = 0; + udev->ctrl_ep_desc.bInterval = 0; + + /* set up default endpoint companion descriptor */ + udev->ctrl_ep_comp_desc.bLength = sizeof(udev->ctrl_ep_comp_desc); + udev->ctrl_ep_comp_desc.bDescriptorType = UDESC_ENDPOINT_SS_COMP; + + udev->ddesc.bMaxPacketSize = USB_MAX_IPACKET; + + udev->speed = speed; + udev->flags.usb_mode = mode; + + /* search for our High Speed USB HUB, if any */ + + adev = udev; + hub = udev->parent_hub; + + while (hub) { + if (hub->speed == USB_SPEED_HIGH) { + udev->hs_hub_addr = hub->address; + udev->parent_hs_hub = hub; + udev->hs_port_no = adev->port_no; + break; + } + adev = hub; + hub = hub->parent_hub; + } + + /* init the default endpoint */ + usb_init_endpoint(udev, 0, + &udev->ctrl_ep_desc, + &udev->ctrl_ep_comp_desc, + &udev->ctrl_ep); + + /* set device index */ + udev->device_index = device_index; + +#if USB_HAVE_UGEN + /* Create ugen name */ + snprintf(udev->ugen_name, sizeof(udev->ugen_name), + USB_GENERIC_NAME "%u.%u", device_get_unit(bus->bdev), + device_index); + LIST_INIT(&udev->pd_list); + + /* Create the control endpoint device */ + udev->ctrl_dev = usb_make_dev(udev, 0, FREAD|FWRITE); + + /* Create a link from /dev/ugenX.X to the default endpoint */ + make_dev_alias(udev->ctrl_dev, "%s", udev->ugen_name); +#endif + /* Initialise device */ + if (bus->methods->device_init != NULL) { + err = (bus->methods->device_init) (udev); + if (err != 0) { + DPRINTFN(0, "device init %d failed " + "(%s, ignored)\n", device_index, + usbd_errstr(err)); + goto done; + } + } + /* set powered device state after device init is complete */ + usb_set_device_state(udev, USB_STATE_POWERED); + + if (udev->flags.usb_mode == USB_MODE_HOST) { + + err = usbd_req_set_address(udev, NULL, device_index); + + /* + * This is the new USB device address from now on, if + * the set address request didn't set it already. + */ + if (udev->address == USB_START_ADDR) + udev->address = device_index; + + /* + * We ignore any set-address errors, hence there are + * buggy USB devices out there that actually receive + * the SETUP PID, but manage to set the address before + * the STATUS stage is ACK'ed. If the device responds + * to the subsequent get-descriptor at the new + * address, then we know that the set-address command + * was successful. + */ + if (err) { + DPRINTFN(0, "set address %d failed " + "(%s, ignored)\n", udev->address, + usbd_errstr(err)); + } + } else { + /* We are not self powered */ + udev->flags.self_powered = 0; + + /* Set unconfigured state */ + udev->curr_config_no = USB_UNCONFIG_NO; + udev->curr_config_index = USB_UNCONFIG_INDEX; + + /* Setup USB descriptors */ + err = (usb_temp_setup_by_index_p) (udev, usb_template); + if (err) { + DPRINTFN(0, "setting up USB template failed maybe the USB " + "template module has not been loaded\n"); + goto done; + } + } + usb_set_device_state(udev, USB_STATE_ADDRESSED); + + /* setup the device descriptor and the initial "wMaxPacketSize" */ + err = usbd_setup_device_desc(udev, NULL); + + if (err != 0) { + /* XXX try to re-enumerate the device */ + err = usbd_req_re_enumerate(udev, NULL); + if (err) + goto done; + } + + /* + * Setup temporary USB attach args so that we can figure out some + * basic quirks for this device. + */ + usb_init_attach_arg(udev, &uaa); + + if (usb_test_quirk(&uaa, UQ_BUS_POWERED)) { + udev->flags.uq_bus_powered = 1; + } + if (usb_test_quirk(&uaa, UQ_NO_STRINGS)) { + udev->flags.no_strings = 1; + } + /* + * Workaround for buggy USB devices. + * + * It appears that some string-less USB chips will crash and + * disappear if any attempts are made to read any string + * descriptors. + * + * Try to detect such chips by checking the strings in the USB + * device descriptor. If no strings are present there we + * simply disable all USB strings. + */ + scratch_ptr = udev->bus->scratch[0].data; + scratch_size = sizeof(udev->bus->scratch[0].data); + + if (udev->ddesc.iManufacturer || + udev->ddesc.iProduct || + udev->ddesc.iSerialNumber) { + /* read out the language ID string */ + err = usbd_req_get_string_desc(udev, NULL, + (char *)scratch_ptr, 4, 0, USB_LANGUAGE_TABLE); + } else { + err = USB_ERR_INVAL; + } + + if (err || (scratch_ptr[0] < 4)) { + udev->flags.no_strings = 1; + } else { + uint16_t langid; + uint16_t pref; + uint16_t mask; + uint8_t x; + + /* load preferred value and mask */ + pref = usb_lang_id; + mask = usb_lang_mask; + + /* align length correctly */ + scratch_ptr[0] &= ~1; + + /* fix compiler warning */ + langid = 0; + + /* search for preferred language */ + for (x = 2; (x < scratch_ptr[0]); x += 2) { + langid = UGETW(scratch_ptr + x); + if ((langid & mask) == pref) + break; + } + if (x >= scratch_ptr[0]) { + /* pick the first language as the default */ + DPRINTFN(1, "Using first language\n"); + langid = UGETW(scratch_ptr + 2); + } + + DPRINTFN(1, "Language selected: 0x%04x\n", langid); + udev->langid = langid; + } + + /* assume 100mA bus powered for now. Changed when configured. */ + udev->power = USB_MIN_POWER; + /* fetch the vendor and product strings from the device */ + usbd_set_device_strings(udev); + + if (udev->flags.usb_mode == USB_MODE_DEVICE) { + /* USB device mode setup is complete */ + err = 0; + goto config_done; + } + + /* + * Most USB devices should attach to config index 0 by + * default + */ + if (usb_test_quirk(&uaa, UQ_CFG_INDEX_0)) { + config_index = 0; + config_quirk = 1; + } else if (usb_test_quirk(&uaa, UQ_CFG_INDEX_1)) { + config_index = 1; + config_quirk = 1; + } else if (usb_test_quirk(&uaa, UQ_CFG_INDEX_2)) { + config_index = 2; + config_quirk = 1; + } else if (usb_test_quirk(&uaa, UQ_CFG_INDEX_3)) { + config_index = 3; + config_quirk = 1; + } else if (usb_test_quirk(&uaa, UQ_CFG_INDEX_4)) { + config_index = 4; + config_quirk = 1; + } else { + config_index = 0; + config_quirk = 0; + } + + set_config_failed = 0; +repeat_set_config: + + DPRINTF("setting config %u\n", config_index); + + /* get the USB device configured */ + err = usbd_set_config_index(udev, config_index); + if (err) { + if (udev->ddesc.bNumConfigurations != 0) { + if (!set_config_failed) { + set_config_failed = 1; + /* XXX try to re-enumerate the device */ + err = usbd_req_re_enumerate(udev, NULL); + if (err == 0) + goto repeat_set_config; + } + DPRINTFN(0, "Failure selecting configuration index %u:" + "%s, port %u, addr %u (ignored)\n", + config_index, usbd_errstr(err), udev->port_no, + udev->address); + } + /* + * Some USB devices do not have any configurations. Ignore any + * set config failures! + */ + err = 0; + goto config_done; + } + if (!config_quirk && config_index + 1 < udev->ddesc.bNumConfigurations) { + if ((udev->cdesc->bNumInterface < 2) && + usbd_get_no_descriptors(udev->cdesc, UDESC_ENDPOINT) == 0) { + DPRINTFN(0, "Found no endpoints, trying next config\n"); + config_index++; + goto repeat_set_config; + } + if (config_index == 0) { + /* + * Try to figure out if we have an + * auto-install disk there: + */ + if (usb_iface_is_cdrom(udev, 0)) { + DPRINTFN(0, "Found possible auto-install " + "disk (trying next config)\n"); + config_index++; + goto repeat_set_config; + } + } + } +#ifndef __rtems__ + EVENTHANDLER_INVOKE(usb_dev_configured, udev, &uaa); +#endif /* __rtems__ */ + if (uaa.dev_state != UAA_DEV_READY) { + /* leave device unconfigured */ + usb_unconfigure(udev, 0); + } + +config_done: + DPRINTF("new dev (addr %d), udev=%p, parent_hub=%p\n", + udev->address, udev, udev->parent_hub); + + /* register our device - we are ready */ + usb_bus_port_set_device(bus, parent_hub ? + parent_hub->hub->ports + port_index : NULL, udev, device_index); + +#if USB_HAVE_UGEN + /* Symlink the ugen device name */ + udev->ugen_symlink = usb_alloc_symlink(udev->ugen_name); + + /* Announce device */ + printf("%s: <%s> at %s\n", udev->ugen_name, + usb_get_manufacturer(udev), + device_get_nameunit(udev->bus->bdev)); + + usb_notify_addq("ATTACH", udev); +#endif +done: + if (err) { + /* + * Free USB device and all subdevices, if any. + */ + usb_free_device(udev, 0); + udev = NULL; + } + return (udev); +} + +#if USB_HAVE_UGEN +static struct cdev * +usb_make_dev(struct usb_device *udev, int ep, int mode) +{ + struct usb_fs_privdata* pd; + char devname[20]; + + /* Store information to locate ourselves again later */ + pd = malloc(sizeof(struct usb_fs_privdata), M_USBDEV, + M_WAITOK | M_ZERO); + pd->bus_index = device_get_unit(udev->bus->bdev); + pd->dev_index = udev->device_index; + pd->ep_addr = ep; + pd->mode = mode; + + /* Now, create the device itself */ + snprintf(devname, sizeof(devname), "%u.%u.%u", + pd->bus_index, pd->dev_index, pd->ep_addr); + pd->cdev = make_dev(&usb_devsw, 0, UID_ROOT, + GID_OPERATOR, 0600, USB_DEVICE_DIR "/%s", devname); + pd->cdev->si_drv1 = pd; + + return (pd->cdev); +} + +static void +usb_cdev_create(struct usb_device *udev) +{ + struct usb_config_descriptor *cd; + struct usb_endpoint_descriptor *ed; + struct usb_descriptor *desc; + struct usb_fs_privdata* pd; + struct cdev *dev; + int inmode, outmode, inmask, outmask, mode; + uint8_t ep; + + KASSERT(LIST_FIRST(&udev->pd_list) == NULL, ("stale cdev entries")); + + DPRINTFN(2, "Creating device nodes\n"); + + if (usbd_get_mode(udev) == USB_MODE_DEVICE) { + inmode = FWRITE; + outmode = FREAD; + } else { /* USB_MODE_HOST */ + inmode = FREAD; + outmode = FWRITE; + } + + inmask = 0; + outmask = 0; + desc = NULL; + + /* + * Collect all used endpoint numbers instead of just + * generating 16 static endpoints. + */ + cd = usbd_get_config_descriptor(udev); + while ((desc = usb_desc_foreach(cd, desc))) { + /* filter out all endpoint descriptors */ + if ((desc->bDescriptorType == UDESC_ENDPOINT) && + (desc->bLength >= sizeof(*ed))) { + ed = (struct usb_endpoint_descriptor *)desc; + + /* update masks */ + ep = ed->bEndpointAddress; + if (UE_GET_DIR(ep) == UE_DIR_OUT) + outmask |= 1 << UE_GET_ADDR(ep); + else + inmask |= 1 << UE_GET_ADDR(ep); + } + } + + /* Create all available endpoints except EP0 */ + for (ep = 1; ep < 16; ep++) { + mode = inmask & (1 << ep) ? inmode : 0; + mode |= outmask & (1 << ep) ? outmode : 0; + if (mode == 0) + continue; /* no IN or OUT endpoint */ + + dev = usb_make_dev(udev, ep, mode); + pd = dev->si_drv1; + LIST_INSERT_HEAD(&udev->pd_list, pd, pd_next); + } +} + +static void +usb_cdev_free(struct usb_device *udev) +{ + struct usb_fs_privdata* pd; + struct cdev* pcdev; + + DPRINTFN(2, "Freeing device nodes\n"); + + while ((pd = LIST_FIRST(&udev->pd_list)) != NULL) { + KASSERT(pd->cdev->si_drv1 == pd, ("privdata corrupt")); + + pcdev = pd->cdev; + pd->cdev = NULL; + LIST_REMOVE(pd, pd_next); + if (pcdev != NULL) + destroy_dev_sched_cb(pcdev, usb_cdev_cleanup, pd); + } +} + +static void +usb_cdev_cleanup(void* arg) +{ + free(arg, M_USBDEV); +} +#endif + +/*------------------------------------------------------------------------* + * usb_free_device + * + * This function is NULL safe and will free an USB device and its + * children devices, if any. + * + * Flag values: Reserved, set to zero. + *------------------------------------------------------------------------*/ +void +usb_free_device(struct usb_device *udev, uint8_t flag) +{ + struct usb_bus *bus; + + if (udev == NULL) + return; /* already freed */ + + DPRINTFN(4, "udev=%p port=%d\n", udev, udev->port_no); + + bus = udev->bus; + usb_set_device_state(udev, USB_STATE_DETACHED); + +#if USB_HAVE_UGEN + usb_notify_addq("DETACH", udev); + + printf("%s: <%s> at %s (disconnected)\n", udev->ugen_name, + usb_get_manufacturer(udev), device_get_nameunit(bus->bdev)); + + /* Destroy UGEN symlink, if any */ + if (udev->ugen_symlink) { + usb_free_symlink(udev->ugen_symlink); + udev->ugen_symlink = NULL; + } +#endif + /* + * Unregister our device first which will prevent any further + * references: + */ + usb_bus_port_set_device(bus, udev->parent_hub ? + udev->parent_hub->hub->ports + udev->port_index : NULL, + NULL, USB_ROOT_HUB_ADDR); + +#if USB_HAVE_UGEN + /* wait for all pending references to go away: */ + mtx_lock(&usb_ref_lock); + udev->refcount--; + while (udev->refcount != 0) { + cv_wait(&udev->ref_cv, &usb_ref_lock); + } + mtx_unlock(&usb_ref_lock); + + destroy_dev_sched_cb(udev->ctrl_dev, usb_cdev_cleanup, + udev->ctrl_dev->si_drv1); +#endif + + if (udev->flags.usb_mode == USB_MODE_DEVICE) { + /* stop receiving any control transfers (Device Side Mode) */ + usbd_transfer_unsetup(udev->ctrl_xfer, USB_CTRL_XFER_MAX); + } + + /* the following will get the device unconfigured in software */ + usb_unconfigure(udev, USB_UNCFG_FLAG_FREE_EP0); + + /* unsetup any leftover default USB transfers */ + usbd_transfer_unsetup(udev->ctrl_xfer, USB_CTRL_XFER_MAX); + + /* template unsetup, if any */ + (usb_temp_unsetup_p) (udev); + + /* + * Make sure that our clear-stall messages are not queued + * anywhere: + */ + USB_BUS_LOCK(udev->bus); + usb_proc_mwait(&udev->bus->non_giant_callback_proc, + &udev->cs_msg[0], &udev->cs_msg[1]); + USB_BUS_UNLOCK(udev->bus); + + sx_destroy(&udev->ctrl_sx); + sx_destroy(&udev->enum_sx); + sx_destroy(&udev->sr_sx); + + cv_destroy(&udev->ctrlreq_cv); + cv_destroy(&udev->ref_cv); + + mtx_destroy(&udev->device_mtx); +#if USB_HAVE_UGEN + KASSERT(LIST_FIRST(&udev->pd_list) == NULL, ("leaked cdev entries")); +#endif + + /* Uninitialise device */ + if (bus->methods->device_uninit != NULL) + (bus->methods->device_uninit) (udev); + + /* free device */ + free(udev->serial, M_USB); + free(udev->manufacturer, M_USB); + free(udev->product, M_USB); + free(udev, M_USB); +} + +/*------------------------------------------------------------------------* + * usbd_get_iface + * + * This function is the safe way to get the USB interface structure + * pointer by interface index. + * + * Return values: + * NULL: Interface not present. + * Else: Pointer to USB interface structure. + *------------------------------------------------------------------------*/ +struct usb_interface * +usbd_get_iface(struct usb_device *udev, uint8_t iface_index) +{ + struct usb_interface *iface = udev->ifaces + iface_index; + + if (iface_index >= udev->ifaces_max) + return (NULL); + return (iface); +} + +/*------------------------------------------------------------------------* + * usbd_find_descriptor + * + * This function will lookup the first descriptor that matches the + * criteria given by the arguments "type" and "subtype". Descriptors + * will only be searched within the interface having the index + * "iface_index". If the "id" argument points to an USB descriptor, + * it will be skipped before the search is started. This allows + * searching for multiple descriptors using the same criteria. Else + * the search is started after the interface descriptor. + * + * Return values: + * NULL: End of descriptors + * Else: A descriptor matching the criteria + *------------------------------------------------------------------------*/ +void * +usbd_find_descriptor(struct usb_device *udev, void *id, uint8_t iface_index, + uint8_t type, uint8_t type_mask, + uint8_t subtype, uint8_t subtype_mask) +{ + struct usb_descriptor *desc; + struct usb_config_descriptor *cd; + struct usb_interface *iface; + + cd = usbd_get_config_descriptor(udev); + if (cd == NULL) { + return (NULL); + } + if (id == NULL) { + iface = usbd_get_iface(udev, iface_index); + if (iface == NULL) { + return (NULL); + } + id = usbd_get_interface_descriptor(iface); + if (id == NULL) { + return (NULL); + } + } + desc = (void *)id; + + while ((desc = usb_desc_foreach(cd, desc))) { + + if (desc->bDescriptorType == UDESC_INTERFACE) { + break; + } + if (((desc->bDescriptorType & type_mask) == type) && + ((desc->bDescriptorSubtype & subtype_mask) == subtype)) { + return (desc); + } + } + return (NULL); +} + +/*------------------------------------------------------------------------* + * usb_devinfo + * + * This function will dump information from the device descriptor + * belonging to the USB device pointed to by "udev", to the string + * pointed to by "dst_ptr" having a maximum length of "dst_len" bytes + * including the terminating zero. + *------------------------------------------------------------------------*/ +void +usb_devinfo(struct usb_device *udev, char *dst_ptr, uint16_t dst_len) +{ + struct usb_device_descriptor *udd = &udev->ddesc; + uint16_t bcdDevice; + uint16_t bcdUSB; + + bcdUSB = UGETW(udd->bcdUSB); + bcdDevice = UGETW(udd->bcdDevice); + + if (udd->bDeviceClass != 0xFF) { + snprintf(dst_ptr, dst_len, "%s %s, class %d/%d, rev %x.%02x/" + "%x.%02x, addr %d", + usb_get_manufacturer(udev), + usb_get_product(udev), + udd->bDeviceClass, udd->bDeviceSubClass, + (bcdUSB >> 8), bcdUSB & 0xFF, + (bcdDevice >> 8), bcdDevice & 0xFF, + udev->address); + } else { + snprintf(dst_ptr, dst_len, "%s %s, rev %x.%02x/" + "%x.%02x, addr %d", + usb_get_manufacturer(udev), + usb_get_product(udev), + (bcdUSB >> 8), bcdUSB & 0xFF, + (bcdDevice >> 8), bcdDevice & 0xFF, + udev->address); + } +} + +#ifdef USB_VERBOSE +/* + * Descriptions of of known vendors and devices ("products"). + */ +struct usb_knowndev { + uint16_t vendor; + uint16_t product; + uint32_t flags; + const char *vendorname; + const char *productname; +}; + +#define USB_KNOWNDEV_NOPROD 0x01 /* match on vendor only */ + +#include <freebsd/local/usbdevs.h> +#include <freebsd/local/usbdevs_data.h> +#endif /* USB_VERBOSE */ + +static void +usbd_set_device_strings(struct usb_device *udev) +{ + struct usb_device_descriptor *udd = &udev->ddesc; +#ifdef USB_VERBOSE + const struct usb_knowndev *kdp; +#endif + char *temp_ptr; + size_t temp_size; + uint16_t vendor_id; + uint16_t product_id; + + temp_ptr = (char *)udev->bus->scratch[0].data; + temp_size = sizeof(udev->bus->scratch[0].data); + + vendor_id = UGETW(udd->idVendor); + product_id = UGETW(udd->idProduct); + + /* get serial number string */ + usbd_req_get_string_any(udev, NULL, temp_ptr, temp_size, + udev->ddesc.iSerialNumber); + udev->serial = strdup(temp_ptr, M_USB); + + /* get manufacturer string */ + usbd_req_get_string_any(udev, NULL, temp_ptr, temp_size, + udev->ddesc.iManufacturer); + usb_trim_spaces(temp_ptr); + if (temp_ptr[0] != '\0') + udev->manufacturer = strdup(temp_ptr, M_USB); + + /* get product string */ + usbd_req_get_string_any(udev, NULL, temp_ptr, temp_size, + udev->ddesc.iProduct); + usb_trim_spaces(temp_ptr); + if (temp_ptr[0] != '\0') + udev->product = strdup(temp_ptr, M_USB); + +#ifdef USB_VERBOSE + if (udev->manufacturer == NULL || udev->product == NULL) { + for (kdp = usb_knowndevs; kdp->vendorname != NULL; kdp++) { + if (kdp->vendor == vendor_id && + (kdp->product == product_id || + (kdp->flags & USB_KNOWNDEV_NOPROD) != 0)) + break; + } + if (kdp->vendorname != NULL) { + /* XXX should use pointer to knowndevs string */ + if (udev->manufacturer == NULL) { + udev->manufacturer = strdup(kdp->vendorname, + M_USB); + } + if (udev->product == NULL && + (kdp->flags & USB_KNOWNDEV_NOPROD) == 0) { + udev->product = strdup(kdp->productname, + M_USB); + } + } + } +#endif + /* Provide default strings if none were found */ + if (udev->manufacturer == NULL) { + snprintf(temp_ptr, temp_size, "vendor 0x%04x", vendor_id); + udev->manufacturer = strdup(temp_ptr, M_USB); + } + if (udev->product == NULL) { + snprintf(temp_ptr, temp_size, "product 0x%04x", product_id); + udev->product = strdup(temp_ptr, M_USB); + } +} + +/* + * Returns: + * See: USB_MODE_XXX + */ +enum usb_hc_mode +usbd_get_mode(struct usb_device *udev) +{ + return (udev->flags.usb_mode); +} + +/* + * Returns: + * See: USB_SPEED_XXX + */ +enum usb_dev_speed +usbd_get_speed(struct usb_device *udev) +{ + return (udev->speed); +} + +uint32_t +usbd_get_isoc_fps(struct usb_device *udev) +{ + ; /* indent fix */ + switch (udev->speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + return (1000); + default: + return (8000); + } +} + +struct usb_device_descriptor * +usbd_get_device_descriptor(struct usb_device *udev) +{ + if (udev == NULL) + return (NULL); /* be NULL safe */ + return (&udev->ddesc); +} + +struct usb_config_descriptor * +usbd_get_config_descriptor(struct usb_device *udev) +{ + if (udev == NULL) + return (NULL); /* be NULL safe */ + return (udev->cdesc); +} + +/*------------------------------------------------------------------------* + * usb_test_quirk - test a device for a given quirk + * + * Return values: + * 0: The USB device does not have the given quirk. + * Else: The USB device has the given quirk. + *------------------------------------------------------------------------*/ +uint8_t +usb_test_quirk(const struct usb_attach_arg *uaa, uint16_t quirk) +{ + uint8_t found; + + found = (usb_test_quirk_p) (&uaa->info, quirk); + return (found); +} + +struct usb_interface_descriptor * +usbd_get_interface_descriptor(struct usb_interface *iface) +{ + if (iface == NULL) + return (NULL); /* be NULL safe */ + return (iface->idesc); +} + +uint8_t +usbd_get_interface_altindex(struct usb_interface *iface) +{ + return (iface->alt_index); +} + +uint8_t +usbd_get_bus_index(struct usb_device *udev) +{ + return ((uint8_t)device_get_unit(udev->bus->bdev)); +} + +uint8_t +usbd_get_device_index(struct usb_device *udev) +{ + return (udev->device_index); +} + +#if USB_HAVE_UGEN +/*------------------------------------------------------------------------* + * usb_notify_addq + * + * This function will generate events for dev. + *------------------------------------------------------------------------*/ +#ifndef BURN_BRIDGES +static void +usb_notify_addq_compat(const char *type, struct usb_device *udev) +{ + char *data = NULL; + const char *ntype; + struct malloc_type *mt; + const size_t buf_size = 512; + + /* Convert notify type */ + if (strcmp(type, "ATTACH") == 0) + ntype = "+"; + else if (strcmp(type, "DETACH") == 0) + ntype = "-"; + else + return; + + mtx_lock(&malloc_mtx); + mt = malloc_desc2type("bus"); /* XXX M_BUS */ + mtx_unlock(&malloc_mtx); + if (mt == NULL) + return; + + data = malloc(buf_size, mt, M_NOWAIT); + if (data == NULL) + return; + + /* String it all together. */ + snprintf(data, buf_size, + "%s" + "%s " + "vendor=0x%04x " + "product=0x%04x " + "devclass=0x%02x " + "devsubclass=0x%02x " + "sernum=\"%s\" " + "release=0x%04x " + "at " + "port=%u " + "on " + "%s\n", + ntype, + udev->ugen_name, + UGETW(udev->ddesc.idVendor), + UGETW(udev->ddesc.idProduct), + udev->ddesc.bDeviceClass, + udev->ddesc.bDeviceSubClass, + usb_get_serial(udev), + UGETW(udev->ddesc.bcdDevice), + udev->port_no, + udev->parent_hub != NULL ? + udev->parent_hub->ugen_name : + device_get_nameunit(device_get_parent(udev->bus->bdev))); + + devctl_queue_data(data); +} +#endif + +static void +usb_notify_addq(const char *type, struct usb_device *udev) +{ + struct usb_interface *iface; + struct sbuf *sb; + int i; + +#ifndef BURN_BRIDGES + usb_notify_addq_compat(type, udev); +#endif + + /* announce the device */ + sb = sbuf_new_auto(); + sbuf_printf(sb, + "cdev=%s " + "vendor=0x%04x " + "product=0x%04x " + "devclass=0x%02x " + "devsubclass=0x%02x " + "sernum=\"%s\" " + "release=0x%04x " + "mode=%s " + "port=%u " + "parent=%s\n", + udev->ugen_name, + UGETW(udev->ddesc.idVendor), + UGETW(udev->ddesc.idProduct), + udev->ddesc.bDeviceClass, + udev->ddesc.bDeviceSubClass, + usb_get_serial(udev), + UGETW(udev->ddesc.bcdDevice), + (udev->flags.usb_mode == USB_MODE_HOST) ? "host" : "device", + udev->port_no, + udev->parent_hub != NULL ? + udev->parent_hub->ugen_name : + device_get_nameunit(device_get_parent(udev->bus->bdev))); + sbuf_finish(sb); + devctl_notify("USB", "DEVICE", type, sbuf_data(sb)); + sbuf_delete(sb); + + /* announce each interface */ + for (i = 0; i < USB_IFACE_MAX; i++) { + iface = usbd_get_iface(udev, i); + if (iface == NULL) + break; /* end of interfaces */ + if (iface->idesc == NULL) + continue; /* no interface descriptor */ + + sb = sbuf_new_auto(); + sbuf_printf(sb, + "cdev=%s " + "vendor=0x%04x " + "product=0x%04x " + "devclass=0x%02x " + "devsubclass=0x%02x " + "sernum=\"%s\" " + "release=0x%04x " + "mode=%s " + "interface=%d " + "endpoints=%d " + "intclass=0x%02x " + "intsubclass=0x%02x " + "intprotocol=0x%02x\n", + udev->ugen_name, + UGETW(udev->ddesc.idVendor), + UGETW(udev->ddesc.idProduct), + udev->ddesc.bDeviceClass, + udev->ddesc.bDeviceSubClass, + usb_get_serial(udev), + UGETW(udev->ddesc.bcdDevice), + (udev->flags.usb_mode == USB_MODE_HOST) ? "host" : "device", + iface->idesc->bInterfaceNumber, + iface->idesc->bNumEndpoints, + iface->idesc->bInterfaceClass, + iface->idesc->bInterfaceSubClass, + iface->idesc->bInterfaceProtocol); + sbuf_finish(sb); + devctl_notify("USB", "INTERFACE", type, sbuf_data(sb)); + sbuf_delete(sb); + } +} + +/*------------------------------------------------------------------------* + * usb_fifo_free_wrap + * + * This function will free the FIFOs. + * + * Description of "flag" argument: If the USB_UNCFG_FLAG_FREE_EP0 flag + * is set and "iface_index" is set to "USB_IFACE_INDEX_ANY", we free + * all FIFOs. If the USB_UNCFG_FLAG_FREE_EP0 flag is not set and + * "iface_index" is set to "USB_IFACE_INDEX_ANY", we free all non + * control endpoint FIFOs. If "iface_index" is not set to + * "USB_IFACE_INDEX_ANY" the flag has no effect. + *------------------------------------------------------------------------*/ +static void +usb_fifo_free_wrap(struct usb_device *udev, + uint8_t iface_index, uint8_t flag) +{ + struct usb_fifo *f; + uint16_t i; + + /* + * Free any USB FIFOs on the given interface: + */ + for (i = 0; i != USB_FIFO_MAX; i++) { + f = udev->fifo[i]; + if (f == NULL) { + continue; + } + /* Check if the interface index matches */ + if (iface_index == f->iface_index) { + if (f->methods != &usb_ugen_methods) { + /* + * Don't free any non-generic FIFOs in + * this case. + */ + continue; + } + if ((f->dev_ep_index == 0) && + (f->fs_xfer == NULL)) { + /* no need to free this FIFO */ + continue; + } + } else if (iface_index == USB_IFACE_INDEX_ANY) { + if ((f->methods == &usb_ugen_methods) && + (f->dev_ep_index == 0) && + (!(flag & USB_UNCFG_FLAG_FREE_EP0)) && + (f->fs_xfer == NULL)) { + /* no need to free this FIFO */ + continue; + } + } else { + /* no need to free this FIFO */ + continue; + } + /* free this FIFO */ + usb_fifo_free(f); + } +} +#endif + +/*------------------------------------------------------------------------* + * usb_peer_can_wakeup + * + * Return values: + * 0: Peer cannot do resume signalling. + * Else: Peer can do resume signalling. + *------------------------------------------------------------------------*/ +uint8_t +usb_peer_can_wakeup(struct usb_device *udev) +{ + const struct usb_config_descriptor *cdp; + + cdp = udev->cdesc; + if ((cdp != NULL) && (udev->flags.usb_mode == USB_MODE_HOST)) { + return (cdp->bmAttributes & UC_REMOTE_WAKEUP); + } + return (0); /* not supported */ +} + +void +usb_set_device_state(struct usb_device *udev, enum usb_dev_state state) +{ + + KASSERT(state < USB_STATE_MAX, ("invalid udev state")); + + DPRINTF("udev %p state %s -> %s\n", udev, + usb_statestr(udev->state), usb_statestr(state)); + udev->state = state; + + if (udev->bus->methods->device_state_change != NULL) + (udev->bus->methods->device_state_change) (udev); +} + +enum usb_dev_state +usb_get_device_state(struct usb_device *udev) +{ + if (udev == NULL) + return (USB_STATE_DETACHED); + return (udev->state); +} + +uint8_t +usbd_device_attached(struct usb_device *udev) +{ + return (udev->state > USB_STATE_DETACHED); +} + +/* The following function locks enumerating the given USB device. */ + +void +usbd_enum_lock(struct usb_device *udev) +{ + sx_xlock(&udev->enum_sx); + sx_xlock(&udev->sr_sx); + /* + * NEWBUS LOCK NOTE: We should check if any parent SX locks + * are locked before locking Giant. Else the lock can be + * locked multiple times. + */ + mtx_lock(&Giant); +} + +/* The following function unlocks enumerating the given USB device. */ + +void +usbd_enum_unlock(struct usb_device *udev) +{ + mtx_unlock(&Giant); + sx_xunlock(&udev->enum_sx); + sx_xunlock(&udev->sr_sx); +} + +/* The following function locks suspend and resume. */ + +void +usbd_sr_lock(struct usb_device *udev) +{ + sx_xlock(&udev->sr_sx); + /* + * NEWBUS LOCK NOTE: We should check if any parent SX locks + * are locked before locking Giant. Else the lock can be + * locked multiple times. + */ + mtx_lock(&Giant); +} + +/* The following function unlocks suspend and resume. */ + +void +usbd_sr_unlock(struct usb_device *udev) +{ + mtx_unlock(&Giant); + sx_xunlock(&udev->sr_sx); +} + +/* + * The following function checks the enumerating lock for the given + * USB device. + */ + +uint8_t +usbd_enum_is_locked(struct usb_device *udev) +{ + return (sx_xlocked(&udev->enum_sx)); +} diff --git a/freebsd/sys/dev/usb/usb_device.h b/freebsd/sys/dev/usb/usb_device.h new file mode 100644 index 00000000..eb6a3fcb --- /dev/null +++ b/freebsd/sys/dev/usb/usb_device.h @@ -0,0 +1,227 @@ +/* $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. + */ + +#ifndef _USB_DEVICE_HH_ +#define _USB_DEVICE_HH_ + +struct usb_symlink; /* UGEN */ +struct usb_device; /* linux compat */ + +#define USB_CTRL_XFER_MAX 2 + +/* "usb_parse_config()" commands */ + +#define USB_CFG_ALLOC 0 +#define USB_CFG_FREE 1 +#define USB_CFG_INIT 2 + +/* "usb_unconfigure()" flags */ + +#define USB_UNCFG_FLAG_NONE 0x00 +#define USB_UNCFG_FLAG_FREE_EP0 0x02 /* endpoint zero is freed */ + +struct usb_clear_stall_msg { + struct usb_proc_msg hdr; + struct usb_device *udev; +}; + +/* The following four structures makes up a tree, where we have the + * leaf structure, "usb_host_endpoint", first, and the root structure, + * "usb_device", last. The four structures below mirror the structure + * of the USB descriptors belonging to an USB configuration. Please + * refer to the USB specification for a definition of "endpoints" and + * "interfaces". + */ +struct usb_host_endpoint { + struct usb_endpoint_descriptor desc; + TAILQ_HEAD(, urb) bsd_urb_list; + struct usb_xfer *bsd_xfer[2]; + uint8_t *extra; /* Extra descriptors */ + usb_frlength_t fbsd_buf_size; + uint16_t extralen; + uint8_t bsd_iface_index; +} __aligned(USB_HOST_ALIGN); + +struct usb_host_interface { + struct usb_interface_descriptor desc; + /* the following array has size "desc.bNumEndpoint" */ + struct usb_host_endpoint *endpoint; + const char *string; /* iInterface string, if present */ + uint8_t *extra; /* Extra descriptors */ + uint16_t extralen; + uint8_t bsd_iface_index; +} __aligned(USB_HOST_ALIGN); + +/* + * The following structure defines the USB device flags. + */ +struct usb_device_flags { + enum usb_hc_mode usb_mode; /* host or device mode */ + uint8_t self_powered:1; /* set if USB device is self powered */ + uint8_t no_strings:1; /* set if USB device does not support + * strings */ + uint8_t remote_wakeup:1; /* set if remote wakeup is enabled */ + uint8_t uq_bus_powered:1; /* set if BUS powered quirk is present */ + + /* + * NOTE: Although the flags below will reach the same value + * over time, but the instant values may differ, and + * consequently the flags cannot be merged into one! + */ + uint8_t peer_suspended:1; /* set if peer is suspended */ + uint8_t self_suspended:1; /* set if self is suspended */ +}; + +/* + * The following structure is used for power-save purposes. The data + * in this structure is protected by the USB BUS lock. + */ +struct usb_power_save { + usb_ticks_t last_xfer_time; /* copy of "ticks" */ + usb_size_t type_refs[4]; /* transfer reference count */ + usb_size_t read_refs; /* data read references */ + usb_size_t write_refs; /* data write references */ +}; + +/* + * The following structure defines an USB device. There exists one of + * these structures for every USB device. + */ +struct usb_device { + struct usb_clear_stall_msg cs_msg[2]; /* generic clear stall + * messages */ + struct sx ctrl_sx; + struct sx enum_sx; + struct sx sr_sx; + struct mtx device_mtx; + struct cv ctrlreq_cv; + struct cv ref_cv; + struct usb_interface *ifaces; + struct usb_endpoint ctrl_ep; /* Control Endpoint 0 */ + struct usb_endpoint *endpoints; + struct usb_power_save pwr_save;/* power save data */ + struct usb_bus *bus; /* our USB BUS */ + device_t parent_dev; /* parent device */ + struct usb_device *parent_hub; + struct usb_device *parent_hs_hub; /* high-speed parent HUB */ + struct usb_config_descriptor *cdesc; /* full config descr */ + struct usb_hub *hub; /* only if this is a hub */ + struct usb_xfer *ctrl_xfer[USB_CTRL_XFER_MAX]; + struct usb_temp_data *usb_template_ptr; + struct usb_endpoint *ep_curr; /* current clear stall endpoint */ +#if USB_HAVE_UGEN + struct usb_fifo *fifo[USB_FIFO_MAX]; + struct usb_symlink *ugen_symlink; /* our generic symlink */ + struct cdev *ctrl_dev; /* Control Endpoint 0 device node */ + LIST_HEAD(,usb_fs_privdata) pd_list; + char ugen_name[20]; /* name of ugenX.X device */ +#endif + usb_ticks_t plugtime; /* copy of "ticks" */ + + enum usb_dev_state state; + enum usb_dev_speed speed; + uint16_t refcount; +#define USB_DEV_REF_MAX 0xffff + + uint16_t power; /* mA the device uses */ + uint16_t langid; /* language for strings */ + + uint8_t address; /* device addess */ + uint8_t device_index; /* device index in "bus->devices" */ + uint8_t controller_slot_id; /* controller specific value */ + uint8_t curr_config_index; /* current configuration index */ + uint8_t curr_config_no; /* current configuration number */ + uint8_t depth; /* distance from root HUB */ + uint8_t port_index; /* parent HUB port index */ + uint8_t port_no; /* parent HUB port number */ + uint8_t hs_hub_addr; /* high-speed HUB address */ + uint8_t hs_port_no; /* high-speed HUB port number */ + uint8_t driver_added_refcount; /* our driver added generation count */ + uint8_t power_mode; /* see USB_POWER_XXX */ + uint8_t re_enumerate_wait; /* set if re-enum. is in progress */ + uint8_t ifaces_max; /* number of interfaces present */ + uint8_t endpoints_max; /* number of endpoints present */ + + /* the "flags" field is write-protected by "bus->mtx" */ + + struct usb_device_flags flags; + + struct usb_endpoint_descriptor ctrl_ep_desc; /* for endpoint 0 */ + struct usb_endpoint_ss_comp_descriptor ctrl_ep_comp_desc; /* for endpoint 0 */ + struct usb_device_descriptor ddesc; /* device descriptor */ + + char *serial; /* serial number, can be NULL */ + char *manufacturer; /* manufacturer string, can be NULL */ + char *product; /* product string, can be NULL */ + +#if USB_HAVE_COMPAT_LINUX + /* Linux compat */ + struct usb_device_descriptor descriptor; + struct usb_host_endpoint ep0; + struct usb_interface *linux_iface_start; + struct usb_interface *linux_iface_end; + struct usb_host_endpoint *linux_endpoint_start; + struct usb_host_endpoint *linux_endpoint_end; + uint16_t devnum; +#endif +}; + +/* globals */ + +extern int usb_template; + +/* function prototypes */ + +const char *usb_statestr(enum usb_dev_state state); +struct usb_device *usb_alloc_device(device_t parent_dev, struct usb_bus *bus, + struct usb_device *parent_hub, uint8_t depth, + uint8_t port_index, uint8_t port_no, + enum usb_dev_speed speed, enum usb_hc_mode mode); +usb_error_t usb_probe_and_attach(struct usb_device *udev, + uint8_t iface_index); +void usb_detach_device(struct usb_device *, uint8_t, uint8_t); +usb_error_t usb_reset_iface_endpoints(struct usb_device *udev, + uint8_t iface_index); +usb_error_t usbd_set_config_index(struct usb_device *udev, uint8_t index); +usb_error_t usbd_set_endpoint_stall(struct usb_device *udev, + struct usb_endpoint *ep, uint8_t do_stall); +usb_error_t usb_suspend_resume(struct usb_device *udev, + uint8_t do_suspend); +void usb_devinfo(struct usb_device *udev, char *dst_ptr, uint16_t dst_len); +void usb_free_device(struct usb_device *, uint8_t); +void usb_linux_free_device(struct usb_device *dev); +uint8_t usb_peer_can_wakeup(struct usb_device *udev); +struct usb_endpoint *usb_endpoint_foreach(struct usb_device *udev, struct usb_endpoint *ep); +void usb_set_device_state(struct usb_device *, enum usb_dev_state); +enum usb_dev_state usb_get_device_state(struct usb_device *); + +void usbd_enum_lock(struct usb_device *); +void usbd_enum_unlock(struct usb_device *); +void usbd_sr_lock(struct usb_device *); +void usbd_sr_unlock(struct usb_device *); +uint8_t usbd_enum_is_locked(struct usb_device *); + +#endif /* _USB_DEVICE_HH_ */ diff --git a/freebsd/sys/dev/usb/usb_dynamic.c b/freebsd/sys/dev/usb/usb_dynamic.c new file mode 100644 index 00000000..7651f97e --- /dev/null +++ b/freebsd/sys/dev/usb/usb_dynamic.c @@ -0,0 +1,151 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/* $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. + */ + +#include <freebsd/sys/stdint.h> +#include <freebsd/sys/stddef.h> +#include <freebsd/sys/param.h> +#include <freebsd/sys/queue.h> +#include <freebsd/sys/types.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/bus.h> +#include <freebsd/sys/linker_set.h> +#include <freebsd/sys/module.h> +#include <freebsd/sys/lock.h> +#include <freebsd/sys/mutex.h> +#include <freebsd/sys/condvar.h> +#include <freebsd/sys/sysctl.h> +#include <freebsd/sys/sx.h> +#include <freebsd/sys/unistd.h> +#include <freebsd/sys/callout.h> +#include <freebsd/sys/malloc.h> +#include <freebsd/sys/priv.h> + +#include <freebsd/dev/usb/usb.h> +#include <freebsd/dev/usb/usbdi.h> + +#include <freebsd/dev/usb/usb_core.h> +#include <freebsd/dev/usb/usb_process.h> +#include <freebsd/dev/usb/usb_device.h> +#include <freebsd/dev/usb/usb_dynamic.h> + +/* function prototypes */ +static usb_handle_req_t usb_temp_get_desc_w; +static usb_temp_setup_by_index_t usb_temp_setup_by_index_w; +static usb_temp_unsetup_t usb_temp_unsetup_w; +static usb_test_quirk_t usb_test_quirk_w; +static usb_quirk_ioctl_t usb_quirk_ioctl_w; + +/* global variables */ +usb_handle_req_t *usb_temp_get_desc_p = &usb_temp_get_desc_w; +usb_temp_setup_by_index_t *usb_temp_setup_by_index_p = &usb_temp_setup_by_index_w; +usb_temp_unsetup_t *usb_temp_unsetup_p = &usb_temp_unsetup_w; +usb_test_quirk_t *usb_test_quirk_p = &usb_test_quirk_w; +usb_quirk_ioctl_t *usb_quirk_ioctl_p = &usb_quirk_ioctl_w; +devclass_t usb_devclass_ptr = NULL; + +static usb_error_t +usb_temp_setup_by_index_w(struct usb_device *udev, uint16_t index) +{ + return (USB_ERR_INVAL); +} + +static uint8_t +usb_test_quirk_w(const struct usbd_lookup_info *info, uint16_t quirk) +{ + return (0); /* no match */ +} + +static int +usb_quirk_ioctl_w(unsigned long cmd, caddr_t data, int fflag, struct thread *td) +{ + return (ENOIOCTL); +} + +static usb_error_t +usb_temp_get_desc_w(struct usb_device *udev, struct usb_device_request *req, const void **pPtr, uint16_t *pLength) +{ + /* stall */ + return (USB_ERR_STALLED); +} + +static void +usb_temp_unsetup_w(struct usb_device *udev) +{ + if (udev->usb_template_ptr) { + + free(udev->usb_template_ptr, M_USB); + + udev->usb_template_ptr = NULL; + } +} + +void +usb_quirk_unload(void *arg) +{ + /* reset function pointers */ + + usb_test_quirk_p = &usb_test_quirk_w; + usb_quirk_ioctl_p = &usb_quirk_ioctl_w; + + /* wait for CPU to exit the loaded functions, if any */ + + /* XXX this is a tradeoff */ + + pause("WAIT", hz); +} + +void +usb_temp_unload(void *arg) +{ + /* reset function pointers */ + + usb_temp_get_desc_p = &usb_temp_get_desc_w; + usb_temp_setup_by_index_p = &usb_temp_setup_by_index_w; + usb_temp_unsetup_p = &usb_temp_unsetup_w; + + /* wait for CPU to exit the loaded functions, if any */ + + /* XXX this is a tradeoff */ + + pause("WAIT", hz); +} + +void +usb_bus_unload(void *arg) +{ + /* reset function pointers */ + + usb_devclass_ptr = NULL; + + /* wait for CPU to exit the loaded functions, if any */ + + /* XXX this is a tradeoff */ + + pause("WAIT", hz); +} diff --git a/freebsd/sys/dev/usb/usb_dynamic.h b/freebsd/sys/dev/usb/usb_dynamic.h new file mode 100644 index 00000000..32fc8362 --- /dev/null +++ b/freebsd/sys/dev/usb/usb_dynamic.h @@ -0,0 +1,61 @@ +/* $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. + */ + +#ifndef _USB_DYNAMIC_HH_ +#define _USB_DYNAMIC_HH_ + +/* prototypes */ + +struct usb_device; +struct usbd_lookup_info; +struct usb_device_request; + +/* typedefs */ + +typedef usb_error_t (usb_temp_setup_by_index_t)(struct usb_device *udev, + uint16_t index); +typedef uint8_t (usb_test_quirk_t)(const struct usbd_lookup_info *info, + uint16_t quirk); +typedef int (usb_quirk_ioctl_t)(unsigned long cmd, caddr_t data, + int fflag, struct thread *td); +typedef void (usb_temp_unsetup_t)(struct usb_device *udev); + +/* global function pointers */ + +extern usb_handle_req_t *usb_temp_get_desc_p; +extern usb_temp_setup_by_index_t *usb_temp_setup_by_index_p; +extern usb_temp_unsetup_t *usb_temp_unsetup_p; +extern usb_test_quirk_t *usb_test_quirk_p; +extern usb_quirk_ioctl_t *usb_quirk_ioctl_p; +extern devclass_t usb_devclass_ptr; + +/* function prototypes */ + +void usb_temp_unload(void *); +void usb_quirk_unload(void *); +void usb_bus_unload(void *); + +#endif /* _USB_DYNAMIC_HH_ */ diff --git a/freebsd/sys/dev/usb/usb_endian.h b/freebsd/sys/dev/usb/usb_endian.h new file mode 100644 index 00000000..54c965d7 --- /dev/null +++ b/freebsd/sys/dev/usb/usb_endian.h @@ -0,0 +1,119 @@ +/* $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. + */ + +#ifndef _USB_ENDIAN_HH_ +#define _USB_ENDIAN_HH_ + +#include <freebsd/sys/stdint.h> +#include <freebsd/sys/endian.h> + +/* + * Declare the basic USB record types. USB records have an alignment + * of 1 byte and are always packed. + */ +typedef uint8_t uByte; +typedef uint8_t uWord[2]; +typedef uint8_t uDWord[4]; +typedef uint8_t uQWord[8]; + +/* + * Define a set of macros that can get and set data independent of + * CPU endianness and CPU alignment requirements: + */ +#define UGETB(w) \ + ((w)[0]) + +#define UGETW(w) \ + ((w)[0] | \ + (((uint16_t)((w)[1])) << 8)) + +#define UGETDW(w) \ + ((w)[0] | \ + (((uint16_t)((w)[1])) << 8) | \ + (((uint32_t)((w)[2])) << 16) | \ + (((uint32_t)((w)[3])) << 24)) + +#define UGETQW(w) \ + ((w)[0] | \ + (((uint16_t)((w)[1])) << 8) | \ + (((uint32_t)((w)[2])) << 16) | \ + (((uint32_t)((w)[3])) << 24) | \ + (((uint64_t)((w)[4])) << 32) | \ + (((uint64_t)((w)[5])) << 40) | \ + (((uint64_t)((w)[6])) << 48) | \ + (((uint64_t)((w)[7])) << 56)) + +#define USETB(w,v) do { \ + (w)[0] = (uint8_t)(v); \ +} while (0) + +#define USETW(w,v) do { \ + (w)[0] = (uint8_t)(v); \ + (w)[1] = (uint8_t)((v) >> 8); \ +} while (0) + +#define USETDW(w,v) do { \ + (w)[0] = (uint8_t)(v); \ + (w)[1] = (uint8_t)((v) >> 8); \ + (w)[2] = (uint8_t)((v) >> 16); \ + (w)[3] = (uint8_t)((v) >> 24); \ +} while (0) + +#define USETQW(w,v) do { \ + (w)[0] = (uint8_t)(v); \ + (w)[1] = (uint8_t)((v) >> 8); \ + (w)[2] = (uint8_t)((v) >> 16); \ + (w)[3] = (uint8_t)((v) >> 24); \ + (w)[4] = (uint8_t)((v) >> 32); \ + (w)[5] = (uint8_t)((v) >> 40); \ + (w)[6] = (uint8_t)((v) >> 48); \ + (w)[7] = (uint8_t)((v) >> 56); \ +} while (0) + +#define USETW2(w,b1,b0) do { \ + (w)[0] = (uint8_t)(b0); \ + (w)[1] = (uint8_t)(b1); \ +} while (0) + +#define USETW4(w,b3,b2,b1,b0) do { \ + (w)[0] = (uint8_t)(b0); \ + (w)[1] = (uint8_t)(b1); \ + (w)[2] = (uint8_t)(b2); \ + (w)[3] = (uint8_t)(b3); \ +} while (0) + +#define USETW8(w,b7,b6,b5,b4,b3,b2,b1,b0) do { \ + (w)[0] = (uint8_t)(b0); \ + (w)[1] = (uint8_t)(b1); \ + (w)[2] = (uint8_t)(b2); \ + (w)[3] = (uint8_t)(b3); \ + (w)[4] = (uint8_t)(b4); \ + (w)[5] = (uint8_t)(b5); \ + (w)[6] = (uint8_t)(b6); \ + (w)[7] = (uint8_t)(b7); \ +} while (0) + +#endif /* _USB_ENDIAN_HH_ */ diff --git a/freebsd/sys/dev/usb/usb_error.c b/freebsd/sys/dev/usb/usb_error.c new file mode 100644 index 00000000..b9798a15 --- /dev/null +++ b/freebsd/sys/dev/usb/usb_error.c @@ -0,0 +1,93 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/* $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. + */ + +#include <freebsd/sys/stdint.h> +#include <freebsd/sys/stddef.h> +#include <freebsd/sys/param.h> +#include <freebsd/sys/queue.h> +#include <freebsd/sys/types.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/bus.h> +#include <freebsd/sys/linker_set.h> +#include <freebsd/sys/module.h> +#include <freebsd/sys/lock.h> +#include <freebsd/sys/mutex.h> +#include <freebsd/sys/condvar.h> +#include <freebsd/sys/sysctl.h> +#include <freebsd/sys/sx.h> +#include <freebsd/sys/unistd.h> +#include <freebsd/sys/callout.h> +#include <freebsd/sys/malloc.h> +#include <freebsd/sys/priv.h> + +#include <freebsd/dev/usb/usb.h> +#include <freebsd/dev/usb/usbdi.h> + +static const char* usb_errstr_table[USB_ERR_MAX] = { + [USB_ERR_NORMAL_COMPLETION] = "USB_ERR_NORMAL_COMPLETION", + [USB_ERR_PENDING_REQUESTS] = "USB_ERR_PENDING_REQUESTS", + [USB_ERR_NOT_STARTED] = "USB_ERR_NOT_STARTED", + [USB_ERR_INVAL] = "USB_ERR_INVAL", + [USB_ERR_NOMEM] = "USB_ERR_NOMEM", + [USB_ERR_CANCELLED] = "USB_ERR_CANCELLED", + [USB_ERR_BAD_ADDRESS] = "USB_ERR_BAD_ADDRESS", + [USB_ERR_BAD_BUFSIZE] = "USB_ERR_BAD_BUFSIZE", + [USB_ERR_BAD_FLAG] = "USB_ERR_BAD_FLAG", + [USB_ERR_NO_CALLBACK] = "USB_ERR_NO_CALLBACK", + [USB_ERR_IN_USE] = "USB_ERR_IN_USE", + [USB_ERR_NO_ADDR] = "USB_ERR_NO_ADDR", + [USB_ERR_NO_PIPE] = "USB_ERR_NO_PIPE", + [USB_ERR_ZERO_NFRAMES] = "USB_ERR_ZERO_NFRAMES", + [USB_ERR_ZERO_MAXP] = "USB_ERR_ZERO_MAXP", + [USB_ERR_SET_ADDR_FAILED] = "USB_ERR_SET_ADDR_FAILED", + [USB_ERR_NO_POWER] = "USB_ERR_NO_POWER", + [USB_ERR_TOO_DEEP] = "USB_ERR_TOO_DEEP", + [USB_ERR_IOERROR] = "USB_ERR_IOERROR", + [USB_ERR_NOT_CONFIGURED] = "USB_ERR_NOT_CONFIGURED", + [USB_ERR_TIMEOUT] = "USB_ERR_TIMEOUT", + [USB_ERR_SHORT_XFER] = "USB_ERR_SHORT_XFER", + [USB_ERR_STALLED] = "USB_ERR_STALLED", + [USB_ERR_INTERRUPTED] = "USB_ERR_INTERRUPTED", + [USB_ERR_DMA_LOAD_FAILED] = "USB_ERR_DMA_LOAD_FAILED", + [USB_ERR_BAD_CONTEXT] = "USB_ERR_BAD_CONTEXT", + [USB_ERR_NO_ROOT_HUB] = "USB_ERR_NO_ROOT_HUB", + [USB_ERR_NO_INTR_THREAD] = "USB_ERR_NO_INTR_THREAD", + [USB_ERR_NOT_LOCKED] = "USB_ERR_NOT_LOCKED", +}; + +/*------------------------------------------------------------------------* + * usbd_errstr + * + * This function converts an USB error code into a string. + *------------------------------------------------------------------------*/ +const char * +usbd_errstr(usb_error_t err) +{ + return (err < USB_ERR_MAX ? usb_errstr_table[err] : "USB_ERR_UNKNOWN"); +} diff --git a/freebsd/sys/dev/usb/usb_freebsd.h b/freebsd/sys/dev/usb/usb_freebsd.h new file mode 100644 index 00000000..ed0f0356 --- /dev/null +++ b/freebsd/sys/dev/usb/usb_freebsd.h @@ -0,0 +1,69 @@ +/* $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. + */ + +/* + * Including this file is mandatory for all USB related c-files in the kernel. + */ + +#ifndef _USB_FREEBSD_HH_ +#define _USB_FREEBSD_HH_ + +/* Default USB configuration */ +#ifndef __rtems__ +#define USB_HAVE_UGEN 1 +#define USB_HAVE_BUSDMA 1 +#define USB_HAVE_COMPAT_LINUX 1 +#define USB_HAVE_USER_IO 1 +#define USB_HAVE_MBUF 1 +#define USB_HAVE_TT_SUPPORT 1 +#define USB_HAVE_POWERD 1 +#define USB_HAVE_MSCTEST 1 +#endif /* __rtems__ */ + +#define USB_TD_GET_PROC(td) (td)->td_proc +#define USB_PROC_GET_GID(td) (td)->p_pgid + +#define USB_HOST_ALIGN 8 /* bytes, must be power of two */ +#define USB_FS_ISOC_UFRAME_MAX 4 /* exclusive unit */ +#define USB_BUS_MAX 256 /* units */ +#define USB_MAX_DEVICES 128 /* units */ +#define USB_IFACE_MAX 32 /* units */ +#define USB_FIFO_MAX 128 /* units */ + +#define USB_MAX_FS_ISOC_FRAMES_PER_XFER (120) /* units */ +#define USB_MAX_HS_ISOC_FRAMES_PER_XFER (8*120) /* units */ + +#define USB_HUB_MAX_DEPTH 5 +#define USB_EP0_BUFSIZE 1024 /* bytes */ + +typedef uint32_t usb_timeout_t; /* milliseconds */ +typedef uint32_t usb_frlength_t; /* bytes */ +typedef uint32_t usb_frcount_t; /* units */ +typedef uint32_t usb_size_t; /* bytes */ +typedef uint32_t usb_ticks_t; /* system defined */ +typedef uint16_t usb_power_mask_t; /* see "USB_HW_POWER_XXX" */ + +#endif /* _USB_FREEBSD_HH_ */ diff --git a/freebsd/sys/dev/usb/usb_generic.c b/freebsd/sys/dev/usb/usb_generic.c new file mode 100644 index 00000000..89217a93 --- /dev/null +++ b/freebsd/sys/dev/usb/usb_generic.c @@ -0,0 +1,2239 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/* $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. + */ + +#include <freebsd/sys/stdint.h> +#include <freebsd/sys/stddef.h> +#include <freebsd/sys/param.h> +#include <freebsd/sys/queue.h> +#include <freebsd/sys/types.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/bus.h> +#include <freebsd/sys/linker_set.h> +#include <freebsd/sys/module.h> +#include <freebsd/sys/lock.h> +#include <freebsd/sys/mutex.h> +#include <freebsd/sys/condvar.h> +#include <freebsd/sys/sysctl.h> +#include <freebsd/sys/sx.h> +#include <freebsd/sys/unistd.h> +#include <freebsd/sys/callout.h> +#include <freebsd/sys/malloc.h> +#include <freebsd/sys/priv.h> +#include <freebsd/sys/conf.h> +#include <freebsd/sys/fcntl.h> + +#include <freebsd/dev/usb/usb.h> +#include <freebsd/dev/usb/usb_ioctl.h> +#include <freebsd/dev/usb/usbdi.h> +#include <freebsd/dev/usb/usbdi_util.h> + +#define USB_DEBUG_VAR ugen_debug + +#include <freebsd/dev/usb/usb_core.h> +#include <freebsd/dev/usb/usb_dev.h> +#include <freebsd/dev/usb/usb_mbuf.h> +#include <freebsd/dev/usb/usb_process.h> +#include <freebsd/dev/usb/usb_device.h> +#include <freebsd/dev/usb/usb_debug.h> +#include <freebsd/dev/usb/usb_request.h> +#include <freebsd/dev/usb/usb_busdma.h> +#include <freebsd/dev/usb/usb_util.h> +#include <freebsd/dev/usb/usb_hub.h> +#include <freebsd/dev/usb/usb_generic.h> +#include <freebsd/dev/usb/usb_transfer.h> + +#include <freebsd/dev/usb/usb_controller.h> +#include <freebsd/dev/usb/usb_bus.h> + +#if USB_HAVE_UGEN + +/* defines */ + +#define UGEN_BULK_FS_BUFFER_SIZE (64*32) /* bytes */ +#define UGEN_BULK_HS_BUFFER_SIZE (1024*32) /* bytes */ +#define UGEN_HW_FRAMES 50 /* number of milliseconds per transfer */ + +/* function prototypes */ + +static usb_callback_t ugen_read_clear_stall_callback; +static usb_callback_t ugen_write_clear_stall_callback; +static usb_callback_t ugen_ctrl_read_callback; +static usb_callback_t ugen_ctrl_write_callback; +static usb_callback_t ugen_isoc_read_callback; +static usb_callback_t ugen_isoc_write_callback; +static usb_callback_t ugen_ctrl_fs_callback; + +static usb_fifo_open_t ugen_open; +static usb_fifo_close_t ugen_close; +static usb_fifo_ioctl_t ugen_ioctl; +static usb_fifo_ioctl_t ugen_ioctl_post; +static usb_fifo_cmd_t ugen_start_read; +static usb_fifo_cmd_t ugen_start_write; +static usb_fifo_cmd_t ugen_stop_io; + +static int ugen_transfer_setup(struct usb_fifo *, + const struct usb_config *, uint8_t); +static int ugen_open_pipe_write(struct usb_fifo *); +static int ugen_open_pipe_read(struct usb_fifo *); +static int ugen_set_config(struct usb_fifo *, uint8_t); +static int ugen_set_interface(struct usb_fifo *, uint8_t, uint8_t); +static int ugen_get_cdesc(struct usb_fifo *, struct usb_gen_descriptor *); +static int ugen_get_sdesc(struct usb_fifo *, struct usb_gen_descriptor *); +static int ugen_get_iface_driver(struct usb_fifo *f, struct usb_gen_descriptor *ugd); +static int usb_gen_fill_deviceinfo(struct usb_fifo *, + struct usb_device_info *); +static int ugen_re_enumerate(struct usb_fifo *); +static int ugen_iface_ioctl(struct usb_fifo *, u_long, void *, int); +static uint8_t ugen_fs_get_complete(struct usb_fifo *, uint8_t *); +static int ugen_fs_uninit(struct usb_fifo *f); + +/* structures */ + +struct usb_fifo_methods usb_ugen_methods = { + .f_open = &ugen_open, + .f_close = &ugen_close, + .f_ioctl = &ugen_ioctl, + .f_ioctl_post = &ugen_ioctl_post, + .f_start_read = &ugen_start_read, + .f_stop_read = &ugen_stop_io, + .f_start_write = &ugen_start_write, + .f_stop_write = &ugen_stop_io, +}; + +#ifdef USB_DEBUG +static int ugen_debug = 0; + +SYSCTL_NODE(_hw_usb, OID_AUTO, ugen, CTLFLAG_RW, 0, "USB generic"); +SYSCTL_INT(_hw_usb_ugen, OID_AUTO, debug, CTLFLAG_RW, &ugen_debug, + 0, "Debug level"); + +TUNABLE_INT("hw.usb.ugen.debug", &ugen_debug); +#endif + + +/* prototypes */ + +static int +ugen_transfer_setup(struct usb_fifo *f, + const struct usb_config *setup, uint8_t n_setup) +{ + struct usb_endpoint *ep = usb_fifo_softc(f); + struct usb_device *udev = f->udev; + uint8_t iface_index = ep->iface_index; + int error; + + mtx_unlock(f->priv_mtx); + + /* + * "usbd_transfer_setup()" can sleep so one needs to make a wrapper, + * exiting the mutex and checking things + */ + error = usbd_transfer_setup(udev, &iface_index, f->xfer, + setup, n_setup, f, f->priv_mtx); + if (error == 0) { + + if (f->xfer[0]->nframes == 1) { + error = usb_fifo_alloc_buffer(f, + f->xfer[0]->max_data_length, 2); + } else { + error = usb_fifo_alloc_buffer(f, + f->xfer[0]->max_frame_size, + 2 * f->xfer[0]->nframes); + } + if (error) { + usbd_transfer_unsetup(f->xfer, n_setup); + } + } + mtx_lock(f->priv_mtx); + + return (error); +} + +static int +ugen_open(struct usb_fifo *f, int fflags) +{ + struct usb_endpoint *ep = usb_fifo_softc(f); + struct usb_endpoint_descriptor *ed = ep->edesc; + uint8_t type; + + DPRINTFN(6, "flag=0x%x\n", fflags); + + mtx_lock(f->priv_mtx); + switch (usbd_get_speed(f->udev)) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + f->nframes = UGEN_HW_FRAMES; + f->bufsize = UGEN_BULK_FS_BUFFER_SIZE; + break; + default: + f->nframes = UGEN_HW_FRAMES * 8; + f->bufsize = UGEN_BULK_HS_BUFFER_SIZE; + break; + } + + type = ed->bmAttributes & UE_XFERTYPE; + if (type == UE_INTERRUPT) { + f->bufsize = 0; /* use "wMaxPacketSize" */ + } + f->timeout = USB_NO_TIMEOUT; + f->flag_short = 0; + f->fifo_zlp = 0; + mtx_unlock(f->priv_mtx); + + return (0); +} + +static void +ugen_close(struct usb_fifo *f, int fflags) +{ + DPRINTFN(6, "flag=0x%x\n", fflags); + + /* cleanup */ + + mtx_lock(f->priv_mtx); + usbd_transfer_stop(f->xfer[0]); + usbd_transfer_stop(f->xfer[1]); + mtx_unlock(f->priv_mtx); + + usbd_transfer_unsetup(f->xfer, 2); + usb_fifo_free_buffer(f); + + if (ugen_fs_uninit(f)) { + /* ignore any errors - we are closing */ + DPRINTFN(6, "no FIFOs\n"); + } +} + +static int +ugen_open_pipe_write(struct usb_fifo *f) +{ + struct usb_config usb_config[2]; + struct usb_endpoint *ep = usb_fifo_softc(f); + struct usb_endpoint_descriptor *ed = ep->edesc; + + mtx_assert(f->priv_mtx, MA_OWNED); + + if (f->xfer[0] || f->xfer[1]) { + /* transfers are already opened */ + return (0); + } + bzero(usb_config, sizeof(usb_config)); + + usb_config[1].type = UE_CONTROL; + usb_config[1].endpoint = 0; + usb_config[1].direction = UE_DIR_ANY; + usb_config[1].timeout = 1000; /* 1 second */ + usb_config[1].interval = 50;/* 50 milliseconds */ + usb_config[1].bufsize = sizeof(struct usb_device_request); + usb_config[1].callback = &ugen_write_clear_stall_callback; + usb_config[1].usb_mode = USB_MODE_HOST; + + usb_config[0].type = ed->bmAttributes & UE_XFERTYPE; + usb_config[0].endpoint = ed->bEndpointAddress & UE_ADDR; + usb_config[0].direction = UE_DIR_TX; + usb_config[0].interval = USB_DEFAULT_INTERVAL; + usb_config[0].flags.proxy_buffer = 1; + usb_config[0].usb_mode = USB_MODE_DUAL; /* both modes */ + + switch (ed->bmAttributes & UE_XFERTYPE) { + case UE_INTERRUPT: + case UE_BULK: + if (f->flag_short) { + usb_config[0].flags.force_short_xfer = 1; + } + usb_config[0].callback = &ugen_ctrl_write_callback; + usb_config[0].timeout = f->timeout; + usb_config[0].frames = 1; + usb_config[0].bufsize = f->bufsize; + if (ugen_transfer_setup(f, usb_config, 2)) { + return (EIO); + } + /* first transfer does not clear stall */ + f->flag_stall = 0; + break; + + case UE_ISOCHRONOUS: + usb_config[0].flags.short_xfer_ok = 1; + usb_config[0].bufsize = 0; /* use default */ + usb_config[0].frames = f->nframes; + usb_config[0].callback = &ugen_isoc_write_callback; + usb_config[0].timeout = 0; + + /* clone configuration */ + usb_config[1] = usb_config[0]; + + if (ugen_transfer_setup(f, usb_config, 2)) { + return (EIO); + } + break; + default: + return (EINVAL); + } + return (0); +} + +static int +ugen_open_pipe_read(struct usb_fifo *f) +{ + struct usb_config usb_config[2]; + struct usb_endpoint *ep = usb_fifo_softc(f); + struct usb_endpoint_descriptor *ed = ep->edesc; + + mtx_assert(f->priv_mtx, MA_OWNED); + + if (f->xfer[0] || f->xfer[1]) { + /* transfers are already opened */ + return (0); + } + bzero(usb_config, sizeof(usb_config)); + + usb_config[1].type = UE_CONTROL; + usb_config[1].endpoint = 0; + usb_config[1].direction = UE_DIR_ANY; + usb_config[1].timeout = 1000; /* 1 second */ + usb_config[1].interval = 50;/* 50 milliseconds */ + usb_config[1].bufsize = sizeof(struct usb_device_request); + usb_config[1].callback = &ugen_read_clear_stall_callback; + usb_config[1].usb_mode = USB_MODE_HOST; + + usb_config[0].type = ed->bmAttributes & UE_XFERTYPE; + usb_config[0].endpoint = ed->bEndpointAddress & UE_ADDR; + usb_config[0].direction = UE_DIR_RX; + usb_config[0].interval = USB_DEFAULT_INTERVAL; + usb_config[0].flags.proxy_buffer = 1; + usb_config[0].usb_mode = USB_MODE_DUAL; /* both modes */ + + switch (ed->bmAttributes & UE_XFERTYPE) { + case UE_INTERRUPT: + case UE_BULK: + if (f->flag_short) { + usb_config[0].flags.short_xfer_ok = 1; + } + usb_config[0].timeout = f->timeout; + usb_config[0].frames = 1; + usb_config[0].callback = &ugen_ctrl_read_callback; + usb_config[0].bufsize = f->bufsize; + + if (ugen_transfer_setup(f, usb_config, 2)) { + return (EIO); + } + /* first transfer does not clear stall */ + f->flag_stall = 0; + break; + + case UE_ISOCHRONOUS: + usb_config[0].flags.short_xfer_ok = 1; + usb_config[0].bufsize = 0; /* use default */ + usb_config[0].frames = f->nframes; + usb_config[0].callback = &ugen_isoc_read_callback; + usb_config[0].timeout = 0; + + /* clone configuration */ + usb_config[1] = usb_config[0]; + + if (ugen_transfer_setup(f, usb_config, 2)) { + return (EIO); + } + break; + + default: + return (EINVAL); + } + return (0); +} + +static void +ugen_start_read(struct usb_fifo *f) +{ + /* check that pipes are open */ + if (ugen_open_pipe_read(f)) { + /* signal error */ + usb_fifo_put_data_error(f); + } + /* start transfers */ + usbd_transfer_start(f->xfer[0]); + usbd_transfer_start(f->xfer[1]); +} + +static void +ugen_start_write(struct usb_fifo *f) +{ + /* check that pipes are open */ + if (ugen_open_pipe_write(f)) { + /* signal error */ + usb_fifo_get_data_error(f); + } + /* start transfers */ + usbd_transfer_start(f->xfer[0]); + usbd_transfer_start(f->xfer[1]); +} + +static void +ugen_stop_io(struct usb_fifo *f) +{ + /* stop transfers */ + usbd_transfer_stop(f->xfer[0]); + usbd_transfer_stop(f->xfer[1]); +} + +static void +ugen_ctrl_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct usb_fifo *f = usbd_xfer_softc(xfer); + struct usb_mbuf *m; + + DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + if (xfer->actlen == 0) { + if (f->fifo_zlp != 4) { + f->fifo_zlp++; + } else { + /* + * Throttle a little bit we have multiple ZLPs + * in a row! + */ + xfer->interval = 64; /* ms */ + } + } else { + /* clear throttle */ + xfer->interval = 0; + f->fifo_zlp = 0; + } + usb_fifo_put_data(f, xfer->frbuffers, 0, + xfer->actlen, 1); + + case USB_ST_SETUP: + if (f->flag_stall) { + usbd_transfer_start(f->xfer[1]); + break; + } + USB_IF_POLL(&f->free_q, m); + if (m) { + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + } + break; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* send a zero length packet to userland */ + usb_fifo_put_data(f, xfer->frbuffers, 0, 0, 1); + f->flag_stall = 1; + f->fifo_zlp = 0; + usbd_transfer_start(f->xfer[1]); + } + break; + } +} + +static void +ugen_ctrl_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct usb_fifo *f = usbd_xfer_softc(xfer); + usb_frlength_t actlen; + + DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: + /* + * If writing is in stall, just jump to clear stall + * callback and solve the situation. + */ + if (f->flag_stall) { + usbd_transfer_start(f->xfer[1]); + break; + } + /* + * Write data, setup and perform hardware transfer. + */ + if (usb_fifo_get_data(f, xfer->frbuffers, 0, + xfer->max_data_length, &actlen, 0)) { + usbd_xfer_set_frame_len(xfer, 0, actlen); + usbd_transfer_submit(xfer); + } + break; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + f->flag_stall = 1; + usbd_transfer_start(f->xfer[1]); + } + break; + } +} + +static void +ugen_read_clear_stall_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct usb_fifo *f = usbd_xfer_softc(xfer); + struct usb_xfer *xfer_other = f->xfer[0]; + + if (f->flag_stall == 0) { + /* nothing to do */ + return; + } + if (usbd_clear_stall_callback(xfer, xfer_other)) { + DPRINTFN(5, "f=%p: stall cleared\n", f); + f->flag_stall = 0; + usbd_transfer_start(xfer_other); + } +} + +static void +ugen_write_clear_stall_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct usb_fifo *f = usbd_xfer_softc(xfer); + struct usb_xfer *xfer_other = f->xfer[0]; + + if (f->flag_stall == 0) { + /* nothing to do */ + return; + } + if (usbd_clear_stall_callback(xfer, xfer_other)) { + DPRINTFN(5, "f=%p: stall cleared\n", f); + f->flag_stall = 0; + usbd_transfer_start(xfer_other); + } +} + +static void +ugen_isoc_read_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct usb_fifo *f = usbd_xfer_softc(xfer); + usb_frlength_t offset; + usb_frcount_t n; + + DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + DPRINTFN(6, "actlen=%d\n", xfer->actlen); + + offset = 0; + + for (n = 0; n != xfer->aframes; n++) { + usb_fifo_put_data(f, xfer->frbuffers, offset, + xfer->frlengths[n], 1); + offset += xfer->max_frame_size; + } + + case USB_ST_SETUP: +tr_setup: + for (n = 0; n != xfer->nframes; n++) { + /* setup size for next transfer */ + usbd_xfer_set_frame_len(xfer, n, xfer->max_frame_size); + } + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + if (xfer->error == USB_ERR_CANCELLED) { + break; + } + goto tr_setup; + } +} + +static void +ugen_isoc_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct usb_fifo *f = usbd_xfer_softc(xfer); + usb_frlength_t actlen; + usb_frlength_t offset; + usb_frcount_t n; + + DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + case USB_ST_SETUP: +tr_setup: + offset = 0; + for (n = 0; n != xfer->nframes; n++) { + if (usb_fifo_get_data(f, xfer->frbuffers, offset, + xfer->max_frame_size, &actlen, 1)) { + usbd_xfer_set_frame_len(xfer, n, actlen); + offset += actlen; + } else { + break; + } + } + + for (; n != xfer->nframes; n++) { + /* fill in zero frames */ + usbd_xfer_set_frame_len(xfer, n, 0); + } + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + if (xfer->error == USB_ERR_CANCELLED) { + break; + } + goto tr_setup; + } +} + +static int +ugen_set_config(struct usb_fifo *f, uint8_t index) +{ + DPRINTFN(2, "index %u\n", index); + + if (f->udev->flags.usb_mode != USB_MODE_HOST) { + /* not possible in device side mode */ + return (ENOTTY); + } + if (f->udev->curr_config_index == index) { + /* no change needed */ + return (0); + } + /* make sure all FIFO's are gone */ + /* else there can be a deadlock */ + if (ugen_fs_uninit(f)) { + /* ignore any errors */ + DPRINTFN(6, "no FIFOs\n"); + } + /* change setting - will free generic FIFOs, if any */ + if (usbd_set_config_index(f->udev, index)) { + return (EIO); + } + /* probe and attach */ + if (usb_probe_and_attach(f->udev, USB_IFACE_INDEX_ANY)) { + return (EIO); + } + return (0); +} + +static int +ugen_set_interface(struct usb_fifo *f, + uint8_t iface_index, uint8_t alt_index) +{ + DPRINTFN(2, "%u, %u\n", iface_index, alt_index); + + if (f->udev->flags.usb_mode != USB_MODE_HOST) { + /* not possible in device side mode */ + return (ENOTTY); + } + /* make sure all FIFO's are gone */ + /* else there can be a deadlock */ + if (ugen_fs_uninit(f)) { + /* ignore any errors */ + DPRINTFN(6, "no FIFOs\n"); + } + /* change setting - will free generic FIFOs, if any */ + if (usbd_set_alt_interface_index(f->udev, iface_index, alt_index)) { + return (EIO); + } + /* probe and attach */ + if (usb_probe_and_attach(f->udev, iface_index)) { + return (EIO); + } + return (0); +} + +/*------------------------------------------------------------------------* + * ugen_get_cdesc + * + * This function will retrieve the complete configuration descriptor + * at the given index. + *------------------------------------------------------------------------*/ +static int +ugen_get_cdesc(struct usb_fifo *f, struct usb_gen_descriptor *ugd) +{ + struct usb_config_descriptor *cdesc; + struct usb_device *udev = f->udev; + int error; + uint16_t len; + uint8_t free_data; + + DPRINTFN(6, "\n"); + + if (ugd->ugd_data == NULL) { + /* userland pointer should not be zero */ + return (EINVAL); + } + if ((ugd->ugd_config_index == USB_UNCONFIG_INDEX) || + (ugd->ugd_config_index == udev->curr_config_index)) { + cdesc = usbd_get_config_descriptor(udev); + if (cdesc == NULL) { + return (ENXIO); + } + free_data = 0; + + } else { + if (usbd_req_get_config_desc_full(udev, + NULL, &cdesc, M_USBDEV, + ugd->ugd_config_index)) { + return (ENXIO); + } + free_data = 1; + } + + len = UGETW(cdesc->wTotalLength); + if (len > ugd->ugd_maxlen) { + len = ugd->ugd_maxlen; + } + DPRINTFN(6, "len=%u\n", len); + + ugd->ugd_actlen = len; + ugd->ugd_offset = 0; + + error = copyout(cdesc, ugd->ugd_data, len); + + if (free_data) { + free(cdesc, M_USBDEV); + } + return (error); +} + +static int +ugen_get_sdesc(struct usb_fifo *f, struct usb_gen_descriptor *ugd) +{ + void *ptr = f->udev->bus->scratch[0].data; + uint16_t size = sizeof(f->udev->bus->scratch[0].data); + int error; + + if (usbd_req_get_string_desc(f->udev, NULL, ptr, + size, ugd->ugd_lang_id, ugd->ugd_string_index)) { + error = EINVAL; + } else { + + if (size > ((uint8_t *)ptr)[0]) { + size = ((uint8_t *)ptr)[0]; + } + if (size > ugd->ugd_maxlen) { + size = ugd->ugd_maxlen; + } + ugd->ugd_actlen = size; + ugd->ugd_offset = 0; + + error = copyout(ptr, ugd->ugd_data, size); + } + return (error); +} + +/*------------------------------------------------------------------------* + * ugen_get_iface_driver + * + * This function generates an USB interface description for userland. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static int +ugen_get_iface_driver(struct usb_fifo *f, struct usb_gen_descriptor *ugd) +{ + struct usb_device *udev = f->udev; + struct usb_interface *iface; + const char *ptr; + const char *desc; + unsigned int len; + unsigned int maxlen; + char buf[128]; + int error; + + DPRINTFN(6, "\n"); + + if ((ugd->ugd_data == NULL) || (ugd->ugd_maxlen == 0)) { + /* userland pointer should not be zero */ + return (EINVAL); + } + + iface = usbd_get_iface(udev, ugd->ugd_iface_index); + if ((iface == NULL) || (iface->idesc == NULL)) { + /* invalid interface index */ + return (EINVAL); + } + + /* read out device nameunit string, if any */ + if ((iface->subdev != NULL) && + device_is_attached(iface->subdev) && + (ptr = device_get_nameunit(iface->subdev)) && + (desc = device_get_desc(iface->subdev))) { + + /* print description */ + snprintf(buf, sizeof(buf), "%s: <%s>", ptr, desc); + + /* range checks */ + maxlen = ugd->ugd_maxlen - 1; + len = strlen(buf); + if (len > maxlen) + len = maxlen; + + /* update actual length, including terminating zero */ + ugd->ugd_actlen = len + 1; + + /* copy out interface description */ + error = copyout(buf, ugd->ugd_data, ugd->ugd_actlen); + } else { + /* zero length string is default */ + error = copyout("", ugd->ugd_data, 1); + } + return (error); +} + +/*------------------------------------------------------------------------* + * usb_gen_fill_deviceinfo + * + * This function dumps information about an USB device to the + * structure pointed to by the "di" argument. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static int +usb_gen_fill_deviceinfo(struct usb_fifo *f, struct usb_device_info *di) +{ + struct usb_device *udev; + struct usb_device *hub; + + udev = f->udev; + + bzero(di, sizeof(di[0])); + + di->udi_bus = device_get_unit(udev->bus->bdev); + di->udi_addr = udev->address; + di->udi_index = udev->device_index; + strlcpy(di->udi_serial, usb_get_serial(udev), sizeof(di->udi_serial)); + strlcpy(di->udi_vendor, usb_get_manufacturer(udev), sizeof(di->udi_vendor)); + strlcpy(di->udi_product, usb_get_product(udev), sizeof(di->udi_product)); + usb_printbcd(di->udi_release, sizeof(di->udi_release), + UGETW(udev->ddesc.bcdDevice)); + di->udi_vendorNo = UGETW(udev->ddesc.idVendor); + di->udi_productNo = UGETW(udev->ddesc.idProduct); + di->udi_releaseNo = UGETW(udev->ddesc.bcdDevice); + di->udi_class = udev->ddesc.bDeviceClass; + di->udi_subclass = udev->ddesc.bDeviceSubClass; + di->udi_protocol = udev->ddesc.bDeviceProtocol; + di->udi_config_no = udev->curr_config_no; + di->udi_config_index = udev->curr_config_index; + di->udi_power = udev->flags.self_powered ? 0 : udev->power; + di->udi_speed = udev->speed; + di->udi_mode = udev->flags.usb_mode; + di->udi_power_mode = udev->power_mode; + di->udi_suspended = udev->flags.peer_suspended; + + hub = udev->parent_hub; + if (hub) { + di->udi_hubaddr = hub->address; + di->udi_hubindex = hub->device_index; + di->udi_hubport = udev->port_no; + } + return (0); +} + +/*------------------------------------------------------------------------* + * ugen_check_request + * + * Return values: + * 0: Access allowed + * Else: No access + *------------------------------------------------------------------------*/ +static int +ugen_check_request(struct usb_device *udev, struct usb_device_request *req) +{ + struct usb_endpoint *ep; + int error; + + /* + * Avoid requests that would damage the bus integrity: + */ + if (((req->bmRequestType == UT_WRITE_DEVICE) && + (req->bRequest == UR_SET_ADDRESS)) || + ((req->bmRequestType == UT_WRITE_DEVICE) && + (req->bRequest == UR_SET_CONFIG)) || + ((req->bmRequestType == UT_WRITE_INTERFACE) && + (req->bRequest == UR_SET_INTERFACE))) { + /* + * These requests can be useful for testing USB drivers. + */ + error = priv_check(curthread, PRIV_DRIVER); + if (error) { + return (error); + } + } + /* + * Special case - handle clearing of stall + */ + if (req->bmRequestType == UT_WRITE_ENDPOINT) { + + ep = usbd_get_ep_by_addr(udev, req->wIndex[0]); + if (ep == NULL) { + return (EINVAL); + } + if ((req->bRequest == UR_CLEAR_FEATURE) && + (UGETW(req->wValue) == UF_ENDPOINT_HALT)) { + usbd_clear_data_toggle(udev, ep); + } + } + /* TODO: add more checks to verify the interface index */ + + return (0); +} + +int +ugen_do_request(struct usb_fifo *f, struct usb_ctl_request *ur) +{ + int error; + uint16_t len; + uint16_t actlen; + + if (ugen_check_request(f->udev, &ur->ucr_request)) { + return (EPERM); + } + len = UGETW(ur->ucr_request.wLength); + + /* check if "ucr_data" is valid */ + if (len != 0) { + if (ur->ucr_data == NULL) { + return (EFAULT); + } + } + /* do the USB request */ + error = usbd_do_request_flags + (f->udev, NULL, &ur->ucr_request, ur->ucr_data, + (ur->ucr_flags & USB_SHORT_XFER_OK) | + USB_USER_DATA_PTR, &actlen, + USB_DEFAULT_TIMEOUT); + + ur->ucr_actlen = actlen; + + if (error) { + error = EIO; + } + return (error); +} + +/*------------------------------------------------------------------------ + * ugen_re_enumerate + *------------------------------------------------------------------------*/ +static int +ugen_re_enumerate(struct usb_fifo *f) +{ + struct usb_device *udev = f->udev; + int error; + + /* + * This request can be useful for testing USB drivers: + */ + error = priv_check(curthread, PRIV_DRIVER); + if (error) { + return (error); + } + if (udev->flags.usb_mode != USB_MODE_HOST) { + /* not possible in device side mode */ + return (ENOTTY); + } + /* make sure all FIFO's are gone */ + /* else there can be a deadlock */ + if (ugen_fs_uninit(f)) { + /* ignore any errors */ + DPRINTFN(6, "no FIFOs\n"); + } + if (udev->re_enumerate_wait == 0) { + udev->re_enumerate_wait = 1; + usb_needs_explore(udev->bus, 0); + } + return (0); +} + +int +ugen_fs_uninit(struct usb_fifo *f) +{ + if (f->fs_xfer == NULL) { + return (EINVAL); + } + usbd_transfer_unsetup(f->fs_xfer, f->fs_ep_max); + free(f->fs_xfer, M_USB); + f->fs_xfer = NULL; + f->fs_ep_max = 0; + f->fs_ep_ptr = NULL; + f->flag_iscomplete = 0; + usb_fifo_free_buffer(f); + return (0); +} + +static uint8_t +ugen_fs_get_complete(struct usb_fifo *f, uint8_t *pindex) +{ + struct usb_mbuf *m; + + USB_IF_DEQUEUE(&f->used_q, m); + + if (m) { + *pindex = *((uint8_t *)(m->cur_data_ptr)); + + USB_IF_ENQUEUE(&f->free_q, m); + + return (0); /* success */ + } else { + + *pindex = 0; /* fix compiler warning */ + + f->flag_iscomplete = 0; + } + return (1); /* failure */ +} + +static void +ugen_fs_set_complete(struct usb_fifo *f, uint8_t index) +{ + struct usb_mbuf *m; + + USB_IF_DEQUEUE(&f->free_q, m); + + if (m == NULL) { + /* can happen during close */ + DPRINTF("out of buffers\n"); + return; + } + USB_MBUF_RESET(m); + + *((uint8_t *)(m->cur_data_ptr)) = index; + + USB_IF_ENQUEUE(&f->used_q, m); + + f->flag_iscomplete = 1; + + usb_fifo_wakeup(f); +} + +static int +ugen_fs_copy_in(struct usb_fifo *f, uint8_t ep_index) +{ + struct usb_device_request *req; + struct usb_xfer *xfer; + struct usb_fs_endpoint fs_ep; + void *uaddr; /* userland pointer */ + void *kaddr; + usb_frlength_t offset; + usb_frlength_t rem; + usb_frcount_t n; + uint32_t length; + int error; + uint8_t isread; + + if (ep_index >= f->fs_ep_max) { + return (EINVAL); + } + xfer = f->fs_xfer[ep_index]; + if (xfer == NULL) { + return (EINVAL); + } + mtx_lock(f->priv_mtx); + if (usbd_transfer_pending(xfer)) { + mtx_unlock(f->priv_mtx); + return (EBUSY); /* should not happen */ + } + mtx_unlock(f->priv_mtx); + + error = copyin(f->fs_ep_ptr + + ep_index, &fs_ep, sizeof(fs_ep)); + if (error) { + return (error); + } + /* security checks */ + + if (fs_ep.nFrames > xfer->max_frame_count) { + xfer->error = USB_ERR_INVAL; + goto complete; + } + if (fs_ep.nFrames == 0) { + xfer->error = USB_ERR_INVAL; + goto complete; + } + error = copyin(fs_ep.ppBuffer, + &uaddr, sizeof(uaddr)); + if (error) { + return (error); + } + /* reset first frame */ + usbd_xfer_set_frame_offset(xfer, 0, 0); + + if (xfer->flags_int.control_xfr) { + + req = xfer->frbuffers[0].buffer; + + error = copyin(fs_ep.pLength, + &length, sizeof(length)); + if (error) { + return (error); + } + if (length != sizeof(*req)) { + xfer->error = USB_ERR_INVAL; + goto complete; + } + if (length != 0) { + error = copyin(uaddr, req, length); + if (error) { + return (error); + } + } + if (ugen_check_request(f->udev, req)) { + xfer->error = USB_ERR_INVAL; + goto complete; + } + usbd_xfer_set_frame_len(xfer, 0, length); + + /* Host mode only ! */ + if ((req->bmRequestType & + (UT_READ | UT_WRITE)) == UT_READ) { + isread = 1; + } else { + isread = 0; + } + n = 1; + offset = sizeof(*req); + + } else { + /* Device and Host mode */ + if (USB_GET_DATA_ISREAD(xfer)) { + isread = 1; + } else { + isread = 0; + } + n = 0; + offset = 0; + } + + rem = usbd_xfer_max_len(xfer); + xfer->nframes = fs_ep.nFrames; + xfer->timeout = fs_ep.timeout; + if (xfer->timeout > 65535) { + xfer->timeout = 65535; + } + if (fs_ep.flags & USB_FS_FLAG_SINGLE_SHORT_OK) + xfer->flags.short_xfer_ok = 1; + else + xfer->flags.short_xfer_ok = 0; + + if (fs_ep.flags & USB_FS_FLAG_MULTI_SHORT_OK) + xfer->flags.short_frames_ok = 1; + else + xfer->flags.short_frames_ok = 0; + + if (fs_ep.flags & USB_FS_FLAG_FORCE_SHORT) + xfer->flags.force_short_xfer = 1; + else + xfer->flags.force_short_xfer = 0; + + if (fs_ep.flags & USB_FS_FLAG_CLEAR_STALL) + usbd_xfer_set_stall(xfer); + else + xfer->flags.stall_pipe = 0; + + for (; n != xfer->nframes; n++) { + + error = copyin(fs_ep.pLength + n, + &length, sizeof(length)); + if (error) { + break; + } + usbd_xfer_set_frame_len(xfer, n, length); + + if (length > rem) { + xfer->error = USB_ERR_INVAL; + goto complete; + } + rem -= length; + + if (!isread) { + + /* we need to know the source buffer */ + error = copyin(fs_ep.ppBuffer + n, + &uaddr, sizeof(uaddr)); + if (error) { + break; + } + if (xfer->flags_int.isochronous_xfr) { + /* get kernel buffer address */ + kaddr = xfer->frbuffers[0].buffer; + kaddr = USB_ADD_BYTES(kaddr, offset); + } else { + /* set current frame offset */ + usbd_xfer_set_frame_offset(xfer, offset, n); + + /* get kernel buffer address */ + kaddr = xfer->frbuffers[n].buffer; + } + + /* move data */ + error = copyin(uaddr, kaddr, length); + if (error) { + break; + } + } + offset += length; + } + return (error); + +complete: + mtx_lock(f->priv_mtx); + ugen_fs_set_complete(f, ep_index); + mtx_unlock(f->priv_mtx); + return (0); +} + +static int +ugen_fs_copy_out(struct usb_fifo *f, uint8_t ep_index) +{ + struct usb_device_request *req; + struct usb_xfer *xfer; + struct usb_fs_endpoint fs_ep; + struct usb_fs_endpoint *fs_ep_uptr; /* userland ptr */ + void *uaddr; /* userland ptr */ + void *kaddr; + usb_frlength_t offset; + usb_frlength_t rem; + usb_frcount_t n; + uint32_t length; + uint32_t temp; + int error; + uint8_t isread; + + if (ep_index >= f->fs_ep_max) + return (EINVAL); + + xfer = f->fs_xfer[ep_index]; + if (xfer == NULL) + return (EINVAL); + + mtx_lock(f->priv_mtx); + if (usbd_transfer_pending(xfer)) { + mtx_unlock(f->priv_mtx); + return (EBUSY); /* should not happen */ + } + mtx_unlock(f->priv_mtx); + + fs_ep_uptr = f->fs_ep_ptr + ep_index; + error = copyin(fs_ep_uptr, &fs_ep, sizeof(fs_ep)); + if (error) { + return (error); + } + fs_ep.status = xfer->error; + fs_ep.aFrames = xfer->aframes; + fs_ep.isoc_time_complete = xfer->isoc_time_complete; + if (xfer->error) { + goto complete; + } + if (xfer->flags_int.control_xfr) { + req = xfer->frbuffers[0].buffer; + + /* Host mode only ! */ + if ((req->bmRequestType & (UT_READ | UT_WRITE)) == UT_READ) { + isread = 1; + } else { + isread = 0; + } + if (xfer->nframes == 0) + n = 0; /* should never happen */ + else + n = 1; + } else { + /* Device and Host mode */ + if (USB_GET_DATA_ISREAD(xfer)) { + isread = 1; + } else { + isread = 0; + } + n = 0; + } + + /* Update lengths and copy out data */ + + rem = usbd_xfer_max_len(xfer); + offset = 0; + + for (; n != xfer->nframes; n++) { + + /* get initial length into "temp" */ + error = copyin(fs_ep.pLength + n, + &temp, sizeof(temp)); + if (error) { + return (error); + } + if (temp > rem) { + /* the userland length has been corrupted */ + DPRINTF("corrupt userland length " + "%u > %u\n", temp, rem); + fs_ep.status = USB_ERR_INVAL; + goto complete; + } + rem -= temp; + + /* get actual transfer length */ + length = xfer->frlengths[n]; + if (length > temp) { + /* data overflow */ + fs_ep.status = USB_ERR_INVAL; + DPRINTF("data overflow %u > %u\n", + length, temp); + goto complete; + } + if (isread) { + + /* we need to know the destination buffer */ + error = copyin(fs_ep.ppBuffer + n, + &uaddr, sizeof(uaddr)); + if (error) { + return (error); + } + if (xfer->flags_int.isochronous_xfr) { + /* only one frame buffer */ + kaddr = USB_ADD_BYTES( + xfer->frbuffers[0].buffer, offset); + } else { + /* multiple frame buffers */ + kaddr = xfer->frbuffers[n].buffer; + } + + /* move data */ + error = copyout(kaddr, uaddr, length); + if (error) { + return (error); + } + } + /* + * Update offset according to initial length, which is + * needed by isochronous transfers! + */ + offset += temp; + + /* update length */ + error = copyout(&length, + fs_ep.pLength + n, sizeof(length)); + if (error) { + return (error); + } + } + +complete: + /* update "aFrames" */ + error = copyout(&fs_ep.aFrames, &fs_ep_uptr->aFrames, + sizeof(fs_ep.aFrames)); + if (error) + goto done; + + /* update "isoc_time_complete" */ + error = copyout(&fs_ep.isoc_time_complete, + &fs_ep_uptr->isoc_time_complete, + sizeof(fs_ep.isoc_time_complete)); + if (error) + goto done; + /* update "status" */ + error = copyout(&fs_ep.status, &fs_ep_uptr->status, + sizeof(fs_ep.status)); +done: + return (error); +} + +static uint8_t +ugen_fifo_in_use(struct usb_fifo *f, int fflags) +{ + struct usb_fifo *f_rx; + struct usb_fifo *f_tx; + + f_rx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_RX]; + f_tx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_TX]; + + if ((fflags & FREAD) && f_rx && + (f_rx->xfer[0] || f_rx->xfer[1])) { + return (1); /* RX FIFO in use */ + } + if ((fflags & FWRITE) && f_tx && + (f_tx->xfer[0] || f_tx->xfer[1])) { + return (1); /* TX FIFO in use */ + } + return (0); /* not in use */ +} + +static int +ugen_ioctl(struct usb_fifo *f, u_long cmd, void *addr, int fflags) +{ + struct usb_config usb_config[1]; + struct usb_device_request req; + union { + struct usb_fs_complete *pcomp; + struct usb_fs_start *pstart; + struct usb_fs_stop *pstop; + struct usb_fs_open *popen; + struct usb_fs_close *pclose; + struct usb_fs_clear_stall_sync *pstall; + void *addr; + } u; + struct usb_endpoint *ep; + struct usb_endpoint_descriptor *ed; + int error = 0; + uint8_t iface_index; + uint8_t isread; + uint8_t ep_index; + + u.addr = addr; + + DPRINTFN(6, "cmd=0x%08lx\n", cmd); + + switch (cmd) { + case USB_FS_COMPLETE: + mtx_lock(f->priv_mtx); + error = ugen_fs_get_complete(f, &ep_index); + mtx_unlock(f->priv_mtx); + + if (error) { + error = EBUSY; + break; + } + u.pcomp->ep_index = ep_index; + error = ugen_fs_copy_out(f, u.pcomp->ep_index); + break; + + case USB_FS_START: + error = ugen_fs_copy_in(f, u.pstart->ep_index); + if (error) { + break; + } + mtx_lock(f->priv_mtx); + usbd_transfer_start(f->fs_xfer[u.pstart->ep_index]); + mtx_unlock(f->priv_mtx); + break; + + case USB_FS_STOP: + if (u.pstop->ep_index >= f->fs_ep_max) { + error = EINVAL; + break; + } + mtx_lock(f->priv_mtx); + usbd_transfer_stop(f->fs_xfer[u.pstop->ep_index]); + mtx_unlock(f->priv_mtx); + break; + + case USB_FS_OPEN: + if (u.popen->ep_index >= f->fs_ep_max) { + error = EINVAL; + break; + } + if (f->fs_xfer[u.popen->ep_index] != NULL) { + error = EBUSY; + break; + } + if (u.popen->max_bufsize > USB_FS_MAX_BUFSIZE) { + u.popen->max_bufsize = USB_FS_MAX_BUFSIZE; + } + if (u.popen->max_frames > USB_FS_MAX_FRAMES) { + u.popen->max_frames = USB_FS_MAX_FRAMES; + break; + } + if (u.popen->max_frames == 0) { + error = EINVAL; + break; + } + ep = usbd_get_ep_by_addr(f->udev, u.popen->ep_no); + if (ep == NULL) { + error = EINVAL; + break; + } + ed = ep->edesc; + if (ed == NULL) { + error = ENXIO; + break; + } + iface_index = ep->iface_index; + + bzero(usb_config, sizeof(usb_config)); + + usb_config[0].type = ed->bmAttributes & UE_XFERTYPE; + usb_config[0].endpoint = ed->bEndpointAddress & UE_ADDR; + usb_config[0].direction = ed->bEndpointAddress & (UE_DIR_OUT | UE_DIR_IN); + usb_config[0].interval = USB_DEFAULT_INTERVAL; + usb_config[0].flags.proxy_buffer = 1; + usb_config[0].callback = &ugen_ctrl_fs_callback; + usb_config[0].timeout = 0; /* no timeout */ + usb_config[0].frames = u.popen->max_frames; + usb_config[0].bufsize = u.popen->max_bufsize; + usb_config[0].usb_mode = USB_MODE_DUAL; /* both modes */ + + if (usb_config[0].type == UE_CONTROL) { + if (f->udev->flags.usb_mode != USB_MODE_HOST) { + error = EINVAL; + break; + } + } else { + + isread = ((usb_config[0].endpoint & + (UE_DIR_IN | UE_DIR_OUT)) == UE_DIR_IN); + + if (f->udev->flags.usb_mode != USB_MODE_HOST) { + isread = !isread; + } + /* check permissions */ + if (isread) { + if (!(fflags & FREAD)) { + error = EPERM; + break; + } + } else { + if (!(fflags & FWRITE)) { + error = EPERM; + break; + } + } + } + error = usbd_transfer_setup(f->udev, &iface_index, + f->fs_xfer + u.popen->ep_index, usb_config, 1, + f, f->priv_mtx); + if (error == 0) { + /* update maximums */ + u.popen->max_packet_length = + f->fs_xfer[u.popen->ep_index]->max_frame_size; + u.popen->max_bufsize = + f->fs_xfer[u.popen->ep_index]->max_data_length; + f->fs_xfer[u.popen->ep_index]->priv_fifo = + ((uint8_t *)0) + u.popen->ep_index; + } else { + error = ENOMEM; + } + break; + + case USB_FS_CLOSE: + if (u.pclose->ep_index >= f->fs_ep_max) { + error = EINVAL; + break; + } + if (f->fs_xfer[u.pclose->ep_index] == NULL) { + error = EINVAL; + break; + } + usbd_transfer_unsetup(f->fs_xfer + u.pclose->ep_index, 1); + break; + + case USB_FS_CLEAR_STALL_SYNC: + if (u.pstall->ep_index >= f->fs_ep_max) { + error = EINVAL; + break; + } + if (f->fs_xfer[u.pstall->ep_index] == NULL) { + error = EINVAL; + break; + } + if (f->udev->flags.usb_mode != USB_MODE_HOST) { + error = EINVAL; + break; + } + mtx_lock(f->priv_mtx); + error = usbd_transfer_pending(f->fs_xfer[u.pstall->ep_index]); + mtx_unlock(f->priv_mtx); + + if (error) { + return (EBUSY); + } + ep = f->fs_xfer[u.pstall->ep_index]->endpoint; + + /* setup a clear-stall packet */ + req.bmRequestType = UT_WRITE_ENDPOINT; + req.bRequest = UR_CLEAR_FEATURE; + USETW(req.wValue, UF_ENDPOINT_HALT); + req.wIndex[0] = ep->edesc->bEndpointAddress; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + error = usbd_do_request(f->udev, NULL, &req, NULL); + if (error == 0) { + usbd_clear_data_toggle(f->udev, ep); + } else { + error = ENXIO; + } + break; + + default: + error = ENOIOCTL; + break; + } + + DPRINTFN(6, "error=%d\n", error); + + return (error); +} + +static int +ugen_set_short_xfer(struct usb_fifo *f, void *addr) +{ + uint8_t t; + + if (*(int *)addr) + t = 1; + else + t = 0; + + if (f->flag_short == t) { + /* same value like before - accept */ + return (0); + } + if (f->xfer[0] || f->xfer[1]) { + /* cannot change this during transfer */ + return (EBUSY); + } + f->flag_short = t; + return (0); +} + +static int +ugen_set_timeout(struct usb_fifo *f, void *addr) +{ + f->timeout = *(int *)addr; + if (f->timeout > 65535) { + /* limit user input */ + f->timeout = 65535; + } + return (0); +} + +static int +ugen_get_frame_size(struct usb_fifo *f, void *addr) +{ + if (f->xfer[0]) { + *(int *)addr = f->xfer[0]->max_frame_size; + } else { + return (EINVAL); + } + return (0); +} + +static int +ugen_set_buffer_size(struct usb_fifo *f, void *addr) +{ + usb_frlength_t t; + + if (*(int *)addr < 0) + t = 0; /* use "wMaxPacketSize" */ + else if (*(int *)addr < (256 * 1024)) + t = *(int *)addr; + else + t = 256 * 1024; + + if (f->bufsize == t) { + /* same value like before - accept */ + return (0); + } + if (f->xfer[0] || f->xfer[1]) { + /* cannot change this during transfer */ + return (EBUSY); + } + f->bufsize = t; + return (0); +} + +static int +ugen_get_buffer_size(struct usb_fifo *f, void *addr) +{ + *(int *)addr = f->bufsize; + return (0); +} + +static int +ugen_get_iface_desc(struct usb_fifo *f, + struct usb_interface_descriptor *idesc) +{ + struct usb_interface *iface; + + iface = usbd_get_iface(f->udev, f->iface_index); + if (iface && iface->idesc) { + *idesc = *(iface->idesc); + } else { + return (EIO); + } + return (0); +} + +static int +ugen_get_endpoint_desc(struct usb_fifo *f, + struct usb_endpoint_descriptor *ed) +{ + struct usb_endpoint *ep; + + ep = usb_fifo_softc(f); + + if (ep && ep->edesc) { + *ed = *ep->edesc; + } else { + return (EINVAL); + } + return (0); +} + +static int +ugen_set_power_mode(struct usb_fifo *f, int mode) +{ + struct usb_device *udev = f->udev; + int err; + uint8_t old_mode; + + if ((udev == NULL) || + (udev->parent_hub == NULL)) { + return (EINVAL); + } + err = priv_check(curthread, PRIV_DRIVER); + if (err) + return (err); + + /* get old power mode */ + old_mode = udev->power_mode; + + /* if no change, then just return */ + if (old_mode == mode) + return (0); + + switch (mode) { + case USB_POWER_MODE_OFF: + /* get the device unconfigured */ + err = ugen_set_config(f, USB_UNCONFIG_INDEX); + if (err) { + DPRINTFN(0, "Could not unconfigure " + "device (ignored)\n"); + } + + /* clear port enable */ + err = usbd_req_clear_port_feature(udev->parent_hub, + NULL, udev->port_no, UHF_PORT_ENABLE); + break; + + case USB_POWER_MODE_ON: + case USB_POWER_MODE_SAVE: + break; + + case USB_POWER_MODE_RESUME: +#if USB_HAVE_POWERD + /* let USB-powerd handle resume */ + USB_BUS_LOCK(udev->bus); + udev->pwr_save.write_refs++; + udev->pwr_save.last_xfer_time = ticks; + USB_BUS_UNLOCK(udev->bus); + + /* set new power mode */ + usbd_set_power_mode(udev, USB_POWER_MODE_SAVE); + + /* wait for resume to complete */ + usb_pause_mtx(NULL, hz / 4); + + /* clear write reference */ + USB_BUS_LOCK(udev->bus); + udev->pwr_save.write_refs--; + USB_BUS_UNLOCK(udev->bus); +#endif + mode = USB_POWER_MODE_SAVE; + break; + + case USB_POWER_MODE_SUSPEND: +#if USB_HAVE_POWERD + /* let USB-powerd handle suspend */ + USB_BUS_LOCK(udev->bus); + udev->pwr_save.last_xfer_time = ticks - (256 * hz); + USB_BUS_UNLOCK(udev->bus); +#endif + mode = USB_POWER_MODE_SAVE; + break; + + default: + return (EINVAL); + } + + if (err) + return (ENXIO); /* I/O failure */ + + /* if we are powered off we need to re-enumerate first */ + if (old_mode == USB_POWER_MODE_OFF) { + if (udev->flags.usb_mode == USB_MODE_HOST) { + if (udev->re_enumerate_wait == 0) + udev->re_enumerate_wait = 1; + } + /* set power mode will wake up the explore thread */ + } + + /* set new power mode */ + usbd_set_power_mode(udev, mode); + + return (0); /* success */ +} + +static int +ugen_get_power_mode(struct usb_fifo *f) +{ + struct usb_device *udev = f->udev; + + if (udev == NULL) + return (USB_POWER_MODE_ON); + + return (udev->power_mode); +} + +static int +ugen_do_port_feature(struct usb_fifo *f, uint8_t port_no, + uint8_t set, uint16_t feature) +{ + struct usb_device *udev = f->udev; + struct usb_hub *hub; + int err; + + err = priv_check(curthread, PRIV_DRIVER); + if (err) { + return (err); + } + if (port_no == 0) { + return (EINVAL); + } + if ((udev == NULL) || + (udev->hub == NULL)) { + return (EINVAL); + } + hub = udev->hub; + + if (port_no > hub->nports) { + return (EINVAL); + } + if (set) + err = usbd_req_set_port_feature(udev, + NULL, port_no, feature); + else + err = usbd_req_clear_port_feature(udev, + NULL, port_no, feature); + + if (err) + return (ENXIO); /* failure */ + + return (0); /* success */ +} + +static int +ugen_iface_ioctl(struct usb_fifo *f, u_long cmd, void *addr, int fflags) +{ + struct usb_fifo *f_rx; + struct usb_fifo *f_tx; + int error = 0; + + f_rx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_RX]; + f_tx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_TX]; + + switch (cmd) { + case USB_SET_RX_SHORT_XFER: + if (fflags & FREAD) { + error = ugen_set_short_xfer(f_rx, addr); + } else { + error = EINVAL; + } + break; + + case USB_SET_TX_FORCE_SHORT: + if (fflags & FWRITE) { + error = ugen_set_short_xfer(f_tx, addr); + } else { + error = EINVAL; + } + break; + + case USB_SET_RX_TIMEOUT: + if (fflags & FREAD) { + error = ugen_set_timeout(f_rx, addr); + } else { + error = EINVAL; + } + break; + + case USB_SET_TX_TIMEOUT: + if (fflags & FWRITE) { + error = ugen_set_timeout(f_tx, addr); + } else { + error = EINVAL; + } + break; + + case USB_GET_RX_FRAME_SIZE: + if (fflags & FREAD) { + error = ugen_get_frame_size(f_rx, addr); + } else { + error = EINVAL; + } + break; + + case USB_GET_TX_FRAME_SIZE: + if (fflags & FWRITE) { + error = ugen_get_frame_size(f_tx, addr); + } else { + error = EINVAL; + } + break; + + case USB_SET_RX_BUFFER_SIZE: + if (fflags & FREAD) { + error = ugen_set_buffer_size(f_rx, addr); + } else { + error = EINVAL; + } + break; + + case USB_SET_TX_BUFFER_SIZE: + if (fflags & FWRITE) { + error = ugen_set_buffer_size(f_tx, addr); + } else { + error = EINVAL; + } + break; + + case USB_GET_RX_BUFFER_SIZE: + if (fflags & FREAD) { + error = ugen_get_buffer_size(f_rx, addr); + } else { + error = EINVAL; + } + break; + + case USB_GET_TX_BUFFER_SIZE: + if (fflags & FWRITE) { + error = ugen_get_buffer_size(f_tx, addr); + } else { + error = EINVAL; + } + break; + + case USB_GET_RX_INTERFACE_DESC: + if (fflags & FREAD) { + error = ugen_get_iface_desc(f_rx, addr); + } else { + error = EINVAL; + } + break; + + case USB_GET_TX_INTERFACE_DESC: + if (fflags & FWRITE) { + error = ugen_get_iface_desc(f_tx, addr); + } else { + error = EINVAL; + } + break; + + case USB_GET_RX_ENDPOINT_DESC: + if (fflags & FREAD) { + error = ugen_get_endpoint_desc(f_rx, addr); + } else { + error = EINVAL; + } + break; + + case USB_GET_TX_ENDPOINT_DESC: + if (fflags & FWRITE) { + error = ugen_get_endpoint_desc(f_tx, addr); + } else { + error = EINVAL; + } + break; + + case USB_SET_RX_STALL_FLAG: + if ((fflags & FREAD) && (*(int *)addr)) { + f_rx->flag_stall = 1; + } + break; + + case USB_SET_TX_STALL_FLAG: + if ((fflags & FWRITE) && (*(int *)addr)) { + f_tx->flag_stall = 1; + } + break; + + default: + error = ENOIOCTL; + break; + } + return (error); +} + +static int +ugen_ioctl_post(struct usb_fifo *f, u_long cmd, void *addr, int fflags) +{ + union { + struct usb_interface_descriptor *idesc; + struct usb_alt_interface *ai; + struct usb_device_descriptor *ddesc; + struct usb_config_descriptor *cdesc; + struct usb_device_stats *stat; + struct usb_fs_init *pinit; + struct usb_fs_uninit *puninit; + uint32_t *ptime; + void *addr; + int *pint; + } u; + struct usb_device_descriptor *dtemp; + struct usb_config_descriptor *ctemp; + struct usb_interface *iface; + int error = 0; + uint8_t n; + + u.addr = addr; + + DPRINTFN(6, "cmd=0x%08lx\n", cmd); + + switch (cmd) { + case USB_DISCOVER: + usb_needs_explore_all(); + break; + + case USB_SETDEBUG: + if (!(fflags & FWRITE)) { + error = EPERM; + break; + } + usb_debug = *(int *)addr; + break; + + case USB_GET_CONFIG: + *(int *)addr = f->udev->curr_config_index; + break; + + case USB_SET_CONFIG: + if (!(fflags & FWRITE)) { + error = EPERM; + break; + } + error = ugen_set_config(f, *(int *)addr); + break; + + case USB_GET_ALTINTERFACE: + iface = usbd_get_iface(f->udev, + u.ai->uai_interface_index); + if (iface && iface->idesc) { + u.ai->uai_alt_index = iface->alt_index; + } else { + error = EINVAL; + } + break; + + case USB_SET_ALTINTERFACE: + if (!(fflags & FWRITE)) { + error = EPERM; + break; + } + error = ugen_set_interface(f, + u.ai->uai_interface_index, u.ai->uai_alt_index); + break; + + case USB_GET_DEVICE_DESC: + dtemp = usbd_get_device_descriptor(f->udev); + if (!dtemp) { + error = EIO; + break; + } + *u.ddesc = *dtemp; + break; + + case USB_GET_CONFIG_DESC: + ctemp = usbd_get_config_descriptor(f->udev); + if (!ctemp) { + error = EIO; + break; + } + *u.cdesc = *ctemp; + break; + + case USB_GET_FULL_DESC: + error = ugen_get_cdesc(f, addr); + break; + + case USB_GET_STRING_DESC: + error = ugen_get_sdesc(f, addr); + break; + + case USB_GET_IFACE_DRIVER: + error = ugen_get_iface_driver(f, addr); + break; + + case USB_REQUEST: + case USB_DO_REQUEST: + if (!(fflags & FWRITE)) { + error = EPERM; + break; + } + error = ugen_do_request(f, addr); + break; + + case USB_DEVICEINFO: + case USB_GET_DEVICEINFO: + error = usb_gen_fill_deviceinfo(f, addr); + break; + + case USB_DEVICESTATS: + for (n = 0; n != 4; n++) { + + u.stat->uds_requests_fail[n] = + f->udev->bus->stats_err.uds_requests[n]; + + u.stat->uds_requests_ok[n] = + f->udev->bus->stats_ok.uds_requests[n]; + } + break; + + case USB_DEVICEENUMERATE: + error = ugen_re_enumerate(f); + break; + + case USB_GET_PLUGTIME: + *u.ptime = f->udev->plugtime; + break; + + case USB_CLAIM_INTERFACE: + case USB_RELEASE_INTERFACE: + /* TODO */ + break; + + case USB_IFACE_DRIVER_ACTIVE: + + n = *u.pint & 0xFF; + + iface = usbd_get_iface(f->udev, n); + + if (iface && iface->subdev) + error = 0; + else + error = ENXIO; + break; + + case USB_IFACE_DRIVER_DETACH: + + error = priv_check(curthread, PRIV_DRIVER); + + if (error) + break; + + n = *u.pint & 0xFF; + + if (n == USB_IFACE_INDEX_ANY) { + error = EINVAL; + break; + } + + usb_detach_device(f->udev, n, 0); + break; + + case USB_SET_POWER_MODE: + error = ugen_set_power_mode(f, *u.pint); + break; + + case USB_GET_POWER_MODE: + *u.pint = ugen_get_power_mode(f); + break; + + case USB_SET_PORT_ENABLE: + error = ugen_do_port_feature(f, + *u.pint, 1, UHF_PORT_ENABLE); + break; + + case USB_SET_PORT_DISABLE: + error = ugen_do_port_feature(f, + *u.pint, 0, UHF_PORT_ENABLE); + break; + + case USB_FS_INIT: + /* verify input parameters */ + if (u.pinit->pEndpoints == NULL) { + error = EINVAL; + break; + } + if (u.pinit->ep_index_max > 127) { + error = EINVAL; + break; + } + if (u.pinit->ep_index_max == 0) { + error = EINVAL; + break; + } + if (f->fs_xfer != NULL) { + error = EBUSY; + break; + } + if (f->dev_ep_index != 0) { + error = EINVAL; + break; + } + if (ugen_fifo_in_use(f, fflags)) { + error = EBUSY; + break; + } + error = usb_fifo_alloc_buffer(f, 1, u.pinit->ep_index_max); + if (error) { + break; + } + f->fs_xfer = malloc(sizeof(f->fs_xfer[0]) * + u.pinit->ep_index_max, M_USB, M_WAITOK | M_ZERO); + if (f->fs_xfer == NULL) { + usb_fifo_free_buffer(f); + error = ENOMEM; + break; + } + f->fs_ep_max = u.pinit->ep_index_max; + f->fs_ep_ptr = u.pinit->pEndpoints; + break; + + case USB_FS_UNINIT: + if (u.puninit->dummy != 0) { + error = EINVAL; + break; + } + error = ugen_fs_uninit(f); + break; + + default: + mtx_lock(f->priv_mtx); + error = ugen_iface_ioctl(f, cmd, addr, fflags); + mtx_unlock(f->priv_mtx); + break; + } + DPRINTFN(6, "error=%d\n", error); + return (error); +} + +static void +ugen_ctrl_fs_callback(struct usb_xfer *xfer, usb_error_t error) +{ + ; /* workaround for a bug in "indent" */ + + DPRINTF("st=%u alen=%u aframes=%u\n", + USB_GET_STATE(xfer), xfer->actlen, xfer->aframes); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + usbd_transfer_submit(xfer); + break; + default: + ugen_fs_set_complete(xfer->priv_sc, USB_P2U(xfer->priv_fifo)); + break; + } +} +#endif /* USB_HAVE_UGEN */ diff --git a/freebsd/sys/dev/usb/usb_generic.h b/freebsd/sys/dev/usb/usb_generic.h new file mode 100644 index 00000000..17506001 --- /dev/null +++ b/freebsd/sys/dev/usb/usb_generic.h @@ -0,0 +1,33 @@ +/* $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. + */ + +#ifndef _USB_GENERIC_HH_ +#define _USB_GENERIC_HH_ + +extern struct usb_fifo_methods usb_ugen_methods; +int ugen_do_request(struct usb_fifo *f, struct usb_ctl_request *ur); + +#endif /* _USB_GENERIC_HH_ */ diff --git a/freebsd/sys/dev/usb/usb_handle_request.c b/freebsd/sys/dev/usb/usb_handle_request.c new file mode 100644 index 00000000..40428d8b --- /dev/null +++ b/freebsd/sys/dev/usb/usb_handle_request.c @@ -0,0 +1,807 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/* $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. + */ + +#include <freebsd/sys/stdint.h> +#include <freebsd/sys/stddef.h> +#include <freebsd/sys/param.h> +#include <freebsd/sys/queue.h> +#include <freebsd/sys/types.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/bus.h> +#include <freebsd/sys/linker_set.h> +#include <freebsd/sys/module.h> +#include <freebsd/sys/lock.h> +#include <freebsd/sys/mutex.h> +#include <freebsd/sys/condvar.h> +#include <freebsd/sys/sysctl.h> +#include <freebsd/sys/sx.h> +#include <freebsd/sys/unistd.h> +#include <freebsd/sys/callout.h> +#include <freebsd/sys/malloc.h> +#include <freebsd/sys/priv.h> + +#include <freebsd/dev/usb/usb.h> +#include <freebsd/dev/usb/usbdi.h> +#include <freebsd/dev/usb/usbdi_util.h> +#include <freebsd/local/usb_if.h> + +#define USB_DEBUG_VAR usb_debug + +#include <freebsd/dev/usb/usb_core.h> +#include <freebsd/dev/usb/usb_process.h> +#include <freebsd/dev/usb/usb_busdma.h> +#include <freebsd/dev/usb/usb_transfer.h> +#include <freebsd/dev/usb/usb_device.h> +#include <freebsd/dev/usb/usb_debug.h> +#include <freebsd/dev/usb/usb_dynamic.h> +#include <freebsd/dev/usb/usb_hub.h> + +#include <freebsd/dev/usb/usb_controller.h> +#include <freebsd/dev/usb/usb_bus.h> + +/* function prototypes */ + +static uint8_t usb_handle_get_stall(struct usb_device *, uint8_t); +static usb_error_t usb_handle_remote_wakeup(struct usb_xfer *, uint8_t); +static usb_error_t usb_handle_request(struct usb_xfer *); +static usb_error_t usb_handle_set_config(struct usb_xfer *, uint8_t); +static usb_error_t usb_handle_set_stall(struct usb_xfer *, uint8_t, + uint8_t); +static usb_error_t usb_handle_iface_request(struct usb_xfer *, void **, + uint16_t *, struct usb_device_request, uint16_t, + uint8_t); + +/*------------------------------------------------------------------------* + * usb_handle_request_callback + * + * This function is the USB callback for generic USB Device control + * transfers. + *------------------------------------------------------------------------*/ +void +usb_handle_request_callback(struct usb_xfer *xfer, usb_error_t error) +{ + usb_error_t err; + + /* check the current transfer state */ + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + case USB_ST_TRANSFERRED: + + /* handle the request */ + err = usb_handle_request(xfer); + + if (err) { + + if (err == USB_ERR_BAD_CONTEXT) { + /* we need to re-setup the control transfer */ + usb_needs_explore(xfer->xroot->bus, 0); + break; + } + goto tr_restart; + } + usbd_transfer_submit(xfer); + break; + + default: + /* check if a control transfer is active */ + if (xfer->flags_int.control_rem != 0xFFFF) { + /* handle the request */ + err = usb_handle_request(xfer); + } + if (xfer->error != USB_ERR_CANCELLED) { + /* should not happen - try stalling */ + goto tr_restart; + } + break; + } + return; + +tr_restart: + /* + * If a control transfer is active, stall it, and wait for the + * next control transfer. + */ + usbd_xfer_set_frame_len(xfer, 0, sizeof(struct usb_device_request)); + xfer->nframes = 1; + xfer->flags.manual_status = 1; + xfer->flags.force_short_xfer = 0; + usbd_xfer_set_stall(xfer); /* cancel previous transfer, if any */ + usbd_transfer_submit(xfer); +} + +/*------------------------------------------------------------------------* + * usb_handle_set_config + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static usb_error_t +usb_handle_set_config(struct usb_xfer *xfer, uint8_t conf_no) +{ + struct usb_device *udev = xfer->xroot->udev; + usb_error_t err = 0; + + /* + * We need to protect against other threads doing probe and + * attach: + */ + USB_XFER_UNLOCK(xfer); + + usbd_enum_lock(udev); + + if (conf_no == USB_UNCONFIG_NO) { + conf_no = USB_UNCONFIG_INDEX; + } else { + /* + * The relationship between config number and config index + * is very simple in our case: + */ + conf_no--; + } + + if (usbd_set_config_index(udev, conf_no)) { + DPRINTF("set config %d failed\n", conf_no); + err = USB_ERR_STALLED; + goto done; + } + if (usb_probe_and_attach(udev, USB_IFACE_INDEX_ANY)) { + DPRINTF("probe and attach failed\n"); + err = USB_ERR_STALLED; + goto done; + } +done: + usbd_enum_unlock(udev); + USB_XFER_LOCK(xfer); + return (err); +} + +static usb_error_t +usb_check_alt_setting(struct usb_device *udev, + struct usb_interface *iface, uint8_t alt_index) +{ + uint8_t do_unlock; + usb_error_t err = 0; + + /* automatic locking */ + if (usbd_enum_is_locked(udev)) { + do_unlock = 0; + } else { + do_unlock = 1; + usbd_enum_lock(udev); + } + + if (alt_index >= usbd_get_no_alts(udev->cdesc, iface->idesc)) + err = USB_ERR_INVAL; + + if (do_unlock) + usbd_enum_unlock(udev); + + return (err); +} + +/*------------------------------------------------------------------------* + * usb_handle_iface_request + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static usb_error_t +usb_handle_iface_request(struct usb_xfer *xfer, + void **ppdata, uint16_t *plen, + struct usb_device_request req, uint16_t off, uint8_t state) +{ + struct usb_interface *iface; + struct usb_interface *iface_parent; /* parent interface */ + struct usb_device *udev = xfer->xroot->udev; + int error; + uint8_t iface_index; + uint8_t temp_state; + + if ((req.bmRequestType & 0x1F) == UT_INTERFACE) { + iface_index = req.wIndex[0]; /* unicast */ + } else { + iface_index = 0; /* broadcast */ + } + + /* + * We need to protect against other threads doing probe and + * attach: + */ + USB_XFER_UNLOCK(xfer); + + usbd_enum_lock(udev); + + error = ENXIO; + +tr_repeat: + iface = usbd_get_iface(udev, iface_index); + if ((iface == NULL) || + (iface->idesc == NULL)) { + /* end of interfaces non-existing interface */ + goto tr_stalled; + } + /* set initial state */ + + temp_state = state; + + /* forward request to interface, if any */ + + if ((error != 0) && + (error != ENOTTY) && + (iface->subdev != NULL) && + device_is_attached(iface->subdev)) { +#if 0 + DEVMETHOD(usb_handle_request, NULL); /* dummy */ +#endif + error = USB_HANDLE_REQUEST(iface->subdev, + &req, ppdata, plen, + off, &temp_state); + } + iface_parent = usbd_get_iface(udev, iface->parent_iface_index); + + if ((iface_parent == NULL) || + (iface_parent->idesc == NULL)) { + /* non-existing interface */ + iface_parent = NULL; + } + /* forward request to parent interface, if any */ + + if ((error != 0) && + (error != ENOTTY) && + (iface_parent != NULL) && + (iface_parent->subdev != NULL) && + ((req.bmRequestType & 0x1F) == UT_INTERFACE) && + (iface_parent->subdev != iface->subdev) && + device_is_attached(iface_parent->subdev)) { + error = USB_HANDLE_REQUEST(iface_parent->subdev, + &req, ppdata, plen, off, &temp_state); + } + if (error == 0) { + /* negativly adjust pointer and length */ + *ppdata = ((uint8_t *)(*ppdata)) - off; + *plen += off; + + if ((state == USB_HR_NOT_COMPLETE) && + (temp_state == USB_HR_COMPLETE_OK)) + goto tr_short; + else + goto tr_valid; + } else if (error == ENOTTY) { + goto tr_stalled; + } + if ((req.bmRequestType & 0x1F) != UT_INTERFACE) { + iface_index++; /* iterate */ + goto tr_repeat; + } + if (state != USB_HR_NOT_COMPLETE) { + /* we are complete */ + goto tr_valid; + } + switch (req.bmRequestType) { + case UT_WRITE_INTERFACE: + switch (req.bRequest) { + case UR_SET_INTERFACE: + /* + * We assume that the endpoints are the same + * accross the alternate settings. + * + * Reset the endpoints, because re-attaching + * only a part of the device is not possible. + */ + error = usb_check_alt_setting(udev, + iface, req.wValue[0]); + if (error) { + DPRINTF("alt setting does not exist %s\n", + usbd_errstr(error)); + goto tr_stalled; + } + error = usb_reset_iface_endpoints(udev, iface_index); + if (error) { + DPRINTF("alt setting failed %s\n", + usbd_errstr(error)); + goto tr_stalled; + } + /* update the current alternate setting */ + iface->alt_index = req.wValue[0]; + break; + + default: + goto tr_stalled; + } + break; + + case UT_READ_INTERFACE: + switch (req.bRequest) { + case UR_GET_INTERFACE: + *ppdata = &iface->alt_index; + *plen = 1; + break; + + default: + goto tr_stalled; + } + break; + default: + goto tr_stalled; + } +tr_valid: + usbd_enum_unlock(udev); + USB_XFER_LOCK(xfer); + return (0); + +tr_short: + usbd_enum_unlock(udev); + USB_XFER_LOCK(xfer); + return (USB_ERR_SHORT_XFER); + +tr_stalled: + usbd_enum_unlock(udev); + USB_XFER_LOCK(xfer); + return (USB_ERR_STALLED); +} + +/*------------------------------------------------------------------------* + * usb_handle_stall + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static usb_error_t +usb_handle_set_stall(struct usb_xfer *xfer, uint8_t ep, uint8_t do_stall) +{ + struct usb_device *udev = xfer->xroot->udev; + usb_error_t err; + + USB_XFER_UNLOCK(xfer); + err = usbd_set_endpoint_stall(udev, + usbd_get_ep_by_addr(udev, ep), do_stall); + USB_XFER_LOCK(xfer); + return (err); +} + +/*------------------------------------------------------------------------* + * usb_handle_get_stall + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +usb_handle_get_stall(struct usb_device *udev, uint8_t ea_val) +{ + struct usb_endpoint *ep; + uint8_t halted; + + ep = usbd_get_ep_by_addr(udev, ea_val); + if (ep == NULL) { + /* nothing to do */ + return (0); + } + USB_BUS_LOCK(udev->bus); + halted = ep->is_stalled; + USB_BUS_UNLOCK(udev->bus); + + return (halted); +} + +/*------------------------------------------------------------------------* + * usb_handle_remote_wakeup + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static usb_error_t +usb_handle_remote_wakeup(struct usb_xfer *xfer, uint8_t is_on) +{ + struct usb_device *udev; + struct usb_bus *bus; + + udev = xfer->xroot->udev; + bus = udev->bus; + + USB_BUS_LOCK(bus); + + if (is_on) { + udev->flags.remote_wakeup = 1; + } else { + udev->flags.remote_wakeup = 0; + } + + USB_BUS_UNLOCK(bus); + + /* In case we are out of sync, update the power state. */ + usb_bus_power_update(udev->bus); + return (0); /* success */ +} + +/*------------------------------------------------------------------------* + * usb_handle_request + * + * Internal state sequence: + * + * USB_HR_NOT_COMPLETE -> USB_HR_COMPLETE_OK v USB_HR_COMPLETE_ERR + * + * Returns: + * 0: Ready to start hardware + * Else: Stall current transfer, if any + *------------------------------------------------------------------------*/ +static usb_error_t +usb_handle_request(struct usb_xfer *xfer) +{ + struct usb_device_request req; + struct usb_device *udev; + const void *src_zcopy; /* zero-copy source pointer */ + const void *src_mcopy; /* non zero-copy source pointer */ + uint16_t off; /* data offset */ + uint16_t rem; /* data remainder */ + uint16_t max_len; /* max fragment length */ + uint16_t wValue; + uint16_t wIndex; + uint8_t state; + uint8_t is_complete = 1; + usb_error_t err; + union { + uWord wStatus; + uint8_t buf[2]; + } temp; + + /* + * Filter the USB transfer state into + * something which we understand: + */ + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + state = USB_HR_NOT_COMPLETE; + + if (!xfer->flags_int.control_act) { + /* nothing to do */ + goto tr_stalled; + } + break; + case USB_ST_TRANSFERRED: + if (!xfer->flags_int.control_act) { + state = USB_HR_COMPLETE_OK; + } else { + state = USB_HR_NOT_COMPLETE; + } + break; + default: + state = USB_HR_COMPLETE_ERR; + break; + } + + /* reset frame stuff */ + + usbd_xfer_set_frame_len(xfer, 0, 0); + + usbd_xfer_set_frame_offset(xfer, 0, 0); + usbd_xfer_set_frame_offset(xfer, sizeof(req), 1); + + /* get the current request, if any */ + + usbd_copy_out(xfer->frbuffers, 0, &req, sizeof(req)); + + if (xfer->flags_int.control_rem == 0xFFFF) { + /* first time - not initialised */ + rem = UGETW(req.wLength); + off = 0; + } else { + /* not first time - initialised */ + rem = xfer->flags_int.control_rem; + off = UGETW(req.wLength) - rem; + } + + /* set some defaults */ + + max_len = 0; + src_zcopy = NULL; + src_mcopy = NULL; + udev = xfer->xroot->udev; + + /* get some request fields decoded */ + + wValue = UGETW(req.wValue); + wIndex = UGETW(req.wIndex); + + DPRINTF("req 0x%02x 0x%02x 0x%04x 0x%04x " + "off=0x%x rem=0x%x, state=%d\n", req.bmRequestType, + req.bRequest, wValue, wIndex, off, rem, state); + + /* demultiplex the control request */ + + switch (req.bmRequestType) { + case UT_READ_DEVICE: + if (state != USB_HR_NOT_COMPLETE) { + break; + } + switch (req.bRequest) { + case UR_GET_DESCRIPTOR: + goto tr_handle_get_descriptor; + case UR_GET_CONFIG: + goto tr_handle_get_config; + case UR_GET_STATUS: + goto tr_handle_get_status; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_DEVICE: + switch (req.bRequest) { + case UR_SET_ADDRESS: + goto tr_handle_set_address; + case UR_SET_CONFIG: + goto tr_handle_set_config; + case UR_CLEAR_FEATURE: + switch (wValue) { + case UF_DEVICE_REMOTE_WAKEUP: + goto tr_handle_clear_wakeup; + default: + goto tr_stalled; + } + break; + case UR_SET_FEATURE: + switch (wValue) { + case UF_DEVICE_REMOTE_WAKEUP: + goto tr_handle_set_wakeup; + default: + goto tr_stalled; + } + break; + default: + goto tr_stalled; + } + break; + + case UT_WRITE_ENDPOINT: + switch (req.bRequest) { + case UR_CLEAR_FEATURE: + switch (wValue) { + case UF_ENDPOINT_HALT: + goto tr_handle_clear_halt; + default: + goto tr_stalled; + } + break; + case UR_SET_FEATURE: + switch (wValue) { + case UF_ENDPOINT_HALT: + goto tr_handle_set_halt; + default: + goto tr_stalled; + } + break; + default: + goto tr_stalled; + } + break; + + case UT_READ_ENDPOINT: + switch (req.bRequest) { + case UR_GET_STATUS: + goto tr_handle_get_ep_status; + default: + goto tr_stalled; + } + break; + default: + /* we use "USB_ADD_BYTES" to de-const the src_zcopy */ + err = usb_handle_iface_request(xfer, + USB_ADD_BYTES(&src_zcopy, 0), + &max_len, req, off, state); + if (err == 0) { + is_complete = 0; + goto tr_valid; + } else if (err == USB_ERR_SHORT_XFER) { + goto tr_valid; + } + /* + * Reset zero-copy pointer and max length + * variable in case they were unintentionally + * set: + */ + src_zcopy = NULL; + max_len = 0; + + /* + * Check if we have a vendor specific + * descriptor: + */ + goto tr_handle_get_descriptor; + } + goto tr_valid; + +tr_handle_get_descriptor: + err = (usb_temp_get_desc_p) (udev, &req, &src_zcopy, &max_len); + if (err) + goto tr_stalled; + if (src_zcopy == NULL) + goto tr_stalled; + goto tr_valid; + +tr_handle_get_config: + temp.buf[0] = udev->curr_config_no; + src_mcopy = temp.buf; + max_len = 1; + goto tr_valid; + +tr_handle_get_status: + + wValue = 0; + + USB_BUS_LOCK(udev->bus); + if (udev->flags.remote_wakeup) { + wValue |= UDS_REMOTE_WAKEUP; + } + if (udev->flags.self_powered) { + wValue |= UDS_SELF_POWERED; + } + USB_BUS_UNLOCK(udev->bus); + + USETW(temp.wStatus, wValue); + src_mcopy = temp.wStatus; + max_len = sizeof(temp.wStatus); + goto tr_valid; + +tr_handle_set_address: + if (state == USB_HR_NOT_COMPLETE) { + if (wValue >= 0x80) { + /* invalid value */ + goto tr_stalled; + } else if (udev->curr_config_no != 0) { + /* we are configured ! */ + goto tr_stalled; + } + } else if (state != USB_HR_NOT_COMPLETE) { + udev->address = (wValue & 0x7F); + goto tr_bad_context; + } + goto tr_valid; + +tr_handle_set_config: + if (state == USB_HR_NOT_COMPLETE) { + if (usb_handle_set_config(xfer, req.wValue[0])) { + goto tr_stalled; + } + } + goto tr_valid; + +tr_handle_clear_halt: + if (state == USB_HR_NOT_COMPLETE) { + if (usb_handle_set_stall(xfer, req.wIndex[0], 0)) { + goto tr_stalled; + } + } + goto tr_valid; + +tr_handle_clear_wakeup: + if (state == USB_HR_NOT_COMPLETE) { + if (usb_handle_remote_wakeup(xfer, 0)) { + goto tr_stalled; + } + } + goto tr_valid; + +tr_handle_set_halt: + if (state == USB_HR_NOT_COMPLETE) { + if (usb_handle_set_stall(xfer, req.wIndex[0], 1)) { + goto tr_stalled; + } + } + goto tr_valid; + +tr_handle_set_wakeup: + if (state == USB_HR_NOT_COMPLETE) { + if (usb_handle_remote_wakeup(xfer, 1)) { + goto tr_stalled; + } + } + goto tr_valid; + +tr_handle_get_ep_status: + if (state == USB_HR_NOT_COMPLETE) { + temp.wStatus[0] = + usb_handle_get_stall(udev, req.wIndex[0]); + temp.wStatus[1] = 0; + src_mcopy = temp.wStatus; + max_len = sizeof(temp.wStatus); + } + goto tr_valid; + +tr_valid: + if (state != USB_HR_NOT_COMPLETE) { + goto tr_stalled; + } + /* subtract offset from length */ + + max_len -= off; + + /* Compute the real maximum data length */ + + if (max_len > xfer->max_data_length) { + max_len = usbd_xfer_max_len(xfer); + } + if (max_len > rem) { + max_len = rem; + } + /* + * If the remainder is greater than the maximum data length, + * we need to truncate the value for the sake of the + * comparison below: + */ + if (rem > xfer->max_data_length) { + rem = usbd_xfer_max_len(xfer); + } + if ((rem != max_len) && (is_complete != 0)) { + /* + * If we don't transfer the data we can transfer, then + * the transfer is short ! + */ + xfer->flags.force_short_xfer = 1; + xfer->nframes = 2; + } else { + /* + * Default case + */ + xfer->flags.force_short_xfer = 0; + xfer->nframes = max_len ? 2 : 1; + } + if (max_len > 0) { + if (src_mcopy) { + src_mcopy = USB_ADD_BYTES(src_mcopy, off); + usbd_copy_in(xfer->frbuffers + 1, 0, + src_mcopy, max_len); + usbd_xfer_set_frame_len(xfer, 1, max_len); + } else { + usbd_xfer_set_frame_data(xfer, 1, + USB_ADD_BYTES(src_zcopy, off), max_len); + } + } else { + /* the end is reached, send status */ + xfer->flags.manual_status = 0; + usbd_xfer_set_frame_len(xfer, 1, 0); + } + DPRINTF("success\n"); + return (0); /* success */ + +tr_stalled: + DPRINTF("%s\n", (state != USB_HR_NOT_COMPLETE) ? + "complete" : "stalled"); + return (USB_ERR_STALLED); + +tr_bad_context: + DPRINTF("bad context\n"); + return (USB_ERR_BAD_CONTEXT); +} diff --git a/freebsd/sys/dev/usb/usb_hid.c b/freebsd/sys/dev/usb/usb_hid.c new file mode 100644 index 00000000..b1780aff --- /dev/null +++ b/freebsd/sys/dev/usb/usb_hid.c @@ -0,0 +1,820 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/* $NetBSD: hid.c,v 1.17 2001/11/13 06:24:53 lukem Exp $ */ + + +#include <freebsd/sys/cdefs.h> +__FBSDID("$FreeBSD$"); +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <freebsd/sys/stdint.h> +#include <freebsd/sys/stddef.h> +#include <freebsd/sys/param.h> +#include <freebsd/sys/queue.h> +#include <freebsd/sys/types.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/bus.h> +#include <freebsd/sys/linker_set.h> +#include <freebsd/sys/module.h> +#include <freebsd/sys/lock.h> +#include <freebsd/sys/mutex.h> +#include <freebsd/sys/condvar.h> +#include <freebsd/sys/sysctl.h> +#include <freebsd/sys/sx.h> +#include <freebsd/sys/unistd.h> +#include <freebsd/sys/callout.h> +#include <freebsd/sys/malloc.h> +#include <freebsd/sys/priv.h> + +#include <freebsd/dev/usb/usb.h> +#include <freebsd/dev/usb/usbdi.h> +#include <freebsd/dev/usb/usbdi_util.h> +#include <freebsd/dev/usb/usbhid.h> + +#define USB_DEBUG_VAR usb_debug + +#include <freebsd/dev/usb/usb_core.h> +#include <freebsd/dev/usb/usb_debug.h> +#include <freebsd/dev/usb/usb_process.h> +#include <freebsd/dev/usb/usb_device.h> +#include <freebsd/dev/usb/usb_request.h> + +static void hid_clear_local(struct hid_item *); +static uint8_t hid_get_byte(struct hid_data *s, const uint16_t wSize); + +#define MAXUSAGE 64 +#define MAXPUSH 4 +#define MAXID 16 + +struct hid_pos_data { + int32_t rid; + uint32_t pos; +}; + +struct hid_data { + const uint8_t *start; + const uint8_t *end; + const uint8_t *p; + struct hid_item cur[MAXPUSH]; + struct hid_pos_data last_pos[MAXID]; + int32_t usages_min[MAXUSAGE]; + int32_t usages_max[MAXUSAGE]; + int32_t usage_last; /* last seen usage */ + uint32_t loc_size; /* last seen size */ + uint32_t loc_count; /* last seen count */ + uint8_t kindset; /* we have 5 kinds so 8 bits are enough */ + uint8_t pushlevel; /* current pushlevel */ + uint8_t ncount; /* end usage item count */ + uint8_t icount; /* current usage item count */ + uint8_t nusage; /* end "usages_min/max" index */ + uint8_t iusage; /* current "usages_min/max" index */ + uint8_t ousage; /* current "usages_min/max" offset */ + uint8_t susage; /* usage set flags */ +}; + +/*------------------------------------------------------------------------* + * hid_clear_local + *------------------------------------------------------------------------*/ +static void +hid_clear_local(struct hid_item *c) +{ + + c->loc.count = 0; + c->loc.size = 0; + c->usage = 0; + c->usage_minimum = 0; + c->usage_maximum = 0; + c->designator_index = 0; + c->designator_minimum = 0; + c->designator_maximum = 0; + c->string_index = 0; + c->string_minimum = 0; + c->string_maximum = 0; + c->set_delimiter = 0; +} + +static void +hid_switch_rid(struct hid_data *s, struct hid_item *c, int32_t next_rID) +{ + uint8_t i; + + /* check for same report ID - optimise */ + + if (c->report_ID == next_rID) + return; + + /* save current position for current rID */ + + if (c->report_ID == 0) { + i = 0; + } else { + for (i = 1; i != MAXID; i++) { + if (s->last_pos[i].rid == c->report_ID) + break; + if (s->last_pos[i].rid == 0) + break; + } + } + if (i != MAXID) { + s->last_pos[i].rid = c->report_ID; + s->last_pos[i].pos = c->loc.pos; + } + + /* store next report ID */ + + c->report_ID = next_rID; + + /* lookup last position for next rID */ + + if (next_rID == 0) { + i = 0; + } else { + for (i = 1; i != MAXID; i++) { + if (s->last_pos[i].rid == next_rID) + break; + if (s->last_pos[i].rid == 0) + break; + } + } + if (i != MAXID) { + s->last_pos[i].rid = next_rID; + c->loc.pos = s->last_pos[i].pos; + } else { + DPRINTF("Out of RID entries, position is set to zero!\n"); + c->loc.pos = 0; + } +} + +/*------------------------------------------------------------------------* + * hid_start_parse + *------------------------------------------------------------------------*/ +struct hid_data * +hid_start_parse(const void *d, usb_size_t len, int kindset) +{ + struct hid_data *s; + + if ((kindset-1) & kindset) { + DPRINTFN(0, "Only one bit can be " + "set in the kindset\n"); + return (NULL); + } + + s = malloc(sizeof *s, M_TEMP, M_WAITOK | M_ZERO); + s->start = s->p = d; + s->end = ((const uint8_t *)d) + len; + s->kindset = kindset; + return (s); +} + +/*------------------------------------------------------------------------* + * hid_end_parse + *------------------------------------------------------------------------*/ +void +hid_end_parse(struct hid_data *s) +{ + if (s == NULL) + return; + + free(s, M_TEMP); +} + +/*------------------------------------------------------------------------* + * get byte from HID descriptor + *------------------------------------------------------------------------*/ +static uint8_t +hid_get_byte(struct hid_data *s, const uint16_t wSize) +{ + const uint8_t *ptr; + uint8_t retval; + + ptr = s->p; + + /* check if end is reached */ + if (ptr == s->end) + return (0); + + /* read out a byte */ + retval = *ptr; + + /* check if data pointer can be advanced by "wSize" bytes */ + if ((s->end - ptr) < wSize) + ptr = s->end; + else + ptr += wSize; + + /* update pointer */ + s->p = ptr; + + return (retval); +} + +/*------------------------------------------------------------------------* + * hid_get_item + *------------------------------------------------------------------------*/ +int +hid_get_item(struct hid_data *s, struct hid_item *h) +{ + struct hid_item *c; + unsigned int bTag, bType, bSize; + uint32_t oldpos; + int32_t mask; + int32_t dval; + + if (s == NULL) + return (0); + + c = &s->cur[s->pushlevel]; + + top: + /* check if there is an array of items */ + if (s->icount < s->ncount) { + /* get current usage */ + if (s->iusage < s->nusage) { + dval = s->usages_min[s->iusage] + s->ousage; + c->usage = dval; + s->usage_last = dval; + if (dval == s->usages_max[s->iusage]) { + s->iusage ++; + s->ousage = 0; + } else { + s->ousage ++; + } + } else { + DPRINTFN(1, "Using last usage\n"); + dval = s->usage_last; + } + s->icount ++; + /* + * Only copy HID item, increment position and return + * if correct kindset! + */ + if (s->kindset & (1 << c->kind)) { + *h = *c; + DPRINTFN(1, "%u,%u,%u\n", h->loc.pos, + h->loc.size, h->loc.count); + c->loc.pos += c->loc.size * c->loc.count; + return (1); + } + } + + /* reset state variables */ + s->icount = 0; + s->ncount = 0; + s->iusage = 0; + s->nusage = 0; + s->susage = 0; + s->ousage = 0; + hid_clear_local(c); + + /* get next item */ + while (s->p != s->end) { + + bSize = hid_get_byte(s, 1); + if (bSize == 0xfe) { + /* long item */ + bSize = hid_get_byte(s, 1); + bSize |= hid_get_byte(s, 1) << 8; + bTag = hid_get_byte(s, 1); + bType = 0xff; /* XXX what should it be */ + } else { + /* short item */ + bTag = bSize >> 4; + bType = (bSize >> 2) & 3; + bSize &= 3; + if (bSize == 3) + bSize = 4; + } + switch (bSize) { + case 0: + dval = 0; + mask = 0; + break; + case 1: + dval = (int8_t)hid_get_byte(s, 1); + mask = 0xFF; + break; + case 2: + dval = hid_get_byte(s, 1); + dval |= hid_get_byte(s, 1) << 8; + dval = (int16_t)dval; + mask = 0xFFFF; + break; + case 4: + dval = hid_get_byte(s, 1); + dval |= hid_get_byte(s, 1) << 8; + dval |= hid_get_byte(s, 1) << 16; + dval |= hid_get_byte(s, 1) << 24; + mask = 0xFFFFFFFF; + break; + default: + dval = hid_get_byte(s, bSize); + DPRINTFN(0, "bad length %u (data=0x%02x)\n", + bSize, dval); + continue; + } + + switch (bType) { + case 0: /* Main */ + switch (bTag) { + case 8: /* Input */ + c->kind = hid_input; + c->flags = dval; + ret: + c->loc.count = s->loc_count; + c->loc.size = s->loc_size; + + if (c->flags & HIO_VARIABLE) { + /* range check usage count */ + if (c->loc.count > 255) { + DPRINTFN(0, "Number of " + "items truncated to 255\n"); + s->ncount = 255; + } else + s->ncount = c->loc.count; + + /* + * The "top" loop will return + * one and one item: + */ + c->loc.count = 1; + } else { + s->ncount = 1; + } + goto top; + + case 9: /* Output */ + c->kind = hid_output; + c->flags = dval; + goto ret; + case 10: /* Collection */ + c->kind = hid_collection; + c->collection = dval; + c->collevel++; + c->usage = s->usage_last; + *h = *c; + return (1); + case 11: /* Feature */ + c->kind = hid_feature; + c->flags = dval; + goto ret; + case 12: /* End collection */ + c->kind = hid_endcollection; + if (c->collevel == 0) { + DPRINTFN(0, "invalid end collection\n"); + return (0); + } + c->collevel--; + *h = *c; + return (1); + default: + DPRINTFN(0, "Main bTag=%d\n", bTag); + break; + } + break; + case 1: /* Global */ + switch (bTag) { + case 0: + c->_usage_page = dval << 16; + break; + case 1: + c->logical_minimum = dval; + break; + case 2: + c->logical_maximum = dval; + break; + case 3: + c->physical_minimum = dval; + break; + case 4: + c->physical_maximum = dval; + break; + case 5: + c->unit_exponent = dval; + break; + case 6: + c->unit = dval; + break; + case 7: + /* mask because value is unsigned */ + s->loc_size = dval & mask; + break; + case 8: + hid_switch_rid(s, c, dval); + break; + case 9: + /* mask because value is unsigned */ + s->loc_count = dval & mask; + break; + case 10: /* Push */ + s->pushlevel ++; + if (s->pushlevel < MAXPUSH) { + s->cur[s->pushlevel] = *c; + /* store size and count */ + c->loc.size = s->loc_size; + c->loc.count = s->loc_count; + /* update current item pointer */ + c = &s->cur[s->pushlevel]; + } else { + DPRINTFN(0, "Cannot push " + "item @ %d\n", s->pushlevel); + } + break; + case 11: /* Pop */ + s->pushlevel --; + if (s->pushlevel < MAXPUSH) { + /* preserve position */ + oldpos = c->loc.pos; + c = &s->cur[s->pushlevel]; + /* restore size and count */ + s->loc_size = c->loc.size; + s->loc_count = c->loc.count; + /* set default item location */ + c->loc.pos = oldpos; + c->loc.size = 0; + c->loc.count = 0; + } else { + DPRINTFN(0, "Cannot pop " + "item @ %d\n", s->pushlevel); + } + break; + default: + DPRINTFN(0, "Global bTag=%d\n", bTag); + break; + } + break; + case 2: /* Local */ + switch (bTag) { + case 0: + if (bSize != 4) + dval = (dval & mask) | c->_usage_page; + + /* set last usage, in case of a collection */ + s->usage_last = dval; + + if (s->nusage < MAXUSAGE) { + s->usages_min[s->nusage] = dval; + s->usages_max[s->nusage] = dval; + s->nusage ++; + } else { + DPRINTFN(0, "max usage reached\n"); + } + + /* clear any pending usage sets */ + s->susage = 0; + break; + case 1: + s->susage |= 1; + + if (bSize != 4) + dval = (dval & mask) | c->_usage_page; + c->usage_minimum = dval; + + goto check_set; + case 2: + s->susage |= 2; + + if (bSize != 4) + dval = (dval & mask) | c->_usage_page; + c->usage_maximum = dval; + + check_set: + if (s->susage != 3) + break; + + /* sanity check */ + if ((s->nusage < MAXUSAGE) && + (c->usage_minimum <= c->usage_maximum)) { + /* add usage range */ + s->usages_min[s->nusage] = + c->usage_minimum; + s->usages_max[s->nusage] = + c->usage_maximum; + s->nusage ++; + } else { + DPRINTFN(0, "Usage set dropped\n"); + } + s->susage = 0; + break; + case 3: + c->designator_index = dval; + break; + case 4: + c->designator_minimum = dval; + break; + case 5: + c->designator_maximum = dval; + break; + case 7: + c->string_index = dval; + break; + case 8: + c->string_minimum = dval; + break; + case 9: + c->string_maximum = dval; + break; + case 10: + c->set_delimiter = dval; + break; + default: + DPRINTFN(0, "Local bTag=%d\n", bTag); + break; + } + break; + default: + DPRINTFN(0, "default bType=%d\n", bType); + break; + } + } + return (0); +} + +/*------------------------------------------------------------------------* + * hid_report_size + *------------------------------------------------------------------------*/ +int +hid_report_size(const void *buf, usb_size_t len, enum hid_kind k, uint8_t *id) +{ + struct hid_data *d; + struct hid_item h; + uint32_t temp; + uint32_t hpos; + uint32_t lpos; + uint8_t any_id; + + any_id = 0; + hpos = 0; + lpos = 0xFFFFFFFF; + + for (d = hid_start_parse(buf, len, 1 << k); hid_get_item(d, &h);) { + if (h.kind == k) { + /* check for ID-byte presense */ + if ((h.report_ID != 0) && !any_id) { + if (id != NULL) + *id = h.report_ID; + any_id = 1; + } + /* compute minimum */ + if (lpos > h.loc.pos) + lpos = h.loc.pos; + /* compute end position */ + temp = h.loc.pos + (h.loc.size * h.loc.count); + /* compute maximum */ + if (hpos < temp) + hpos = temp; + } + } + hid_end_parse(d); + + /* safety check - can happen in case of currupt descriptors */ + if (lpos > hpos) + temp = 0; + else + temp = hpos - lpos; + + /* check for ID byte */ + if (any_id) + temp += 8; + else if (id != NULL) + *id = 0; + + /* return length in bytes rounded up */ + return ((temp + 7) / 8); +} + +/*------------------------------------------------------------------------* + * hid_locate + *------------------------------------------------------------------------*/ +int +hid_locate(const void *desc, usb_size_t size, uint32_t u, enum hid_kind k, + uint8_t index, struct hid_location *loc, uint32_t *flags, uint8_t *id) +{ + struct hid_data *d; + struct hid_item h; + + for (d = hid_start_parse(desc, size, 1 << k); hid_get_item(d, &h);) { + if (h.kind == k && !(h.flags & HIO_CONST) && h.usage == u) { + if (index--) + continue; + if (loc != NULL) + *loc = h.loc; + if (flags != NULL) + *flags = h.flags; + if (id != NULL) + *id = h.report_ID; + hid_end_parse(d); + return (1); + } + } + if (loc != NULL) + loc->size = 0; + if (flags != NULL) + *flags = 0; + if (id != NULL) + *id = 0; + hid_end_parse(d); + return (0); +} + +/*------------------------------------------------------------------------* + * hid_get_data + *------------------------------------------------------------------------*/ +static uint32_t +hid_get_data_sub(const uint8_t *buf, usb_size_t len, struct hid_location *loc, + int is_signed) +{ + uint32_t hpos = loc->pos; + uint32_t hsize = loc->size; + uint32_t data; + uint32_t rpos; + uint8_t n; + + DPRINTFN(11, "hid_get_data: loc %d/%d\n", hpos, hsize); + + /* Range check and limit */ + if (hsize == 0) + return (0); + if (hsize > 32) + hsize = 32; + + /* Get data in a safe way */ + data = 0; + rpos = (hpos / 8); + n = (hsize + 7) / 8; + rpos += n; + while (n--) { + rpos--; + if (rpos < len) + data |= buf[rpos] << (8 * n); + } + + /* Correctly shift down data */ + data = (data >> (hpos % 8)); + n = 32 - hsize; + + /* Mask and sign extend in one */ + if (is_signed != 0) + data = (int32_t)((int32_t)data << n) >> n; + else + data = (uint32_t)((uint32_t)data << n) >> n; + + DPRINTFN(11, "hid_get_data: loc %d/%d = %lu\n", + loc->pos, loc->size, (long)data); + return (data); +} + +int32_t +hid_get_data(const uint8_t *buf, usb_size_t len, struct hid_location *loc) +{ + return (hid_get_data_sub(buf, len, loc, 1)); +} + +uint32_t +hid_get_data_unsigned(const uint8_t *buf, usb_size_t len, struct hid_location *loc) +{ + return (hid_get_data_sub(buf, len, loc, 0)); +} + +/*------------------------------------------------------------------------* + * hid_is_collection + *------------------------------------------------------------------------*/ +int +hid_is_collection(const void *desc, usb_size_t size, uint32_t usage) +{ + struct hid_data *hd; + struct hid_item hi; + int err; + + hd = hid_start_parse(desc, size, hid_input); + if (hd == NULL) + return (0); + + while ((err = hid_get_item(hd, &hi))) { + if (hi.kind == hid_collection && + hi.usage == usage) + break; + } + hid_end_parse(hd); + return (err); +} + +/*------------------------------------------------------------------------* + * hid_get_descriptor_from_usb + * + * This function will search for a HID descriptor between two USB + * interface descriptors. + * + * Return values: + * NULL: No more HID descriptors. + * Else: Pointer to HID descriptor. + *------------------------------------------------------------------------*/ +struct usb_hid_descriptor * +hid_get_descriptor_from_usb(struct usb_config_descriptor *cd, + struct usb_interface_descriptor *id) +{ + struct usb_descriptor *desc = (void *)id; + + if (desc == NULL) { + return (NULL); + } + while ((desc = usb_desc_foreach(cd, desc))) { + if ((desc->bDescriptorType == UDESC_HID) && + (desc->bLength >= USB_HID_DESCRIPTOR_SIZE(0))) { + return (void *)desc; + } + if (desc->bDescriptorType == UDESC_INTERFACE) { + break; + } + } + return (NULL); +} + +/*------------------------------------------------------------------------* + * usbd_req_get_hid_desc + * + * This function will read out an USB report descriptor from the USB + * device. + * + * Return values: + * NULL: Failure. + * Else: Success. The pointer should eventually be passed to free(). + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_hid_desc(struct usb_device *udev, struct mtx *mtx, + void **descp, uint16_t *sizep, + struct malloc_type *mem, uint8_t iface_index) +{ + struct usb_interface *iface = usbd_get_iface(udev, iface_index); + struct usb_hid_descriptor *hid; + usb_error_t err; + + if ((iface == NULL) || (iface->idesc == NULL)) { + return (USB_ERR_INVAL); + } + hid = hid_get_descriptor_from_usb + (usbd_get_config_descriptor(udev), iface->idesc); + + if (hid == NULL) { + return (USB_ERR_IOERROR); + } + *sizep = UGETW(hid->descrs[0].wDescriptorLength); + if (*sizep == 0) { + return (USB_ERR_IOERROR); + } + if (mtx) + mtx_unlock(mtx); + + *descp = malloc(*sizep, mem, M_ZERO | M_WAITOK); + + if (mtx) + mtx_lock(mtx); + + if (*descp == NULL) { + return (USB_ERR_NOMEM); + } + err = usbd_req_get_report_descriptor + (udev, mtx, *descp, *sizep, iface_index); + + if (err) { + free(*descp, mem); + *descp = NULL; + return (err); + } + return (USB_ERR_NORMAL_COMPLETION); +} diff --git a/freebsd/sys/dev/usb/usb_hub.c b/freebsd/sys/dev/usb/usb_hub.c new file mode 100644 index 00000000..4f34f25e --- /dev/null +++ b/freebsd/sys/dev/usb/usb_hub.c @@ -0,0 +1,2474 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/* $FreeBSD$ */ +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved. + * Copyright (c) 1998 Lennart Augustsson. All rights reserved. + * Copyright (c) 2008-2010 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. + */ + +/* + * USB spec: http://www.usb.org/developers/docs/usbspec.zip + */ + +#include <freebsd/sys/stdint.h> +#include <freebsd/sys/stddef.h> +#include <freebsd/sys/param.h> +#include <freebsd/sys/queue.h> +#include <freebsd/sys/types.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/bus.h> +#include <freebsd/sys/linker_set.h> +#include <freebsd/sys/module.h> +#include <freebsd/sys/lock.h> +#include <freebsd/sys/mutex.h> +#include <freebsd/sys/condvar.h> +#include <freebsd/sys/sysctl.h> +#include <freebsd/sys/sx.h> +#include <freebsd/sys/unistd.h> +#include <freebsd/sys/callout.h> +#include <freebsd/sys/malloc.h> +#include <freebsd/sys/priv.h> + +#include <freebsd/dev/usb/usb.h> +#include <freebsd/dev/usb/usb_ioctl.h> +#include <freebsd/dev/usb/usbdi.h> +#include <freebsd/dev/usb/usbdi_util.h> + +#define USB_DEBUG_VAR uhub_debug + +#include <freebsd/dev/usb/usb_core.h> +#include <freebsd/dev/usb/usb_process.h> +#include <freebsd/dev/usb/usb_device.h> +#include <freebsd/dev/usb/usb_request.h> +#include <freebsd/dev/usb/usb_debug.h> +#include <freebsd/dev/usb/usb_hub.h> +#include <freebsd/dev/usb/usb_util.h> +#include <freebsd/dev/usb/usb_busdma.h> +#include <freebsd/dev/usb/usb_transfer.h> +#include <freebsd/dev/usb/usb_dynamic.h> + +#include <freebsd/dev/usb/usb_controller.h> +#include <freebsd/dev/usb/usb_bus.h> + +#define UHUB_INTR_INTERVAL 250 /* ms */ +#define UHUB_N_TRANSFER 1 + +#ifdef USB_DEBUG +static int uhub_debug = 0; + +SYSCTL_NODE(_hw_usb, OID_AUTO, uhub, CTLFLAG_RW, 0, "USB HUB"); +SYSCTL_INT(_hw_usb_uhub, OID_AUTO, debug, CTLFLAG_RW, &uhub_debug, 0, + "Debug level"); + +TUNABLE_INT("hw.usb.uhub.debug", &uhub_debug); +#endif + +#if USB_HAVE_POWERD +static int usb_power_timeout = 30; /* seconds */ + +SYSCTL_INT(_hw_usb, OID_AUTO, power_timeout, CTLFLAG_RW, + &usb_power_timeout, 0, "USB power timeout"); +#endif + +struct uhub_current_state { + uint16_t port_change; + uint16_t port_status; +}; + +struct uhub_softc { + struct uhub_current_state sc_st;/* current state */ + device_t sc_dev; /* base device */ + struct mtx sc_mtx; /* our mutex */ + struct usb_device *sc_udev; /* USB device */ + struct usb_xfer *sc_xfer[UHUB_N_TRANSFER]; /* interrupt xfer */ + uint8_t sc_flags; +#define UHUB_FLAG_DID_EXPLORE 0x01 + char sc_name[32]; +}; + +#define UHUB_PROTO(sc) ((sc)->sc_udev->ddesc.bDeviceProtocol) +#define UHUB_IS_HIGH_SPEED(sc) (UHUB_PROTO(sc) != UDPROTO_FSHUB) +#define UHUB_IS_SINGLE_TT(sc) (UHUB_PROTO(sc) == UDPROTO_HSHUBSTT) +#define UHUB_IS_SUPER_SPEED(sc) (UHUB_PROTO(sc) == UDPROTO_SSHUB) + +/* prototypes for type checking: */ + +static device_probe_t uhub_probe; +static device_attach_t uhub_attach; +static device_detach_t uhub_detach; +static device_suspend_t uhub_suspend; +static device_resume_t uhub_resume; + +static bus_driver_added_t uhub_driver_added; +static bus_child_location_str_t uhub_child_location_string; +static bus_child_pnpinfo_str_t uhub_child_pnpinfo_string; + +static usb_callback_t uhub_intr_callback; + +static void usb_dev_resume_peer(struct usb_device *udev); +static void usb_dev_suspend_peer(struct usb_device *udev); +static uint8_t usb_peer_should_wakeup(struct usb_device *udev); + +static const struct usb_config uhub_config[UHUB_N_TRANSFER] = { + + [0] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_ANY, + .timeout = 0, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .bufsize = 0, /* use wMaxPacketSize */ + .callback = &uhub_intr_callback, + .interval = UHUB_INTR_INTERVAL, + }, +}; + +/* + * driver instance for "hub" connected to "usb" + * and "hub" connected to "hub" + */ +static devclass_t uhub_devclass; + +static device_method_t uhub_methods[] = { + DEVMETHOD(device_probe, uhub_probe), + DEVMETHOD(device_attach, uhub_attach), + DEVMETHOD(device_detach, uhub_detach), + + DEVMETHOD(device_suspend, uhub_suspend), + DEVMETHOD(device_resume, uhub_resume), + + DEVMETHOD(bus_child_location_str, uhub_child_location_string), + DEVMETHOD(bus_child_pnpinfo_str, uhub_child_pnpinfo_string), + DEVMETHOD(bus_driver_added, uhub_driver_added), + {0, 0} +}; + +static driver_t uhub_driver = { + .name = "uhub", + .methods = uhub_methods, + .size = sizeof(struct uhub_softc) +}; + +DRIVER_MODULE(uhub, usbus, uhub_driver, uhub_devclass, 0, 0); +DRIVER_MODULE(uhub, uhub, uhub_driver, uhub_devclass, NULL, 0); +MODULE_VERSION(uhub, 1); + +static void +uhub_intr_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct uhub_softc *sc = usbd_xfer_softc(xfer); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTFN(2, "\n"); + /* + * This is an indication that some port + * has changed status. Notify the bus + * event handler thread that we need + * to be explored again: + */ + usb_needs_explore(sc->sc_udev->bus, 0); + + case USB_ST_SETUP: + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + break; + + default: /* Error */ + if (xfer->error != USB_ERR_CANCELLED) { + /* + * Do a clear-stall. The "stall_pipe" flag + * will get cleared before next callback by + * the USB stack. + */ + usbd_xfer_set_stall(xfer); + usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer)); + usbd_transfer_submit(xfer); + } + break; + } +} + +/*------------------------------------------------------------------------* + * uhub_explore_sub - subroutine + * + * Return values: + * 0: Success + * Else: A control transaction failed + *------------------------------------------------------------------------*/ +static usb_error_t +uhub_explore_sub(struct uhub_softc *sc, struct usb_port *up) +{ + struct usb_bus *bus; + struct usb_device *child; + uint8_t refcount; + usb_error_t err; + + bus = sc->sc_udev->bus; + err = 0; + + /* get driver added refcount from USB bus */ + refcount = bus->driver_added_refcount; + + /* get device assosiated with the given port */ + child = usb_bus_port_get_device(bus, up); + if (child == NULL) { + /* nothing to do */ + goto done; + } + + /* check if device should be re-enumerated */ + + if (child->flags.usb_mode == USB_MODE_HOST) { + usbd_enum_lock(child); + if (child->re_enumerate_wait) { + err = usbd_set_config_index(child, USB_UNCONFIG_INDEX); + if (err == 0) + err = usbd_req_re_enumerate(child, NULL); + if (err == 0) + err = usbd_set_config_index(child, 0); + if (err == 0) { + err = usb_probe_and_attach(child, + USB_IFACE_INDEX_ANY); + } + child->re_enumerate_wait = 0; + err = 0; + } + usbd_enum_unlock(child); + } + + /* check if probe and attach should be done */ + + if (child->driver_added_refcount != refcount) { + child->driver_added_refcount = refcount; + err = usb_probe_and_attach(child, + USB_IFACE_INDEX_ANY); + if (err) { + goto done; + } + } + /* start control transfer, if device mode */ + + if (child->flags.usb_mode == USB_MODE_DEVICE) + usbd_ctrl_transfer_setup(child); + + /* if a HUB becomes present, do a recursive HUB explore */ + + if (child->hub) + err = (child->hub->explore) (child); + +done: + return (err); +} + +/*------------------------------------------------------------------------* + * uhub_read_port_status - factored out code + *------------------------------------------------------------------------*/ +static usb_error_t +uhub_read_port_status(struct uhub_softc *sc, uint8_t portno) +{ + struct usb_port_status ps; + usb_error_t err; + + err = usbd_req_get_port_status( + sc->sc_udev, NULL, &ps, portno); + + /* update status regardless of error */ + + sc->sc_st.port_status = UGETW(ps.wPortStatus); + sc->sc_st.port_change = UGETW(ps.wPortChange); + + /* debugging print */ + + DPRINTFN(4, "port %d, wPortStatus=0x%04x, " + "wPortChange=0x%04x, err=%s\n", + portno, sc->sc_st.port_status, + sc->sc_st.port_change, usbd_errstr(err)); + return (err); +} + +/*------------------------------------------------------------------------* + * uhub_reattach_port + * + * Returns: + * 0: Success + * Else: A control transaction failed + *------------------------------------------------------------------------*/ +static usb_error_t +uhub_reattach_port(struct uhub_softc *sc, uint8_t portno) +{ + struct usb_device *child; + struct usb_device *udev; + enum usb_dev_speed speed; + enum usb_hc_mode mode; + usb_error_t err; + uint8_t timeout; + + DPRINTF("reattaching port %d\n", portno); + + err = 0; + timeout = 0; + udev = sc->sc_udev; + child = usb_bus_port_get_device(udev->bus, + udev->hub->ports + portno - 1); + +repeat: + + /* first clear the port connection change bit */ + + err = usbd_req_clear_port_feature(udev, NULL, + portno, UHF_C_PORT_CONNECTION); + + if (err) { + goto error; + } + /* check if there is a child */ + + if (child != NULL) { + /* + * Free USB device and all subdevices, if any. + */ + usb_free_device(child, 0); + child = NULL; + } + /* get fresh status */ + + err = uhub_read_port_status(sc, portno); + if (err) { + goto error; + } + /* check if nothing is connected to the port */ + + if (!(sc->sc_st.port_status & UPS_CURRENT_CONNECT_STATUS)) { + goto error; + } + /* check if there is no power on the port and print a warning */ + + if (!(sc->sc_st.port_status & UPS_PORT_POWER)) { + DPRINTF("WARNING: strange, connected port %d " + "has no power\n", portno); + } + /* check if the device is in Host Mode */ + + if (!(sc->sc_st.port_status & UPS_PORT_MODE_DEVICE)) { + + DPRINTF("Port %d is in Host Mode\n", portno); + + if (sc->sc_st.port_status & UPS_SUSPEND) { + /* + * NOTE: Should not get here in SuperSpeed + * mode, because the HUB should report this + * bit as zero. + */ + DPRINTF("Port %d was still " + "suspended, clearing.\n", portno); + err = usbd_req_clear_port_feature(udev, + NULL, portno, UHF_PORT_SUSPEND); + } + + /* USB Host Mode */ + + /* wait for maximum device power up time */ + + usb_pause_mtx(NULL, + USB_MS_TO_TICKS(USB_PORT_POWERUP_DELAY)); + + /* reset port, which implies enabling it */ + + err = usbd_req_reset_port(udev, NULL, portno); + + if (err) { + DPRINTFN(0, "port %d reset " + "failed, error=%s\n", + portno, usbd_errstr(err)); + goto error; + } + /* get port status again, it might have changed during reset */ + + err = uhub_read_port_status(sc, portno); + if (err) { + goto error; + } + /* check if something changed during port reset */ + + if ((sc->sc_st.port_change & UPS_C_CONNECT_STATUS) || + (!(sc->sc_st.port_status & UPS_CURRENT_CONNECT_STATUS))) { + if (timeout) { + DPRINTFN(0, "giving up port reset " + "- device vanished\n"); + goto error; + } + timeout = 1; + goto repeat; + } + } else { + DPRINTF("Port %d is in Device Mode\n", portno); + } + + /* + * Figure out the device speed + */ + switch (udev->speed) { + case USB_SPEED_HIGH: + if (sc->sc_st.port_status & UPS_HIGH_SPEED) + speed = USB_SPEED_HIGH; + else if (sc->sc_st.port_status & UPS_LOW_SPEED) + speed = USB_SPEED_LOW; + else + speed = USB_SPEED_FULL; + break; + case USB_SPEED_FULL: + if (sc->sc_st.port_status & UPS_LOW_SPEED) + speed = USB_SPEED_LOW; + else + speed = USB_SPEED_FULL; + break; + case USB_SPEED_LOW: + speed = USB_SPEED_LOW; + break; + case USB_SPEED_SUPER: + if (udev->parent_hub == NULL) { + /* Root HUB - special case */ + switch (sc->sc_st.port_status & UPS_OTHER_SPEED) { + case 0: + speed = USB_SPEED_FULL; + break; + case UPS_LOW_SPEED: + speed = USB_SPEED_LOW; + break; + case UPS_HIGH_SPEED: + speed = USB_SPEED_HIGH; + break; + default: + speed = USB_SPEED_SUPER; + break; + } + } else { + speed = USB_SPEED_SUPER; + } + break; + default: + /* same speed like parent */ + speed = udev->speed; + break; + } + if (speed == USB_SPEED_SUPER) { + err = usbd_req_set_hub_u1_timeout(udev, NULL, + portno, 128 - (2 * udev->depth)); + if (err) { + DPRINTFN(0, "port %d U1 timeout " + "failed, error=%s\n", + portno, usbd_errstr(err)); + } + err = usbd_req_set_hub_u2_timeout(udev, NULL, + portno, 128 - (2 * udev->depth)); + if (err) { + DPRINTFN(0, "port %d U2 timeout " + "failed, error=%s\n", + portno, usbd_errstr(err)); + } + } + + /* + * Figure out the device mode + * + * NOTE: This part is currently FreeBSD specific. + */ + if (sc->sc_st.port_status & UPS_PORT_MODE_DEVICE) + mode = USB_MODE_DEVICE; + else + mode = USB_MODE_HOST; + + /* need to create a new child */ + child = usb_alloc_device(sc->sc_dev, udev->bus, udev, + udev->depth + 1, portno - 1, portno, speed, mode); + if (child == NULL) { + DPRINTFN(0, "could not allocate new device\n"); + goto error; + } + return (0); /* success */ + +error: + if (child != NULL) { + /* + * Free USB device and all subdevices, if any. + */ + usb_free_device(child, 0); + child = NULL; + } + if (err == 0) { + if (sc->sc_st.port_status & UPS_PORT_ENABLED) { + err = usbd_req_clear_port_feature( + sc->sc_udev, NULL, + portno, UHF_PORT_ENABLE); + } + } + if (err) { + DPRINTFN(0, "device problem (%s), " + "disabling port %d\n", usbd_errstr(err), portno); + } + return (err); +} + +/*------------------------------------------------------------------------* + * usb_device_20_compatible + * + * Returns: + * 0: HUB does not support suspend and resume + * Else: HUB supports suspend and resume + *------------------------------------------------------------------------*/ +static uint8_t +usb_device_20_compatible(struct usb_device *udev) +{ + if (udev == NULL) + return (0); + switch (udev->speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + case USB_SPEED_HIGH: + return (1); + default: + return (0); + } +} + +/*------------------------------------------------------------------------* + * uhub_suspend_resume_port + * + * Returns: + * 0: Success + * Else: A control transaction failed + *------------------------------------------------------------------------*/ +static usb_error_t +uhub_suspend_resume_port(struct uhub_softc *sc, uint8_t portno) +{ + struct usb_device *child; + struct usb_device *udev; + uint8_t is_suspend; + usb_error_t err; + + DPRINTF("port %d\n", portno); + + udev = sc->sc_udev; + child = usb_bus_port_get_device(udev->bus, + udev->hub->ports + portno - 1); + + /* first clear the port suspend change bit */ + + if (usb_device_20_compatible(udev)) { + err = usbd_req_clear_port_feature(udev, NULL, + portno, UHF_C_PORT_SUSPEND); + } else { + err = usbd_req_clear_port_feature(udev, NULL, + portno, UHF_C_PORT_LINK_STATE); + } + + if (err) { + DPRINTF("clearing suspend failed.\n"); + goto done; + } + /* get fresh status */ + + err = uhub_read_port_status(sc, portno); + if (err) { + DPRINTF("reading port status failed.\n"); + goto done; + } + /* convert current state */ + + if (usb_device_20_compatible(udev)) { + if (sc->sc_st.port_status & UPS_SUSPEND) { + is_suspend = 1; + } else { + is_suspend = 0; + } + } else { + switch (UPS_PORT_LINK_STATE_GET(sc->sc_st.port_status)) { + case UPS_PORT_LS_U0: + case UPS_PORT_LS_U1: + is_suspend = 0; + break; + default: + is_suspend = 1; + break; + } + } + + DPRINTF("suspended=%u\n", is_suspend); + + /* do the suspend or resume */ + + if (child) { + /* + * This code handle two cases: 1) Host Mode - we can only + * receive resume here 2) Device Mode - we can receive + * suspend and resume here + */ + if (is_suspend == 0) + usb_dev_resume_peer(child); + else if ((child->flags.usb_mode == USB_MODE_DEVICE) || + (usb_device_20_compatible(child) == 0)) + usb_dev_suspend_peer(child); + } +done: + return (err); +} + +/*------------------------------------------------------------------------* + * uhub_root_interrupt + * + * This function is called when a Root HUB interrupt has + * happened. "ptr" and "len" makes up the Root HUB interrupt + * packet. This function is called having the "bus_mtx" locked. + *------------------------------------------------------------------------*/ +void +uhub_root_intr(struct usb_bus *bus, const uint8_t *ptr, uint8_t len) +{ + USB_BUS_LOCK_ASSERT(bus, MA_OWNED); + + usb_needs_explore(bus, 0); +} + +static uint8_t +uhub_is_too_deep(struct usb_device *udev) +{ + switch (udev->speed) { + case USB_SPEED_FULL: + case USB_SPEED_LOW: + case USB_SPEED_HIGH: + if (udev->depth > USB_HUB_MAX_DEPTH) + return (1); + break; + case USB_SPEED_SUPER: + if (udev->depth > USB_SS_HUB_DEPTH_MAX) + return (1); + break; + default: + break; + } + return (0); +} + +/*------------------------------------------------------------------------* + * uhub_explore + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static usb_error_t +uhub_explore(struct usb_device *udev) +{ + struct usb_hub *hub; + struct uhub_softc *sc; + struct usb_port *up; + usb_error_t err; + uint8_t portno; + uint8_t x; + + hub = udev->hub; + sc = hub->hubsoftc; + + DPRINTFN(11, "udev=%p addr=%d\n", udev, udev->address); + + /* ignore devices that are too deep */ + if (uhub_is_too_deep(udev)) + return (USB_ERR_TOO_DEEP); + + /* check if device is suspended */ + if (udev->flags.self_suspended) { + /* need to wait until the child signals resume */ + DPRINTF("Device is suspended!\n"); + return (0); + } + for (x = 0; x != hub->nports; x++) { + up = hub->ports + x; + portno = x + 1; + + err = uhub_read_port_status(sc, portno); + if (err) { + /* most likely the HUB is gone */ + break; + } + if (sc->sc_st.port_change & UPS_C_OVERCURRENT_INDICATOR) { + DPRINTF("Overcurrent on port %u.\n", portno); + err = usbd_req_clear_port_feature( + udev, NULL, portno, UHF_C_PORT_OVER_CURRENT); + if (err) { + /* most likely the HUB is gone */ + break; + } + } + if (!(sc->sc_flags & UHUB_FLAG_DID_EXPLORE)) { + /* + * Fake a connect status change so that the + * status gets checked initially! + */ + sc->sc_st.port_change |= + UPS_C_CONNECT_STATUS; + } + if (sc->sc_st.port_change & UPS_C_PORT_ENABLED) { + err = usbd_req_clear_port_feature( + udev, NULL, portno, UHF_C_PORT_ENABLE); + if (err) { + /* most likely the HUB is gone */ + break; + } + if (sc->sc_st.port_change & UPS_C_CONNECT_STATUS) { + /* + * Ignore the port error if the device + * has vanished ! + */ + } else if (sc->sc_st.port_status & UPS_PORT_ENABLED) { + DPRINTFN(0, "illegal enable change, " + "port %d\n", portno); + } else { + + if (up->restartcnt == USB_RESTART_MAX) { + /* XXX could try another speed ? */ + DPRINTFN(0, "port error, giving up " + "port %d\n", portno); + } else { + sc->sc_st.port_change |= + UPS_C_CONNECT_STATUS; + up->restartcnt++; + } + } + } + if (sc->sc_st.port_change & UPS_C_CONNECT_STATUS) { + err = uhub_reattach_port(sc, portno); + if (err) { + /* most likely the HUB is gone */ + break; + } + } + if (sc->sc_st.port_change & (UPS_C_SUSPEND | UPS_C_PORT_LINK_STATE)) { + err = uhub_suspend_resume_port(sc, portno); + if (err) { + /* most likely the HUB is gone */ + break; + } + } + err = uhub_explore_sub(sc, up); + if (err) { + /* no device(s) present */ + continue; + } + /* explore succeeded - reset restart counter */ + up->restartcnt = 0; + } + + /* initial status checked */ + sc->sc_flags |= UHUB_FLAG_DID_EXPLORE; + + /* return success */ + return (USB_ERR_NORMAL_COMPLETION); +} + +static int +uhub_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + + /* + * The subclass for USB HUBs is currently ignored because it + * is 0 for some and 1 for others. + */ + if (uaa->info.bConfigIndex == 0 && + uaa->info.bDeviceClass == UDCLASS_HUB) + return (0); + + return (ENXIO); +} + +/* NOTE: The information returned by this function can be wrong. */ +usb_error_t +uhub_query_info(struct usb_device *udev, uint8_t *pnports, uint8_t *ptt) +{ + struct usb_hub_descriptor hubdesc20; + struct usb_hub_ss_descriptor hubdesc30; + usb_error_t err; + uint8_t nports; + uint8_t tt; + + if (udev->ddesc.bDeviceClass != UDCLASS_HUB) + return (USB_ERR_INVAL); + + nports = 0; + tt = 0; + + switch (udev->speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + case USB_SPEED_HIGH: + /* assuming that there is one port */ + err = usbd_req_get_hub_descriptor(udev, NULL, &hubdesc20, 1); + if (err) { + DPRINTFN(0, "getting USB 2.0 HUB descriptor failed," + "error=%s\n", usbd_errstr(err)); + break; + } + nports = hubdesc20.bNbrPorts; + if (nports > 127) + nports = 127; + + if (udev->speed == USB_SPEED_HIGH) + tt = (UGETW(hubdesc20.wHubCharacteristics) >> 5) & 3; + break; + + case USB_SPEED_SUPER: + err = usbd_req_get_ss_hub_descriptor(udev, NULL, &hubdesc30, 1); + if (err) { + DPRINTFN(0, "Getting USB 3.0 HUB descriptor failed," + "error=%s\n", usbd_errstr(err)); + break; + } + nports = hubdesc30.bNbrPorts; + if (nports > 16) + nports = 16; + break; + + default: + err = USB_ERR_INVAL; + break; + } + + if (pnports != NULL) + *pnports = nports; + + if (ptt != NULL) + *ptt = tt; + + return (err); +} + +static int +uhub_attach(device_t dev) +{ + struct uhub_softc *sc = device_get_softc(dev); + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct usb_device *udev = uaa->device; + struct usb_device *parent_hub = udev->parent_hub; + struct usb_hub *hub; + struct usb_hub_descriptor hubdesc20; + struct usb_hub_ss_descriptor hubdesc30; + uint16_t pwrdly; + uint8_t x; + uint8_t nports; + uint8_t portno; + uint8_t removable; + uint8_t iface_index; + usb_error_t err; + + sc->sc_udev = udev; + sc->sc_dev = dev; + + mtx_init(&sc->sc_mtx, "USB HUB mutex", NULL, MTX_DEF); + + snprintf(sc->sc_name, sizeof(sc->sc_name), "%s", + device_get_nameunit(dev)); + + device_set_usb_desc(dev); + + DPRINTFN(2, "depth=%d selfpowered=%d, parent=%p, " + "parent->selfpowered=%d\n", + udev->depth, + udev->flags.self_powered, + parent_hub, + parent_hub ? + parent_hub->flags.self_powered : 0); + + if (uhub_is_too_deep(udev)) { + DPRINTFN(0, "HUB at depth %d, " + "exceeds maximum. HUB ignored\n", (int)udev->depth); + goto error; + } + + if (!udev->flags.self_powered && parent_hub && + !parent_hub->flags.self_powered) { + DPRINTFN(0, "Bus powered HUB connected to " + "bus powered HUB. HUB ignored\n"); + goto error; + } + /* get HUB descriptor */ + + DPRINTFN(2, "Getting HUB descriptor\n"); + + switch (udev->speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + case USB_SPEED_HIGH: + /* assuming that there is one port */ + err = usbd_req_get_hub_descriptor(udev, NULL, &hubdesc20, 1); + if (err) { + DPRINTFN(0, "getting USB 2.0 HUB descriptor failed," + "error=%s\n", usbd_errstr(err)); + goto error; + } + /* get number of ports */ + nports = hubdesc20.bNbrPorts; + + /* get power delay */ + pwrdly = ((hubdesc20.bPwrOn2PwrGood * UHD_PWRON_FACTOR) + + USB_EXTRA_POWER_UP_TIME); + + /* get complete HUB descriptor */ + if (nports >= 8) { + /* check number of ports */ + if (nports > 127) { + DPRINTFN(0, "Invalid number of USB 2.0 ports," + "error=%s\n", usbd_errstr(err)); + goto error; + } + /* get complete HUB descriptor */ + err = usbd_req_get_hub_descriptor(udev, NULL, &hubdesc20, nports); + + if (err) { + DPRINTFN(0, "Getting USB 2.0 HUB descriptor failed," + "error=%s\n", usbd_errstr(err)); + goto error; + } + if (hubdesc20.bNbrPorts != nports) { + DPRINTFN(0, "Number of ports changed\n"); + goto error; + } + } + break; + case USB_SPEED_SUPER: + if (udev->parent_hub != NULL) { + err = usbd_req_set_hub_depth(udev, NULL, + udev->depth - 1); + if (err) { + DPRINTFN(0, "Setting USB 3.0 HUB depth failed," + "error=%s\n", usbd_errstr(err)); + goto error; + } + } + err = usbd_req_get_ss_hub_descriptor(udev, NULL, &hubdesc30, 1); + if (err) { + DPRINTFN(0, "Getting USB 3.0 HUB descriptor failed," + "error=%s\n", usbd_errstr(err)); + goto error; + } + /* get number of ports */ + nports = hubdesc30.bNbrPorts; + + /* get power delay */ + pwrdly = ((hubdesc30.bPwrOn2PwrGood * UHD_PWRON_FACTOR) + + USB_EXTRA_POWER_UP_TIME); + + /* get complete HUB descriptor */ + if (nports >= 8) { + /* check number of ports */ + if (nports > ((udev->parent_hub != NULL) ? 15 : 127)) { + DPRINTFN(0, "Invalid number of USB 3.0 ports," + "error=%s\n", usbd_errstr(err)); + goto error; + } + /* get complete HUB descriptor */ + err = usbd_req_get_ss_hub_descriptor(udev, NULL, &hubdesc30, nports); + + if (err) { + DPRINTFN(0, "Getting USB 2.0 HUB descriptor failed," + "error=%s\n", usbd_errstr(err)); + goto error; + } + if (hubdesc30.bNbrPorts != nports) { + DPRINTFN(0, "Number of ports changed\n"); + goto error; + } + } + break; + default: + DPRINTF("Assuming HUB has only one port\n"); + /* default number of ports */ + nports = 1; + /* default power delay */ + pwrdly = ((10 * UHD_PWRON_FACTOR) + USB_EXTRA_POWER_UP_TIME); + break; + } + if (nports == 0) { + DPRINTFN(0, "portless HUB\n"); + goto error; + } + hub = malloc(sizeof(hub[0]) + (sizeof(hub->ports[0]) * nports), + M_USBDEV, M_WAITOK | M_ZERO); + + if (hub == NULL) { + goto error; + } + udev->hub = hub; + +#if USB_HAVE_TT_SUPPORT + /* init FULL-speed ISOCHRONOUS schedule */ + usbd_fs_isoc_schedule_init_all(hub->fs_isoc_schedule); +#endif + /* initialize HUB structure */ + hub->hubsoftc = sc; + hub->explore = &uhub_explore; + hub->nports = nports; + hub->hubudev = udev; + + /* if self powered hub, give ports maximum current */ + if (udev->flags.self_powered) { + hub->portpower = USB_MAX_POWER; + } else { + hub->portpower = USB_MIN_POWER; + } + + /* set up interrupt pipe */ + iface_index = 0; + if (udev->parent_hub == NULL) { + /* root HUB is special */ + err = 0; + } else { + /* normal HUB */ + err = usbd_transfer_setup(udev, &iface_index, sc->sc_xfer, + uhub_config, UHUB_N_TRANSFER, sc, &sc->sc_mtx); + } + if (err) { + DPRINTFN(0, "cannot setup interrupt transfer, " + "errstr=%s\n", usbd_errstr(err)); + goto error; + } + /* wait with power off for a while */ + usb_pause_mtx(NULL, USB_MS_TO_TICKS(USB_POWER_DOWN_TIME)); + + /* + * To have the best chance of success we do things in the exact same + * order as Windoze98. This should not be necessary, but some + * devices do not follow the USB specs to the letter. + * + * These are the events on the bus when a hub is attached: + * Get device and config descriptors (see attach code) + * Get hub descriptor (see above) + * For all ports + * turn on power + * wait for power to become stable + * (all below happens in explore code) + * For all ports + * clear C_PORT_CONNECTION + * For all ports + * get port status + * if device connected + * wait 100 ms + * turn on reset + * wait + * clear C_PORT_RESET + * get port status + * proceed with device attachment + */ + + /* XXX should check for none, individual, or ganged power? */ + + removable = 0; + + for (x = 0; x != nports; x++) { + /* set up data structures */ + struct usb_port *up = hub->ports + x; + + up->device_index = 0; + up->restartcnt = 0; + portno = x + 1; + + /* check if port is removable */ + switch (udev->speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + case USB_SPEED_HIGH: + if (!UHD_NOT_REMOV(&hubdesc20, portno)) + removable++; + break; + case USB_SPEED_SUPER: + if (!UHD_NOT_REMOV(&hubdesc30, portno)) + removable++; + break; + default: + DPRINTF("Assuming removable port\n"); + removable++; + break; + } + if (!err) { + /* turn the power on */ + err = usbd_req_set_port_feature(udev, NULL, + portno, UHF_PORT_POWER); + } + if (err) { + DPRINTFN(0, "port %d power on failed, %s\n", + portno, usbd_errstr(err)); + } + DPRINTF("turn on port %d power\n", + portno); + + /* wait for stable power */ + usb_pause_mtx(NULL, USB_MS_TO_TICKS(pwrdly)); + } + + device_printf(dev, "%d port%s with %d " + "removable, %s powered\n", nports, (nports != 1) ? "s" : "", + removable, udev->flags.self_powered ? "self" : "bus"); + + /* Start the interrupt endpoint, if any */ + + if (sc->sc_xfer[0] != NULL) { + mtx_lock(&sc->sc_mtx); + usbd_transfer_start(sc->sc_xfer[0]); + mtx_unlock(&sc->sc_mtx); + } + + /* Enable automatic power save on all USB HUBs */ + + usbd_set_power_mode(udev, USB_POWER_MODE_SAVE); + + return (0); + +error: + usbd_transfer_unsetup(sc->sc_xfer, UHUB_N_TRANSFER); + + if (udev->hub) { + free(udev->hub, M_USBDEV); + udev->hub = NULL; + } + + mtx_destroy(&sc->sc_mtx); + + return (ENXIO); +} + +/* + * Called from process context when the hub is gone. + * Detach all devices on active ports. + */ +static int +uhub_detach(device_t dev) +{ + struct uhub_softc *sc = device_get_softc(dev); + struct usb_hub *hub = sc->sc_udev->hub; + struct usb_device *child; + uint8_t x; + + if (hub == NULL) /* must be partially working */ + return (0); + + /* Make sure interrupt transfer is gone. */ + usbd_transfer_unsetup(sc->sc_xfer, UHUB_N_TRANSFER); + + /* Detach all ports */ + for (x = 0; x != hub->nports; x++) { + + child = usb_bus_port_get_device(sc->sc_udev->bus, hub->ports + x); + + if (child == NULL) { + continue; + } + + /* + * Free USB device and all subdevices, if any. + */ + usb_free_device(child, 0); + } + + free(hub, M_USBDEV); + sc->sc_udev->hub = NULL; + + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static int +uhub_suspend(device_t dev) +{ + DPRINTF("\n"); + /* Sub-devices are not suspended here! */ + return (0); +} + +static int +uhub_resume(device_t dev) +{ + DPRINTF("\n"); + /* Sub-devices are not resumed here! */ + return (0); +} + +static void +uhub_driver_added(device_t dev, driver_t *driver) +{ + usb_needs_explore_all(); +} + +struct hub_result { + struct usb_device *udev; + uint8_t portno; + uint8_t iface_index; +}; + +static void +uhub_find_iface_index(struct usb_hub *hub, device_t child, + struct hub_result *res) +{ + struct usb_interface *iface; + struct usb_device *udev; + uint8_t nports; + uint8_t x; + uint8_t i; + + nports = hub->nports; + for (x = 0; x != nports; x++) { + udev = usb_bus_port_get_device(hub->hubudev->bus, + hub->ports + x); + if (!udev) { + continue; + } + for (i = 0; i != USB_IFACE_MAX; i++) { + iface = usbd_get_iface(udev, i); + if (iface && + (iface->subdev == child)) { + res->iface_index = i; + res->udev = udev; + res->portno = x + 1; + return; + } + } + } + res->iface_index = 0; + res->udev = NULL; + res->portno = 0; +} + +static int +uhub_child_location_string(device_t parent, device_t child, + char *buf, size_t buflen) +{ + struct uhub_softc *sc; + struct usb_hub *hub; + struct hub_result res; + + if (!device_is_attached(parent)) { + if (buflen) + buf[0] = 0; + return (0); + } + + sc = device_get_softc(parent); + hub = sc->sc_udev->hub; + + mtx_lock(&Giant); + uhub_find_iface_index(hub, child, &res); + if (!res.udev) { + DPRINTF("device not on hub\n"); + if (buflen) { + buf[0] = '\0'; + } + goto done; + } + snprintf(buf, buflen, "bus=%u hubaddr=%u port=%u devaddr=%u interface=%u", + (res.udev->parent_hub != NULL) ? res.udev->parent_hub->device_index : 0, + res.portno, device_get_unit(res.udev->bus->bdev), + res.udev->device_index, res.iface_index); +done: + mtx_unlock(&Giant); + + return (0); +} + +static int +uhub_child_pnpinfo_string(device_t parent, device_t child, + char *buf, size_t buflen) +{ + struct uhub_softc *sc; + struct usb_hub *hub; + struct usb_interface *iface; + struct hub_result res; + + if (!device_is_attached(parent)) { + if (buflen) + buf[0] = 0; + return (0); + } + + sc = device_get_softc(parent); + hub = sc->sc_udev->hub; + + mtx_lock(&Giant); + uhub_find_iface_index(hub, child, &res); + if (!res.udev) { + DPRINTF("device not on hub\n"); + if (buflen) { + buf[0] = '\0'; + } + goto done; + } + iface = usbd_get_iface(res.udev, res.iface_index); + if (iface && iface->idesc) { + snprintf(buf, buflen, "vendor=0x%04x product=0x%04x " + "devclass=0x%02x devsubclass=0x%02x " + "sernum=\"%s\" " + "release=0x%04x " + "intclass=0x%02x intsubclass=0x%02x", + UGETW(res.udev->ddesc.idVendor), + UGETW(res.udev->ddesc.idProduct), + res.udev->ddesc.bDeviceClass, + res.udev->ddesc.bDeviceSubClass, + usb_get_serial(res.udev), + UGETW(res.udev->ddesc.bcdDevice), + iface->idesc->bInterfaceClass, + iface->idesc->bInterfaceSubClass); + } else { + if (buflen) { + buf[0] = '\0'; + } + goto done; + } +done: + mtx_unlock(&Giant); + + return (0); +} + +/* + * The USB Transaction Translator: + * =============================== + * + * When doing LOW- and FULL-speed USB transfers accross a HIGH-speed + * USB HUB, bandwidth must be allocated for ISOCHRONOUS and INTERRUPT + * USB transfers. To utilize bandwidth dynamically the "scatter and + * gather" principle must be applied. This means that bandwidth must + * be divided into equal parts of bandwidth. With regard to USB all + * data is transferred in smaller packets with length + * "wMaxPacketSize". The problem however is that "wMaxPacketSize" is + * not a constant! + * + * The bandwidth scheduler which I have implemented will simply pack + * the USB transfers back to back until there is no more space in the + * schedule. Out of the 8 microframes which the USB 2.0 standard + * provides, only 6 are available for non-HIGH-speed devices. I have + * reserved the first 4 microframes for ISOCHRONOUS transfers. The + * last 2 microframes I have reserved for INTERRUPT transfers. Without + * this division, it is very difficult to allocate and free bandwidth + * dynamically. + * + * NOTE about the Transaction Translator in USB HUBs: + * + * USB HUBs have a very simple Transaction Translator, that will + * simply pipeline all the SPLIT transactions. That means that the + * transactions will be executed in the order they are queued! + * + */ + +/*------------------------------------------------------------------------* + * usb_intr_find_best_slot + * + * Return value: + * The best Transaction Translation slot for an interrupt endpoint. + *------------------------------------------------------------------------*/ +static uint8_t +usb_intr_find_best_slot(usb_size_t *ptr, uint8_t start, + uint8_t end, uint8_t mask) +{ + usb_size_t min = 0 - 1; + usb_size_t sum; + uint8_t x; + uint8_t y; + uint8_t z; + + y = 0; + + /* find the last slot with lesser used bandwidth */ + + for (x = start; x < end; x++) { + + sum = 0; + + /* compute sum of bandwidth */ + for (z = x; z < end; z++) { + if (mask & (1U << (z - x))) + sum += ptr[z]; + } + + /* check if the current multi-slot is more optimal */ + if (min >= sum) { + min = sum; + y = x; + } + + /* check if the mask is about to be shifted out */ + if (mask & (1U << (end - 1 - x))) + break; + } + return (y); +} + +/*------------------------------------------------------------------------* + * usb_hs_bandwidth_adjust + * + * This function will update the bandwith usage for the microframe + * having index "slot" by "len" bytes. "len" can be negative. If the + * "slot" argument is greater or equal to "USB_HS_MICRO_FRAMES_MAX" + * the "slot" argument will be replaced by the slot having least used + * bandwidth. The "mask" argument is used for multi-slot allocations. + * + * Returns: + * The slot in which the bandwidth update was done: 0..7 + *------------------------------------------------------------------------*/ +static uint8_t +usb_hs_bandwidth_adjust(struct usb_device *udev, int16_t len, + uint8_t slot, uint8_t mask) +{ + struct usb_bus *bus = udev->bus; + struct usb_hub *hub; + enum usb_dev_speed speed; + uint8_t x; + + USB_BUS_LOCK_ASSERT(bus, MA_OWNED); + + speed = usbd_get_speed(udev); + + switch (speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + if (speed == USB_SPEED_LOW) { + len *= 8; + } + /* + * The Host Controller Driver should have + * performed checks so that the lookup + * below does not result in a NULL pointer + * access. + */ + + hub = udev->parent_hs_hub->hub; + if (slot >= USB_HS_MICRO_FRAMES_MAX) { + slot = usb_intr_find_best_slot(hub->uframe_usage, + USB_FS_ISOC_UFRAME_MAX, 6, mask); + } + for (x = slot; x < 8; x++) { + if (mask & (1U << (x - slot))) { + hub->uframe_usage[x] += len; + bus->uframe_usage[x] += len; + } + } + break; + default: + if (slot >= USB_HS_MICRO_FRAMES_MAX) { + slot = usb_intr_find_best_slot(bus->uframe_usage, 0, + USB_HS_MICRO_FRAMES_MAX, mask); + } + for (x = slot; x < 8; x++) { + if (mask & (1U << (x - slot))) { + bus->uframe_usage[x] += len; + } + } + break; + } + return (slot); +} + +/*------------------------------------------------------------------------* + * usb_hs_bandwidth_alloc + * + * This function is a wrapper function for "usb_hs_bandwidth_adjust()". + *------------------------------------------------------------------------*/ +void +usb_hs_bandwidth_alloc(struct usb_xfer *xfer) +{ + struct usb_device *udev; + uint8_t slot; + uint8_t mask; + uint8_t speed; + + udev = xfer->xroot->udev; + + if (udev->flags.usb_mode != USB_MODE_HOST) + return; /* not supported */ + + xfer->endpoint->refcount_bw++; + if (xfer->endpoint->refcount_bw != 1) + return; /* already allocated */ + + speed = usbd_get_speed(udev); + + switch (xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE) { + case UE_INTERRUPT: + /* allocate a microframe slot */ + + mask = 0x01; + slot = usb_hs_bandwidth_adjust(udev, + xfer->max_frame_size, USB_HS_MICRO_FRAMES_MAX, mask); + + xfer->endpoint->usb_uframe = slot; + xfer->endpoint->usb_smask = mask << slot; + + if ((speed != USB_SPEED_FULL) && + (speed != USB_SPEED_LOW)) { + xfer->endpoint->usb_cmask = 0x00 ; + } else { + xfer->endpoint->usb_cmask = (-(0x04 << slot)) & 0xFE; + } + break; + + case UE_ISOCHRONOUS: + switch (usbd_xfer_get_fps_shift(xfer)) { + case 0: + mask = 0xFF; + break; + case 1: + mask = 0x55; + break; + case 2: + mask = 0x11; + break; + default: + mask = 0x01; + break; + } + + /* allocate a microframe multi-slot */ + + slot = usb_hs_bandwidth_adjust(udev, + xfer->max_frame_size, USB_HS_MICRO_FRAMES_MAX, mask); + + xfer->endpoint->usb_uframe = slot; + xfer->endpoint->usb_cmask = 0; + xfer->endpoint->usb_smask = mask << slot; + break; + + default: + xfer->endpoint->usb_uframe = 0; + xfer->endpoint->usb_cmask = 0; + xfer->endpoint->usb_smask = 0; + break; + } + + DPRINTFN(11, "slot=%d, mask=0x%02x\n", + xfer->endpoint->usb_uframe, + xfer->endpoint->usb_smask >> xfer->endpoint->usb_uframe); +} + +/*------------------------------------------------------------------------* + * usb_hs_bandwidth_free + * + * This function is a wrapper function for "usb_hs_bandwidth_adjust()". + *------------------------------------------------------------------------*/ +void +usb_hs_bandwidth_free(struct usb_xfer *xfer) +{ + struct usb_device *udev; + uint8_t slot; + uint8_t mask; + + udev = xfer->xroot->udev; + + if (udev->flags.usb_mode != USB_MODE_HOST) + return; /* not supported */ + + xfer->endpoint->refcount_bw--; + if (xfer->endpoint->refcount_bw != 0) + return; /* still allocated */ + + switch (xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE) { + case UE_INTERRUPT: + case UE_ISOCHRONOUS: + + slot = xfer->endpoint->usb_uframe; + mask = xfer->endpoint->usb_smask; + + /* free microframe slot(s): */ + usb_hs_bandwidth_adjust(udev, + -xfer->max_frame_size, slot, mask >> slot); + + DPRINTFN(11, "slot=%d, mask=0x%02x\n", + slot, mask >> slot); + + xfer->endpoint->usb_uframe = 0; + xfer->endpoint->usb_cmask = 0; + xfer->endpoint->usb_smask = 0; + break; + + default: + break; + } +} + +/*------------------------------------------------------------------------* + * usbd_fs_isoc_schedule_init_sub + * + * This function initialises an USB FULL speed isochronous schedule + * entry. + *------------------------------------------------------------------------*/ +#if USB_HAVE_TT_SUPPORT +static void +usbd_fs_isoc_schedule_init_sub(struct usb_fs_isoc_schedule *fss) +{ + fss->total_bytes = (USB_FS_ISOC_UFRAME_MAX * + USB_FS_BYTES_PER_HS_UFRAME); + fss->frame_bytes = (USB_FS_BYTES_PER_HS_UFRAME); + fss->frame_slot = 0; +} +#endif + +/*------------------------------------------------------------------------* + * usbd_fs_isoc_schedule_init_all + * + * This function will reset the complete USB FULL speed isochronous + * bandwidth schedule. + *------------------------------------------------------------------------*/ +#if USB_HAVE_TT_SUPPORT +void +usbd_fs_isoc_schedule_init_all(struct usb_fs_isoc_schedule *fss) +{ + struct usb_fs_isoc_schedule *fss_end = fss + USB_ISOC_TIME_MAX; + + while (fss != fss_end) { + usbd_fs_isoc_schedule_init_sub(fss); + fss++; + } +} +#endif + +/*------------------------------------------------------------------------* + * usb_isoc_time_expand + * + * This function will expand the time counter from 7-bit to 16-bit. + * + * Returns: + * 16-bit isochronous time counter. + *------------------------------------------------------------------------*/ +uint16_t +usb_isoc_time_expand(struct usb_bus *bus, uint16_t isoc_time_curr) +{ + uint16_t rem; + + USB_BUS_LOCK_ASSERT(bus, MA_OWNED); + + rem = bus->isoc_time_last & (USB_ISOC_TIME_MAX - 1); + + isoc_time_curr &= (USB_ISOC_TIME_MAX - 1); + + if (isoc_time_curr < rem) { + /* the time counter wrapped around */ + bus->isoc_time_last += USB_ISOC_TIME_MAX; + } + /* update the remainder */ + + bus->isoc_time_last &= ~(USB_ISOC_TIME_MAX - 1); + bus->isoc_time_last |= isoc_time_curr; + + return (bus->isoc_time_last); +} + +/*------------------------------------------------------------------------* + * usbd_fs_isoc_schedule_isoc_time_expand + * + * This function does multiple things. First of all it will expand the + * passed isochronous time, which is the return value. Then it will + * store where the current FULL speed isochronous schedule is + * positioned in time and where the end is. See "pp_start" and + * "pp_end" arguments. + * + * Returns: + * Expanded version of "isoc_time". + * + * NOTE: This function depends on being called regularly with + * intervals less than "USB_ISOC_TIME_MAX". + *------------------------------------------------------------------------*/ +#if USB_HAVE_TT_SUPPORT +uint16_t +usbd_fs_isoc_schedule_isoc_time_expand(struct usb_device *udev, + struct usb_fs_isoc_schedule **pp_start, + struct usb_fs_isoc_schedule **pp_end, + uint16_t isoc_time) +{ + struct usb_fs_isoc_schedule *fss_end; + struct usb_fs_isoc_schedule *fss_a; + struct usb_fs_isoc_schedule *fss_b; + struct usb_hub *hs_hub; + + isoc_time = usb_isoc_time_expand(udev->bus, isoc_time); + + hs_hub = udev->parent_hs_hub->hub; + + if (hs_hub != NULL) { + + fss_a = hs_hub->fs_isoc_schedule + + (hs_hub->isoc_last_time % USB_ISOC_TIME_MAX); + + hs_hub->isoc_last_time = isoc_time; + + fss_b = hs_hub->fs_isoc_schedule + + (isoc_time % USB_ISOC_TIME_MAX); + + fss_end = hs_hub->fs_isoc_schedule + USB_ISOC_TIME_MAX; + + *pp_start = hs_hub->fs_isoc_schedule; + *pp_end = fss_end; + + while (fss_a != fss_b) { + if (fss_a == fss_end) { + fss_a = hs_hub->fs_isoc_schedule; + continue; + } + usbd_fs_isoc_schedule_init_sub(fss_a); + fss_a++; + } + + } else { + + *pp_start = NULL; + *pp_end = NULL; + } + return (isoc_time); +} +#endif + +/*------------------------------------------------------------------------* + * usbd_fs_isoc_schedule_alloc + * + * This function will allocate bandwidth for an isochronous FULL speed + * transaction in the FULL speed schedule. The microframe slot where + * the transaction should be started is stored in the byte pointed to + * by "pstart". The "len" argument specifies the length of the + * transaction in bytes. + * + * Returns: + * 0: Success + * Else: Error + *------------------------------------------------------------------------*/ +#if USB_HAVE_TT_SUPPORT +uint8_t +usbd_fs_isoc_schedule_alloc(struct usb_fs_isoc_schedule *fss, + uint8_t *pstart, uint16_t len) +{ + uint8_t slot = fss->frame_slot; + + /* Compute overhead and bit-stuffing */ + + len += 8; + + len *= 7; + len /= 6; + + if (len > fss->total_bytes) { + *pstart = 0; /* set some dummy value */ + return (1); /* error */ + } + if (len > 0) { + + fss->total_bytes -= len; + + while (len >= fss->frame_bytes) { + len -= fss->frame_bytes; + fss->frame_bytes = USB_FS_BYTES_PER_HS_UFRAME; + fss->frame_slot++; + } + + fss->frame_bytes -= len; + } + *pstart = slot; + return (0); /* success */ +} +#endif + +/*------------------------------------------------------------------------* + * usb_bus_port_get_device + * + * This function is NULL safe. + *------------------------------------------------------------------------*/ +struct usb_device * +usb_bus_port_get_device(struct usb_bus *bus, struct usb_port *up) +{ + if ((bus == NULL) || (up == NULL)) { + /* be NULL safe */ + return (NULL); + } + if (up->device_index == 0) { + /* nothing to do */ + return (NULL); + } + return (bus->devices[up->device_index]); +} + +/*------------------------------------------------------------------------* + * usb_bus_port_set_device + * + * This function is NULL safe. + *------------------------------------------------------------------------*/ +void +usb_bus_port_set_device(struct usb_bus *bus, struct usb_port *up, + struct usb_device *udev, uint8_t device_index) +{ + if (bus == NULL) { + /* be NULL safe */ + return; + } + /* + * There is only one case where we don't + * have an USB port, and that is the Root Hub! + */ + if (up) { + if (udev) { + up->device_index = device_index; + } else { + device_index = up->device_index; + up->device_index = 0; + } + } + /* + * Make relationships to our new device + */ + if (device_index != 0) { +#if USB_HAVE_UGEN + mtx_lock(&usb_ref_lock); +#endif + bus->devices[device_index] = udev; +#if USB_HAVE_UGEN + mtx_unlock(&usb_ref_lock); +#endif + } + /* + * Debug print + */ + DPRINTFN(2, "bus %p devices[%u] = %p\n", bus, device_index, udev); +} + +/*------------------------------------------------------------------------* + * usb_needs_explore + * + * This functions is called when the USB event thread needs to run. + *------------------------------------------------------------------------*/ +void +usb_needs_explore(struct usb_bus *bus, uint8_t do_probe) +{ + uint8_t do_unlock; + + DPRINTF("\n"); + + if (bus == NULL) { + DPRINTF("No bus pointer!\n"); + return; + } + if ((bus->devices == NULL) || + (bus->devices[USB_ROOT_HUB_ADDR] == NULL)) { + DPRINTF("No root HUB\n"); + return; + } + if (mtx_owned(&bus->bus_mtx)) { + do_unlock = 0; + } else { + USB_BUS_LOCK(bus); + do_unlock = 1; + } + if (do_probe) { + bus->do_probe = 1; + } + if (usb_proc_msignal(&bus->explore_proc, + &bus->explore_msg[0], &bus->explore_msg[1])) { + /* ignore */ + } + if (do_unlock) { + USB_BUS_UNLOCK(bus); + } +} + +/*------------------------------------------------------------------------* + * usb_needs_explore_all + * + * This function is called whenever a new driver is loaded and will + * cause that all USB busses are re-explored. + *------------------------------------------------------------------------*/ +void +usb_needs_explore_all(void) +{ + struct usb_bus *bus; + devclass_t dc; + device_t dev; + int max; + + DPRINTFN(3, "\n"); + + dc = usb_devclass_ptr; + if (dc == NULL) { + DPRINTFN(0, "no devclass\n"); + return; + } + /* + * Explore all USB busses in parallell. + */ + max = devclass_get_maxunit(dc); + while (max >= 0) { + dev = devclass_get_device(dc, max); + if (dev) { + bus = device_get_softc(dev); + if (bus) { + usb_needs_explore(bus, 1); + } + } + max--; + } +} + +/*------------------------------------------------------------------------* + * usb_bus_power_update + * + * This function will ensure that all USB devices on the given bus are + * properly suspended or resumed according to the device transfer + * state. + *------------------------------------------------------------------------*/ +#if USB_HAVE_POWERD +void +usb_bus_power_update(struct usb_bus *bus) +{ + usb_needs_explore(bus, 0 /* no probe */ ); +} +#endif + +/*------------------------------------------------------------------------* + * usbd_transfer_power_ref + * + * This function will modify the power save reference counts and + * wakeup the USB device associated with the given USB transfer, if + * needed. + *------------------------------------------------------------------------*/ +#if USB_HAVE_POWERD +void +usbd_transfer_power_ref(struct usb_xfer *xfer, int val) +{ + static const usb_power_mask_t power_mask[4] = { + [UE_CONTROL] = USB_HW_POWER_CONTROL, + [UE_BULK] = USB_HW_POWER_BULK, + [UE_INTERRUPT] = USB_HW_POWER_INTERRUPT, + [UE_ISOCHRONOUS] = USB_HW_POWER_ISOC, + }; + struct usb_device *udev; + uint8_t needs_explore; + uint8_t needs_hw_power; + uint8_t xfer_type; + + udev = xfer->xroot->udev; + + if (udev->device_index == USB_ROOT_HUB_ADDR) { + /* no power save for root HUB */ + return; + } + USB_BUS_LOCK(udev->bus); + + xfer_type = xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE; + + udev->pwr_save.last_xfer_time = ticks; + udev->pwr_save.type_refs[xfer_type] += val; + + if (xfer->flags_int.control_xfr) { + udev->pwr_save.read_refs += val; + if (xfer->flags_int.usb_mode == USB_MODE_HOST) { + /* + * It is not allowed to suspend during a + * control transfer: + */ + udev->pwr_save.write_refs += val; + } + } else if (USB_GET_DATA_ISREAD(xfer)) { + udev->pwr_save.read_refs += val; + } else { + udev->pwr_save.write_refs += val; + } + + if (val > 0) { + if (udev->flags.self_suspended) + needs_explore = usb_peer_should_wakeup(udev); + else + needs_explore = 0; + + if (!(udev->bus->hw_power_state & power_mask[xfer_type])) { + DPRINTF("Adding type %u to power state\n", xfer_type); + udev->bus->hw_power_state |= power_mask[xfer_type]; + needs_hw_power = 1; + } else { + needs_hw_power = 0; + } + } else { + needs_explore = 0; + needs_hw_power = 0; + } + + USB_BUS_UNLOCK(udev->bus); + + if (needs_explore) { + DPRINTF("update\n"); + usb_bus_power_update(udev->bus); + } else if (needs_hw_power) { + DPRINTF("needs power\n"); + if (udev->bus->methods->set_hw_power != NULL) { + (udev->bus->methods->set_hw_power) (udev->bus); + } + } +} +#endif + +/*------------------------------------------------------------------------* + * usb_peer_should_wakeup + * + * This function returns non-zero if the current device should wake up. + *------------------------------------------------------------------------*/ +static uint8_t +usb_peer_should_wakeup(struct usb_device *udev) +{ + return ((udev->power_mode == USB_POWER_MODE_ON) || + (udev->driver_added_refcount != udev->bus->driver_added_refcount) || + (udev->re_enumerate_wait != 0) || + (udev->pwr_save.type_refs[UE_ISOCHRONOUS] != 0) || + (udev->pwr_save.write_refs != 0) || + ((udev->pwr_save.read_refs != 0) && + (udev->flags.usb_mode == USB_MODE_HOST) && + (usb_device_20_compatible(udev) != 0) && + (usb_peer_can_wakeup(udev) == 0))); +} + +/*------------------------------------------------------------------------* + * usb_bus_powerd + * + * This function implements the USB power daemon and is called + * regularly from the USB explore thread. + *------------------------------------------------------------------------*/ +#if USB_HAVE_POWERD +void +usb_bus_powerd(struct usb_bus *bus) +{ + struct usb_device *udev; + usb_ticks_t temp; + usb_ticks_t limit; + usb_ticks_t mintime; + usb_size_t type_refs[5]; + uint8_t x; + + limit = usb_power_timeout; + if (limit == 0) + limit = hz; + else if (limit > 255) + limit = 255 * hz; + else + limit = limit * hz; + + DPRINTF("bus=%p\n", bus); + + USB_BUS_LOCK(bus); + + /* + * The root HUB device is never suspended + * and we simply skip it. + */ + for (x = USB_ROOT_HUB_ADDR + 1; + x != bus->devices_max; x++) { + + udev = bus->devices[x]; + if (udev == NULL) + continue; + + temp = ticks - udev->pwr_save.last_xfer_time; + + if (usb_peer_should_wakeup(udev)) { + /* check if we are suspended */ + if (udev->flags.self_suspended != 0) { + USB_BUS_UNLOCK(bus); + usb_dev_resume_peer(udev); + USB_BUS_LOCK(bus); + } + } else if ((temp >= limit) && + (udev->flags.usb_mode == USB_MODE_HOST) && + (udev->flags.self_suspended == 0)) { + /* try to do suspend */ + + USB_BUS_UNLOCK(bus); + usb_dev_suspend_peer(udev); + USB_BUS_LOCK(bus); + } + } + + /* reset counters */ + + mintime = 0 - 1; + type_refs[0] = 0; + type_refs[1] = 0; + type_refs[2] = 0; + type_refs[3] = 0; + type_refs[4] = 0; + + /* Re-loop all the devices to get the actual state */ + + for (x = USB_ROOT_HUB_ADDR + 1; + x != bus->devices_max; x++) { + + udev = bus->devices[x]; + if (udev == NULL) + continue; + + /* we found a non-Root-Hub USB device */ + type_refs[4] += 1; + + /* "last_xfer_time" can be updated by a resume */ + temp = ticks - udev->pwr_save.last_xfer_time; + + /* + * Compute minimum time since last transfer for the complete + * bus: + */ + if (temp < mintime) + mintime = temp; + + if (udev->flags.self_suspended == 0) { + type_refs[0] += udev->pwr_save.type_refs[0]; + type_refs[1] += udev->pwr_save.type_refs[1]; + type_refs[2] += udev->pwr_save.type_refs[2]; + type_refs[3] += udev->pwr_save.type_refs[3]; + } + } + + if (mintime >= (1 * hz)) { + /* recompute power masks */ + DPRINTF("Recomputing power masks\n"); + bus->hw_power_state = 0; + if (type_refs[UE_CONTROL] != 0) + bus->hw_power_state |= USB_HW_POWER_CONTROL; + if (type_refs[UE_BULK] != 0) + bus->hw_power_state |= USB_HW_POWER_BULK; + if (type_refs[UE_INTERRUPT] != 0) + bus->hw_power_state |= USB_HW_POWER_INTERRUPT; + if (type_refs[UE_ISOCHRONOUS] != 0) + bus->hw_power_state |= USB_HW_POWER_ISOC; + if (type_refs[4] != 0) + bus->hw_power_state |= USB_HW_POWER_NON_ROOT_HUB; + } + USB_BUS_UNLOCK(bus); + + if (bus->methods->set_hw_power != NULL) { + /* always update hardware power! */ + (bus->methods->set_hw_power) (bus); + } + return; +} +#endif + +/*------------------------------------------------------------------------* + * usb_dev_resume_peer + * + * This function will resume an USB peer and do the required USB + * signalling to get an USB device out of the suspended state. + *------------------------------------------------------------------------*/ +static void +usb_dev_resume_peer(struct usb_device *udev) +{ + struct usb_bus *bus; + int err; + + /* be NULL safe */ + if (udev == NULL) + return; + + /* check if already resumed */ + if (udev->flags.self_suspended == 0) + return; + + /* we need a parent HUB to do resume */ + if (udev->parent_hub == NULL) + return; + + DPRINTF("udev=%p\n", udev); + + if ((udev->flags.usb_mode == USB_MODE_DEVICE) && + (udev->flags.remote_wakeup == 0)) { + /* + * If the host did not set the remote wakeup feature, we can + * not wake it up either! + */ + DPRINTF("remote wakeup is not set!\n"); + return; + } + /* get bus pointer */ + bus = udev->bus; + + /* resume parent hub first */ + usb_dev_resume_peer(udev->parent_hub); + + /* reduce chance of instant resume failure by waiting a little bit */ + usb_pause_mtx(NULL, USB_MS_TO_TICKS(20)); + + if (usb_device_20_compatible(udev)) { + /* resume current port (Valid in Host and Device Mode) */ + err = usbd_req_clear_port_feature(udev->parent_hub, + NULL, udev->port_no, UHF_PORT_SUSPEND); + if (err) { + DPRINTFN(0, "Resuming port failed\n"); + return; + } + } + + /* resume settle time */ + usb_pause_mtx(NULL, USB_MS_TO_TICKS(USB_PORT_RESUME_DELAY)); + + if (bus->methods->device_resume != NULL) { + /* resume USB device on the USB controller */ + (bus->methods->device_resume) (udev); + } + USB_BUS_LOCK(bus); + /* set that this device is now resumed */ + udev->flags.self_suspended = 0; +#if USB_HAVE_POWERD + /* make sure that we don't go into suspend right away */ + udev->pwr_save.last_xfer_time = ticks; + + /* make sure the needed power masks are on */ + if (udev->pwr_save.type_refs[UE_CONTROL] != 0) + bus->hw_power_state |= USB_HW_POWER_CONTROL; + if (udev->pwr_save.type_refs[UE_BULK] != 0) + bus->hw_power_state |= USB_HW_POWER_BULK; + if (udev->pwr_save.type_refs[UE_INTERRUPT] != 0) + bus->hw_power_state |= USB_HW_POWER_INTERRUPT; + if (udev->pwr_save.type_refs[UE_ISOCHRONOUS] != 0) + bus->hw_power_state |= USB_HW_POWER_ISOC; +#endif + USB_BUS_UNLOCK(bus); + + if (bus->methods->set_hw_power != NULL) { + /* always update hardware power! */ + (bus->methods->set_hw_power) (bus); + } + + usbd_sr_lock(udev); + + /* notify all sub-devices about resume */ + err = usb_suspend_resume(udev, 0); + + usbd_sr_unlock(udev); + + /* check if peer has wakeup capability */ + if (usb_peer_can_wakeup(udev) && + usb_device_20_compatible(udev)) { + /* clear remote wakeup */ + err = usbd_req_clear_device_feature(udev, + NULL, UF_DEVICE_REMOTE_WAKEUP); + if (err) { + DPRINTFN(0, "Clearing device " + "remote wakeup failed: %s\n", + usbd_errstr(err)); + } + } +} + +/*------------------------------------------------------------------------* + * usb_dev_suspend_peer + * + * This function will suspend an USB peer and do the required USB + * signalling to get an USB device into the suspended state. + *------------------------------------------------------------------------*/ +static void +usb_dev_suspend_peer(struct usb_device *udev) +{ + struct usb_device *child; + int err; + uint8_t x; + uint8_t nports; + +repeat: + /* be NULL safe */ + if (udev == NULL) + return; + + /* check if already suspended */ + if (udev->flags.self_suspended) + return; + + /* we need a parent HUB to do suspend */ + if (udev->parent_hub == NULL) + return; + + DPRINTF("udev=%p\n", udev); + + /* check if the current device is a HUB */ + if (udev->hub != NULL) { + nports = udev->hub->nports; + + /* check if all devices on the HUB are suspended */ + for (x = 0; x != nports; x++) { + child = usb_bus_port_get_device(udev->bus, + udev->hub->ports + x); + + if (child == NULL) + continue; + + if (child->flags.self_suspended) + continue; + + DPRINTFN(1, "Port %u is busy on the HUB!\n", x + 1); + return; + } + } + + if (usb_peer_can_wakeup(udev) && + usb_device_20_compatible(udev)) { + /* + * This request needs to be done before we set + * "udev->flags.self_suspended": + */ + + /* allow device to do remote wakeup */ + err = usbd_req_set_device_feature(udev, + NULL, UF_DEVICE_REMOTE_WAKEUP); + if (err) { + DPRINTFN(0, "Setting device " + "remote wakeup failed\n"); + } + } + + USB_BUS_LOCK(udev->bus); + /* + * Checking for suspend condition and setting suspended bit + * must be atomic! + */ + err = usb_peer_should_wakeup(udev); + if (err == 0) { + /* + * Set that this device is suspended. This variable + * must be set before calling USB controller suspend + * callbacks. + */ + udev->flags.self_suspended = 1; + } + USB_BUS_UNLOCK(udev->bus); + + if (err != 0) { + if (usb_peer_can_wakeup(udev) && + usb_device_20_compatible(udev)) { + /* allow device to do remote wakeup */ + err = usbd_req_clear_device_feature(udev, + NULL, UF_DEVICE_REMOTE_WAKEUP); + if (err) { + DPRINTFN(0, "Setting device " + "remote wakeup failed\n"); + } + } + + if (udev->flags.usb_mode == USB_MODE_DEVICE) { + /* resume parent HUB first */ + usb_dev_resume_peer(udev->parent_hub); + + /* reduce chance of instant resume failure by waiting a little bit */ + usb_pause_mtx(NULL, USB_MS_TO_TICKS(20)); + + /* resume current port (Valid in Host and Device Mode) */ + err = usbd_req_clear_port_feature(udev->parent_hub, + NULL, udev->port_no, UHF_PORT_SUSPEND); + + /* resume settle time */ + usb_pause_mtx(NULL, USB_MS_TO_TICKS(USB_PORT_RESUME_DELAY)); + } + DPRINTF("Suspend was cancelled!\n"); + return; + } + + usbd_sr_lock(udev); + + /* notify all sub-devices about suspend */ + err = usb_suspend_resume(udev, 1); + + usbd_sr_unlock(udev); + + if (udev->bus->methods->device_suspend != NULL) { + usb_timeout_t temp; + + /* suspend device on the USB controller */ + (udev->bus->methods->device_suspend) (udev); + + /* do DMA delay */ + temp = usbd_get_dma_delay(udev); + if (temp != 0) + usb_pause_mtx(NULL, USB_MS_TO_TICKS(temp)); + + } + + if (usb_device_20_compatible(udev)) { + /* suspend current port */ + err = usbd_req_set_port_feature(udev->parent_hub, + NULL, udev->port_no, UHF_PORT_SUSPEND); + if (err) { + DPRINTFN(0, "Suspending port failed\n"); + return; + } + } + + udev = udev->parent_hub; + goto repeat; +} + +/*------------------------------------------------------------------------* + * usbd_set_power_mode + * + * This function will set the power mode, see USB_POWER_MODE_XXX for a + * USB device. + *------------------------------------------------------------------------*/ +void +usbd_set_power_mode(struct usb_device *udev, uint8_t power_mode) +{ + /* filter input argument */ + if ((power_mode != USB_POWER_MODE_ON) && + (power_mode != USB_POWER_MODE_OFF)) + power_mode = USB_POWER_MODE_SAVE; + + power_mode = usbd_filter_power_mode(udev, power_mode); + + udev->power_mode = power_mode; /* update copy of power mode */ + +#if USB_HAVE_POWERD + usb_bus_power_update(udev->bus); +#endif +} + +/*------------------------------------------------------------------------* + * usbd_filter_power_mode + * + * This function filters the power mode based on hardware requirements. + *------------------------------------------------------------------------*/ +uint8_t +usbd_filter_power_mode(struct usb_device *udev, uint8_t power_mode) +{ + struct usb_bus_methods *mtod; + int8_t temp; + + mtod = udev->bus->methods; + temp = -1; + + if (mtod->get_power_mode != NULL) + (mtod->get_power_mode) (udev, &temp); + + /* check if we should not filter */ + if (temp < 0) + return (power_mode); + + /* use fixed power mode given by hardware driver */ + return (temp); +} diff --git a/freebsd/sys/dev/usb/usb_hub.h b/freebsd/sys/dev/usb/usb_hub.h new file mode 100644 index 00000000..6f4637da --- /dev/null +++ b/freebsd/sys/dev/usb/usb_hub.h @@ -0,0 +1,83 @@ +/* $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. + */ + +#ifndef _USB_HUB_HH_ +#define _USB_HUB_HH_ + +/* + * The following structure defines an USB port. + */ +struct usb_port { + uint8_t restartcnt; +#define USB_RESTART_MAX 5 + uint8_t device_index; /* zero means not valid */ + enum usb_hc_mode usb_mode; /* host or device mode */ +}; + +/* + * The following structure defines how many bytes are + * left in an 1ms USB time slot. + */ +struct usb_fs_isoc_schedule { + uint16_t total_bytes; + uint8_t frame_bytes; + uint8_t frame_slot; +}; + +/* + * The following structure defines an USB HUB. + */ +struct usb_hub { +#if USB_HAVE_TT_SUPPORT + struct usb_fs_isoc_schedule fs_isoc_schedule[USB_ISOC_TIME_MAX]; +#endif + struct usb_device *hubudev; /* the HUB device */ + usb_error_t (*explore) (struct usb_device *hub); + void *hubsoftc; + usb_size_t uframe_usage[USB_HS_MICRO_FRAMES_MAX]; + uint16_t portpower; /* mA per USB port */ + uint8_t isoc_last_time; + uint8_t nports; + struct usb_port ports[0]; +}; + +/* function prototypes */ + +void usb_hs_bandwidth_alloc(struct usb_xfer *xfer); +void usb_hs_bandwidth_free(struct usb_xfer *xfer); +void usbd_fs_isoc_schedule_init_all(struct usb_fs_isoc_schedule *fss); +void usb_bus_port_set_device(struct usb_bus *bus, struct usb_port *up, + struct usb_device *udev, uint8_t device_index); +struct usb_device *usb_bus_port_get_device(struct usb_bus *bus, + struct usb_port *up); +void usb_needs_explore(struct usb_bus *bus, uint8_t do_probe); +void usb_needs_explore_all(void); +void usb_bus_power_update(struct usb_bus *bus); +void usb_bus_powerd(struct usb_bus *bus); +void uhub_root_intr(struct usb_bus *, const uint8_t *, uint8_t); +usb_error_t uhub_query_info(struct usb_device *, uint8_t *, uint8_t *); + +#endif /* _USB_HUB_HH_ */ diff --git a/freebsd/sys/dev/usb/usb_ioctl.h b/freebsd/sys/dev/usb/usb_ioctl.h new file mode 100644 index 00000000..99e21f09 --- /dev/null +++ b/freebsd/sys/dev/usb/usb_ioctl.h @@ -0,0 +1,272 @@ +/* $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. + */ + +#ifndef _USB_IOCTL_HH_ +#define _USB_IOCTL_HH_ + +#include <freebsd/sys/ioccom.h> + +/* Building "kdump" depends on these includes */ + +#include <freebsd/dev/usb/usb_endian.h> +#include <freebsd/dev/usb/usb.h> + +#define USB_DEVICE_NAME "usbctl" +#define USB_DEVICE_DIR "usb" +#define USB_GENERIC_NAME "ugen" + +struct usb_read_dir { + void *urd_data; + uint32_t urd_startentry; + uint32_t urd_maxlen; +}; + +struct usb_ctl_request { + void *ucr_data; + uint16_t ucr_flags; + uint16_t ucr_actlen; /* actual length transferred */ + uint8_t ucr_addr; /* zero - currently not used */ + struct usb_device_request ucr_request; +}; + +struct usb_alt_interface { + uint8_t uai_interface_index; + uint8_t uai_alt_index; +}; + +struct usb_gen_descriptor { + void *ugd_data; + uint16_t ugd_lang_id; + uint16_t ugd_maxlen; + uint16_t ugd_actlen; + uint16_t ugd_offset; + uint8_t ugd_config_index; + uint8_t ugd_string_index; + uint8_t ugd_iface_index; + uint8_t ugd_altif_index; + uint8_t ugd_endpt_index; + uint8_t ugd_report_type; + uint8_t reserved[8]; +}; + +struct usb_device_info { + uint16_t udi_productNo; + uint16_t udi_vendorNo; + uint16_t udi_releaseNo; + uint16_t udi_power; /* power consumption in mA, 0 if + * selfpowered */ + uint8_t udi_bus; + uint8_t udi_addr; /* device address */ + uint8_t udi_index; /* device index */ + uint8_t udi_class; + uint8_t udi_subclass; + uint8_t udi_protocol; + uint8_t udi_config_no; /* current config number */ + uint8_t udi_config_index; /* current config index */ + uint8_t udi_speed; /* see "USB_SPEED_XXX" */ + uint8_t udi_mode; /* see "USB_MODE_XXX" */ + uint8_t udi_nports; + uint8_t udi_hubaddr; /* parent HUB address */ + uint8_t udi_hubindex; /* parent HUB device index */ + uint8_t udi_hubport; /* parent HUB port */ + uint8_t udi_power_mode; /* see "USB_POWER_MODE_XXX" */ + uint8_t udi_suspended; /* set if device is suspended */ + uint8_t udi_reserved[16]; /* leave space for the future */ + char udi_product[128]; + char udi_vendor[128]; + char udi_serial[64]; + char udi_release[8]; +}; + +struct usb_device_stats { + uint32_t uds_requests_ok[4]; /* Indexed by transfer type UE_XXX */ + uint32_t uds_requests_fail[4]; /* Indexed by transfer type UE_XXX */ +}; + +struct usb_fs_start { + uint8_t ep_index; +}; + +struct usb_fs_stop { + uint8_t ep_index; +}; + +struct usb_fs_complete { + uint8_t ep_index; +}; + +/* This structure is used for all endpoint types */ +struct usb_fs_endpoint { + /* + * NOTE: isochronous USB transfer only use one buffer, but can have + * multiple frame lengths ! + */ + void **ppBuffer; /* pointer to userland buffers */ + uint32_t *pLength; /* pointer to frame lengths, updated + * to actual length */ + uint32_t nFrames; /* number of frames */ + uint32_t aFrames; /* actual number of frames */ + uint16_t flags; + /* a single short frame will terminate */ +#define USB_FS_FLAG_SINGLE_SHORT_OK 0x0001 + /* multiple short frames are allowed */ +#define USB_FS_FLAG_MULTI_SHORT_OK 0x0002 + /* all frame(s) transmitted are short terminated */ +#define USB_FS_FLAG_FORCE_SHORT 0x0004 + /* will do a clear-stall before xfer */ +#define USB_FS_FLAG_CLEAR_STALL 0x0008 + uint16_t timeout; /* in milliseconds */ + /* isocronous completion time in milliseconds - used for echo cancel */ + uint16_t isoc_time_complete; + /* timeout value for no timeout */ +#define USB_FS_TIMEOUT_NONE 0 + int status; /* see USB_ERR_XXX */ +}; + +struct usb_fs_init { + /* userland pointer to endpoints structure */ + struct usb_fs_endpoint *pEndpoints; + /* maximum number of endpoints */ + uint8_t ep_index_max; +}; + +struct usb_fs_uninit { + uint8_t dummy; /* zero */ +}; + +struct usb_fs_open { +#define USB_FS_MAX_BUFSIZE (1 << 18) + uint32_t max_bufsize; +#define USB_FS_MAX_FRAMES (1 << 12) + uint32_t max_frames; + uint16_t max_packet_length; /* read only */ + uint8_t dev_index; /* currently unused */ + uint8_t ep_index; + uint8_t ep_no; /* bEndpointNumber */ +}; + +struct usb_fs_close { + uint8_t ep_index; +}; + +struct usb_fs_clear_stall_sync { + uint8_t ep_index; +}; + +struct usb_gen_quirk { + uint16_t index; /* Quirk Index */ + uint16_t vid; /* Vendor ID */ + uint16_t pid; /* Product ID */ + uint16_t bcdDeviceLow; /* Low Device Revision */ + uint16_t bcdDeviceHigh; /* High Device Revision */ + uint16_t reserved[2]; + /* + * String version of quirk including terminating zero. See UQ_XXX in + * "usb_quirk.h". + */ + char quirkname[64 - 14]; +}; + +/* USB controller */ +#define USB_REQUEST _IOWR('U', 1, struct usb_ctl_request) +#define USB_SETDEBUG _IOW ('U', 2, int) +#define USB_DISCOVER _IO ('U', 3) +#define USB_DEVICEINFO _IOWR('U', 4, struct usb_device_info) +#define USB_DEVICESTATS _IOR ('U', 5, struct usb_device_stats) +#define USB_DEVICEENUMERATE _IOW ('U', 6, int) + +/* Generic HID device */ +#define USB_GET_REPORT_DESC _IOWR('U', 21, struct usb_gen_descriptor) +#define USB_SET_IMMED _IOW ('U', 22, int) +#define USB_GET_REPORT _IOWR('U', 23, struct usb_gen_descriptor) +#define USB_SET_REPORT _IOW ('U', 24, struct usb_gen_descriptor) +#define USB_GET_REPORT_ID _IOR ('U', 25, int) + +/* Generic USB device */ +#define USB_GET_CONFIG _IOR ('U', 100, int) +#define USB_SET_CONFIG _IOW ('U', 101, int) +#define USB_GET_ALTINTERFACE _IOWR('U', 102, struct usb_alt_interface) +#define USB_SET_ALTINTERFACE _IOWR('U', 103, struct usb_alt_interface) +#define USB_GET_DEVICE_DESC _IOR ('U', 105, struct usb_device_descriptor) +#define USB_GET_CONFIG_DESC _IOR ('U', 106, struct usb_config_descriptor) +#define USB_GET_RX_INTERFACE_DESC _IOR ('U', 107, struct usb_interface_descriptor) +#define USB_GET_RX_ENDPOINT_DESC _IOR ('U', 108, struct usb_endpoint_descriptor) +#define USB_GET_FULL_DESC _IOWR('U', 109, struct usb_gen_descriptor) +#define USB_GET_STRING_DESC _IOWR('U', 110, struct usb_gen_descriptor) +#define USB_DO_REQUEST _IOWR('U', 111, struct usb_ctl_request) +#define USB_GET_DEVICEINFO _IOR ('U', 112, struct usb_device_info) +#define USB_SET_RX_SHORT_XFER _IOW ('U', 113, int) +#define USB_SET_RX_TIMEOUT _IOW ('U', 114, int) +#define USB_GET_RX_FRAME_SIZE _IOR ('U', 115, int) +#define USB_GET_RX_BUFFER_SIZE _IOR ('U', 117, int) +#define USB_SET_RX_BUFFER_SIZE _IOW ('U', 118, int) +#define USB_SET_RX_STALL_FLAG _IOW ('U', 119, int) +#define USB_SET_TX_STALL_FLAG _IOW ('U', 120, int) +#define USB_GET_IFACE_DRIVER _IOWR('U', 121, struct usb_gen_descriptor) +#define USB_CLAIM_INTERFACE _IOW ('U', 122, int) +#define USB_RELEASE_INTERFACE _IOW ('U', 123, int) +#define USB_IFACE_DRIVER_ACTIVE _IOW ('U', 124, int) +#define USB_IFACE_DRIVER_DETACH _IOW ('U', 125, int) +#define USB_GET_PLUGTIME _IOR ('U', 126, uint32_t) +#define USB_READ_DIR _IOW ('U', 127, struct usb_read_dir) +/* 128 - 135 unused */ +#define USB_SET_TX_FORCE_SHORT _IOW ('U', 136, int) +#define USB_SET_TX_TIMEOUT _IOW ('U', 137, int) +#define USB_GET_TX_FRAME_SIZE _IOR ('U', 138, int) +#define USB_GET_TX_BUFFER_SIZE _IOR ('U', 139, int) +#define USB_SET_TX_BUFFER_SIZE _IOW ('U', 140, int) +#define USB_GET_TX_INTERFACE_DESC _IOR ('U', 141, struct usb_interface_descriptor) +#define USB_GET_TX_ENDPOINT_DESC _IOR ('U', 142, struct usb_endpoint_descriptor) +#define USB_SET_PORT_ENABLE _IOW ('U', 143, int) +#define USB_SET_PORT_DISABLE _IOW ('U', 144, int) +#define USB_SET_POWER_MODE _IOW ('U', 145, int) +#define USB_GET_POWER_MODE _IOR ('U', 146, int) +#define USB_SET_TEMPLATE _IOW ('U', 147, int) +#define USB_GET_TEMPLATE _IOR ('U', 148, int) + +/* Modem device */ +#define USB_GET_CM_OVER_DATA _IOR ('U', 180, int) +#define USB_SET_CM_OVER_DATA _IOW ('U', 181, int) + +/* USB file system interface */ +#define USB_FS_START _IOW ('U', 192, struct usb_fs_start) +#define USB_FS_STOP _IOW ('U', 193, struct usb_fs_stop) +#define USB_FS_COMPLETE _IOR ('U', 194, struct usb_fs_complete) +#define USB_FS_INIT _IOW ('U', 195, struct usb_fs_init) +#define USB_FS_UNINIT _IOW ('U', 196, struct usb_fs_uninit) +#define USB_FS_OPEN _IOWR('U', 197, struct usb_fs_open) +#define USB_FS_CLOSE _IOW ('U', 198, struct usb_fs_close) +#define USB_FS_CLEAR_STALL_SYNC _IOW ('U', 199, struct usb_fs_clear_stall_sync) + +/* USB quirk system interface */ +#define USB_DEV_QUIRK_GET _IOWR('Q', 0, struct usb_gen_quirk) +#define USB_QUIRK_NAME_GET _IOWR('Q', 1, struct usb_gen_quirk) +#define USB_DEV_QUIRK_ADD _IOW ('Q', 2, struct usb_gen_quirk) +#define USB_DEV_QUIRK_REMOVE _IOW ('Q', 3, struct usb_gen_quirk) + +#endif /* _USB_IOCTL_HH_ */ diff --git a/freebsd/sys/dev/usb/usb_lookup.c b/freebsd/sys/dev/usb/usb_lookup.c new file mode 100644 index 00000000..c730b6ba --- /dev/null +++ b/freebsd/sys/dev/usb/usb_lookup.c @@ -0,0 +1,156 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/* $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. + */ + +#include <freebsd/sys/stdint.h> +#include <freebsd/sys/stddef.h> +#include <freebsd/sys/param.h> +#include <freebsd/sys/queue.h> +#include <freebsd/sys/types.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/bus.h> +#include <freebsd/sys/linker_set.h> +#include <freebsd/sys/module.h> +#include <freebsd/sys/lock.h> +#include <freebsd/sys/mutex.h> +#include <freebsd/sys/condvar.h> +#include <freebsd/sys/sysctl.h> +#include <freebsd/sys/sx.h> +#include <freebsd/sys/unistd.h> +#include <freebsd/sys/callout.h> +#include <freebsd/sys/malloc.h> +#include <freebsd/sys/priv.h> + +#include <freebsd/dev/usb/usb.h> +#include <freebsd/dev/usb/usbdi.h> + +/*------------------------------------------------------------------------* + * usbd_lookup_id_by_info + * + * This functions takes an array of "struct usb_device_id" and tries + * to match the entries with the information in "struct usbd_lookup_info". + * + * NOTE: The "sizeof_id" parameter must be a multiple of the + * usb_device_id structure size. Else the behaviour of this function + * is undefined. + * + * Return values: + * NULL: No match found. + * Else: Pointer to matching entry. + *------------------------------------------------------------------------*/ +const struct usb_device_id * +usbd_lookup_id_by_info(const struct usb_device_id *id, usb_size_t sizeof_id, + const struct usbd_lookup_info *info) +{ + const struct usb_device_id *id_end; + + if (id == NULL) { + goto done; + } + id_end = (const void *)(((const uint8_t *)id) + sizeof_id); + + /* + * Keep on matching array entries until we find a match or + * until we reach the end of the matching array: + */ + for (; id != id_end; id++) { + + if ((id->match_flag_vendor) && + (id->idVendor != info->idVendor)) { + continue; + } + if ((id->match_flag_product) && + (id->idProduct != info->idProduct)) { + continue; + } + if ((id->match_flag_dev_lo) && + (id->bcdDevice_lo > info->bcdDevice)) { + continue; + } + if ((id->match_flag_dev_hi) && + (id->bcdDevice_hi < info->bcdDevice)) { + continue; + } + if ((id->match_flag_dev_class) && + (id->bDeviceClass != info->bDeviceClass)) { + continue; + } + if ((id->match_flag_dev_subclass) && + (id->bDeviceSubClass != info->bDeviceSubClass)) { + continue; + } + if ((id->match_flag_dev_protocol) && + (id->bDeviceProtocol != info->bDeviceProtocol)) { + continue; + } + if ((info->bDeviceClass == 0xFF) && + (!(id->match_flag_vendor)) && + ((id->match_flag_int_class) || + (id->match_flag_int_subclass) || + (id->match_flag_int_protocol))) { + continue; + } + if ((id->match_flag_int_class) && + (id->bInterfaceClass != info->bInterfaceClass)) { + continue; + } + if ((id->match_flag_int_subclass) && + (id->bInterfaceSubClass != info->bInterfaceSubClass)) { + continue; + } + if ((id->match_flag_int_protocol) && + (id->bInterfaceProtocol != info->bInterfaceProtocol)) { + continue; + } + /* We found a match! */ + return (id); + } + +done: + return (NULL); +} + +/*------------------------------------------------------------------------* + * usbd_lookup_id_by_uaa - factored out code + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +int +usbd_lookup_id_by_uaa(const struct usb_device_id *id, usb_size_t sizeof_id, + struct usb_attach_arg *uaa) +{ + id = usbd_lookup_id_by_info(id, sizeof_id, &uaa->info); + if (id) { + /* copy driver info */ + uaa->driver_info = id->driver_info; + return (0); + } + return (ENXIO); +} diff --git a/freebsd/sys/dev/usb/usb_mbuf.c b/freebsd/sys/dev/usb/usb_mbuf.c new file mode 100644 index 00000000..5aa99df4 --- /dev/null +++ b/freebsd/sys/dev/usb/usb_mbuf.c @@ -0,0 +1,101 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/* $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. + */ + +#include <freebsd/sys/stdint.h> +#include <freebsd/sys/stddef.h> +#include <freebsd/sys/param.h> +#include <freebsd/sys/queue.h> +#include <freebsd/sys/types.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/bus.h> +#include <freebsd/sys/linker_set.h> +#include <freebsd/sys/module.h> +#include <freebsd/sys/lock.h> +#include <freebsd/sys/mutex.h> +#include <freebsd/sys/condvar.h> +#include <freebsd/sys/sysctl.h> +#include <freebsd/sys/sx.h> +#include <freebsd/sys/unistd.h> +#include <freebsd/sys/callout.h> +#include <freebsd/sys/malloc.h> +#include <freebsd/sys/priv.h> + +#include <freebsd/dev/usb/usb.h> +#include <freebsd/dev/usb/usbdi.h> +#include <freebsd/dev/usb/usb_dev.h> +#include <freebsd/dev/usb/usb_mbuf.h> + +/*------------------------------------------------------------------------* + * usb_alloc_mbufs - allocate mbufs to an usbd interface queue + * + * Returns: + * A pointer that should be passed to "free()" when the buffer(s) + * should be released. + *------------------------------------------------------------------------*/ +void * +usb_alloc_mbufs(struct malloc_type *type, struct usb_ifqueue *ifq, + usb_size_t block_size, uint16_t nblocks) +{ + struct usb_mbuf *m_ptr; + uint8_t *data_ptr; + void *free_ptr = NULL; + usb_size_t alloc_size; + + /* align data */ + block_size += ((-block_size) & (USB_HOST_ALIGN - 1)); + + if (nblocks && block_size) { + + alloc_size = (block_size + sizeof(struct usb_mbuf)) * nblocks; + + free_ptr = malloc(alloc_size, type, M_WAITOK | M_ZERO); + + if (free_ptr == NULL) { + goto done; + } + m_ptr = free_ptr; + data_ptr = (void *)(m_ptr + nblocks); + + while (nblocks--) { + + m_ptr->cur_data_ptr = + m_ptr->min_data_ptr = data_ptr; + + m_ptr->cur_data_len = + m_ptr->max_data_len = block_size; + + USB_IF_ENQUEUE(ifq, m_ptr); + + m_ptr++; + data_ptr += block_size; + } + } +done: + return (free_ptr); +} diff --git a/freebsd/sys/dev/usb/usb_mbuf.h b/freebsd/sys/dev/usb/usb_mbuf.h new file mode 100644 index 00000000..44dba71b --- /dev/null +++ b/freebsd/sys/dev/usb/usb_mbuf.h @@ -0,0 +1,90 @@ +/* $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. + */ + +#ifndef _USB_MBUF_HH_ +#define _USB_MBUF_HH_ + +/* + * The following structure defines a minimum re-implementation of the + * mbuf system in the kernel. + */ +struct usb_mbuf { + uint8_t *cur_data_ptr; + uint8_t *min_data_ptr; + struct usb_mbuf *usb_nextpkt; + struct usb_mbuf *usb_next; + + usb_size_t cur_data_len; + usb_size_t max_data_len; + uint8_t last_packet:1; + uint8_t unused:7; +}; + +#define USB_IF_ENQUEUE(ifq, m) do { \ + (m)->usb_nextpkt = NULL; \ + if ((ifq)->ifq_tail == NULL) \ + (ifq)->ifq_head = (m); \ + else \ + (ifq)->ifq_tail->usb_nextpkt = (m); \ + (ifq)->ifq_tail = (m); \ + (ifq)->ifq_len++; \ + } while (0) + +#define USB_IF_DEQUEUE(ifq, m) do { \ + (m) = (ifq)->ifq_head; \ + if (m) { \ + if (((ifq)->ifq_head = (m)->usb_nextpkt) == NULL) { \ + (ifq)->ifq_tail = NULL; \ + } \ + (m)->usb_nextpkt = NULL; \ + (ifq)->ifq_len--; \ + } \ + } while (0) + +#define USB_IF_PREPEND(ifq, m) do { \ + (m)->usb_nextpkt = (ifq)->ifq_head; \ + if ((ifq)->ifq_tail == NULL) { \ + (ifq)->ifq_tail = (m); \ + } \ + (ifq)->ifq_head = (m); \ + (ifq)->ifq_len++; \ + } while (0) + +#define USB_IF_QFULL(ifq) ((ifq)->ifq_len >= (ifq)->ifq_maxlen) +#define USB_IF_QLEN(ifq) ((ifq)->ifq_len) +#define USB_IF_POLL(ifq, m) ((m) = (ifq)->ifq_head) + +#define USB_MBUF_RESET(m) do { \ + (m)->cur_data_ptr = (m)->min_data_ptr; \ + (m)->cur_data_len = (m)->max_data_len; \ + (m)->last_packet = 0; \ + } while (0) + +/* prototypes */ +void *usb_alloc_mbufs(struct malloc_type *type, struct usb_ifqueue *ifq, + usb_size_t block_size, uint16_t nblocks); + +#endif /* _USB_MBUF_HH_ */ diff --git a/freebsd/sys/dev/usb/usb_msctest.c b/freebsd/sys/dev/usb/usb_msctest.c new file mode 100644 index 00000000..0b773e29 --- /dev/null +++ b/freebsd/sys/dev/usb/usb_msctest.c @@ -0,0 +1,641 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/* $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. + */ + +/* + * The following file contains code that will detect USB autoinstall + * disks. + * + * TODO: Potentially we could add code to automatically detect USB + * mass storage quirks for not supported SCSI commands! + */ + +#include <freebsd/sys/stdint.h> +#include <freebsd/sys/stddef.h> +#include <freebsd/sys/param.h> +#include <freebsd/sys/queue.h> +#include <freebsd/sys/types.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/bus.h> +#include <freebsd/sys/linker_set.h> +#include <freebsd/sys/module.h> +#include <freebsd/sys/lock.h> +#include <freebsd/sys/mutex.h> +#include <freebsd/sys/condvar.h> +#include <freebsd/sys/sysctl.h> +#include <freebsd/sys/sx.h> +#include <freebsd/sys/unistd.h> +#include <freebsd/sys/callout.h> +#include <freebsd/sys/malloc.h> +#include <freebsd/sys/priv.h> + +#include <freebsd/dev/usb/usb.h> +#include <freebsd/dev/usb/usbdi.h> +#include <freebsd/dev/usb/usbdi_util.h> + +#define USB_DEBUG_VAR usb_debug + +#include <freebsd/dev/usb/usb_busdma.h> +#include <freebsd/dev/usb/usb_process.h> +#include <freebsd/dev/usb/usb_transfer.h> +#include <freebsd/dev/usb/usb_msctest.h> +#include <freebsd/dev/usb/usb_debug.h> +#include <freebsd/dev/usb/usb_busdma.h> +#include <freebsd/dev/usb/usb_device.h> +#include <freebsd/dev/usb/usb_request.h> +#include <freebsd/dev/usb/usb_util.h> +#include <freebsd/dev/usb/quirk/usb_quirk.h> + +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_INQ_LEN 0x24 +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_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_tct_eject[] = { 0x06, 0xf5, 0x04, 0x02, 0x52, 0x70 }; + +#define BULK_SIZE 64 /* dummy */ +#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 */ + + uint8_t cmd_len; /* bytes */ + uint8_t dir; + uint8_t lun; + uint8_t state; + uint8_t status_try; + int error; + + uint8_t buffer[256]; +}; + +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 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 uint8_t 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); +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 = BULK_SIZE, + .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 = BULK_SIZE, + .flags = {.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 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_data(xfer, 0, &sc->cbw, sizeof(sc->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); + return; + } + 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); + return; + + default: /* Error */ + if (error == USB_ERR_CANCELLED) { + bbb_done(sc, error); + } else { + bbb_transfer_start(sc, ST_DATA_WR_CS); + } + return; + + } +} + +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, sumlen; + + usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + + /* very simple status check */ + + if (actlen < sizeof(sc->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_data(xfer, 0, &sc->csw, sizeof(sc->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; + } +} + +/*------------------------------------------------------------------------* + * bbb_command_start - execute a SCSI command synchronously + * + * Return values + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static uint8_t +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->cmd_len = cmd_len; + bzero(&sc->cbw.CBWCDB, sizeof(sc->cbw.CBWCDB)); + bcopy(cmd_ptr, &sc->cbw.CBWCDB, cmd_len); + DPRINTFN(1, "SCSI cmd = %*D\n", (int)cmd_len, &sc->cbw.CBWCDB, ":"); + + 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); + } + mtx_unlock(&sc->mtx); + return (sc->error); +} + +static struct bbb_transfer * +bbb_attach(struct usb_device *udev, uint8_t iface_index) +{ + struct usb_interface *iface; + struct usb_interface_descriptor *id; + struct bbb_transfer *sc; + usb_error_t err; + + iface = usbd_get_iface(udev, iface_index); + if (iface == NULL) + return (NULL); + + id = iface->idesc; + if (id == NULL || id->bInterfaceClass != UICLASS_MASS) + return (NULL); + + 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); + } + + 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, bbb_config, + ST_MAX, sc, &sc->mtx); + if (err) { + bbb_detach(sc); + return (NULL); + } + 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; + usb_error_t err; + uint8_t timeout, is_cdrom; + uint8_t sid_type; + + sc = bbb_attach(udev, iface_index); + 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); +} + +usb_error_t +usb_msc_eject(struct usb_device *udev, uint8_t iface_index, int method) +{ + struct bbb_transfer *sc; + usb_error_t err; + + sc = bbb_attach(udev, iface_index); + if (sc == NULL) + return (USB_ERR_INVAL); + + err = 0; + 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_start_stop_unit, sizeof(scsi_start_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_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, + sizeof(sc->buffer), &scsi_tct_eject, + sizeof(scsi_tct_eject), USB_MS_HZ); + break; + default: + printf("usb_msc_eject: unknown eject method (%d)\n", method); + break; + } + DPRINTF("Eject CD command status: %s\n", usbd_errstr(err)); + + bbb_detach(sc); + return (0); +} diff --git a/freebsd/sys/dev/usb/usb_msctest.h b/freebsd/sys/dev/usb/usb_msctest.h new file mode 100644 index 00000000..6ba2c3fd --- /dev/null +++ b/freebsd/sys/dev/usb/usb_msctest.h @@ -0,0 +1,44 @@ +/* $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. + */ + +#ifndef _USB_MSCTEST_HH_ +#define _USB_MSCTEST_HH_ + +enum { + MSC_EJECT_STOPUNIT, + MSC_EJECT_REZERO, + MSC_EJECT_ZTESTOR, + MSC_EJECT_CMOTECH, + MSC_EJECT_HUAWEI, + MSC_EJECT_TCT, +}; + +int usb_iface_is_cdrom(struct usb_device *udev, + uint8_t iface_index); +usb_error_t usb_msc_eject(struct usb_device *udev, + uint8_t iface_index, int method); + +#endif /* _USB_MSCTEST_HH_ */ diff --git a/freebsd/sys/dev/usb/usb_parse.c b/freebsd/sys/dev/usb/usb_parse.c new file mode 100644 index 00000000..8681a94a --- /dev/null +++ b/freebsd/sys/dev/usb/usb_parse.c @@ -0,0 +1,291 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/* $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. + */ + +#include <freebsd/sys/stdint.h> +#include <freebsd/sys/stddef.h> +#include <freebsd/sys/param.h> +#include <freebsd/sys/queue.h> +#include <freebsd/sys/types.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/bus.h> +#include <freebsd/sys/linker_set.h> +#include <freebsd/sys/module.h> +#include <freebsd/sys/lock.h> +#include <freebsd/sys/mutex.h> +#include <freebsd/sys/condvar.h> +#include <freebsd/sys/sysctl.h> +#include <freebsd/sys/sx.h> +#include <freebsd/sys/unistd.h> +#include <freebsd/sys/callout.h> +#include <freebsd/sys/malloc.h> +#include <freebsd/sys/priv.h> + +#include <freebsd/dev/usb/usb.h> +#include <freebsd/dev/usb/usbdi.h> +#include <freebsd/dev/usb/usbdi_util.h> + + +/*------------------------------------------------------------------------* + * usb_desc_foreach + * + * This function is the safe way to iterate across the USB config + * descriptor. It contains several checks against invalid + * descriptors. If the "desc" argument passed to this function is + * "NULL" the first descriptor, if any, will be returned. + * + * Return values: + * NULL: End of descriptors + * Else: Next descriptor after "desc" + *------------------------------------------------------------------------*/ +struct usb_descriptor * +usb_desc_foreach(struct usb_config_descriptor *cd, + struct usb_descriptor *_desc) +{ + uint8_t *desc_next; + uint8_t *start; + uint8_t *end; + uint8_t *desc; + + /* be NULL safe */ + if (cd == NULL) + return (NULL); + + /* We assume that the "wTotalLength" has been checked. */ + start = (uint8_t *)cd; + end = start + UGETW(cd->wTotalLength); + desc = (uint8_t *)_desc; + + /* Get start of next USB descriptor. */ + if (desc == NULL) + desc = start; + else + desc = desc + desc[0]; + + /* Check that the next USB descriptor is within the range. */ + if ((desc < start) || (desc >= end)) + return (NULL); /* out of range, or EOD */ + + /* Check that the second next USB descriptor is within range. */ + desc_next = desc + desc[0]; + if ((desc_next < start) || (desc_next > end)) + return (NULL); /* out of range */ + + /* Check minimum descriptor length. */ + if (desc[0] < 3) + return (NULL); /* too short descriptor */ + + /* Return start of next descriptor. */ + return ((struct usb_descriptor *)desc); +} + +/*------------------------------------------------------------------------* + * usb_idesc_foreach + * + * This function will iterate the interface descriptors in the config + * descriptor. The parse state structure should be zeroed before + * calling this function the first time. + * + * Return values: + * NULL: End of descriptors + * Else: A valid interface descriptor + *------------------------------------------------------------------------*/ +struct usb_interface_descriptor * +usb_idesc_foreach(struct usb_config_descriptor *cd, + struct usb_idesc_parse_state *ps) +{ + struct usb_interface_descriptor *id; + uint8_t new_iface; + + /* retrieve current descriptor */ + id = (struct usb_interface_descriptor *)ps->desc; + /* default is to start a new interface */ + new_iface = 1; + + while (1) { + id = (struct usb_interface_descriptor *) + usb_desc_foreach(cd, (struct usb_descriptor *)id); + if (id == NULL) + break; + if ((id->bDescriptorType == UDESC_INTERFACE) && + (id->bLength >= sizeof(*id))) { + if (ps->iface_no_last == id->bInterfaceNumber) + new_iface = 0; + ps->iface_no_last = id->bInterfaceNumber; + break; + } + } + + if (ps->desc == NULL) { + /* first time */ + } else if (new_iface) { + /* new interface */ + ps->iface_index ++; + ps->iface_index_alt = 0; + } else { + /* new alternate interface */ + ps->iface_index_alt ++; + } + + /* store and return current descriptor */ + ps->desc = (struct usb_descriptor *)id; + return (id); +} + +/*------------------------------------------------------------------------* + * usb_edesc_foreach + * + * This function will iterate all the endpoint descriptors within an + * interface descriptor. Starting value for the "ped" argument should + * be a valid interface descriptor. + * + * Return values: + * NULL: End of descriptors + * Else: A valid endpoint descriptor + *------------------------------------------------------------------------*/ +struct usb_endpoint_descriptor * +usb_edesc_foreach(struct usb_config_descriptor *cd, + struct usb_endpoint_descriptor *ped) +{ + struct usb_descriptor *desc; + + desc = ((struct usb_descriptor *)ped); + + while ((desc = usb_desc_foreach(cd, desc))) { + if (desc->bDescriptorType == UDESC_INTERFACE) { + break; + } + if (desc->bDescriptorType == UDESC_ENDPOINT) { + if (desc->bLength < sizeof(*ped)) { + /* endpoint descriptor is invalid */ + break; + } + return ((struct usb_endpoint_descriptor *)desc); + } + } + return (NULL); +} + +/*------------------------------------------------------------------------* + * usb_ed_comp_foreach + * + * This function will iterate all the endpoint companion descriptors + * within an endpoint descriptor in an interface descriptor. Starting + * value for the "ped" argument should be a valid endpoint companion + * descriptor. + * + * Return values: + * NULL: End of descriptors + * Else: A valid endpoint companion descriptor + *------------------------------------------------------------------------*/ +struct usb_endpoint_ss_comp_descriptor * +usb_ed_comp_foreach(struct usb_config_descriptor *cd, + struct usb_endpoint_ss_comp_descriptor *ped) +{ + struct usb_descriptor *desc; + + desc = ((struct usb_descriptor *)ped); + + while ((desc = usb_desc_foreach(cd, desc))) { + if (desc->bDescriptorType == UDESC_INTERFACE) + break; + if (desc->bDescriptorType == UDESC_ENDPOINT) + break; + if (desc->bDescriptorType == UDESC_ENDPOINT_SS_COMP) { + if (desc->bLength < sizeof(*ped)) { + /* endpoint companion descriptor is invalid */ + break; + } + return ((struct usb_endpoint_ss_comp_descriptor *)desc); + } + } + return (NULL); +} + +/*------------------------------------------------------------------------* + * usbd_get_no_descriptors + * + * This function will count the total number of descriptors in the + * configuration descriptor of type "type". + *------------------------------------------------------------------------*/ +uint8_t +usbd_get_no_descriptors(struct usb_config_descriptor *cd, uint8_t type) +{ + struct usb_descriptor *desc = NULL; + uint8_t count = 0; + + while ((desc = usb_desc_foreach(cd, desc))) { + if (desc->bDescriptorType == type) { + count++; + if (count == 0xFF) + break; /* crazy */ + } + } + return (count); +} + +/*------------------------------------------------------------------------* + * usbd_get_no_alts + * + * Return value: + * Number of alternate settings for the given interface descriptor + * pointer. If the USB descriptor is corrupt, the returned value can + * be greater than the actual number of alternate settings. + *------------------------------------------------------------------------*/ +uint8_t +usbd_get_no_alts(struct usb_config_descriptor *cd, + struct usb_interface_descriptor *id) +{ + struct usb_descriptor *desc; + uint8_t n; + uint8_t ifaceno; + + /* Reset interface count */ + + n = 0; + + /* Get the interface number */ + + ifaceno = id->bInterfaceNumber; + + /* Iterate all the USB descriptors */ + + desc = NULL; + while ((desc = usb_desc_foreach(cd, desc))) { + if ((desc->bDescriptorType == UDESC_INTERFACE) && + (desc->bLength >= sizeof(*id))) { + id = (struct usb_interface_descriptor *)desc; + if (id->bInterfaceNumber == ifaceno) { + n++; + if (n == 0xFF) + break; /* crazy */ + } + } + } + return (n); +} diff --git a/freebsd/sys/dev/usb/usb_process.c b/freebsd/sys/dev/usb/usb_process.c new file mode 100644 index 00000000..cfaa5ee6 --- /dev/null +++ b/freebsd/sys/dev/usb/usb_process.c @@ -0,0 +1,501 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/* $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. + */ + +#define USB_DEBUG_VAR usb_proc_debug + +#include <freebsd/sys/stdint.h> +#include <freebsd/sys/stddef.h> +#include <freebsd/sys/param.h> +#include <freebsd/sys/queue.h> +#include <freebsd/sys/types.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/bus.h> +#include <freebsd/sys/linker_set.h> +#include <freebsd/sys/module.h> +#include <freebsd/sys/lock.h> +#include <freebsd/sys/mutex.h> +#include <freebsd/sys/condvar.h> +#include <freebsd/sys/sysctl.h> +#include <freebsd/sys/sx.h> +#include <freebsd/sys/unistd.h> +#include <freebsd/sys/callout.h> +#include <freebsd/sys/malloc.h> +#include <freebsd/sys/priv.h> + +#include <freebsd/dev/usb/usb.h> +#include <freebsd/dev/usb/usbdi.h> +#include <freebsd/dev/usb/usbdi_util.h> +#include <freebsd/dev/usb/usb_process.h> +#include <freebsd/dev/usb/usb_debug.h> +#include <freebsd/dev/usb/usb_util.h> + +#include <freebsd/sys/proc.h> +#include <freebsd/sys/kthread.h> +#include <freebsd/sys/sched.h> + +#if (__FreeBSD_version < 700000) +#define thread_lock(td) mtx_lock_spin(&sched_lock) +#define thread_unlock(td) mtx_unlock_spin(&sched_lock) +#endif + +#if (__FreeBSD_version >= 800000) +static struct proc *usbproc; +static int usb_pcount; +#define USB_THREAD_CREATE(f, s, p, ...) \ + kproc_kthread_add((f), (s), &usbproc, (p), RFHIGHPID, \ + 0, "usb", __VA_ARGS__) +#define USB_THREAD_SUSPEND(p) kthread_suspend(p,0) +#define USB_THREAD_EXIT(err) kthread_exit() +#else +#define USB_THREAD_CREATE(f, s, p, ...) \ + kthread_create((f), (s), (p), RFHIGHPID, 0, __VA_ARGS__) +#define USB_THREAD_SUSPEND(p) kthread_suspend(p,0) +#define USB_THREAD_EXIT(err) kthread_exit(err) +#endif + +#ifdef USB_DEBUG +static int usb_proc_debug; + +SYSCTL_NODE(_hw_usb, OID_AUTO, proc, CTLFLAG_RW, 0, "USB process"); +SYSCTL_INT(_hw_usb_proc, OID_AUTO, debug, CTLFLAG_RW, &usb_proc_debug, 0, + "Debug level"); + +TUNABLE_INT("hw.usb.proc.debug", &usb_proc_debug); +#endif + +/*------------------------------------------------------------------------* + * usb_process + * + * This function is the USB process dispatcher. + *------------------------------------------------------------------------*/ +static void +usb_process(void *arg) +{ + struct usb_process *up = arg; + struct usb_proc_msg *pm; +#ifndef __rtems__ + struct thread *td; + + /* adjust priority */ + td = curthread; + thread_lock(td); + sched_prio(td, up->up_prio); + thread_unlock(td); +#endif /* __rtems__ */ + + mtx_lock(up->up_mtx); + +#ifndef __rtems__ + up->up_curtd = td; +#endif /* __rtems__ */ + + while (1) { + + if (up->up_gone) + break; + + /* + * NOTE to reimplementors: dequeueing a command from the + * "used" queue and executing it must be atomic, with regard + * to the "up_mtx" mutex. That means any attempt to queue a + * command by another thread must be blocked until either: + * + * 1) the command sleeps + * + * 2) the command returns + * + * Here is a practical example that shows how this helps + * solving a problem: + * + * Assume that you want to set the baud rate on a USB serial + * device. During the programming of the device you don't + * want to receive nor transmit any data, because it will be + * garbage most likely anyway. The programming of our USB + * device takes 20 milliseconds and it needs to call + * functions that sleep. + * + * Non-working solution: Before we queue the programming + * command, we stop transmission and reception of data. Then + * we queue a programming command. At the end of the + * programming command we enable transmission and reception + * of data. + * + * Problem: If a second programming command is queued while the + * first one is sleeping, we end up enabling transmission + * and reception of data too early. + * + * Working solution: Before we queue the programming command, + * we stop transmission and reception of data. Then we queue + * a programming command. Then we queue a second command + * that only enables transmission and reception of data. + * + * Why it works: If a second programming command is queued + * while the first one is sleeping, then the queueing of a + * second command to enable the data transfers, will cause + * the previous one, which is still on the queue, to be + * removed from the queue, and re-inserted after the last + * baud rate programming command, which then gives the + * desired result. + */ + pm = TAILQ_FIRST(&up->up_qhead); + + if (pm) { + DPRINTF("Message pm=%p, cb=%p (enter)\n", + pm, pm->pm_callback); + + (pm->pm_callback) (pm); + + if (pm == TAILQ_FIRST(&up->up_qhead)) { + /* nothing changed */ + TAILQ_REMOVE(&up->up_qhead, pm, pm_qentry); + pm->pm_qentry.tqe_prev = NULL; + } + DPRINTF("Message pm=%p (leave)\n", pm); + + continue; + } + /* end if messages - check if anyone is waiting for sync */ + if (up->up_dsleep) { + up->up_dsleep = 0; + cv_broadcast(&up->up_drain); + } + up->up_msleep = 1; + cv_wait(&up->up_cv, up->up_mtx); + } + + up->up_ptr = NULL; + cv_signal(&up->up_cv); + mtx_unlock(up->up_mtx); +#if (__FreeBSD_version >= 800000) + /* Clear the proc pointer if this is the last thread. */ + if (--usb_pcount == 0) + usbproc = NULL; +#endif + + USB_THREAD_EXIT(0); +} + +/*------------------------------------------------------------------------* + * usb_proc_create + * + * This function will create a process using the given "prio" that can + * execute callbacks. The mutex pointed to by "p_mtx" will be applied + * before calling the callbacks and released after that the callback + * has returned. The structure pointed to by "up" is assumed to be + * zeroed before this function is called. + * + * Return values: + * 0: success + * Else: failure + *------------------------------------------------------------------------*/ +int +usb_proc_create(struct usb_process *up, struct mtx *p_mtx, + const char *pmesg, uint8_t prio) +{ + up->up_mtx = p_mtx; + up->up_prio = prio; + + TAILQ_INIT(&up->up_qhead); + + cv_init(&up->up_cv, "-"); + cv_init(&up->up_drain, "usbdrain"); + + if (USB_THREAD_CREATE(&usb_process, up, + &up->up_ptr, "%s", pmesg)) { + DPRINTFN(0, "Unable to create USB process."); + up->up_ptr = NULL; + goto error; + } +#if (__FreeBSD_version >= 800000) + usb_pcount++; +#endif + return (0); + +error: + usb_proc_free(up); + return (ENOMEM); +} + +/*------------------------------------------------------------------------* + * usb_proc_free + * + * NOTE: If the structure pointed to by "up" is all zero, this + * function does nothing. + * + * NOTE: Messages that are pending on the process queue will not be + * removed nor called. + *------------------------------------------------------------------------*/ +void +usb_proc_free(struct usb_process *up) +{ + /* check if not initialised */ + if (up->up_mtx == NULL) + return; + + usb_proc_drain(up); + + cv_destroy(&up->up_cv); + cv_destroy(&up->up_drain); + + /* make sure that we do not enter here again */ + up->up_mtx = NULL; +} + +/*------------------------------------------------------------------------* + * usb_proc_msignal + * + * This function will queue one of the passed USB process messages on + * the USB process queue. The first message that is not already queued + * will get queued. If both messages are already queued the one queued + * last will be removed from the queue and queued in the end. The USB + * process mutex must be locked when calling this function. This + * function exploits the fact that a process can only do one callback + * at a time. The message that was queued is returned. + *------------------------------------------------------------------------*/ +void * +usb_proc_msignal(struct usb_process *up, void *_pm0, void *_pm1) +{ + struct usb_proc_msg *pm0 = _pm0; + struct usb_proc_msg *pm1 = _pm1; + struct usb_proc_msg *pm2; + usb_size_t d; + uint8_t t; + + /* check if gone, return dummy value */ + if (up->up_gone) + return (_pm0); + + mtx_assert(up->up_mtx, MA_OWNED); + + t = 0; + + if (pm0->pm_qentry.tqe_prev) { + t |= 1; + } + if (pm1->pm_qentry.tqe_prev) { + t |= 2; + } + if (t == 0) { + /* + * No entries are queued. Queue "pm0" and use the existing + * message number. + */ + pm2 = pm0; + } else if (t == 1) { + /* Check if we need to increment the message number. */ + if (pm0->pm_num == up->up_msg_num) { + up->up_msg_num++; + } + pm2 = pm1; + } else if (t == 2) { + /* Check if we need to increment the message number. */ + if (pm1->pm_num == up->up_msg_num) { + up->up_msg_num++; + } + pm2 = pm0; + } else if (t == 3) { + /* + * Both entries are queued. Re-queue the entry closest to + * the end. + */ + d = (pm1->pm_num - pm0->pm_num); + + /* Check sign after subtraction */ + if (d & 0x80000000) { + pm2 = pm0; + } else { + pm2 = pm1; + } + + TAILQ_REMOVE(&up->up_qhead, pm2, pm_qentry); + } else { + pm2 = NULL; /* panic - should not happen */ + } + + DPRINTF(" t=%u, num=%u\n", t, up->up_msg_num); + + /* Put message last on queue */ + + pm2->pm_num = up->up_msg_num; + TAILQ_INSERT_TAIL(&up->up_qhead, pm2, pm_qentry); + + /* Check if we need to wakeup the USB process. */ + + if (up->up_msleep) { + up->up_msleep = 0; /* save "cv_signal()" calls */ + cv_signal(&up->up_cv); + } + return (pm2); +} + +/*------------------------------------------------------------------------* + * usb_proc_is_gone + * + * Return values: + * 0: USB process is running + * Else: USB process is tearing down + *------------------------------------------------------------------------*/ +uint8_t +usb_proc_is_gone(struct usb_process *up) +{ + if (up->up_gone) + return (1); + + mtx_assert(up->up_mtx, MA_OWNED); + return (0); +} + +/*------------------------------------------------------------------------* + * usb_proc_mwait + * + * This function will return when the USB process message pointed to + * by "pm" is no longer on a queue. This function must be called + * having "up->up_mtx" locked. + *------------------------------------------------------------------------*/ +void +usb_proc_mwait(struct usb_process *up, void *_pm0, void *_pm1) +{ + struct usb_proc_msg *pm0 = _pm0; + struct usb_proc_msg *pm1 = _pm1; + + /* check if gone */ + if (up->up_gone) + return; + + mtx_assert(up->up_mtx, MA_OWNED); + +#ifndef __rtems__ + if (up->up_curtd == curthread) { +#else /* __rtems__ */ + if (up->up_ptr->td_id == rtems_task_self()) { +#endif /* __rtems__ */ + /* Just remove the messages from the queue. */ + if (pm0->pm_qentry.tqe_prev) { + TAILQ_REMOVE(&up->up_qhead, pm0, pm_qentry); + pm0->pm_qentry.tqe_prev = NULL; + } + if (pm1->pm_qentry.tqe_prev) { + TAILQ_REMOVE(&up->up_qhead, pm1, pm_qentry); + pm1->pm_qentry.tqe_prev = NULL; + } + } else + while (pm0->pm_qentry.tqe_prev || + pm1->pm_qentry.tqe_prev) { + /* check if config thread is gone */ + if (up->up_gone) + break; + up->up_dsleep = 1; + cv_wait(&up->up_drain, up->up_mtx); + } +} + +/*------------------------------------------------------------------------* + * usb_proc_drain + * + * This function will tear down an USB process, waiting for the + * currently executing command to return. + * + * NOTE: If the structure pointed to by "up" is all zero, + * this function does nothing. + *------------------------------------------------------------------------*/ +void +usb_proc_drain(struct usb_process *up) +{ + /* check if not initialised */ + if (up->up_mtx == NULL) + return; + /* handle special case with Giant */ + if (up->up_mtx != &Giant) + mtx_assert(up->up_mtx, MA_NOTOWNED); + + mtx_lock(up->up_mtx); + + /* Set the gone flag */ + + up->up_gone = 1; + + while (up->up_ptr) { + + /* Check if we need to wakeup the USB process */ + + if (up->up_msleep || up->up_csleep) { + up->up_msleep = 0; + up->up_csleep = 0; + cv_signal(&up->up_cv); + } + /* Check if we are still cold booted */ + + if (cold) { +#ifndef __rtems__ + USB_THREAD_SUSPEND(up->up_ptr); + printf("WARNING: A USB process has " + "been left suspended\n"); + break; +#else /* __rtems__ */ + BSD_ASSERT(0); +#endif /* __rtems__ */ + } + cv_wait(&up->up_cv, up->up_mtx); + } + /* Check if someone is waiting - should not happen */ + + if (up->up_dsleep) { + up->up_dsleep = 0; + cv_broadcast(&up->up_drain); + DPRINTF("WARNING: Someone is waiting " + "for USB process drain!\n"); + } + mtx_unlock(up->up_mtx); +} + +/*------------------------------------------------------------------------* + * usb_proc_rewakeup + * + * This function is called to re-wakeup the the given USB + * process. This usually happens after that the USB system has been in + * polling mode, like during a panic. This function must be called + * having "up->up_mtx" locked. + *------------------------------------------------------------------------*/ +void +usb_proc_rewakeup(struct usb_process *up) +{ + /* check if not initialised */ + if (up->up_mtx == NULL) + return; + /* check if gone */ + if (up->up_gone) + return; + + mtx_assert(up->up_mtx, MA_OWNED); + + if (up->up_msleep == 0) { + /* re-wakeup */ + cv_signal(&up->up_cv); + } +} diff --git a/freebsd/sys/dev/usb/usb_process.h b/freebsd/sys/dev/usb/usb_process.h new file mode 100644 index 00000000..75c86ece --- /dev/null +++ b/freebsd/sys/dev/usb/usb_process.h @@ -0,0 +1,84 @@ +/* $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. + */ + +#ifndef _USB_PROCESS_HH_ +#define _USB_PROCESS_HH_ + +#include <freebsd/sys/priority.h> + +/* defines */ +#define USB_PRI_HIGH PI_NET +#define USB_PRI_MED PI_DISK + +#define USB_PROC_WAIT_TIMEOUT 2 +#define USB_PROC_WAIT_DRAIN 1 +#define USB_PROC_WAIT_NORMAL 0 + +/* structure prototypes */ + +struct usb_proc_msg; + +/* + * The following structure defines the USB process. + */ +struct usb_process { + TAILQ_HEAD(, usb_proc_msg) up_qhead; + struct cv up_cv; + struct cv up_drain; + +#ifndef __rtems__ +#if (__FreeBSD_version >= 800000) + struct thread *up_ptr; +#else + struct proc *up_ptr; +#endif + struct thread *up_curtd; +#else /* __rtems__ */ + struct thread *up_ptr; +#endif /* __rtems__ */ + struct mtx *up_mtx; + + usb_size_t up_msg_num; + + uint8_t up_prio; + uint8_t up_gone; + uint8_t up_msleep; + uint8_t up_csleep; + uint8_t up_dsleep; +}; + +/* prototypes */ + +uint8_t usb_proc_is_gone(struct usb_process *up); +int usb_proc_create(struct usb_process *up, struct mtx *p_mtx, + const char *pmesg, uint8_t prio); +void usb_proc_drain(struct usb_process *up); +void usb_proc_mwait(struct usb_process *up, void *pm0, void *pm1); +void usb_proc_free(struct usb_process *up); +void *usb_proc_msignal(struct usb_process *up, void *pm0, void *pm1); +void usb_proc_rewakeup(struct usb_process *up); + +#endif /* _USB_PROCESS_HH_ */ diff --git a/freebsd/sys/dev/usb/usb_request.c b/freebsd/sys/dev/usb/usb_request.c new file mode 100644 index 00000000..61dafd7d --- /dev/null +++ b/freebsd/sys/dev/usb/usb_request.c @@ -0,0 +1,2031 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/* $FreeBSD$ */ +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved. + * Copyright (c) 1998 Lennart Augustsson. All rights reserved. + * 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. + */ + +#include <freebsd/sys/stdint.h> +#include <freebsd/sys/stddef.h> +#include <freebsd/sys/param.h> +#include <freebsd/sys/queue.h> +#include <freebsd/sys/types.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/bus.h> +#include <freebsd/sys/linker_set.h> +#include <freebsd/sys/module.h> +#include <freebsd/sys/lock.h> +#include <freebsd/sys/mutex.h> +#include <freebsd/sys/condvar.h> +#include <freebsd/sys/sysctl.h> +#include <freebsd/sys/sx.h> +#include <freebsd/sys/unistd.h> +#include <freebsd/sys/callout.h> +#include <freebsd/sys/malloc.h> +#include <freebsd/sys/priv.h> + +#include <freebsd/dev/usb/usb.h> +#include <freebsd/dev/usb/usbdi.h> +#include <freebsd/dev/usb/usbdi_util.h> +#include <freebsd/dev/usb/usb_ioctl.h> +#include <freebsd/dev/usb/usbhid.h> + +#define USB_DEBUG_VAR usb_debug + +#include <freebsd/dev/usb/usb_core.h> +#include <freebsd/dev/usb/usb_busdma.h> +#include <freebsd/dev/usb/usb_request.h> +#include <freebsd/dev/usb/usb_process.h> +#include <freebsd/dev/usb/usb_transfer.h> +#include <freebsd/dev/usb/usb_debug.h> +#include <freebsd/dev/usb/usb_device.h> +#include <freebsd/dev/usb/usb_util.h> +#include <freebsd/dev/usb/usb_dynamic.h> + +#include <freebsd/dev/usb/usb_controller.h> +#include <freebsd/dev/usb/usb_bus.h> +#include <freebsd/sys/ctype.h> + +#ifdef USB_DEBUG +static int usb_pr_poll_delay = USB_PORT_RESET_DELAY; +static int usb_pr_recovery_delay = USB_PORT_RESET_RECOVERY; + +SYSCTL_INT(_hw_usb, OID_AUTO, pr_poll_delay, CTLFLAG_RW, + &usb_pr_poll_delay, 0, "USB port reset poll delay in ms"); +SYSCTL_INT(_hw_usb, OID_AUTO, pr_recovery_delay, CTLFLAG_RW, + &usb_pr_recovery_delay, 0, "USB port reset recovery delay in ms"); + +#ifdef USB_REQ_DEBUG +/* The following structures are used in connection to fault injection. */ +struct usb_ctrl_debug { + int bus_index; /* target bus */ + int dev_index; /* target address */ + int ds_fail; /* fail data stage */ + int ss_fail; /* fail data stage */ + int ds_delay; /* data stage delay in ms */ + int ss_delay; /* status stage delay in ms */ + int bmRequestType_value; + int bRequest_value; +}; + +struct usb_ctrl_debug_bits { + uint16_t ds_delay; + uint16_t ss_delay; + uint8_t ds_fail:1; + uint8_t ss_fail:1; + uint8_t enabled:1; +}; + +/* The default is to disable fault injection. */ + +static struct usb_ctrl_debug usb_ctrl_debug = { + .bus_index = -1, + .dev_index = -1, + .bmRequestType_value = -1, + .bRequest_value = -1, +}; + +SYSCTL_INT(_hw_usb, OID_AUTO, ctrl_bus_fail, CTLFLAG_RW, + &usb_ctrl_debug.bus_index, 0, "USB controller index to fail"); +SYSCTL_INT(_hw_usb, OID_AUTO, ctrl_dev_fail, CTLFLAG_RW, + &usb_ctrl_debug.dev_index, 0, "USB device address to fail"); +SYSCTL_INT(_hw_usb, OID_AUTO, ctrl_ds_fail, CTLFLAG_RW, + &usb_ctrl_debug.ds_fail, 0, "USB fail data stage"); +SYSCTL_INT(_hw_usb, OID_AUTO, ctrl_ss_fail, CTLFLAG_RW, + &usb_ctrl_debug.ss_fail, 0, "USB fail status stage"); +SYSCTL_INT(_hw_usb, OID_AUTO, ctrl_ds_delay, CTLFLAG_RW, + &usb_ctrl_debug.ds_delay, 0, "USB data stage delay in ms"); +SYSCTL_INT(_hw_usb, OID_AUTO, ctrl_ss_delay, CTLFLAG_RW, + &usb_ctrl_debug.ss_delay, 0, "USB status stage delay in ms"); +SYSCTL_INT(_hw_usb, OID_AUTO, ctrl_rt_fail, CTLFLAG_RW, + &usb_ctrl_debug.bmRequestType_value, 0, "USB bmRequestType to fail"); +SYSCTL_INT(_hw_usb, OID_AUTO, ctrl_rv_fail, CTLFLAG_RW, + &usb_ctrl_debug.bRequest_value, 0, "USB bRequest to fail"); + +/*------------------------------------------------------------------------* + * usbd_get_debug_bits + * + * This function is only useful in USB host mode. + *------------------------------------------------------------------------*/ +static void +usbd_get_debug_bits(struct usb_device *udev, struct usb_device_request *req, + struct usb_ctrl_debug_bits *dbg) +{ + int temp; + + memset(dbg, 0, sizeof(*dbg)); + + /* Compute data stage delay */ + + temp = usb_ctrl_debug.ds_delay; + if (temp < 0) + temp = 0; + else if (temp > (16*1024)) + temp = (16*1024); + + dbg->ds_delay = temp; + + /* Compute status stage delay */ + + temp = usb_ctrl_debug.ss_delay; + if (temp < 0) + temp = 0; + else if (temp > (16*1024)) + temp = (16*1024); + + dbg->ss_delay = temp; + + /* Check if this control request should be failed */ + + if (usbd_get_bus_index(udev) != usb_ctrl_debug.bus_index) + return; + + if (usbd_get_device_index(udev) != usb_ctrl_debug.dev_index) + return; + + temp = usb_ctrl_debug.bmRequestType_value; + + if ((temp != req->bmRequestType) && (temp >= 0) && (temp <= 255)) + return; + + temp = usb_ctrl_debug.bRequest_value; + + if ((temp != req->bRequest) && (temp >= 0) && (temp <= 255)) + return; + + temp = usb_ctrl_debug.ds_fail; + if (temp) + dbg->ds_fail = 1; + + temp = usb_ctrl_debug.ss_fail; + if (temp) + dbg->ss_fail = 1; + + dbg->enabled = 1; +} +#endif /* USB_REQ_DEBUG */ +#endif /* USB_DEBUG */ + +/*------------------------------------------------------------------------* + * usbd_do_request_callback + * + * This function is the USB callback for generic USB Host control + * transfers. + *------------------------------------------------------------------------*/ +void +usbd_do_request_callback(struct usb_xfer *xfer, usb_error_t error) +{ + ; /* workaround for a bug in "indent" */ + + DPRINTF("st=%u\n", USB_GET_STATE(xfer)); + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + usbd_transfer_submit(xfer); + break; + default: + cv_signal(&xfer->xroot->udev->ctrlreq_cv); + break; + } +} + +/*------------------------------------------------------------------------* + * usb_do_clear_stall_callback + * + * This function is the USB callback for generic clear stall requests. + *------------------------------------------------------------------------*/ +void +usb_do_clear_stall_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct usb_device_request req; + struct usb_device *udev; + struct usb_endpoint *ep; + struct usb_endpoint *ep_end; + struct usb_endpoint *ep_first; + uint8_t to; + + udev = xfer->xroot->udev; + + USB_BUS_LOCK(udev->bus); + + /* round robin endpoint clear stall */ + + ep = udev->ep_curr; + ep_end = udev->endpoints + udev->endpoints_max; + ep_first = udev->endpoints; + to = udev->endpoints_max; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + if (ep == NULL) + goto tr_setup; /* device was unconfigured */ + if (ep->edesc && + ep->is_stalled) { + ep->toggle_next = 0; + ep->is_stalled = 0; + /* some hardware needs a callback to clear the data toggle */ + usbd_clear_stall_locked(udev, ep); + /* start up the current or next transfer, if any */ + usb_command_wrapper(&ep->endpoint_q, + ep->endpoint_q.curr); + } + ep++; + + case USB_ST_SETUP: +tr_setup: + if (to == 0) + break; /* no endpoints - nothing to do */ + if ((ep < ep_first) || (ep >= ep_end)) + ep = ep_first; /* endpoint wrapped around */ + if (ep->edesc && + ep->is_stalled) { + + /* setup a clear-stall packet */ + + req.bmRequestType = UT_WRITE_ENDPOINT; + req.bRequest = UR_CLEAR_FEATURE; + USETW(req.wValue, UF_ENDPOINT_HALT); + req.wIndex[0] = ep->edesc->bEndpointAddress; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + /* copy in the transfer */ + + usbd_copy_in(xfer->frbuffers, 0, &req, sizeof(req)); + + /* set length */ + usbd_xfer_set_frame_len(xfer, 0, sizeof(req)); + xfer->nframes = 1; + USB_BUS_UNLOCK(udev->bus); + + usbd_transfer_submit(xfer); + + USB_BUS_LOCK(udev->bus); + break; + } + ep++; + to--; + goto tr_setup; + + default: + if (xfer->error == USB_ERR_CANCELLED) { + break; + } + goto tr_setup; + } + + /* store current endpoint */ + udev->ep_curr = ep; + USB_BUS_UNLOCK(udev->bus); +} + +static usb_handle_req_t * +usbd_get_hr_func(struct usb_device *udev) +{ + /* figure out if there is a Handle Request function */ + if (udev->flags.usb_mode == USB_MODE_DEVICE) + return (usb_temp_get_desc_p); + else if (udev->parent_hub == NULL) + return (udev->bus->methods->roothub_exec); + else + return (NULL); +} + +/*------------------------------------------------------------------------* + * usbd_do_request_flags and usbd_do_request + * + * Description of arguments passed to these functions: + * + * "udev" - this is the "usb_device" structure pointer on which the + * request should be performed. It is possible to call this function + * in both Host Side mode and Device Side mode. + * + * "mtx" - if this argument is non-NULL the mutex pointed to by it + * will get dropped and picked up during the execution of this + * function, hence this function sometimes needs to sleep. If this + * argument is NULL it has no effect. + * + * "req" - this argument must always be non-NULL and points to an + * 8-byte structure holding the USB request to be done. The USB + * request structure has a bit telling the direction of the USB + * request, if it is a read or a write. + * + * "data" - if the "wLength" part of the structure pointed to by "req" + * is non-zero this argument must point to a valid kernel buffer which + * can hold at least "wLength" bytes. If "wLength" is zero "data" can + * be NULL. + * + * "flags" - here is a list of valid flags: + * + * o USB_SHORT_XFER_OK: allows the data transfer to be shorter than + * specified + * + * o USB_DELAY_STATUS_STAGE: allows the status stage to be performed + * at a later point in time. This is tunable by the "hw.usb.ss_delay" + * sysctl. This flag is mostly useful for debugging. + * + * o USB_USER_DATA_PTR: treat the "data" pointer like a userland + * pointer. + * + * "actlen" - if non-NULL the actual transfer length will be stored in + * the 16-bit unsigned integer pointed to by "actlen". This + * information is mostly useful when the "USB_SHORT_XFER_OK" flag is + * used. + * + * "timeout" - gives the timeout for the control transfer in + * milliseconds. A "timeout" value less than 50 milliseconds is + * treated like a 50 millisecond timeout. A "timeout" value greater + * than 30 seconds is treated like a 30 second timeout. This USB stack + * does not allow control requests without a timeout. + * + * NOTE: This function is thread safe. All calls to + * "usbd_do_request_flags" will be serialised by the use of an + * internal "sx_lock". + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_do_request_flags(struct usb_device *udev, struct mtx *mtx, + struct usb_device_request *req, void *data, uint16_t flags, + uint16_t *actlen, usb_timeout_t timeout) +{ +#ifdef USB_REQ_DEBUG + struct usb_ctrl_debug_bits dbg; +#endif + usb_handle_req_t *hr_func; + struct usb_xfer *xfer; + const void *desc; + int err = 0; + usb_ticks_t start_ticks; + usb_ticks_t delta_ticks; + usb_ticks_t max_ticks; + uint16_t length; + uint16_t temp; + uint16_t acttemp; + uint8_t enum_locked; + + if (timeout < 50) { + /* timeout is too small */ + timeout = 50; + } + if (timeout > 30000) { + /* timeout is too big */ + timeout = 30000; + } + length = UGETW(req->wLength); + + enum_locked = usbd_enum_is_locked(udev); + + DPRINTFN(5, "udev=%p bmRequestType=0x%02x bRequest=0x%02x " + "wValue=0x%02x%02x wIndex=0x%02x%02x wLength=0x%02x%02x\n", + udev, req->bmRequestType, req->bRequest, + req->wValue[1], req->wValue[0], + req->wIndex[1], req->wIndex[0], + req->wLength[1], req->wLength[0]); + + /* Check if the device is still alive */ + if (udev->state < USB_STATE_POWERED) { + DPRINTF("usb device has gone\n"); + return (USB_ERR_NOT_CONFIGURED); + } + + /* + * Set "actlen" to a known value in case the caller does not + * check the return value: + */ + if (actlen) + *actlen = 0; + +#if (USB_HAVE_USER_IO == 0) + if (flags & USB_USER_DATA_PTR) + return (USB_ERR_INVAL); +#endif + if ((mtx != NULL) && (mtx != &Giant)) { + mtx_unlock(mtx); + mtx_assert(mtx, MA_NOTOWNED); + } + + /* + * We need to allow suspend and resume at this point, else the + * control transfer will timeout if the device is suspended! + */ + if (enum_locked) + usbd_sr_unlock(udev); + + /* + * Grab the default sx-lock so that serialisation + * is achieved when multiple threads are involved: + */ + sx_xlock(&udev->ctrl_sx); + + hr_func = usbd_get_hr_func(udev); + + if (hr_func != NULL) { + DPRINTF("Handle Request function is set\n"); + + desc = NULL; + temp = 0; + + if (!(req->bmRequestType & UT_READ)) { + if (length != 0) { + DPRINTFN(1, "The handle request function " + "does not support writing data!\n"); + err = USB_ERR_INVAL; + goto done; + } + } + + /* The root HUB code needs the BUS lock locked */ + + USB_BUS_LOCK(udev->bus); + err = (hr_func) (udev, req, &desc, &temp); + USB_BUS_UNLOCK(udev->bus); + + if (err) + goto done; + + if (length > temp) { + if (!(flags & USB_SHORT_XFER_OK)) { + err = USB_ERR_SHORT_XFER; + goto done; + } + length = temp; + } + if (actlen) + *actlen = length; + + if (length > 0) { +#if USB_HAVE_USER_IO + if (flags & USB_USER_DATA_PTR) { + if (copyout(desc, data, length)) { + err = USB_ERR_INVAL; + goto done; + } + } else +#endif + bcopy(desc, data, length); + } + goto done; /* success */ + } + + /* + * Setup a new USB transfer or use the existing one, if any: + */ + usbd_ctrl_transfer_setup(udev); + + xfer = udev->ctrl_xfer[0]; + if (xfer == NULL) { + /* most likely out of memory */ + err = USB_ERR_NOMEM; + goto done; + } + +#ifdef USB_REQ_DEBUG + /* Get debug bits */ + usbd_get_debug_bits(udev, req, &dbg); + + /* Check for fault injection */ + if (dbg.enabled) + flags |= USB_DELAY_STATUS_STAGE; +#endif + USB_XFER_LOCK(xfer); + + if (flags & USB_DELAY_STATUS_STAGE) + xfer->flags.manual_status = 1; + else + xfer->flags.manual_status = 0; + + if (flags & USB_SHORT_XFER_OK) + xfer->flags.short_xfer_ok = 1; + else + xfer->flags.short_xfer_ok = 0; + + xfer->timeout = timeout; + + start_ticks = ticks; + + max_ticks = USB_MS_TO_TICKS(timeout); + + usbd_copy_in(xfer->frbuffers, 0, req, sizeof(*req)); + + usbd_xfer_set_frame_len(xfer, 0, sizeof(*req)); + + while (1) { + temp = length; + if (temp > usbd_xfer_max_len(xfer)) { + temp = usbd_xfer_max_len(xfer); + } +#ifdef USB_REQ_DEBUG + if (xfer->flags.manual_status) { + if (usbd_xfer_frame_len(xfer, 0) != 0) { + /* Execute data stage separately */ + temp = 0; + } else if (temp > 0) { + if (dbg.ds_fail) { + err = USB_ERR_INVAL; + break; + } + if (dbg.ds_delay > 0) { + usb_pause_mtx( + xfer->xroot->xfer_mtx, + USB_MS_TO_TICKS(dbg.ds_delay)); + /* make sure we don't time out */ + start_ticks = ticks; + } + } + } +#endif + usbd_xfer_set_frame_len(xfer, 1, temp); + + if (temp > 0) { + if (!(req->bmRequestType & UT_READ)) { +#if USB_HAVE_USER_IO + if (flags & USB_USER_DATA_PTR) { + USB_XFER_UNLOCK(xfer); + err = usbd_copy_in_user(xfer->frbuffers + 1, + 0, data, temp); + USB_XFER_LOCK(xfer); + if (err) { + err = USB_ERR_INVAL; + break; + } + } else +#endif + usbd_copy_in(xfer->frbuffers + 1, + 0, data, temp); + } + usbd_xfer_set_frames(xfer, 2); + } else { + if (usbd_xfer_frame_len(xfer, 0) == 0) { + if (xfer->flags.manual_status) { +#ifdef USB_REQ_DEBUG + if (dbg.ss_fail) { + err = USB_ERR_INVAL; + break; + } + if (dbg.ss_delay > 0) { + usb_pause_mtx( + xfer->xroot->xfer_mtx, + USB_MS_TO_TICKS(dbg.ss_delay)); + /* make sure we don't time out */ + start_ticks = ticks; + } +#endif + xfer->flags.manual_status = 0; + } else { + break; + } + } + usbd_xfer_set_frames(xfer, 1); + } + + usbd_transfer_start(xfer); + + while (usbd_transfer_pending(xfer)) { + cv_wait(&udev->ctrlreq_cv, + xfer->xroot->xfer_mtx); + } + + err = xfer->error; + + if (err) { + break; + } + + /* get actual length of DATA stage */ + + if (xfer->aframes < 2) { + acttemp = 0; + } else { + acttemp = usbd_xfer_frame_len(xfer, 1); + } + + /* check for short packet */ + + if (temp > acttemp) { + temp = acttemp; + length = temp; + } + if (temp > 0) { + if (req->bmRequestType & UT_READ) { +#if USB_HAVE_USER_IO + if (flags & USB_USER_DATA_PTR) { + USB_XFER_UNLOCK(xfer); + err = usbd_copy_out_user(xfer->frbuffers + 1, + 0, data, temp); + USB_XFER_LOCK(xfer); + if (err) { + err = USB_ERR_INVAL; + break; + } + } else +#endif + usbd_copy_out(xfer->frbuffers + 1, + 0, data, temp); + } + } + /* + * Clear "frlengths[0]" so that we don't send the setup + * packet again: + */ + usbd_xfer_set_frame_len(xfer, 0, 0); + + /* update length and data pointer */ + length -= temp; + data = USB_ADD_BYTES(data, temp); + + if (actlen) { + (*actlen) += temp; + } + /* check for timeout */ + + delta_ticks = ticks - start_ticks; + if (delta_ticks > max_ticks) { + if (!err) { + err = USB_ERR_TIMEOUT; + } + } + if (err) { + break; + } + } + + if (err) { + /* + * Make sure that the control endpoint is no longer + * blocked in case of a non-transfer related error: + */ + usbd_transfer_stop(xfer); + } + USB_XFER_UNLOCK(xfer); + +done: + sx_xunlock(&udev->ctrl_sx); + + if (enum_locked) + usbd_sr_lock(udev); + + if ((mtx != NULL) && (mtx != &Giant)) + mtx_lock(mtx); + + return ((usb_error_t)err); +} + +/*------------------------------------------------------------------------* + * usbd_do_request_proc - factored out code + * + * This function is factored out code. It does basically the same like + * usbd_do_request_flags, except it will check the status of the + * passed process argument before doing the USB request. If the + * process is draining the USB_ERR_IOERROR code will be returned. It + * is assumed that the mutex associated with the process is locked + * when calling this function. + *------------------------------------------------------------------------*/ +usb_error_t +usbd_do_request_proc(struct usb_device *udev, struct usb_process *pproc, + struct usb_device_request *req, void *data, uint16_t flags, + uint16_t *actlen, usb_timeout_t timeout) +{ + usb_error_t err; + uint16_t len; + + /* get request data length */ + len = UGETW(req->wLength); + + /* check if the device is being detached */ + if (usb_proc_is_gone(pproc)) { + err = USB_ERR_IOERROR; + goto done; + } + + /* forward the USB request */ + err = usbd_do_request_flags(udev, pproc->up_mtx, + req, data, flags, actlen, timeout); + +done: + /* on failure we zero the data */ + /* on short packet we zero the unused data */ + if ((len != 0) && (req->bmRequestType & UE_DIR_IN)) { + if (err) + memset(data, 0, len); + else if (actlen && *actlen != len) + memset(((uint8_t *)data) + *actlen, 0, len - *actlen); + } + return (err); +} + +/*------------------------------------------------------------------------* + * usbd_req_reset_port + * + * This function will instruct a USB HUB to perform a reset sequence + * on the specified port number. + * + * Returns: + * 0: Success. The USB device should now be at address zero. + * Else: Failure. No USB device is present and the USB port should be + * disabled. + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_reset_port(struct usb_device *udev, struct mtx *mtx, uint8_t port) +{ + struct usb_port_status ps; + usb_error_t err; + uint16_t n; + +#ifdef USB_DEBUG + uint16_t pr_poll_delay; + uint16_t pr_recovery_delay; + +#endif + err = usbd_req_set_port_feature(udev, mtx, port, UHF_PORT_RESET); + if (err) { + goto done; + } +#ifdef USB_DEBUG + /* range check input parameters */ + pr_poll_delay = usb_pr_poll_delay; + if (pr_poll_delay < 1) { + pr_poll_delay = 1; + } else if (pr_poll_delay > 1000) { + pr_poll_delay = 1000; + } + pr_recovery_delay = usb_pr_recovery_delay; + if (pr_recovery_delay > 1000) { + pr_recovery_delay = 1000; + } +#endif + n = 0; + while (1) { +#ifdef USB_DEBUG + /* wait for the device to recover from reset */ + usb_pause_mtx(mtx, USB_MS_TO_TICKS(pr_poll_delay)); + n += pr_poll_delay; +#else + /* wait for the device to recover from reset */ + usb_pause_mtx(mtx, USB_MS_TO_TICKS(USB_PORT_RESET_DELAY)); + n += USB_PORT_RESET_DELAY; +#endif + err = usbd_req_get_port_status(udev, mtx, &ps, port); + if (err) { + goto done; + } + /* if the device disappeared, just give up */ + if (!(UGETW(ps.wPortStatus) & UPS_CURRENT_CONNECT_STATUS)) { + goto done; + } + /* check if reset is complete */ + if (UGETW(ps.wPortChange) & UPS_C_PORT_RESET) { + break; + } + /* check for timeout */ + if (n > 1000) { + n = 0; + break; + } + } + + /* clear port reset first */ + err = usbd_req_clear_port_feature( + udev, mtx, port, UHF_C_PORT_RESET); + if (err) { + goto done; + } + /* check for timeout */ + if (n == 0) { + err = USB_ERR_TIMEOUT; + goto done; + } +#ifdef USB_DEBUG + /* wait for the device to recover from reset */ + usb_pause_mtx(mtx, USB_MS_TO_TICKS(pr_recovery_delay)); +#else + /* wait for the device to recover from reset */ + usb_pause_mtx(mtx, USB_MS_TO_TICKS(USB_PORT_RESET_RECOVERY)); +#endif + +done: + DPRINTFN(2, "port %d reset returning error=%s\n", + port, usbd_errstr(err)); + return (err); +} + +/*------------------------------------------------------------------------* + * usbd_req_warm_reset_port + * + * This function will instruct an USB HUB to perform a warm reset + * sequence on the specified port number. This kind of reset is not + * mandatory for LOW-, FULL- and HIGH-speed USB HUBs and is targeted + * for SUPER-speed USB HUBs. + * + * Returns: + * 0: Success. The USB device should now be available again. + * Else: Failure. No USB device is present and the USB port should be + * disabled. + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_warm_reset_port(struct usb_device *udev, struct mtx *mtx, uint8_t port) +{ + struct usb_port_status ps; + usb_error_t err; + uint16_t n; + +#ifdef USB_DEBUG + uint16_t pr_poll_delay; + uint16_t pr_recovery_delay; + +#endif + err = usbd_req_set_port_feature(udev, mtx, port, UHF_BH_PORT_RESET); + if (err) { + goto done; + } +#ifdef USB_DEBUG + /* range check input parameters */ + pr_poll_delay = usb_pr_poll_delay; + if (pr_poll_delay < 1) { + pr_poll_delay = 1; + } else if (pr_poll_delay > 1000) { + pr_poll_delay = 1000; + } + pr_recovery_delay = usb_pr_recovery_delay; + if (pr_recovery_delay > 1000) { + pr_recovery_delay = 1000; + } +#endif + n = 0; + while (1) { +#ifdef USB_DEBUG + /* wait for the device to recover from reset */ + usb_pause_mtx(mtx, USB_MS_TO_TICKS(pr_poll_delay)); + n += pr_poll_delay; +#else + /* wait for the device to recover from reset */ + usb_pause_mtx(mtx, USB_MS_TO_TICKS(USB_PORT_RESET_DELAY)); + n += USB_PORT_RESET_DELAY; +#endif + err = usbd_req_get_port_status(udev, mtx, &ps, port); + if (err) { + goto done; + } + /* if the device disappeared, just give up */ + if (!(UGETW(ps.wPortStatus) & UPS_CURRENT_CONNECT_STATUS)) { + goto done; + } + /* check if reset is complete */ + if (UGETW(ps.wPortChange) & UPS_C_BH_PORT_RESET) { + break; + } + /* check for timeout */ + if (n > 1000) { + n = 0; + break; + } + } + + /* clear port reset first */ + err = usbd_req_clear_port_feature( + udev, mtx, port, UHF_C_BH_PORT_RESET); + if (err) { + goto done; + } + /* check for timeout */ + if (n == 0) { + err = USB_ERR_TIMEOUT; + goto done; + } +#ifdef USB_DEBUG + /* wait for the device to recover from reset */ + usb_pause_mtx(mtx, USB_MS_TO_TICKS(pr_recovery_delay)); +#else + /* wait for the device to recover from reset */ + usb_pause_mtx(mtx, USB_MS_TO_TICKS(USB_PORT_RESET_RECOVERY)); +#endif + +done: + DPRINTFN(2, "port %d warm reset returning error=%s\n", + port, usbd_errstr(err)); + return (err); +} + +/*------------------------------------------------------------------------* + * usbd_req_get_desc + * + * This function can be used to retrieve USB descriptors. It contains + * some additional logic like zeroing of missing descriptor bytes and + * retrying an USB descriptor in case of failure. The "min_len" + * argument specifies the minimum descriptor length. The "max_len" + * argument specifies the maximum descriptor length. If the real + * descriptor length is less than the minimum length the missing + * byte(s) will be zeroed. The type field, the second byte of the USB + * descriptor, will get forced to the correct type. If the "actlen" + * pointer is non-NULL, the actual length of the transfer will get + * stored in the 16-bit unsigned integer which it is pointing to. The + * first byte of the descriptor will not get updated. If the "actlen" + * pointer is NULL the first byte of the descriptor will get updated + * to reflect the actual length instead. If "min_len" is not equal to + * "max_len" then this function will try to retrive the beginning of + * the descriptor and base the maximum length on the first byte of the + * descriptor. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_desc(struct usb_device *udev, + struct mtx *mtx, uint16_t *actlen, void *desc, + uint16_t min_len, uint16_t max_len, + uint16_t id, uint8_t type, uint8_t index, + uint8_t retries) +{ + struct usb_device_request req; + uint8_t *buf; + usb_error_t err; + + DPRINTFN(4, "id=%d, type=%d, index=%d, max_len=%d\n", + id, type, index, max_len); + + req.bmRequestType = UT_READ_DEVICE; + req.bRequest = UR_GET_DESCRIPTOR; + USETW2(req.wValue, type, index); + USETW(req.wIndex, id); + + while (1) { + + if ((min_len < 2) || (max_len < 2)) { + err = USB_ERR_INVAL; + goto done; + } + USETW(req.wLength, min_len); + + err = usbd_do_request_flags(udev, mtx, &req, + desc, 0, NULL, 1000); + + if (err) { + if (!retries) { + goto done; + } + retries--; + + usb_pause_mtx(mtx, hz / 5); + + continue; + } + buf = desc; + + if (min_len == max_len) { + + /* enforce correct length */ + if ((buf[0] > min_len) && (actlen == NULL)) + buf[0] = min_len; + + /* enforce correct type */ + buf[1] = type; + + goto done; + } + /* range check */ + + if (max_len > buf[0]) { + max_len = buf[0]; + } + /* zero minimum data */ + + while (min_len > max_len) { + min_len--; + buf[min_len] = 0; + } + + /* set new minimum length */ + + min_len = max_len; + } +done: + if (actlen != NULL) { + if (err) + *actlen = 0; + else + *actlen = min_len; + } + return (err); +} + +/*------------------------------------------------------------------------* + * usbd_req_get_string_any + * + * This function will return the string given by "string_index" + * using the first language ID. The maximum length "len" includes + * the terminating zero. The "len" argument should be twice as + * big pluss 2 bytes, compared with the actual maximum string length ! + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_string_any(struct usb_device *udev, struct mtx *mtx, char *buf, + uint16_t len, uint8_t string_index) +{ + char *s; + uint8_t *temp; + uint16_t i; + uint16_t n; + uint16_t c; + uint8_t swap; + usb_error_t err; + + if (len == 0) { + /* should not happen */ + return (USB_ERR_NORMAL_COMPLETION); + } + if (string_index == 0) { + /* this is the language table */ + buf[0] = 0; + return (USB_ERR_INVAL); + } + if (udev->flags.no_strings) { + buf[0] = 0; + return (USB_ERR_STALLED); + } + err = usbd_req_get_string_desc + (udev, mtx, buf, len, udev->langid, string_index); + if (err) { + buf[0] = 0; + return (err); + } + temp = (uint8_t *)buf; + + if (temp[0] < 2) { + /* string length is too short */ + buf[0] = 0; + return (USB_ERR_INVAL); + } + /* reserve one byte for terminating zero */ + len--; + + /* find maximum length */ + s = buf; + n = (temp[0] / 2) - 1; + if (n > len) { + n = len; + } + /* skip descriptor header */ + temp += 2; + + /* reset swap state */ + swap = 3; + + /* convert and filter */ + for (i = 0; (i != n); i++) { + c = UGETW(temp + (2 * i)); + + /* convert from Unicode, handle buggy strings */ + if (((c & 0xff00) == 0) && (swap & 1)) { + /* Little Endian, default */ + *s = c; + swap = 1; + } else if (((c & 0x00ff) == 0) && (swap & 2)) { + /* Big Endian */ + *s = c >> 8; + swap = 2; + } else { + /* silently skip bad character */ + continue; + } + + /* + * Filter by default - we don't allow greater and less than + * signs because they might confuse the dmesg printouts! + */ + if ((*s == '<') || (*s == '>') || (!isprint(*s))) { + /* silently skip bad character */ + continue; + } + s++; + } + *s = 0; /* zero terminate resulting string */ + return (USB_ERR_NORMAL_COMPLETION); +} + +/*------------------------------------------------------------------------* + * usbd_req_get_string_desc + * + * If you don't know the language ID, consider using + * "usbd_req_get_string_any()". + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_string_desc(struct usb_device *udev, struct mtx *mtx, void *sdesc, + uint16_t max_len, uint16_t lang_id, + uint8_t string_index) +{ + return (usbd_req_get_desc(udev, mtx, NULL, sdesc, 2, max_len, lang_id, + UDESC_STRING, string_index, 0)); +} + +/*------------------------------------------------------------------------* + * usbd_req_get_config_desc_ptr + * + * This function is used in device side mode to retrieve the pointer + * to the generated config descriptor. This saves allocating space for + * an additional config descriptor when setting the configuration. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_descriptor_ptr(struct usb_device *udev, + struct usb_config_descriptor **ppcd, uint16_t wValue) +{ + struct usb_device_request req; + usb_handle_req_t *hr_func; + const void *ptr; + uint16_t len; + usb_error_t err; + + req.bmRequestType = UT_READ_DEVICE; + req.bRequest = UR_GET_DESCRIPTOR; + USETW(req.wValue, wValue); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + + ptr = NULL; + len = 0; + + hr_func = usbd_get_hr_func(udev); + + if (hr_func == NULL) + err = USB_ERR_INVAL; + else { + USB_BUS_LOCK(udev->bus); + err = (hr_func) (udev, &req, &ptr, &len); + USB_BUS_UNLOCK(udev->bus); + } + + if (err) + ptr = NULL; + else if (ptr == NULL) + err = USB_ERR_INVAL; + + *ppcd = __DECONST(struct usb_config_descriptor *, ptr); + + return (err); +} + +/*------------------------------------------------------------------------* + * usbd_req_get_config_desc + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_config_desc(struct usb_device *udev, struct mtx *mtx, + struct usb_config_descriptor *d, uint8_t conf_index) +{ + usb_error_t err; + + DPRINTFN(4, "confidx=%d\n", conf_index); + + err = usbd_req_get_desc(udev, mtx, NULL, d, sizeof(*d), + sizeof(*d), 0, UDESC_CONFIG, conf_index, 0); + if (err) { + goto done; + } + /* Extra sanity checking */ + if (UGETW(d->wTotalLength) < sizeof(*d)) { + err = USB_ERR_INVAL; + } +done: + return (err); +} + +/*------------------------------------------------------------------------* + * usbd_req_get_config_desc_full + * + * This function gets the complete USB configuration descriptor and + * ensures that "wTotalLength" is correct. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_config_desc_full(struct usb_device *udev, struct mtx *mtx, + struct usb_config_descriptor **ppcd, struct malloc_type *mtype, + uint8_t index) +{ + struct usb_config_descriptor cd; + struct usb_config_descriptor *cdesc; + uint16_t len; + usb_error_t err; + + DPRINTFN(4, "index=%d\n", index); + + *ppcd = NULL; + + err = usbd_req_get_config_desc(udev, mtx, &cd, index); + if (err) { + return (err); + } + /* get full descriptor */ + len = UGETW(cd.wTotalLength); + if (len < sizeof(*cdesc)) { + /* corrupt descriptor */ + return (USB_ERR_INVAL); + } + cdesc = malloc(len, mtype, M_WAITOK); + if (cdesc == NULL) { + return (USB_ERR_NOMEM); + } + err = usbd_req_get_desc(udev, mtx, NULL, cdesc, len, len, 0, + UDESC_CONFIG, index, 3); + if (err) { + free(cdesc, mtype); + return (err); + } + /* make sure that the device is not fooling us: */ + USETW(cdesc->wTotalLength, len); + + *ppcd = cdesc; + + return (0); /* success */ +} + +/*------------------------------------------------------------------------* + * usbd_req_get_device_desc + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_device_desc(struct usb_device *udev, struct mtx *mtx, + struct usb_device_descriptor *d) +{ + DPRINTFN(4, "\n"); + return (usbd_req_get_desc(udev, mtx, NULL, d, sizeof(*d), + sizeof(*d), 0, UDESC_DEVICE, 0, 3)); +} + +/*------------------------------------------------------------------------* + * usbd_req_get_alt_interface_no + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_alt_interface_no(struct usb_device *udev, struct mtx *mtx, + uint8_t *alt_iface_no, uint8_t iface_index) +{ + struct usb_interface *iface = usbd_get_iface(udev, iface_index); + struct usb_device_request req; + + if ((iface == NULL) || (iface->idesc == NULL)) + return (USB_ERR_INVAL); + + req.bmRequestType = UT_READ_INTERFACE; + req.bRequest = UR_GET_INTERFACE; + USETW(req.wValue, 0); + req.wIndex[0] = iface->idesc->bInterfaceNumber; + req.wIndex[1] = 0; + USETW(req.wLength, 1); + return (usbd_do_request(udev, mtx, &req, alt_iface_no)); +} + +/*------------------------------------------------------------------------* + * usbd_req_set_alt_interface_no + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_set_alt_interface_no(struct usb_device *udev, struct mtx *mtx, + uint8_t iface_index, uint8_t alt_no) +{ + struct usb_interface *iface = usbd_get_iface(udev, iface_index); + struct usb_device_request req; + + if ((iface == NULL) || (iface->idesc == NULL)) + return (USB_ERR_INVAL); + + req.bmRequestType = UT_WRITE_INTERFACE; + req.bRequest = UR_SET_INTERFACE; + req.wValue[0] = alt_no; + req.wValue[1] = 0; + req.wIndex[0] = iface->idesc->bInterfaceNumber; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + return (usbd_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usbd_req_get_device_status + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_device_status(struct usb_device *udev, struct mtx *mtx, + struct usb_status *st) +{ + struct usb_device_request req; + + req.bmRequestType = UT_READ_DEVICE; + req.bRequest = UR_GET_STATUS; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, sizeof(*st)); + return (usbd_do_request(udev, mtx, &req, st)); +} + +/*------------------------------------------------------------------------* + * usbd_req_get_hub_descriptor + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_hub_descriptor(struct usb_device *udev, struct mtx *mtx, + struct usb_hub_descriptor *hd, uint8_t nports) +{ + struct usb_device_request req; + uint16_t len = (nports + 7 + (8 * 8)) / 8; + + req.bmRequestType = UT_READ_CLASS_DEVICE; + req.bRequest = UR_GET_DESCRIPTOR; + USETW2(req.wValue, UDESC_HUB, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, len); + return (usbd_do_request(udev, mtx, &req, hd)); +} + +/*------------------------------------------------------------------------* + * usbd_req_get_ss_hub_descriptor + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_ss_hub_descriptor(struct usb_device *udev, struct mtx *mtx, + struct usb_hub_ss_descriptor *hd, uint8_t nports) +{ + struct usb_device_request req; + uint16_t len = sizeof(*hd) - 32 + 1 + ((nports + 7) / 8); + + req.bmRequestType = UT_READ_CLASS_DEVICE; + req.bRequest = UR_GET_DESCRIPTOR; + USETW2(req.wValue, UDESC_SS_HUB, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, len); + return (usbd_do_request(udev, mtx, &req, hd)); +} + +/*------------------------------------------------------------------------* + * usbd_req_get_hub_status + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_hub_status(struct usb_device *udev, struct mtx *mtx, + struct usb_hub_status *st) +{ + struct usb_device_request req; + + req.bmRequestType = UT_READ_CLASS_DEVICE; + req.bRequest = UR_GET_STATUS; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, sizeof(struct usb_hub_status)); + return (usbd_do_request(udev, mtx, &req, st)); +} + +/*------------------------------------------------------------------------* + * usbd_req_set_address + * + * This function is used to set the address for an USB device. After + * port reset the USB device will respond at address zero. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_set_address(struct usb_device *udev, struct mtx *mtx, uint16_t addr) +{ + struct usb_device_request req; + usb_error_t err; + + DPRINTFN(6, "setting device address=%d\n", addr); + + req.bmRequestType = UT_WRITE_DEVICE; + req.bRequest = UR_SET_ADDRESS; + USETW(req.wValue, addr); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + + err = USB_ERR_INVAL; + + /* check if USB controller handles set address */ + if (udev->bus->methods->set_address != NULL) + err = (udev->bus->methods->set_address) (udev, mtx, addr); + + if (err != USB_ERR_INVAL) + goto done; + + /* Setting the address should not take more than 1 second ! */ + err = usbd_do_request_flags(udev, mtx, &req, NULL, + USB_DELAY_STATUS_STAGE, NULL, 1000); + +done: + /* allow device time to set new address */ + usb_pause_mtx(mtx, + USB_MS_TO_TICKS(USB_SET_ADDRESS_SETTLE)); + + return (err); +} + +/*------------------------------------------------------------------------* + * usbd_req_get_port_status + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_port_status(struct usb_device *udev, struct mtx *mtx, + struct usb_port_status *ps, uint8_t port) +{ + struct usb_device_request req; + + req.bmRequestType = UT_READ_CLASS_OTHER; + req.bRequest = UR_GET_STATUS; + USETW(req.wValue, 0); + req.wIndex[0] = port; + req.wIndex[1] = 0; + USETW(req.wLength, sizeof *ps); + return (usbd_do_request(udev, mtx, &req, ps)); +} + +/*------------------------------------------------------------------------* + * usbd_req_clear_hub_feature + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_clear_hub_feature(struct usb_device *udev, struct mtx *mtx, + uint16_t sel) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_CLASS_DEVICE; + req.bRequest = UR_CLEAR_FEATURE; + USETW(req.wValue, sel); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + return (usbd_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usbd_req_set_hub_feature + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_set_hub_feature(struct usb_device *udev, struct mtx *mtx, + uint16_t sel) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_CLASS_DEVICE; + req.bRequest = UR_SET_FEATURE; + USETW(req.wValue, sel); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + return (usbd_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usbd_req_set_hub_u1_timeout + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_set_hub_u1_timeout(struct usb_device *udev, struct mtx *mtx, + uint8_t port, uint8_t timeout) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_CLASS_OTHER; + req.bRequest = UR_SET_FEATURE; + USETW(req.wValue, UHF_PORT_U1_TIMEOUT); + req.wIndex[0] = port; + req.wIndex[1] = timeout; + USETW(req.wLength, 0); + return (usbd_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usbd_req_set_hub_u2_timeout + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_set_hub_u2_timeout(struct usb_device *udev, struct mtx *mtx, + uint8_t port, uint8_t timeout) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_CLASS_OTHER; + req.bRequest = UR_SET_FEATURE; + USETW(req.wValue, UHF_PORT_U2_TIMEOUT); + req.wIndex[0] = port; + req.wIndex[1] = timeout; + USETW(req.wLength, 0); + return (usbd_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usbd_req_set_hub_depth + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_set_hub_depth(struct usb_device *udev, struct mtx *mtx, + uint16_t depth) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_CLASS_DEVICE; + req.bRequest = UR_SET_HUB_DEPTH; + USETW(req.wValue, depth); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + return (usbd_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usbd_req_clear_port_feature + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_clear_port_feature(struct usb_device *udev, struct mtx *mtx, + uint8_t port, uint16_t sel) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_CLASS_OTHER; + req.bRequest = UR_CLEAR_FEATURE; + USETW(req.wValue, sel); + req.wIndex[0] = port; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + return (usbd_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usbd_req_set_port_feature + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_set_port_feature(struct usb_device *udev, struct mtx *mtx, + uint8_t port, uint16_t sel) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_CLASS_OTHER; + req.bRequest = UR_SET_FEATURE; + USETW(req.wValue, sel); + req.wIndex[0] = port; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + return (usbd_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usbd_req_set_protocol + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_set_protocol(struct usb_device *udev, struct mtx *mtx, + uint8_t iface_index, uint16_t report) +{ + struct usb_interface *iface = usbd_get_iface(udev, iface_index); + struct usb_device_request req; + + if ((iface == NULL) || (iface->idesc == NULL)) { + return (USB_ERR_INVAL); + } + DPRINTFN(5, "iface=%p, report=%d, endpt=%d\n", + iface, report, iface->idesc->bInterfaceNumber); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_SET_PROTOCOL; + USETW(req.wValue, report); + req.wIndex[0] = iface->idesc->bInterfaceNumber; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + return (usbd_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usbd_req_set_report + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_set_report(struct usb_device *udev, struct mtx *mtx, void *data, uint16_t len, + uint8_t iface_index, uint8_t type, uint8_t id) +{ + struct usb_interface *iface = usbd_get_iface(udev, iface_index); + struct usb_device_request req; + + if ((iface == NULL) || (iface->idesc == NULL)) { + return (USB_ERR_INVAL); + } + DPRINTFN(5, "len=%d\n", len); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_SET_REPORT; + USETW2(req.wValue, type, id); + req.wIndex[0] = iface->idesc->bInterfaceNumber; + req.wIndex[1] = 0; + USETW(req.wLength, len); + return (usbd_do_request(udev, mtx, &req, data)); +} + +/*------------------------------------------------------------------------* + * usbd_req_get_report + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_report(struct usb_device *udev, struct mtx *mtx, void *data, + uint16_t len, uint8_t iface_index, uint8_t type, uint8_t id) +{ + struct usb_interface *iface = usbd_get_iface(udev, iface_index); + struct usb_device_request req; + + if ((iface == NULL) || (iface->idesc == NULL) || (id == 0)) { + return (USB_ERR_INVAL); + } + DPRINTFN(5, "len=%d\n", len); + + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = UR_GET_REPORT; + USETW2(req.wValue, type, id); + req.wIndex[0] = iface->idesc->bInterfaceNumber; + req.wIndex[1] = 0; + USETW(req.wLength, len); + return (usbd_do_request(udev, mtx, &req, data)); +} + +/*------------------------------------------------------------------------* + * usbd_req_set_idle + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_set_idle(struct usb_device *udev, struct mtx *mtx, + uint8_t iface_index, uint8_t duration, uint8_t id) +{ + struct usb_interface *iface = usbd_get_iface(udev, iface_index); + struct usb_device_request req; + + if ((iface == NULL) || (iface->idesc == NULL)) { + return (USB_ERR_INVAL); + } + DPRINTFN(5, "%d %d\n", duration, id); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_SET_IDLE; + USETW2(req.wValue, duration, id); + req.wIndex[0] = iface->idesc->bInterfaceNumber; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + return (usbd_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usbd_req_get_report_descriptor + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_report_descriptor(struct usb_device *udev, struct mtx *mtx, + void *d, uint16_t size, uint8_t iface_index) +{ + struct usb_interface *iface = usbd_get_iface(udev, iface_index); + struct usb_device_request req; + + if ((iface == NULL) || (iface->idesc == NULL)) { + return (USB_ERR_INVAL); + } + req.bmRequestType = UT_READ_INTERFACE; + req.bRequest = UR_GET_DESCRIPTOR; + USETW2(req.wValue, UDESC_REPORT, 0); /* report id should be 0 */ + req.wIndex[0] = iface->idesc->bInterfaceNumber; + req.wIndex[1] = 0; + USETW(req.wLength, size); + return (usbd_do_request(udev, mtx, &req, d)); +} + +/*------------------------------------------------------------------------* + * usbd_req_set_config + * + * This function is used to select the current configuration number in + * both USB device side mode and USB host side mode. When setting the + * configuration the function of the interfaces can change. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_set_config(struct usb_device *udev, struct mtx *mtx, uint8_t conf) +{ + struct usb_device_request req; + + DPRINTF("setting config %d\n", conf); + + /* do "set configuration" request */ + + req.bmRequestType = UT_WRITE_DEVICE; + req.bRequest = UR_SET_CONFIG; + req.wValue[0] = conf; + req.wValue[1] = 0; + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + return (usbd_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usbd_req_get_config + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_get_config(struct usb_device *udev, struct mtx *mtx, uint8_t *pconf) +{ + struct usb_device_request req; + + req.bmRequestType = UT_READ_DEVICE; + req.bRequest = UR_GET_CONFIG; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, 1); + return (usbd_do_request(udev, mtx, &req, pconf)); +} + +/*------------------------------------------------------------------------* + * usbd_setup_device_desc + *------------------------------------------------------------------------*/ +usb_error_t +usbd_setup_device_desc(struct usb_device *udev, struct mtx *mtx) +{ + usb_error_t err; + + /* + * Get the first 8 bytes of the device descriptor ! + * + * NOTE: "usbd_do_request()" will check the device descriptor + * next time we do a request to see if the maximum packet size + * changed! The 8 first bytes of the device descriptor + * contains the maximum packet size to use on control endpoint + * 0. If this value is different from "USB_MAX_IPACKET" a new + * USB control request will be setup! + */ + switch (udev->speed) { + case USB_SPEED_FULL: + case USB_SPEED_LOW: + err = usbd_req_get_desc(udev, mtx, NULL, &udev->ddesc, + USB_MAX_IPACKET, USB_MAX_IPACKET, 0, UDESC_DEVICE, 0, 0); + if (err != 0) { + DPRINTFN(0, "getting device descriptor " + "at addr %d failed, %s\n", udev->address, + usbd_errstr(err)); + return (err); + } + break; + default: + DPRINTF("Minimum MaxPacketSize is large enough " + "to hold the complete device descriptor\n"); + break; + } + + /* get the full device descriptor */ + err = usbd_req_get_device_desc(udev, mtx, &udev->ddesc); + + /* try one more time, if error */ + if (err) + err = usbd_req_get_device_desc(udev, mtx, &udev->ddesc); + + if (err) { + DPRINTF("addr=%d, getting full desc failed\n", + udev->address); + return (err); + } + + DPRINTF("adding unit addr=%d, rev=%02x, class=%d, " + "subclass=%d, protocol=%d, maxpacket=%d, len=%d, speed=%d\n", + udev->address, UGETW(udev->ddesc.bcdUSB), + udev->ddesc.bDeviceClass, + udev->ddesc.bDeviceSubClass, + udev->ddesc.bDeviceProtocol, + udev->ddesc.bMaxPacketSize, + udev->ddesc.bLength, + udev->speed); + + return (err); +} + +/*------------------------------------------------------------------------* + * usbd_req_re_enumerate + * + * NOTE: After this function returns the hardware is in the + * unconfigured state! The application is responsible for setting a + * new configuration. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_re_enumerate(struct usb_device *udev, struct mtx *mtx) +{ + struct usb_device *parent_hub; + usb_error_t err; + uint8_t old_addr; + uint8_t do_retry = 1; + + if (udev->flags.usb_mode != USB_MODE_HOST) { + return (USB_ERR_INVAL); + } + old_addr = udev->address; + parent_hub = udev->parent_hub; + if (parent_hub == NULL) { + return (USB_ERR_INVAL); + } +retry: + err = usbd_req_reset_port(parent_hub, mtx, udev->port_no); + if (err) { + DPRINTFN(0, "addr=%d, port reset failed, %s\n", + old_addr, usbd_errstr(err)); + goto done; + } + + /* + * After that the port has been reset our device should be at + * address zero: + */ + udev->address = USB_START_ADDR; + + /* reset "bMaxPacketSize" */ + udev->ddesc.bMaxPacketSize = USB_MAX_IPACKET; + + /* reset USB state */ + usb_set_device_state(udev, USB_STATE_POWERED); + + /* + * Restore device address: + */ + err = usbd_req_set_address(udev, mtx, old_addr); + if (err) { + /* XXX ignore any errors! */ + DPRINTFN(0, "addr=%d, set address failed! (%s, ignored)\n", + old_addr, usbd_errstr(err)); + } + /* + * Restore device address, if the controller driver did not + * set a new one: + */ + if (udev->address == USB_START_ADDR) + udev->address = old_addr; + + /* setup the device descriptor and the initial "wMaxPacketSize" */ + err = usbd_setup_device_desc(udev, mtx); + +done: + if (err && do_retry) { + /* give the USB firmware some time to load */ + usb_pause_mtx(mtx, hz / 2); + /* no more retries after this retry */ + do_retry = 0; + /* try again */ + goto retry; + } + /* restore address */ + if (udev->address == USB_START_ADDR) + udev->address = old_addr; + /* update state, if successful */ + if (err == 0) + usb_set_device_state(udev, USB_STATE_ADDRESSED); + return (err); +} + +/*------------------------------------------------------------------------* + * usbd_req_clear_device_feature + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_clear_device_feature(struct usb_device *udev, struct mtx *mtx, + uint16_t sel) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_DEVICE; + req.bRequest = UR_CLEAR_FEATURE; + USETW(req.wValue, sel); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + return (usbd_do_request(udev, mtx, &req, 0)); +} + +/*------------------------------------------------------------------------* + * usbd_req_set_device_feature + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_req_set_device_feature(struct usb_device *udev, struct mtx *mtx, + uint16_t sel) +{ + struct usb_device_request req; + + req.bmRequestType = UT_WRITE_DEVICE; + req.bRequest = UR_SET_FEATURE; + USETW(req.wValue, sel); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + return (usbd_do_request(udev, mtx, &req, 0)); +} diff --git a/freebsd/sys/dev/usb/usb_request.h b/freebsd/sys/dev/usb/usb_request.h new file mode 100644 index 00000000..0cf882dc --- /dev/null +++ b/freebsd/sys/dev/usb/usb_request.h @@ -0,0 +1,89 @@ +/* $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. + */ + +#ifndef _USB_REQUEST_HH_ +#define _USB_REQUEST_HH_ + +struct usb_process; + +usb_error_t usbd_req_clear_hub_feature(struct usb_device *udev, + struct mtx *mtx, uint16_t sel); +usb_error_t usbd_req_clear_port_feature(struct usb_device *udev, + struct mtx *mtx, uint8_t port, uint16_t sel); +usb_error_t usbd_req_get_alt_interface_no(struct usb_device *udev, + struct mtx *mtx, uint8_t *alt_iface_no, + uint8_t iface_index); +usb_error_t usbd_req_get_config(struct usb_device *udev, struct mtx *mtx, + uint8_t *pconf); +usb_error_t usbd_req_get_descriptor_ptr(struct usb_device *udev, + struct usb_config_descriptor **ppcd, uint16_t wValue); +usb_error_t usbd_req_get_config_desc(struct usb_device *udev, struct mtx *mtx, + struct usb_config_descriptor *d, uint8_t conf_index); +usb_error_t usbd_req_get_config_desc_full(struct usb_device *udev, + struct mtx *mtx, struct usb_config_descriptor **ppcd, + struct malloc_type *mtype, uint8_t conf_index); +usb_error_t usbd_req_get_desc(struct usb_device *udev, struct mtx *mtx, + uint16_t *actlen, void *desc, uint16_t min_len, + uint16_t max_len, uint16_t id, uint8_t type, + uint8_t index, uint8_t retries); +usb_error_t usbd_req_get_device_desc(struct usb_device *udev, struct mtx *mtx, + struct usb_device_descriptor *d); +usb_error_t usbd_req_get_device_status(struct usb_device *udev, + struct mtx *mtx, struct usb_status *st); +usb_error_t usbd_req_get_hub_descriptor(struct usb_device *udev, + struct mtx *mtx, struct usb_hub_descriptor *hd, + uint8_t nports); +usb_error_t usbd_req_get_ss_hub_descriptor(struct usb_device *udev, + struct mtx *mtx, struct usb_hub_ss_descriptor *hd, + uint8_t nports); +usb_error_t usbd_req_get_hub_status(struct usb_device *udev, struct mtx *mtx, + struct usb_hub_status *st); +usb_error_t usbd_req_get_port_status(struct usb_device *udev, struct mtx *mtx, + struct usb_port_status *ps, uint8_t port); +usb_error_t usbd_req_reset_port(struct usb_device *udev, struct mtx *mtx, + uint8_t port); +usb_error_t usbd_req_warm_reset_port(struct usb_device *udev, + struct mtx *mtx, uint8_t port); +usb_error_t usbd_req_set_address(struct usb_device *udev, struct mtx *mtx, + uint16_t addr); +usb_error_t usbd_req_set_hub_feature(struct usb_device *udev, struct mtx *mtx, + uint16_t sel); +usb_error_t usbd_req_set_port_feature(struct usb_device *udev, + struct mtx *mtx, uint8_t port, uint16_t sel); +usb_error_t usbd_setup_device_desc(struct usb_device *udev, struct mtx *mtx); +usb_error_t usbd_req_re_enumerate(struct usb_device *udev, struct mtx *mtx); +usb_error_t usbd_req_clear_device_feature(struct usb_device *udev, + struct mtx *mtx, uint16_t sel); +usb_error_t usbd_req_set_device_feature(struct usb_device *udev, + struct mtx *mtx, uint16_t sel); +usb_error_t usbd_req_set_hub_u1_timeout(struct usb_device *udev, + struct mtx *mtx, uint8_t port, uint8_t timeout); +usb_error_t usbd_req_set_hub_u2_timeout(struct usb_device *udev, + struct mtx *mtx, uint8_t port, uint8_t timeout); +usb_error_t usbd_req_set_hub_depth(struct usb_device *udev, + struct mtx *mtx, uint16_t depth); + +#endif /* _USB_REQUEST_HH_ */ diff --git a/freebsd/sys/dev/usb/usb_transfer.c b/freebsd/sys/dev/usb/usb_transfer.c new file mode 100644 index 00000000..caf3d31b --- /dev/null +++ b/freebsd/sys/dev/usb/usb_transfer.c @@ -0,0 +1,3305 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/* $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. + */ + +#include <freebsd/sys/stdint.h> +#include <freebsd/sys/stddef.h> +#include <freebsd/sys/param.h> +#include <freebsd/sys/queue.h> +#include <freebsd/sys/types.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/bus.h> +#include <freebsd/sys/linker_set.h> +#include <freebsd/sys/module.h> +#include <freebsd/sys/lock.h> +#include <freebsd/sys/mutex.h> +#include <freebsd/sys/condvar.h> +#include <freebsd/sys/sysctl.h> +#include <freebsd/sys/sx.h> +#include <freebsd/sys/unistd.h> +#include <freebsd/sys/callout.h> +#include <freebsd/sys/malloc.h> +#include <freebsd/sys/priv.h> + +#include <freebsd/dev/usb/usb.h> +#include <freebsd/dev/usb/usbdi.h> +#include <freebsd/dev/usb/usbdi_util.h> + +#define USB_DEBUG_VAR usb_debug + +#include <freebsd/dev/usb/usb_core.h> +#include <freebsd/dev/usb/usb_busdma.h> +#include <freebsd/dev/usb/usb_process.h> +#include <freebsd/dev/usb/usb_transfer.h> +#include <freebsd/dev/usb/usb_device.h> +#include <freebsd/dev/usb/usb_debug.h> +#include <freebsd/dev/usb/usb_util.h> + +#include <freebsd/dev/usb/usb_controller.h> +#include <freebsd/dev/usb/usb_bus.h> +#ifdef __rtems__ +#include <freebsd/machine/rtems-bsd-cache.h> +#endif /* __rtems__ */ + +struct usb_std_packet_size { + struct { + uint16_t min; /* inclusive */ + uint16_t max; /* inclusive */ + } range; + + uint16_t fixed[4]; +}; + +static usb_callback_t usb_request_callback; + +static const struct usb_config usb_control_ep_cfg[USB_CTRL_XFER_MAX] = { + + /* This transfer is used for generic control endpoint transfers */ + + [0] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control endpoint */ + .direction = UE_DIR_ANY, + .bufsize = USB_EP0_BUFSIZE, /* bytes */ + .flags = {.proxy_buffer = 1,}, + .callback = &usb_request_callback, + .usb_mode = USB_MODE_DUAL, /* both modes */ + }, + + /* This transfer is used for generic clear stall only */ + + [1] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .bufsize = sizeof(struct usb_device_request), + .callback = &usb_do_clear_stall_callback, + .timeout = 1000, /* 1 second */ + .interval = 50, /* 50ms */ + .usb_mode = USB_MODE_HOST, + }, +}; + +/* function prototypes */ + +static void usbd_update_max_frame_size(struct usb_xfer *); +static void usbd_transfer_unsetup_sub(struct usb_xfer_root *, uint8_t); +static void usbd_control_transfer_init(struct usb_xfer *); +static int usbd_setup_ctrl_transfer(struct usb_xfer *); +static void usb_callback_proc(struct usb_proc_msg *); +static void usbd_callback_ss_done_defer(struct usb_xfer *); +static void usbd_callback_wrapper(struct usb_xfer_queue *); +static void usbd_transfer_start_cb(void *); +static uint8_t usbd_callback_wrapper_sub(struct usb_xfer *); +static void usbd_get_std_packet_size(struct usb_std_packet_size *ptr, + uint8_t type, enum usb_dev_speed speed); + +/*------------------------------------------------------------------------* + * usb_request_callback + *------------------------------------------------------------------------*/ +static void +usb_request_callback(struct usb_xfer *xfer, usb_error_t error) +{ + if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) + usb_handle_request_callback(xfer, error); + else + usbd_do_request_callback(xfer, error); +} + +/*------------------------------------------------------------------------* + * usbd_update_max_frame_size + * + * This function updates the maximum frame size, hence high speed USB + * can transfer multiple consecutive packets. + *------------------------------------------------------------------------*/ +static void +usbd_update_max_frame_size(struct usb_xfer *xfer) +{ + /* compute maximum frame size */ + /* this computation should not overflow 16-bit */ + /* max = 15 * 1024 */ + + xfer->max_frame_size = xfer->max_packet_size * xfer->max_packet_count; +} + +/*------------------------------------------------------------------------* + * usbd_get_dma_delay + * + * The following function is called when we need to + * synchronize with DMA hardware. + * + * Returns: + * 0: no DMA delay required + * Else: milliseconds of DMA delay + *------------------------------------------------------------------------*/ +usb_timeout_t +usbd_get_dma_delay(struct usb_device *udev) +{ + struct usb_bus_methods *mtod; + uint32_t temp; + + mtod = udev->bus->methods; + temp = 0; + + if (mtod->get_dma_delay) { + (mtod->get_dma_delay) (udev, &temp); + /* + * Round up and convert to milliseconds. Note that we use + * 1024 milliseconds per second. to save a division. + */ + temp += 0x3FF; + temp /= 0x400; + } + return (temp); +} + +/*------------------------------------------------------------------------* + * usbd_transfer_setup_sub_malloc + * + * This function will allocate one or more DMA'able memory chunks + * according to "size", "align" and "count" arguments. "ppc" is + * pointed to a linear array of USB page caches afterwards. + * + * Returns: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +#if USB_HAVE_BUSDMA +uint8_t +usbd_transfer_setup_sub_malloc(struct usb_setup_params *parm, + struct usb_page_cache **ppc, usb_size_t size, usb_size_t align, + usb_size_t count) +{ + struct usb_page_cache *pc; + struct usb_page *pg; + void *buf; + usb_size_t n_dma_pc; + usb_size_t n_obj; + usb_size_t x; + usb_size_t y; + usb_size_t r; + usb_size_t z; + + USB_ASSERT(align > 1, ("Invalid alignment, 0x%08x\n", + align)); + USB_ASSERT(size > 0, ("Invalid size = 0\n")); + + if (count == 0) { + return (0); /* nothing to allocate */ + } +#ifdef __rtems__ +#ifdef CPU_DATA_CACHE_ALIGNMENT + if (align < CPU_DATA_CACHE_ALIGNMENT) { + align = CPU_DATA_CACHE_ALIGNMENT; + } +#endif /* CPU_DATA_CACHE_ALIGNMENT */ +#endif /* __rtems__ */ + /* + * Make sure that the size is aligned properly. + */ + size = -((-size) & (-align)); + + /* + * Try multi-allocation chunks to reduce the number of DMA + * allocations, hence DMA allocations are slow. + */ + if (size >= PAGE_SIZE) { + n_dma_pc = count; + n_obj = 1; + } else { + /* compute number of objects per page */ + n_obj = (PAGE_SIZE / size); + /* + * Compute number of DMA chunks, rounded up + * to nearest one: + */ + n_dma_pc = ((count + n_obj - 1) / n_obj); + } + + if (parm->buf == NULL) { + /* for the future */ + parm->dma_page_ptr += n_dma_pc; + parm->dma_page_cache_ptr += n_dma_pc; + parm->dma_page_ptr += count; + parm->xfer_page_cache_ptr += count; + return (0); + } + for (x = 0; x != n_dma_pc; x++) { + /* need to initialize the page cache */ + parm->dma_page_cache_ptr[x].tag_parent = + &parm->curr_xfer->xroot->dma_parent_tag; + } + for (x = 0; x != count; x++) { + /* need to initialize the page cache */ + parm->xfer_page_cache_ptr[x].tag_parent = + &parm->curr_xfer->xroot->dma_parent_tag; + } + + if (ppc) { + *ppc = parm->xfer_page_cache_ptr; + } + r = count; /* set remainder count */ + z = n_obj * size; /* set allocation size */ + pc = parm->xfer_page_cache_ptr; + pg = parm->dma_page_ptr; + + for (x = 0; x != n_dma_pc; x++) { + + if (r < n_obj) { + /* compute last remainder */ + z = r * size; + n_obj = r; + } + if (usb_pc_alloc_mem(parm->dma_page_cache_ptr, + pg, z, align)) { + return (1); /* failure */ + } + /* Set beginning of current buffer */ + buf = parm->dma_page_cache_ptr->buffer; + /* Make room for one DMA page cache and one page */ + parm->dma_page_cache_ptr++; + pg++; + + for (y = 0; (y != n_obj); y++, r--, pc++, pg++) { + + /* Load sub-chunk into DMA */ + if (usb_pc_dmamap_create(pc, size)) { + return (1); /* failure */ + } + pc->buffer = USB_ADD_BYTES(buf, y * size); + pc->page_start = pg; + + mtx_lock(pc->tag_parent->mtx); + if (usb_pc_load_mem(pc, size, 1 /* synchronous */ )) { + mtx_unlock(pc->tag_parent->mtx); + return (1); /* failure */ + } + mtx_unlock(pc->tag_parent->mtx); + } + } + + parm->xfer_page_cache_ptr = pc; + parm->dma_page_ptr = pg; + return (0); +} +#endif + +/*------------------------------------------------------------------------* + * usbd_transfer_setup_sub - transfer setup subroutine + * + * This function must be called from the "xfer_setup" callback of the + * USB Host or Device controller driver when setting up an USB + * transfer. This function will setup correct packet sizes, buffer + * sizes, flags and more, that are stored in the "usb_xfer" + * structure. + *------------------------------------------------------------------------*/ +void +usbd_transfer_setup_sub(struct usb_setup_params *parm) +{ + enum { + REQ_SIZE = 8, + MIN_PKT = 8, + }; + struct usb_xfer *xfer = parm->curr_xfer; + const struct usb_config *setup = parm->curr_setup; + struct usb_endpoint_ss_comp_descriptor *ecomp; + struct usb_endpoint_descriptor *edesc; + struct usb_std_packet_size std_size; + usb_frcount_t n_frlengths; + usb_frcount_t n_frbuffers; + usb_frcount_t x; + uint8_t type; + uint8_t zmps; + + /* + * Sanity check. The following parameters must be initialized before + * calling this function. + */ + if ((parm->hc_max_packet_size == 0) || + (parm->hc_max_packet_count == 0) || + (parm->hc_max_frame_size == 0)) { + parm->err = USB_ERR_INVAL; + goto done; + } + edesc = xfer->endpoint->edesc; + ecomp = xfer->endpoint->ecomp; + + type = (edesc->bmAttributes & UE_XFERTYPE); + + xfer->flags = setup->flags; + xfer->nframes = setup->frames; + xfer->timeout = setup->timeout; + xfer->callback = setup->callback; + xfer->interval = setup->interval; + xfer->endpointno = edesc->bEndpointAddress; + xfer->max_packet_size = UGETW(edesc->wMaxPacketSize); + xfer->max_packet_count = 1; + /* make a shadow copy: */ + xfer->flags_int.usb_mode = parm->udev->flags.usb_mode; + + parm->bufsize = setup->bufsize; + + switch (parm->speed) { + case USB_SPEED_HIGH: + switch (type) { + case UE_ISOCHRONOUS: + case UE_INTERRUPT: + xfer->max_packet_count += (xfer->max_packet_size >> 11) & 3; + + /* check for invalid max packet count */ + if (xfer->max_packet_count > 3) + xfer->max_packet_count = 3; + break; + default: + break; + } + xfer->max_packet_size &= 0x7FF; + break; + case USB_SPEED_SUPER: + xfer->max_packet_count += (xfer->max_packet_size >> 11) & 3; + + if (ecomp != NULL) + xfer->max_packet_count += ecomp->bMaxBurst; + + if ((xfer->max_packet_count == 0) || + (xfer->max_packet_count > 16)) + xfer->max_packet_count = 16; + + switch (type) { + case UE_CONTROL: + xfer->max_packet_count = 1; + break; + case UE_ISOCHRONOUS: + if (ecomp != NULL) { + uint8_t mult; + + mult = (ecomp->bmAttributes & 3) + 1; + if (mult > 3) + mult = 3; + + xfer->max_packet_count *= mult; + } + break; + default: + break; + } + xfer->max_packet_size &= 0x7FF; + break; + default: + break; + } + /* range check "max_packet_count" */ + + if (xfer->max_packet_count > parm->hc_max_packet_count) { + xfer->max_packet_count = parm->hc_max_packet_count; + } + /* filter "wMaxPacketSize" according to HC capabilities */ + + if ((xfer->max_packet_size > parm->hc_max_packet_size) || + (xfer->max_packet_size == 0)) { + xfer->max_packet_size = parm->hc_max_packet_size; + } + /* filter "wMaxPacketSize" according to standard sizes */ + + usbd_get_std_packet_size(&std_size, type, parm->speed); + + if (std_size.range.min || std_size.range.max) { + + if (xfer->max_packet_size < std_size.range.min) { + xfer->max_packet_size = std_size.range.min; + } + if (xfer->max_packet_size > std_size.range.max) { + xfer->max_packet_size = std_size.range.max; + } + } else { + + if (xfer->max_packet_size >= std_size.fixed[3]) { + xfer->max_packet_size = std_size.fixed[3]; + } else if (xfer->max_packet_size >= std_size.fixed[2]) { + xfer->max_packet_size = std_size.fixed[2]; + } else if (xfer->max_packet_size >= std_size.fixed[1]) { + xfer->max_packet_size = std_size.fixed[1]; + } else { + /* only one possibility left */ + xfer->max_packet_size = std_size.fixed[0]; + } + } + + /* compute "max_frame_size" */ + + usbd_update_max_frame_size(xfer); + + /* check interrupt interval and transfer pre-delay */ + + if (type == UE_ISOCHRONOUS) { + + uint16_t frame_limit; + + xfer->interval = 0; /* not used, must be zero */ + xfer->flags_int.isochronous_xfr = 1; /* set flag */ + + if (xfer->timeout == 0) { + /* + * set a default timeout in + * case something goes wrong! + */ + xfer->timeout = 1000 / 4; + } + switch (parm->speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + frame_limit = USB_MAX_FS_ISOC_FRAMES_PER_XFER; + xfer->fps_shift = 0; + break; + default: + frame_limit = USB_MAX_HS_ISOC_FRAMES_PER_XFER; + xfer->fps_shift = edesc->bInterval; + if (xfer->fps_shift > 0) + xfer->fps_shift--; + if (xfer->fps_shift > 3) + xfer->fps_shift = 3; + break; + } + + if (xfer->nframes > frame_limit) { + /* + * this is not going to work + * cross hardware + */ + parm->err = USB_ERR_INVAL; + goto done; + } + if (xfer->nframes == 0) { + /* + * this is not a valid value + */ + parm->err = USB_ERR_ZERO_NFRAMES; + goto done; + } + } else { + + /* + * If a value is specified use that else check the + * endpoint descriptor! + */ + if (type == UE_INTERRUPT) { + + uint32_t temp; + + if (xfer->interval == 0) { + + xfer->interval = edesc->bInterval; + + switch (parm->speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + break; + default: + /* 125us -> 1ms */ + if (xfer->interval < 4) + xfer->interval = 1; + else if (xfer->interval > 16) + xfer->interval = (1 << (16 - 4)); + else + xfer->interval = + (1 << (xfer->interval - 4)); + break; + } + } + + if (xfer->interval == 0) { + /* + * One millisecond is the smallest + * interval we support: + */ + xfer->interval = 1; + } + + xfer->fps_shift = 0; + temp = 1; + + while ((temp != 0) && (temp < xfer->interval)) { + xfer->fps_shift++; + temp *= 2; + } + + switch (parm->speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + break; + default: + xfer->fps_shift += 3; + break; + } + } + } + + /* + * NOTE: we do not allow "max_packet_size" or "max_frame_size" + * to be equal to zero when setting up USB transfers, hence + * this leads to alot of extra code in the USB kernel. + */ + + if ((xfer->max_frame_size == 0) || + (xfer->max_packet_size == 0)) { + + zmps = 1; + + if ((parm->bufsize <= MIN_PKT) && + (type != UE_CONTROL) && + (type != UE_BULK)) { + + /* workaround */ + xfer->max_packet_size = MIN_PKT; + xfer->max_packet_count = 1; + parm->bufsize = 0; /* automatic setup length */ + usbd_update_max_frame_size(xfer); + + } else { + parm->err = USB_ERR_ZERO_MAXP; + goto done; + } + + } else { + zmps = 0; + } + + /* + * check if we should setup a default + * length: + */ + + if (parm->bufsize == 0) { + + parm->bufsize = xfer->max_frame_size; + + if (type == UE_ISOCHRONOUS) { + parm->bufsize *= xfer->nframes; + } + } + /* + * check if we are about to setup a proxy + * type of buffer: + */ + + if (xfer->flags.proxy_buffer) { + + /* round bufsize up */ + + parm->bufsize += (xfer->max_frame_size - 1); + + if (parm->bufsize < xfer->max_frame_size) { + /* length wrapped around */ + parm->err = USB_ERR_INVAL; + goto done; + } + /* subtract remainder */ + + parm->bufsize -= (parm->bufsize % xfer->max_frame_size); + + /* add length of USB device request structure, if any */ + + if (type == UE_CONTROL) { + parm->bufsize += REQ_SIZE; /* SETUP message */ + } + } + xfer->max_data_length = parm->bufsize; + + /* Setup "n_frlengths" and "n_frbuffers" */ + + if (type == UE_ISOCHRONOUS) { + n_frlengths = xfer->nframes; + n_frbuffers = 1; + } else { + + if (type == UE_CONTROL) { + xfer->flags_int.control_xfr = 1; + if (xfer->nframes == 0) { + if (parm->bufsize <= REQ_SIZE) { + /* + * there will never be any data + * stage + */ + xfer->nframes = 1; + } else { + xfer->nframes = 2; + } + } + } else { + if (xfer->nframes == 0) { + xfer->nframes = 1; + } + } + + n_frlengths = xfer->nframes; + n_frbuffers = xfer->nframes; + } + + /* + * check if we have room for the + * USB device request structure: + */ + + if (type == UE_CONTROL) { + + if (xfer->max_data_length < REQ_SIZE) { + /* length wrapped around or too small bufsize */ + parm->err = USB_ERR_INVAL; + goto done; + } + xfer->max_data_length -= REQ_SIZE; + } + /* setup "frlengths" */ + xfer->frlengths = parm->xfer_length_ptr; + parm->xfer_length_ptr += n_frlengths; + + /* setup "frbuffers" */ + xfer->frbuffers = parm->xfer_page_cache_ptr; + parm->xfer_page_cache_ptr += n_frbuffers; + + /* initialize max frame count */ + xfer->max_frame_count = xfer->nframes; + + /* + * check if we need to setup + * a local buffer: + */ + + if (!xfer->flags.ext_buffer) { + + /* align data */ +#ifdef __rtems__ +#ifdef CPU_DATA_CACHE_ALIGNMENT + parm->size[0] += CPU_DATA_CACHE_ALIGNMENT; +#endif /* CPU_DATA_CACHE_ALIGNMENT */ +#else /* __rtems__ */ + parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1)); +#endif /* __rtems__ */ + + if (parm->buf) { + + xfer->local_buffer = + USB_ADD_BYTES(parm->buf, parm->size[0]); +#ifdef __rtems__ +#ifdef CPU_DATA_CACHE_ALIGNMENT + xfer->local_buffer = (char *) xfer->local_buffer + + ((-(uintptr_t) xfer->local_buffer) + & (CPU_DATA_CACHE_ALIGNMENT - 1)); +#endif /* CPU_DATA_CACHE_ALIGNMENT */ +#endif /* __rtems__ */ + + usbd_xfer_set_frame_offset(xfer, 0, 0); + + if ((type == UE_CONTROL) && (n_frbuffers > 1)) { + usbd_xfer_set_frame_offset(xfer, REQ_SIZE, 1); + } + } + parm->size[0] += parm->bufsize; + + /* align data again */ +#ifdef __rtems__ +#ifdef CPU_DATA_CACHE_ALIGNMENT + parm->size[0] += CPU_DATA_CACHE_ALIGNMENT; +#endif /* CPU_DATA_CACHE_ALIGNMENT */ +#endif /* __rtems__ */ + parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1)); + } + /* + * Compute maximum buffer size + */ + + if (parm->bufsize_max < parm->bufsize) { + parm->bufsize_max = parm->bufsize; + } +#if USB_HAVE_BUSDMA + if (xfer->flags_int.bdma_enable) { + /* + * Setup "dma_page_ptr". + * + * Proof for formula below: + * + * Assume there are three USB frames having length "a", "b" and + * "c". These USB frames will at maximum need "z" + * "usb_page" structures. "z" is given by: + * + * z = ((a / USB_PAGE_SIZE) + 2) + ((b / USB_PAGE_SIZE) + 2) + + * ((c / USB_PAGE_SIZE) + 2); + * + * Constraining "a", "b" and "c" like this: + * + * (a + b + c) <= parm->bufsize + * + * We know that: + * + * z <= ((parm->bufsize / USB_PAGE_SIZE) + (3*2)); + * + * Here is the general formula: + */ + xfer->dma_page_ptr = parm->dma_page_ptr; + parm->dma_page_ptr += (2 * n_frbuffers); + parm->dma_page_ptr += (parm->bufsize / USB_PAGE_SIZE); + } +#endif + if (zmps) { + /* correct maximum data length */ + xfer->max_data_length = 0; + } + /* subtract USB frame remainder from "hc_max_frame_size" */ + + xfer->max_hc_frame_size = + (parm->hc_max_frame_size - + (parm->hc_max_frame_size % xfer->max_frame_size)); + + if (xfer->max_hc_frame_size == 0) { + parm->err = USB_ERR_INVAL; + goto done; + } + + /* initialize frame buffers */ + + if (parm->buf) { + for (x = 0; x != n_frbuffers; x++) { + xfer->frbuffers[x].tag_parent = + &xfer->xroot->dma_parent_tag; +#if USB_HAVE_BUSDMA + if (xfer->flags_int.bdma_enable && + (parm->bufsize_max > 0)) { + + if (usb_pc_dmamap_create( + xfer->frbuffers + x, + parm->bufsize_max)) { + parm->err = USB_ERR_NOMEM; + goto done; + } + } +#endif + } + } +done: + if (parm->err) { + /* + * Set some dummy values so that we avoid division by zero: + */ + xfer->max_hc_frame_size = 1; + xfer->max_frame_size = 1; + xfer->max_packet_size = 1; + xfer->max_data_length = 0; + xfer->nframes = 0; + xfer->max_frame_count = 0; + } +} + +/*------------------------------------------------------------------------* + * usbd_transfer_setup - setup an array of USB transfers + * + * NOTE: You must always call "usbd_transfer_unsetup" after calling + * "usbd_transfer_setup" if success was returned. + * + * The idea is that the USB device driver should pre-allocate all its + * transfers by one call to this function. + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +usb_error_t +usbd_transfer_setup(struct usb_device *udev, + const uint8_t *ifaces, struct usb_xfer **ppxfer, + const struct usb_config *setup_start, uint16_t n_setup, + void *priv_sc, struct mtx *xfer_mtx) +{ + struct usb_xfer dummy; + struct usb_setup_params parm; + const struct usb_config *setup_end = setup_start + n_setup; + const struct usb_config *setup; + struct usb_endpoint *ep; + struct usb_xfer_root *info; + struct usb_xfer *xfer; + void *buf = NULL; + uint16_t n; + uint16_t refcount; + + parm.err = 0; + refcount = 0; + info = NULL; + + WITNESS_WARN(WARN_GIANTOK | WARN_SLEEPOK, NULL, + "usbd_transfer_setup can sleep!"); + + /* do some checking first */ + + if (n_setup == 0) { + DPRINTFN(6, "setup array has zero length!\n"); + return (USB_ERR_INVAL); + } + if (ifaces == 0) { + DPRINTFN(6, "ifaces array is NULL!\n"); + return (USB_ERR_INVAL); + } + if (xfer_mtx == NULL) { + DPRINTFN(6, "using global lock\n"); + xfer_mtx = &Giant; + } + /* sanity checks */ + for (setup = setup_start, n = 0; + setup != setup_end; setup++, n++) { + if (setup->bufsize == (usb_frlength_t)-1) { + parm.err = USB_ERR_BAD_BUFSIZE; + DPRINTF("invalid bufsize\n"); + } + if (setup->callback == NULL) { + parm.err = USB_ERR_NO_CALLBACK; + DPRINTF("no callback\n"); + } + ppxfer[n] = NULL; + } + + if (parm.err) { + goto done; + } + bzero(&parm, sizeof(parm)); + + parm.udev = udev; + parm.speed = usbd_get_speed(udev); + parm.hc_max_packet_count = 1; + + if (parm.speed >= USB_SPEED_MAX) { + parm.err = USB_ERR_INVAL; + goto done; + } + /* setup all transfers */ + + while (1) { + + if (buf) { + /* + * Initialize the "usb_xfer_root" structure, + * which is common for all our USB transfers. + */ + info = USB_ADD_BYTES(buf, 0); + + info->memory_base = buf; + info->memory_size = parm.size[0]; + +#if USB_HAVE_BUSDMA + info->dma_page_cache_start = USB_ADD_BYTES(buf, parm.size[4]); + info->dma_page_cache_end = USB_ADD_BYTES(buf, parm.size[5]); +#endif + info->xfer_page_cache_start = USB_ADD_BYTES(buf, parm.size[5]); + info->xfer_page_cache_end = USB_ADD_BYTES(buf, parm.size[2]); + + cv_init(&info->cv_drain, "WDRAIN"); + + info->xfer_mtx = xfer_mtx; +#if USB_HAVE_BUSDMA + usb_dma_tag_setup(&info->dma_parent_tag, + parm.dma_tag_p, udev->bus->dma_parent_tag[0].tag, + xfer_mtx, &usb_bdma_done_event, 32, parm.dma_tag_max); +#endif + + info->bus = udev->bus; + info->udev = udev; + + TAILQ_INIT(&info->done_q.head); + info->done_q.command = &usbd_callback_wrapper; +#if USB_HAVE_BUSDMA + TAILQ_INIT(&info->dma_q.head); + info->dma_q.command = &usb_bdma_work_loop; +#endif + info->done_m[0].hdr.pm_callback = &usb_callback_proc; + info->done_m[0].xroot = info; + info->done_m[1].hdr.pm_callback = &usb_callback_proc; + info->done_m[1].xroot = info; + + /* + * In device side mode control endpoint + * requests need to run from a separate + * context, else there is a chance of + * deadlock! + */ + if (setup_start == usb_control_ep_cfg) + info->done_p = + &udev->bus->control_xfer_proc; + else if (xfer_mtx == &Giant) + info->done_p = + &udev->bus->giant_callback_proc; + else + info->done_p = + &udev->bus->non_giant_callback_proc; + } + /* reset sizes */ + + parm.size[0] = 0; + parm.buf = buf; + parm.size[0] += sizeof(info[0]); + + for (setup = setup_start, n = 0; + setup != setup_end; setup++, n++) { + + /* skip USB transfers without callbacks: */ + if (setup->callback == NULL) { + continue; + } + /* see if there is a matching endpoint */ + ep = usbd_get_endpoint(udev, + ifaces[setup->if_index], setup); + + if ((ep == NULL) || (ep->methods == NULL)) { + if (setup->flags.no_pipe_ok) + continue; + if ((setup->usb_mode != USB_MODE_DUAL) && + (setup->usb_mode != udev->flags.usb_mode)) + continue; + parm.err = USB_ERR_NO_PIPE; + goto done; + } + + /* align data properly */ + parm.size[0] += ((-parm.size[0]) & (USB_HOST_ALIGN - 1)); + + /* store current setup pointer */ + parm.curr_setup = setup; + + if (buf) { + /* + * Common initialization of the + * "usb_xfer" structure. + */ + xfer = USB_ADD_BYTES(buf, parm.size[0]); + xfer->address = udev->address; + xfer->priv_sc = priv_sc; + xfer->xroot = info; + + usb_callout_init_mtx(&xfer->timeout_handle, + &udev->bus->bus_mtx, 0); + } else { + /* + * Setup a dummy xfer, hence we are + * writing to the "usb_xfer" + * structure pointed to by "xfer" + * before we have allocated any + * memory: + */ + xfer = &dummy; + bzero(&dummy, sizeof(dummy)); + refcount++; + } + + /* set transfer endpoint pointer */ + xfer->endpoint = ep; + + parm.size[0] += sizeof(xfer[0]); + parm.methods = xfer->endpoint->methods; + parm.curr_xfer = xfer; + + /* + * Call the Host or Device controller transfer + * setup routine: + */ + (udev->bus->methods->xfer_setup) (&parm); + + /* check for error */ + if (parm.err) + goto done; + + if (buf) { + /* + * Increment the endpoint refcount. This + * basically prevents setting a new + * configuration and alternate setting + * when USB transfers are in use on + * the given interface. Search the USB + * code for "endpoint->refcount_alloc" if you + * want more information. + */ + USB_BUS_LOCK(info->bus); + if (xfer->endpoint->refcount_alloc >= USB_EP_REF_MAX) + parm.err = USB_ERR_INVAL; + + xfer->endpoint->refcount_alloc++; + + if (xfer->endpoint->refcount_alloc == 0) + panic("usbd_transfer_setup(): Refcount wrapped to zero\n"); + USB_BUS_UNLOCK(info->bus); + + /* + * Whenever we set ppxfer[] then we + * also need to increment the + * "setup_refcount": + */ + info->setup_refcount++; + + /* + * Transfer is successfully setup and + * can be used: + */ + ppxfer[n] = xfer; + } + + /* check for error */ + if (parm.err) + goto done; + } + + if (buf || parm.err) { + goto done; + } + if (refcount == 0) { + /* no transfers - nothing to do ! */ + goto done; + } + /* align data properly */ + parm.size[0] += ((-parm.size[0]) & (USB_HOST_ALIGN - 1)); + + /* store offset temporarily */ + parm.size[1] = parm.size[0]; + + /* + * The number of DMA tags required depends on + * the number of endpoints. The current estimate + * for maximum number of DMA tags per endpoint + * is two. + */ + parm.dma_tag_max += 2 * MIN(n_setup, USB_EP_MAX); + + /* + * DMA tags for QH, TD, Data and more. + */ + parm.dma_tag_max += 8; + + parm.dma_tag_p += parm.dma_tag_max; + + parm.size[0] += ((uint8_t *)parm.dma_tag_p) - + ((uint8_t *)0); + + /* align data properly */ + parm.size[0] += ((-parm.size[0]) & (USB_HOST_ALIGN - 1)); + + /* store offset temporarily */ + parm.size[3] = parm.size[0]; + + parm.size[0] += ((uint8_t *)parm.dma_page_ptr) - + ((uint8_t *)0); + + /* align data properly */ + parm.size[0] += ((-parm.size[0]) & (USB_HOST_ALIGN - 1)); + + /* store offset temporarily */ + parm.size[4] = parm.size[0]; + + parm.size[0] += ((uint8_t *)parm.dma_page_cache_ptr) - + ((uint8_t *)0); + + /* store end offset temporarily */ + parm.size[5] = parm.size[0]; + + parm.size[0] += ((uint8_t *)parm.xfer_page_cache_ptr) - + ((uint8_t *)0); + + /* store end offset temporarily */ + + parm.size[2] = parm.size[0]; + + /* align data properly */ + parm.size[0] += ((-parm.size[0]) & (USB_HOST_ALIGN - 1)); + + parm.size[6] = parm.size[0]; + + parm.size[0] += ((uint8_t *)parm.xfer_length_ptr) - + ((uint8_t *)0); + + /* align data properly */ + parm.size[0] += ((-parm.size[0]) & (USB_HOST_ALIGN - 1)); + + /* allocate zeroed memory */ + buf = malloc(parm.size[0], M_USB, M_WAITOK | M_ZERO); + + if (buf == NULL) { + parm.err = USB_ERR_NOMEM; + DPRINTFN(0, "cannot allocate memory block for " + "configuration (%d bytes)\n", + parm.size[0]); + goto done; + } + parm.dma_tag_p = USB_ADD_BYTES(buf, parm.size[1]); + parm.dma_page_ptr = USB_ADD_BYTES(buf, parm.size[3]); + parm.dma_page_cache_ptr = USB_ADD_BYTES(buf, parm.size[4]); + parm.xfer_page_cache_ptr = USB_ADD_BYTES(buf, parm.size[5]); + parm.xfer_length_ptr = USB_ADD_BYTES(buf, parm.size[6]); + } + +done: + if (buf) { + if (info->setup_refcount == 0) { + /* + * "usbd_transfer_unsetup_sub" will unlock + * the bus mutex before returning ! + */ + USB_BUS_LOCK(info->bus); + + /* something went wrong */ + usbd_transfer_unsetup_sub(info, 0); + } + } + if (parm.err) { + usbd_transfer_unsetup(ppxfer, n_setup); + } + return (parm.err); +} + +/*------------------------------------------------------------------------* + * usbd_transfer_unsetup_sub - factored out code + *------------------------------------------------------------------------*/ +static void +usbd_transfer_unsetup_sub(struct usb_xfer_root *info, uint8_t needs_delay) +{ + struct usb_page_cache *pc; + + USB_BUS_LOCK_ASSERT(info->bus, MA_OWNED); + + /* wait for any outstanding DMA operations */ + + if (needs_delay) { + usb_timeout_t temp; + temp = usbd_get_dma_delay(info->udev); + if (temp != 0) { + usb_pause_mtx(&info->bus->bus_mtx, + USB_MS_TO_TICKS(temp)); + } + } + + /* make sure that our done messages are not queued anywhere */ + usb_proc_mwait(info->done_p, &info->done_m[0], &info->done_m[1]); + + USB_BUS_UNLOCK(info->bus); + +#if USB_HAVE_BUSDMA + /* free DMA'able memory, if any */ + pc = info->dma_page_cache_start; + while (pc != info->dma_page_cache_end) { + usb_pc_free_mem(pc); + pc++; + } + + /* free DMA maps in all "xfer->frbuffers" */ + pc = info->xfer_page_cache_start; + while (pc != info->xfer_page_cache_end) { + usb_pc_dmamap_destroy(pc); + pc++; + } + + /* free all DMA tags */ + usb_dma_tag_unsetup(&info->dma_parent_tag); +#endif + + cv_destroy(&info->cv_drain); + + /* + * free the "memory_base" last, hence the "info" structure is + * contained within the "memory_base"! + */ + free(info->memory_base, M_USB); +} + +/*------------------------------------------------------------------------* + * usbd_transfer_unsetup - unsetup/free an array of USB transfers + * + * NOTE: All USB transfers in progress will get called back passing + * the error code "USB_ERR_CANCELLED" before this function + * returns. + *------------------------------------------------------------------------*/ +void +usbd_transfer_unsetup(struct usb_xfer **pxfer, uint16_t n_setup) +{ + struct usb_xfer *xfer; + struct usb_xfer_root *info; + uint8_t needs_delay = 0; + + WITNESS_WARN(WARN_GIANTOK | WARN_SLEEPOK, NULL, + "usbd_transfer_unsetup can sleep!"); + + while (n_setup--) { + xfer = pxfer[n_setup]; + + if (xfer == NULL) + continue; + + info = xfer->xroot; + + USB_XFER_LOCK(xfer); + USB_BUS_LOCK(info->bus); + + /* + * HINT: when you start/stop a transfer, it might be a + * good idea to directly use the "pxfer[]" structure: + * + * usbd_transfer_start(sc->pxfer[0]); + * usbd_transfer_stop(sc->pxfer[0]); + * + * That way, if your code has many parts that will not + * stop running under the same lock, in other words + * "xfer_mtx", the usbd_transfer_start and + * usbd_transfer_stop functions will simply return + * when they detect a NULL pointer argument. + * + * To avoid any races we clear the "pxfer[]" pointer + * while holding the private mutex of the driver: + */ + pxfer[n_setup] = NULL; + + USB_BUS_UNLOCK(info->bus); + USB_XFER_UNLOCK(xfer); + + usbd_transfer_drain(xfer); + +#if USB_HAVE_BUSDMA + if (xfer->flags_int.bdma_enable) + needs_delay = 1; +#endif + /* + * NOTE: default endpoint does not have an + * interface, even if endpoint->iface_index == 0 + */ + USB_BUS_LOCK(info->bus); + xfer->endpoint->refcount_alloc--; + USB_BUS_UNLOCK(info->bus); + + usb_callout_drain(&xfer->timeout_handle); + + USB_BUS_LOCK(info->bus); + + USB_ASSERT(info->setup_refcount != 0, ("Invalid setup " + "reference count\n")); + + info->setup_refcount--; + + if (info->setup_refcount == 0) { + usbd_transfer_unsetup_sub(info, + needs_delay); + } else { + USB_BUS_UNLOCK(info->bus); + } + } +} + +/*------------------------------------------------------------------------* + * usbd_control_transfer_init - factored out code + * + * In USB Device Mode we have to wait for the SETUP packet which + * containst the "struct usb_device_request" structure, before we can + * transfer any data. In USB Host Mode we already have the SETUP + * packet at the moment the USB transfer is started. This leads us to + * having to setup the USB transfer at two different places in + * time. This function just contains factored out control transfer + * initialisation code, so that we don't duplicate the code. + *------------------------------------------------------------------------*/ +static void +usbd_control_transfer_init(struct usb_xfer *xfer) +{ + struct usb_device_request req; + + /* copy out the USB request header */ + + usbd_copy_out(xfer->frbuffers, 0, &req, sizeof(req)); + + /* setup remainder */ + + xfer->flags_int.control_rem = UGETW(req.wLength); + + /* copy direction to endpoint variable */ + + xfer->endpointno &= ~(UE_DIR_IN | UE_DIR_OUT); + xfer->endpointno |= + (req.bmRequestType & UT_READ) ? UE_DIR_IN : UE_DIR_OUT; +} + +/*------------------------------------------------------------------------* + * usbd_setup_ctrl_transfer + * + * This function handles initialisation of control transfers. Control + * transfers are special in that regard that they can both transmit + * and receive data. + * + * Return values: + * 0: Success + * Else: Failure + *------------------------------------------------------------------------*/ +static int +usbd_setup_ctrl_transfer(struct usb_xfer *xfer) +{ + usb_frlength_t len; + + /* Check for control endpoint stall */ + if (xfer->flags.stall_pipe && xfer->flags_int.control_act) { + /* the control transfer is no longer active */ + xfer->flags_int.control_stall = 1; + xfer->flags_int.control_act = 0; + } else { + /* don't stall control transfer by default */ + xfer->flags_int.control_stall = 0; + } + + /* Check for invalid number of frames */ + if (xfer->nframes > 2) { + /* + * If you need to split a control transfer, you + * have to do one part at a time. Only with + * non-control transfers you can do multiple + * parts a time. + */ + DPRINTFN(0, "Too many frames: %u\n", + (unsigned int)xfer->nframes); + goto error; + } + + /* + * Check if there is a control + * transfer in progress: + */ + if (xfer->flags_int.control_act) { + + if (xfer->flags_int.control_hdr) { + + /* clear send header flag */ + + xfer->flags_int.control_hdr = 0; + + /* setup control transfer */ + if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) { + usbd_control_transfer_init(xfer); + } + } + /* get data length */ + + len = xfer->sumlen; + + } else { + + /* the size of the SETUP structure is hardcoded ! */ + + if (xfer->frlengths[0] != sizeof(struct usb_device_request)) { + DPRINTFN(0, "Wrong framelength %u != %zu\n", + xfer->frlengths[0], sizeof(struct + usb_device_request)); + goto error; + } + /* check USB mode */ + if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) { + + /* check number of frames */ + if (xfer->nframes != 1) { + /* + * We need to receive the setup + * message first so that we know the + * data direction! + */ + DPRINTF("Misconfigured transfer\n"); + goto error; + } + /* + * Set a dummy "control_rem" value. This + * variable will be overwritten later by a + * call to "usbd_control_transfer_init()" ! + */ + xfer->flags_int.control_rem = 0xFFFF; + } else { + + /* setup "endpoint" and "control_rem" */ + + usbd_control_transfer_init(xfer); + } + + /* set transfer-header flag */ + + xfer->flags_int.control_hdr = 1; + + /* get data length */ + + len = (xfer->sumlen - sizeof(struct usb_device_request)); + } + + /* check if there is a length mismatch */ + + if (len > xfer->flags_int.control_rem) { + DPRINTFN(0, "Length (%d) greater than " + "remaining length (%d)\n", len, + xfer->flags_int.control_rem); + goto error; + } + /* check if we are doing a short transfer */ + + if (xfer->flags.force_short_xfer) { + xfer->flags_int.control_rem = 0; + } else { + if ((len != xfer->max_data_length) && + (len != xfer->flags_int.control_rem) && + (xfer->nframes != 1)) { + DPRINTFN(0, "Short control transfer without " + "force_short_xfer set\n"); + goto error; + } + xfer->flags_int.control_rem -= len; + } + + /* the status part is executed when "control_act" is 0 */ + + if ((xfer->flags_int.control_rem > 0) || + (xfer->flags.manual_status)) { + /* don't execute the STATUS stage yet */ + xfer->flags_int.control_act = 1; + + /* sanity check */ + if ((!xfer->flags_int.control_hdr) && + (xfer->nframes == 1)) { + /* + * This is not a valid operation! + */ + DPRINTFN(0, "Invalid parameter " + "combination\n"); + goto error; + } + } else { + /* time to execute the STATUS stage */ + xfer->flags_int.control_act = 0; + } + return (0); /* success */ + +error: + return (1); /* failure */ +} + +/*------------------------------------------------------------------------* + * usbd_transfer_submit - start USB hardware for the given transfer + * + * This function should only be called from the USB callback. + *------------------------------------------------------------------------*/ +void +usbd_transfer_submit(struct usb_xfer *xfer) +{ + struct usb_xfer_root *info; + struct usb_bus *bus; + usb_frcount_t x; + + info = xfer->xroot; + bus = info->bus; + + DPRINTF("xfer=%p, endpoint=%p, nframes=%d, dir=%s\n", + xfer, xfer->endpoint, xfer->nframes, USB_GET_DATA_ISREAD(xfer) ? + "read" : "write"); + +#ifdef USB_DEBUG + if (USB_DEBUG_VAR > 0) { + USB_BUS_LOCK(bus); + + usb_dump_endpoint(xfer->endpoint); + + USB_BUS_UNLOCK(bus); + } +#endif + + USB_XFER_LOCK_ASSERT(xfer, MA_OWNED); + USB_BUS_LOCK_ASSERT(bus, MA_NOTOWNED); + + /* Only open the USB transfer once! */ + if (!xfer->flags_int.open) { + xfer->flags_int.open = 1; + + DPRINTF("open\n"); + + USB_BUS_LOCK(bus); + (xfer->endpoint->methods->open) (xfer); + USB_BUS_UNLOCK(bus); + } + /* set "transferring" flag */ + xfer->flags_int.transferring = 1; + +#if USB_HAVE_POWERD + /* increment power reference */ + usbd_transfer_power_ref(xfer, 1); +#endif + /* + * Check if the transfer is waiting on a queue, most + * frequently the "done_q": + */ + if (xfer->wait_queue) { + USB_BUS_LOCK(bus); + usbd_transfer_dequeue(xfer); + USB_BUS_UNLOCK(bus); + } + /* clear "did_dma_delay" flag */ + xfer->flags_int.did_dma_delay = 0; + + /* clear "did_close" flag */ + xfer->flags_int.did_close = 0; + +#if USB_HAVE_BUSDMA + /* clear "bdma_setup" flag */ + xfer->flags_int.bdma_setup = 0; +#endif + /* by default we cannot cancel any USB transfer immediately */ + xfer->flags_int.can_cancel_immed = 0; + + /* clear lengths and frame counts by default */ + xfer->sumlen = 0; + xfer->actlen = 0; + xfer->aframes = 0; + + /* clear any previous errors */ + xfer->error = 0; + + /* Check if the device is still alive */ + if (info->udev->state < USB_STATE_POWERED) { + USB_BUS_LOCK(bus); + /* + * Must return cancelled error code else + * device drivers can hang. + */ + usbd_transfer_done(xfer, USB_ERR_CANCELLED); + USB_BUS_UNLOCK(bus); + return; + } + + /* sanity check */ + if (xfer->nframes == 0) { + if (xfer->flags.stall_pipe) { + /* + * Special case - want to stall without transferring + * any data: + */ + DPRINTF("xfer=%p nframes=0: stall " + "or clear stall!\n", xfer); + USB_BUS_LOCK(bus); + xfer->flags_int.can_cancel_immed = 1; + /* start the transfer */ + usb_command_wrapper(&xfer->endpoint->endpoint_q, xfer); + USB_BUS_UNLOCK(bus); + return; + } + USB_BUS_LOCK(bus); + usbd_transfer_done(xfer, USB_ERR_INVAL); + USB_BUS_UNLOCK(bus); + return; + } + /* compute total transfer length */ + + for (x = 0; x != xfer->nframes; x++) { + xfer->sumlen += xfer->frlengths[x]; + if (xfer->sumlen < xfer->frlengths[x]) { + /* length wrapped around */ + USB_BUS_LOCK(bus); + usbd_transfer_done(xfer, USB_ERR_INVAL); + USB_BUS_UNLOCK(bus); + return; + } + } + + /* clear some internal flags */ + + xfer->flags_int.short_xfer_ok = 0; + xfer->flags_int.short_frames_ok = 0; + + /* check if this is a control transfer */ + + if (xfer->flags_int.control_xfr) { + + if (usbd_setup_ctrl_transfer(xfer)) { + USB_BUS_LOCK(bus); + usbd_transfer_done(xfer, USB_ERR_STALLED); + USB_BUS_UNLOCK(bus); + return; + } + } + /* + * Setup filtered version of some transfer flags, + * in case of data read direction + */ + if (USB_GET_DATA_ISREAD(xfer)) { + + if (xfer->flags.short_frames_ok) { + xfer->flags_int.short_xfer_ok = 1; + xfer->flags_int.short_frames_ok = 1; + } else if (xfer->flags.short_xfer_ok) { + xfer->flags_int.short_xfer_ok = 1; + + /* check for control transfer */ + if (xfer->flags_int.control_xfr) { + /* + * 1) Control transfers do not support + * reception of multiple short USB + * frames in host mode and device side + * mode, with exception of: + * + * 2) Due to sometimes buggy device + * side firmware we need to do a + * STATUS stage in case of short + * control transfers in USB host mode. + * The STATUS stage then becomes the + * "alt_next" to the DATA stage. + */ + xfer->flags_int.short_frames_ok = 1; + } + } + } + /* + * Check if BUS-DMA support is enabled and try to load virtual + * buffers into DMA, if any: + */ +#if USB_HAVE_BUSDMA + if (xfer->flags_int.bdma_enable) { + /* insert the USB transfer last in the BUS-DMA queue */ + usb_command_wrapper(&xfer->xroot->dma_q, xfer); + return; + } +#endif + /* + * Enter the USB transfer into the Host Controller or + * Device Controller schedule: + */ + usbd_pipe_enter(xfer); +} + +/*------------------------------------------------------------------------* + * usbd_pipe_enter - factored out code + *------------------------------------------------------------------------*/ +void +usbd_pipe_enter(struct usb_xfer *xfer) +{ + struct usb_endpoint *ep; + + USB_XFER_LOCK_ASSERT(xfer, MA_OWNED); + + USB_BUS_LOCK(xfer->xroot->bus); + + ep = xfer->endpoint; + + DPRINTF("enter\n"); + + /* enter the transfer */ + (ep->methods->enter) (xfer); + + xfer->flags_int.can_cancel_immed = 1; + + /* check for transfer error */ + if (xfer->error) { + /* some error has happened */ + usbd_transfer_done(xfer, 0); + USB_BUS_UNLOCK(xfer->xroot->bus); + return; + } + + /* start the transfer */ + usb_command_wrapper(&ep->endpoint_q, xfer); + USB_BUS_UNLOCK(xfer->xroot->bus); +} + +/*------------------------------------------------------------------------* + * usbd_transfer_start - start an USB transfer + * + * NOTE: Calling this function more than one time will only + * result in a single transfer start, until the USB transfer + * completes. + *------------------------------------------------------------------------*/ +void +usbd_transfer_start(struct usb_xfer *xfer) +{ + if (xfer == NULL) { + /* transfer is gone */ + return; + } + USB_XFER_LOCK_ASSERT(xfer, MA_OWNED); + + /* mark the USB transfer started */ + + if (!xfer->flags_int.started) { + /* lock the BUS lock to avoid races updating flags_int */ + USB_BUS_LOCK(xfer->xroot->bus); + xfer->flags_int.started = 1; + USB_BUS_UNLOCK(xfer->xroot->bus); + } + /* check if the USB transfer callback is already transferring */ + + if (xfer->flags_int.transferring) { + return; + } + USB_BUS_LOCK(xfer->xroot->bus); + /* call the USB transfer callback */ + usbd_callback_ss_done_defer(xfer); + USB_BUS_UNLOCK(xfer->xroot->bus); +} + +/*------------------------------------------------------------------------* + * usbd_transfer_stop - stop an USB transfer + * + * NOTE: Calling this function more than one time will only + * result in a single transfer stop. + * NOTE: When this function returns it is not safe to free nor + * reuse any DMA buffers. See "usbd_transfer_drain()". + *------------------------------------------------------------------------*/ +void +usbd_transfer_stop(struct usb_xfer *xfer) +{ + struct usb_endpoint *ep; + + if (xfer == NULL) { + /* transfer is gone */ + return; + } + USB_XFER_LOCK_ASSERT(xfer, MA_OWNED); + + /* check if the USB transfer was ever opened */ + + if (!xfer->flags_int.open) { + if (xfer->flags_int.started) { + /* nothing to do except clearing the "started" flag */ + /* lock the BUS lock to avoid races updating flags_int */ + USB_BUS_LOCK(xfer->xroot->bus); + xfer->flags_int.started = 0; + USB_BUS_UNLOCK(xfer->xroot->bus); + } + return; + } + /* try to stop the current USB transfer */ + + USB_BUS_LOCK(xfer->xroot->bus); + /* override any previous error */ + xfer->error = USB_ERR_CANCELLED; + + /* + * Clear "open" and "started" when both private and USB lock + * is locked so that we don't get a race updating "flags_int" + */ + xfer->flags_int.open = 0; + xfer->flags_int.started = 0; + + /* + * Check if we can cancel the USB transfer immediately. + */ + if (xfer->flags_int.transferring) { + if (xfer->flags_int.can_cancel_immed && + (!xfer->flags_int.did_close)) { + DPRINTF("close\n"); + /* + * The following will lead to an USB_ERR_CANCELLED + * error code being passed to the USB callback. + */ + (xfer->endpoint->methods->close) (xfer); + /* only close once */ + xfer->flags_int.did_close = 1; + } else { + /* need to wait for the next done callback */ + } + } else { + DPRINTF("close\n"); + + /* close here and now */ + (xfer->endpoint->methods->close) (xfer); + + /* + * Any additional DMA delay is done by + * "usbd_transfer_unsetup()". + */ + + /* + * Special case. Check if we need to restart a blocked + * endpoint. + */ + ep = xfer->endpoint; + + /* + * If the current USB transfer is completing we need + * to start the next one: + */ + if (ep->endpoint_q.curr == xfer) { + usb_command_wrapper(&ep->endpoint_q, NULL); + } + } + + USB_BUS_UNLOCK(xfer->xroot->bus); +} + +/*------------------------------------------------------------------------* + * usbd_transfer_pending + * + * This function will check if an USB transfer is pending which is a + * little bit complicated! + * Return values: + * 0: Not pending + * 1: Pending: The USB transfer will receive a callback in the future. + *------------------------------------------------------------------------*/ +uint8_t +usbd_transfer_pending(struct usb_xfer *xfer) +{ + struct usb_xfer_root *info; + struct usb_xfer_queue *pq; + + if (xfer == NULL) { + /* transfer is gone */ + return (0); + } + USB_XFER_LOCK_ASSERT(xfer, MA_OWNED); + + if (xfer->flags_int.transferring) { + /* trivial case */ + return (1); + } + USB_BUS_LOCK(xfer->xroot->bus); + if (xfer->wait_queue) { + /* we are waiting on a queue somewhere */ + USB_BUS_UNLOCK(xfer->xroot->bus); + return (1); + } + info = xfer->xroot; + pq = &info->done_q; + + if (pq->curr == xfer) { + /* we are currently scheduled for callback */ + USB_BUS_UNLOCK(xfer->xroot->bus); + return (1); + } + /* we are not pending */ + USB_BUS_UNLOCK(xfer->xroot->bus); + return (0); +} + +/*------------------------------------------------------------------------* + * usbd_transfer_drain + * + * This function will stop the USB transfer and wait for any + * additional BUS-DMA and HW-DMA operations to complete. Buffers that + * are loaded into DMA can safely be freed or reused after that this + * function has returned. + *------------------------------------------------------------------------*/ +void +usbd_transfer_drain(struct usb_xfer *xfer) +{ + WITNESS_WARN(WARN_GIANTOK | WARN_SLEEPOK, NULL, + "usbd_transfer_drain can sleep!"); + + if (xfer == NULL) { + /* transfer is gone */ + return; + } + if (xfer->xroot->xfer_mtx != &Giant) { + USB_XFER_LOCK_ASSERT(xfer, MA_NOTOWNED); + } + USB_XFER_LOCK(xfer); + + usbd_transfer_stop(xfer); + + while (usbd_transfer_pending(xfer) || + xfer->flags_int.doing_callback) { + + /* + * It is allowed that the callback can drop its + * transfer mutex. In that case checking only + * "usbd_transfer_pending()" is not enough to tell if + * the USB transfer is fully drained. We also need to + * check the internal "doing_callback" flag. + */ + xfer->flags_int.draining = 1; + + /* + * Wait until the current outstanding USB + * transfer is complete ! + */ + cv_wait(&xfer->xroot->cv_drain, xfer->xroot->xfer_mtx); + } + USB_XFER_UNLOCK(xfer); +} + +struct usb_page_cache * +usbd_xfer_get_frame(struct usb_xfer *xfer, usb_frcount_t frindex) +{ + KASSERT(frindex < xfer->max_frame_count, ("frame index overflow")); + + return (&xfer->frbuffers[frindex]); +} + +/*------------------------------------------------------------------------* + * usbd_xfer_get_fps_shift + * + * The following function is only useful for isochronous transfers. It + * returns how many times the frame execution rate has been shifted + * down. + * + * Return value: + * Success: 0..3 + * Failure: 0 + *------------------------------------------------------------------------*/ +uint8_t +usbd_xfer_get_fps_shift(struct usb_xfer *xfer) +{ + return (xfer->fps_shift); +} + +usb_frlength_t +usbd_xfer_frame_len(struct usb_xfer *xfer, usb_frcount_t frindex) +{ + KASSERT(frindex < xfer->max_frame_count, ("frame index overflow")); + + return (xfer->frlengths[frindex]); +} + +/*------------------------------------------------------------------------* + * usbd_xfer_set_frame_data + * + * This function sets the pointer of the buffer that should + * loaded directly into DMA for the given USB frame. Passing "ptr" + * equal to NULL while the corresponding "frlength" is greater + * than zero gives undefined results! + *------------------------------------------------------------------------*/ +void +usbd_xfer_set_frame_data(struct usb_xfer *xfer, usb_frcount_t frindex, + void *ptr, usb_frlength_t len) +{ + KASSERT(frindex < xfer->max_frame_count, ("frame index overflow")); + + /* set virtual address to load and length */ + xfer->frbuffers[frindex].buffer = ptr; + usbd_xfer_set_frame_len(xfer, frindex, len); +} + +void +usbd_xfer_frame_data(struct usb_xfer *xfer, usb_frcount_t frindex, + void **ptr, int *len) +{ + KASSERT(frindex < xfer->max_frame_count, ("frame index overflow")); + + if (ptr != NULL) + *ptr = xfer->frbuffers[frindex].buffer; + if (len != NULL) + *len = xfer->frlengths[frindex]; +} + +void +usbd_xfer_status(struct usb_xfer *xfer, int *actlen, int *sumlen, int *aframes, + int *nframes) +{ + if (actlen != NULL) + *actlen = xfer->actlen; + if (sumlen != NULL) + *sumlen = xfer->sumlen; + if (aframes != NULL) + *aframes = xfer->aframes; + if (nframes != NULL) + *nframes = xfer->nframes; +} + +/*------------------------------------------------------------------------* + * usbd_xfer_set_frame_offset + * + * This function sets the frame data buffer offset relative to the beginning + * of the USB DMA buffer allocated for this USB transfer. + *------------------------------------------------------------------------*/ +void +usbd_xfer_set_frame_offset(struct usb_xfer *xfer, usb_frlength_t offset, + usb_frcount_t frindex) +{ + KASSERT(!xfer->flags.ext_buffer, ("Cannot offset data frame " + "when the USB buffer is external\n")); + KASSERT(frindex < xfer->max_frame_count, ("frame index overflow")); + + /* set virtual address to load */ + xfer->frbuffers[frindex].buffer = + USB_ADD_BYTES(xfer->local_buffer, offset); +} + +void +usbd_xfer_set_interval(struct usb_xfer *xfer, int i) +{ + xfer->interval = i; +} + +void +usbd_xfer_set_timeout(struct usb_xfer *xfer, int t) +{ + xfer->timeout = t; +} + +void +usbd_xfer_set_frames(struct usb_xfer *xfer, usb_frcount_t n) +{ + xfer->nframes = n; +} + +usb_frcount_t +usbd_xfer_max_frames(struct usb_xfer *xfer) +{ + return (xfer->max_frame_count); +} + +usb_frlength_t +usbd_xfer_max_len(struct usb_xfer *xfer) +{ + return (xfer->max_data_length); +} + +usb_frlength_t +usbd_xfer_max_framelen(struct usb_xfer *xfer) +{ + return (xfer->max_frame_size); +} + +void +usbd_xfer_set_frame_len(struct usb_xfer *xfer, usb_frcount_t frindex, + usb_frlength_t len) +{ + KASSERT(frindex < xfer->max_frame_count, ("frame index overflow")); + + xfer->frlengths[frindex] = len; +} + +/*------------------------------------------------------------------------* + * usb_callback_proc - factored out code + * + * This function performs USB callbacks. + *------------------------------------------------------------------------*/ +static void +usb_callback_proc(struct usb_proc_msg *_pm) +{ + struct usb_done_msg *pm = (void *)_pm; + struct usb_xfer_root *info = pm->xroot; + + /* Change locking order */ + USB_BUS_UNLOCK(info->bus); + + /* + * We exploit the fact that the mutex is the same for all + * callbacks that will be called from this thread: + */ + mtx_lock(info->xfer_mtx); + USB_BUS_LOCK(info->bus); + + /* Continue where we lost track */ + usb_command_wrapper(&info->done_q, + info->done_q.curr); + + mtx_unlock(info->xfer_mtx); +} + +/*------------------------------------------------------------------------* + * usbd_callback_ss_done_defer + * + * This function will defer the start, stop and done callback to the + * correct thread. + *------------------------------------------------------------------------*/ +static void +usbd_callback_ss_done_defer(struct usb_xfer *xfer) +{ + struct usb_xfer_root *info = xfer->xroot; + struct usb_xfer_queue *pq = &info->done_q; + + USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); + + if (pq->curr != xfer) { + usbd_transfer_enqueue(pq, xfer); + } + if (!pq->recurse_1) { + + /* + * We have to postpone the callback due to the fact we + * will have a Lock Order Reversal, LOR, if we try to + * proceed ! + */ + if (usb_proc_msignal(info->done_p, + &info->done_m[0], &info->done_m[1])) { + /* ignore */ + } + } else { + /* clear second recurse flag */ + pq->recurse_2 = 0; + } + return; + +} + +/*------------------------------------------------------------------------* + * usbd_callback_wrapper + * + * This is a wrapper for USB callbacks. This wrapper does some + * auto-magic things like figuring out if we can call the callback + * directly from the current context or if we need to wakeup the + * interrupt process. + *------------------------------------------------------------------------*/ +static void +usbd_callback_wrapper(struct usb_xfer_queue *pq) +{ + struct usb_xfer *xfer = pq->curr; + struct usb_xfer_root *info = xfer->xroot; + + USB_BUS_LOCK_ASSERT(info->bus, MA_OWNED); + if (!mtx_owned(info->xfer_mtx)) { + /* + * Cases that end up here: + * + * 5) HW interrupt done callback or other source. + */ + DPRINTFN(3, "case 5\n"); + + /* + * We have to postpone the callback due to the fact we + * will have a Lock Order Reversal, LOR, if we try to + * proceed ! + */ + if (usb_proc_msignal(info->done_p, + &info->done_m[0], &info->done_m[1])) { + /* ignore */ + } + return; + } + /* + * Cases that end up here: + * + * 1) We are starting a transfer + * 2) We are prematurely calling back a transfer + * 3) We are stopping a transfer + * 4) We are doing an ordinary callback + */ + DPRINTFN(3, "case 1-4\n"); + /* get next USB transfer in the queue */ + info->done_q.curr = NULL; + + /* set flag in case of drain */ + xfer->flags_int.doing_callback = 1; + + USB_BUS_UNLOCK(info->bus); + USB_BUS_LOCK_ASSERT(info->bus, MA_NOTOWNED); + + /* set correct USB state for callback */ + if (!xfer->flags_int.transferring) { + xfer->usb_state = USB_ST_SETUP; + if (!xfer->flags_int.started) { + /* we got stopped before we even got started */ + USB_BUS_LOCK(info->bus); + goto done; + } + } else { + + if (usbd_callback_wrapper_sub(xfer)) { + /* the callback has been deferred */ + USB_BUS_LOCK(info->bus); + goto done; + } +#if USB_HAVE_POWERD + /* decrement power reference */ + usbd_transfer_power_ref(xfer, -1); +#endif + xfer->flags_int.transferring = 0; + + if (xfer->error) { + xfer->usb_state = USB_ST_ERROR; + } else { + /* set transferred state */ + xfer->usb_state = USB_ST_TRANSFERRED; +#if USB_HAVE_BUSDMA + /* sync DMA memory, if any */ + if (xfer->flags_int.bdma_enable && + (!xfer->flags_int.bdma_no_post_sync)) { + usb_bdma_post_sync(xfer); + } +#endif + } + } + + /* call processing routine */ + (xfer->callback) (xfer, xfer->error); + + /* pickup the USB mutex again */ + USB_BUS_LOCK(info->bus); + + /* + * Check if we got started after that we got cancelled, but + * before we managed to do the callback. + */ + if ((!xfer->flags_int.open) && + (xfer->flags_int.started) && + (xfer->usb_state == USB_ST_ERROR)) { + /* clear flag in case of drain */ + xfer->flags_int.doing_callback = 0; + /* try to loop, but not recursivly */ + usb_command_wrapper(&info->done_q, xfer); + return; + } + +done: + /* clear flag in case of drain */ + xfer->flags_int.doing_callback = 0; + + /* + * Check if we are draining. + */ + if (xfer->flags_int.draining && + (!xfer->flags_int.transferring)) { + /* "usbd_transfer_drain()" is waiting for end of transfer */ + xfer->flags_int.draining = 0; + cv_broadcast(&info->cv_drain); + } + + /* do the next callback, if any */ + usb_command_wrapper(&info->done_q, + info->done_q.curr); +} + +/*------------------------------------------------------------------------* + * usb_dma_delay_done_cb + * + * This function is called when the DMA delay has been exectuded, and + * will make sure that the callback is called to complete the USB + * transfer. This code path is ususally only used when there is an USB + * error like USB_ERR_CANCELLED. + *------------------------------------------------------------------------*/ +void +usb_dma_delay_done_cb(struct usb_xfer *xfer) +{ + USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); + + DPRINTFN(3, "Completed %p\n", xfer); + + /* queue callback for execution, again */ + usbd_transfer_done(xfer, 0); +} + +/*------------------------------------------------------------------------* + * usbd_transfer_dequeue + * + * - This function is used to remove an USB transfer from a USB + * transfer queue. + * + * - This function can be called multiple times in a row. + *------------------------------------------------------------------------*/ +void +usbd_transfer_dequeue(struct usb_xfer *xfer) +{ + struct usb_xfer_queue *pq; + + pq = xfer->wait_queue; + if (pq) { + TAILQ_REMOVE(&pq->head, xfer, wait_entry); + xfer->wait_queue = NULL; + } +} + +/*------------------------------------------------------------------------* + * usbd_transfer_enqueue + * + * - This function is used to insert an USB transfer into a USB * + * transfer queue. + * + * - This function can be called multiple times in a row. + *------------------------------------------------------------------------*/ +void +usbd_transfer_enqueue(struct usb_xfer_queue *pq, struct usb_xfer *xfer) +{ + /* + * Insert the USB transfer into the queue, if it is not + * already on a USB transfer queue: + */ + if (xfer->wait_queue == NULL) { + xfer->wait_queue = pq; + TAILQ_INSERT_TAIL(&pq->head, xfer, wait_entry); + } +} + +/*------------------------------------------------------------------------* + * usbd_transfer_done + * + * - This function is used to remove an USB transfer from the busdma, + * pipe or interrupt queue. + * + * - This function is used to queue the USB transfer on the done + * queue. + * + * - This function is used to stop any USB transfer timeouts. + *------------------------------------------------------------------------*/ +void +usbd_transfer_done(struct usb_xfer *xfer, usb_error_t error) +{ + USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); + + DPRINTF("err=%s\n", usbd_errstr(error)); + + /* + * If we are not transferring then just return. + * This can happen during transfer cancel. + */ + if (!xfer->flags_int.transferring) { + DPRINTF("not transferring\n"); + /* end of control transfer, if any */ + xfer->flags_int.control_act = 0; + return; + } + /* only set transfer error if not already set */ + if (!xfer->error) { + xfer->error = error; + } + /* stop any callouts */ + usb_callout_stop(&xfer->timeout_handle); + + /* + * If we are waiting on a queue, just remove the USB transfer + * from the queue, if any. We should have the required locks + * locked to do the remove when this function is called. + */ + usbd_transfer_dequeue(xfer); + +#if USB_HAVE_BUSDMA + if (mtx_owned(xfer->xroot->xfer_mtx)) { + struct usb_xfer_queue *pq; + + /* + * If the private USB lock is not locked, then we assume + * that the BUS-DMA load stage has been passed: + */ + pq = &xfer->xroot->dma_q; + + if (pq->curr == xfer) { + /* start the next BUS-DMA load, if any */ + usb_command_wrapper(pq, NULL); + } + } +#endif + /* keep some statistics */ + if (xfer->error) { + xfer->xroot->bus->stats_err.uds_requests + [xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE]++; + } else { + xfer->xroot->bus->stats_ok.uds_requests + [xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE]++; + } + + /* call the USB transfer callback */ + usbd_callback_ss_done_defer(xfer); +} + +/*------------------------------------------------------------------------* + * usbd_transfer_start_cb + * + * This function is called to start the USB transfer when + * "xfer->interval" is greater than zero, and and the endpoint type is + * BULK or CONTROL. + *------------------------------------------------------------------------*/ +static void +usbd_transfer_start_cb(void *arg) +{ + struct usb_xfer *xfer = arg; + struct usb_endpoint *ep = xfer->endpoint; + + USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); + + DPRINTF("start\n"); + + /* start the transfer */ + (ep->methods->start) (xfer); + + xfer->flags_int.can_cancel_immed = 1; + + /* check for error */ + if (xfer->error) { + /* some error has happened */ + usbd_transfer_done(xfer, 0); + } +} + +/*------------------------------------------------------------------------* + * usbd_xfer_set_stall + * + * This function is used to set the stall flag outside the + * callback. This function is NULL safe. + *------------------------------------------------------------------------*/ +void +usbd_xfer_set_stall(struct usb_xfer *xfer) +{ + if (xfer == NULL) { + /* tearing down */ + return; + } + USB_XFER_LOCK_ASSERT(xfer, MA_OWNED); + + /* avoid any races by locking the USB mutex */ + USB_BUS_LOCK(xfer->xroot->bus); + xfer->flags.stall_pipe = 1; + USB_BUS_UNLOCK(xfer->xroot->bus); +} + +int +usbd_xfer_is_stalled(struct usb_xfer *xfer) +{ + return (xfer->endpoint->is_stalled); +} + +/*------------------------------------------------------------------------* + * usbd_transfer_clear_stall + * + * This function is used to clear the stall flag outside the + * callback. This function is NULL safe. + *------------------------------------------------------------------------*/ +void +usbd_transfer_clear_stall(struct usb_xfer *xfer) +{ + if (xfer == NULL) { + /* tearing down */ + return; + } + USB_XFER_LOCK_ASSERT(xfer, MA_OWNED); + + /* avoid any races by locking the USB mutex */ + USB_BUS_LOCK(xfer->xroot->bus); + + xfer->flags.stall_pipe = 0; + + USB_BUS_UNLOCK(xfer->xroot->bus); +} + +/*------------------------------------------------------------------------* + * usbd_pipe_start + * + * This function is used to add an USB transfer to the pipe transfer list. + *------------------------------------------------------------------------*/ +void +usbd_pipe_start(struct usb_xfer_queue *pq) +{ + struct usb_endpoint *ep; + struct usb_xfer *xfer; + uint8_t type; + + xfer = pq->curr; + ep = xfer->endpoint; + + USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); + + /* + * If the endpoint is already stalled we do nothing ! + */ + if (ep->is_stalled) { + return; + } + /* + * Check if we are supposed to stall the endpoint: + */ + if (xfer->flags.stall_pipe) { + struct usb_device *udev; + struct usb_xfer_root *info; + + /* clear stall command */ + xfer->flags.stall_pipe = 0; + + /* get pointer to USB device */ + info = xfer->xroot; + udev = info->udev; + + /* + * Only stall BULK and INTERRUPT endpoints. + */ + type = (ep->edesc->bmAttributes & UE_XFERTYPE); + if ((type == UE_BULK) || + (type == UE_INTERRUPT)) { + uint8_t did_stall; + + did_stall = 1; + + if (udev->flags.usb_mode == USB_MODE_DEVICE) { + (udev->bus->methods->set_stall) ( + udev, NULL, ep, &did_stall); + } else if (udev->ctrl_xfer[1]) { + info = udev->ctrl_xfer[1]->xroot; + usb_proc_msignal( + &info->bus->non_giant_callback_proc, + &udev->cs_msg[0], &udev->cs_msg[1]); + } else { + /* should not happen */ + DPRINTFN(0, "No stall handler\n"); + } + /* + * Check if we should stall. Some USB hardware + * handles set- and clear-stall in hardware. + */ + if (did_stall) { + /* + * The transfer will be continued when + * the clear-stall control endpoint + * message is received. + */ + ep->is_stalled = 1; + return; + } + } else if (type == UE_ISOCHRONOUS) { + + /* + * Make sure any FIFO overflow or other FIFO + * error conditions go away by resetting the + * endpoint FIFO through the clear stall + * method. + */ + if (udev->flags.usb_mode == USB_MODE_DEVICE) { + (udev->bus->methods->clear_stall) (udev, ep); + } + } + } + /* Set or clear stall complete - special case */ + if (xfer->nframes == 0) { + /* we are complete */ + xfer->aframes = 0; + usbd_transfer_done(xfer, 0); + return; + } + /* + * Handled cases: + * + * 1) Start the first transfer queued. + * + * 2) Re-start the current USB transfer. + */ + /* + * Check if there should be any + * pre transfer start delay: + */ + if (xfer->interval > 0) { + type = (ep->edesc->bmAttributes & UE_XFERTYPE); + if ((type == UE_BULK) || + (type == UE_CONTROL)) { + usbd_transfer_timeout_ms(xfer, + &usbd_transfer_start_cb, + xfer->interval); + return; + } + } + DPRINTF("start\n"); + + /* start USB transfer */ + (ep->methods->start) (xfer); + + xfer->flags_int.can_cancel_immed = 1; + + /* check for error */ + if (xfer->error) { + /* some error has happened */ + usbd_transfer_done(xfer, 0); + } +} + +/*------------------------------------------------------------------------* + * usbd_transfer_timeout_ms + * + * This function is used to setup a timeout on the given USB + * transfer. If the timeout has been deferred the callback given by + * "cb" will get called after "ms" milliseconds. + *------------------------------------------------------------------------*/ +void +usbd_transfer_timeout_ms(struct usb_xfer *xfer, + void (*cb) (void *arg), usb_timeout_t ms) +{ + USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED); + + /* defer delay */ + usb_callout_reset(&xfer->timeout_handle, + USB_MS_TO_TICKS(ms), cb, xfer); +} + +/*------------------------------------------------------------------------* + * usbd_callback_wrapper_sub + * + * - This function will update variables in an USB transfer after + * that the USB transfer is complete. + * + * - This function is used to start the next USB transfer on the + * ep transfer queue, if any. + * + * NOTE: In some special cases the USB transfer will not be removed from + * the pipe queue, but remain first. To enforce USB transfer removal call + * this function passing the error code "USB_ERR_CANCELLED". + * + * Return values: + * 0: Success. + * Else: The callback has been deferred. + *------------------------------------------------------------------------*/ +static uint8_t +usbd_callback_wrapper_sub(struct usb_xfer *xfer) +{ + struct usb_endpoint *ep; + struct usb_bus *bus; + usb_frcount_t x; + + bus = xfer->xroot->bus; + + if ((!xfer->flags_int.open) && + (!xfer->flags_int.did_close)) { + DPRINTF("close\n"); + USB_BUS_LOCK(bus); + (xfer->endpoint->methods->close) (xfer); + USB_BUS_UNLOCK(bus); + /* only close once */ + xfer->flags_int.did_close = 1; + return (1); /* wait for new callback */ + } + /* + * If we have a non-hardware induced error we + * need to do the DMA delay! + */ + if (xfer->error != 0 && !xfer->flags_int.did_dma_delay && + (xfer->error == USB_ERR_CANCELLED || + xfer->error == USB_ERR_TIMEOUT || + bus->methods->start_dma_delay != NULL)) { + + usb_timeout_t temp; + + /* only delay once */ + xfer->flags_int.did_dma_delay = 1; + + /* we can not cancel this delay */ + xfer->flags_int.can_cancel_immed = 0; + + temp = usbd_get_dma_delay(xfer->xroot->udev); + + DPRINTFN(3, "DMA delay, %u ms, " + "on %p\n", temp, xfer); + + if (temp != 0) { + USB_BUS_LOCK(bus); + /* + * Some hardware solutions have dedicated + * events when it is safe to free DMA'ed + * memory. For the other hardware platforms we + * use a static delay. + */ + if (bus->methods->start_dma_delay != NULL) { + (bus->methods->start_dma_delay) (xfer); + } else { + usbd_transfer_timeout_ms(xfer, + (void *)&usb_dma_delay_done_cb, temp); + } + USB_BUS_UNLOCK(bus); + return (1); /* wait for new callback */ + } + } + /* check actual number of frames */ + if (xfer->aframes > xfer->nframes) { + if (xfer->error == 0) { + panic("%s: actual number of frames, %d, is " + "greater than initial number of frames, %d\n", + __FUNCTION__, xfer->aframes, xfer->nframes); + } else { + /* just set some valid value */ + xfer->aframes = xfer->nframes; + } + } + /* compute actual length */ + xfer->actlen = 0; + + for (x = 0; x != xfer->aframes; x++) { + xfer->actlen += xfer->frlengths[x]; + } + + /* + * Frames that were not transferred get zero actual length in + * case the USB device driver does not check the actual number + * of frames transferred, "xfer->aframes": + */ + for (; x < xfer->nframes; x++) { + usbd_xfer_set_frame_len(xfer, x, 0); + } + + /* check actual length */ + if (xfer->actlen > xfer->sumlen) { + if (xfer->error == 0) { + panic("%s: actual length, %d, is greater than " + "initial length, %d\n", + __FUNCTION__, xfer->actlen, xfer->sumlen); + } else { + /* just set some valid value */ + xfer->actlen = xfer->sumlen; + } + } + DPRINTFN(1, "xfer=%p endpoint=%p sts=%d alen=%d, slen=%d, afrm=%d, nfrm=%d\n", + xfer, xfer->endpoint, xfer->error, xfer->actlen, xfer->sumlen, + xfer->aframes, xfer->nframes); + + if (xfer->error) { + /* end of control transfer, if any */ + xfer->flags_int.control_act = 0; + + /* check if we should block the execution queue */ + if ((xfer->error != USB_ERR_CANCELLED) && + (xfer->flags.pipe_bof)) { + DPRINTFN(2, "xfer=%p: Block On Failure " + "on endpoint=%p\n", xfer, xfer->endpoint); + goto done; + } + } else { + /* check for short transfers */ + if (xfer->actlen < xfer->sumlen) { + + /* end of control transfer, if any */ + xfer->flags_int.control_act = 0; + + if (!xfer->flags_int.short_xfer_ok) { + xfer->error = USB_ERR_SHORT_XFER; + if (xfer->flags.pipe_bof) { + DPRINTFN(2, "xfer=%p: Block On Failure on " + "Short Transfer on endpoint %p.\n", + xfer, xfer->endpoint); + goto done; + } + } + } else { + /* + * Check if we are in the middle of a + * control transfer: + */ + if (xfer->flags_int.control_act) { + DPRINTFN(5, "xfer=%p: Control transfer " + "active on endpoint=%p\n", xfer, xfer->endpoint); + goto done; + } + } + } + + ep = xfer->endpoint; + + /* + * If the current USB transfer is completing we need to start the + * next one: + */ + USB_BUS_LOCK(bus); + if (ep->endpoint_q.curr == xfer) { + usb_command_wrapper(&ep->endpoint_q, NULL); + + if (ep->endpoint_q.curr || TAILQ_FIRST(&ep->endpoint_q.head)) { + /* there is another USB transfer waiting */ + } else { + /* this is the last USB transfer */ + /* clear isochronous sync flag */ + xfer->endpoint->is_synced = 0; + } + } + USB_BUS_UNLOCK(bus); +done: + return (0); +} + +/*------------------------------------------------------------------------* + * usb_command_wrapper + * + * This function is used to execute commands non-recursivly on an USB + * transfer. + *------------------------------------------------------------------------*/ +void +usb_command_wrapper(struct usb_xfer_queue *pq, struct usb_xfer *xfer) +{ + if (xfer) { + /* + * If the transfer is not already processing, + * queue it! + */ + if (pq->curr != xfer) { + usbd_transfer_enqueue(pq, xfer); + if (pq->curr != NULL) { + /* something is already processing */ + DPRINTFN(6, "busy %p\n", pq->curr); + return; + } + } + } else { + /* Get next element in queue */ + pq->curr = NULL; + } + + if (!pq->recurse_1) { + + do { + + /* set both recurse flags */ + pq->recurse_1 = 1; + pq->recurse_2 = 1; + + if (pq->curr == NULL) { + xfer = TAILQ_FIRST(&pq->head); + if (xfer) { + TAILQ_REMOVE(&pq->head, xfer, + wait_entry); + xfer->wait_queue = NULL; + pq->curr = xfer; + } else { + break; + } + } + DPRINTFN(6, "cb %p (enter)\n", pq->curr); + (pq->command) (pq); + DPRINTFN(6, "cb %p (leave)\n", pq->curr); + + } while (!pq->recurse_2); + + /* clear first recurse flag */ + pq->recurse_1 = 0; + + } else { + /* clear second recurse flag */ + pq->recurse_2 = 0; + } +} + +/*------------------------------------------------------------------------* + * usbd_ctrl_transfer_setup + * + * This function is used to setup the default USB control endpoint + * transfer. + *------------------------------------------------------------------------*/ +void +usbd_ctrl_transfer_setup(struct usb_device *udev) +{ + struct usb_xfer *xfer; + uint8_t no_resetup; + uint8_t iface_index; + + /* check for root HUB */ + if (udev->parent_hub == NULL) + return; +repeat: + + xfer = udev->ctrl_xfer[0]; + if (xfer) { + USB_XFER_LOCK(xfer); + no_resetup = + ((xfer->address == udev->address) && + (udev->ctrl_ep_desc.wMaxPacketSize[0] == + udev->ddesc.bMaxPacketSize)); + if (udev->flags.usb_mode == USB_MODE_DEVICE) { + if (no_resetup) { + /* + * NOTE: checking "xfer->address" and + * starting the USB transfer must be + * atomic! + */ + usbd_transfer_start(xfer); + } + } + USB_XFER_UNLOCK(xfer); + } else { + no_resetup = 0; + } + + if (no_resetup) { + /* + * All parameters are exactly the same like before. + * Just return. + */ + return; + } + /* + * Update wMaxPacketSize for the default control endpoint: + */ + udev->ctrl_ep_desc.wMaxPacketSize[0] = + udev->ddesc.bMaxPacketSize; + + /* + * Unsetup any existing USB transfer: + */ + usbd_transfer_unsetup(udev->ctrl_xfer, USB_CTRL_XFER_MAX); + + /* + * Try to setup a new USB transfer for the + * default control endpoint: + */ + iface_index = 0; + if (usbd_transfer_setup(udev, &iface_index, + udev->ctrl_xfer, usb_control_ep_cfg, USB_CTRL_XFER_MAX, NULL, + &udev->device_mtx)) { + DPRINTFN(0, "could not setup default " + "USB transfer\n"); + } else { + goto repeat; + } +} + +/*------------------------------------------------------------------------* + * usbd_clear_data_toggle - factored out code + * + * NOTE: the intention of this function is not to reset the hardware + * data toggle. + *------------------------------------------------------------------------*/ +void +usbd_clear_stall_locked(struct usb_device *udev, struct usb_endpoint *ep) +{ + USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED); + + /* check that we have a valid case */ + if (udev->flags.usb_mode == USB_MODE_HOST && + udev->parent_hub != NULL && + udev->bus->methods->clear_stall != NULL && + ep->methods != NULL) { + (udev->bus->methods->clear_stall) (udev, ep); + } +} + +/*------------------------------------------------------------------------* + * usbd_clear_data_toggle - factored out code + * + * NOTE: the intention of this function is not to reset the hardware + * data toggle on the USB device side. + *------------------------------------------------------------------------*/ +void +usbd_clear_data_toggle(struct usb_device *udev, struct usb_endpoint *ep) +{ + DPRINTFN(5, "udev=%p endpoint=%p\n", udev, ep); + + USB_BUS_LOCK(udev->bus); + ep->toggle_next = 0; + /* some hardware needs a callback to clear the data toggle */ + usbd_clear_stall_locked(udev, ep); + USB_BUS_UNLOCK(udev->bus); +} + +/*------------------------------------------------------------------------* + * usbd_clear_stall_callback - factored out clear stall callback + * + * Input parameters: + * xfer1: Clear Stall Control Transfer + * xfer2: Stalled USB Transfer + * + * This function is NULL safe. + * + * Return values: + * 0: In progress + * Else: Finished + * + * Clear stall config example: + * + * static const struct usb_config my_clearstall = { + * .type = UE_CONTROL, + * .endpoint = 0, + * .direction = UE_DIR_ANY, + * .interval = 50, //50 milliseconds + * .bufsize = sizeof(struct usb_device_request), + * .timeout = 1000, //1.000 seconds + * .callback = &my_clear_stall_callback, // ** + * .usb_mode = USB_MODE_HOST, + * }; + * + * ** "my_clear_stall_callback" calls "usbd_clear_stall_callback" + * passing the correct parameters. + *------------------------------------------------------------------------*/ +uint8_t +usbd_clear_stall_callback(struct usb_xfer *xfer1, + struct usb_xfer *xfer2) +{ + struct usb_device_request req; + + if (xfer2 == NULL) { + /* looks like we are tearing down */ + DPRINTF("NULL input parameter\n"); + return (0); + } + USB_XFER_LOCK_ASSERT(xfer1, MA_OWNED); + USB_XFER_LOCK_ASSERT(xfer2, MA_OWNED); + + switch (USB_GET_STATE(xfer1)) { + case USB_ST_SETUP: + + /* + * pre-clear the data toggle to DATA0 ("umass.c" and + * "ata-usb.c" depends on this) + */ + + usbd_clear_data_toggle(xfer2->xroot->udev, xfer2->endpoint); + + /* setup a clear-stall packet */ + + req.bmRequestType = UT_WRITE_ENDPOINT; + req.bRequest = UR_CLEAR_FEATURE; + USETW(req.wValue, UF_ENDPOINT_HALT); + req.wIndex[0] = xfer2->endpoint->edesc->bEndpointAddress; + req.wIndex[1] = 0; + USETW(req.wLength, 0); + + /* + * "usbd_transfer_setup_sub()" will ensure that + * we have sufficient room in the buffer for + * the request structure! + */ + + /* copy in the transfer */ + + usbd_copy_in(xfer1->frbuffers, 0, &req, sizeof(req)); + + /* set length */ + xfer1->frlengths[0] = sizeof(req); + xfer1->nframes = 1; + + usbd_transfer_submit(xfer1); + return (0); + + case USB_ST_TRANSFERRED: + break; + + default: /* Error */ + if (xfer1->error == USB_ERR_CANCELLED) { + return (0); + } + break; + } + return (1); /* Clear Stall Finished */ +} + +/*------------------------------------------------------------------------* + * usbd_transfer_poll + * + * The following function gets called from the USB keyboard driver and + * UMASS when the system has paniced. + * + * NOTE: It is currently not possible to resume normal operation on + * the USB controller which has been polled, due to clearing of the + * "up_dsleep" and "up_msleep" flags. + *------------------------------------------------------------------------*/ +void +usbd_transfer_poll(struct usb_xfer **ppxfer, uint16_t max) +{ + struct usb_xfer *xfer; + struct usb_xfer_root *xroot; + struct usb_device *udev; + struct usb_proc_msg *pm; + uint16_t n; + uint16_t drop_bus; + uint16_t drop_xfer; + + for (n = 0; n != max; n++) { + /* Extra checks to avoid panic */ + xfer = ppxfer[n]; + if (xfer == NULL) + continue; /* no USB transfer */ + xroot = xfer->xroot; + if (xroot == NULL) + continue; /* no USB root */ + udev = xroot->udev; + if (udev == NULL) + continue; /* no USB device */ + if (udev->bus == NULL) + continue; /* no BUS structure */ + if (udev->bus->methods == NULL) + continue; /* no BUS methods */ + if (udev->bus->methods->xfer_poll == NULL) + continue; /* no poll method */ + + /* make sure that the BUS mutex is not locked */ + drop_bus = 0; + while (mtx_owned(&xroot->udev->bus->bus_mtx)) { + mtx_unlock(&xroot->udev->bus->bus_mtx); + drop_bus++; + } + + /* make sure that the transfer mutex is not locked */ + drop_xfer = 0; + while (mtx_owned(xroot->xfer_mtx)) { + mtx_unlock(xroot->xfer_mtx); + drop_xfer++; + } + + /* Make sure cv_signal() and cv_broadcast() is not called */ + udev->bus->control_xfer_proc.up_msleep = 0; + udev->bus->explore_proc.up_msleep = 0; + udev->bus->giant_callback_proc.up_msleep = 0; + udev->bus->non_giant_callback_proc.up_msleep = 0; + + /* poll USB hardware */ + (udev->bus->methods->xfer_poll) (udev->bus); + + USB_BUS_LOCK(xroot->bus); + + /* check for clear stall */ + if (udev->ctrl_xfer[1] != NULL) { + + /* poll clear stall start */ + pm = &udev->cs_msg[0].hdr; + (pm->pm_callback) (pm); + /* poll clear stall done thread */ + pm = &udev->ctrl_xfer[1]-> + xroot->done_m[0].hdr; + (pm->pm_callback) (pm); + } + + /* poll done thread */ + pm = &xroot->done_m[0].hdr; + (pm->pm_callback) (pm); + + USB_BUS_UNLOCK(xroot->bus); + + /* restore transfer mutex */ + while (drop_xfer--) + mtx_lock(xroot->xfer_mtx); + + /* restore BUS mutex */ + while (drop_bus--) + mtx_lock(&xroot->udev->bus->bus_mtx); + } +} + +static void +usbd_get_std_packet_size(struct usb_std_packet_size *ptr, + uint8_t type, enum usb_dev_speed speed) +{ + static const uint16_t intr_range_max[USB_SPEED_MAX] = { + [USB_SPEED_LOW] = 8, + [USB_SPEED_FULL] = 64, + [USB_SPEED_HIGH] = 1024, + [USB_SPEED_VARIABLE] = 1024, + [USB_SPEED_SUPER] = 1024, + }; + + static const uint16_t isoc_range_max[USB_SPEED_MAX] = { + [USB_SPEED_LOW] = 0, /* invalid */ + [USB_SPEED_FULL] = 1023, + [USB_SPEED_HIGH] = 1024, + [USB_SPEED_VARIABLE] = 3584, + [USB_SPEED_SUPER] = 1024, + }; + + static const uint16_t control_min[USB_SPEED_MAX] = { + [USB_SPEED_LOW] = 8, + [USB_SPEED_FULL] = 8, + [USB_SPEED_HIGH] = 64, + [USB_SPEED_VARIABLE] = 512, + [USB_SPEED_SUPER] = 512, + }; + + static const uint16_t bulk_min[USB_SPEED_MAX] = { + [USB_SPEED_LOW] = 8, + [USB_SPEED_FULL] = 8, + [USB_SPEED_HIGH] = 512, + [USB_SPEED_VARIABLE] = 512, + [USB_SPEED_SUPER] = 1024, + }; + + uint16_t temp; + + memset(ptr, 0, sizeof(*ptr)); + + switch (type) { + case UE_INTERRUPT: + ptr->range.max = intr_range_max[speed]; + break; + case UE_ISOCHRONOUS: + ptr->range.max = isoc_range_max[speed]; + break; + default: + if (type == UE_BULK) + temp = bulk_min[speed]; + else /* UE_CONTROL */ + temp = control_min[speed]; + + /* default is fixed */ + ptr->fixed[0] = temp; + ptr->fixed[1] = temp; + ptr->fixed[2] = temp; + ptr->fixed[3] = temp; + + if (speed == USB_SPEED_FULL) { + /* multiple sizes */ + ptr->fixed[1] = 16; + ptr->fixed[2] = 32; + ptr->fixed[3] = 64; + } + if ((speed == USB_SPEED_VARIABLE) && + (type == UE_BULK)) { + /* multiple sizes */ + ptr->fixed[2] = 1024; + ptr->fixed[3] = 1536; + } + break; + } +} + +void * +usbd_xfer_softc(struct usb_xfer *xfer) +{ + return (xfer->priv_sc); +} + +void * +usbd_xfer_get_priv(struct usb_xfer *xfer) +{ + return (xfer->priv_fifo); +} + +void +usbd_xfer_set_priv(struct usb_xfer *xfer, void *ptr) +{ + xfer->priv_fifo = ptr; +} + +uint8_t +usbd_xfer_state(struct usb_xfer *xfer) +{ + return (xfer->usb_state); +} + +void +usbd_xfer_set_flag(struct usb_xfer *xfer, int flag) +{ + switch (flag) { + case USB_FORCE_SHORT_XFER: + xfer->flags.force_short_xfer = 1; + break; + case USB_SHORT_XFER_OK: + xfer->flags.short_xfer_ok = 1; + break; + case USB_MULTI_SHORT_OK: + xfer->flags.short_frames_ok = 1; + break; + case USB_MANUAL_STATUS: + xfer->flags.manual_status = 1; + break; + } +} + +void +usbd_xfer_clr_flag(struct usb_xfer *xfer, int flag) +{ + switch (flag) { + case USB_FORCE_SHORT_XFER: + xfer->flags.force_short_xfer = 0; + break; + case USB_SHORT_XFER_OK: + xfer->flags.short_xfer_ok = 0; + break; + case USB_MULTI_SHORT_OK: + xfer->flags.short_frames_ok = 0; + break; + case USB_MANUAL_STATUS: + xfer->flags.manual_status = 0; + break; + } +} + +/* + * The following function returns in milliseconds when the isochronous + * transfer was completed by the hardware. The returned value wraps + * around 65536 milliseconds. + */ +uint16_t +usbd_xfer_get_timestamp(struct usb_xfer *xfer) +{ + return (xfer->isoc_time_complete); +} diff --git a/freebsd/sys/dev/usb/usb_transfer.h b/freebsd/sys/dev/usb/usb_transfer.h new file mode 100644 index 00000000..eb2abd0c --- /dev/null +++ b/freebsd/sys/dev/usb/usb_transfer.h @@ -0,0 +1,140 @@ +/* $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. + */ + +#ifndef _USB_TRANSFER_HH_ +#define _USB_TRANSFER_HH_ + +/* + * The following structure defines the messages that is used to signal + * the "done_p" USB process. + */ +struct usb_done_msg { + struct usb_proc_msg hdr; + struct usb_xfer_root *xroot; +}; + +#define USB_DMATAG_TO_XROOT(dpt) \ + ((struct usb_xfer_root *)( \ + ((uint8_t *)(dpt)) - \ + ((uint8_t *)&((struct usb_xfer_root *)0)->dma_parent_tag))) + +/* + * The following structure is used to keep information about memory + * that should be automatically freed at the moment all USB transfers + * have been freed. + */ +struct usb_xfer_root { + struct usb_dma_parent_tag dma_parent_tag; +#if USB_HAVE_BUSDMA + struct usb_xfer_queue dma_q; +#endif + struct usb_xfer_queue done_q; + struct usb_done_msg done_m[2]; + struct cv cv_drain; + + struct usb_process *done_p; /* pointer to callback process */ + void *memory_base; + struct mtx *xfer_mtx; /* cannot be changed during operation */ +#if USB_HAVE_BUSDMA + struct usb_page_cache *dma_page_cache_start; + struct usb_page_cache *dma_page_cache_end; +#endif + struct usb_page_cache *xfer_page_cache_start; + struct usb_page_cache *xfer_page_cache_end; + struct usb_bus *bus; /* pointer to USB bus (cached) */ + struct usb_device *udev; /* pointer to USB device */ + + usb_size_t memory_size; + usb_size_t setup_refcount; +#if USB_HAVE_BUSDMA + usb_frcount_t dma_nframes; /* number of page caches to load */ + usb_frcount_t dma_currframe; /* currect page cache number */ + usb_frlength_t dma_frlength_0; /* length of page cache zero */ + uint8_t dma_error; /* set if virtual memory could not be + * loaded */ +#endif + uint8_t done_sleep; /* set if done thread is sleeping */ +}; + +/* + * The following structure is used when setting up an array of USB + * transfers. + */ +struct usb_setup_params { + struct usb_dma_tag *dma_tag_p; + struct usb_page *dma_page_ptr; + struct usb_page_cache *dma_page_cache_ptr; /* these will be + * auto-freed */ + struct usb_page_cache *xfer_page_cache_ptr; /* these will not be + * auto-freed */ + struct usb_device *udev; + struct usb_xfer *curr_xfer; + const struct usb_config *curr_setup; + const struct usb_pipe_methods *methods; + void *buf; + usb_frlength_t *xfer_length_ptr; + + usb_size_t size[7]; + usb_frlength_t bufsize; + usb_frlength_t bufsize_max; + + uint32_t hc_max_frame_size; + uint16_t hc_max_packet_size; + uint8_t hc_max_packet_count; + enum usb_dev_speed speed; + uint8_t dma_tag_max; + usb_error_t err; +}; + +/* function prototypes */ + +uint8_t usbd_transfer_setup_sub_malloc(struct usb_setup_params *parm, + struct usb_page_cache **ppc, usb_size_t size, usb_size_t align, + usb_size_t count); +void usb_dma_delay_done_cb(struct usb_xfer *); +void usb_command_wrapper(struct usb_xfer_queue *pq, + struct usb_xfer *xfer); +void usbd_pipe_enter(struct usb_xfer *xfer); +void usbd_pipe_start(struct usb_xfer_queue *pq); +void usbd_transfer_dequeue(struct usb_xfer *xfer); +void usbd_transfer_done(struct usb_xfer *xfer, usb_error_t error); +void usbd_transfer_enqueue(struct usb_xfer_queue *pq, + struct usb_xfer *xfer); +void usbd_transfer_setup_sub(struct usb_setup_params *parm); +void usbd_ctrl_transfer_setup(struct usb_device *udev); +void usbd_clear_stall_locked(struct usb_device *udev, + struct usb_endpoint *ep); +void usbd_clear_data_toggle(struct usb_device *udev, + struct usb_endpoint *ep); +usb_callback_t usbd_do_request_callback; +usb_callback_t usb_handle_request_callback; +usb_callback_t usb_do_clear_stall_callback; +void usbd_transfer_timeout_ms(struct usb_xfer *xfer, + void (*cb) (void *arg), usb_timeout_t ms); +usb_timeout_t usbd_get_dma_delay(struct usb_device *udev); +void usbd_transfer_power_ref(struct usb_xfer *xfer, int val); + +#endif /* _USB_TRANSFER_HH_ */ diff --git a/freebsd/sys/dev/usb/usb_util.c b/freebsd/sys/dev/usb/usb_util.c new file mode 100644 index 00000000..3bd1f8c4 --- /dev/null +++ b/freebsd/sys/dev/usb/usb_util.c @@ -0,0 +1,251 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/* $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. + */ + +#include <freebsd/sys/stdint.h> +#include <freebsd/sys/stddef.h> +#include <freebsd/sys/param.h> +#include <freebsd/sys/queue.h> +#include <freebsd/sys/types.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/bus.h> +#include <freebsd/sys/linker_set.h> +#include <freebsd/sys/module.h> +#include <freebsd/sys/lock.h> +#include <freebsd/sys/mutex.h> +#include <freebsd/sys/condvar.h> +#include <freebsd/sys/sysctl.h> +#include <freebsd/sys/sx.h> +#include <freebsd/sys/unistd.h> +#include <freebsd/sys/callout.h> +#include <freebsd/sys/malloc.h> +#include <freebsd/sys/priv.h> + +#include <freebsd/dev/usb/usb.h> +#include <freebsd/dev/usb/usbdi.h> +#include <freebsd/dev/usb/usbdi_util.h> + +#include <freebsd/dev/usb/usb_core.h> +#include <freebsd/dev/usb/usb_util.h> +#include <freebsd/dev/usb/usb_process.h> +#include <freebsd/dev/usb/usb_device.h> +#include <freebsd/dev/usb/usb_request.h> +#include <freebsd/dev/usb/usb_busdma.h> + +#include <freebsd/dev/usb/usb_controller.h> +#include <freebsd/dev/usb/usb_bus.h> + +/*------------------------------------------------------------------------* + * device_delete_all_children - delete all children of a device + *------------------------------------------------------------------------*/ +#ifndef device_delete_all_children +int +device_delete_all_children(device_t dev) +{ + device_t *devlist; + int devcount; + int error; + + error = device_get_children(dev, &devlist, &devcount); + if (error == 0) { + while (devcount-- > 0) { + error = device_delete_child(dev, devlist[devcount]); + if (error) { + break; + } + } + free(devlist, M_TEMP); + } + return (error); +} +#endif + +/*------------------------------------------------------------------------* + * device_set_usb_desc + * + * This function can be called at probe or attach to set the USB + * device supplied textual description for the given device. + *------------------------------------------------------------------------*/ +void +device_set_usb_desc(device_t dev) +{ + struct usb_attach_arg *uaa; + struct usb_device *udev; + struct usb_interface *iface; + char *temp_p; + usb_error_t err; + + if (dev == NULL) { + /* should not happen */ + return; + } + uaa = device_get_ivars(dev); + if (uaa == NULL) { + /* can happen if called at the wrong time */ + return; + } + udev = uaa->device; + iface = uaa->iface; + + if ((iface == NULL) || + (iface->idesc == NULL) || + (iface->idesc->iInterface == 0)) { + err = USB_ERR_INVAL; + } else { + err = 0; + } + + temp_p = (char *)udev->bus->scratch[0].data; + + if (!err) { + /* try to get the interface string ! */ + err = usbd_req_get_string_any + (udev, NULL, temp_p, + sizeof(udev->bus->scratch), iface->idesc->iInterface); + } + if (err) { + /* use default description */ + usb_devinfo(udev, temp_p, + sizeof(udev->bus->scratch)); + } + device_set_desc_copy(dev, temp_p); + device_printf(dev, "<%s> on %s\n", temp_p, + device_get_nameunit(udev->bus->bdev)); +} + +/*------------------------------------------------------------------------* + * usb_pause_mtx - factored out code + * + * This function will delay the code by the passed number of system + * ticks. The passed mutex "mtx" will be dropped while waiting, if + * "mtx" is not NULL. + *------------------------------------------------------------------------*/ +void +usb_pause_mtx(struct mtx *mtx, int _ticks) +{ + if (mtx != NULL) + mtx_unlock(mtx); + + if (cold) { + /* convert to milliseconds */ + _ticks = (_ticks * 1000) / hz; + /* convert to microseconds, rounded up */ + _ticks = (_ticks + 1) * 1000; + DELAY(_ticks); + + } else { + + /* + * Add one to the number of ticks so that we don't return + * too early! + */ + _ticks++; + + if (pause("USBWAIT", _ticks)) { + /* ignore */ + } + } + if (mtx != NULL) + mtx_lock(mtx); +} + +/*------------------------------------------------------------------------* + * usb_printbcd + * + * This function will print the version number "bcd" to the string + * pointed to by "p" having a maximum length of "p_len" bytes + * including the terminating zero. + *------------------------------------------------------------------------*/ +void +usb_printbcd(char *p, uint16_t p_len, uint16_t bcd) +{ + if (snprintf(p, p_len, "%x.%02x", bcd >> 8, bcd & 0xff)) { + /* ignore any errors */ + } +} + +/*------------------------------------------------------------------------* + * usb_trim_spaces + * + * This function removes spaces at the beginning and the end of the string + * pointed to by the "p" argument. + *------------------------------------------------------------------------*/ +void +usb_trim_spaces(char *p) +{ + char *q; + char *e; + + if (p == NULL) + return; + q = e = p; + while (*q == ' ') /* skip leading spaces */ + q++; + while ((*p = *q++)) /* copy string */ + if (*p++ != ' ') /* remember last non-space */ + e = p; + *e = 0; /* kill trailing spaces */ +} + +/*------------------------------------------------------------------------* + * usb_make_str_desc - convert an ASCII string into a UNICODE string + *------------------------------------------------------------------------*/ +uint8_t +usb_make_str_desc(void *ptr, uint16_t max_len, const char *s) +{ + struct usb_string_descriptor *p = ptr; + uint8_t totlen; + int j; + + if (max_len < 2) { + /* invalid length */ + return (0); + } + max_len = ((max_len / 2) - 1); + + j = strlen(s); + + if (j < 0) { + j = 0; + } + if (j > 126) { + j = 126; + } + if (max_len > j) { + max_len = j; + } + totlen = (max_len + 1) * 2; + + p->bLength = totlen; + p->bDescriptorType = UDESC_STRING; + + while (max_len--) { + USETW2(p->bString[max_len], 0, s[max_len]); + } + return (totlen); +} diff --git a/freebsd/sys/dev/usb/usb_util.h b/freebsd/sys/dev/usb/usb_util.h new file mode 100644 index 00000000..9e001088 --- /dev/null +++ b/freebsd/sys/dev/usb/usb_util.h @@ -0,0 +1,35 @@ +/* $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. + */ + +#ifndef _USB_UTIL_HH_ +#define _USB_UTIL_HH_ + +int device_delete_all_children(device_t dev); +uint8_t usb_make_str_desc(void *ptr, uint16_t max_len, const char *s); +void usb_printbcd(char *p, uint16_t p_len, uint16_t bcd); +void usb_trim_spaces(char *p); + +#endif /* _USB_UTIL_HH_ */ diff --git a/freebsd/sys/dev/usb/usbdi.h b/freebsd/sys/dev/usb/usbdi.h new file mode 100644 index 00000000..a3f0630f --- /dev/null +++ b/freebsd/sys/dev/usb/usbdi.h @@ -0,0 +1,562 @@ +/*- + * Copyright (c) 2009 Andrew Thompson + * + * 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 ``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 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$ + */ +#ifndef _USB_USBDI_HH_ +#define _USB_USBDI_HH_ + +struct usb_fifo; +struct usb_xfer; +struct usb_device; +struct usb_attach_arg; +struct usb_interface; +struct usb_endpoint; +struct usb_page_cache; +struct usb_page_search; +struct usb_process; +struct usb_proc_msg; +struct usb_mbuf; +struct mbuf; + +typedef enum { /* keep in sync with usb_errstr_table */ + USB_ERR_NORMAL_COMPLETION = 0, + USB_ERR_PENDING_REQUESTS, /* 1 */ + USB_ERR_NOT_STARTED, /* 2 */ + USB_ERR_INVAL, /* 3 */ + USB_ERR_NOMEM, /* 4 */ + USB_ERR_CANCELLED, /* 5 */ + USB_ERR_BAD_ADDRESS, /* 6 */ + USB_ERR_BAD_BUFSIZE, /* 7 */ + USB_ERR_BAD_FLAG, /* 8 */ + USB_ERR_NO_CALLBACK, /* 9 */ + USB_ERR_IN_USE, /* 10 */ + USB_ERR_NO_ADDR, /* 11 */ + USB_ERR_NO_PIPE, /* 12 */ + USB_ERR_ZERO_NFRAMES, /* 13 */ + USB_ERR_ZERO_MAXP, /* 14 */ + USB_ERR_SET_ADDR_FAILED, /* 15 */ + USB_ERR_NO_POWER, /* 16 */ + USB_ERR_TOO_DEEP, /* 17 */ + USB_ERR_IOERROR, /* 18 */ + USB_ERR_NOT_CONFIGURED, /* 19 */ + USB_ERR_TIMEOUT, /* 20 */ + USB_ERR_SHORT_XFER, /* 21 */ + USB_ERR_STALLED, /* 22 */ + USB_ERR_INTERRUPTED, /* 23 */ + USB_ERR_DMA_LOAD_FAILED, /* 24 */ + USB_ERR_BAD_CONTEXT, /* 25 */ + USB_ERR_NO_ROOT_HUB, /* 26 */ + USB_ERR_NO_INTR_THREAD, /* 27 */ + USB_ERR_NOT_LOCKED, /* 28 */ + USB_ERR_MAX +} usb_error_t; + +/* + * Flags for transfers + */ +#define USB_FORCE_SHORT_XFER 0x0001 /* force a short transmit last */ +#define USB_SHORT_XFER_OK 0x0004 /* allow short reads */ +#define USB_DELAY_STATUS_STAGE 0x0010 /* insert delay before STATUS stage */ +#define USB_USER_DATA_PTR 0x0020 /* internal flag */ +#define USB_MULTI_SHORT_OK 0x0040 /* allow multiple short frames */ +#define USB_MANUAL_STATUS 0x0080 /* manual ctrl status */ + +#define USB_NO_TIMEOUT 0 +#define USB_DEFAULT_TIMEOUT 5000 /* 5000 ms = 5 seconds */ + +#if defined(_KERNEL) +/* typedefs */ + +typedef void (usb_callback_t)(struct usb_xfer *, usb_error_t); +typedef void (usb_proc_callback_t)(struct usb_proc_msg *); +typedef usb_error_t (usb_handle_req_t)(struct usb_device *, + struct usb_device_request *, const void **, uint16_t *); + +typedef int (usb_fifo_open_t)(struct usb_fifo *fifo, int fflags); +typedef void (usb_fifo_close_t)(struct usb_fifo *fifo, int fflags); +typedef int (usb_fifo_ioctl_t)(struct usb_fifo *fifo, u_long cmd, void *addr, int fflags); +typedef void (usb_fifo_cmd_t)(struct usb_fifo *fifo); +typedef void (usb_fifo_filter_t)(struct usb_fifo *fifo, struct usb_mbuf *m); + + +/* USB events */ +#include <freebsd/sys/eventhandler.h> +typedef void (*usb_dev_configured_t)(void *, struct usb_device *, + struct usb_attach_arg *); +EVENTHANDLER_DECLARE(usb_dev_configured, usb_dev_configured_t); + +/* + * The following macros are used used to convert milliseconds into + * HZ. We use 1024 instead of 1000 milliseconds per second to save a + * full division. + */ +#define USB_MS_HZ 1024 + +#define USB_MS_TO_TICKS(ms) \ + (((uint32_t)((((uint32_t)(ms)) * ((uint32_t)(hz))) + USB_MS_HZ - 1)) / USB_MS_HZ) + +/* + * Common queue structure for USB transfers. + */ +struct usb_xfer_queue { + TAILQ_HEAD(, usb_xfer) head; + struct usb_xfer *curr; /* current USB transfer processed */ + void (*command) (struct usb_xfer_queue *pq); + uint8_t recurse_1:1; + uint8_t recurse_2:1; +}; + +/* + * The following structure defines an USB endpoint + * USB endpoint. + */ +struct usb_endpoint { + struct usb_xfer_queue endpoint_q; /* queue of USB transfers */ + + struct usb_endpoint_descriptor *edesc; + struct usb_endpoint_ss_comp_descriptor *ecomp; + struct usb_pipe_methods *methods; /* set by HC driver */ + + uint16_t isoc_next; + + uint8_t toggle_next:1; /* next data toggle value */ + uint8_t is_stalled:1; /* set if endpoint is stalled */ + uint8_t is_synced:1; /* set if we a synchronised */ + uint8_t unused:5; + uint8_t iface_index; /* not used by "default endpoint" */ + + uint8_t refcount_alloc; /* allocation refcount */ + uint8_t refcount_bw; /* bandwidth refcount */ +#define USB_EP_REF_MAX 0x3f + + /* High-Speed resource allocation (valid if "refcount_bw" > 0) */ + + uint8_t usb_smask; /* USB start mask */ + uint8_t usb_cmask; /* USB complete mask */ + uint8_t usb_uframe; /* USB microframe */ +}; + +/* + * The following structure defines an USB interface. + */ +struct usb_interface { + struct usb_interface_descriptor *idesc; + device_t subdev; + uint8_t alt_index; + uint8_t parent_iface_index; + + /* Linux compat */ + struct usb_host_interface *altsetting; + struct usb_host_interface *cur_altsetting; + struct usb_device *linux_udev; + void *bsd_priv_sc; /* device specific information */ + uint8_t num_altsetting; /* number of alternate settings */ + uint8_t bsd_iface_index; +}; + +/* + * The following structure defines a set of USB transfer flags. + */ +struct usb_xfer_flags { + uint8_t force_short_xfer:1; /* force a short transmit transfer + * last */ + uint8_t short_xfer_ok:1; /* allow short receive transfers */ + uint8_t short_frames_ok:1; /* allow short frames */ + uint8_t pipe_bof:1; /* block pipe on failure */ + uint8_t proxy_buffer:1; /* makes buffer size a factor of + * "max_frame_size" */ + uint8_t ext_buffer:1; /* uses external DMA buffer */ + uint8_t manual_status:1; /* non automatic status stage on + * control transfers */ + uint8_t no_pipe_ok:1; /* set if "USB_ERR_NO_PIPE" error can + * be ignored */ + uint8_t stall_pipe:1; /* set if the endpoint belonging to + * this USB transfer should be stalled + * before starting this transfer! */ +}; + +/* + * The following structure define an USB configuration, that basically + * is used when setting up an USB transfer. + */ +struct usb_config { + usb_callback_t *callback; /* USB transfer callback */ + usb_frlength_t bufsize; /* total pipe buffer size in bytes */ + usb_frcount_t frames; /* maximum number of USB frames */ + usb_timeout_t interval; /* interval in milliseconds */ +#define USB_DEFAULT_INTERVAL 0 + usb_timeout_t timeout; /* transfer timeout in milliseconds */ + struct usb_xfer_flags flags; /* transfer flags */ + enum usb_hc_mode usb_mode; /* host or device mode */ + uint8_t type; /* pipe type */ + uint8_t endpoint; /* pipe number */ + uint8_t direction; /* pipe direction */ + uint8_t ep_index; /* pipe index match to use */ + uint8_t if_index; /* "ifaces" index to use */ +}; + +/* + * The following structure is used when looking up an USB driver for + * an USB device. It is inspired by the Linux structure called + * "usb_device_id". + */ +struct usb_device_id { + + /* Hook for driver specific information */ + unsigned long driver_info; + + /* Used for product specific matches; the BCD range is inclusive */ + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice_lo; + uint16_t bcdDevice_hi; + + /* Used for device class matches */ + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + + /* Used for interface class matches */ + uint8_t bInterfaceClass; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + + /* Select which fields to match against */ + uint8_t match_flag_vendor:1; + uint8_t match_flag_product:1; + uint8_t match_flag_dev_lo:1; + uint8_t match_flag_dev_hi:1; + uint8_t match_flag_dev_class:1; + uint8_t match_flag_dev_subclass:1; + uint8_t match_flag_dev_protocol:1; + uint8_t match_flag_int_class:1; + uint8_t match_flag_int_subclass:1; + uint8_t match_flag_int_protocol:1; + +#if USB_HAVE_COMPAT_LINUX + /* which fields to match against */ + uint16_t match_flags; +#define USB_DEVICE_ID_MATCH_VENDOR 0x0001 +#define USB_DEVICE_ID_MATCH_PRODUCT 0x0002 +#define USB_DEVICE_ID_MATCH_DEV_LO 0x0004 +#define USB_DEVICE_ID_MATCH_DEV_HI 0x0008 +#define USB_DEVICE_ID_MATCH_DEV_CLASS 0x0010 +#define USB_DEVICE_ID_MATCH_DEV_SUBCLASS 0x0020 +#define USB_DEVICE_ID_MATCH_DEV_PROTOCOL 0x0040 +#define USB_DEVICE_ID_MATCH_INT_CLASS 0x0080 +#define USB_DEVICE_ID_MATCH_INT_SUBCLASS 0x0100 +#define USB_DEVICE_ID_MATCH_INT_PROTOCOL 0x0200 +#endif +}; + +#define USB_VENDOR(vend) \ + .match_flag_vendor = 1, .idVendor = (vend) + +#define USB_PRODUCT(prod) \ + .match_flag_product = 1, .idProduct = (prod) + +#define USB_VP(vend,prod) \ + USB_VENDOR(vend), USB_PRODUCT(prod) + +#define USB_VPI(vend,prod,info) \ + USB_VENDOR(vend), USB_PRODUCT(prod), USB_DRIVER_INFO(info) + +#define USB_DEV_BCD_GTEQ(lo) /* greater than or equal */ \ + .match_flag_dev_lo = 1, .bcdDevice_lo = (lo) + +#define USB_DEV_BCD_LTEQ(hi) /* less than or equal */ \ + .match_flag_dev_hi = 1, .bcdDevice_hi = (hi) + +#define USB_DEV_CLASS(dc) \ + .match_flag_dev_class = 1, .bDeviceClass = (dc) + +#define USB_DEV_SUBCLASS(dsc) \ + .match_flag_dev_subclass = 1, .bDeviceSubClass = (dsc) + +#define USB_DEV_PROTOCOL(dp) \ + .match_flag_dev_protocol = 1, .bDeviceProtocol = (dp) + +#define USB_IFACE_CLASS(ic) \ + .match_flag_int_class = 1, .bInterfaceClass = (ic) + +#define USB_IFACE_SUBCLASS(isc) \ + .match_flag_int_subclass = 1, .bInterfaceSubClass = (isc) + +#define USB_IFACE_PROTOCOL(ip) \ + .match_flag_int_protocol = 1, .bInterfaceProtocol = (ip) + +#define USB_IF_CSI(class,subclass,info) \ + USB_IFACE_CLASS(class), USB_IFACE_SUBCLASS(subclass), USB_DRIVER_INFO(info) + +#define USB_DRIVER_INFO(n) \ + .driver_info = (n) + +#define USB_GET_DRIVER_INFO(did) \ + (did)->driver_info + +/* + * The following structure keeps information that is used to match + * against an array of "usb_device_id" elements. + */ +struct usbd_lookup_info { + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bInterfaceClass; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + uint8_t bIfaceIndex; + uint8_t bIfaceNum; + uint8_t bConfigIndex; + uint8_t bConfigNum; +}; + +/* Structure used by probe and attach */ + +struct usb_attach_arg { + struct usbd_lookup_info info; + device_t temp_dev; /* for internal use */ + unsigned long driver_info; /* for internal use */ + void *driver_ivar; + struct usb_device *device; /* current device */ + struct usb_interface *iface; /* current interface */ + enum usb_hc_mode usb_mode; /* host or device mode */ + uint8_t port; + uint8_t use_generic; /* hint for generic drivers */ + uint8_t dev_state; +#define UAA_DEV_READY 0 +#define UAA_DEV_DISABLED 1 +#define UAA_DEV_EJECTING 2 +}; + +/* + * The following is a wrapper for the callout structure to ease + * porting the code to other platforms. + */ +struct usb_callout { + struct callout co; +}; +#define usb_callout_init_mtx(c,m,f) callout_init_mtx(&(c)->co,m,f) +#define usb_callout_reset(c,t,f,d) callout_reset(&(c)->co,t,f,d) +#define usb_callout_stop(c) callout_stop(&(c)->co) +#define usb_callout_drain(c) callout_drain(&(c)->co) +#define usb_callout_pending(c) callout_pending(&(c)->co) + +/* USB transfer states */ + +#define USB_ST_SETUP 0 +#define USB_ST_TRANSFERRED 1 +#define USB_ST_ERROR 2 + +/* USB handle request states */ +#define USB_HR_NOT_COMPLETE 0 +#define USB_HR_COMPLETE_OK 1 +#define USB_HR_COMPLETE_ERR 2 + +/* + * The following macro will return the current state of an USB + * transfer like defined by the "USB_ST_XXX" enums. + */ +#define USB_GET_STATE(xfer) (usbd_xfer_state(xfer)) + +/* + * The following structure defines the USB process message header. + */ +struct usb_proc_msg { + TAILQ_ENTRY(usb_proc_msg) pm_qentry; + usb_proc_callback_t *pm_callback; + usb_size_t pm_num; +}; + +#define USB_FIFO_TX 0 +#define USB_FIFO_RX 1 + +/* + * Locking note for the following functions. All the + * "usb_fifo_cmd_t" and "usb_fifo_filter_t" functions are called + * locked. The others are called unlocked. + */ +struct usb_fifo_methods { + usb_fifo_open_t *f_open; + usb_fifo_close_t *f_close; + usb_fifo_ioctl_t *f_ioctl; + /* + * NOTE: The post-ioctl callback is called after the USB reference + * gets locked in the IOCTL handler: + */ + usb_fifo_ioctl_t *f_ioctl_post; + usb_fifo_cmd_t *f_start_read; + usb_fifo_cmd_t *f_stop_read; + usb_fifo_cmd_t *f_start_write; + usb_fifo_cmd_t *f_stop_write; + usb_fifo_filter_t *f_filter_read; + usb_fifo_filter_t *f_filter_write; + const char *basename[4]; + const char *postfix[4]; +}; + +struct usb_fifo_sc { + struct usb_fifo *fp[2]; + struct cdev* dev; +}; + +const char *usbd_errstr(usb_error_t error); +void *usbd_find_descriptor(struct usb_device *udev, void *id, + uint8_t iface_index, uint8_t type, uint8_t type_mask, + uint8_t subtype, uint8_t subtype_mask); +struct usb_config_descriptor *usbd_get_config_descriptor( + struct usb_device *udev); +struct usb_device_descriptor *usbd_get_device_descriptor( + struct usb_device *udev); +struct usb_interface *usbd_get_iface(struct usb_device *udev, + uint8_t iface_index); +struct usb_interface_descriptor *usbd_get_interface_descriptor( + struct usb_interface *iface); +struct usb_endpoint *usbd_get_endpoint(struct usb_device *udev, uint8_t iface_index, + const struct usb_config *setup); +struct usb_endpoint *usbd_get_ep_by_addr(struct usb_device *udev, uint8_t ea_val); +usb_error_t usbd_interface_count(struct usb_device *udev, uint8_t *count); +enum usb_hc_mode usbd_get_mode(struct usb_device *udev); +enum usb_dev_speed usbd_get_speed(struct usb_device *udev); +void device_set_usb_desc(device_t dev); +void usb_pause_mtx(struct mtx *mtx, int _ticks); + +const struct usb_device_id *usbd_lookup_id_by_info( + const struct usb_device_id *id, usb_size_t sizeof_id, + const struct usbd_lookup_info *info); +int usbd_lookup_id_by_uaa(const struct usb_device_id *id, + usb_size_t sizeof_id, struct usb_attach_arg *uaa); + +usb_error_t usbd_do_request_flags(struct usb_device *udev, struct mtx *mtx, + struct usb_device_request *req, void *data, uint16_t flags, + uint16_t *actlen, usb_timeout_t timeout); +#define usbd_do_request(u,m,r,d) \ + usbd_do_request_flags(u,m,r,d,0,NULL,USB_DEFAULT_TIMEOUT) + +uint8_t usbd_clear_stall_callback(struct usb_xfer *xfer1, + struct usb_xfer *xfer2); +uint8_t usbd_get_interface_altindex(struct usb_interface *iface); +usb_error_t usbd_set_alt_interface_index(struct usb_device *udev, + uint8_t iface_index, uint8_t alt_index); +uint32_t usbd_get_isoc_fps(struct usb_device *udev); +usb_error_t usbd_transfer_setup(struct usb_device *udev, + const uint8_t *ifaces, struct usb_xfer **pxfer, + const struct usb_config *setup_start, uint16_t n_setup, + void *priv_sc, struct mtx *priv_mtx); +void usbd_transfer_submit(struct usb_xfer *xfer); +void usbd_transfer_clear_stall(struct usb_xfer *xfer); +void usbd_transfer_drain(struct usb_xfer *xfer); +uint8_t usbd_transfer_pending(struct usb_xfer *xfer); +void usbd_transfer_start(struct usb_xfer *xfer); +void usbd_transfer_stop(struct usb_xfer *xfer); +void usbd_transfer_unsetup(struct usb_xfer **pxfer, uint16_t n_setup); +void usbd_transfer_poll(struct usb_xfer **ppxfer, uint16_t max); +void usbd_set_parent_iface(struct usb_device *udev, uint8_t iface_index, + uint8_t parent_index); +uint8_t usbd_get_bus_index(struct usb_device *udev); +uint8_t usbd_get_device_index(struct usb_device *udev); +void usbd_set_power_mode(struct usb_device *udev, uint8_t power_mode); +uint8_t usbd_filter_power_mode(struct usb_device *udev, uint8_t power_mode); +uint8_t usbd_device_attached(struct usb_device *udev); + +void usbd_xfer_status(struct usb_xfer *xfer, int *actlen, int *sumlen, + int *aframes, int *nframes); +struct usb_page_cache *usbd_xfer_get_frame(struct usb_xfer *xfer, + usb_frcount_t frindex); +void *usbd_xfer_softc(struct usb_xfer *xfer); +void *usbd_xfer_get_priv(struct usb_xfer *xfer); +void usbd_xfer_set_priv(struct usb_xfer *xfer, void *); +void usbd_xfer_set_interval(struct usb_xfer *xfer, int); +uint8_t usbd_xfer_state(struct usb_xfer *xfer); +void usbd_xfer_set_frame_data(struct usb_xfer *xfer, usb_frcount_t frindex, + void *ptr, usb_frlength_t len); +void usbd_xfer_frame_data(struct usb_xfer *xfer, usb_frcount_t frindex, + void **ptr, int *len); +void usbd_xfer_set_frame_offset(struct usb_xfer *xfer, usb_frlength_t offset, + usb_frcount_t frindex); +usb_frlength_t usbd_xfer_max_len(struct usb_xfer *xfer); +usb_frlength_t usbd_xfer_max_framelen(struct usb_xfer *xfer); +usb_frcount_t usbd_xfer_max_frames(struct usb_xfer *xfer); +uint8_t usbd_xfer_get_fps_shift(struct usb_xfer *xfer); +usb_frlength_t usbd_xfer_frame_len(struct usb_xfer *xfer, + usb_frcount_t frindex); +void usbd_xfer_set_frame_len(struct usb_xfer *xfer, usb_frcount_t frindex, + usb_frlength_t len); +void usbd_xfer_set_timeout(struct usb_xfer *xfer, int timeout); +void usbd_xfer_set_frames(struct usb_xfer *xfer, usb_frcount_t n); +void usbd_xfer_set_stall(struct usb_xfer *xfer); +int usbd_xfer_is_stalled(struct usb_xfer *xfer); +void usbd_xfer_set_flag(struct usb_xfer *xfer, int flag); +void usbd_xfer_clr_flag(struct usb_xfer *xfer, int flag); +uint16_t usbd_xfer_get_timestamp(struct usb_xfer *xfer); + +void usbd_copy_in(struct usb_page_cache *cache, usb_frlength_t offset, + const void *ptr, usb_frlength_t len); +int usbd_copy_in_user(struct usb_page_cache *cache, usb_frlength_t offset, + const void *ptr, usb_frlength_t len); +void usbd_copy_out(struct usb_page_cache *cache, usb_frlength_t offset, + void *ptr, usb_frlength_t len); +int usbd_copy_out_user(struct usb_page_cache *cache, usb_frlength_t offset, + void *ptr, usb_frlength_t len); +void usbd_get_page(struct usb_page_cache *pc, usb_frlength_t offset, + struct usb_page_search *res); +void usbd_m_copy_in(struct usb_page_cache *cache, usb_frlength_t dst_offset, + struct mbuf *m, usb_size_t src_offset, usb_frlength_t src_len); +void usbd_frame_zero(struct usb_page_cache *cache, usb_frlength_t offset, + usb_frlength_t len); + +int usb_fifo_attach(struct usb_device *udev, void *priv_sc, + struct mtx *priv_mtx, struct usb_fifo_methods *pm, + struct usb_fifo_sc *f_sc, uint16_t unit, uint16_t subunit, + uint8_t iface_index, uid_t uid, gid_t gid, int mode); +void usb_fifo_detach(struct usb_fifo_sc *f_sc); +int usb_fifo_alloc_buffer(struct usb_fifo *f, uint32_t bufsize, + uint16_t nbuf); +void usb_fifo_free_buffer(struct usb_fifo *f); +uint32_t usb_fifo_put_bytes_max(struct usb_fifo *fifo); +void usb_fifo_put_data(struct usb_fifo *fifo, struct usb_page_cache *pc, + usb_frlength_t offset, usb_frlength_t len, uint8_t what); +void usb_fifo_put_data_linear(struct usb_fifo *fifo, void *ptr, + usb_size_t len, uint8_t what); +uint8_t usb_fifo_put_data_buffer(struct usb_fifo *f, void *ptr, usb_size_t len); +void usb_fifo_put_data_error(struct usb_fifo *fifo); +uint8_t usb_fifo_get_data(struct usb_fifo *fifo, struct usb_page_cache *pc, + usb_frlength_t offset, usb_frlength_t len, usb_frlength_t *actlen, + uint8_t what); +uint8_t usb_fifo_get_data_linear(struct usb_fifo *fifo, void *ptr, + usb_size_t len, usb_size_t *actlen, uint8_t what); +uint8_t usb_fifo_get_data_buffer(struct usb_fifo *f, void **pptr, + usb_size_t *plen); +void usb_fifo_reset(struct usb_fifo *f); +void usb_fifo_wakeup(struct usb_fifo *f); +void usb_fifo_get_data_error(struct usb_fifo *fifo); +void *usb_fifo_softc(struct usb_fifo *fifo); +void usb_fifo_set_close_zlp(struct usb_fifo *, uint8_t); +void usb_fifo_set_write_defrag(struct usb_fifo *, uint8_t); +void usb_fifo_free(struct usb_fifo *f); +#endif /* _KERNEL */ +#endif /* _USB_USBDI_HH_ */ diff --git a/freebsd/sys/dev/usb/usbdi_util.h b/freebsd/sys/dev/usb/usbdi_util.h new file mode 100644 index 00000000..32931e52 --- /dev/null +++ b/freebsd/sys/dev/usb/usbdi_util.h @@ -0,0 +1,91 @@ +/*- + * Copyright (c) 2009 Andrew Thompson + * + * 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 ``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 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$ + */ +#ifndef _USB_USBDI_UTIL_HH_ +#define _USB_USBDI_UTIL_HH_ + +struct cv; + +/* structures */ + +struct usb_idesc_parse_state { + struct usb_descriptor *desc; + uint8_t iface_index; /* current interface index */ + uint8_t iface_no_last; + uint8_t iface_index_alt; /* current alternate setting */ +}; + +/* prototypes */ + +usb_error_t usbd_do_request_proc(struct usb_device *udev, struct usb_process *pproc, + struct usb_device_request *req, void *data, uint16_t flags, + uint16_t *actlen, usb_timeout_t timeout); + +struct usb_descriptor *usb_desc_foreach(struct usb_config_descriptor *cd, + struct usb_descriptor *desc); +struct usb_interface_descriptor *usb_idesc_foreach( + struct usb_config_descriptor *cd, + struct usb_idesc_parse_state *ps); +struct usb_endpoint_descriptor *usb_edesc_foreach( + struct usb_config_descriptor *cd, + struct usb_endpoint_descriptor *ped); +struct usb_endpoint_ss_comp_descriptor *usb_ed_comp_foreach( + struct usb_config_descriptor *cd, + struct usb_endpoint_ss_comp_descriptor *ped); +uint8_t usbd_get_no_descriptors(struct usb_config_descriptor *cd, + uint8_t type); +uint8_t usbd_get_no_alts(struct usb_config_descriptor *cd, + struct usb_interface_descriptor *id); + +usb_error_t usbd_req_get_report(struct usb_device *udev, struct mtx *mtx, + void *data, uint16_t len, uint8_t iface_index, uint8_t type, + uint8_t id); +usb_error_t usbd_req_get_report_descriptor(struct usb_device *udev, + struct mtx *mtx, void *d, uint16_t size, + uint8_t iface_index); +usb_error_t usbd_req_get_string_any(struct usb_device *udev, struct mtx *mtx, + char *buf, uint16_t len, uint8_t string_index); +usb_error_t usbd_req_get_string_desc(struct usb_device *udev, struct mtx *mtx, + void *sdesc, uint16_t max_len, uint16_t lang_id, + uint8_t string_index); +usb_error_t usbd_req_set_config(struct usb_device *udev, struct mtx *mtx, + uint8_t conf); +usb_error_t usbd_req_set_alt_interface_no(struct usb_device *udev, + struct mtx *mtx, uint8_t iface_index, uint8_t alt_no); +usb_error_t usbd_req_set_idle(struct usb_device *udev, struct mtx *mtx, + uint8_t iface_index, uint8_t duration, uint8_t id); +usb_error_t usbd_req_set_protocol(struct usb_device *udev, struct mtx *mtx, + uint8_t iface_index, uint16_t report); +usb_error_t usbd_req_set_report(struct usb_device *udev, struct mtx *mtx, + void *data, uint16_t len, uint8_t iface_index, + uint8_t type, uint8_t id); + +/* The following functions will not return NULL strings. */ + +const char *usb_get_manufacturer(struct usb_device *); +const char *usb_get_product(struct usb_device *); +const char *usb_get_serial(struct usb_device *); + +#endif /* _USB_USBDI_UTIL_HH_ */ diff --git a/freebsd/sys/dev/usb/usbhid.h b/freebsd/sys/dev/usb/usbhid.h new file mode 100644 index 00000000..192a2359 --- /dev/null +++ b/freebsd/sys/dev/usb/usbhid.h @@ -0,0 +1,244 @@ +/* $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. + */ + +#ifndef _USB_HID_HH_ +#define _USB_HID_HH_ + +#include <freebsd/dev/usb/usb_endian.h> + +#define UR_GET_HID_DESCRIPTOR 0x06 +#define UDESC_HID 0x21 +#define UDESC_REPORT 0x22 +#define UDESC_PHYSICAL 0x23 +#define UR_SET_HID_DESCRIPTOR 0x07 +#define UR_GET_REPORT 0x01 +#define UR_SET_REPORT 0x09 +#define UR_GET_IDLE 0x02 +#define UR_SET_IDLE 0x0a +#define UR_GET_PROTOCOL 0x03 +#define UR_SET_PROTOCOL 0x0b + +struct usb_hid_descriptor { + uByte bLength; + uByte bDescriptorType; + uWord bcdHID; + uByte bCountryCode; + uByte bNumDescriptors; + struct { + uByte bDescriptorType; + uWord wDescriptorLength; + } descrs[1]; +} __packed; + +#define USB_HID_DESCRIPTOR_SIZE(n) (9+((n)*3)) + +/* Usage pages */ +#define HUP_UNDEFINED 0x0000 +#define HUP_GENERIC_DESKTOP 0x0001 +#define HUP_SIMULATION 0x0002 +#define HUP_VR_CONTROLS 0x0003 +#define HUP_SPORTS_CONTROLS 0x0004 +#define HUP_GAMING_CONTROLS 0x0005 +#define HUP_KEYBOARD 0x0007 +#define HUP_LEDS 0x0008 +#define HUP_BUTTON 0x0009 +#define HUP_ORDINALS 0x000a +#define HUP_TELEPHONY 0x000b +#define HUP_CONSUMER 0x000c +#define HUP_DIGITIZERS 0x000d +#define HUP_PHYSICAL_IFACE 0x000e +#define HUP_UNICODE 0x0010 +#define HUP_ALPHANUM_DISPLAY 0x0014 +#define HUP_MONITOR 0x0080 +#define HUP_MONITOR_ENUM_VAL 0x0081 +#define HUP_VESA_VC 0x0082 +#define HUP_VESA_CMD 0x0083 +#define HUP_POWER 0x0084 +#define HUP_BATTERY_SYSTEM 0x0085 +#define HUP_BARCODE_SCANNER 0x008b +#define HUP_SCALE 0x008c +#define HUP_CAMERA_CONTROL 0x0090 +#define HUP_ARCADE 0x0091 +#define HUP_MICROSOFT 0xff00 + +/* Usages, generic desktop */ +#define HUG_POINTER 0x0001 +#define HUG_MOUSE 0x0002 +#define HUG_JOYSTICK 0x0004 +#define HUG_GAME_PAD 0x0005 +#define HUG_KEYBOARD 0x0006 +#define HUG_KEYPAD 0x0007 +#define HUG_X 0x0030 +#define HUG_Y 0x0031 +#define HUG_Z 0x0032 +#define HUG_RX 0x0033 +#define HUG_RY 0x0034 +#define HUG_RZ 0x0035 +#define HUG_SLIDER 0x0036 +#define HUG_DIAL 0x0037 +#define HUG_WHEEL 0x0038 +#define HUG_HAT_SWITCH 0x0039 +#define HUG_COUNTED_BUFFER 0x003a +#define HUG_BYTE_COUNT 0x003b +#define HUG_MOTION_WAKEUP 0x003c +#define HUG_VX 0x0040 +#define HUG_VY 0x0041 +#define HUG_VZ 0x0042 +#define HUG_VBRX 0x0043 +#define HUG_VBRY 0x0044 +#define HUG_VBRZ 0x0045 +#define HUG_VNO 0x0046 +#define HUG_TWHEEL 0x0048 /* M$ Wireless Intellimouse Wheel */ +#define HUG_SYSTEM_CONTROL 0x0080 +#define HUG_SYSTEM_POWER_DOWN 0x0081 +#define HUG_SYSTEM_SLEEP 0x0082 +#define HUG_SYSTEM_WAKEUP 0x0083 +#define HUG_SYSTEM_CONTEXT_MENU 0x0084 +#define HUG_SYSTEM_MAIN_MENU 0x0085 +#define HUG_SYSTEM_APP_MENU 0x0086 +#define HUG_SYSTEM_MENU_HELP 0x0087 +#define HUG_SYSTEM_MENU_EXIT 0x0088 +#define HUG_SYSTEM_MENU_SELECT 0x0089 +#define HUG_SYSTEM_MENU_RIGHT 0x008a +#define HUG_SYSTEM_MENU_LEFT 0x008b +#define HUG_SYSTEM_MENU_UP 0x008c +#define HUG_SYSTEM_MENU_DOWN 0x008d +#define HUG_APPLE_EJECT 0x00b8 + +/* Usages Digitizers */ +#define HUD_UNDEFINED 0x0000 +#define HUD_TIP_PRESSURE 0x0030 +#define HUD_BARREL_PRESSURE 0x0031 +#define HUD_IN_RANGE 0x0032 +#define HUD_TOUCH 0x0033 +#define HUD_UNTOUCH 0x0034 +#define HUD_TAP 0x0035 +#define HUD_QUALITY 0x0036 +#define HUD_DATA_VALID 0x0037 +#define HUD_TRANSDUCER_INDEX 0x0038 +#define HUD_TABLET_FKEYS 0x0039 +#define HUD_PROGRAM_CHANGE_KEYS 0x003a +#define HUD_BATTERY_STRENGTH 0x003b +#define HUD_INVERT 0x003c +#define HUD_X_TILT 0x003d +#define HUD_Y_TILT 0x003e +#define HUD_AZIMUTH 0x003f +#define HUD_ALTITUDE 0x0040 +#define HUD_TWIST 0x0041 +#define HUD_TIP_SWITCH 0x0042 +#define HUD_SEC_TIP_SWITCH 0x0043 +#define HUD_BARREL_SWITCH 0x0044 +#define HUD_ERASER 0x0045 +#define HUD_TABLET_PICK 0x0046 + +/* Usages, Consumer */ +#define HUC_AC_PAN 0x0238 + +#define HID_USAGE2(p,u) (((p) << 16) | (u)) + +#define UHID_INPUT_REPORT 0x01 +#define UHID_OUTPUT_REPORT 0x02 +#define UHID_FEATURE_REPORT 0x03 + +/* Bits in the input/output/feature items */ +#define HIO_CONST 0x001 +#define HIO_VARIABLE 0x002 +#define HIO_RELATIVE 0x004 +#define HIO_WRAP 0x008 +#define HIO_NONLINEAR 0x010 +#define HIO_NOPREF 0x020 +#define HIO_NULLSTATE 0x040 +#define HIO_VOLATILE 0x080 +#define HIO_BUFBYTES 0x100 + +#ifdef _KERNEL +struct usb_config_descriptor; + +enum hid_kind { + hid_input, hid_output, hid_feature, hid_collection, hid_endcollection +}; + +struct hid_location { + uint32_t size; + uint32_t count; + uint32_t pos; +}; + +struct hid_item { + /* Global */ + int32_t _usage_page; + int32_t logical_minimum; + int32_t logical_maximum; + int32_t physical_minimum; + int32_t physical_maximum; + int32_t unit_exponent; + int32_t unit; + int32_t report_ID; + /* Local */ + int32_t usage; + int32_t usage_minimum; + int32_t usage_maximum; + int32_t designator_index; + int32_t designator_minimum; + int32_t designator_maximum; + int32_t string_index; + int32_t string_minimum; + int32_t string_maximum; + int32_t set_delimiter; + /* Misc */ + int32_t collection; + int collevel; + enum hid_kind kind; + uint32_t flags; + /* Location */ + struct hid_location loc; +}; + +/* prototypes from "usb_hid.c" */ + +struct hid_data *hid_start_parse(const void *d, usb_size_t len, int kindset); +void hid_end_parse(struct hid_data *s); +int hid_get_item(struct hid_data *s, struct hid_item *h); +int hid_report_size(const void *buf, usb_size_t len, enum hid_kind k, + uint8_t *id); +int hid_locate(const void *desc, usb_size_t size, uint32_t usage, + enum hid_kind kind, uint8_t index, struct hid_location *loc, + uint32_t *flags, uint8_t *id); +int32_t hid_get_data(const uint8_t *buf, usb_size_t len, + struct hid_location *loc); +uint32_t hid_get_data_unsigned(const uint8_t *buf, usb_size_t len, + struct hid_location *loc); +int hid_is_collection(const void *desc, usb_size_t size, uint32_t usage); +struct usb_hid_descriptor *hid_get_descriptor_from_usb( + struct usb_config_descriptor *cd, + struct usb_interface_descriptor *id); +usb_error_t usbd_req_get_hid_desc(struct usb_device *udev, struct mtx *mtx, + void **descp, uint16_t *sizep, struct malloc_type *mem, + uint8_t iface_index); +#endif /* _KERNEL */ +#endif /* _USB_HID_HH_ */ |