summaryrefslogtreecommitdiffstats
path: root/freebsd/sys/dev/usb
diff options
context:
space:
mode:
authorSebastian Huber <sebastian.huber@embedded-brains.de>2013-10-09 22:42:09 +0200
committerSebastian Huber <sebastian.huber@embedded-brains.de>2013-10-10 09:06:58 +0200
commitbceabc95c1c85d793200446fa85f1ddc6313ea29 (patch)
tree973c8bd8deca9fd69913f2895cc91e0e6114d46c /freebsd/sys/dev/usb
parentAdd FreeBSD sources as a submodule (diff)
downloadrtems-libbsd-bceabc95c1c85d793200446fa85f1ddc6313ea29.tar.bz2
Move files to match FreeBSD layout
Diffstat (limited to 'freebsd/sys/dev/usb')
-rw-r--r--freebsd/sys/dev/usb/controller/ehci.c3939
-rw-r--r--freebsd/sys/dev/usb/controller/ehci.h486
-rw-r--r--freebsd/sys/dev/usb/controller/ehcireg.h171
-rw-r--r--freebsd/sys/dev/usb/controller/ohci.c2764
-rw-r--r--freebsd/sys/dev/usb/controller/ohci.h276
-rw-r--r--freebsd/sys/dev/usb/controller/ohcireg.h131
-rw-r--r--freebsd/sys/dev/usb/controller/usb_controller.c606
-rw-r--r--freebsd/sys/dev/usb/quirk/usb_quirk.c807
-rw-r--r--freebsd/sys/dev/usb/quirk/usb_quirk.h108
-rw-r--r--freebsd/sys/dev/usb/storage/umass.c3119
-rw-r--r--freebsd/sys/dev/usb/ufm_ioctl.h39
-rw-r--r--freebsd/sys/dev/usb/usb.h755
-rw-r--r--freebsd/sys/dev/usb/usb_bus.h114
-rw-r--r--freebsd/sys/dev/usb/usb_busdma.c1071
-rw-r--r--freebsd/sys/dev/usb/usb_busdma.h161
-rw-r--r--freebsd/sys/dev/usb/usb_cdc.h295
-rw-r--r--freebsd/sys/dev/usb/usb_controller.h225
-rw-r--r--freebsd/sys/dev/usb/usb_core.c62
-rw-r--r--freebsd/sys/dev/usb/usb_core.h183
-rw-r--r--freebsd/sys/dev/usb/usb_debug.c179
-rw-r--r--freebsd/sys/dev/usb/usb_debug.h62
-rw-r--r--freebsd/sys/dev/usb/usb_dev.c2309
-rw-r--r--freebsd/sys/dev/usb/usb_dev.h154
-rw-r--r--freebsd/sys/dev/usb/usb_device.c2693
-rw-r--r--freebsd/sys/dev/usb/usb_device.h227
-rw-r--r--freebsd/sys/dev/usb/usb_dynamic.c151
-rw-r--r--freebsd/sys/dev/usb/usb_dynamic.h61
-rw-r--r--freebsd/sys/dev/usb/usb_endian.h119
-rw-r--r--freebsd/sys/dev/usb/usb_error.c93
-rw-r--r--freebsd/sys/dev/usb/usb_freebsd.h69
-rw-r--r--freebsd/sys/dev/usb/usb_generic.c2239
-rw-r--r--freebsd/sys/dev/usb/usb_generic.h33
-rw-r--r--freebsd/sys/dev/usb/usb_handle_request.c807
-rw-r--r--freebsd/sys/dev/usb/usb_hid.c820
-rw-r--r--freebsd/sys/dev/usb/usb_hub.c2474
-rw-r--r--freebsd/sys/dev/usb/usb_hub.h83
-rw-r--r--freebsd/sys/dev/usb/usb_ioctl.h272
-rw-r--r--freebsd/sys/dev/usb/usb_lookup.c156
-rw-r--r--freebsd/sys/dev/usb/usb_mbuf.c101
-rw-r--r--freebsd/sys/dev/usb/usb_mbuf.h90
-rw-r--r--freebsd/sys/dev/usb/usb_msctest.c641
-rw-r--r--freebsd/sys/dev/usb/usb_msctest.h44
-rw-r--r--freebsd/sys/dev/usb/usb_parse.c291
-rw-r--r--freebsd/sys/dev/usb/usb_process.c501
-rw-r--r--freebsd/sys/dev/usb/usb_process.h84
-rw-r--r--freebsd/sys/dev/usb/usb_request.c2031
-rw-r--r--freebsd/sys/dev/usb/usb_request.h89
-rw-r--r--freebsd/sys/dev/usb/usb_transfer.c3305
-rw-r--r--freebsd/sys/dev/usb/usb_transfer.h140
-rw-r--r--freebsd/sys/dev/usb/usb_util.c251
-rw-r--r--freebsd/sys/dev/usb/usb_util.h35
-rw-r--r--freebsd/sys/dev/usb/usbdi.h562
-rw-r--r--freebsd/sys/dev/usb/usbdi_util.h91
-rw-r--r--freebsd/sys/dev/usb/usbhid.h244
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_ */