diff options
Diffstat (limited to 'sys/legacy/dev')
106 files changed, 82919 insertions, 0 deletions
diff --git a/sys/legacy/dev/usb/FILES b/sys/legacy/dev/usb/FILES new file mode 100644 index 0000000..5a77cbd --- /dev/null +++ b/sys/legacy/dev/usb/FILES @@ -0,0 +1,52 @@ +$FreeBSD$ + +A small roadmap of the USB files: + +FILES this file +dsbr100io.h API for ufm.c +ehci.c Host controller driver for EHCI +ehcireg.h Hardware definitions for EHCI +ehcivar.h API for ehci.c +hid.c subroutines to parse and access HID data +hid.h API for hid.c +if_aue.c USB Pegasus Ethernet driver +if_auereg.h and definitions for it +if_axe.c USB ASIX Electronics Ethernet driver +if_axereg.h and definitions for it +if_cue.c USB CATC Ethernet driver +if_cuereg.h and definitions for it +if_kue.c USB Kawasaki Ethernet driver +if_kuereg.h and definitions for it +ohci.c Host controller driver for OHCI +ohcireg.h Hardware definitions for OHCI +ohcivar.h API for ohci.c +ufm.c USB fm radio driver +[Merged] ugen.c generic driver that can handle access to any USB device +uhci.c Host controller driver for UHCI +uhcireg.h Hardware definitions for UHCI +uhcivar.h API for uhci.c +uhid.c USB HID class driver +uhub.c USB hub driver +ukbd.c USB keyboard driver +ulpt.c USB printer class driver +umass.c USB mass storage driver +umodem.c USB modem (CDC ACM) driver +ums.c USB mouse driver +urio.c USB Diamond Rio500 driver +usb.c usb (bus) device driver +usb.h general USB defines +usb_mem.c memory allocation for DMAable memory +usb_mem.h API for usb_mem.c +usb_port.h compatibility defines for different OSs +usb_quirks.c table of non-conforming USB devices and their problems +usb_quirks.h API for usb_quirks.c +usb_subr.c various subroutines used by USB code +usbcdc.h USB CDC class definitions +usbdevs data base of known device +usbdi.c implementation of the USBDI API, which all drivers use +usbdi.h API for usbdi.c +usbdi_util.c utilities built on top of usbdi.h +usbdi_util.h API for usbdi_util.c +usbdivar.h internal defines and structures for usbdi.c +uscanner.c minimal USB scanner driver +usbhid.h USB HID class definitions diff --git a/sys/legacy/dev/usb/dsbr100io.h b/sys/legacy/dev/usb/dsbr100io.h new file mode 100644 index 0000000..f1c8a97 --- /dev/null +++ b/sys/legacy/dev/usb/dsbr100io.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 <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/sys/legacy/dev/usb/ehci.c b/sys/legacy/dev/usb/ehci.c new file mode 100644 index 0000000..c159600 --- /dev/null +++ b/sys/legacy/dev/usb/ehci.c @@ -0,0 +1,3932 @@ +/* $NetBSD: ehci.c,v 1.91 2005/02/27 00:27:51 perry Exp $ */ + +/*- + * Copyright (c) 2004 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) and by Charles M. Hannum. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * USB Enhanced Host Controller Driver, a.k.a. USB 2.0 controller. + * + * The EHCI 1.0 spec can be found at + * http://developer.intel.com/technology/usb/download/ehci-r10.pdf + * and the USB 2.0 spec at + * http://www.usb.org/developers/docs/usb_20.zip + * + */ + +/* + * TODO: + * 1) The EHCI driver lacks support for isochronous transfers, so + * devices using them don't work. + * + * 2) Interrupt transfer scheduling does not manage the time available + * in each frame, so it is possible for the transfers to overrun + * the end of the frame. + * + * 3) Command failures are not recovered correctly. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/kernel.h> +#include <sys/endian.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/lock.h> +#include <sys/lockmgr.h> +#if defined(DIAGNOSTIC) && defined(__i386__) && defined(__FreeBSD__) +#include <machine/cpu.h> +#endif +#include <sys/proc.h> +#include <sys/queue.h> +#include <sys/sysctl.h> + +#include <machine/bus.h> +#include <machine/endian.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdivar.h> +#include <dev/usb/usb_mem.h> +#include <dev/usb/usb_quirks.h> + +#include <dev/usb/ehcireg.h> +#include <dev/usb/ehcivar.h> + +#define delay(d) DELAY(d) + +#ifdef USB_DEBUG +#define EHCI_DEBUG USB_DEBUG +#define DPRINTF(x) do { if (ehcidebug) printf x; } while (0) +#define DPRINTFN(n,x) do { if (ehcidebug>(n)) printf x; } while (0) +int ehcidebug = 0; +SYSCTL_NODE(_hw_usb, OID_AUTO, ehci, CTLFLAG_RW, 0, "USB ehci"); +SYSCTL_INT(_hw_usb_ehci, OID_AUTO, debug, CTLFLAG_RW, + &ehcidebug, 0, "ehci debug level"); +#else +#define DPRINTF(x) +#define DPRINTFN(n,x) +#endif + +struct ehci_pipe { + struct usbd_pipe pipe; + + ehci_soft_qh_t *sqh; + union { + ehci_soft_qtd_t *qtd; + /* ehci_soft_itd_t *itd; */ + } tail; + union { + /* Control pipe */ + struct { + usb_dma_t reqdma; + u_int length; + /*ehci_soft_qtd_t *setup, *data, *stat;*/ + } ctl; + /* Interrupt pipe */ + struct { + u_int length; + } intr; + /* Bulk pipe */ + struct { + u_int length; + } bulk; + /* Iso pipe */ + struct { + u_int next_frame; + u_int cur_xfers; + } isoc; + } u; +}; + +static usbd_status ehci_open(usbd_pipe_handle); +static void ehci_poll(struct usbd_bus *); +static void ehci_softintr(void *); +static int ehci_intr1(ehci_softc_t *); +static void ehci_waitintr(ehci_softc_t *, usbd_xfer_handle); +static void ehci_check_intr(ehci_softc_t *, struct ehci_xfer *); +static void ehci_check_qh_intr(ehci_softc_t *, struct ehci_xfer *); +static void ehci_check_itd_intr(ehci_softc_t *, struct ehci_xfer *); +static void ehci_idone(struct ehci_xfer *); +static void ehci_timeout(void *); +static void ehci_timeout_task(void *); +static void ehci_intrlist_timeout(void *); + +static usbd_status ehci_allocm(struct usbd_bus *, usb_dma_t *, u_int32_t); +static void ehci_freem(struct usbd_bus *, usb_dma_t *); + +static usbd_xfer_handle ehci_allocx(struct usbd_bus *); +static void ehci_freex(struct usbd_bus *, usbd_xfer_handle); + +static usbd_status ehci_root_ctrl_transfer(usbd_xfer_handle); +static usbd_status ehci_root_ctrl_start(usbd_xfer_handle); +static void ehci_root_ctrl_abort(usbd_xfer_handle); +static void ehci_root_ctrl_close(usbd_pipe_handle); +static void ehci_root_ctrl_done(usbd_xfer_handle); + +static usbd_status ehci_root_intr_transfer(usbd_xfer_handle); +static usbd_status ehci_root_intr_start(usbd_xfer_handle); +static void ehci_root_intr_abort(usbd_xfer_handle); +static void ehci_root_intr_close(usbd_pipe_handle); +static void ehci_root_intr_done(usbd_xfer_handle); + +static usbd_status ehci_device_ctrl_transfer(usbd_xfer_handle); +static usbd_status ehci_device_ctrl_start(usbd_xfer_handle); +static void ehci_device_ctrl_abort(usbd_xfer_handle); +static void ehci_device_ctrl_close(usbd_pipe_handle); +static void ehci_device_ctrl_done(usbd_xfer_handle); + +static usbd_status ehci_device_bulk_transfer(usbd_xfer_handle); +static usbd_status ehci_device_bulk_start(usbd_xfer_handle); +static void ehci_device_bulk_abort(usbd_xfer_handle); +static void ehci_device_bulk_close(usbd_pipe_handle); +static void ehci_device_bulk_done(usbd_xfer_handle); + +static usbd_status ehci_device_intr_transfer(usbd_xfer_handle); +static usbd_status ehci_device_intr_start(usbd_xfer_handle); +static void ehci_device_intr_abort(usbd_xfer_handle); +static void ehci_device_intr_close(usbd_pipe_handle); +static void ehci_device_intr_done(usbd_xfer_handle); + +static usbd_status ehci_device_isoc_transfer(usbd_xfer_handle); +static usbd_status ehci_device_isoc_start(usbd_xfer_handle); +static void ehci_device_isoc_abort(usbd_xfer_handle); +static void ehci_device_isoc_close(usbd_pipe_handle); +static void ehci_device_isoc_done(usbd_xfer_handle); + +static void ehci_device_clear_toggle(usbd_pipe_handle pipe); +static void ehci_noop(usbd_pipe_handle pipe); + +static int ehci_str(usb_string_descriptor_t *, int, char *); +static void ehci_pcd(ehci_softc_t *, usbd_xfer_handle); +static void ehci_disown(ehci_softc_t *, int, int); + +static ehci_soft_qh_t *ehci_alloc_sqh(ehci_softc_t *); +static void ehci_free_sqh(ehci_softc_t *, ehci_soft_qh_t *); + +static ehci_soft_qtd_t *ehci_alloc_sqtd(ehci_softc_t *); +static void ehci_free_sqtd(ehci_softc_t *, ehci_soft_qtd_t *); +static usbd_status ehci_alloc_sqtd_chain(struct ehci_pipe *, + ehci_softc_t *, int, int, usbd_xfer_handle, + ehci_soft_qtd_t *, ehci_soft_qtd_t *, + ehci_soft_qtd_t **, ehci_soft_qtd_t **); +static void ehci_free_sqtd_chain(ehci_softc_t *, ehci_soft_qh_t *, + ehci_soft_qtd_t *, ehci_soft_qtd_t *); + +static ehci_soft_itd_t *ehci_alloc_itd(ehci_softc_t *); +static void ehci_free_itd(ehci_softc_t *, ehci_soft_itd_t *); +static void ehci_rem_free_itd_chain(ehci_softc_t *, + struct ehci_xfer *); +static void ehci_abort_isoc_xfer(usbd_xfer_handle, usbd_status); + +static usbd_status ehci_device_request(usbd_xfer_handle xfer); + +static usbd_status ehci_device_setintr(ehci_softc_t *, ehci_soft_qh_t *, + int ival); + +static void ehci_add_qh(ehci_softc_t *, ehci_soft_qh_t *, + ehci_soft_qh_t *); +static void ehci_rem_qh(ehci_softc_t *, ehci_soft_qh_t *, + ehci_soft_qh_t *); +static void ehci_activate_qh(ehci_softc_t *sc, ehci_soft_qh_t *, + ehci_soft_qtd_t *); +static void ehci_sync_hc(ehci_softc_t *); + +static void ehci_close_pipe(usbd_pipe_handle, ehci_soft_qh_t *); +static void ehci_abort_xfer(usbd_xfer_handle, usbd_status); + +ehci_softc_t *theehci; + +#define EHCI_NULL(sc) htohc32(sc, EHCI_LINK_TERMINATE) + +#define EHCI_INTR_ENDPT 1 + +#define ehci_add_intr_list(sc, ex) \ + LIST_INSERT_HEAD(&(sc)->sc_intrhead, (ex), inext); +#define ehci_del_intr_list(ex) \ + do { \ + LIST_REMOVE((ex), inext); \ + (ex)->inext.le_prev = NULL; \ + } while (0) +#define ehci_active_intr_list(ex) ((ex)->inext.le_prev != NULL) + +static struct usbd_bus_methods ehci_bus_methods = { + ehci_open, + ehci_softintr, + ehci_poll, + ehci_allocm, + ehci_freem, + ehci_allocx, + ehci_freex, +}; + +static struct usbd_pipe_methods ehci_root_ctrl_methods = { + ehci_root_ctrl_transfer, + ehci_root_ctrl_start, + ehci_root_ctrl_abort, + ehci_root_ctrl_close, + ehci_noop, + ehci_root_ctrl_done, +}; + +static struct usbd_pipe_methods ehci_root_intr_methods = { + ehci_root_intr_transfer, + ehci_root_intr_start, + ehci_root_intr_abort, + ehci_root_intr_close, + ehci_noop, + ehci_root_intr_done, +}; + +static struct usbd_pipe_methods ehci_device_ctrl_methods = { + ehci_device_ctrl_transfer, + ehci_device_ctrl_start, + ehci_device_ctrl_abort, + ehci_device_ctrl_close, + ehci_noop, + ehci_device_ctrl_done, +}; + +static struct usbd_pipe_methods ehci_device_intr_methods = { + ehci_device_intr_transfer, + ehci_device_intr_start, + ehci_device_intr_abort, + ehci_device_intr_close, + ehci_device_clear_toggle, + ehci_device_intr_done, +}; + +static struct usbd_pipe_methods ehci_device_bulk_methods = { + ehci_device_bulk_transfer, + ehci_device_bulk_start, + ehci_device_bulk_abort, + ehci_device_bulk_close, + ehci_device_clear_toggle, + ehci_device_bulk_done, +}; + +static struct usbd_pipe_methods ehci_device_isoc_methods = { + ehci_device_isoc_transfer, + ehci_device_isoc_start, + ehci_device_isoc_abort, + ehci_device_isoc_close, + ehci_noop, + ehci_device_isoc_done, +}; + +usbd_status +ehci_reset(ehci_softc_t *sc) +{ + u_int32_t hcr; + u_int i; + + EOWRITE4(sc, EHCI_USBCMD, EHCI_CMD_HCRESET); + for (i = 0; i < 100; i++) { + usb_delay_ms(&sc->sc_bus, 1); + hcr = EOREAD4(sc, EHCI_USBCMD) & EHCI_CMD_HCRESET; + if (!hcr) { + 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 (USBD_NORMAL_COMPLETION); + } + } + printf("%s: reset timeout\n", device_get_nameunit(sc->sc_bus.bdev)); + return (USBD_IOERROR); +} + +static usbd_status +ehci_hcreset(ehci_softc_t *sc) +{ + u_int32_t hcr; + u_int i; + + EOWRITE4(sc, EHCI_USBCMD, 0); /* Halt controller */ + for (i = 0; i < 100; i++) { + usb_delay_ms(&sc->sc_bus, 1); + hcr = EOREAD4(sc, EHCI_USBSTS) & EHCI_STS_HCH; + if (hcr) + break; + } + if (!hcr) + /* + * 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); +} + +usbd_status +ehci_init(ehci_softc_t *sc) +{ + u_int32_t version, sparams, cparams, hcr; + u_int i; + usbd_status err; + ehci_soft_qh_t *sqh; + u_int ncomp; + int lev; + + DPRINTF(("ehci_init: start\n")); +#ifdef EHCI_DEBUG + theehci = sc; +#endif + + /* NB: must handle byte-order manually before ehci_hcreset */ + + sc->sc_offs = EREAD1(sc, EHCI_CAPLENGTH); + + version = EREAD2(sc, EHCI_HCIVERSION); + device_printf(sc->sc_bus.bdev, "EHCI version %x.%x\n", + version >> 8, version & 0xff); + + sparams = EREAD4(sc, EHCI_HCSPARAMS); + DPRINTF(("ehci_init: sparams=0x%x\n", sparams)); + sc->sc_npcomp = EHCI_HCS_N_PCC(sparams); + ncomp = EHCI_HCS_N_CC(sparams); + if (ncomp != sc->sc_ncomp) { + printf("%s: wrong number of companions (%d != %d)\n", + device_get_nameunit(sc->sc_bus.bdev), + ncomp, sc->sc_ncomp); + if (ncomp < sc->sc_ncomp) + sc->sc_ncomp = ncomp; + } + if (sc->sc_ncomp > 0) { + printf("%s: companion controller%s, %d port%s each:", + device_get_nameunit(sc->sc_bus.bdev), sc->sc_ncomp!=1 ? "s" : "", + EHCI_HCS_N_PCC(sparams), + EHCI_HCS_N_PCC(sparams)!=1 ? "s" : ""); + for (i = 0; i < sc->sc_ncomp; i++) + printf(" %s", device_get_nameunit(sc->sc_comps[i]->bdev)); + printf("\n"); + } + sc->sc_noport = EHCI_HCS_N_PORTS(sparams); + cparams = EREAD4(sc, EHCI_HCCPARAMS); + DPRINTF(("ehci_init: cparams=0x%x\n", cparams)); + + if (EHCI_HCC_64BIT(cparams)) { + /* MUST clear segment register if 64 bit capable. */ + EWRITE4(sc, EHCI_CTRLDSSEGMENT, 0); + } + + sc->sc_bus.usbrev = USBREV_2_0; + + /* Reset the controller */ + DPRINTF(("%s: resetting\n", device_get_nameunit(sc->sc_bus.bdev))); + err = ehci_hcreset(sc); + if (err != USBD_NORMAL_COMPLETION) + return (err); + + /* frame list size at default, read back what we got and use that */ + switch (EHCI_CMD_FLS(EOREAD4(sc, EHCI_USBCMD))) { + case 0: sc->sc_flsize = 1024; break; + case 1: sc->sc_flsize = 512; break; + case 2: sc->sc_flsize = 256; break; + case 3: return (USBD_IOERROR); + } + err = usb_allocmem(&sc->sc_bus, sc->sc_flsize * sizeof(ehci_link_t), + EHCI_FLALIGN_ALIGN, &sc->sc_fldma); + if (err) + return (err); + DPRINTF(("%s: flsize=%d\n", device_get_nameunit(sc->sc_bus.bdev),sc->sc_flsize)); + sc->sc_flist = KERNADDR(&sc->sc_fldma, 0); + + for (i = 0; i < sc->sc_flsize; i++) { + sc->sc_flist[i] = EHCI_NULL(sc); + } + + EOWRITE4(sc, EHCI_PERIODICLISTBASE, DMAADDR(&sc->sc_fldma, 0)); + + sc->sc_softitds = malloc(sc->sc_flsize * sizeof(ehci_soft_itd_t *), + M_USB, M_NOWAIT | M_ZERO); + if (sc->sc_softitds == NULL) + return (ENOMEM); + LIST_INIT(&sc->sc_freeitds); + + /* Set up the bus struct. */ + sc->sc_bus.methods = &ehci_bus_methods; + sc->sc_bus.pipe_size = sizeof(struct ehci_pipe); + + sc->sc_eintrs = EHCI_NORMAL_INTRS; + + /* + * Allocate the interrupt dummy QHs. These are arranged to give + * poll intervals that are powers of 2 times 1ms. + */ + for (i = 0; i < EHCI_INTRQHS; i++) { + sqh = ehci_alloc_sqh(sc); + if (sqh == NULL) { + err = USBD_NOMEM; + goto bad1; + } + sc->sc_islots[i].sqh = sqh; + } + lev = 0; + for (i = 0; i < EHCI_INTRQHS; i++) { + if (i == EHCI_IQHIDX(lev + 1, 0)) + lev++; + sqh = sc->sc_islots[i].sqh; + if (i == 0) { + /* The last (1ms) QH terminates. */ + sqh->qh.qh_link = EHCI_NULL(sc); + sqh->next = NULL; + } else { + /* Otherwise the next QH has half the poll interval */ + sqh->next = + sc->sc_islots[EHCI_IQHIDX(lev - 1, i + 1)].sqh; + sqh->qh.qh_link = htohc32(sc, sqh->next->physaddr | + EHCI_LINK_QH); + } + sqh->qh.qh_endp = htohc32(sc, EHCI_QH_SET_EPS(EHCI_QH_SPEED_HIGH)); + sqh->qh.qh_endphub = htohc32(sc, EHCI_QH_SET_MULT(1)); + sqh->qh.qh_curqtd = EHCI_NULL(sc); + sqh->qh.qh_qtd.qtd_next = EHCI_NULL(sc); + sqh->qh.qh_qtd.qtd_altnext = EHCI_NULL(sc); + sqh->qh.qh_qtd.qtd_status = htohc32(sc, EHCI_QTD_HALTED); + } + /* Point the frame list at the last level (128ms). */ + for (i = 0; i < sc->sc_flsize; i++) { + sc->sc_flist[i] = htohc32(sc, EHCI_LINK_QH | + sc->sc_islots[EHCI_IQHIDX(EHCI_IPOLLRATES - 1, i)].sqh->physaddr); + } + + /* Allocate dummy QH that starts the async list. */ + sqh = ehci_alloc_sqh(sc); + if (sqh == NULL) { + err = USBD_NOMEM; + goto bad1; + } + /* Fill the QH */ + sqh->qh.qh_endp = + htohc32(sc, EHCI_QH_SET_EPS(EHCI_QH_SPEED_HIGH) | EHCI_QH_HRECL); + sqh->qh.qh_link = + htohc32(sc, sqh->physaddr | EHCI_LINK_QH); + sqh->qh.qh_curqtd = EHCI_NULL(sc); + sqh->prev = sqh; /*It's a circular list.. */ + sqh->next = sqh; + /* Fill the overlay qTD */ + sqh->qh.qh_qtd.qtd_next = EHCI_NULL(sc); + sqh->qh.qh_qtd.qtd_altnext = EHCI_NULL(sc); + sqh->qh.qh_qtd.qtd_status = htohc32(sc, 0); +#ifdef EHCI_DEBUG + if (ehcidebug) { + ehci_dump_sqh(sc, sqh); + } +#endif + + /* Point to async list */ + sc->sc_async_head = sqh; + EOWRITE4(sc, EHCI_ASYNCLISTADDR, sqh->physaddr | EHCI_LINK_QH); + + callout_init(&sc->sc_tmo_intrlist, 0); + + lockinit(&sc->sc_doorbell_lock, PZERO, "ehcidb", 0, 0); + + /* Enable interrupts */ + EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs); + + /* Turn on controller */ + EOWRITE4(sc, EHCI_USBCMD, + EHCI_CMD_ITC_2 | /* 2 microframes interrupt delay */ + (EOREAD4(sc, EHCI_USBCMD) & EHCI_CMD_FLS_M) | + EHCI_CMD_ASE | + EHCI_CMD_PSE | + EHCI_CMD_RS); + + /* Take over port ownership */ + EOWRITE4(sc, EHCI_CONFIGFLAG, EHCI_CONF_CF); + + for (i = 0; i < 100; i++) { + usb_delay_ms(&sc->sc_bus, 1); + hcr = EOREAD4(sc, EHCI_USBSTS) & EHCI_STS_HCH; + if (!hcr) + break; + } + if (hcr) { + printf("%s: run timeout\n", device_get_nameunit(sc->sc_bus.bdev)); + return (USBD_IOERROR); + } + + return (USBD_NORMAL_COMPLETION); + +#if 0 + bad2: + ehci_free_sqh(sc, sc->sc_async_head); +#endif + bad1: + usb_freemem(&sc->sc_bus, &sc->sc_fldma); + return (err); +} + +int +ehci_intr(void *v) +{ + ehci_softc_t *sc = v; + + if (sc == NULL || sc->sc_dying) + return (0); + + /* If we get an interrupt while polling, then just ignore it. */ + if (sc->sc_bus.use_polling) { + u_int32_t intrs = EHCI_STS_INTRS(EOREAD4(sc, EHCI_USBSTS)); + + if (intrs) + EOWRITE4(sc, EHCI_USBSTS, intrs); /* Acknowledge */ +#ifdef DIAGNOSTIC + DPRINTFN(16, ("ehci_intr: ignored interrupt while polling\n")); +#endif + return (0); + } + + return (ehci_intr1(sc)); +} + +static int +ehci_intr1(ehci_softc_t *sc) +{ + u_int32_t intrs, eintrs; + + DPRINTFN(20,("ehci_intr1: enter\n")); + + /* In case the interrupt occurs before initialization has completed. */ + if (sc == NULL) { +#ifdef DIAGNOSTIC + printf("ehci_intr1: sc == NULL\n"); +#endif + return (0); + } + + intrs = EHCI_STS_INTRS(EOREAD4(sc, EHCI_USBSTS)); + if (!intrs) + return (0); + + eintrs = intrs & sc->sc_eintrs; + DPRINTFN(7, ("ehci_intr1: sc=%p intrs=0x%x(0x%x) eintrs=0x%x\n", + sc, (u_int)intrs, EOREAD4(sc, EHCI_USBSTS), + (u_int)eintrs)); + if (!eintrs) + return (0); + + EOWRITE4(sc, EHCI_USBSTS, intrs); /* Acknowledge */ + sc->sc_bus.intr_context++; + sc->sc_bus.no_intrs++; + if (eintrs & EHCI_STS_IAA) { + DPRINTF(("ehci_intr1: door bell\n")); + wakeup(&sc->sc_async_head); + eintrs &= ~EHCI_STS_IAA; + } + if (eintrs & (EHCI_STS_INT | EHCI_STS_ERRINT)) { + DPRINTFN(5,("ehci_intr1: %s %s\n", + eintrs & EHCI_STS_INT ? "INT" : "", + eintrs & EHCI_STS_ERRINT ? "ERRINT" : "")); + usb_schedsoftintr(&sc->sc_bus); + eintrs &= ~(EHCI_STS_INT | EHCI_STS_ERRINT); + } + if (eintrs & EHCI_STS_HSE) { + printf("%s: unrecoverable error, controller halted\n", + device_get_nameunit(sc->sc_bus.bdev)); + /* XXX what else */ + } + if (eintrs & EHCI_STS_PCD) { + ehci_pcd(sc, sc->sc_intrxfer); + eintrs &= ~EHCI_STS_PCD; + } + + sc->sc_bus.intr_context--; + + if (eintrs != 0) { + /* Block unprocessed interrupts. */ + sc->sc_eintrs &= ~eintrs; + EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs); + printf("%s: blocking intrs 0x%x\n", + device_get_nameunit(sc->sc_bus.bdev), eintrs); + } + + return (1); +} + +/* + * XXX write back xfer data for architectures with a write-back + * data cache; this is a hack because usb is mis-architected + * in blindly mixing bus_dma w/ PIO. + */ +static __inline void +hacksync(usbd_xfer_handle xfer) +{ + bus_dma_tag_t tag; + struct usb_dma_mapping *dmap; + + if (xfer->length == 0) + return; + tag = xfer->pipe->device->bus->buffer_dmatag; + dmap = &xfer->dmamap; + bus_dmamap_sync(tag, dmap->map, BUS_DMASYNC_PREWRITE); +} + +void +ehci_pcd(ehci_softc_t *sc, usbd_xfer_handle xfer) +{ + usbd_pipe_handle pipe; + u_char *p; + int i, m; + + if (xfer == NULL) { + /* Just ignore the change. */ + return; + } + + pipe = xfer->pipe; + + p = xfer->buffer; + m = min(sc->sc_noport, xfer->length * 8 - 1); + memset(p, 0, xfer->length); + for (i = 1; i <= m; i++) { + /* Pick out CHANGE bits from the status reg. */ + if (EOREAD4(sc, EHCI_PORTSC(i)) & EHCI_PS_CLEAR) + p[i/8] |= 1 << (i%8); + } + DPRINTF(("ehci_pcd: change=0x%02x\n", *p)); + xfer->actlen = xfer->length; + xfer->status = USBD_NORMAL_COMPLETION; + + hacksync(xfer); /* XXX to compensate for usb_transfer_complete */ + usb_transfer_complete(xfer); +} + +void +ehci_softintr(void *v) +{ + ehci_softc_t *sc = v; + struct ehci_xfer *ex, *nextex; + + DPRINTFN(10,("%s: ehci_softintr (%d)\n", device_get_nameunit(sc->sc_bus.bdev), + sc->sc_bus.intr_context)); + + sc->sc_bus.intr_context++; + + /* + * The only explanation I can think of for why EHCI is as brain dead + * as UHCI interrupt-wise is that Intel was involved in both. + * An interrupt just tells us that something is done, we have no + * clue what, so we need to scan through all active transfers. :-( + */ + for (ex = LIST_FIRST(&sc->sc_intrhead); ex; ex = nextex) { + nextex = LIST_NEXT(ex, inext); + ehci_check_intr(sc, ex); + } + + /* Schedule a callout to catch any dropped transactions. */ + if ((sc->sc_flags & EHCI_SCFLG_LOSTINTRBUG) && + !LIST_EMPTY(&sc->sc_intrhead)) + callout_reset(&sc->sc_tmo_intrlist, hz / 5, + ehci_intrlist_timeout, sc); + +#ifdef USB_USE_SOFTINTR + if (sc->sc_softwake) { + sc->sc_softwake = 0; + wakeup(&sc->sc_softwake); + } +#endif /* USB_USE_SOFTINTR */ + + sc->sc_bus.intr_context--; +} + +/* Check for an interrupt. */ +void +ehci_check_intr(ehci_softc_t *sc, struct ehci_xfer *ex) +{ + int attr; + + DPRINTFN(/*15*/2, ("ehci_check_intr: ex=%p\n", ex)); + + attr = ex->xfer.pipe->endpoint->edesc->bmAttributes; + if (UE_GET_XFERTYPE(attr) == UE_ISOCHRONOUS) + ehci_check_itd_intr(sc, ex); + else + ehci_check_qh_intr(sc, ex); +} + +void +ehci_check_qh_intr(ehci_softc_t *sc, struct ehci_xfer *ex) +{ + ehci_soft_qtd_t *sqtd, *lsqtd; + u_int32_t status; + + if (ex->sqtdstart == NULL) { + printf("ehci_check_qh_intr: not valid sqtd\n"); + return; + } + lsqtd = ex->sqtdend; +#ifdef DIAGNOSTIC + if (lsqtd == NULL) { + printf("ehci_check_qh_intr: lsqtd==0\n"); + return; + } +#endif + /* + * If the last TD is still active we need to check whether there + * is a an error somewhere in the middle, or whether there was a + * short packet (SPD and not ACTIVE). + */ + if (hc32toh(sc, lsqtd->qtd.qtd_status) & EHCI_QTD_ACTIVE) { + DPRINTFN(12, ("ehci_check_intr: active ex=%p\n", ex)); + for (sqtd = ex->sqtdstart; sqtd != lsqtd; sqtd=sqtd->nextqtd) { + status = hc32toh(sc, sqtd->qtd.qtd_status); + /* If there's an active QTD the xfer isn't done. */ + if (status & EHCI_QTD_ACTIVE) + break; + /* Any kind of error makes the xfer done. */ + if (status & EHCI_QTD_HALTED) + goto done; + /* We want short packets, and it is short: it's done */ + if (EHCI_QTD_GET_BYTES(status) != 0) + goto done; + } + DPRINTFN(12, ("ehci_check_intr: ex=%p std=%p still active\n", + ex, ex->sqtdstart)); + return; + } + done: + DPRINTFN(12, ("ehci_check_intr: ex=%p done\n", ex)); + callout_stop(&ex->xfer.timeout_handle); + usb_rem_task(ex->xfer.pipe->device, &ex->abort_task); + ehci_idone(ex); +} + +void +ehci_check_itd_intr(ehci_softc_t *sc, struct ehci_xfer *ex) +{ + ehci_soft_itd_t *itd; + int i; + + if (ex->itdstart == NULL) { + printf("ehci_check_itd_intr: not valid itd\n"); + return; + } + + itd = ex->itdend; +#ifdef DIAGNOSTIC + if (itd == NULL) { + printf("ehci_check_itd_intr: itdend == 0\n"); + return; + } +#endif + + /* + * Step 1, check no active transfers in last itd, meaning we're finished + */ + for (i = 0; i < 8; i++) { + if (hc32toh(sc, itd->itd.itd_ctl[i]) & EHCI_ITD_ACTIVE) + break; + } + + if (i == 8) { + goto done; /* All 8 descriptors inactive, it's done */ + } + + /* + * Step 2, check for errors in status bits, throughout chain... + */ + + DPRINTFN(12, ("ehci_check_itd_intr: active ex=%p\n", ex)); + + for (itd = ex->itdstart; itd != ex->itdend; itd = itd->xfer_next) { + for (i = 0; i < 8; i++) { + if (hc32toh(sc, itd->itd.itd_ctl[i]) & (EHCI_ITD_BUF_ERR | + EHCI_ITD_BABBLE | EHCI_ITD_ERROR)) + break; + } + if (i != 8) { /* Error in one of the itds */ + goto done; + } + } /* itd search loop */ + + DPRINTFN(12, ("ehci_check_itd_intr: ex %p itd %p still active\n", ex, + ex->itdstart)); + return; +done: + DPRINTFN(12, ("ehci_check_itd_intr: ex=%p done\n", ex)); + callout_stop(&ex->xfer.timeout_handle); + usb_rem_task(ex->xfer.pipe->device, &ex->abort_task); + ehci_idone(ex); +} + +void +ehci_idone(struct ehci_xfer *ex) +{ + usbd_xfer_handle xfer = &ex->xfer; + struct ehci_pipe *epipe = (struct ehci_pipe *)xfer->pipe; + ehci_softc_t *sc = (ehci_softc_t *)epipe->pipe.device->bus; + ehci_soft_qtd_t *sqtd, *lsqtd; + u_int32_t status = 0, nstatus = 0; + ehci_physaddr_t nextphys, altnextphys; + int actlen, cerr; + + DPRINTFN(/*12*/2, ("ehci_idone: ex=%p\n", ex)); +#ifdef DIAGNOSTIC + { + int s = splhigh(); + if (ex->isdone) { + splx(s); +#ifdef EHCI_DEBUG + printf("ehci_idone: ex is done!\n "); + ehci_dump_exfer(ex); +#else + printf("ehci_idone: ex=%p is done!\n", ex); +#endif + return; + } + ex->isdone = 1; + splx(s); + } +#endif + + if (xfer->status == USBD_CANCELLED || + xfer->status == USBD_TIMEOUT) { + DPRINTF(("ehci_idone: aborted xfer=%p\n", xfer)); + return; + } + +#ifdef EHCI_DEBUG + DPRINTFN(/*10*/2, ("ehci_idone: xfer=%p, pipe=%p ready\n", xfer, epipe)); + if (ehcidebug > 10) + ehci_dump_sqtds(sc, ex->sqtdstart); +#endif + + /* + * Make sure that the QH overlay qTD does not reference any + * of the qTDs we are about to free. This is probably only + * necessary if the transfer is marked as HALTED. + */ + nextphys = EHCI_LINK_ADDR(hc32toh(sc, epipe->sqh->qh.qh_qtd.qtd_next)); + altnextphys = + EHCI_LINK_ADDR(hc32toh(sc, epipe->sqh->qh.qh_qtd.qtd_altnext)); + for (sqtd = ex->sqtdstart; sqtd != ex->sqtdend->nextqtd; + sqtd = sqtd->nextqtd) { + if (sqtd->physaddr == nextphys) { + epipe->sqh->qh.qh_qtd.qtd_next = + htohc32(sc, ex->sqtdend->nextqtd->physaddr); + DPRINTFN(4, ("ehci_idone: updated overlay next ptr\n")); + + } + if (sqtd->physaddr == altnextphys) { + DPRINTFN(4, + ("ehci_idone: updated overlay altnext ptr\n")); + epipe->sqh->qh.qh_qtd.qtd_altnext = + htohc32(sc, ex->sqtdend->nextqtd->physaddr); + } + } + + /* The transfer is done, compute actual length and status. */ + if (UE_GET_XFERTYPE(xfer->pipe->endpoint->edesc->bmAttributes) + == UE_ISOCHRONOUS) { + /* Isoc transfer */ + struct ehci_soft_itd *itd; + int i, nframes, len, uframes; + + nframes = 0; + actlen = 0; + + switch (xfer->pipe->endpoint->edesc->bInterval) { + case 0: + panic("ehci: isoc xfer suddenly has 0 bInterval, " + "invalid\n"); + case 1: + uframes = 1; + break; + case 2: + uframes = 2; + break; + case 3: + uframes = 4; + break; + default: + uframes = 8; + break; + } + + for (itd = ex->itdstart; itd != NULL; itd = itd->xfer_next) { + for (i = 0; i < 8; i += uframes) { + /* XXX - driver didn't fill in the frame full + * of uframes. This leads to scheduling + * inefficiencies, but working around + * this doubles complexity of tracking + * an xfer. + */ + if (nframes >= xfer->nframes) + break; + + status = hc32toh(sc, itd->itd.itd_ctl[i]); + len = EHCI_ITD_GET_LEN(status); + xfer->frlengths[nframes++] = len; + actlen += len; + } + if (nframes >= xfer->nframes) + break; + } + xfer->actlen = actlen; + xfer->status = USBD_NORMAL_COMPLETION; + + goto end; + } + + /* Continue processing xfers using queue heads */ + + lsqtd = ex->sqtdend; + actlen = 0; + for (sqtd = ex->sqtdstart; sqtd != lsqtd->nextqtd; + sqtd =sqtd->nextqtd) { + nstatus = hc32toh(sc, sqtd->qtd.qtd_status); + if (nstatus & EHCI_QTD_ACTIVE) + break; + + status = nstatus; + /* halt is ok if descriptor is last, and complete */ + if (sqtd == lsqtd && EHCI_QTD_GET_BYTES(status) == 0) + status &= ~EHCI_QTD_HALTED; + if (EHCI_QTD_GET_PID(status) != EHCI_QTD_PID_SETUP) + actlen += sqtd->len - EHCI_QTD_GET_BYTES(status); + } + + cerr = EHCI_QTD_GET_CERR(status); + DPRINTFN(/*10*/2, ("ehci_idone: len=%d, actlen=%d, cerr=%d, " + "status=0x%x\n", xfer->length, actlen, cerr, status)); + xfer->actlen = actlen; + if ((status & EHCI_QTD_HALTED) != 0) { + DPRINTFN(2, + ("ehci_idone: error, addr=%d, endpt=0x%02x, status %b\n", + xfer->pipe->device->address, + xfer->pipe->endpoint->edesc->bEndpointAddress, + status, EHCI_QTD_STATUS_BITS)); + if ((status & EHCI_QTD_BABBLE) == 0 && cerr > 0) + xfer->status = USBD_STALLED; + else + xfer->status = USBD_IOERROR; /* more info XXX */ + } else { + xfer->status = USBD_NORMAL_COMPLETION; + } +#ifdef EHCI_DEBUG + if (ehcidebug > 2) { + ehci_dump_sqh(sc, epipe->sqh); + ehci_dump_sqtds(sc, ex->sqtdstart); + } +#endif +end: + /* XXX transfer_complete memcpys out transfer data (for in endpoints) + * during this call, before methods->done is called: dma sync required + * beforehand? + */ + usb_transfer_complete(xfer); + DPRINTFN(/*12*/2, ("ehci_idone: ex=%p done\n", ex)); +} + +/* + * Wait here until controller claims to have an interrupt. + * Then call ehci_intr and return. Use timeout to avoid waiting + * too long. + */ +void +ehci_waitintr(ehci_softc_t *sc, usbd_xfer_handle xfer) +{ + int timo = xfer->timeout; + int usecs; + u_int32_t intrs; + + xfer->status = USBD_IN_PROGRESS; + for (usecs = timo * 1000000 / hz; usecs > 0; usecs -= 1000) { + usb_delay_ms(&sc->sc_bus, 1); + if (sc->sc_dying) + break; + intrs = EHCI_STS_INTRS(EOREAD4(sc, EHCI_USBSTS)) & + sc->sc_eintrs; + DPRINTFN(15,("ehci_waitintr: 0x%04x\n", intrs)); +#ifdef EHCI_DEBUG + if (ehcidebug > 15) + ehci_dump_regs(sc); +#endif + if (intrs) { + ehci_intr1(sc); + if (xfer->status != USBD_IN_PROGRESS) + return; + } + } + + /* Timeout */ + DPRINTF(("ehci_waitintr: timeout\n")); + xfer->status = USBD_TIMEOUT; + usb_transfer_complete(xfer); + /* XXX should free TD */ +} + +void +ehci_poll(struct usbd_bus *bus) +{ + ehci_softc_t *sc = (ehci_softc_t *)bus; +#ifdef EHCI_DEBUG + static int last; + int new; + new = EHCI_STS_INTRS(EOREAD4(sc, EHCI_USBSTS)); + if (new != last) { + DPRINTFN(10,("ehci_poll: intrs=0x%04x\n", new)); + last = new; + } +#endif + + if (EOREAD4(sc, EHCI_USBSTS) & sc->sc_eintrs) + ehci_intr1(sc); +} + +int +ehci_detach(struct ehci_softc *sc, int flags) +{ + int rv = 0; + + sc->sc_dying = 1; + + EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs); + (void) ehci_hcreset(sc); + callout_stop(&sc->sc_tmo_intrlist); + + usb_delay_ms(&sc->sc_bus, 300); /* XXX let stray task complete */ + + usb_freemem(&sc->sc_bus, &sc->sc_fldma); + /* XXX free other data structures XXX */ + + return (rv); +} + +/* + * Handle suspend/resume. + * + * We need to switch to polling mode here, because this routine is + * called from an interrupt context. This is all right since we + * are almost suspended anyway. + */ +void +ehci_power(int why, void *v) +{ + ehci_softc_t *sc = v; + u_int32_t cmd, hcr; + int s, i; + +#ifdef EHCI_DEBUG + DPRINTF(("ehci_power: sc=%p, why=%d\n", sc, why)); + if (ehcidebug > 0) + ehci_dump_regs(sc); +#endif + + s = splhardusb(); + switch (why) { + case PWR_SUSPEND: + case PWR_STANDBY: + sc->sc_bus.use_polling++; + + for (i = 1; i <= sc->sc_noport; i++) { + cmd = EOREAD4(sc, EHCI_PORTSC(i)); + if ((cmd & EHCI_PS_PO) == 0 && + (cmd & EHCI_PS_PE) == EHCI_PS_PE) + EOWRITE4(sc, EHCI_PORTSC(i), + cmd | EHCI_PS_SUSP); + } + + sc->sc_cmd = EOREAD4(sc, EHCI_USBCMD); + + cmd = sc->sc_cmd & ~(EHCI_CMD_ASE | EHCI_CMD_PSE); + EOWRITE4(sc, EHCI_USBCMD, cmd); + + for (i = 0; i < 100; i++) { + hcr = EOREAD4(sc, EHCI_USBSTS) & + (EHCI_STS_ASS | EHCI_STS_PSS); + if (hcr == 0) + break; + + usb_delay_ms(&sc->sc_bus, 1); + } + if (hcr != 0) { + printf("%s: reset timeout\n", + device_get_nameunit(sc->sc_bus.bdev)); + } + + cmd &= ~EHCI_CMD_RS; + EOWRITE4(sc, EHCI_USBCMD, cmd); + + for (i = 0; i < 100; i++) { + hcr = EOREAD4(sc, EHCI_USBSTS) & EHCI_STS_HCH; + if (hcr == EHCI_STS_HCH) + break; + + usb_delay_ms(&sc->sc_bus, 1); + } + if (hcr != EHCI_STS_HCH) { + printf("%s: config timeout\n", + device_get_nameunit(sc->sc_bus.bdev)); + } + + sc->sc_bus.use_polling--; + break; + + case PWR_RESUME: + sc->sc_bus.use_polling++; + + /* restore things in case the bios sucks */ + EOWRITE4(sc, EHCI_CTRLDSSEGMENT, 0); + EOWRITE4(sc, EHCI_PERIODICLISTBASE, DMAADDR(&sc->sc_fldma, 0)); + EOWRITE4(sc, EHCI_ASYNCLISTADDR, + sc->sc_async_head->physaddr | EHCI_LINK_QH); + EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs); + + hcr = 0; + for (i = 1; i <= sc->sc_noport; i++) { + cmd = EOREAD4(sc, EHCI_PORTSC(i)); + if ((cmd & EHCI_PS_PO) == 0 && + (cmd & EHCI_PS_SUSP) == EHCI_PS_SUSP) { + EOWRITE4(sc, EHCI_PORTSC(i), + cmd | EHCI_PS_FPR); + hcr = 1; + } + } + + if (hcr) { + usb_delay_ms(&sc->sc_bus, USB_RESUME_WAIT); + + for (i = 1; i <= sc->sc_noport; i++) { + cmd = EOREAD4(sc, EHCI_PORTSC(i)); + if ((cmd & EHCI_PS_PO) == 0 && + (cmd & EHCI_PS_SUSP) == EHCI_PS_SUSP) + EOWRITE4(sc, EHCI_PORTSC(i), + cmd & ~EHCI_PS_FPR); + } + } + + EOWRITE4(sc, EHCI_USBCMD, sc->sc_cmd); + + for (i = 0; i < 100; i++) { + hcr = EOREAD4(sc, EHCI_USBSTS) & EHCI_STS_HCH; + if (hcr != EHCI_STS_HCH) + break; + + usb_delay_ms(&sc->sc_bus, 1); + } + if (hcr == EHCI_STS_HCH) { + printf("%s: config timeout\n", + device_get_nameunit(sc->sc_bus.bdev)); + } + + usb_delay_ms(&sc->sc_bus, USB_RESUME_WAIT); + + sc->sc_bus.use_polling--; + break; + case PWR_SOFTSUSPEND: + case PWR_SOFTSTANDBY: + case PWR_SOFTRESUME: + break; + } + splx(s); + +#ifdef EHCI_DEBUG + DPRINTF(("ehci_power: sc=%p\n", sc)); + if (ehcidebug > 0) + ehci_dump_regs(sc); +#endif +} + +/* + * Shut down the controller when the system is going down. + */ +void +ehci_shutdown(void *v) +{ + ehci_softc_t *sc = v; + + DPRINTF(("ehci_shutdown: stopping the HC\n")); + (void) ehci_hcreset(sc); +} + +usbd_status +ehci_allocm(struct usbd_bus *bus, usb_dma_t *dma, u_int32_t size) +{ + usbd_status err; + + err = usb_allocmem(bus, size, 0, dma); +#ifdef EHCI_DEBUG + if (err) + printf("ehci_allocm: usb_allocmem()=%d\n", err); +#endif + return (err); +} + +void +ehci_freem(struct usbd_bus *bus, usb_dma_t *dma) +{ + usb_freemem(bus, dma); +} + +usbd_xfer_handle +ehci_allocx(struct usbd_bus *bus) +{ + struct ehci_softc *sc = (struct ehci_softc *)bus; + usbd_xfer_handle xfer; + + xfer = STAILQ_FIRST(&sc->sc_free_xfers); + if (xfer != NULL) { + STAILQ_REMOVE_HEAD(&sc->sc_free_xfers, next); +#ifdef DIAGNOSTIC + if (xfer->busy_free != XFER_FREE) { + printf("ehci_allocx: xfer=%p not free, 0x%08x\n", xfer, + xfer->busy_free); + } +#endif + } else { + xfer = malloc(sizeof(struct ehci_xfer), M_USB, M_NOWAIT); + } + if (xfer != NULL) { + memset(xfer, 0, sizeof(struct ehci_xfer)); + usb_init_task(&EXFER(xfer)->abort_task, ehci_timeout_task, + xfer); + EXFER(xfer)->ehci_xfer_flags = 0; +#ifdef DIAGNOSTIC + EXFER(xfer)->isdone = 1; + xfer->busy_free = XFER_BUSY; +#endif + } + return (xfer); +} + +void +ehci_freex(struct usbd_bus *bus, usbd_xfer_handle xfer) +{ + struct ehci_softc *sc = (struct ehci_softc *)bus; + +#ifdef DIAGNOSTIC + if (xfer->busy_free != XFER_BUSY) { + printf("ehci_freex: xfer=%p not busy, 0x%08x\n", xfer, + xfer->busy_free); + return; + } + xfer->busy_free = XFER_FREE; + if (!EXFER(xfer)->isdone) { + printf("ehci_freex: !isdone\n"); + return; + } +#endif + STAILQ_INSERT_HEAD(&sc->sc_free_xfers, xfer, next); +} + +static void +ehci_device_clear_toggle(usbd_pipe_handle pipe) +{ + struct ehci_pipe *epipe = (struct ehci_pipe *)pipe; + ehci_softc_t *sc = (ehci_softc_t *)epipe->pipe.device->bus; + + DPRINTF(("ehci_device_clear_toggle: epipe=%p status=0x%x\n", + epipe, epipe->sqh->qh.qh_qtd.qtd_status)); +#ifdef USB_DEBUG + if (ehcidebug) + usbd_dump_pipe(pipe); +#endif + KASSERT((epipe->sqh->qh.qh_qtd.qtd_status & + htohc32(sc, EHCI_QTD_ACTIVE)) == 0, + ("ehci_device_clear_toggle: queue active")); + epipe->sqh->qh.qh_qtd.qtd_status &= htohc32(sc, ~EHCI_QTD_TOGGLE_MASK); +} + +static void +ehci_noop(usbd_pipe_handle pipe) +{ +} + +usbd_status +ehci_open(usbd_pipe_handle pipe) +{ + usbd_device_handle dev = pipe->device; + ehci_softc_t *sc = (ehci_softc_t *)dev->bus; + usb_endpoint_descriptor_t *ed = pipe->endpoint->edesc; + u_int8_t addr = dev->address; + u_int8_t xfertype = ed->bmAttributes & UE_XFERTYPE; + struct ehci_pipe *epipe = (struct ehci_pipe *)pipe; + ehci_soft_qh_t *sqh; + usbd_status err; + int s; + int ival, speed, naks; + int hshubaddr, hshubport; + + DPRINTFN(1, ("ehci_open: pipe=%p, xfertype=%d, addr=%d, endpt=%d (%d)\n", + pipe, addr, ed->bEndpointAddress, sc->sc_addr, xfertype)); + + if (dev->myhsport) { + hshubaddr = dev->myhsport->parent->address; + hshubport = dev->myhsport->portno; + } else { + hshubaddr = 0; + hshubport = 0; + } + + if (sc->sc_dying) + return (USBD_IOERROR); + + if (addr == sc->sc_addr) { + switch (ed->bEndpointAddress) { + case USB_CONTROL_ENDPOINT: + pipe->methods = &ehci_root_ctrl_methods; + break; + case UE_DIR_IN | EHCI_INTR_ENDPT: + pipe->methods = &ehci_root_intr_methods; + break; + default: + DPRINTF(("ehci_open: bad bEndpointAddress 0x%02x\n", + ed->bEndpointAddress)); + return (USBD_INVAL); + } + return (USBD_NORMAL_COMPLETION); + } + + /* XXX All this stuff is only valid for async. */ + switch (dev->speed) { + case USB_SPEED_LOW: speed = EHCI_QH_SPEED_LOW; break; + case USB_SPEED_FULL: speed = EHCI_QH_SPEED_FULL; break; + case USB_SPEED_HIGH: speed = EHCI_QH_SPEED_HIGH; break; + default: panic("ehci_open: bad device speed %d", dev->speed); + } + if (speed != EHCI_QH_SPEED_HIGH && xfertype == UE_ISOCHRONOUS) { + printf("%s: *** Error: opening low/full speed isoc device on" + "ehci, this does not work yet. Feel free to implement\n", + device_get_nameunit(sc->sc_bus.bdev)); + DPRINTFN(1,("ehci_open: hshubaddr=%d hshubport=%d\n", + hshubaddr, hshubport)); + return USBD_INVAL; + } + + naks = 8; /* XXX */ + /* Allocate sqh for everything, save isoc xfers */ + if (xfertype != UE_ISOCHRONOUS) { + sqh = ehci_alloc_sqh(sc); + if (sqh == NULL) + goto bad0; + /* qh_link filled when the QH is added */ + sqh->qh.qh_endp = htohc32(sc, + EHCI_QH_SET_ADDR(addr) | + EHCI_QH_SET_ENDPT(UE_GET_ADDR(ed->bEndpointAddress)) | + EHCI_QH_SET_EPS(speed) | + (xfertype == UE_CONTROL ? EHCI_QH_DTC : 0) | + EHCI_QH_SET_MPL(UGETW(ed->wMaxPacketSize)) | + (speed != EHCI_QH_SPEED_HIGH && xfertype == UE_CONTROL ? + EHCI_QH_CTL : 0) | + EHCI_QH_SET_NRL(naks) + ); + sqh->qh.qh_endphub = htohc32(sc, + EHCI_QH_SET_MULT(1) | + EHCI_QH_SET_HUBA(hshubaddr) | + EHCI_QH_SET_PORT(hshubport) | + EHCI_QH_SET_CMASK(0x1c) | + EHCI_QH_SET_SMASK(xfertype == UE_INTERRUPT ? 0x01 : 0) + ); + sqh->qh.qh_curqtd = EHCI_NULL(sc); + /* The overlay qTD was already set up by ehci_alloc_sqh(). */ + sqh->qh.qh_qtd.qtd_status = + htohc32(sc, EHCI_QTD_SET_TOGGLE(pipe->endpoint->savedtoggle)); + epipe->sqh = sqh; + } else { + sqh = NULL; + } + + switch (xfertype) { + case UE_CONTROL: + err = usb_allocmem(&sc->sc_bus, sizeof(usb_device_request_t), + 0, &epipe->u.ctl.reqdma); +#ifdef EHCI_DEBUG + if (err) + printf("ehci_open: usb_allocmem()=%d\n", err); +#endif + if (err) + goto bad1; + pipe->methods = &ehci_device_ctrl_methods; + s = splusb(); + ehci_add_qh(sc, sqh, sc->sc_async_head); + splx(s); + break; + case UE_BULK: + pipe->methods = &ehci_device_bulk_methods; + s = splusb(); + ehci_add_qh(sc, sqh, sc->sc_async_head); + splx(s); + break; + case UE_INTERRUPT: + pipe->methods = &ehci_device_intr_methods; + ival = pipe->interval; + if (ival == USBD_DEFAULT_INTERVAL) + ival = ed->bInterval; + return (ehci_device_setintr(sc, sqh, ival)); + case UE_ISOCHRONOUS: + pipe->methods = &ehci_device_isoc_methods; + if (ed->bInterval == 0 || ed->bInterval > 16) { + printf("ehci: opening pipe with invalid bInterval\n"); + err = USBD_INVAL; + goto bad1; + } + if (UGETW(ed->wMaxPacketSize) == 0) { + printf("ehci: zero length endpoint open request\n"); + err = USBD_INVAL; + goto bad1; + } + epipe->u.isoc.next_frame = 0; + epipe->u.isoc.cur_xfers = 0; + break; + default: + DPRINTF(("ehci: bad xfer type %d\n", xfertype)); + return (USBD_INVAL); + } + return (USBD_NORMAL_COMPLETION); + + bad1: + if (sqh != NULL) + ehci_free_sqh(sc, sqh); + return (err); + bad0: + return (USBD_NOMEM); +} + +/* + * Add an ED to the schedule. Called at splusb(). + * If in the async schedule, it will always have a next. + * If in the intr schedule it may not. + */ +void +ehci_add_qh(ehci_softc_t *sc, ehci_soft_qh_t *sqh, ehci_soft_qh_t *head) +{ + SPLUSBCHECK; + + sqh->next = head->next; + sqh->prev = head; + sqh->qh.qh_link = head->qh.qh_link; + head->next = sqh; + if (sqh->next) + sqh->next->prev = sqh; + head->qh.qh_link = htohc32(sc, sqh->physaddr | EHCI_LINK_QH); + +#ifdef EHCI_DEBUG + if (ehcidebug > 5) { + printf("ehci_add_qh:\n"); + ehci_dump_sqh(sc, sqh); + } +#endif +} + +/* + * Remove an ED from the schedule. Called at splusb(). + * Will always have a 'next' if it's in the async list as it's circular. + */ +void +ehci_rem_qh(ehci_softc_t *sc, ehci_soft_qh_t *sqh, ehci_soft_qh_t *head) +{ + SPLUSBCHECK; + /* XXX */ + sqh->prev->qh.qh_link = sqh->qh.qh_link; + sqh->prev->next = sqh->next; + if (sqh->next) + sqh->next->prev = sqh->prev; + ehci_sync_hc(sc); +} + +/* Restart a QH following the addition of a qTD. */ +void +ehci_activate_qh(ehci_softc_t *sc, ehci_soft_qh_t *sqh, ehci_soft_qtd_t *sqtd) +{ + KASSERT((sqtd->qtd.qtd_status & htohc32(sc, EHCI_QTD_ACTIVE)) == 0, + ("ehci_activate_qh: already active")); + + /* + * When a QH is idle, the overlay qTD should be marked as not + * halted and not active. This causes the host controller to + * retrieve the real qTD on each pass (rather than just examinig + * the overlay), so it will notice when we activate the qTD. + */ + if (sqtd == sqh->sqtd) { + /* Check that the hardware is in the state we expect. */ + if (EHCI_LINK_ADDR(hc32toh(sc, sqh->qh.qh_qtd.qtd_next)) != + sqtd->physaddr) { +#ifdef EHCI_DEBUG + printf("ehci_activate_qh: unexpected next ptr\n"); + ehci_dump_sqh(sc, sqh); + ehci_dump_sqtds(sc, sqh->sqtd); +#endif + sqh->qh.qh_qtd.qtd_next = htohc32(sc, sqtd->physaddr); + sqh->qh.qh_qtd.qtd_altnext = EHCI_NULL(sc); + } + /* Ensure the flags are correct. */ + sqh->qh.qh_qtd.qtd_status &= htohc32(sc, EHCI_QTD_PINGSTATE | + EHCI_QTD_TOGGLE_MASK); + } + + /* Now activate the qTD. */ + sqtd->qtd.qtd_status |= htohc32(sc, EHCI_QTD_ACTIVE); +} + +/* + * Ensure that the HC has released all references to the QH. We do this + * by asking for a Async Advance Doorbell interrupt and then we wait for + * the interrupt. + * To make this easier we first obtain exclusive use of the doorbell. + */ +void +ehci_sync_hc(ehci_softc_t *sc) +{ + int s, error; + + if (sc->sc_dying) { + DPRINTFN(2,("ehci_sync_hc: dying\n")); + return; + } + DPRINTFN(2,("ehci_sync_hc: enter\n")); + /* get doorbell */ + lockmgr(&sc->sc_doorbell_lock, LK_EXCLUSIVE, NULL); + s = splhardusb(); + /* ask for doorbell */ + EOWRITE4(sc, EHCI_USBCMD, EOREAD4(sc, EHCI_USBCMD) | EHCI_CMD_IAAD); + DPRINTFN(1,("ehci_sync_hc: cmd=0x%08x sts=0x%08x\n", + EOREAD4(sc, EHCI_USBCMD), EOREAD4(sc, EHCI_USBSTS))); + error = tsleep(&sc->sc_async_head, PZERO, "ehcidi", hz); /* bell wait */ + DPRINTFN(1,("ehci_sync_hc: cmd=0x%08x sts=0x%08x\n", + EOREAD4(sc, EHCI_USBCMD), EOREAD4(sc, EHCI_USBSTS))); + splx(s); + /* release doorbell */ + lockmgr(&sc->sc_doorbell_lock, LK_RELEASE, NULL); +#ifdef DIAGNOSTIC + if (error) + printf("ehci_sync_hc: tsleep() = %d\n", error); +#endif + DPRINTFN(2,("ehci_sync_hc: exit\n")); +} + +/*Call at splusb*/ +void +ehci_rem_free_itd_chain(ehci_softc_t *sc, struct ehci_xfer *exfer) +{ + struct ehci_soft_itd *itd, *prev; + + prev = NULL; + + if (exfer->itdstart == NULL || exfer->itdend == NULL) + panic("ehci isoc xfer being freed, but with no itd chain\n"); + + for (itd = exfer->itdstart; itd != NULL; itd = itd->xfer_next) { + prev = itd->u.frame_list.prev; + /* Unlink itd from hardware chain, or frame array */ + if (prev == NULL) { /* We're at the table head */ + sc->sc_softitds[itd->slot] = itd->u.frame_list.next; + sc->sc_flist[itd->slot] = itd->itd.itd_next; + + if (itd->u.frame_list.next != NULL) + itd->u.frame_list.next->u.frame_list.prev = + NULL; + } else { + /* XXX this part is untested... */ + prev->itd.itd_next = itd->itd.itd_next; + prev->u.frame_list.next = itd->u.frame_list.next; + if (itd->u.frame_list.next != NULL) + itd->u.frame_list.next->u.frame_list.prev = + prev; + } + } + + prev = NULL; + for (itd = exfer->itdstart; itd != NULL; itd = itd->xfer_next) { + if (prev != NULL) + ehci_free_itd(sc, prev); + prev = itd; + } + if (prev) + ehci_free_itd(sc, prev); + exfer->itdstart = NULL; + exfer->itdend = NULL; +} + +/***********/ + +/* + * Data structures and routines to emulate the root hub. + */ +static usb_device_descriptor_t ehci_devd = { + USB_DEVICE_DESCRIPTOR_SIZE, + UDESC_DEVICE, /* type */ + {0x00, 0x02}, /* USB version */ + UDCLASS_HUB, /* class */ + UDSUBCLASS_HUB, /* subclass */ + UDPROTO_HSHUBSTT, /* protocol */ + 64, /* max packet */ + {0},{0},{0x00,0x01}, /* device id */ + 1,2,0, /* string indicies */ + 1 /* # of configurations */ +}; + +static usb_device_qualifier_t ehci_odevd = { + USB_DEVICE_DESCRIPTOR_SIZE, + UDESC_DEVICE_QUALIFIER, /* type */ + {0x00, 0x02}, /* USB version */ + UDCLASS_HUB, /* class */ + UDSUBCLASS_HUB, /* subclass */ + UDPROTO_FSHUB, /* protocol */ + 64, /* max packet */ + 1, /* # of configurations */ + 0 +}; + +static usb_config_descriptor_t ehci_confd = { + USB_CONFIG_DESCRIPTOR_SIZE, + UDESC_CONFIG, + {USB_CONFIG_DESCRIPTOR_SIZE + + USB_INTERFACE_DESCRIPTOR_SIZE + + USB_ENDPOINT_DESCRIPTOR_SIZE}, + 1, + 1, + 0, + UC_SELF_POWERED, + 0 /* max power */ +}; + +static usb_interface_descriptor_t ehci_ifcd = { + USB_INTERFACE_DESCRIPTOR_SIZE, + UDESC_INTERFACE, + 0, + 0, + 1, + UICLASS_HUB, + UISUBCLASS_HUB, + UIPROTO_HSHUBSTT, + 0 +}; + +static usb_endpoint_descriptor_t ehci_endpd = { + USB_ENDPOINT_DESCRIPTOR_SIZE, + UDESC_ENDPOINT, + UE_DIR_IN | EHCI_INTR_ENDPT, + UE_INTERRUPT, + {8, 0}, /* max packet */ + 255 +}; + +static usb_hub_descriptor_t ehci_hubd = { + USB_HUB_DESCRIPTOR_SIZE, + UDESC_HUB, + 0, + {0,0}, + 0, + 0, + {0}, +}; + +static int +ehci_str(usb_string_descriptor_t *p, int l, char *s) +{ + int i; + + if (l == 0) + return (0); + p->bLength = 2 * strlen(s) + 2; + if (l == 1) + return (1); + p->bDescriptorType = UDESC_STRING; + l -= 2; + for (i = 0; s[i] && l > 1; i++, l -= 2) + USETW2(p->bString[i], 0, s[i]); + return (2*i+2); +} + +/* + * Simulate a hardware hub by handling all the necessary requests. + */ +static usbd_status +ehci_root_ctrl_transfer(usbd_xfer_handle xfer) +{ + usbd_status err; + + /* Insert last in queue. */ + err = usb_insert_transfer(xfer); + if (err) + return (err); + + /* Pipe isn't running, start first */ + return (ehci_root_ctrl_start(STAILQ_FIRST(&xfer->pipe->queue))); +} + +static usbd_status +ehci_root_ctrl_start(usbd_xfer_handle xfer) +{ + ehci_softc_t *sc = (ehci_softc_t *)xfer->pipe->device->bus; + usb_device_request_t *req; + void *buf = NULL; + int port, i; + int s, len, value, index, l, totlen = 0; + usb_port_status_t ps; + usb_hub_descriptor_t hubd; + usbd_status err; + u_int32_t v; + + if (sc->sc_dying) + return (USBD_IOERROR); + +#ifdef DIAGNOSTIC + if (!(xfer->rqflags & URQ_REQUEST)) + /* XXX panic */ + return (USBD_INVAL); +#endif + req = &xfer->request; + + DPRINTFN(4,("ehci_root_ctrl_start: type=0x%02x request=%02x\n", + req->bmRequestType, req->bRequest)); + + len = UGETW(req->wLength); + value = UGETW(req->wValue); + index = UGETW(req->wIndex); + + if (len != 0) + buf = xfer->buffer; + +#define C(x,y) ((x) | ((y) << 8)) + switch(C(req->bRequest, req->bmRequestType)) { + case C(UR_CLEAR_FEATURE, UT_WRITE_DEVICE): + case C(UR_CLEAR_FEATURE, UT_WRITE_INTERFACE): + case C(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT): + /* + * DEVICE_REMOTE_WAKEUP and ENDPOINT_HALT are no-ops + * for the integrated root hub. + */ + break; + case C(UR_GET_CONFIG, UT_READ_DEVICE): + if (len > 0) { + *(u_int8_t *)buf = sc->sc_conf; + totlen = 1; + } + break; + case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE): + DPRINTFN(8,("ehci_root_ctrl_start: wValue=0x%04x\n", value)); + switch(value >> 8) { + case UDESC_DEVICE: + if ((value & 0xff) != 0) { + err = USBD_IOERROR; + goto ret; + } + totlen = l = min(len, USB_DEVICE_DESCRIPTOR_SIZE); + USETW(ehci_devd.idVendor, sc->sc_id_vendor); + memcpy(buf, &ehci_devd, l); + break; + /* + * We can't really operate at another speed, but the spec says + * we need this descriptor. + */ + case UDESC_DEVICE_QUALIFIER: + if ((value & 0xff) != 0) { + err = USBD_IOERROR; + goto ret; + } + totlen = l = min(len, USB_DEVICE_DESCRIPTOR_SIZE); + memcpy(buf, &ehci_odevd, l); + break; + /* + * We can't really operate at another speed, but the spec says + * we need this descriptor. + */ + case UDESC_OTHER_SPEED_CONFIGURATION: + case UDESC_CONFIG: + if ((value & 0xff) != 0) { + err = USBD_IOERROR; + goto ret; + } + totlen = l = min(len, USB_CONFIG_DESCRIPTOR_SIZE); + memcpy(buf, &ehci_confd, l); + ((usb_config_descriptor_t *)buf)->bDescriptorType = + value >> 8; + buf = (char *)buf + l; + len -= l; + l = min(len, USB_INTERFACE_DESCRIPTOR_SIZE); + totlen += l; + memcpy(buf, &ehci_ifcd, l); + buf = (char *)buf + l; + len -= l; + l = min(len, USB_ENDPOINT_DESCRIPTOR_SIZE); + totlen += l; + memcpy(buf, &ehci_endpd, l); + break; + case UDESC_STRING: + if (len == 0) + break; + *(u_int8_t *)buf = 0; + totlen = 1; + switch (value & 0xff) { + case 0: /* Language table */ + totlen = ehci_str(buf, len, "\001"); + break; + case 1: /* Vendor */ + totlen = ehci_str(buf, len, sc->sc_vendor); + break; + case 2: /* Product */ + totlen = ehci_str(buf, len, "EHCI root hub"); + break; + } + break; + default: + err = USBD_IOERROR; + goto ret; + } + break; + case C(UR_GET_INTERFACE, UT_READ_INTERFACE): + if (len > 0) { + *(u_int8_t *)buf = 0; + totlen = 1; + } + break; + case C(UR_GET_STATUS, UT_READ_DEVICE): + if (len > 1) { + USETW(((usb_status_t *)buf)->wStatus,UDS_SELF_POWERED); + totlen = 2; + } + break; + case C(UR_GET_STATUS, UT_READ_INTERFACE): + case C(UR_GET_STATUS, UT_READ_ENDPOINT): + if (len > 1) { + USETW(((usb_status_t *)buf)->wStatus, 0); + totlen = 2; + } + break; + case C(UR_SET_ADDRESS, UT_WRITE_DEVICE): + if (value >= USB_MAX_DEVICES) { + err = USBD_IOERROR; + goto ret; + } + sc->sc_addr = value; + break; + case C(UR_SET_CONFIG, UT_WRITE_DEVICE): + if (value != 0 && value != 1) { + err = USBD_IOERROR; + goto ret; + } + sc->sc_conf = value; + break; + case C(UR_SET_DESCRIPTOR, UT_WRITE_DEVICE): + break; + case C(UR_SET_FEATURE, UT_WRITE_DEVICE): + case C(UR_SET_FEATURE, UT_WRITE_INTERFACE): + case C(UR_SET_FEATURE, UT_WRITE_ENDPOINT): + err = USBD_IOERROR; + goto ret; + case C(UR_SET_INTERFACE, UT_WRITE_INTERFACE): + break; + case C(UR_SYNCH_FRAME, UT_WRITE_ENDPOINT): + break; + /* Hub requests */ + case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_DEVICE): + break; + case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_OTHER): + DPRINTFN(8, ("ehci_root_ctrl_start: UR_CLEAR_PORT_FEATURE " + "port=%d feature=%d\n", + index, value)); + if (index < 1 || index > sc->sc_noport) { + err = USBD_IOERROR; + goto ret; + } + port = EHCI_PORTSC(index); + v = EOREAD4(sc, port) &~ EHCI_PS_CLEAR; + switch(value) { + case UHF_PORT_ENABLE: + EOWRITE4(sc, port, v &~ EHCI_PS_PE); + break; + case UHF_PORT_SUSPEND: + EOWRITE4(sc, port, v &~ EHCI_PS_SUSP); + break; + case UHF_PORT_POWER: + EOWRITE4(sc, port, v &~ EHCI_PS_PP); + break; + case UHF_PORT_TEST: + DPRINTFN(2,("ehci_root_ctrl_start: clear port test " + "%d\n", index)); + break; + case UHF_PORT_INDICATOR: + DPRINTFN(2,("ehci_root_ctrl_start: clear port ind " + "%d\n", index)); + EOWRITE4(sc, port, v &~ EHCI_PS_PIC); + break; + case UHF_C_PORT_CONNECTION: + EOWRITE4(sc, port, v | EHCI_PS_CSC); + break; + case UHF_C_PORT_ENABLE: + EOWRITE4(sc, port, v | EHCI_PS_PEC); + break; + case UHF_C_PORT_SUSPEND: + /* how? */ + break; + case UHF_C_PORT_OVER_CURRENT: + EOWRITE4(sc, port, v | EHCI_PS_OCC); + break; + case UHF_C_PORT_RESET: + sc->sc_isreset = 0; + break; + default: + err = USBD_IOERROR; + goto ret; + } + break; + case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE): + if ((value & 0xff) != 0) { + err = USBD_IOERROR; + goto ret; + } + hubd = ehci_hubd; + hubd.bNbrPorts = sc->sc_noport; + v = EOREAD4(sc, EHCI_HCSPARAMS); + USETW(hubd.wHubCharacteristics, + EHCI_HCS_PPC(v) ? UHD_PWR_INDIVIDUAL : UHD_PWR_NO_SWITCH | + EHCI_HCS_P_INDICATOR(EREAD4(sc, EHCI_HCSPARAMS)) + ? UHD_PORT_IND : 0); + hubd.bPwrOn2PwrGood = 200; /* XXX can't find out? */ + for (i = 0, l = sc->sc_noport; l > 0; i++, l -= 8, v >>= 8) + hubd.DeviceRemovable[i++] = 0; /* XXX can't find out? */ + hubd.bDescLength = USB_HUB_DESCRIPTOR_SIZE + i; + l = min(len, hubd.bDescLength); + totlen = l; + memcpy(buf, &hubd, l); + break; + case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE): + if (len != 4) { + err = USBD_IOERROR; + goto ret; + } + memset(buf, 0, len); /* ? XXX */ + totlen = len; + break; + case C(UR_GET_STATUS, UT_READ_CLASS_OTHER): + DPRINTFN(8,("ehci_root_ctrl_start: get port status i=%d\n", + index)); + if (index < 1 || index > sc->sc_noport) { + err = USBD_IOERROR; + goto ret; + } + if (len != 4) { + err = USBD_IOERROR; + goto ret; + } + v = EOREAD4(sc, EHCI_PORTSC(index)); + DPRINTFN(8,("ehci_root_ctrl_start: port status=0x%04x\n", + v)); + + i = UPS_HIGH_SPEED; + + if (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; + } + + if (v & EHCI_PS_CS) i |= UPS_CURRENT_CONNECT_STATUS; + if (v & EHCI_PS_PE) i |= UPS_PORT_ENABLED; + if (v & EHCI_PS_SUSP) i |= UPS_SUSPEND; + if (v & EHCI_PS_OCA) i |= UPS_OVERCURRENT_INDICATOR; + if (v & EHCI_PS_PR) i |= UPS_RESET; + if (v & EHCI_PS_PP) i |= UPS_PORT_POWER; + USETW(ps.wPortStatus, i); + i = 0; + if (v & EHCI_PS_CSC) i |= UPS_C_CONNECT_STATUS; + if (v & EHCI_PS_PEC) i |= UPS_C_PORT_ENABLED; + if (v & EHCI_PS_OCC) i |= UPS_C_OVERCURRENT_INDICATOR; + if (sc->sc_isreset) i |= UPS_C_PORT_RESET; + USETW(ps.wPortChange, i); + l = min(len, sizeof ps); + memcpy(buf, &ps, l); + totlen = l; + break; + case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE): + err = USBD_IOERROR; + goto ret; + case C(UR_SET_FEATURE, UT_WRITE_CLASS_DEVICE): + break; + case C(UR_SET_FEATURE, UT_WRITE_CLASS_OTHER): + if (index < 1 || index > sc->sc_noport) { + err = USBD_IOERROR; + goto ret; + } + port = EHCI_PORTSC(index); + v = EOREAD4(sc, port) &~ EHCI_PS_CLEAR; + switch(value) { + case UHF_PORT_ENABLE: + EOWRITE4(sc, port, v | EHCI_PS_PE); + break; + case UHF_PORT_SUSPEND: + EOWRITE4(sc, port, v | EHCI_PS_SUSP); + break; + case UHF_PORT_RESET: + DPRINTFN(5,("ehci_root_ctrl_start: reset port %d\n", + index)); + if (EHCI_PS_IS_LOWSPEED(v) && + (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_delay_ms(&sc->sc_bus, USB_PORT_ROOT_RESET_DELAY); + if (sc->sc_dying) { + err = USBD_IOERROR; + goto ret; + } + /* Terminate reset sequence. */ + if (sc->sc_flags & EHCI_SCFLG_NORESTERM) + ; + else + EOWRITE4(sc, port, v); + + /* Wait for HC to complete reset. */ + usb_delay_ms(&sc->sc_bus, EHCI_PORT_RESET_COMPLETE); + if (sc->sc_dying) { + err = USBD_IOERROR; + goto ret; + } + v = EOREAD4(sc, port); + DPRINTF(("ehci after reset, status=0x%08x\n", v)); + if (v & EHCI_PS_PR) { + printf("%s: port reset timeout\n", + device_get_nameunit(sc->sc_bus.bdev)); + return (USBD_TIMEOUT); + } + if (!(v & EHCI_PS_PE) && + (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(2,("ehci_root_ctrl_start: set port power " + "%d\n", index)); + EOWRITE4(sc, port, v | EHCI_PS_PP); + break; + case UHF_PORT_TEST: + DPRINTFN(2,("ehci_root_ctrl_start: set port test " + "%d\n", index)); + break; + case UHF_PORT_INDICATOR: + DPRINTFN(2,("ehci_root_ctrl_start: set port ind " + "%d\n", index)); + EOWRITE4(sc, port, v | EHCI_PS_PIC); + break; + default: + err = USBD_IOERROR; + goto ret; + } + break; + case C(UR_CLEAR_TT_BUFFER, UT_WRITE_CLASS_OTHER): + case C(UR_RESET_TT, UT_WRITE_CLASS_OTHER): + case C(UR_GET_TT_STATE, UT_READ_CLASS_OTHER): + case C(UR_STOP_TT, UT_WRITE_CLASS_OTHER): + break; + default: + err = USBD_IOERROR; + goto ret; + } + xfer->actlen = totlen; + err = USBD_NORMAL_COMPLETION; + ret: + xfer->status = err; + s = splusb(); + hacksync(xfer); /* XXX to compensate for usb_transfer_complete */ + usb_transfer_complete(xfer); + splx(s); + return (USBD_IN_PROGRESS); +} + +void +ehci_disown(ehci_softc_t *sc, int index, int lowspeed) +{ + int port; + u_int32_t v; + + DPRINTF(("ehci_disown: index=%d lowspeed=%d\n", index, lowspeed)); +#ifdef DIAGNOSTIC + if (sc->sc_npcomp != 0) { + int i = (index-1) / sc->sc_npcomp; + if (i >= sc->sc_ncomp) + printf("%s: strange port\n", + device_get_nameunit(sc->sc_bus.bdev)); + else + printf("%s: handing over %s speed device on " + "port %d to %s\n", + device_get_nameunit(sc->sc_bus.bdev), + lowspeed ? "low" : "full", + index, device_get_nameunit(sc->sc_comps[i]->bdev)); + } else { + printf("%s: npcomp == 0\n", device_get_nameunit(sc->sc_bus.bdev)); + } +#endif + port = EHCI_PORTSC(index); + v = EOREAD4(sc, port) &~ EHCI_PS_CLEAR; + EOWRITE4(sc, port, v | EHCI_PS_PO); +} + +/* Abort a root control request. */ +static void +ehci_root_ctrl_abort(usbd_xfer_handle xfer) +{ + /* Nothing to do, all transfers are synchronous. */ +} + +/* Close the root pipe. */ +static void +ehci_root_ctrl_close(usbd_pipe_handle pipe) +{ + DPRINTF(("ehci_root_ctrl_close\n")); + /* Nothing to do. */ +} + +void +ehci_root_intr_done(usbd_xfer_handle xfer) +{ +} + +static usbd_status +ehci_root_intr_transfer(usbd_xfer_handle xfer) +{ + usbd_status err; + + /* Insert last in queue. */ + err = usb_insert_transfer(xfer); + if (err) + return (err); + + /* Pipe isn't running, start first */ + return (ehci_root_intr_start(STAILQ_FIRST(&xfer->pipe->queue))); +} + +static usbd_status +ehci_root_intr_start(usbd_xfer_handle xfer) +{ + usbd_pipe_handle pipe = xfer->pipe; + ehci_softc_t *sc = (ehci_softc_t *)pipe->device->bus; + + if (sc->sc_dying) + return (USBD_IOERROR); + + sc->sc_intrxfer = xfer; + + return (USBD_IN_PROGRESS); +} + +/* Abort a root interrupt request. */ +static void +ehci_root_intr_abort(usbd_xfer_handle xfer) +{ + int s; + + if (xfer->pipe->intrxfer == xfer) { + DPRINTF(("ehci_root_intr_abort: remove\n")); + xfer->pipe->intrxfer = NULL; + } + xfer->status = USBD_CANCELLED; + s = splusb(); + usb_transfer_complete(xfer); + splx(s); +} + +/* Close the root pipe. */ +static void +ehci_root_intr_close(usbd_pipe_handle pipe) +{ + ehci_softc_t *sc = (ehci_softc_t *)pipe->device->bus; + + DPRINTF(("ehci_root_intr_close\n")); + + sc->sc_intrxfer = NULL; +} + +void +ehci_root_ctrl_done(usbd_xfer_handle xfer) +{ +} + +/************************/ + +ehci_soft_qh_t * +ehci_alloc_sqh(ehci_softc_t *sc) +{ + ehci_soft_qh_t *sqh; + ehci_soft_qtd_t *sqtd; + usbd_status err; + int i, offs; + usb_dma_t dma; + + if (sc->sc_freeqhs == NULL) { + DPRINTFN(2, ("ehci_alloc_sqh: allocating chunk\n")); + err = usb_allocmem(&sc->sc_bus, EHCI_SQH_SIZE * EHCI_SQH_CHUNK, + EHCI_PAGE_SIZE, &dma); +#ifdef EHCI_DEBUG + if (err) + printf("ehci_alloc_sqh: usb_allocmem()=%d\n", err); +#endif + if (err) + return (NULL); + for(i = 0; i < EHCI_SQH_CHUNK; i++) { + offs = i * EHCI_SQH_SIZE; + sqh = KERNADDR(&dma, offs); + sqh->physaddr = DMAADDR(&dma, offs); + sqh->next = sc->sc_freeqhs; + sc->sc_freeqhs = sqh; + } + } + /* Allocate the initial inactive sqtd. */ + sqtd = ehci_alloc_sqtd(sc); + if (sqtd == NULL) + return (NULL); + sqtd->qtd.qtd_status = htohc32(sc, 0); + sqtd->qtd.qtd_next = EHCI_NULL(sc); + sqtd->qtd.qtd_altnext = EHCI_NULL(sc); + + sqh = sc->sc_freeqhs; + sc->sc_freeqhs = sqh->next; + + /* The overlay QTD should begin zeroed. */ + sqh->qh.qh_qtd.qtd_next = htohc32(sc, sqtd->physaddr); + sqh->qh.qh_qtd.qtd_altnext = EHCI_NULL(sc); + sqh->qh.qh_qtd.qtd_status = 0; + for (i = 0; i < EHCI_QTD_NBUFFERS; i++) { + sqh->qh.qh_qtd.qtd_buffer[i] = 0; + sqh->qh.qh_qtd.qtd_buffer_hi[i] = 0; + } + sqh->next = NULL; + sqh->prev = NULL; + sqh->sqtd = sqtd; + sqh->inactivesqtd = sqtd; + return (sqh); +} + +void +ehci_free_sqh(ehci_softc_t *sc, ehci_soft_qh_t *sqh) +{ + ehci_free_sqtd(sc, sqh->inactivesqtd); + sqh->next = sc->sc_freeqhs; + sc->sc_freeqhs = sqh; +} + +ehci_soft_qtd_t * +ehci_alloc_sqtd(ehci_softc_t *sc) +{ + ehci_soft_qtd_t *sqtd; + usbd_status err; + int i, offs; + usb_dma_t dma; + int s; + + if (sc->sc_freeqtds == NULL) { + DPRINTFN(2, ("ehci_alloc_sqtd: allocating chunk\n")); + err = usb_allocmem(&sc->sc_bus, EHCI_SQTD_SIZE*EHCI_SQTD_CHUNK, + EHCI_PAGE_SIZE, &dma); +#ifdef EHCI_DEBUG + if (err) + printf("ehci_alloc_sqtd: usb_allocmem()=%d\n", err); +#endif + if (err) + return (NULL); + s = splusb(); + for(i = 0; i < EHCI_SQTD_CHUNK; i++) { + offs = i * EHCI_SQTD_SIZE; + sqtd = KERNADDR(&dma, offs); + sqtd->physaddr = DMAADDR(&dma, offs); + sqtd->nextqtd = sc->sc_freeqtds; + sc->sc_freeqtds = sqtd; + } + splx(s); + } + + s = splusb(); + sqtd = sc->sc_freeqtds; + sc->sc_freeqtds = sqtd->nextqtd; + sqtd->qtd.qtd_next = EHCI_NULL(sc); + sqtd->qtd.qtd_altnext = EHCI_NULL(sc); + sqtd->qtd.qtd_status = 0; + for (i = 0; i < EHCI_QTD_NBUFFERS; i++) { + sqtd->qtd.qtd_buffer[i] = 0; + sqtd->qtd.qtd_buffer_hi[i] = 0; + } + sqtd->nextqtd = NULL; + sqtd->xfer = NULL; + splx(s); + + return (sqtd); +} + +void +ehci_free_sqtd(ehci_softc_t *sc, ehci_soft_qtd_t *sqtd) +{ + int s; + + s = splusb(); + sqtd->nextqtd = sc->sc_freeqtds; + sc->sc_freeqtds = sqtd; + splx(s); +} + +usbd_status +ehci_alloc_sqtd_chain(struct ehci_pipe *epipe, ehci_softc_t *sc, + int alen, int rd, usbd_xfer_handle xfer, ehci_soft_qtd_t *start, + ehci_soft_qtd_t *newinactive, ehci_soft_qtd_t **sp, ehci_soft_qtd_t **ep) +{ + ehci_soft_qtd_t *next, *cur; + ehci_physaddr_t dataphys, nextphys; + u_int32_t qtdstatus; + int adj, len, curlen, mps, offset, pagelen, seg, segoff; + int i, iscontrol, forceshort; + struct usb_dma_mapping *dma = &xfer->dmamap; + + DPRINTFN(alen<4*4096,("ehci_alloc_sqtd_chain: start len=%d\n", alen)); + + offset = 0; + len = alen; + iscontrol = (epipe->pipe.endpoint->edesc->bmAttributes & UE_XFERTYPE) == + UE_CONTROL; + qtdstatus = EHCI_QTD_ACTIVE | + EHCI_QTD_SET_PID(rd ? EHCI_QTD_PID_IN : EHCI_QTD_PID_OUT) | + EHCI_QTD_SET_CERR(3) + /* IOC set below */ + /* BYTES set below */ + ; + mps = UGETW(epipe->pipe.endpoint->edesc->wMaxPacketSize); + forceshort = ((xfer->flags & USBD_FORCE_SHORT_XFER) || len == 0) && + len % mps == 0; + /* + * The control transfer data stage always starts with a toggle of 1. + * For other transfers we let the hardware track the toggle state. + */ + if (iscontrol) + qtdstatus |= EHCI_QTD_SET_TOGGLE(1); + + if (start != NULL) { + /* + * If we are given a starting qTD, assume it is linked into + * an active QH so be careful not to mark it active. + */ + cur = start; + *sp = cur; + qtdstatus &= ~EHCI_QTD_ACTIVE; + } else { + cur = ehci_alloc_sqtd(sc); + *sp = cur; + if (cur == NULL) + goto nomem; + } + seg = 0; + segoff = 0; + for (;;) { + curlen = 0; + + /* The EHCI hardware can handle at most 5 pages. */ + for (i = 0; i < EHCI_QTD_NBUFFERS && curlen < len; i++) { + KASSERT(seg < dma->nsegs, + ("ehci_alloc_sqtd_chain: overrun")); + dataphys = dma->segs[seg].ds_addr + segoff; + pagelen = dma->segs[seg].ds_len - segoff; + if (pagelen > len - curlen) + pagelen = len - curlen; + if (pagelen > EHCI_PAGE_SIZE - + EHCI_PAGE_OFFSET(dataphys)) + pagelen = EHCI_PAGE_SIZE - + EHCI_PAGE_OFFSET(dataphys); + segoff += pagelen; + if (segoff >= dma->segs[seg].ds_len) { + KASSERT(segoff == dma->segs[seg].ds_len, + ("ehci_alloc_sqtd_chain: overlap")); + seg++; + segoff = 0; + } + + cur->qtd.qtd_buffer[i] = htohc32(sc, dataphys); + cur->qtd.qtd_buffer_hi[i] = 0; + curlen += pagelen; + + /* + * Must stop if there is any gap before or after + * the page boundary. + */ + if (EHCI_PAGE_OFFSET(dataphys + pagelen) != 0) + break; + if (seg < dma->nsegs && EHCI_PAGE_OFFSET(segoff + + dma->segs[seg].ds_addr) != 0) + break; + } + /* Adjust down to a multiple of mps if not at the end. */ + if (curlen < len && curlen % mps != 0) { + adj = curlen % mps; + curlen -= adj; + KASSERT(curlen > 0, + ("ehci_alloc_sqtd_chain: need to copy")); + segoff -= adj; + if (segoff < 0) { + seg--; + segoff += dma->segs[seg].ds_len; + } + KASSERT(seg >= 0 && segoff >= 0, + ("ehci_alloc_sqtd_chain: adjust to mps")); + } + + len -= curlen; + + if (len != 0 || forceshort) { + next = ehci_alloc_sqtd(sc); + if (next == NULL) + goto nomem; + nextphys = htohc32(sc, next->physaddr); + } else { + next = NULL; + nextphys = EHCI_NULL(sc); + } + + cur->nextqtd = next; + cur->qtd.qtd_next = nextphys; + /* Make sure to stop after a short transfer. */ + cur->qtd.qtd_altnext = htohc32(sc, newinactive->physaddr); + cur->qtd.qtd_status = + htohc32(sc, qtdstatus | EHCI_QTD_SET_BYTES(curlen)); + cur->xfer = xfer; + cur->len = curlen; + DPRINTFN(10,("ehci_alloc_sqtd_chain: curlen=%d\n", curlen)); + if (iscontrol) { + /* + * adjust the toggle based on the number of packets + * in this qtd + */ + if ((((curlen + mps - 1) / mps) & 1) || curlen == 0) + qtdstatus ^= EHCI_QTD_TOGGLE_MASK; + } + qtdstatus |= EHCI_QTD_ACTIVE; + if (len == 0) { + if (!forceshort) + break; + forceshort = 0; + } + DPRINTFN(10,("ehci_alloc_sqtd_chain: extend chain\n")); + offset += curlen; + cur = next; + } + cur->qtd.qtd_status |= htohc32(sc, EHCI_QTD_IOC); + *ep = cur; + + DPRINTFN(10,("ehci_alloc_sqtd_chain: return sqtd=%p sqtdend=%p\n", + *sp, *ep)); + + return (USBD_NORMAL_COMPLETION); + + nomem: + /* XXX free chain */ + DPRINTFN(-1,("ehci_alloc_sqtd_chain: no memory\n")); + return (USBD_NOMEM); +} + +/* Free the chain starting at sqtd and end at the qTD before sqtdend */ +static void +ehci_free_sqtd_chain(ehci_softc_t *sc, ehci_soft_qh_t *sqh, + ehci_soft_qtd_t *sqtd, ehci_soft_qtd_t *sqtdend) +{ + ehci_soft_qtd_t *p, **prevp; + int i; + + DPRINTFN(10,("ehci_free_sqtd_chain: sqtd=%p sqtdend=%p\n", + sqtd, sqtdend)); + + /* First unlink the chain from the QH's software qTD list. */ + prevp = &sqh->sqtd; + for (p = sqh->sqtd; p != NULL; p = p->nextqtd) { + if (p == sqtd) { + *prevp = sqtdend; + break; + } + prevp = &p->nextqtd; + } + KASSERT(p != NULL, ("ehci_free_sqtd_chain: chain not found")); + for (i = 0; sqtd != sqtdend; sqtd = p, i++) { + p = sqtd->nextqtd; + ehci_free_sqtd(sc, sqtd); + } +} + +ehci_soft_itd_t * +ehci_alloc_itd(ehci_softc_t *sc) +{ + struct ehci_soft_itd *itd, *freeitd; + usbd_status err; + int i, s, offs, frindex, previndex; + usb_dma_t dma; + + s = splusb(); + + /* Find an itd that wasn't freed this frame or last frame. This can + * discard itds that were freed before frindex wrapped around + * XXX - can this lead to thrashing? Could fix by enabling wrap-around + * interrupt and fiddling with list when that happens */ + frindex = (EOREAD4(sc, EHCI_FRINDEX) + 1) >> 3; + previndex = (frindex != 0) ? frindex - 1 : sc->sc_flsize; + + freeitd = NULL; + LIST_FOREACH(itd, &sc->sc_freeitds, u.free_list) { + if (itd == NULL) + break; + if (itd->slot != frindex && itd->slot != previndex) { + freeitd = itd; + break; + } + } + + if (freeitd == NULL) { + DPRINTFN(2, ("ehci_alloc_itd allocating chunk\n")); + err = usb_allocmem(&sc->sc_bus, EHCI_ITD_SIZE * EHCI_ITD_CHUNK, + EHCI_PAGE_SIZE, &dma); + + if (err) { + DPRINTF(("ehci_alloc_itd, alloc returned %d\n", err)); + return NULL; + } + + for (i = 0; i < EHCI_ITD_CHUNK; i++) { + offs = i * EHCI_ITD_SIZE; + itd = KERNADDR(&dma, offs); + itd->physaddr = DMAADDR(&dma, offs); + itd->dma = dma; + itd->offs = offs; + LIST_INSERT_HEAD(&sc->sc_freeitds, itd, u.free_list); + } + freeitd = LIST_FIRST(&sc->sc_freeitds); + } + + itd = freeitd; + LIST_REMOVE(itd, u.free_list); + memset(&itd->itd, 0, sizeof(ehci_itd_t)); + itd->u.frame_list.next = NULL; + itd->u.frame_list.prev = NULL; + itd->xfer_next = NULL; + itd->slot = 0; + splx(s); + + return (itd); +} + +void +ehci_free_itd(ehci_softc_t *sc, ehci_soft_itd_t *itd) +{ + int s; + + s = splusb(); + LIST_INSERT_AFTER(LIST_FIRST(&sc->sc_freeitds), itd, u.free_list); + splx(s); +} + +/****************/ + +/* + * Close a reqular pipe. + * Assumes that there are no pending transactions. + */ +void +ehci_close_pipe(usbd_pipe_handle pipe, ehci_soft_qh_t *head) +{ + struct ehci_pipe *epipe = (struct ehci_pipe *)pipe; + ehci_softc_t *sc = (ehci_softc_t *)pipe->device->bus; + ehci_soft_qh_t *sqh = epipe->sqh; + int s; + + s = splusb(); + ehci_rem_qh(sc, sqh, head); + splx(s); + pipe->endpoint->savedtoggle = + EHCI_QTD_GET_TOGGLE(hc32toh(sc, sqh->qh.qh_qtd.qtd_status)); + ehci_free_sqh(sc, epipe->sqh); +} + +/* + * Abort a device request. + * If this routine is called at splusb() it guarantees that the request + * will be removed from the hardware scheduling and that the callback + * for it will be called with USBD_CANCELLED status. + * It's impossible to guarantee that the requested transfer will not + * have happened since the hardware runs concurrently. + * If the transaction has already happened we rely on the ordinary + * interrupt processing to process it. + */ +void +ehci_abort_xfer(usbd_xfer_handle xfer, usbd_status status) +{ +#define exfer EXFER(xfer) + struct ehci_pipe *epipe = (struct ehci_pipe *)xfer->pipe; + ehci_softc_t *sc = (ehci_softc_t *)epipe->pipe.device->bus; + ehci_soft_qh_t *sqh = epipe->sqh; + ehci_soft_qtd_t *sqtd, *snext; + ehci_physaddr_t cur, us, next; + int s; + int hit, i; + /* int count = 0; */ + ehci_soft_qh_t *psqh; + + DPRINTF(("ehci_abort_xfer: xfer=%p pipe=%p\n", xfer, epipe)); + + if (sc->sc_dying) { + /* If we're dying, just do the software part. */ + s = splusb(); + xfer->status = status; /* make software ignore it */ + callout_stop(&xfer->timeout_handle); + usb_rem_task(epipe->pipe.device, &exfer->abort_task); + usb_transfer_complete(xfer); + splx(s); + return; + } + + if (xfer->device->bus->intr_context) + panic("ehci_abort_xfer: not in process context"); + + /* + * If an abort is already in progress then just wait for it to + * complete and return. + */ + if (exfer->ehci_xfer_flags & EHCI_XFER_ABORTING) { + DPRINTFN(2, ("ehci_abort_xfer: already aborting\n")); + /* No need to wait if we're aborting from a timeout. */ + if (status == USBD_TIMEOUT) + return; + /* Override the status which might be USBD_TIMEOUT. */ + xfer->status = status; + DPRINTFN(2, ("ehci_abort_xfer: waiting for abort to finish\n")); + exfer->ehci_xfer_flags |= EHCI_XFER_ABORTWAIT; + while (exfer->ehci_xfer_flags & EHCI_XFER_ABORTING) + tsleep(&exfer->ehci_xfer_flags, PZERO, "ehciaw", 0); + return; + } + + /* + * Step 1: Make interrupt routine and timeouts ignore xfer. + */ + s = splusb(); + exfer->ehci_xfer_flags |= EHCI_XFER_ABORTING; + xfer->status = status; /* make software ignore it */ + callout_stop(&xfer->timeout_handle); + usb_rem_task(epipe->pipe.device, &exfer->abort_task); + splx(s); + + /* + * Step 2: Wait until we know hardware has finished any possible + * use of the xfer. We do this by removing the entire + * queue from the async schedule and waiting for the doorbell. + * Nothing else should be touching the queue now. + */ + psqh = sqh->prev; + ehci_rem_qh(sc, sqh, psqh); + + /* + * Step 3: make sure the soft interrupt routine + * has run. This should remove any completed items off the queue. + * The hardware has no reference to completed items (TDs). + * It's safe to remove them at any time. + */ + s = splusb(); +#ifdef USB_USE_SOFTINTR + sc->sc_softwake = 1; +#endif /* USB_USE_SOFTINTR */ + usb_schedsoftintr(&sc->sc_bus); +#ifdef USB_USE_SOFTINTR + tsleep(&sc->sc_softwake, PZERO, "ehciab", 0); +#endif /* USB_USE_SOFTINTR */ + + /* + * Step 4: Remove any vestiges of the xfer from the hardware. + * The complication here is that the hardware may have executed + * into or even beyond the xfer we're trying to abort. + * So as we're scanning the TDs of this xfer we check if + * the hardware points to any of them. + * + * first we need to see if there are any transfers + * on this queue before the xfer we are aborting.. we need + * to update any pointers that point to us to point past + * the aborting xfer. (If there is something past us). + * Hardware and software. + */ + cur = EHCI_LINK_ADDR(hc32toh(sc, sqh->qh.qh_curqtd)); + hit = 0; + + /* If they initially point here. */ + us = exfer->sqtdstart->physaddr; + + /* We will change them to point here */ + snext = exfer->sqtdend->nextqtd; + next = (snext != NULL) ? htohc32(sc, snext->physaddr) : EHCI_NULL(sc); + + /* + * Now loop through any qTDs before us and keep track of the pointer + * that points to us for the end. + */ + sqtd = sqh->sqtd; + while (sqtd && sqtd != exfer->sqtdstart) { + hit |= (cur == sqtd->physaddr); + if (EHCI_LINK_ADDR(hc32toh(sc, sqtd->qtd.qtd_next)) == us) + sqtd->qtd.qtd_next = next; + if (EHCI_LINK_ADDR(hc32toh(sc, sqtd->qtd.qtd_altnext)) == us) + sqtd->qtd.qtd_altnext = next; + sqtd = sqtd->nextqtd; + } + + /* + * If we already saw the active one then we are pretty much done. + * We've done all the relinking we need to do. + */ + if (!hit) { + + /* + * Now reinitialise the QH to point to the next qTD + * (if there is one). We only need to do this if + * it was previously pointing to us. + */ + for (sqtd = exfer->sqtdstart; ; sqtd = sqtd->nextqtd) { + if (cur == sqtd->physaddr) { + hit++; + } + if (sqtd == exfer->sqtdend) + break; + } + sqtd = sqtd->nextqtd; + /* + * Only need to alter the QH if it was pointing at a qTD + * that we are removing. + */ + if (hit) { + sqh->qh.qh_qtd.qtd_next = htohc32(sc, snext->physaddr); + sqh->qh.qh_qtd.qtd_altnext = EHCI_NULL(sc); + sqh->qh.qh_qtd.qtd_status &= + htohc32(sc, EHCI_QTD_TOGGLE_MASK); + for (i = 0; i < EHCI_QTD_NBUFFERS; i++) { + sqh->qh.qh_qtd.qtd_buffer[i] = 0; + sqh->qh.qh_qtd.qtd_buffer_hi[i] = 0; + } + } + } + ehci_add_qh(sc, sqh, psqh); + /* + * Step 5: Execute callback. + */ +#ifdef DIAGNOSTIC + exfer->isdone = 1; +#endif + /* Do the wakeup first to avoid touching the xfer after the callback. */ + exfer->ehci_xfer_flags &= ~EHCI_XFER_ABORTING; + if (exfer->ehci_xfer_flags & EHCI_XFER_ABORTWAIT) { + exfer->ehci_xfer_flags &= ~EHCI_XFER_ABORTWAIT; + wakeup(&exfer->ehci_xfer_flags); + } + usb_transfer_complete(xfer); + + /* printf("%s: %d TDs aborted\n", __func__, count); */ + splx(s); +#undef exfer +} + +void +ehci_timeout(void *addr) +{ + struct ehci_xfer *exfer = addr; + struct ehci_pipe *epipe = (struct ehci_pipe *)exfer->xfer.pipe; + ehci_softc_t *sc = (ehci_softc_t *)epipe->pipe.device->bus; + + DPRINTF(("ehci_timeout: exfer=%p\n", exfer)); +#ifdef USB_DEBUG + if (ehcidebug > 1) + usbd_dump_pipe(exfer->xfer.pipe); +#endif + + if (sc->sc_dying) { + ehci_abort_xfer(&exfer->xfer, USBD_TIMEOUT); + return; + } + + /* Execute the abort in a process context. */ + usb_add_task(exfer->xfer.pipe->device, &exfer->abort_task, + USB_TASKQ_HC); +} + +void +ehci_abort_isoc_xfer(usbd_xfer_handle xfer, usbd_status status) +{ + ehci_isoc_trans_t trans_status; + struct ehci_pipe *epipe; + struct ehci_xfer *exfer; + ehci_softc_t *sc; + struct ehci_soft_itd *itd; + int s, i; + + epipe = (struct ehci_pipe *) xfer->pipe; + exfer = EXFER(xfer); + sc = (ehci_softc_t *)epipe->pipe.device->bus; + + DPRINTF(("ehci_abort_isoc_xfer: xfer %p pipe %p\n", xfer, epipe)); + + if (sc->sc_dying) { + s = splusb(); + xfer->status = status; + callout_stop(&xfer->timeout_handle); + usb_rem_task(epipe->pipe.device, &exfer->abort_task); + usb_transfer_complete(xfer); + splx(s); + return; + } + + if (exfer->ehci_xfer_flags & EHCI_XFER_ABORTING) { + DPRINTFN(2, ("ehci_abort_isoc_xfer: already aborting\n")); + +#ifdef DIAGNOSTIC + if (status == USBD_TIMEOUT) + printf("ehci_abort_xfer: TIMEOUT while aborting\n"); +#endif + + xfer->status = status; + DPRINTFN(2, ("ehci_abort_xfer: waiting for abort to finish\n")); + exfer->ehci_xfer_flags |= EHCI_XFER_ABORTWAIT; + while (exfer->ehci_xfer_flags & EHCI_XFER_ABORTING) + tsleep(&exfer->ehci_xfer_flags, PZERO, "ehciaw", 0); + return; + } + exfer->ehci_xfer_flags |= EHCI_XFER_ABORTING; + + xfer->status = status; + callout_stop(&xfer->timeout_handle); + usb_rem_task(epipe->pipe.device, &exfer->abort_task); + + s = splusb(); + for (itd = exfer->itdstart; itd != NULL; itd = itd->xfer_next) { + + for (i = 0; i < 8; i++) { + trans_status = hc32toh(sc, itd->itd.itd_ctl[i]); + trans_status &= ~EHCI_ITD_ACTIVE; + itd->itd.itd_ctl[i] = htohc32(sc, trans_status); + } + + } + splx(s); + + s = splusb(); +#ifdef USB_USE_SOFTINTR + sc->sc_softwake = 1; +#endif /* USB_USE_SOFTINTR */ + usb_schedsoftintr(&sc->sc_bus); +#ifdef USB_USE_SOFTINTR + tsleep(&sc->sc_softwake, PZERO, "ehciab", 0); +#endif /* USB_USE_SOFTINTR */ + splx(s); + +#ifdef DIAGNOSTIC + exfer->isdone = 1; +#endif + exfer->ehci_xfer_flags &= ~EHCI_XFER_ABORTING; + if (exfer->ehci_xfer_flags & EHCI_XFER_ABORTWAIT) { + exfer->ehci_xfer_flags &= ~EHCI_XFER_ABORTWAIT; + wakeup(&exfer->ehci_xfer_flags); + } + usb_transfer_complete(xfer); +} + +void +ehci_timeout_task(void *addr) +{ + usbd_xfer_handle xfer = addr; + int s; + + DPRINTF(("ehci_timeout_task: xfer=%p\n", xfer)); + + s = splusb(); + ehci_abort_xfer(xfer, USBD_TIMEOUT); + splx(s); +} + +/* + * Some EHCI chips from VIA / ATI seem to trigger interrupts before writing + * back the qTD status, or miss signalling occasionally under heavy load. + * If the host machine is too fast, we can miss transaction completion - when + * we scan the active list the transaction still seems to be active. This + * generally exhibits itself as a umass stall that never recovers. + * + * We work around this behaviour by setting up this callback after any softintr + * that completes with transactions still pending, giving us another chance to + * check for completion after the writeback has taken place. + */ +void +ehci_intrlist_timeout(void *arg) +{ + ehci_softc_t *sc = arg; + int s = splusb(); + + DPRINTFN(3, ("ehci_intrlist_timeout\n")); + usb_schedsoftintr(&sc->sc_bus); + + splx(s); +} + +/************************/ + +static usbd_status +ehci_device_ctrl_transfer(usbd_xfer_handle xfer) +{ + usbd_status err; + + /* Insert last in queue. */ + err = usb_insert_transfer(xfer); + if (err) + return (err); + + /* Pipe isn't running, start first */ + return (ehci_device_ctrl_start(STAILQ_FIRST(&xfer->pipe->queue))); +} + +static usbd_status +ehci_device_ctrl_start(usbd_xfer_handle xfer) +{ + ehci_softc_t *sc = (ehci_softc_t *)xfer->pipe->device->bus; + usbd_status err; + + if (sc->sc_dying) + return (USBD_IOERROR); + +#ifdef DIAGNOSTIC + if (!(xfer->rqflags & URQ_REQUEST)) { + /* XXX panic */ + printf("ehci_device_ctrl_transfer: not a request\n"); + return (USBD_INVAL); + } +#endif + + err = ehci_device_request(xfer); + if (err) + return (err); + + if (sc->sc_bus.use_polling) + ehci_waitintr(sc, xfer); + return (USBD_IN_PROGRESS); +} + +void +ehci_device_ctrl_done(usbd_xfer_handle xfer) +{ + struct ehci_xfer *ex = EXFER(xfer); + ehci_softc_t *sc = (ehci_softc_t *)xfer->pipe->device->bus; + struct ehci_pipe *epipe = (struct ehci_pipe *)xfer->pipe; + + DPRINTFN(10,("ehci_ctrl_done: xfer=%p\n", xfer)); + +#ifdef DIAGNOSTIC + if (!(xfer->rqflags & URQ_REQUEST)) { + panic("ehci_ctrl_done: not a request"); + } +#endif + + if (xfer->status != USBD_NOMEM && ehci_active_intr_list(ex)) { + ehci_del_intr_list(ex); /* remove from active list */ + ehci_free_sqtd_chain(sc, epipe->sqh, ex->sqtdstart, + ex->sqtdend->nextqtd); + } + + DPRINTFN(5, ("ehci_ctrl_done: length=%d\n", xfer->actlen)); +} + +/* Abort a device control request. */ +static void +ehci_device_ctrl_abort(usbd_xfer_handle xfer) +{ + DPRINTF(("ehci_device_ctrl_abort: xfer=%p\n", xfer)); + ehci_abort_xfer(xfer, USBD_CANCELLED); +} + +/* Close a device control pipe. */ +static void +ehci_device_ctrl_close(usbd_pipe_handle pipe) +{ + ehci_softc_t *sc = (ehci_softc_t *)pipe->device->bus; + /*struct ehci_pipe *epipe = (struct ehci_pipe *)pipe;*/ + + DPRINTF(("ehci_device_ctrl_close: pipe=%p\n", pipe)); + ehci_close_pipe(pipe, sc->sc_async_head); +} + +usbd_status +ehci_device_request(usbd_xfer_handle xfer) +{ +#define exfer EXFER(xfer) + struct ehci_pipe *epipe = (struct ehci_pipe *)xfer->pipe; + usb_device_request_t *req = &xfer->request; + usbd_device_handle dev = epipe->pipe.device; + ehci_softc_t *sc = (ehci_softc_t *)dev->bus; + ehci_soft_qtd_t *newinactive, *setup, *stat, *next; + ehci_soft_qh_t *sqh; + int isread; + int len; + usbd_status err; + int s; + + isread = req->bmRequestType & UT_READ; + len = UGETW(req->wLength); + + DPRINTFN(3,("ehci_device_request: type=0x%02x, request=0x%02x, " + "wValue=0x%04x, wIndex=0x%04x len=%d, addr=%d, endpt=%d\n", + req->bmRequestType, req->bRequest, UGETW(req->wValue), + UGETW(req->wIndex), len, dev->address, + epipe->pipe.endpoint->edesc->bEndpointAddress)); + + newinactive = ehci_alloc_sqtd(sc); + if (newinactive == NULL) { + err = USBD_NOMEM; + goto bad1; + } + newinactive->qtd.qtd_status = htohc32(sc, 0); + newinactive->qtd.qtd_next = EHCI_NULL(sc); + newinactive->qtd.qtd_altnext = EHCI_NULL(sc); + + stat = ehci_alloc_sqtd(sc); + if (stat == NULL) { + err = USBD_NOMEM; + goto bad2; + } + + sqh = epipe->sqh; + setup = sqh->inactivesqtd; + sqh->inactivesqtd = newinactive; + epipe->u.ctl.length = len; + + /* Set up data transaction */ + if (len != 0) { + ehci_soft_qtd_t *end; + + err = ehci_alloc_sqtd_chain(epipe, sc, len, isread, xfer, + NULL, newinactive, &next, &end); + if (err) + goto bad3; + end->qtd.qtd_status &= htohc32(sc, ~EHCI_QTD_IOC); + end->nextqtd = stat; + end->qtd.qtd_next = htohc32(sc, stat->physaddr); + end->qtd.qtd_altnext = htohc32(sc, newinactive->physaddr); + } else { + next = stat; + } + + memcpy(KERNADDR(&epipe->u.ctl.reqdma, 0), req, sizeof *req); + + /* Clear toggle, and do not activate until complete */ + setup->qtd.qtd_status = htohc32(sc, + EHCI_QTD_SET_PID(EHCI_QTD_PID_SETUP) | + EHCI_QTD_SET_CERR(3) | + EHCI_QTD_SET_TOGGLE(0) | + EHCI_QTD_SET_BYTES(sizeof *req) + ); + setup->qtd.qtd_buffer[0] = htohc32(sc, DMAADDR(&epipe->u.ctl.reqdma, 0)); + setup->qtd.qtd_buffer_hi[0] = 0; + setup->nextqtd = next; + setup->qtd.qtd_next = htohc32(sc, next->physaddr); + setup->qtd.qtd_altnext = htohc32(sc, newinactive->physaddr); + setup->xfer = xfer; + setup->len = sizeof *req; + + stat->qtd.qtd_status = htohc32(sc, + EHCI_QTD_ACTIVE | + EHCI_QTD_SET_PID(isread ? EHCI_QTD_PID_OUT : EHCI_QTD_PID_IN) | + EHCI_QTD_SET_CERR(3) | + EHCI_QTD_SET_TOGGLE(1) | + EHCI_QTD_IOC + ); + stat->qtd.qtd_buffer[0] = 0; /* XXX not needed? */ + stat->qtd.qtd_buffer_hi[0] = 0; /* XXX not needed? */ + stat->nextqtd = newinactive; + stat->qtd.qtd_next = htohc32(sc, newinactive->physaddr); + stat->qtd.qtd_altnext = htohc32(sc, newinactive->physaddr); + stat->xfer = xfer; + stat->len = 0; + +#ifdef EHCI_DEBUG + if (ehcidebug > 5) { + DPRINTF(("ehci_device_request:\n")); + ehci_dump_sqh(sc, sqh); + ehci_dump_sqtds(sc, setup); + } +#endif + + exfer->sqtdstart = setup; + exfer->sqtdend = stat; +#ifdef DIAGNOSTIC + if (!exfer->isdone) { + printf("ehci_device_request: not done, exfer=%p\n", exfer); + } + exfer->isdone = 0; +#endif + + /* Activate the new qTD in the QH list. */ + s = splusb(); + ehci_activate_qh(sc, sqh, setup); + if (xfer->timeout && !sc->sc_bus.use_polling) { + callout_reset(&xfer->timeout_handle, MS_TO_TICKS(xfer->timeout), + ehci_timeout, xfer); + } + ehci_add_intr_list(sc, exfer); + xfer->status = USBD_IN_PROGRESS; + splx(s); + +#ifdef EHCI_DEBUG + if (ehcidebug > 10) { + DPRINTF(("ehci_device_request: status=%x\n", + EOREAD4(sc, EHCI_USBSTS))); + delay(10000); + ehci_dump_regs(sc); + ehci_dump_sqh(sc, sc->sc_async_head); + ehci_dump_sqh(sc, sqh); + ehci_dump_sqtds(sc, setup); + } +#endif + + return (USBD_NORMAL_COMPLETION); + + bad3: + sqh->inactivesqtd = setup; + ehci_free_sqtd(sc, stat); + bad2: + ehci_free_sqtd(sc, newinactive); + bad1: + DPRINTFN(-1,("ehci_device_request: no memory\n")); + xfer->status = err; + usb_transfer_complete(xfer); + return (err); +#undef exfer +} + +/************************/ + +static usbd_status +ehci_device_bulk_transfer(usbd_xfer_handle xfer) +{ + usbd_status err; + + /* Insert last in queue. */ + err = usb_insert_transfer(xfer); + if (err) + return (err); + + /* Pipe isn't running, start first */ + return (ehci_device_bulk_start(STAILQ_FIRST(&xfer->pipe->queue))); +} + +usbd_status +ehci_device_bulk_start(usbd_xfer_handle xfer) +{ +#define exfer EXFER(xfer) + struct ehci_pipe *epipe = (struct ehci_pipe *)xfer->pipe; + usbd_device_handle dev = epipe->pipe.device; + ehci_softc_t *sc = (ehci_softc_t *)dev->bus; + ehci_soft_qtd_t *data, *dataend, *newinactive; + ehci_soft_qh_t *sqh; + usbd_status err; + int len, isread, endpt; + int s; + + DPRINTFN(2, ("ehci_device_bulk_start: xfer=%p len=%d flags=%d\n", + xfer, xfer->length, xfer->flags)); + + if (sc->sc_dying) + return (USBD_IOERROR); + +#ifdef DIAGNOSTIC + if (xfer->rqflags & URQ_REQUEST) + panic("ehci_device_bulk_start: a request"); +#endif + + len = xfer->length; + endpt = epipe->pipe.endpoint->edesc->bEndpointAddress; + isread = UE_GET_DIR(endpt) == UE_DIR_IN; + sqh = epipe->sqh; + + epipe->u.bulk.length = len; + + newinactive = ehci_alloc_sqtd(sc); + if (newinactive == NULL) { + DPRINTFN(-1,("ehci_device_bulk_start: no sqtd memory\n")); + err = USBD_NOMEM; + xfer->status = err; + usb_transfer_complete(xfer); + return (err); + } + newinactive->qtd.qtd_status = htohc32(sc, 0); + newinactive->qtd.qtd_next = EHCI_NULL(sc); + newinactive->qtd.qtd_altnext = EHCI_NULL(sc); + err = ehci_alloc_sqtd_chain(epipe, sc, len, isread, xfer, + sqh->inactivesqtd, newinactive, &data, &dataend); + if (err) { + DPRINTFN(-1,("ehci_device_bulk_start: no memory\n")); + ehci_free_sqtd(sc, newinactive); + xfer->status = err; + usb_transfer_complete(xfer); + return (err); + } + dataend->nextqtd = newinactive; + dataend->qtd.qtd_next = htohc32(sc, newinactive->physaddr); + dataend->qtd.qtd_altnext = htohc32(sc, newinactive->physaddr); + sqh->inactivesqtd = newinactive; + +#ifdef EHCI_DEBUG + if (ehcidebug > 5) { + DPRINTF(("ehci_device_bulk_start: data(1)\n")); + ehci_dump_sqh(sc, sqh); + ehci_dump_sqtds(sc, data); + } +#endif + + /* Set up interrupt info. */ + exfer->sqtdstart = data; + exfer->sqtdend = dataend; +#ifdef DIAGNOSTIC + if (!exfer->isdone) { + printf("ehci_device_bulk_start: not done, ex=%p\n", exfer); + } + exfer->isdone = 0; +#endif + + s = splusb(); + ehci_activate_qh(sc, sqh, data); + if (xfer->timeout && !sc->sc_bus.use_polling) { + callout_reset(&xfer->timeout_handle, MS_TO_TICKS(xfer->timeout), + ehci_timeout, xfer); + } + ehci_add_intr_list(sc, exfer); + xfer->status = USBD_IN_PROGRESS; + splx(s); + +#ifdef EHCI_DEBUG + if (ehcidebug > 10) { + DPRINTF(("ehci_device_bulk_start: data(2)\n")); + delay(10000); + DPRINTF(("ehci_device_bulk_start: data(3)\n")); + ehci_dump_regs(sc); +#if 0 + printf("async_head:\n"); + ehci_dump_sqh(sc->sc_async_head); +#endif + printf("sqh:\n"); + ehci_dump_sqh(sc, sqh); + ehci_dump_sqtds(sc, data); + } +#endif + + if (sc->sc_bus.use_polling) + ehci_waitintr(sc, xfer); + + return (USBD_IN_PROGRESS); +#undef exfer +} + +static void +ehci_device_bulk_abort(usbd_xfer_handle xfer) +{ + DPRINTF(("ehci_device_bulk_abort: xfer=%p\n", xfer)); + ehci_abort_xfer(xfer, USBD_CANCELLED); +} + +/* + * Close a device bulk pipe. + */ +static void +ehci_device_bulk_close(usbd_pipe_handle pipe) +{ + ehci_softc_t *sc = (ehci_softc_t *)pipe->device->bus; + + DPRINTF(("ehci_device_bulk_close: pipe=%p\n", pipe)); + ehci_close_pipe(pipe, sc->sc_async_head); +} + +void +ehci_device_bulk_done(usbd_xfer_handle xfer) +{ + struct ehci_xfer *ex = EXFER(xfer); + ehci_softc_t *sc = (ehci_softc_t *)xfer->pipe->device->bus; + struct ehci_pipe *epipe = (struct ehci_pipe *)xfer->pipe; + + DPRINTFN(10,("ehci_bulk_done: xfer=%p, actlen=%d\n", + xfer, xfer->actlen)); + + if (xfer->status != USBD_NOMEM && ehci_active_intr_list(ex)) { + ehci_del_intr_list(ex); /* remove from active list */ + ehci_free_sqtd_chain(sc, epipe->sqh, ex->sqtdstart, + ex->sqtdend->nextqtd); + } + + DPRINTFN(5, ("ehci_bulk_done: length=%d\n", xfer->actlen)); +} + +/************************/ + +static usbd_status +ehci_device_setintr(ehci_softc_t *sc, ehci_soft_qh_t *sqh, int ival) +{ + struct ehci_soft_islot *isp; + int islot, lev; + + /* Find a poll rate that is large enough. */ + for (lev = EHCI_IPOLLRATES - 1; lev > 0; lev--) + if (EHCI_ILEV_IVAL(lev) <= ival) + break; + + /* Pick an interrupt slot at the right level. */ + /* XXX could do better than picking at random. */ + islot = EHCI_IQHIDX(lev, arc4random()); + + sqh->islot = islot; + isp = &sc->sc_islots[islot]; + ehci_add_qh(sc, sqh, isp->sqh); + + return (USBD_NORMAL_COMPLETION); +} + +static usbd_status +ehci_device_intr_transfer(usbd_xfer_handle xfer) +{ + usbd_status err; + + /* Insert last in queue. */ + err = usb_insert_transfer(xfer); + if (err) + return (err); + + /* + * Pipe isn't running (otherwise err would be USBD_INPROG), + * so start it first. + */ + return (ehci_device_intr_start(STAILQ_FIRST(&xfer->pipe->queue))); +} + +static usbd_status +ehci_device_intr_start(usbd_xfer_handle xfer) +{ +#define exfer EXFER(xfer) + struct ehci_pipe *epipe = (struct ehci_pipe *)xfer->pipe; + usbd_device_handle dev = xfer->pipe->device; + ehci_softc_t *sc = (ehci_softc_t *)dev->bus; + ehci_soft_qtd_t *data, *dataend, *newinactive; + ehci_soft_qh_t *sqh; + usbd_status err; + int len, isread, endpt; + int s; + + DPRINTFN(2, ("ehci_device_intr_start: xfer=%p len=%d flags=%d\n", + xfer, xfer->length, xfer->flags)); + + if (sc->sc_dying) + return (USBD_IOERROR); + +#ifdef DIAGNOSTIC + if (xfer->rqflags & URQ_REQUEST) + panic("ehci_device_intr_start: a request"); +#endif + + len = xfer->length; + endpt = epipe->pipe.endpoint->edesc->bEndpointAddress; + isread = UE_GET_DIR(endpt) == UE_DIR_IN; + sqh = epipe->sqh; + + epipe->u.intr.length = len; + + newinactive = ehci_alloc_sqtd(sc); + if (newinactive == NULL) { + DPRINTFN(-1,("ehci_device_intr_start: no sqtd memory\n")); + err = USBD_NOMEM; + xfer->status = err; + usb_transfer_complete(xfer); + return (err); + } + newinactive->qtd.qtd_status = htohc32(sc, 0); + newinactive->qtd.qtd_next = EHCI_NULL(sc); + newinactive->qtd.qtd_altnext = EHCI_NULL(sc); + err = ehci_alloc_sqtd_chain(epipe, sc, len, isread, xfer, + sqh->inactivesqtd, newinactive, &data, &dataend); + if (err) { + DPRINTFN(-1, ("ehci_device_intr_start: no memory\n")); + xfer->status = err; + usb_transfer_complete(xfer); + return (err); + } + dataend->nextqtd = newinactive; + dataend->qtd.qtd_next = htohc32(sc, newinactive->physaddr); + dataend->qtd.qtd_altnext = htohc32(sc, newinactive->physaddr); + sqh->inactivesqtd = newinactive; + +#ifdef EHCI_DEBUG + if (ehcidebug > 5) { + DPRINTF(("ehci_device_intr_start: data(1)\n")); + ehci_dump_sqh(sc, sqh); + ehci_dump_sqtds(sc, data); + } +#endif + + /* Set up interrupt info. */ + exfer->sqtdstart = data; + exfer->sqtdend = dataend; +#ifdef DIAGNOSTIC + if (!exfer->isdone) { + printf("ehci_device_intr_start: not done, ex=%p\n", exfer); + } + exfer->isdone = 0; +#endif + + s = splusb(); + ehci_activate_qh(sc, sqh, data); + if (xfer->timeout && !sc->sc_bus.use_polling) { + callout_reset(&xfer->timeout_handle, MS_TO_TICKS(xfer->timeout), + ehci_timeout, xfer); + } + ehci_add_intr_list(sc, exfer); + xfer->status = USBD_IN_PROGRESS; + splx(s); + +#ifdef EHCI_DEBUG + if (ehcidebug > 10) { + DPRINTF(("ehci_device_intr_start: data(2)\n")); + delay(10000); + DPRINTF(("ehci_device_intr_start: data(3)\n")); + ehci_dump_regs(sc); + printf("sqh:\n"); + ehci_dump_sqh(sc, sqh); + ehci_dump_sqtds(sc, data); + } +#endif + + if (sc->sc_bus.use_polling) + ehci_waitintr(sc, xfer); + + return (USBD_IN_PROGRESS); +#undef exfer +} + +static void +ehci_device_intr_abort(usbd_xfer_handle xfer) +{ + DPRINTFN(1, ("ehci_device_intr_abort: xfer=%p\n", xfer)); + if (xfer->pipe->intrxfer == xfer) { + DPRINTFN(1, ("ehci_device_intr_abort: remove\n")); + xfer->pipe->intrxfer = NULL; + } + /* + * XXX - abort_xfer uses ehci_sync_hc, which syncs via the advance + * async doorbell. That's dependant on the async list, wheras + * intr xfers are periodic, should not use this? + */ + ehci_abort_xfer(xfer, USBD_CANCELLED); +} + +static void +ehci_device_intr_close(usbd_pipe_handle pipe) +{ + ehci_softc_t *sc = (ehci_softc_t *)pipe->device->bus; + struct ehci_pipe *epipe = (struct ehci_pipe *)pipe; + struct ehci_soft_islot *isp; + + isp = &sc->sc_islots[epipe->sqh->islot]; + ehci_close_pipe(pipe, isp->sqh); +} + +static void +ehci_device_intr_done(usbd_xfer_handle xfer) +{ +#define exfer EXFER(xfer) + struct ehci_xfer *ex = EXFER(xfer); + ehci_softc_t *sc = (ehci_softc_t *)xfer->pipe->device->bus; + struct ehci_pipe *epipe = (struct ehci_pipe *)xfer->pipe; + ehci_soft_qtd_t *data, *dataend, *newinactive; + ehci_soft_qh_t *sqh; + usbd_status err; + int len, isread, endpt, s; + + DPRINTFN(10, ("ehci_device_intr_done: xfer=%p, actlen=%d\n", + xfer, xfer->actlen)); + + sqh = epipe->sqh; + if (xfer->pipe->repeat) { + ehci_free_sqtd_chain(sc, sqh, ex->sqtdstart, + ex->sqtdend->nextqtd); + + len = epipe->u.intr.length; + xfer->length = len; + endpt = epipe->pipe.endpoint->edesc->bEndpointAddress; + isread = UE_GET_DIR(endpt) == UE_DIR_IN; + + newinactive = ehci_alloc_sqtd(sc); + if (newinactive == NULL) { + DPRINTFN(-1, + ("ehci_device_intr_done: no sqtd memory\n")); + err = USBD_NOMEM; + xfer->status = err; + return; + } + newinactive->qtd.qtd_status = htohc32(sc, 0); + newinactive->qtd.qtd_next = EHCI_NULL(sc); + newinactive->qtd.qtd_altnext = EHCI_NULL(sc); + err = ehci_alloc_sqtd_chain(epipe, sc, len, isread, xfer, + sqh->inactivesqtd, newinactive, &data, &dataend); + if (err) { + DPRINTFN(-1, ("ehci_device_intr_done: no memory\n")); + xfer->status = err; + return; + } + dataend->nextqtd = newinactive; + dataend->qtd.qtd_next = htohc32(sc, newinactive->physaddr); + dataend->qtd.qtd_altnext = htohc32(sc, newinactive->physaddr); + sqh->inactivesqtd = newinactive; + + /* Set up interrupt info. */ + exfer->sqtdstart = data; + exfer->sqtdend = dataend; +#ifdef DIAGNOSTIC + if (!exfer->isdone) { + printf("ehci_device_intr_done: not done, ex=%p\n", + exfer); + } + exfer->isdone = 0; +#endif + + s = splusb(); + ehci_activate_qh(sc, sqh, data); + if (xfer->timeout && !sc->sc_bus.use_polling) { + callout_reset(&xfer->timeout_handle, + MS_TO_TICKS(xfer->timeout), ehci_timeout, xfer); + } + splx(s); + + xfer->status = USBD_IN_PROGRESS; + } else if (xfer->status != USBD_NOMEM && ehci_active_intr_list(ex)) { + ehci_del_intr_list(ex); /* remove from active list */ + ehci_free_sqtd_chain(sc, sqh, ex->sqtdstart, + ex->sqtdend->nextqtd); + } +#undef exfer +} + +/************************/ + +static usbd_status +ehci_device_isoc_transfer(usbd_xfer_handle xfer) +{ + usbd_status err; + + err = usb_insert_transfer(xfer); + if (err && err != USBD_IN_PROGRESS) + return (err); + + return (ehci_device_isoc_start(xfer)); +} + +static usbd_status +ehci_device_isoc_start(usbd_xfer_handle xfer) +{ + struct ehci_pipe *epipe; + usbd_device_handle dev; + ehci_softc_t *sc; + struct ehci_xfer *exfer; + ehci_soft_itd_t *itd, *prev, *start, *stop; + usb_dma_t *dma_buf; + int i, j, k, frames, uframes, ufrperframe; + int s, trans_count, offs, total_length; + int frindex; + + start = NULL; + prev = NULL; + itd = NULL; + trans_count = 0; + total_length = 0; + exfer = (struct ehci_xfer *) xfer; + sc = (ehci_softc_t *)xfer->pipe->device->bus; + dev = xfer->pipe->device; + epipe = (struct ehci_pipe *)xfer->pipe; + + /* + * To allow continuous transfers, above we start all transfers + * immediately. However, we're still going to get usbd_start_next call + * this when another xfer completes. So, check if this is already + * in progress or not + */ + + if (exfer->itdstart != NULL) + return (USBD_IN_PROGRESS); + + DPRINTFN(2, ("ehci_device_isoc_start: xfer %p len %d flags %d\n", + xfer, xfer->length, xfer->flags)); + + if (sc->sc_dying) + return (USBD_IOERROR); + + /* + * To avoid complication, don't allow a request right now that'll span + * the entire frame table. To within 4 frames, to allow some leeway + * on either side of where the hc currently is. + */ + if ((1 << (epipe->pipe.endpoint->edesc->bInterval)) * + xfer->nframes >= (sc->sc_flsize - 4) * 8) { + printf("ehci: isoc descriptor requested that spans the entire" + " frametable, too many frames\n"); + return (USBD_INVAL); + } + +#ifdef DIAGNOSTIC + if (xfer->rqflags & URQ_REQUEST) + panic("ehci_device_isoc_start: request\n"); + + if (!exfer->isdone) + printf("ehci_device_isoc_start: not done, ex = %p\n", exfer); + exfer->isdone = 0; +#endif + + /* + * Step 1: Allocate and initialize itds, how many do we need? + * One per transfer if interval >= 8 microframes, fewer if we use + * multiple microframes per frame. + */ + + i = epipe->pipe.endpoint->edesc->bInterval; + if (i > 16 || i == 0) { + /* Spec page 271 says intervals > 16 are invalid */ + DPRINTF(("ehci_device_isoc_start: bInvertal %d invalid\n", i)); + return (USBD_INVAL); + } + + switch (i) { + case 1: + ufrperframe = 8; + break; + case 2: + ufrperframe = 4; + break; + case 3: + ufrperframe = 2; + break; + default: + ufrperframe = 1; + break; + } + frames = (xfer->nframes + (ufrperframe - 1)) / ufrperframe; + uframes = 8 / ufrperframe; + + if (frames == 0) { + DPRINTF(("ehci_device_isoc_start: frames == 0\n")); + return (USBD_INVAL); + } + + dma_buf = xfer->buffer; + offs = 0; + + for (i = 0; i < frames; i++) { + int froffs = offs; + itd = ehci_alloc_itd(sc); + + if (prev != NULL) { + prev->itd.itd_next = + htohc32(sc, itd->physaddr | EHCI_LINK_ITD); + prev->xfer_next = itd; + } else { + start = itd; + } + + /* + * Step 1.5, initialize uframes + */ + for (j = 0; j < 8; j += uframes) { + /* Calculate which page in the list this starts in */ + int addr = DMAADDR(dma_buf, froffs); + addr = EHCI_PAGE_OFFSET(addr); + addr += (offs - froffs); + addr = EHCI_PAGE(addr); + addr /= EHCI_PAGE_SIZE; + + /* This gets the initial offset into the first page, + * looks how far further along the current uframe + * offset is. Works out how many pages that is. + */ + + itd->itd.itd_ctl[j] = htohc32(sc, EHCI_ITD_ACTIVE | + EHCI_ITD_SET_LEN(xfer->frlengths[trans_count]) | + EHCI_ITD_SET_PG(addr) | + EHCI_ITD_SET_OFFS(EHCI_PAGE_OFFSET(DMAADDR(dma_buf, + offs)))); + + total_length += xfer->frlengths[trans_count]; + offs += xfer->frlengths[trans_count]; + trans_count++; + + if (trans_count >= xfer->nframes) { /*Set IOC*/ + itd->itd.itd_ctl[j] |= htohc32(sc, EHCI_ITD_IOC); + } + } + + /* Step 1.75, set buffer pointers. To simplify matters, all + * pointers are filled out for the next 7 hardware pages in + * the dma block, so no need to worry what pages to cover + * and what to not. + */ + + for (j=0; j < 7; j++) { + /* + * Don't try to lookup a page that's past the end + * of buffer + */ + int page_offs = EHCI_PAGE(froffs + + (EHCI_PAGE_SIZE * j)); + if (page_offs >= dma_buf->block->size) + break; + + int page = DMAADDR(dma_buf, page_offs); + page = EHCI_PAGE(page); + itd->itd.itd_bufr[j] = + htohc32(sc, EHCI_ITD_SET_BPTR(page) | EHCI_LINK_ITD); + } + + /* + * Other special values + */ + + k = epipe->pipe.endpoint->edesc->bEndpointAddress; + itd->itd.itd_bufr[0] |= htohc32(sc, + EHCI_ITD_SET_EP(UE_GET_ADDR(k)) | + EHCI_ITD_SET_DADDR(epipe->pipe.device->address)); + + k = (UE_GET_DIR(epipe->pipe.endpoint->edesc->bEndpointAddress)) + ? 1 : 0; + j = UE_GET_SIZE( + UGETW(epipe->pipe.endpoint->edesc->wMaxPacketSize)); + itd->itd.itd_bufr[1] |= htohc32(sc, EHCI_ITD_SET_DIR(k) | + EHCI_ITD_SET_MAXPKT(UE_GET_SIZE(j))); + + /* FIXME: handle invalid trans */ + itd->itd.itd_bufr[2] |= + htohc32(sc, EHCI_ITD_SET_MULTI(UE_GET_TRANS(j)+1)); + prev = itd; + } /* End of frame */ + + stop = itd; + stop->xfer_next = NULL; + exfer->isoc_len = total_length; + + /* + * Part 2: Transfer descriptors have now been set up, now they must + * be scheduled into the period frame list. Erk. Not wanting to + * complicate matters, transfer is denied if the transfer spans + * more than the period frame list. + */ + + s = splusb(); + + /* Start inserting frames */ + if (epipe->u.isoc.cur_xfers > 0) { + frindex = epipe->u.isoc.next_frame; + } else { + frindex = EOREAD4(sc, EHCI_FRINDEX); + frindex = frindex >> 3; /* Erase microframe index */ + frindex += 2; + } + + if (frindex >= sc->sc_flsize) + frindex &= (sc->sc_flsize - 1); + + /* Whats the frame interval? */ + i = (1 << epipe->pipe.endpoint->edesc->bInterval); + if (i / 8 == 0) + i = 1; + else + i /= 8; + + itd = start; + for (j = 0; j < frames; j++) { + if (itd == NULL) + panic("ehci: unexpectedly ran out of isoc itds," + "isoc_start\n"); + + itd->itd.itd_next = sc->sc_flist[frindex]; + if (itd->itd.itd_next == 0) + /* FIXME: frindex table gets initialized to NULL + * or EHCI_NULL? */ + itd->itd.itd_next = EHCI_NULL(sc); + + sc->sc_flist[frindex] = htohc32(sc, EHCI_LINK_ITD | itd->physaddr); + + itd->u.frame_list.next = sc->sc_softitds[frindex]; + sc->sc_softitds[frindex] = itd; + if (itd->u.frame_list.next != NULL) + itd->u.frame_list.next->u.frame_list.prev = itd; + itd->slot = frindex; + itd->u.frame_list.prev = NULL; + + frindex += i; + if (frindex >= sc->sc_flsize) + frindex -= sc->sc_flsize; + + itd = itd->xfer_next; + } + + epipe->u.isoc.cur_xfers++; + epipe->u.isoc.next_frame = frindex; + + exfer->itdstart = start; + exfer->itdend = stop; + exfer->sqtdstart = NULL; + exfer->sqtdstart = NULL; + + ehci_add_intr_list(sc, exfer); + xfer->status = USBD_IN_PROGRESS; + xfer->done = 0; + splx(s); + + if (sc->sc_bus.use_polling) { + printf("Starting ehci isoc xfer with polling. Bad idea?\n"); + ehci_waitintr(sc, xfer); + } + + return (USBD_IN_PROGRESS); +} + +static void +ehci_device_isoc_abort(usbd_xfer_handle xfer) +{ + DPRINTFN(1, ("ehci_device_isoc_abort: xfer = %p\n", xfer)); + ehci_abort_isoc_xfer(xfer, USBD_CANCELLED); +} + +static void +ehci_device_isoc_close(usbd_pipe_handle pipe) +{ + printf("ehci_device_isoc_close: nothing in the pipe to free?\n"); +} + +static void +ehci_device_isoc_done(usbd_xfer_handle xfer) +{ + struct ehci_xfer *exfer; + ehci_softc_t *sc; + struct ehci_pipe *epipe; + int s; + + exfer = EXFER(xfer); + sc = (ehci_softc_t *)xfer->pipe->device->bus; + epipe = (struct ehci_pipe *) xfer->pipe; + + s = splusb(); + epipe->u.isoc.cur_xfers--; + if (xfer->status != USBD_NOMEM && ehci_active_intr_list(exfer)) { + ehci_del_intr_list(exfer); + ehci_rem_free_itd_chain(sc, exfer); + } + splx(s); +} diff --git a/sys/legacy/dev/usb/ehci_ddb.c b/sys/legacy/dev/usb/ehci_ddb.c new file mode 100644 index 0000000..3ebd130 --- /dev/null +++ b/sys/legacy/dev/usb/ehci_ddb.c @@ -0,0 +1,255 @@ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include "opt_ddb.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/endian.h> +#include <sys/bus.h> +#include <sys/lock.h> +#include <sys/lockmgr.h> + +#include <machine/bus.h> +#include <machine/endian.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdivar.h> + +#include <dev/usb/ehcireg.h> +#include <dev/usb/ehcivar.h> + +#ifdef DDB +#include <ddb/ddb.h> +#include <ddb/db_sym.h> +#else +#define db_printf printf +#endif + +extern ehci_softc_t *theehci; /* XXX */ + +void +ehci_dump_regs(ehci_softc_t *sc) +{ + int i; + db_printf("cmd=0x%08x, sts=0x%08x, ien=0x%08x\n", + EOREAD4(sc, EHCI_USBCMD), + EOREAD4(sc, EHCI_USBSTS), + EOREAD4(sc, EHCI_USBINTR)); + db_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++) + db_printf("port %d status=0x%08x\n", i, + EOREAD4(sc, EHCI_PORTSC(i))); +} + +static void +ehci_dump_link(ehci_softc_t *sc, ehci_link_t link, int type) +{ + link = hc32toh(sc, link); + db_printf("0x%08x", link); + if (link & EHCI_LINK_TERMINATE) + db_printf("<T>"); + else { + db_printf("<"); + if (type) { + switch (EHCI_LINK_TYPE(link)) { + case EHCI_LINK_ITD: db_printf("ITD"); break; + case EHCI_LINK_QH: db_printf("QH"); break; + case EHCI_LINK_SITD: db_printf("SITD"); break; + case EHCI_LINK_FSTN: db_printf("FSTN"); break; + } + } + db_printf(">"); + } +} + +void +ehci_dump_sqtds(ehci_softc_t *sc, ehci_soft_qtd_t *sqtd) +{ + int i; + u_int32_t stop; + + stop = 0; + for (i = 0; sqtd && i < 20 && !stop; sqtd = sqtd->nextqtd, i++) { + ehci_dump_sqtd(sc, sqtd); + stop = sqtd->qtd.qtd_next & htohc32(sc, EHCI_LINK_TERMINATE); + } + if (sqtd) + db_printf("dump aborted, too many TDs\n"); +} + +void +ehci_dump_qtd(ehci_softc_t *sc, ehci_qtd_t *qtd) +{ + u_int32_t s; + + db_printf(" next="); ehci_dump_link(sc, qtd->qtd_next, 0); + db_printf(" altnext="); ehci_dump_link(sc, qtd->qtd_altnext, 0); + db_printf("\n"); + s = hc32toh(sc, qtd->qtd_status); + db_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)); + db_printf(" cerr=%d pid=%d stat=%b\n", EHCI_QTD_GET_CERR(s), + EHCI_QTD_GET_PID(s), + EHCI_QTD_GET_STATUS(s), EHCI_QTD_STATUS_BITS); + for (s = 0; s < 5; s++) + db_printf(" buffer[%d]=0x%08x\n", s, hc32toh(sc, qtd->qtd_buffer[s])); +} + +void +ehci_dump_sqtd(ehci_softc_t *sc, ehci_soft_qtd_t *sqtd) +{ + db_printf("QTD(%p) at 0x%08x:\n", sqtd, sqtd->physaddr); + ehci_dump_qtd(sc, &sqtd->qtd); +} + +void +ehci_dump_sqh(ehci_softc_t *sc, ehci_soft_qh_t *sqh) +{ + ehci_qh_t *qh = &sqh->qh; + u_int32_t endp, endphub; + + db_printf("QH(%p) at 0x%08x:\n", sqh, sqh->physaddr); + db_printf(" sqtd=%p inactivesqtd=%p\n", sqh->sqtd, sqh->inactivesqtd); + db_printf(" link="); ehci_dump_link(sc, qh->qh_link, 1); db_printf("\n"); + endp = hc32toh(sc, qh->qh_endp); + db_printf(" endp=0x%08x\n", endp); + db_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)); + db_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); + db_printf(" endphub=0x%08x\n", endphub); + db_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)); + db_printf(" curqtd="); ehci_dump_link(sc, qh->qh_curqtd, 0); db_printf("\n"); + db_printf("Overlay qTD:\n"); + ehci_dump_qtd(sc, &qh->qh_qtd); +} + +void +ehci_dump_itd(ehci_softc_t *sc, struct ehci_soft_itd *itd) +{ + ehci_isoc_trans_t t; + ehci_isoc_bufr_ptr_t b, b2, b3; + int i; + + db_printf("ITD: next phys=%X\n", itd->itd.itd_next); + + for (i = 0; i < 8;i++) { + t = hc32toh(sc, itd->itd.itd_ctl[i]); + db_printf("ITDctl %d: stat=%X len=%X ioc=%X pg=%X offs=%X\n", i, + EHCI_ITD_GET_STATUS(t), EHCI_ITD_GET_LEN(t), + EHCI_ITD_GET_IOC(t), EHCI_ITD_GET_PG(t), + EHCI_ITD_GET_OFFS(t)); + } + db_printf("ITDbufr: "); + for (i = 0; i < 7; i++) + db_printf("%X,", EHCI_ITD_GET_BPTR(hc32toh(sc, itd->itd.itd_bufr[i]))); + + b = hc32toh(sc, itd->itd.itd_bufr[0]); + b2 = hc32toh(sc, itd->itd.itd_bufr[1]); + b3 = hc32toh(sc, itd->itd.itd_bufr[2]); + db_printf("\nep=%X daddr=%X dir=%d maxpkt=%X multi=%X\n", + EHCI_ITD_GET_EP(b), EHCI_ITD_GET_DADDR(b), EHCI_ITD_GET_DIR(b2), + EHCI_ITD_GET_MAXPKT(b2), EHCI_ITD_GET_MULTI(b3)); +} + +void +ehci_dump_sitd(ehci_softc_t *sc, struct ehci_soft_itd *itd) +{ + db_printf("SITD %p next=%p prev=%p xfernext=%p physaddr=%X slot=%d\n", + itd, itd->u.frame_list.next, itd->u.frame_list.prev, + itd->xfer_next, itd->physaddr, itd->slot); +} + +void +ehci_dump_exfer(struct ehci_xfer *ex) +{ +#ifdef DIAGNOSTIC + db_printf("%p: sqtdstart %p end %p itdstart %p end %p isdone %d\n", + ex, ex->sqtdstart, ex->sqtdend, ex->itdstart, + ex->itdend, ex->isdone); +#else + db_printf("%p: sqtdstart %p end %p itdstart %p end %p\n", + ex, ex->sqtdstart, ex->sqtdend, ex->itdstart, ex->itdend); +#endif +} + +#ifdef DDB +DB_SHOW_COMMAND(ehci, db_show_ehci) +{ + if (!have_addr) { + db_printf("usage: show ehci <addr>\n"); + return; + } + ehci_dump_regs((ehci_softc_t *) addr); +} + +DB_SHOW_COMMAND(ehci_sqtds, db_show_ehci_sqtds) +{ + if (!have_addr) { + db_printf("usage: show ehci_sqtds <addr>\n"); + return; + } + ehci_dump_sqtds(theehci, (ehci_soft_qtd_t *) addr); +} + +DB_SHOW_COMMAND(ehci_qtd, db_show_ehci_qtd) +{ + if (!have_addr) { + db_printf("usage: show ehci_qtd <addr>\n"); + return; + } + ehci_dump_qtd(theehci, (ehci_qtd_t *) addr); +} + +DB_SHOW_COMMAND(ehci_sqh, db_show_ehci_sqh) +{ + if (!have_addr) { + db_printf("usage: show ehci_sqh <addr>\n"); + return; + } + ehci_dump_sqh(theehci, (ehci_soft_qh_t *) addr); +} + +DB_SHOW_COMMAND(ehci_itd, db_show_ehci_itd) +{ + if (!have_addr) { + db_printf("usage: show ehci_itd <addr>\n"); + return; + } + ehci_dump_itd(theehci, (struct ehci_soft_itd *) addr); +} + +DB_SHOW_COMMAND(ehci_sitd, db_show_ehci_sitd) +{ + if (!have_addr) { + db_printf("usage: show ehci_sitd <addr>\n"); + return; + } + ehci_dump_itd(theehci, (struct ehci_soft_itd *) addr); +} + +DB_SHOW_COMMAND(ehci_xfer, db_show_ehci_xfer) +{ + if (!have_addr) { + db_printf("usage: show ehci_xfer <addr>\n"); + return; + } + ehci_dump_exfer((struct ehci_xfer *) addr); +} +#endif /* DDB */ diff --git a/sys/legacy/dev/usb/ehci_ixp4xx.c b/sys/legacy/dev/usb/ehci_ixp4xx.c new file mode 100644 index 0000000..bc800d3 --- /dev/null +++ b/sys/legacy/dev/usb/ehci_ixp4xx.c @@ -0,0 +1,360 @@ +/*- + * Copyright (c) 2008 Sam Leffler. 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 ``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. + */ + +/* + * IXP435 attachment driver for the USB Enhanced Host Controller. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include "opt_bus.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/bus.h> +#include <sys/endian.h> +#include <sys/queue.h> +#include <sys/lockmgr.h> +#include <sys/rman.h> + +#include <machine/bus.h> +#include <machine/resource.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdivar.h> +#include <dev/usb/usb_mem.h> + +#include <dev/usb/ehcireg.h> +#include <dev/usb/ehcivar.h> + +#include <arm/xscale/ixp425/ixp425reg.h> +#include <arm/xscale/ixp425/ixp425var.h> + +#define EHCI_VENDORID_IXP4XX 0x42fa05 +#define EHCI_HC_DEVSTR "IXP4XX Integrated USB 2.0 controller" + +struct ixp_ehci_softc { + ehci_softc_t base; /* storage for EHCI code */ + bus_space_tag_t iot; + bus_space_handle_t ioh; + struct bus_space tag; /* tag for private bus space ops */ +}; + +static int ehci_ixp_detach(device_t self); + +static uint8_t ehci_bs_r_1(void *, bus_space_handle_t, bus_size_t); +static void ehci_bs_w_1(void *, bus_space_handle_t, bus_size_t, u_int8_t); +static uint16_t ehci_bs_r_2(void *, bus_space_handle_t, bus_size_t); +static void ehci_bs_w_2(void *, bus_space_handle_t, bus_size_t, uint16_t); +static uint32_t ehci_bs_r_4(void *, bus_space_handle_t, bus_size_t); +static void ehci_bs_w_4(void *, bus_space_handle_t, bus_size_t, uint32_t); + +static int +ehci_ixp_suspend(device_t self) +{ + ehci_softc_t *sc; + int err; + + err = bus_generic_suspend(self); + if (err == 0) { + sc = device_get_softc(self); + ehci_power(PWR_SUSPEND, sc); + } + return err; +} + +static int +ehci_ixp_resume(device_t self) +{ + ehci_softc_t *sc = device_get_softc(self); + + ehci_power(PWR_RESUME, sc); + bus_generic_resume(self); + return 0; +} + +static int +ehci_ixp_shutdown(device_t self) +{ + ehci_softc_t *sc; + int err; + + err = bus_generic_shutdown(self); + if (err == 0) { + sc = device_get_softc(self); + ehci_shutdown(sc); + } + return err; +} + +static int +ehci_ixp_probe(device_t self) +{ + device_set_desc(self, EHCI_HC_DEVSTR); + return BUS_PROBE_DEFAULT; +} + +static int +ehci_ixp_attach(device_t self) +{ + struct ixp_ehci_softc *isc = device_get_softc(self); + ehci_softc_t *sc = &isc->base; + int err, rid; + + sc->sc_bus.usbrev = USBREV_2_0; + + /* NB: hints fix the memory location and irq */ + + rid = 0; + sc->io_res = bus_alloc_resource_any(self, SYS_RES_MEMORY, + &rid, RF_ACTIVE); + if (sc->io_res == NULL) { + device_printf(self, "Could not map memory\n"); + return ENXIO; + } + + /* + * Craft special resource for bus space ops that handle + * byte-alignment of non-word addresses. Also, since + * we're already intercepting bus space ops we handle + * the register window offset that could otherwise be + * done with bus_space_subregion. + */ + isc->iot = rman_get_bustag(sc->io_res); + isc->tag.bs_cookie = isc->iot; + /* read single */ + isc->tag.bs_r_1 = ehci_bs_r_1, + isc->tag.bs_r_2 = ehci_bs_r_2, + isc->tag.bs_r_4 = ehci_bs_r_4, + /* write (single) */ + isc->tag.bs_w_1 = ehci_bs_w_1, + isc->tag.bs_w_2 = ehci_bs_w_2, + isc->tag.bs_w_4 = ehci_bs_w_4, + + sc->iot = &isc->tag; + sc->ioh = rman_get_bushandle(sc->io_res); + sc->sc_size = IXP435_USB1_SIZE - 0x100; + + rid = 0; + sc->irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, + &rid, RF_ACTIVE); + if (sc->irq_res == NULL) { + device_printf(self, "Could not allocate irq\n"); + ehci_ixp_detach(self); + return ENXIO; + } + sc->sc_bus.bdev = device_add_child(self, "usb", -1); + if (!sc->sc_bus.bdev) { + device_printf(self, "Could not add USB device\n"); + ehci_ixp_detach(self); + return ENOMEM; + } + device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); + + sprintf(sc->sc_vendor, "Intel"); + sc->sc_id_vendor = EHCI_VENDORID_IXP4XX; + + err = bus_setup_intr(self, sc->irq_res, INTR_TYPE_BIO, + NULL, (driver_intr_t*)ehci_intr, sc, &sc->ih); + if (err) { + device_printf(self, "Could not setup irq, %d\n", err); + sc->ih = NULL; + ehci_ixp_detach(self); + return ENXIO; + } + + /* There are no companion USB controllers */ + sc->sc_ncomp = 0; + + /* Allocate a parent dma tag for DMA maps */ + err = bus_dma_tag_create(bus_get_dma_tag(self), 1, 0, + BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, + BUS_SPACE_MAXSIZE_32BIT, USB_DMA_NSEG, BUS_SPACE_MAXSIZE_32BIT, 0, + NULL, NULL, &sc->sc_bus.parent_dmatag); + if (err) { + device_printf(self, "Could not allocate parent DMA tag (%d)\n", + err); + ehci_ixp_detach(self); + return ENXIO; + } + + /* Allocate a dma tag for transfer buffers */ + err = bus_dma_tag_create(sc->sc_bus.parent_dmatag, 1, 0, + BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, + BUS_SPACE_MAXSIZE_32BIT, USB_DMA_NSEG, BUS_SPACE_MAXSIZE_32BIT, 0, + busdma_lock_mutex, &Giant, &sc->sc_bus.buffer_dmatag); + if (err) { + device_printf(self, "Could not allocate buffer DMA tag (%d)\n", + err); + ehci_ixp_detach(self); + return ENXIO; + } + + /* + * Arrange to force Host mode, select big-endian byte alignment, + * and arrange to not terminate reset operations (the adapter + * will ignore it if we do but might as well save a reg write). + * Also, the controller has an embedded Transaction Translator + * which means port speed must be read from the Port Status + * register following a port enable. + */ + sc->sc_flags |= EHCI_SCFLG_TT + | EHCI_SCFLG_SETMODE + | EHCI_SCFLG_BIGEDESC + | EHCI_SCFLG_BIGEMMIO + | EHCI_SCFLG_NORESTERM + ; + (void) ehci_reset(sc); + + err = ehci_init(sc); + if (!err) { + sc->sc_flags |= EHCI_SCFLG_DONEINIT; + err = device_probe_and_attach(sc->sc_bus.bdev); + } + + if (err) { + device_printf(self, "USB init failed err=%d\n", err); + ehci_ixp_detach(self); + return EIO; + } + return 0; +} + +static int +ehci_ixp_detach(device_t self) +{ + struct ixp_ehci_softc *isc = device_get_softc(self); + ehci_softc_t *sc = &isc->base; + int err; + + if (sc->sc_flags & EHCI_SCFLG_DONEINIT) { + ehci_detach(sc, 0); + sc->sc_flags &= ~EHCI_SCFLG_DONEINIT; + } + + /* + * Disable interrupts that might have been switched on in ehci_init() + */ + if (sc->iot && sc->ioh) + bus_space_write_4(sc->iot, sc->ioh, EHCI_USBINTR, 0); + if (sc->sc_bus.parent_dmatag != NULL) + bus_dma_tag_destroy(sc->sc_bus.parent_dmatag); + if (sc->sc_bus.buffer_dmatag != NULL) + bus_dma_tag_destroy(sc->sc_bus.buffer_dmatag); + + if (sc->irq_res && sc->ih) { + err = bus_teardown_intr(self, sc->irq_res, sc->ih); + + if (err) + device_printf(self, "Could not tear down irq, %d\n", + err); + sc->ih = NULL; + } + if (sc->sc_bus.bdev != NULL) { + device_delete_child(self, sc->sc_bus.bdev); + sc->sc_bus.bdev = NULL; + } + if (sc->irq_res != NULL) { + bus_release_resource(self, SYS_RES_IRQ, 0, sc->irq_res); + sc->irq_res = NULL; + } + if (sc->io_res != NULL) { + bus_release_resource(self, SYS_RES_MEMORY, 0, sc->io_res); + sc->io_res = NULL; + } + sc->iot = 0; + sc->ioh = 0; + return 0; +} + +/* + * Bus space accessors for PIO operations. + */ + +static uint8_t +ehci_bs_r_1(void *t, bus_space_handle_t h, bus_size_t o) +{ + return bus_space_read_1((bus_space_tag_t) t, h, + 0x100 + (o &~ 3) + (3 - (o & 3))); +} + +static void +ehci_bs_w_1(void *t, bus_space_handle_t h, bus_size_t o, u_int8_t v) +{ + panic("%s", __func__); +} + +static uint16_t +ehci_bs_r_2(void *t, bus_space_handle_t h, bus_size_t o) +{ + return bus_space_read_2((bus_space_tag_t) t, h, + 0x100 + (o &~ 3) + (2 - (o & 3))); +} + +static void +ehci_bs_w_2(void *t, bus_space_handle_t h, bus_size_t o, uint16_t v) +{ + panic("%s", __func__); +} + +static uint32_t +ehci_bs_r_4(void *t, bus_space_handle_t h, bus_size_t o) +{ + return bus_space_read_4((bus_space_tag_t) t, h, 0x100 + o); +} + +static void +ehci_bs_w_4(void *t, bus_space_handle_t h, bus_size_t o, uint32_t v) +{ + bus_space_write_4((bus_space_tag_t) t, h, 0x100 + o, v); +} + +static device_method_t ehci_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ehci_ixp_probe), + DEVMETHOD(device_attach, ehci_ixp_attach), + DEVMETHOD(device_detach, ehci_ixp_detach), + DEVMETHOD(device_suspend, ehci_ixp_suspend), + DEVMETHOD(device_resume, ehci_ixp_resume), + DEVMETHOD(device_shutdown, ehci_ixp_shutdown), + + /* Bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + + {0, 0} +}; + +static driver_t ehci_driver = { + "ehci", + ehci_methods, + sizeof(struct ixp_ehci_softc), +}; +static devclass_t ehci_devclass; +DRIVER_MODULE(ehci, ixp, ehci_driver, ehci_devclass, 0, 0); diff --git a/sys/legacy/dev/usb/ehci_mbus.c b/sys/legacy/dev/usb/ehci_mbus.c new file mode 100644 index 0000000..792c89d --- /dev/null +++ b/sys/legacy/dev/usb/ehci_mbus.c @@ -0,0 +1,396 @@ +/*- + * Copyright (C) 2008 MARVELL INTERNATIONAL LTD. + * All rights reserved. + * + * Developed by Semihalf. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of MARVELL nor the names of contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY 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 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. + */ + +/* + * MBus attachment driver for the USB Enhanced Host Controller. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include "opt_bus.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/bus.h> +#include <sys/queue.h> +#include <sys/lockmgr.h> +#include <sys/rman.h> +#include <sys/endian.h> + +#include <machine/bus.h> +#include <machine/resource.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdivar.h> +#include <dev/usb/usb_mem.h> + +#include <dev/usb/ehcireg.h> +#include <dev/usb/ehcivar.h> + +#include <arm/mv/mvreg.h> +#include <arm/mv/mvvar.h> + +#define EHCI_VENDORID_MRVL 0x1286 +#define EHCI_HC_DEVSTR "Marvell Integrated USB 2.0 controller" + +static device_attach_t ehci_mbus_attach; +static device_detach_t ehci_mbus_detach; +static device_shutdown_t ehci_mbus_shutdown; +static device_suspend_t ehci_mbus_suspend; +static device_resume_t ehci_mbus_resume; + +static int err_intr(void *arg); + +struct resource *irq_err; +void *ih_err; + +#define USB_BRIDGE_INTR_CAUSE 0x210 +#define USB_BRIDGE_INTR_MASK 0x214 + +#define MV_USB_ADDR_DECODE_ERR (1 << 0) +#define MV_USB_HOST_UNDERFLOW (1 << 1) +#define MV_USB_HOST_OVERFLOW (1 << 2) +#define MV_USB_DEVICE_UNDERFLOW (1 << 3) + +static int +ehci_mbus_suspend(device_t self) +{ + ehci_softc_t *sc; + int err; + + err = bus_generic_suspend(self); + if (err) + return (err); + + sc = device_get_softc(self); + ehci_power(PWR_SUSPEND, sc); + + return (0); +} + +static int +ehci_mbus_resume(device_t self) +{ + ehci_softc_t *sc; + + sc = device_get_softc(self); + + ehci_power(PWR_RESUME, sc); + bus_generic_resume(self); + + return (0); +} + +static int +ehci_mbus_shutdown(device_t self) +{ + ehci_softc_t *sc; + int err; + + err = bus_generic_shutdown(self); + if (err) + return (err); + + sc = device_get_softc(self); + ehci_shutdown(sc); + + return (0); +} + +static int +ehci_mbus_probe(device_t self) +{ + + device_set_desc(self, EHCI_HC_DEVSTR); + + return (BUS_PROBE_DEFAULT); +} + +static int +ehci_mbus_attach(device_t self) +{ + ehci_softc_t *sc; + bus_space_handle_t bsh; + int err, rid; + + sc = device_get_softc(self); + sc->sc_bus.usbrev = USBREV_2_0; + + rid = 0; + sc->io_res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid, RF_ACTIVE); + if (!sc->io_res) { + device_printf(self, "Could not map memory\n"); + return (ENXIO); + } + sc->iot = rman_get_bustag(sc->io_res); + bsh = rman_get_bushandle(sc->io_res); + + /* + * Marvell EHCI host controller registers start at certain offset within + * the whole USB registers range, so create a subregion for the host + * mode configuration purposes. + */ + if (bus_space_subregion(sc->iot, bsh, MV_USB_HOST_OFST, + MV_USB_SIZE - MV_USB_HOST_OFST, &sc->ioh) != 0) + panic("%s: unable to subregion USB host registers", + device_get_name(self)); + sc->sc_size = MV_USB_SIZE - MV_USB_HOST_OFST; + + rid = 0; + irq_err = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid, + RF_SHAREABLE | RF_ACTIVE); + if (irq_err == NULL) { + device_printf(self, "Could not allocate error irq\n"); + ehci_mbus_detach(self); + return (ENXIO); + } + + /* + * Notice: Marvell EHCI controller has TWO interrupt lines, so make sure to + * use the correct rid for the main one (controller interrupt) -- + * refer to obio_devices[] for the right resource number to use here. + */ + rid = 1; + sc->irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid, + RF_SHAREABLE | RF_ACTIVE); + if (sc->irq_res == NULL) { + device_printf(self, "Could not allocate irq\n"); + ehci_mbus_detach(self); + return (ENXIO); + } + sc->sc_bus.bdev = device_add_child(self, "usb", -1); + if (!sc->sc_bus.bdev) { + device_printf(self, "Could not add USB device\n"); + ehci_mbus_detach(self); + return (ENOMEM); + } + device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); + + sprintf(sc->sc_vendor, "Marvell"); + sc->sc_id_vendor = EHCI_VENDORID_MRVL; + + err = bus_setup_intr(self, irq_err, INTR_FAST | INTR_TYPE_BIO, + err_intr, NULL, sc, &ih_err); + if (err) { + device_printf(self, "Could not setup error irq, %d\n", err); + ih_err = NULL; + ehci_mbus_detach(self); + return (ENXIO); + } + + EWRITE4(sc, USB_BRIDGE_INTR_MASK, MV_USB_ADDR_DECODE_ERR | + MV_USB_HOST_UNDERFLOW | MV_USB_HOST_OVERFLOW | + MV_USB_DEVICE_UNDERFLOW); + + err = bus_setup_intr(self, sc->irq_res, INTR_TYPE_BIO, + NULL, (driver_intr_t*)ehci_intr, sc, &sc->ih); + if (err) { + device_printf(self, "Could not setup irq, %d\n", err); + sc->ih = NULL; + ehci_mbus_detach(self); + return (ENXIO); + } + + /* There are no companion USB controllers */ + sc->sc_ncomp = 0; + + /* Allocate a parent dma tag for DMA maps */ + err = bus_dma_tag_create(bus_get_dma_tag(self), 1, 0, + BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, + BUS_SPACE_MAXSIZE_32BIT, USB_DMA_NSEG, BUS_SPACE_MAXSIZE_32BIT, 0, + NULL, NULL, &sc->sc_bus.parent_dmatag); + if (err) { + device_printf(self, "Could not allocate parent DMA tag (%d)\n", + err); + ehci_mbus_detach(self); + return (ENXIO); + } + + /* Allocate a dma tag for transfer buffers */ + err = bus_dma_tag_create(sc->sc_bus.parent_dmatag, 1, 0, + BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, + BUS_SPACE_MAXSIZE_32BIT, USB_DMA_NSEG, BUS_SPACE_MAXSIZE_32BIT, 0, + busdma_lock_mutex, &Giant, &sc->sc_bus.buffer_dmatag); + if (err) { + device_printf(self, "Could not allocate buffer DMA tag (%d)\n", + err); + ehci_mbus_detach(self); + return (ENXIO); + } + + /* + * Workaround for Marvell integrated EHCI controller: reset of + * the EHCI core clears the USBMODE register, which sets the core in + * an undefined state (neither host nor agent), so it needs to be set + * again for proper operation. + * + * Refer to errata document MV-S500832-00D.pdf (p. 5.24 GL USB-2) for + * details. + */ + sc->sc_flags |= EHCI_SCFLG_SETMODE; + if (bootverbose) + device_printf(self, "5.24 GL USB-2 workaround enabled\n"); + + /* XXX all MV chips need it? */ + sc->sc_flags |= EHCI_SCFLG_FORCESPEED | EHCI_SCFLG_NORESTERM; + + err = ehci_init(sc); + if (!err) { + sc->sc_flags |= EHCI_SCFLG_DONEINIT; + err = device_probe_and_attach(sc->sc_bus.bdev); + } + + if (err) { + device_printf(self, "USB init failed err=%d\n", err); + ehci_mbus_detach(self); + return (EIO); + } + return (0); +} + +static int +ehci_mbus_detach(device_t self) +{ + ehci_softc_t *sc; + int err; + + sc = device_get_softc(self); + if (sc->sc_flags & EHCI_SCFLG_DONEINIT) { + ehci_detach(sc, 0); + sc->sc_flags &= ~EHCI_SCFLG_DONEINIT; + } + + /* + * Disable interrupts that might have been switched on in ehci_init() + */ + if (sc->iot && sc->ioh) + bus_space_write_4(sc->iot, sc->ioh, EHCI_USBINTR, 0); + if (sc->sc_bus.parent_dmatag != NULL) + bus_dma_tag_destroy(sc->sc_bus.parent_dmatag); + if (sc->sc_bus.buffer_dmatag != NULL) + bus_dma_tag_destroy(sc->sc_bus.buffer_dmatag); + + if (sc->irq_res && sc->ih) { + err = bus_teardown_intr(self, sc->irq_res, sc->ih); + + if (err) + device_printf(self, "Could not tear down irq, %d\n", + err); + sc->ih = NULL; + } + EWRITE4(sc, USB_BRIDGE_INTR_MASK, 0); + + if (irq_err && ih_err) { + err = bus_teardown_intr(self, irq_err, ih_err); + + if (err) + device_printf(self, "Could not tear down irq, %d\n", + err); + ih_err = NULL; + } + + if (sc->sc_bus.bdev) { + device_delete_child(self, sc->sc_bus.bdev); + sc->sc_bus.bdev = NULL; + } + if (sc->irq_res) { + bus_release_resource(self, SYS_RES_IRQ, 0, sc->irq_res); + sc->irq_res = NULL; + } + if (irq_err) { + bus_release_resource(self, SYS_RES_IRQ, 1, irq_err); + irq_err = NULL; + } + if (sc->io_res) { + bus_release_resource(self, SYS_RES_MEMORY, 0, sc->io_res); + sc->io_res = NULL; + sc->iot = 0; + sc->ioh = 0; + } + return (0); +} + +static int +err_intr(void *arg) +{ + ehci_softc_t *sc = arg; + unsigned int cause; + + cause = EREAD4(sc, USB_BRIDGE_INTR_CAUSE); + if (cause) { + printf("IRQ ERR: cause: 0x%08x\n", cause); + if (cause & MV_USB_ADDR_DECODE_ERR) + printf("IRQ ERR: Address decoding error\n"); + if (cause & MV_USB_HOST_UNDERFLOW) + printf("IRQ ERR: USB Host Underflow\n"); + if (cause & MV_USB_HOST_OVERFLOW) + printf("IRQ ERR: USB Host Overflow\n"); + if (cause & MV_USB_DEVICE_UNDERFLOW) + printf("IRQ ERR: USB Device Underflow\n"); + if (cause & ~(MV_USB_ADDR_DECODE_ERR | MV_USB_HOST_UNDERFLOW | + MV_USB_HOST_OVERFLOW | MV_USB_DEVICE_UNDERFLOW)) + printf("IRQ ERR: Unknown error\n"); + + EWRITE4(sc, USB_BRIDGE_INTR_CAUSE, 0); + } + return (FILTER_HANDLED); +} + +static device_method_t ehci_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ehci_mbus_probe), + DEVMETHOD(device_attach, ehci_mbus_attach), + DEVMETHOD(device_detach, ehci_mbus_detach), + DEVMETHOD(device_suspend, ehci_mbus_suspend), + DEVMETHOD(device_resume, ehci_mbus_resume), + DEVMETHOD(device_shutdown, ehci_mbus_shutdown), + + /* Bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + + {0, 0} +}; + +static driver_t ehci_driver = { + "ehci", + ehci_methods, + sizeof(ehci_softc_t), +}; + +static devclass_t ehci_devclass; + +DRIVER_MODULE(ehci, mbus, ehci_driver, ehci_devclass, 0, 0); diff --git a/sys/legacy/dev/usb/ehci_pci.c b/sys/legacy/dev/usb/ehci_pci.c new file mode 100644 index 0000000..819bc25 --- /dev/null +++ b/sys/legacy/dev/usb/ehci_pci.c @@ -0,0 +1,636 @@ +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (augustss@carlstedt.se) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * USB Enhanced Host Controller Driver, a.k.a. USB 2.0 controller. + * + * The EHCI 1.0 spec can be found at + * http://developer.intel.com/technology/usb/download/ehci-r10.pdf + * and the USB 2.0 spec at + * http://www.usb.org/developers/docs/usb_20.zip + */ + +/* The low level controller code for EHCI has been split into + * PCI probes and EHCI specific code. This was done to facilitate the + * sharing of code between *BSD's + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/bus.h> +#include <sys/queue.h> +#include <sys/lockmgr.h> +#include <sys/rman.h> +#include <sys/endian.h> + +#include <machine/bus.h> +#include <machine/resource.h> + +#include <dev/pci/pcivar.h> +#include <dev/pci/pcireg.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdivar.h> +#include <dev/usb/usb_mem.h> + +#include <dev/usb/ehcireg.h> +#include <dev/usb/ehcivar.h> + +#define PCI_EHCI_VENDORID_ACERLABS 0x10b9 +#define PCI_EHCI_VENDORID_AMD 0x1022 +#define PCI_EHCI_VENDORID_APPLE 0x106b +#define PCI_EHCI_VENDORID_ATI 0x1002 +#define PCI_EHCI_VENDORID_CMDTECH 0x1095 +#define PCI_EHCI_VENDORID_INTEL 0x8086 +#define PCI_EHCI_VENDORID_NEC 0x1033 +#define PCI_EHCI_VENDORID_OPTI 0x1045 +#define PCI_EHCI_VENDORID_PHILIPS 0x1131 +#define PCI_EHCI_VENDORID_SIS 0x1039 +#define PCI_EHCI_VENDORID_NVIDIA 0x12D2 +#define PCI_EHCI_VENDORID_NVIDIA2 0x10DE +#define PCI_EHCI_VENDORID_VIA 0x1106 + +/* AcerLabs/ALi */ +#define PCI_EHCI_DEVICEID_M5239 0x523910b9 +static const char *ehci_device_m5239 = "ALi M5239 USB 2.0 controller"; + +/* AMD */ +#define PCI_EHCI_DEVICEID_8111 0x10227463 +static const char *ehci_device_8111 = "AMD 8111 USB 2.0 controller"; +#define PCI_EHCI_DEVICEID_CS5536 0x20951022 +static const char *ehci_device_cs5536 = "AMD CS5536 (Geode) USB 2.0 controller"; + +/* ATI */ +#define PCI_EHCI_DEVICEID_SB200 0x43451002 +static const char *ehci_device_sb200 = "ATI SB200 USB 2.0 controller"; +#define PCI_EHCI_DEVICEID_SB400 0x43731002 +static const char *ehci_device_sb400 = "ATI SB400 USB 2.0 controller"; + +/* Intel */ +#define PCI_EHCI_DEVICEID_6300 0x25ad8086 +static const char *ehci_device_6300 = "Intel 6300ESB USB 2.0 controller"; +#define PCI_EHCI_DEVICEID_ICH4 0x24cd8086 +static const char *ehci_device_ich4 = "Intel 82801DB/L/M (ICH4) USB 2.0 controller"; +#define PCI_EHCI_DEVICEID_ICH5 0x24dd8086 +static const char *ehci_device_ich5 = "Intel 82801EB/R (ICH5) USB 2.0 controller"; +#define PCI_EHCI_DEVICEID_ICH6 0x265c8086 +static const char *ehci_device_ich6 = "Intel 82801FB (ICH6) USB 2.0 controller"; +#define PCI_EHCI_DEVICEID_ICH7 0x27cc8086 +static const char *ehci_device_ich7 = "Intel 82801GB/R (ICH7) USB 2.0 controller"; +#define PCI_EHCI_DEVICEID_ICH8_A 0x28368086 +static const char *ehci_device_ich8_a = "Intel 82801H (ICH8) USB 2.0 controller USB2-A"; +#define PCI_EHCI_DEVICEID_ICH8_B 0x283a8086 +static const char *ehci_device_ich8_b = "Intel 82801H (ICH8) USB 2.0 controller USB2-B"; +#define PCI_EHCI_DEVICEID_ICH9_A 0x293a8086 +#define PCI_EHCI_DEVICEID_ICH9_B 0x293c8086 +static const char *ehci_device_ich9 = "Intel 82801I (ICH9) USB 2.0 controller"; +#define PCI_EHCI_DEVICEID_63XX 0x268c8086 +static const char *ehci_device_63XX = "Intel 63XXESB USB 2.0 controller"; + +/* NEC */ +#define PCI_EHCI_DEVICEID_NEC 0x00e01033 +static const char *ehci_device_nec = "NEC uPD 720100 USB 2.0 controller"; + +/* NVIDIA */ +#define PCI_EHCI_DEVICEID_NF2 0x006810de +static const char *ehci_device_nf2 = "NVIDIA nForce2 USB 2.0 controller"; +#define PCI_EHCI_DEVICEID_NF2_400 0x008810de +static const char *ehci_device_nf2_400 = "NVIDIA nForce2 Ultra 400 USB 2.0 controller"; +#define PCI_EHCI_DEVICEID_NF3 0x00d810de +static const char *ehci_device_nf3 = "NVIDIA nForce3 USB 2.0 controller"; +#define PCI_EHCI_DEVICEID_NF3_250 0x00e810de +static const char *ehci_device_nf3_250 = "NVIDIA nForce3 250 USB 2.0 controller"; +#define PCI_EHCI_DEVICEID_NF4 0x005b10de +static const char *ehci_device_nf4 = "NVIDIA nForce4 USB 2.0 controller"; + +/* Philips */ +#define PCI_EHCI_DEVICEID_ISP156X 0x15621131 +static const char *ehci_device_isp156x = "Philips ISP156x USB 2.0 controller"; + +#define PCI_EHCI_DEVICEID_VIA 0x31041106 +static const char *ehci_device_via = "VIA VT6202 USB 2.0 controller"; + +static const char *ehci_device_generic = "EHCI (generic) USB 2.0 controller"; + +#define PCI_EHCI_BASE_REG 0x10 + +#ifdef USB_DEBUG +#define EHCI_DEBUG USB_DEBUG +#define DPRINTF(x) do { if (ehcidebug) printf x; } while (0) +extern int ehcidebug; +#else +#define DPRINTF(x) +#endif + +static device_attach_t ehci_pci_attach; +static device_detach_t ehci_pci_detach; +static device_shutdown_t ehci_pci_shutdown; +static device_suspend_t ehci_pci_suspend; +static device_resume_t ehci_pci_resume; +static void ehci_pci_givecontroller(device_t self); +static void ehci_pci_takecontroller(device_t self); + +static int +ehci_pci_suspend(device_t self) +{ + ehci_softc_t *sc = device_get_softc(self); + int err; + + err = bus_generic_suspend(self); + if (err) + return (err); + ehci_power(PWR_SUSPEND, sc); + + return 0; +} + +static int +ehci_pci_resume(device_t self) +{ + ehci_softc_t *sc = device_get_softc(self); + + ehci_pci_takecontroller(self); + ehci_power(PWR_RESUME, sc); + bus_generic_resume(self); + + return 0; +} + +static int +ehci_pci_shutdown(device_t self) +{ + ehci_softc_t *sc = device_get_softc(self); + int err; + + err = bus_generic_shutdown(self); + if (err) + return (err); + ehci_shutdown(sc); + ehci_pci_givecontroller(self); + + return 0; +} + +static const char * +ehci_pci_match(device_t self) +{ + u_int32_t device_id = pci_get_devid(self); + + switch (device_id) { + case PCI_EHCI_DEVICEID_M5239: + return (ehci_device_m5239); + case PCI_EHCI_DEVICEID_8111: + return (ehci_device_8111); + case PCI_EHCI_DEVICEID_CS5536: + return (ehci_device_cs5536); + case PCI_EHCI_DEVICEID_SB200: + return (ehci_device_sb200); + case PCI_EHCI_DEVICEID_SB400: + return (ehci_device_sb400); + case PCI_EHCI_DEVICEID_6300: + return (ehci_device_6300); + case PCI_EHCI_DEVICEID_63XX: + return (ehci_device_63XX); + case PCI_EHCI_DEVICEID_ICH4: + return (ehci_device_ich4); + case PCI_EHCI_DEVICEID_ICH5: + return (ehci_device_ich5); + case PCI_EHCI_DEVICEID_ICH6: + return (ehci_device_ich6); + case PCI_EHCI_DEVICEID_ICH7: + return (ehci_device_ich7); + case PCI_EHCI_DEVICEID_ICH8_A: + return (ehci_device_ich8_a); + case PCI_EHCI_DEVICEID_ICH8_B: + return (ehci_device_ich8_b); + case PCI_EHCI_DEVICEID_ICH9_A: + case PCI_EHCI_DEVICEID_ICH9_B: + return (ehci_device_ich9); + case PCI_EHCI_DEVICEID_NEC: + return (ehci_device_nec); + case PCI_EHCI_DEVICEID_NF2: + return (ehci_device_nf2); + case PCI_EHCI_DEVICEID_NF2_400: + return (ehci_device_nf2_400); + case PCI_EHCI_DEVICEID_NF3: + return (ehci_device_nf3); + case PCI_EHCI_DEVICEID_NF3_250: + return (ehci_device_nf3_250); + case PCI_EHCI_DEVICEID_NF4: + return (ehci_device_nf4); + case PCI_EHCI_DEVICEID_ISP156X: + return (ehci_device_isp156x); + case PCI_EHCI_DEVICEID_VIA: + return (ehci_device_via); + default: + if (pci_get_class(self) == PCIC_SERIALBUS + && pci_get_subclass(self) == PCIS_SERIALBUS_USB + && pci_get_progif(self) == PCI_INTERFACE_EHCI) { + return (ehci_device_generic); + } + } + + return NULL; /* dunno */ +} + +static int +ehci_pci_probe(device_t self) +{ + const char *desc = ehci_pci_match(self); + + if (desc) { + device_set_desc(self, desc); + return BUS_PROBE_DEFAULT; + } else { + return ENXIO; + } +} + +static int +ehci_pci_attach(device_t self) +{ + ehci_softc_t *sc = device_get_softc(self); + devclass_t dc; + device_t parent; + device_t *neighbors; + device_t *nbus; + struct usbd_bus *bsc; + int err; + int rid; + int ncomp; + int count, buscount; + int slot, function; + int res; + int i; + + switch(pci_read_config(self, PCI_USBREV, 1) & PCI_USBREV_MASK) { + case PCI_USBREV_PRE_1_0: + case PCI_USBREV_1_0: + case PCI_USBREV_1_1: + device_printf(self, "pre-2.0 USB rev\n"); + if (pci_get_devid(self) == PCI_EHCI_DEVICEID_CS5536) { + sc->sc_bus.usbrev = USBREV_2_0; + device_printf(self, "Quirk for CS5536 USB 2.0 enabled\n"); + break; + } + + /* + * Quirk for Parallels Desktop 4.0. + */ + if (pci_get_devid(self) == PCI_EHCI_DEVICEID_ICH6) { + sc->sc_bus.usbrev = USBREV_2_0; + break; + } + sc->sc_bus.usbrev = USBREV_UNKNOWN; + return ENXIO; + case PCI_USBREV_2_0: + sc->sc_bus.usbrev = USBREV_2_0; + break; + default: + sc->sc_bus.usbrev = USBREV_UNKNOWN; + break; + } + + pci_enable_busmaster(self); + + rid = PCI_CBMEM; + sc->io_res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid, + RF_ACTIVE); + if (!sc->io_res) { + device_printf(self, "Could not map memory\n"); + return ENXIO; + } + sc->iot = rman_get_bustag(sc->io_res); + sc->ioh = rman_get_bushandle(sc->io_res); + + rid = 0; + sc->irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid, + RF_SHAREABLE | RF_ACTIVE); + if (sc->irq_res == NULL) { + device_printf(self, "Could not allocate irq\n"); + ehci_pci_detach(self); + return ENXIO; + } + sc->sc_bus.bdev = device_add_child(self, "usb", -1); + if (!sc->sc_bus.bdev) { + device_printf(self, "Could not add USB device\n"); + ehci_pci_detach(self); + return ENOMEM; + } + device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); + + /* ehci_pci_match will never return NULL if ehci_pci_probe succeeded */ + device_set_desc(sc->sc_bus.bdev, ehci_pci_match(self)); + switch (pci_get_vendor(self)) { + case PCI_EHCI_VENDORID_ACERLABS: + sprintf(sc->sc_vendor, "AcerLabs"); + break; + case PCI_EHCI_VENDORID_AMD: + sprintf(sc->sc_vendor, "AMD"); + break; + case PCI_EHCI_VENDORID_APPLE: + sprintf(sc->sc_vendor, "Apple"); + break; + case PCI_EHCI_VENDORID_ATI: + sprintf(sc->sc_vendor, "ATI"); + break; + case PCI_EHCI_VENDORID_CMDTECH: + sprintf(sc->sc_vendor, "CMDTECH"); + break; + case PCI_EHCI_VENDORID_INTEL: + sprintf(sc->sc_vendor, "Intel"); + break; + case PCI_EHCI_VENDORID_NEC: + sprintf(sc->sc_vendor, "NEC"); + break; + case PCI_EHCI_VENDORID_OPTI: + sprintf(sc->sc_vendor, "OPTi"); + break; + case PCI_EHCI_VENDORID_SIS: + sprintf(sc->sc_vendor, "SiS"); + break; + case PCI_EHCI_VENDORID_NVIDIA: + case PCI_EHCI_VENDORID_NVIDIA2: + sprintf(sc->sc_vendor, "nVidia"); + break; + case PCI_EHCI_VENDORID_VIA: + sprintf(sc->sc_vendor, "VIA"); + break; + default: + if (bootverbose) + device_printf(self, "(New EHCI DeviceId=0x%08x)\n", + pci_get_devid(self)); + sprintf(sc->sc_vendor, "(0x%04x)", pci_get_vendor(self)); + } + + err = bus_setup_intr(self, sc->irq_res, INTR_TYPE_BIO, + NULL, (driver_intr_t *)ehci_intr, sc, &sc->ih); + if (err) { + device_printf(self, "Could not setup irq, %d\n", err); + sc->ih = NULL; + ehci_pci_detach(self); + return ENXIO; + } + + /* Enable workaround for dropped interrupts as required */ + switch (pci_get_vendor(self)) { + case PCI_EHCI_VENDORID_ATI: + case PCI_EHCI_VENDORID_VIA: + sc->sc_flags |= EHCI_SCFLG_LOSTINTRBUG; + if (bootverbose) + device_printf(self, + "Dropped interrupts workaround enabled\n"); + break; + default: + break; + } + + /* + * Find companion controllers. According to the spec they always + * have lower function numbers so they should be enumerated already. + */ + parent = device_get_parent(self); + res = device_get_children(parent, &neighbors, &count); + if (res != 0) { + device_printf(self, "Error finding companion busses\n"); + ehci_pci_detach(self); + return ENXIO; + } + ncomp = 0; + dc = devclass_find("usb"); + slot = pci_get_slot(self); + function = pci_get_function(self); + for (i = 0; i < count; i++) { + if (pci_get_slot(neighbors[i]) == slot && \ + pci_get_function(neighbors[i]) < function) { + res = device_get_children(neighbors[i], + &nbus, &buscount); + if (res != 0) + continue; + if (buscount != 1) { + free(nbus, M_TEMP); + continue; + } + if (device_get_devclass(nbus[0]) != dc) { + free(nbus, M_TEMP); + continue; + } + bsc = device_get_softc(nbus[0]); + free(nbus, M_TEMP); + DPRINTF(("ehci_pci_attach: companion %s\n", + device_get_nameunit(bsc->bdev))); + sc->sc_comps[ncomp++] = bsc; + if (ncomp >= EHCI_COMPANION_MAX) + break; + } + } + sc->sc_ncomp = ncomp; + + /* Allocate a parent dma tag for DMA maps */ + err = bus_dma_tag_create(bus_get_dma_tag(self), 1, 0, + BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, + BUS_SPACE_MAXSIZE_32BIT, USB_DMA_NSEG, BUS_SPACE_MAXSIZE_32BIT, 0, + NULL, NULL, &sc->sc_bus.parent_dmatag); + if (err) { + device_printf(self, "Could not allocate parent DMA tag (%d)\n", + err); + ehci_pci_detach(self); + return ENXIO; + } + + /* Allocate a dma tag for transfer buffers */ + err = bus_dma_tag_create(sc->sc_bus.parent_dmatag, 1, 0, + BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, + BUS_SPACE_MAXSIZE_32BIT, USB_DMA_NSEG, BUS_SPACE_MAXSIZE_32BIT, 0, + busdma_lock_mutex, &Giant, &sc->sc_bus.buffer_dmatag); + if (err) { + device_printf(self, "Could not allocate buffer DMA tag (%d)\n", + err); + ehci_pci_detach(self); + return ENXIO; + } + + ehci_pci_takecontroller(self); + err = ehci_init(sc); + if (!err) { + sc->sc_flags |= EHCI_SCFLG_DONEINIT; + err = device_probe_and_attach(sc->sc_bus.bdev); + } + + if (err) { + device_printf(self, "USB init failed err=%d\n", err); + ehci_pci_detach(self); + return EIO; + } + return 0; +} + +static int +ehci_pci_detach(device_t self) +{ + ehci_softc_t *sc = device_get_softc(self); + + if (sc->sc_flags & EHCI_SCFLG_DONEINIT) { + ehci_detach(sc, 0); + sc->sc_flags &= ~EHCI_SCFLG_DONEINIT; + } + + /* + * disable interrupts that might have been switched on in ehci_init + */ + if (sc->iot && sc->ioh) + bus_space_write_4(sc->iot, sc->ioh, EHCI_USBINTR, 0); + if (sc->sc_bus.parent_dmatag != NULL) + bus_dma_tag_destroy(sc->sc_bus.parent_dmatag); + if (sc->sc_bus.buffer_dmatag != NULL) + bus_dma_tag_destroy(sc->sc_bus.buffer_dmatag); + + if (sc->irq_res && sc->ih) { + int err = bus_teardown_intr(self, sc->irq_res, sc->ih); + + if (err) + /* XXX or should we panic? */ + device_printf(self, "Could not tear down irq, %d\n", + err); + sc->ih = NULL; + } + if (sc->sc_bus.bdev) { + device_delete_child(self, sc->sc_bus.bdev); + sc->sc_bus.bdev = NULL; + } + if (sc->irq_res) { + bus_release_resource(self, SYS_RES_IRQ, 0, sc->irq_res); + sc->irq_res = NULL; + } + if (sc->io_res) { + bus_release_resource(self, SYS_RES_MEMORY, PCI_CBMEM, sc->io_res); + sc->io_res = NULL; + sc->iot = 0; + sc->ioh = 0; + } + return 0; +} + +static void +ehci_pci_takecontroller(device_t self) +{ + ehci_softc_t *sc = device_get_softc(self); + u_int32_t cparams, eec; + uint8_t bios_sem; + int eecp, i; + + cparams = EREAD4(sc, EHCI_HCCPARAMS); + + /* Synchronise with the BIOS if it owns the controller. */ + for (eecp = EHCI_HCC_EECP(cparams); eecp != 0; + eecp = EHCI_EECP_NEXT(eec)) { + eec = pci_read_config(self, eecp, 4); + if (EHCI_EECP_ID(eec) != EHCI_EC_LEGSUP) + continue; + bios_sem = pci_read_config(self, eecp + EHCI_LEGSUP_BIOS_SEM, + 1); + if (bios_sem) { + pci_write_config(self, eecp + EHCI_LEGSUP_OS_SEM, 1, 1); + printf("%s: waiting for BIOS to give up control\n", + device_get_nameunit(sc->sc_bus.bdev)); + for (i = 0; i < 5000; i++) { + bios_sem = pci_read_config(self, eecp + + EHCI_LEGSUP_BIOS_SEM, 1); + if (bios_sem == 0) + break; + DELAY(1000); + } + if (bios_sem) + printf("%s: timed out waiting for BIOS\n", + device_get_nameunit(sc->sc_bus.bdev)); + } + } +} + +static void +ehci_pci_givecontroller(device_t self) +{ +#if 0 + ehci_softc_t *sc = device_get_softc(self); + u_int32_t cparams, eec; + int eecp; + + cparams = EREAD4(sc, EHCI_HCCPARAMS); + for (eecp = EHCI_HCC_EECP(cparams); eecp != 0; + eecp = EHCI_EECP_NEXT(eec)) { + eec = pci_read_config(self, eecp, 4); + if (EHCI_EECP_ID(eec) != EHCI_EC_LEGSUP) + continue; + pci_write_config(self, eecp + EHCI_LEGSUP_OS_SEM, 0, 1); + } +#endif +} + +static device_method_t ehci_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ehci_pci_probe), + DEVMETHOD(device_attach, ehci_pci_attach), + DEVMETHOD(device_detach, ehci_pci_detach), + DEVMETHOD(device_suspend, ehci_pci_suspend), + DEVMETHOD(device_resume, ehci_pci_resume), + DEVMETHOD(device_shutdown, ehci_pci_shutdown), + + /* Bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + + {0, 0} +}; + +static driver_t ehci_driver = { + "ehci", + ehci_methods, + sizeof(ehci_softc_t), +}; + +static devclass_t ehci_devclass; + +DRIVER_MODULE(ehci, pci, ehci_driver, ehci_devclass, 0, 0); +DRIVER_MODULE(ehci, cardbus, ehci_driver, ehci_devclass, 0, 0); +MODULE_DEPEND(ehci, usb, 1, 1, 1); diff --git a/sys/legacy/dev/usb/ehcireg.h b/sys/legacy/dev/usb/ehcireg.h new file mode 100644 index 0000000..3c0f5e7 --- /dev/null +++ b/sys/legacy/dev/usb/ehcireg.h @@ -0,0 +1,344 @@ +/* $NetBSD: ehcireg.h,v 1.18 2004/10/22 10:38:17 augustss Exp $ */ +/* $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. + */ + +/* + * The EHCI 0.96 spec can be found at + * http://developer.intel.com/technology/usb/download/ehci-r096.pdf + * and the USB 2.0 spec at + * http://www.usb.org/developers/data/usb_20.zip + */ + +#ifndef _DEV_PCI_EHCIREG_H_ +#define _DEV_PCI_EHCIREG_H_ + +/*** 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_USBREV_MASK 0xff +#define PCI_USBREV_PRE_1_0 0x00 +#define PCI_USBREV_1_0 0x10 +#define PCI_USBREV_1_1 0x11 +#define PCI_USBREV_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_OS_SEM 0x03 /* OS owned semaphore */ +#define EHCI_LEGSUP_BIOS_SEM 0x02 /* BIOS owned semaphore */ +#define EHCI_LEGSUP_USBLEGCTLSTS 0x04 + +/*** EHCI capability registers ***/ + +#define EHCI_CAPLENGTH 0x00 /*RO Capability register length field */ +/* reserved 0x01 */ +#define EHCI_HCIVERSION 0x02 /* RO Interface version number */ + +#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) + +#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 */ + +#define EHCI_FLALIGN_ALIGN 0x1000 + +/* No data structure may cross a page boundary. */ +#define EHCI_PAGE_SIZE 0x1000 +#define EHCI_PAGE(x) ((x) &~ 0xfff) +#define EHCI_PAGE_OFFSET(x) ((x) & 0xfff) +#if defined(__FreeBSD__) +#define EHCI_PAGE_MASK(x) ((x) & 0xfff) +#endif + +typedef u_int32_t ehci_link_t; +#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) + +typedef u_int32_t ehci_physaddr_t; + +typedef u_int32_t ehci_isoc_trans_t; +typedef u_int32_t ehci_isoc_bufr_ptr_t; + +/* Isochronous Transfer Descriptor */ +typedef struct { + ehci_link_t itd_next; + ehci_isoc_trans_t itd_ctl[8]; +#define EHCI_ITD_GET_STATUS(x) (((x) >> 28) & 0xf) +#define EHCI_ITD_SET_STATUS(x) (((x) & 0xf) << 28) +#define EHCI_ITD_ACTIVE 0x80000000 +#define EHCI_ITD_BUF_ERR 0x40000000 +#define EHCI_ITD_BABBLE 0x20000000 +#define EHCI_ITD_ERROR 0x10000000 +#define EHCI_ITD_GET_LEN(x) (((x) >> 16) & 0xfff) +#define EHCI_ITD_SET_LEN(x) (((x) & 0xfff) << 16) +#define EHCI_ITD_IOC 0x8000 +#define EHCI_ITD_GET_IOC(x) (((x) >> 15) & 1) +#define EHCI_ITD_SET_IOC(x) (((x) << 15) & EHCI_ITD_IOC) +#define EHCI_ITD_GET_PG(x) (((x) >> 12) & 0xf) +#define EHCI_ITD_SET_PG(x) (((x) & 0xf) << 12) +#define EHCI_ITD_GET_OFFS(x) (((x) >> 0) & 0xfff) +#define EHCI_ITD_SET_OFFS(x) (((x) & 0xfff) << 0) + ehci_isoc_bufr_ptr_t itd_bufr[7]; +#define EHCI_ITD_GET_BPTR(x) ((x) & 0xfffff000) +#define EHCI_ITD_SET_BPTR(x) ((x) & 0xfffff000) +#define EHCI_ITD_GET_EP(x) (((x) >> 8) & 0xf) +#define EHCI_ITD_SET_EP(x) (((x) & 0xf) << 8) +#define EHCI_ITD_GET_DADDR(x) ((x) & 0x7f) +#define EHCI_ITD_SET_DADDR(x) ((x) & 0x7f) +#define EHCI_ITD_GET_DIR(x) (((x) >> 11) & 1) +#define EHCI_ITD_SET_DIR(x) (((x) & 1) << 11) +#define EHCI_ITD_GET_MAXPKT(x) ((x) & 0x7ff) +#define EHCI_ITD_SET_MAXPKT(x) ((x) & 0x7ff) +#define EHCI_ITD_GET_MULTI(x) ((x) & 0x3) +#define EHCI_ITD_SET_MULTI(x) ((x) & 0x3) +} ehci_itd_t; +#define EHCI_ITD_ALIGN 32 + +/* Split Transaction Isochronous Transfer Descriptor */ +typedef struct { + ehci_link_t sitd_next; + /* XXX many more */ +} ehci_sitd_t; +#define EHCI_SITD_ALIGN 32 + +/* Queue Element Transfer Descriptor */ +#define EHCI_QTD_NBUFFERS 5 +typedef struct { + ehci_link_t qtd_next; + ehci_link_t qtd_altnext; + u_int32_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 0x7c +#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 + ehci_physaddr_t qtd_buffer[EHCI_QTD_NBUFFERS]; + ehci_physaddr_t qtd_buffer_hi[EHCI_QTD_NBUFFERS]; +} ehci_qtd_t; +#define EHCI_QTD_ALIGN 32 + +#define EHCI_QTD_STATUS_BITS \ + "\20\10ACTIVE\7HALTED\6BUFERR\5BABBLE\4XACTERR\3MISSED\2SPLIT\1PING" + +/* Queue Head */ +typedef struct { + ehci_link_t qh_link; + u_int32_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) + u_int32_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) + ehci_link_t qh_curqtd; + ehci_qtd_t qh_qtd; +} ehci_qh_t; +#define EHCI_QH_ALIGN 32 + +/* Periodic Frame Span Traversal Node */ +typedef struct { + ehci_link_t fstn_link; + ehci_link_t fstn_back; +} ehci_fstn_t; +#define EHCI_FSTN_ALIGN 32 + +#endif /* _DEV_PCI_EHCIREG_H_ */ diff --git a/sys/legacy/dev/usb/ehcivar.h b/sys/legacy/dev/usb/ehcivar.h new file mode 100644 index 0000000..fdd19ba --- /dev/null +++ b/sys/legacy/dev/usb/ehcivar.h @@ -0,0 +1,278 @@ +/* $NetBSD: ehcivar.h,v 1.19 2005/04/29 15:04:29 augustss Exp $ */ +/* $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. + */ + +typedef struct ehci_soft_qtd { + ehci_qtd_t qtd; + struct ehci_soft_qtd *nextqtd; /* mirrors nextqtd in TD */ + ehci_physaddr_t physaddr; + usbd_xfer_handle xfer; + LIST_ENTRY(ehci_soft_qtd) hnext; + u_int16_t len; +} ehci_soft_qtd_t; +#define EHCI_SQTD_SIZE ((sizeof (struct ehci_soft_qtd) + EHCI_QTD_ALIGN - 1) / EHCI_QTD_ALIGN * EHCI_QTD_ALIGN) +#define EHCI_SQTD_CHUNK (EHCI_PAGE_SIZE / EHCI_SQTD_SIZE) + +typedef struct ehci_soft_qh { + ehci_qh_t qh; + struct ehci_soft_qh *next; + struct ehci_soft_qh *prev; + struct ehci_soft_qtd *sqtd; + struct ehci_soft_qtd *inactivesqtd; + ehci_physaddr_t physaddr; + int islot; /* Interrupt list slot. */ +} ehci_soft_qh_t; +#define EHCI_SQH_SIZE ((sizeof (struct ehci_soft_qh) + EHCI_QH_ALIGN - 1) / EHCI_QH_ALIGN * EHCI_QH_ALIGN) +#define EHCI_SQH_CHUNK (EHCI_PAGE_SIZE / EHCI_SQH_SIZE) + +typedef struct ehci_soft_itd { + ehci_itd_t itd; + union { + struct { + /* soft_itds links in a periodic frame*/ + struct ehci_soft_itd *next; + struct ehci_soft_itd *prev; + } frame_list; + /* circular list of free itds */ + LIST_ENTRY(ehci_soft_itd) free_list; + } u; + struct ehci_soft_itd *xfer_next; /* Next soft_itd in xfer */ + ehci_physaddr_t physaddr; + usb_dma_t dma; + int offs; + int slot; + struct timeval t; /* store free time */ +} ehci_soft_itd_t; +#define EHCI_ITD_SIZE ((sizeof(struct ehci_soft_itd) + EHCI_QH_ALIGN - 1) / EHCI_ITD_ALIGN * EHCI_ITD_ALIGN) +#define EHCI_ITD_CHUNK (EHCI_PAGE_SIZE / EHCI_ITD_SIZE) + +struct ehci_xfer { + struct usbd_xfer xfer; + struct usb_task abort_task; + LIST_ENTRY(ehci_xfer) inext; /* list of active xfers */ + ehci_soft_qtd_t *sqtdstart; + ehci_soft_qtd_t *sqtdend; + ehci_soft_itd_t *itdstart; + ehci_soft_itd_t *itdend; + u_int isoc_len; + u_int32_t ehci_xfer_flags; +#ifdef DIAGNOSTIC + int isdone; +#endif +}; +#define EHCI_XFER_ABORTING 0x0001 /* xfer is aborting. */ +#define EHCI_XFER_ABORTWAIT 0x0002 /* abort completion is being awaited. */ + +#define EXFER(xfer) ((struct ehci_xfer *)(xfer)) + +/* + * Information about an entry in the interrupt list. + */ +struct ehci_soft_islot { + ehci_soft_qh_t *sqh; /* Queue Head. */ +}; + +#define EHCI_FRAMELIST_MAXCOUNT 1024 +#define EHCI_IPOLLRATES 8 /* Poll rates (1ms, 2, 4, 8 ... 128) */ +#define EHCI_INTRQHS ((1 << EHCI_IPOLLRATES) - 1) +#define EHCI_MAX_POLLRATE (1 << (EHCI_IPOLLRATES - 1)) +#define EHCI_IQHIDX(lev, pos) \ + ((((pos) & ((1 << (lev)) - 1)) | (1 << (lev))) - 1) +#define EHCI_ILEV_IVAL(lev) (1 << (lev)) + +#define EHCI_HASH_SIZE 128 +#define EHCI_COMPANION_MAX 8 + +#define EHCI_FREE_LIST_INTERVAL 100 + +#define EHCI_SCFLG_DONEINIT 0x0001 /* ehci_init() has been called. */ +#define EHCI_SCFLG_LOSTINTRBUG 0x0002 /* workaround for VIA / ATI chipsets */ +#define EHCI_SCFLG_SETMODE 0x0004 /* set bridge mode again after init (Marvell) */ +#define EHCI_SCFLG_FORCESPEED 0x0008 /* force speed (Marvell) */ +#define EHCI_SCFLG_NORESTERM 0x0010 /* don't terminate reset sequence (Marvell) */ +#define EHCI_SCFLG_BIGEDESC 0x0020 /* big-endian byte order descriptors */ +#define EHCI_SCFLG_BIGEMMIO 0x0040 /* big-endian byte order MMIO */ +#define EHCI_SCFLG_TT 0x0080 /* transaction translator present */ + +typedef struct ehci_softc { + struct usbd_bus sc_bus; /* base device */ + int sc_flags; + bus_space_tag_t iot; + bus_space_handle_t ioh; + bus_size_t sc_size; + void *ih; + + struct resource *io_res; + struct resource *irq_res; + u_int sc_offs; /* offset to operational regs */ + + char sc_vendor[32]; /* vendor string for root hub */ + int sc_id_vendor; /* vendor ID for root hub */ + + u_int32_t sc_cmd; /* shadow of cmd reg during suspend */ + + u_int sc_ncomp; + u_int sc_npcomp; + struct usbd_bus *sc_comps[EHCI_COMPANION_MAX]; + + usb_dma_t sc_fldma; + ehci_link_t *sc_flist; + u_int sc_flsize; + + struct ehci_soft_islot sc_islots[EHCI_INTRQHS]; + + /* jcmm - an array matching sc_flist, but with software pointers, + * not hardware address pointers + */ + struct ehci_soft_itd **sc_softitds; + + LIST_HEAD(, ehci_xfer) sc_intrhead; + + ehci_soft_qh_t *sc_freeqhs; + ehci_soft_qtd_t *sc_freeqtds; + LIST_HEAD(sc_freeitds, ehci_soft_itd) sc_freeitds; + + int sc_noport; + u_int8_t sc_addr; /* device address */ + u_int8_t sc_conf; /* device configuration */ + usbd_xfer_handle sc_intrxfer; + char sc_isreset; +#ifdef USB_USE_SOFTINTR + char sc_softwake; +#endif /* USB_USE_SOFTINTR */ + + u_int32_t sc_eintrs; + ehci_soft_qh_t *sc_async_head; + + STAILQ_HEAD(, usbd_xfer) sc_free_xfers; /* free xfers */ + + struct lock sc_doorbell_lock; + + struct callout sc_tmo_intrlist; + + char sc_dying; +} ehci_softc_t; + +#define EREAD1(sc, a) bus_space_read_1((sc)->iot, (sc)->ioh, (a)) +#define EREAD2(sc, a) bus_space_read_2((sc)->iot, (sc)->ioh, (a)) +#define EREAD4(sc, a) bus_space_read_4((sc)->iot, (sc)->ioh, (a)) +#define EWRITE1(sc, a, x) bus_space_write_1((sc)->iot, (sc)->ioh, (a), (x)) +#define EWRITE2(sc, a, x) bus_space_write_2((sc)->iot, (sc)->ioh, (a), (x)) +#define EWRITE4(sc, a, x) bus_space_write_4((sc)->iot, (sc)->ioh, (a), (x)) +#define EOREAD1(sc, a) bus_space_read_1((sc)->iot, (sc)->ioh, (sc)->sc_offs+(a)) +#define EOREAD2(sc, a) bus_space_read_2((sc)->iot, (sc)->ioh, (sc)->sc_offs+(a)) +#define EOREAD4(sc, a) bus_space_read_4((sc)->iot, (sc)->ioh, (sc)->sc_offs+(a)) +#define EOWRITE1(sc, a, x) bus_space_write_1((sc)->iot, (sc)->ioh, (sc)->sc_offs+(a), (x)) +#define EOWRITE2(sc, a, x) bus_space_write_2((sc)->iot, (sc)->ioh, (sc)->sc_offs+(a), (x)) +#define EOWRITE4(sc, a, x) bus_space_write_4((sc)->iot, (sc)->ioh, (sc)->sc_offs+(a), (x)) + +#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 + +usbd_status ehci_reset(ehci_softc_t *); +usbd_status ehci_init(ehci_softc_t *); +int ehci_intr(void *); +int ehci_detach(ehci_softc_t *, int); +void ehci_power(int state, void *priv); +void ehci_shutdown(void *v); + +#define MS_TO_TICKS(ms) ((ms) * hz / 1000) + +void ehci_dump_regs(ehci_softc_t *); +void ehci_dump_sqtds(ehci_softc_t *, ehci_soft_qtd_t *); +void ehci_dump_qtd(ehci_softc_t *, ehci_qtd_t *); +void ehci_dump_sqtd(ehci_softc_t *, ehci_soft_qtd_t *); +void ehci_dump_sqh(ehci_softc_t *, ehci_soft_qh_t *); +void ehci_dump_itd(ehci_softc_t *, struct ehci_soft_itd *); +void ehci_dump_sitd(ehci_softc_t *, struct ehci_soft_itd *); +void ehci_dump_exfer(struct ehci_xfer *); diff --git a/sys/legacy/dev/usb/hid.c b/sys/legacy/dev/usb/hid.c new file mode 100644 index 0000000..70facb6 --- /dev/null +++ b/sys/legacy/dev/usb/hid.c @@ -0,0 +1,469 @@ +/* $NetBSD: hid.c,v 1.17 2001/11/13 06:24:53 lukem Exp $ */ + + +#include <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 <sys/param.h> +#include <sys/systm.h> +#include <sys/malloc.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbhid.h> + +#include <dev/usb/hid.h> + +#ifdef USB_DEBUG +#define DPRINTF(x) if (usbdebug) printf x +#define DPRINTFN(n,x) if (usbdebug>(n)) printf x +extern int usbdebug; +#else +#define DPRINTF(x) +#define DPRINTFN(n,x) +#endif + +static void hid_clear_local(struct hid_item *); + +#define MAXUSAGE 100 +struct hid_data { + u_char *start; + u_char *end; + u_char *p; + struct hid_item cur; + int32_t usages[MAXUSAGE]; + int nu; + int minset; + int multi; + int multimax; + int kindset; +}; + +static void +hid_clear_local(struct hid_item *c) +{ + + 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; +} + +struct hid_data * +hid_start_parse(void *d, int len, int kindset) +{ + struct hid_data *s; + + s = malloc(sizeof *s, M_TEMP, M_WAITOK|M_ZERO); + s->start = s->p = d; + s->end = (char *)d + len; + s->kindset = kindset; + return (s); +} + +void +hid_end_parse(struct hid_data *s) +{ + + while (s->cur.next != NULL) { + struct hid_item *hi = s->cur.next->next; + free(s->cur.next, M_TEMP); + s->cur.next = hi; + } + free(s, M_TEMP); +} + +int +hid_get_item(struct hid_data *s, struct hid_item *h) +{ + struct hid_item *c = &s->cur; + unsigned int bTag, bType, bSize; + u_int32_t oldpos; + u_char *data; + int32_t dval; + u_char *p; + struct hid_item *hi; + int i; + + top: + if (s->multimax != 0) { + if (s->multi < s->multimax) { + c->usage = s->usages[min(s->multi, s->nu-1)]; + s->multi++; + *h = *c; + c->loc.pos += c->loc.size; + h->next = 0; + return (1); + } else { + c->loc.count = s->multimax; + s->multimax = 0; + s->nu = 0; + hid_clear_local(c); + } + } + for (;;) { + p = s->p; + if (p >= s->end) + return (0); + + bSize = *p++; + if (bSize == 0xfe) { + /* long item */ + bSize = *p++; + bSize |= *p++ << 8; + bTag = *p++; + data = p; + p += bSize; + bType = 0xff; /* XXX what should it be */ + } else { + /* short item */ + bTag = bSize >> 4; + bType = (bSize >> 2) & 3; + bSize &= 3; + if (bSize == 3) bSize = 4; + data = p; + p += bSize; + } + s->p = p; + switch(bSize) { + case 0: + dval = 0; + break; + case 1: + dval = (int8_t)*data++; + break; + case 2: + dval = *data++; + dval |= *data++ << 8; + dval = (int16_t)dval; + break; + case 4: + dval = *data++; + dval |= *data++ << 8; + dval |= *data++ << 16; + dval |= *data++ << 24; + break; + default: + printf("BAD LENGTH %d\n", bSize); + continue; + } + + switch (bType) { + case 0: /* Main */ + switch (bTag) { + case 8: /* Input */ + if (!(s->kindset & (1 << hid_input))) { + if (s->nu > 0) + s->nu--; + continue; + } + c->kind = hid_input; + c->flags = dval; + ret: + if (c->flags & HIO_VARIABLE) { + s->multimax = c->loc.count; + s->multi = 0; + c->loc.count = 1; + if (s->minset) { + for (i = c->usage_minimum; + i <= c->usage_maximum; + i++) { + s->usages[s->nu] = i; + if (s->nu < MAXUSAGE-1) + s->nu++; + } + s->minset = 0; + } + goto top; + } else { + *h = *c; + h->next = 0; + c->loc.pos += + c->loc.size * c->loc.count; + hid_clear_local(c); + s->minset = 0; + return (1); + } + case 9: /* Output */ + if (!(s->kindset & (1 << hid_output))) { + if (s->nu > 0) + s->nu--; + continue; + } + c->kind = hid_output; + c->flags = dval; + goto ret; + case 10: /* Collection */ + c->kind = hid_collection; + c->collection = dval; + c->collevel++; + *h = *c; + hid_clear_local(c); + s->nu = 0; + return (1); + case 11: /* Feature */ + if (!(s->kindset & (1 << hid_feature))) { + if (s->nu > 0) + s->nu--; + continue; + } + c->kind = hid_feature; + c->flags = dval; + goto ret; + case 12: /* End collection */ + c->kind = hid_endcollection; + c->collevel--; + *h = *c; + hid_clear_local(c); + s->nu = 0; + return (1); + default: + printf("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: + c->loc.size = dval; + break; + case 8: + c->report_ID = dval; + break; + case 9: + c->loc.count = dval; + break; + case 10: /* Push */ + hi = malloc(sizeof *hi, M_TEMP, M_WAITOK); + *hi = s->cur; + c->next = hi; + break; + case 11: /* Pop */ + hi = c->next; + oldpos = c->loc.pos; + s->cur = *hi; + c->loc.pos = oldpos; + free(hi, M_TEMP); + break; + default: + printf("Global bTag=%d\n", bTag); + break; + } + break; + case 2: /* Local */ + switch (bTag) { + case 0: + if (bSize == 1) + dval = c->_usage_page | (dval&0xff); + else if (bSize == 2) + dval = c->_usage_page | (dval&0xffff); + c->usage = dval; + if (s->nu < MAXUSAGE) + s->usages[s->nu++] = dval; + /* else XXX */ + break; + case 1: + s->minset = 1; + if (bSize == 1) + dval = c->_usage_page | (dval&0xff); + else if (bSize == 2) + dval = c->_usage_page | (dval&0xffff); + c->usage_minimum = dval; + break; + case 2: + if (bSize == 1) + dval = c->_usage_page | (dval&0xff); + else if (bSize == 2) + dval = c->_usage_page | (dval&0xffff); + c->usage_maximum = dval; + 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: + printf("Local bTag=%d\n", bTag); + break; + } + break; + default: + printf("default bType=%d\n", bType); + break; + } + } +} + +int +hid_report_size(void *buf, int len, enum hid_kind k, u_int8_t *idp) +{ + struct hid_data *d; + struct hid_item h; + int hi, lo, size, id; + + id = 0; + hi = lo = -1; + for (d = hid_start_parse(buf, len, 1<<k); hid_get_item(d, &h); ) + if (h.kind == k) { + if (h.report_ID != 0 && !id) + id = h.report_ID; + if (h.report_ID == id) { + if (lo < 0) + lo = h.loc.pos; + hi = h.loc.pos + h.loc.size * h.loc.count; + } + } + hid_end_parse(d); + size = hi - lo; + if (id != 0) { + size += 8; + *idp = id; /* XXX wrong */ + } else + *idp = 0; + return ((size + 7) / 8); +} + +int +hid_locate(void *desc, int size, u_int32_t u, enum hid_kind k, + struct hid_location *loc, u_int32_t *flags) +{ + 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 (loc != NULL) + *loc = h.loc; + if (flags != NULL) + *flags = h.flags; + hid_end_parse(d); + return (1); + } + } + hid_end_parse(d); + loc->size = 0; + return (0); +} + +u_long +hid_get_data(u_char *buf, struct hid_location *loc) +{ + u_int hpos = loc->pos; + u_int hsize = loc->size; + u_int32_t data; + int i, s; + + DPRINTFN(10, ("hid_get_data: loc %d/%d\n", hpos, hsize)); + + if (hsize == 0) + return (0); + + data = 0; + s = hpos / 8; + for (i = hpos; i < hpos+hsize; i += 8) + data |= buf[i / 8] << ((i / 8 - s) * 8); + data >>= hpos % 8; + data &= (1 << hsize) - 1; + hsize = 32 - hsize; + /* Sign extend */ + data = ((int32_t)data << hsize) >> hsize; + DPRINTFN(10,("hid_get_data: loc %d/%d = %lu\n", + loc->pos, loc->size, (long)data)); + return (data); +} + +int +hid_is_collection(void *desc, int size, u_int32_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); + + err = hid_get_item(hd, &hi) && + hi.kind == hid_collection && + hi.usage == usage; + hid_end_parse(hd); + return (err); +} diff --git a/sys/legacy/dev/usb/hid.h b/sys/legacy/dev/usb/hid.h new file mode 100644 index 0000000..a4ab7d2 --- /dev/null +++ b/sys/legacy/dev/usb/hid.h @@ -0,0 +1,91 @@ +/* $NetBSD: hid.h,v 1.6 2000/06/01 14:28:57 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. + */ + +enum hid_kind { + hid_input, hid_output, hid_feature, hid_collection, hid_endcollection +}; + +struct hid_location { + u_int32_t size; + u_int32_t count; + u_int32_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; + u_int32_t flags; + /* Location */ + struct hid_location loc; + /* */ + struct hid_item *next; +}; + +struct hid_data *hid_start_parse(void *d, int 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(void *buf, int len, enum hid_kind k, u_int8_t *id); +int hid_locate(void *desc, int size, u_int32_t usage, + enum hid_kind kind, struct hid_location *loc, + u_int32_t *flags); +u_long hid_get_data(u_char *buf, struct hid_location *loc); +int hid_is_collection(void *desc, int size, u_int32_t usage); diff --git a/sys/legacy/dev/usb/if_aue.c b/sys/legacy/dev/usb/if_aue.c new file mode 100644 index 0000000..1c6d8a3 --- /dev/null +++ b/sys/legacy/dev/usb/if_aue.c @@ -0,0 +1,1498 @@ +/*- + * Copyright (c) 1997, 1998, 1999, 2000 + * Bill Paul <wpaul@ee.columbia.edu>. All rights reserved. + * + * Copyright (c) 2006 + * Alfred Perlstein <alfred@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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * ADMtek AN986 Pegasus and AN8511 Pegasus II USB to ethernet driver. + * Datasheet is available from http://www.admtek.com.tw. + * + * Written by Bill Paul <wpaul@ee.columbia.edu> + * Electrical Engineering Department + * Columbia University, New York City + * + * SMP locking by Alfred Perlstein <alfred@freebsd.org>. + * RED Inc. + */ + +/* + * The Pegasus chip uses four USB "endpoints" to provide 10/100 ethernet + * support: the control endpoint for reading/writing registers, burst + * read endpoint for packet reception, burst write for packet transmission + * and one for "interrupts." The chip uses the same RX filter scheme + * as the other ADMtek ethernet parts: one perfect filter entry for the + * the station address and a 64-bit multicast hash table. The chip supports + * both MII and HomePNA attachments. + * + * Since the maximum data transfer speed of USB is supposed to be 12Mbps, + * you're never really going to get 100Mbps speeds from this device. I + * think the idea is to allow the device to connect to 10 or 100Mbps + * networks, not necessarily to provide 100Mbps performance. Also, since + * the controller uses an external PHY chip, it's possible that board + * designers might simply choose a 10Mbps PHY. + * + * Registers are accessed using usbd_do_request(). Packet transfers are + * done using usbd_transfer() and friends. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/sockio.h> +#include <sys/mbuf.h> +#include <sys/malloc.h> +#include <sys/kernel.h> +#include <sys/kdb.h> +#include <sys/lock.h> +#include <sys/module.h> +#include <sys/socket.h> +#include <sys/sx.h> +#include <sys/taskqueue.h> + +#include <net/if.h> +#include <net/if_arp.h> +#include <net/ethernet.h> +#include <net/if_dl.h> +#include <net/if_media.h> +#include <net/if_types.h> + +#include <net/bpf.h> + +#include <sys/bus.h> +#include <machine/bus.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usbdivar.h> +#include "usbdevs.h" +#include <dev/usb/usb_ethersubr.h> + +#include <dev/mii/mii.h> +#include <dev/mii/miivar.h> + +#include <dev/usb/if_auereg.h> + +MODULE_DEPEND(aue, usb, 1, 1, 1); +MODULE_DEPEND(aue, ether, 1, 1, 1); +MODULE_DEPEND(aue, miibus, 1, 1, 1); + +/* "device miibus" required. See GENERIC if you get errors here. */ +#include "miibus_if.h" + +/* + * Various supported device vendors/products. + */ +struct aue_type { + struct usb_devno aue_dev; + u_int16_t aue_flags; +#define LSYS 0x0001 /* use Linksys reset */ +#define PNA 0x0002 /* has Home PNA */ +#define PII 0x0004 /* Pegasus II chip */ +}; + +static const struct aue_type aue_devs[] = { + {{ USB_VENDOR_3COM, USB_PRODUCT_3COM_3C460B}, PII }, + {{ USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_XX1}, PNA|PII }, + {{ USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_XX2}, PII }, + {{ USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_UFE1000}, LSYS }, + {{ USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_XX4}, PNA }, + {{ USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_XX5}, PNA }, + {{ USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_XX6}, PII }, + {{ USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_XX7}, PII }, + {{ USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_XX8}, PII }, + {{ USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_XX9}, PNA }, + {{ USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_XX10}, 0 }, + {{ USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_DSB650TX_PNA}, 0 }, + {{ USB_VENDOR_ACCTON, USB_PRODUCT_ACCTON_USB320_EC}, 0 }, + {{ USB_VENDOR_ACCTON, USB_PRODUCT_ACCTON_SS1001}, PII }, + {{ USB_VENDOR_ADMTEK, USB_PRODUCT_ADMTEK_PEGASUS}, PNA }, + {{ USB_VENDOR_ADMTEK, USB_PRODUCT_ADMTEK_PEGASUSII}, PII }, + {{ USB_VENDOR_ADMTEK, USB_PRODUCT_ADMTEK_PEGASUSII_2}, PII }, + {{ USB_VENDOR_ADMTEK, USB_PRODUCT_ADMTEK_PEGASUSII_3}, PII }, + {{ USB_VENDOR_ADMTEK, USB_PRODUCT_ADMTEK_PEGASUSII_4}, PII }, + {{ USB_VENDOR_AEI, USB_PRODUCT_AEI_FASTETHERNET}, PII }, + {{ USB_VENDOR_ALLIEDTELESYN, USB_PRODUCT_ALLIEDTELESYN_ATUSB100}, PII }, + {{ USB_VENDOR_ATEN, USB_PRODUCT_ATEN_UC110T}, PII }, + {{ USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_USB2LAN}, PII }, + {{ USB_VENDOR_BILLIONTON, USB_PRODUCT_BILLIONTON_USB100}, 0 }, + {{ USB_VENDOR_BILLIONTON, USB_PRODUCT_BILLIONTON_USBLP100}, PNA }, + {{ USB_VENDOR_BILLIONTON, USB_PRODUCT_BILLIONTON_USBEL100}, 0 }, + {{ USB_VENDOR_BILLIONTON, USB_PRODUCT_BILLIONTON_USBE100}, PII }, + {{ USB_VENDOR_COREGA, USB_PRODUCT_COREGA_FETHER_USB_TX}, 0 }, + {{ USB_VENDOR_COREGA, USB_PRODUCT_COREGA_FETHER_USB_TXS},PII }, + {{ USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DSB650TX4}, LSYS|PII }, + {{ USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DSB650TX1}, LSYS }, + {{ USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DSB650TX}, LSYS }, + {{ USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DSB650TX_PNA}, PNA }, + {{ USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DSB650TX3}, LSYS|PII }, + {{ USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DSB650TX2}, LSYS|PII }, + {{ USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DSB650}, LSYS }, + {{ USB_VENDOR_ELCON, USB_PRODUCT_ELCON_PLAN}, PNA|PII }, + {{ USB_VENDOR_ELECOM, USB_PRODUCT_ELECOM_LDUSB20}, PII }, + {{ USB_VENDOR_ELECOM, USB_PRODUCT_ELECOM_LDUSBTX0}, 0 }, + {{ USB_VENDOR_ELECOM, USB_PRODUCT_ELECOM_LDUSBTX1}, LSYS }, + {{ USB_VENDOR_ELECOM, USB_PRODUCT_ELECOM_LDUSBTX2}, 0 }, + {{ USB_VENDOR_ELECOM, USB_PRODUCT_ELECOM_LDUSBTX3}, LSYS }, + {{ USB_VENDOR_ELECOM, USB_PRODUCT_ELECOM_LDUSBLTX}, PII }, + {{ USB_VENDOR_ELSA, USB_PRODUCT_ELSA_USB2ETHERNET}, 0 }, + {{ USB_VENDOR_GIGABYTE, USB_PRODUCT_GIGABYTE_GNBR402W}, 0 }, + {{ USB_VENDOR_HAWKING, USB_PRODUCT_HAWKING_UF100}, PII }, + {{ USB_VENDOR_HP, USB_PRODUCT_HP_HN210E}, PII }, + {{ USB_VENDOR_IODATA, USB_PRODUCT_IODATA_USBETTX}, 0 }, + {{ USB_VENDOR_IODATA, USB_PRODUCT_IODATA_USBETTXS}, PII }, + {{ USB_VENDOR_KINGSTON, USB_PRODUCT_KINGSTON_KNU101TX}, 0 }, + {{ USB_VENDOR_LINKSYS, USB_PRODUCT_LINKSYS_USB10TX1}, LSYS|PII }, + {{ USB_VENDOR_LINKSYS, USB_PRODUCT_LINKSYS_USB10T}, LSYS }, + {{ USB_VENDOR_LINKSYS, USB_PRODUCT_LINKSYS_USB100TX}, LSYS }, + {{ USB_VENDOR_LINKSYS, USB_PRODUCT_LINKSYS_USB100H1}, LSYS|PNA }, + {{ USB_VENDOR_LINKSYS, USB_PRODUCT_LINKSYS_USB10TA}, LSYS }, + {{ USB_VENDOR_LINKSYS, USB_PRODUCT_LINKSYS_USB10TX2}, LSYS|PII }, + {{ USB_VENDOR_MELCO, USB_PRODUCT_MELCO_LUATX1}, 0 }, + {{ USB_VENDOR_MELCO, USB_PRODUCT_MELCO_LUATX5}, 0 }, + {{ USB_VENDOR_MELCO, USB_PRODUCT_MELCO_LUA2TX5}, PII }, + {{ USB_VENDOR_MICROSOFT, USB_PRODUCT_MICROSOFT_MN110}, PII }, + {{ USB_VENDOR_NETGEAR, USB_PRODUCT_NETGEAR_FA101}, PII }, + {{ USB_VENDOR_SIEMENS, USB_PRODUCT_SIEMENS_SPEEDSTREAM}, PII }, + {{ USB_VENDOR_SIIG2, USB_PRODUCT_SIIG2_USBTOETHER}, PII }, + {{ USB_VENDOR_SMARTBRIDGES, USB_PRODUCT_SMARTBRIDGES_SMARTNIC},PII }, + {{ USB_VENDOR_SMC, USB_PRODUCT_SMC_2202USB}, 0 }, + {{ USB_VENDOR_SMC, USB_PRODUCT_SMC_2206USB}, PII }, + {{ USB_VENDOR_SOHOWARE, USB_PRODUCT_SOHOWARE_NUB100}, 0 }, + {{ USB_VENDOR_SOHOWARE, USB_PRODUCT_SOHOWARE_NUB110}, PII }, +}; +#define aue_lookup(v, p) ((const struct aue_type *)usb_lookup(aue_devs, v, p)) + +static device_probe_t aue_match; +static device_attach_t aue_attach; +static device_detach_t aue_detach; +static device_shutdown_t aue_shutdown; +static miibus_readreg_t aue_miibus_readreg; +static miibus_writereg_t aue_miibus_writereg; +static miibus_statchg_t aue_miibus_statchg; + +static void aue_reset_pegasus_II(struct aue_softc *sc); +static int aue_encap(struct aue_softc *, struct mbuf *, int); +#ifdef AUE_INTR_PIPE +static void aue_intr(usbd_xfer_handle, usbd_private_handle, usbd_status); +#endif +static void aue_rxeof(usbd_xfer_handle, usbd_private_handle, usbd_status); +static void aue_rxeof_thread(struct aue_softc *sc); +static void aue_txeof(usbd_xfer_handle, usbd_private_handle, usbd_status); +static void aue_txeof_thread(struct aue_softc *); +static void aue_task_sched(struct aue_softc *, int); +static void aue_task(void *xsc, int pending); +static void aue_tick(void *); +static void aue_rxstart(struct ifnet *); +static void aue_rxstart_thread(struct aue_softc *); +static void aue_start(struct ifnet *); +static void aue_start_thread(struct aue_softc *); +static int aue_ioctl(struct ifnet *, u_long, caddr_t); +static void aue_init(void *); +static void aue_init_body(struct aue_softc *); +static void aue_stop(struct aue_softc *); +static void aue_watchdog(struct aue_softc *); +static int aue_ifmedia_upd(struct ifnet *); +static void aue_ifmedia_sts(struct ifnet *, struct ifmediareq *); + +static void aue_eeprom_getword(struct aue_softc *, int, u_int16_t *); +static void aue_read_eeprom(struct aue_softc *, caddr_t, int, int, int); + +static void aue_setmulti(struct aue_softc *); +static void aue_reset(struct aue_softc *); + +static int aue_csr_read_1(struct aue_softc *, int); +static int aue_csr_write_1(struct aue_softc *, int, int); +static int aue_csr_read_2(struct aue_softc *, int); +static int aue_csr_write_2(struct aue_softc *, int, int); + +static device_method_t aue_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, aue_match), + DEVMETHOD(device_attach, aue_attach), + DEVMETHOD(device_detach, aue_detach), + DEVMETHOD(device_shutdown, aue_shutdown), + + /* bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + DEVMETHOD(bus_driver_added, bus_generic_driver_added), + + /* MII interface */ + DEVMETHOD(miibus_readreg, aue_miibus_readreg), + DEVMETHOD(miibus_writereg, aue_miibus_writereg), + DEVMETHOD(miibus_statchg, aue_miibus_statchg), + + { 0, 0 } +}; + +static driver_t aue_driver = { + "aue", + aue_methods, + sizeof(struct aue_softc) +}; + +static devclass_t aue_devclass; + +DRIVER_MODULE(aue, uhub, aue_driver, aue_devclass, usbd_driver_load, 0); +DRIVER_MODULE(miibus, aue, miibus_driver, miibus_devclass, 0, 0); + +#define AUE_SETBIT(sc, reg, x) \ + aue_csr_write_1(sc, reg, aue_csr_read_1(sc, reg) | (x)) + +#define AUE_CLRBIT(sc, reg, x) \ + aue_csr_write_1(sc, reg, aue_csr_read_1(sc, reg) & ~(x)) + +static int +aue_csr_read_1(struct aue_softc *sc, int reg) +{ + usb_device_request_t req; + usbd_status err; + u_int8_t val = 0; + + AUE_SXASSERTLOCKED(sc); + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = AUE_UR_READREG; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, 1); + + err = usbd_do_request(sc->aue_udev, &req, &val); + + if (err) { + return (0); + } + + return (val); +} + +static int +aue_csr_read_2(struct aue_softc *sc, int reg) +{ + usb_device_request_t req; + usbd_status err; + u_int16_t val = 0; + + AUE_SXASSERTLOCKED(sc); + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = AUE_UR_READREG; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, 2); + + err = usbd_do_request(sc->aue_udev, &req, &val); + + if (err) { + return (0); + } + + return (val); +} + +static int +aue_csr_write_1(struct aue_softc *sc, int reg, int val) +{ + usb_device_request_t req; + usbd_status err; + + AUE_SXASSERTLOCKED(sc); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = AUE_UR_WRITEREG; + USETW(req.wValue, val); + USETW(req.wIndex, reg); + USETW(req.wLength, 1); + + err = usbd_do_request(sc->aue_udev, &req, &val); + + if (err) { + return (-1); + } + + return (0); +} + +static int +aue_csr_write_2(struct aue_softc *sc, int reg, int val) +{ + usb_device_request_t req; + usbd_status err; + + AUE_SXASSERTLOCKED(sc); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = AUE_UR_WRITEREG; + USETW(req.wValue, val); + USETW(req.wIndex, reg); + USETW(req.wLength, 2); + + err = usbd_do_request(sc->aue_udev, &req, &val); + + if (err) { + return (-1); + } + + return (0); +} + +/* + * Read a word of data stored in the EEPROM at address 'addr.' + */ +static void +aue_eeprom_getword(struct aue_softc *sc, int addr, u_int16_t *dest) +{ + int i; + u_int16_t word = 0; + + aue_csr_write_1(sc, AUE_EE_REG, addr); + aue_csr_write_1(sc, AUE_EE_CTL, AUE_EECTL_READ); + + for (i = 0; i < AUE_TIMEOUT; i++) { + if (aue_csr_read_1(sc, AUE_EE_CTL) & AUE_EECTL_DONE) + break; + } + + if (i == AUE_TIMEOUT) { + printf("aue%d: EEPROM read timed out\n", + sc->aue_unit); + } + + word = aue_csr_read_2(sc, AUE_EE_DATA); + *dest = word; + + return; +} + +/* + * Read a sequence of words from the EEPROM. + */ +static void +aue_read_eeprom(struct aue_softc *sc, caddr_t dest, int off, int cnt, int swap) +{ + int i; + u_int16_t word = 0, *ptr; + + for (i = 0; i < cnt; i++) { + aue_eeprom_getword(sc, off + i, &word); + ptr = (u_int16_t *)(dest + (i * 2)); + if (swap) + *ptr = ntohs(word); + else + *ptr = word; + } + + return; +} + +static int +aue_miibus_readreg(device_t dev, int phy, int reg) +{ + struct aue_softc *sc = device_get_softc(dev); + int i; + u_int16_t val = 0; + + /* + * The Am79C901 HomePNA PHY actually contains + * two transceivers: a 1Mbps HomePNA PHY and a + * 10Mbps full/half duplex ethernet PHY with + * NWAY autoneg. However in the ADMtek adapter, + * only the 1Mbps PHY is actually connected to + * anything, so we ignore the 10Mbps one. It + * happens to be configured for MII address 3, + * so we filter that out. + */ + if (sc->aue_vendor == USB_VENDOR_ADMTEK && + sc->aue_product == USB_PRODUCT_ADMTEK_PEGASUS) { + if (phy == 3) + return (0); +#ifdef notdef + if (phy != 1) + return (0); +#endif + } + + aue_csr_write_1(sc, AUE_PHY_ADDR, phy); + aue_csr_write_1(sc, AUE_PHY_CTL, reg | AUE_PHYCTL_READ); + + for (i = 0; i < AUE_TIMEOUT; i++) { + if (aue_csr_read_1(sc, AUE_PHY_CTL) & AUE_PHYCTL_DONE) + break; + } + + if (i == AUE_TIMEOUT) { + printf("aue%d: MII read timed out\n", sc->aue_unit); + } + + val = aue_csr_read_2(sc, AUE_PHY_DATA); + + return (val); +} + +static int +aue_miibus_writereg(device_t dev, int phy, int reg, int data) +{ + struct aue_softc *sc = device_get_softc(dev); + int i; + + if (phy == 3) + return (0); + + aue_csr_write_2(sc, AUE_PHY_DATA, data); + aue_csr_write_1(sc, AUE_PHY_ADDR, phy); + aue_csr_write_1(sc, AUE_PHY_CTL, reg | AUE_PHYCTL_WRITE); + + for (i = 0; i < AUE_TIMEOUT; i++) { + if (aue_csr_read_1(sc, AUE_PHY_CTL) & AUE_PHYCTL_DONE) + break; + } + + if (i == AUE_TIMEOUT) { + printf("aue%d: MII read timed out\n", + sc->aue_unit); + } + + return(0); +} + +static void +aue_miibus_statchg(device_t dev) +{ + struct aue_softc *sc = device_get_softc(dev); + struct mii_data *mii = GET_MII(sc); + + AUE_CLRBIT(sc, AUE_CTL0, AUE_CTL0_RX_ENB | AUE_CTL0_TX_ENB); + if (IFM_SUBTYPE(mii->mii_media_active) == IFM_100_TX) { + AUE_SETBIT(sc, AUE_CTL1, AUE_CTL1_SPEEDSEL); + } else { + AUE_CLRBIT(sc, AUE_CTL1, AUE_CTL1_SPEEDSEL); + } + + if ((mii->mii_media_active & IFM_GMASK) == IFM_FDX) + AUE_SETBIT(sc, AUE_CTL1, AUE_CTL1_DUPLEX); + else + AUE_CLRBIT(sc, AUE_CTL1, AUE_CTL1_DUPLEX); + + AUE_SETBIT(sc, AUE_CTL0, AUE_CTL0_RX_ENB | AUE_CTL0_TX_ENB); + + /* + * Set the LED modes on the LinkSys adapter. + * This turns on the 'dual link LED' bin in the auxmode + * register of the Broadcom PHY. + */ + if (sc->aue_flags & LSYS) { + u_int16_t auxmode; + auxmode = aue_miibus_readreg(dev, 0, 0x1b); + aue_miibus_writereg(dev, 0, 0x1b, auxmode | 0x04); + } + + return; +} + +#define AUE_BITS 6 + +static void +aue_setmulti(struct aue_softc *sc) +{ + struct ifnet *ifp; + struct ifmultiaddr *ifma; + u_int32_t h = 0, i; + u_int8_t hashtbl[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + + AUE_SXASSERTLOCKED(sc); + ifp = sc->aue_ifp; + + if (ifp->if_flags & IFF_ALLMULTI || ifp->if_flags & IFF_PROMISC) { + AUE_SETBIT(sc, AUE_CTL0, AUE_CTL0_ALLMULTI); + return; + } + + AUE_CLRBIT(sc, AUE_CTL0, AUE_CTL0_ALLMULTI); + + /* now program new ones */ + IF_ADDR_LOCK(ifp); + TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) + { + if (ifma->ifma_addr->sa_family != AF_LINK) + continue; + h = ether_crc32_le(LLADDR((struct sockaddr_dl *) + ifma->ifma_addr), ETHER_ADDR_LEN) & ((1 << AUE_BITS) - 1); + hashtbl[(h >> 3)] |= 1 << (h & 0x7); + } + IF_ADDR_UNLOCK(ifp); + + /* write the hashtable */ + for (i = 0; i < 8; i++) + aue_csr_write_1(sc, AUE_MAR0 + i, hashtbl[i]); + + return; +} + +static void +aue_reset_pegasus_II(struct aue_softc *sc) +{ + /* Magic constants taken from Linux driver. */ + aue_csr_write_1(sc, AUE_REG_1D, 0); + aue_csr_write_1(sc, AUE_REG_7B, 2); +#if 0 + if ((sc->aue_flags & HAS_HOME_PNA) && mii_mode) + aue_csr_write_1(sc, AUE_REG_81, 6); + else +#endif + aue_csr_write_1(sc, AUE_REG_81, 2); +} + +static void +aue_reset(struct aue_softc *sc) +{ + int i; + + AUE_SXASSERTLOCKED(sc); + AUE_SETBIT(sc, AUE_CTL1, AUE_CTL1_RESETMAC); + + for (i = 0; i < AUE_TIMEOUT; i++) { + if (!(aue_csr_read_1(sc, AUE_CTL1) & AUE_CTL1_RESETMAC)) + break; + } + + if (i == AUE_TIMEOUT) + printf("aue%d: reset failed\n", sc->aue_unit); + + /* + * The PHY(s) attached to the Pegasus chip may be held + * in reset until we flip on the GPIO outputs. Make sure + * to set the GPIO pins high so that the PHY(s) will + * be enabled. + * + * Note: We force all of the GPIO pins low first, *then* + * enable the ones we want. + */ + aue_csr_write_1(sc, AUE_GPIO0, AUE_GPIO_OUT0|AUE_GPIO_SEL0); + aue_csr_write_1(sc, AUE_GPIO0, AUE_GPIO_OUT0|AUE_GPIO_SEL0|AUE_GPIO_SEL1); + + if (sc->aue_flags & LSYS) { + /* Grrr. LinkSys has to be different from everyone else. */ + aue_csr_write_1(sc, AUE_GPIO0, + AUE_GPIO_SEL0 | AUE_GPIO_SEL1); + aue_csr_write_1(sc, AUE_GPIO0, + AUE_GPIO_SEL0 | AUE_GPIO_SEL1 | AUE_GPIO_OUT0); + } + + if (sc->aue_flags & PII) + aue_reset_pegasus_II(sc); + + /* Wait a little while for the chip to get its brains in order. */ + DELAY(10000); + + return; +} + +/* + * Probe for a Pegasus chip. + */ +static int +aue_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + + if (uaa->iface != NULL) + return (UMATCH_NONE); + + /* + * Belkin USB Bluetooth dongles of the F8T012xx1 model series conflict + * with older Belkin USB2LAN adapters. Skip if_aue if we detect one of + * the devices that look like Bluetooth adapters. + */ + if (uaa->vendor == USB_VENDOR_BELKIN && + uaa->product == USB_PRODUCT_BELKIN_F8T012 && uaa->release == 0x0413) + return (UMATCH_NONE); + + return (aue_lookup(uaa->vendor, uaa->product) != NULL ? + UMATCH_VENDOR_PRODUCT : UMATCH_NONE); +} + +/* + * Attach the interface. Allocate softc structures, do ifmedia + * setup and ethernet/BPF attach. + */ +static int +aue_attach(device_t self) +{ + struct aue_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + u_char eaddr[ETHER_ADDR_LEN]; + struct ifnet *ifp; + usbd_interface_handle iface; + usbd_status err; + usb_interface_descriptor_t *id; + usb_endpoint_descriptor_t *ed; + int i; + + sc->aue_dev = self; + sc->aue_udev = uaa->device; + sc->aue_unit = device_get_unit(self); + + if (usbd_set_config_no(sc->aue_udev, AUE_CONFIG_NO, 0)) { + device_printf(self, "getting interface handle failed\n"); + return ENXIO; + } + + err = usbd_device2interface_handle(uaa->device, AUE_IFACE_IDX, &iface); + if (err) { + device_printf(self, "getting interface handle failed\n"); + return ENXIO; + } + + sc->aue_iface = iface; + sc->aue_flags = aue_lookup(uaa->vendor, uaa->product)->aue_flags; + + sc->aue_product = uaa->product; + sc->aue_vendor = uaa->vendor; + + id = usbd_get_interface_descriptor(sc->aue_iface); + + /* Find endpoints. */ + for (i = 0; i < id->bNumEndpoints; i++) { + ed = usbd_interface2endpoint_descriptor(iface, i); + if (ed == NULL) { + device_printf(self, "couldn't get ep %d\n", i); + return ENXIO; + } + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + sc->aue_ed[AUE_ENDPT_RX] = ed->bEndpointAddress; + } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + sc->aue_ed[AUE_ENDPT_TX] = ed->bEndpointAddress; + } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_INTERRUPT) { + sc->aue_ed[AUE_ENDPT_INTR] = ed->bEndpointAddress; + } + } + + mtx_init(&sc->aue_mtx, device_get_nameunit(self), MTX_NETWORK_LOCK, + MTX_DEF | MTX_RECURSE); + sx_init(&sc->aue_sx, device_get_nameunit(self)); + TASK_INIT(&sc->aue_task, 0, aue_task, sc); + usb_ether_task_init(self, 0, &sc->aue_taskqueue); + AUE_SXLOCK(sc); + + /* Reset the adapter. */ + aue_reset(sc); + + /* + * Get station address from the EEPROM. + */ + aue_read_eeprom(sc, (caddr_t)&eaddr, 0, 3, 0); + + ifp = sc->aue_ifp = if_alloc(IFT_ETHER); + if (ifp == NULL) { + device_printf(self, "can not if_alloc()\n"); + AUE_SXUNLOCK(sc); + mtx_destroy(&sc->aue_mtx); + sx_destroy(&sc->aue_sx); + usb_ether_task_destroy(&sc->aue_taskqueue); + return ENXIO; + } + ifp->if_softc = sc; + if_initname(ifp, "aue", sc->aue_unit); + ifp->if_mtu = ETHERMTU; + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; + ifp->if_ioctl = aue_ioctl; + ifp->if_start = aue_start; + ifp->if_init = aue_init; + IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN); + ifp->if_snd.ifq_drv_maxlen = IFQ_MAXLEN; + IFQ_SET_READY(&ifp->if_snd); + + /* + * Do MII setup. + * NOTE: Doing this causes child devices to be attached to us, + * which we would normally disconnect at in the detach routine + * using device_delete_child(). However the USB code is set up + * such that when this driver is removed, all children devices + * are removed as well. In effect, the USB code ends up detaching + * all of our children for us, so we don't have to do is ourselves + * in aue_detach(). It's important to point this out since if + * we *do* try to detach the child devices ourselves, we will + * end up getting the children deleted twice, which will crash + * the system. + */ + if (mii_phy_probe(self, &sc->aue_miibus, + aue_ifmedia_upd, aue_ifmedia_sts)) { + device_printf(self, "MII without any PHY!\n"); + if_free(ifp); + AUE_SXUNLOCK(sc); + mtx_destroy(&sc->aue_mtx); + sx_destroy(&sc->aue_sx); + usb_ether_task_destroy(&sc->aue_taskqueue); + return ENXIO; + } + + sc->aue_qdat.ifp = ifp; + sc->aue_qdat.if_rxstart = aue_rxstart; + + /* + * Call MI attach routine. + */ + ether_ifattach(ifp, eaddr); + usb_register_netisr(); + sc->aue_dying = 0; + sc->aue_link = 1; + + AUE_SXUNLOCK(sc); + return 0; +} + +static int +aue_detach(device_t dev) +{ + struct aue_softc *sc; + struct ifnet *ifp; + + sc = device_get_softc(dev); + AUE_SXLOCK(sc); + ifp = sc->aue_ifp; + ether_ifdetach(ifp); + sc->aue_dying = 1; + AUE_SXUNLOCK(sc); + callout_drain(&sc->aue_tick_callout); + usb_ether_task_drain(&sc->aue_taskqueue, &sc->aue_task); + usb_ether_task_destroy(&sc->aue_taskqueue); + if_free(ifp); + + if (sc->aue_ep[AUE_ENDPT_TX] != NULL) + usbd_abort_pipe(sc->aue_ep[AUE_ENDPT_TX]); + if (sc->aue_ep[AUE_ENDPT_RX] != NULL) + usbd_abort_pipe(sc->aue_ep[AUE_ENDPT_RX]); +#ifdef AUE_INTR_PIPE + if (sc->aue_ep[AUE_ENDPT_INTR] != NULL) + usbd_abort_pipe(sc->aue_ep[AUE_ENDPT_INTR]); +#endif + + mtx_destroy(&sc->aue_mtx); + sx_destroy(&sc->aue_sx); + + return (0); +} + +static void +aue_rxstart(struct ifnet *ifp) +{ + struct aue_softc *sc = ifp->if_softc; + aue_task_sched(sc, AUE_TASK_RXSTART); +} + +static void +aue_rxstart_thread(struct aue_softc *sc) +{ + struct ue_chain *c; + struct ifnet *ifp; + + ifp = sc->aue_ifp; + + sc = ifp->if_softc; + AUE_SXASSERTLOCKED(sc); + c = &sc->aue_cdata.ue_rx_chain[sc->aue_cdata.ue_rx_prod]; + + c->ue_mbuf = usb_ether_newbuf(); + if (c->ue_mbuf == NULL) { + device_printf(sc->aue_dev, "no memory for rx list -- packet " + "dropped!\n"); + ifp->if_ierrors++; + AUE_UNLOCK(sc); + return; + } + + /* Setup new transfer. */ + usbd_setup_xfer(c->ue_xfer, sc->aue_ep[AUE_ENDPT_RX], + c, mtod(c->ue_mbuf, char *), UE_BUFSZ, USBD_SHORT_XFER_OK, + USBD_NO_TIMEOUT, aue_rxeof); + usbd_transfer(c->ue_xfer); + + return; +} + +/* + * A frame has been uploaded: pass the resulting mbuf chain up to + * the higher level protocols. + */ +static void +aue_rxeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + struct ue_chain *c = priv; + c->ue_status = status; + aue_task_sched(c->ue_sc, AUE_TASK_RXEOF); +} + +static void +aue_rxeof_thread(struct aue_softc *sc) +{ + struct ue_chain *c = &(sc->aue_cdata.ue_rx_chain[0]); + struct mbuf *m; + struct ifnet *ifp; + int total_len = 0; + struct aue_rxpkt r; + usbd_status status = c->ue_status; + + + AUE_SXASSERTLOCKED(sc); + ifp = sc->aue_ifp; + + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + return; + } + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) { + return; + } + if (usbd_ratecheck(&sc->aue_rx_notice)) + device_printf(sc->aue_dev, "usb error on rx: %s\n", + usbd_errstr(status)); + if (status == USBD_STALLED) + usbd_clear_endpoint_stall(sc->aue_ep[AUE_ENDPT_RX]); + goto done; + } + + usbd_get_xfer_status(c->ue_xfer, NULL, NULL, &total_len, NULL); + + if (total_len <= 4 + ETHER_CRC_LEN) { + ifp->if_ierrors++; + goto done; + } + + m = c->ue_mbuf; + bcopy(mtod(m, char *) + total_len - 4, (char *)&r, sizeof(r)); + + /* Turn off all the non-error bits in the rx status word. */ + r.aue_rxstat &= AUE_RXSTAT_MASK; + + if (r.aue_rxstat) { + ifp->if_ierrors++; + goto done; + } + + /* No errors; receive the packet. */ + total_len -= (4 + ETHER_CRC_LEN); + + ifp->if_ipackets++; + m->m_pkthdr.rcvif = (void *)&sc->aue_qdat; + m->m_pkthdr.len = m->m_len = total_len; + + /* Put the packet on the special USB input queue. */ + usb_ether_input(m); + return; +done: + + /* Setup new transfer. */ + usbd_setup_xfer(c->ue_xfer, sc->aue_ep[AUE_ENDPT_RX], + c, mtod(c->ue_mbuf, char *), UE_BUFSZ, USBD_SHORT_XFER_OK, + USBD_NO_TIMEOUT, aue_rxeof); + usbd_transfer(c->ue_xfer); + + return; +} + +/* + * A frame was downloaded to the chip. It's safe for us to clean up + * the list buffers. + */ + +static void +aue_txeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + struct ue_chain *c = priv; + c->ue_status = status; + aue_task_sched(c->ue_sc, AUE_TASK_TXEOF); +} + +static void +aue_txeof_thread(struct aue_softc *sc) +{ + struct ue_chain *c = &(sc->aue_cdata.ue_tx_chain[0]); + struct ifnet *ifp; + usbd_status err, status; + + AUE_SXASSERTLOCKED(sc); + status = c->ue_status; + ifp = sc->aue_ifp; + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) { + return; + } + device_printf(sc->aue_dev, "usb error on tx: %s\n", + usbd_errstr(status)); + if (status == USBD_STALLED) + usbd_clear_endpoint_stall(sc->aue_ep[AUE_ENDPT_TX]); + return; + } + + sc->aue_timer = 0; + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + usbd_get_xfer_status(c->ue_xfer, NULL, NULL, NULL, &err); + + if (c->ue_mbuf != NULL) { + c->ue_mbuf->m_pkthdr.rcvif = ifp; + usb_tx_done(c->ue_mbuf); + c->ue_mbuf = NULL; + } + + if (err) + ifp->if_oerrors++; + else + ifp->if_opackets++; + + return; +} + +static void +aue_tick(void *xsc) +{ + struct aue_softc *sc = xsc; + + aue_task_sched(sc, AUE_TASK_TICK); +} + +static void +aue_tick_thread(struct aue_softc *sc) +{ + struct ifnet *ifp; + struct mii_data *mii; + + AUE_SXASSERTLOCKED(sc); + ifp = sc->aue_ifp; + /* + * If a timer is set (non-zero) then decrement it + * and if it hits zero, then call the watchdog routine. + */ + if (sc->aue_timer != 0 && --sc->aue_timer == 0) { + aue_watchdog(sc); + } + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + return; + } + + mii = GET_MII(sc); + if (mii == NULL) { + goto resched; + } + + mii_tick(mii); + if (!sc->aue_link && mii->mii_media_status & IFM_ACTIVE && + IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE) { + sc->aue_link++; + if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) + aue_start_thread(sc); + } +resched: + (void) callout_reset(&sc->aue_tick_callout, hz, aue_tick, sc); + return; +} + +static int +aue_encap(struct aue_softc *sc, struct mbuf *m, int idx) +{ + int total_len; + struct ue_chain *c; + usbd_status err; + + AUE_SXASSERTLOCKED(sc); + + c = &sc->aue_cdata.ue_tx_chain[idx]; + + /* + * Copy the mbuf data into a contiguous buffer, leaving two + * bytes at the beginning to hold the frame length. + */ + m_copydata(m, 0, m->m_pkthdr.len, c->ue_buf + 2); + c->ue_mbuf = m; + + total_len = m->m_pkthdr.len + 2; + + /* + * The ADMtek documentation says that the packet length is + * supposed to be specified in the first two bytes of the + * transfer, however it actually seems to ignore this info + * and base the frame size on the bulk transfer length. + */ + c->ue_buf[0] = (u_int8_t)m->m_pkthdr.len; + c->ue_buf[1] = (u_int8_t)(m->m_pkthdr.len >> 8); + + usbd_setup_xfer(c->ue_xfer, sc->aue_ep[AUE_ENDPT_TX], + c, c->ue_buf, total_len, USBD_FORCE_SHORT_XFER, + 10000, aue_txeof); + + /* Transmit */ + err = usbd_transfer(c->ue_xfer); + if (err != USBD_IN_PROGRESS) { + aue_stop(sc); + return (EIO); + } + + sc->aue_cdata.ue_tx_cnt++; + + return (0); +} + + +static void +aue_start(struct ifnet *ifp) +{ + struct aue_softc *sc = ifp->if_softc; + aue_task_sched(sc, AUE_TASK_START); +} + +static void +aue_start_thread(struct aue_softc *sc) +{ + struct ifnet *ifp = sc->aue_ifp; + struct mbuf *m_head = NULL; + + AUE_SXASSERTLOCKED(sc); + + if (!sc->aue_link) { + return; + } + + if (ifp->if_drv_flags & IFF_DRV_OACTIVE) { + return; + } + + IFQ_DRV_DEQUEUE(&ifp->if_snd, m_head); + if (m_head == NULL) { + return; + } + + if (aue_encap(sc, m_head, 0)) { + IFQ_DRV_PREPEND(&ifp->if_snd, m_head); + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + return; + } + + /* + * If there's a BPF listener, bounce a copy of this frame + * to him. + */ + BPF_MTAP(ifp, m_head); + + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + + /* + * Set a timeout in case the chip goes out to lunch. + */ + sc->aue_timer = 5; + + return; +} + +static void +aue_init(void *xsc) +{ + struct aue_softc *sc = xsc; + + AUE_SXLOCK(sc); + aue_init_body(sc); + AUE_SXUNLOCK(sc); +} + +static void +aue_init_body(struct aue_softc *sc) +{ + struct ifnet *ifp = sc->aue_ifp; + struct mii_data *mii = GET_MII(sc); + struct ue_chain *c; + usbd_status err; + int i; + + AUE_SXASSERTLOCKED(sc); + + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + return; + } + + /* + * Cancel pending I/O and free all RX/TX buffers. + */ + aue_reset(sc); + + /* Set MAC address */ + for (i = 0; i < ETHER_ADDR_LEN; i++) + aue_csr_write_1(sc, AUE_PAR0 + i, IF_LLADDR(sc->aue_ifp)[i]); + + /* If we want promiscuous mode, set the allframes bit. */ + if (ifp->if_flags & IFF_PROMISC) + AUE_SETBIT(sc, AUE_CTL2, AUE_CTL2_RX_PROMISC); + else + AUE_CLRBIT(sc, AUE_CTL2, AUE_CTL2_RX_PROMISC); + + /* Init TX ring. */ + if (usb_ether_tx_list_init(sc, &sc->aue_cdata, + sc->aue_udev) == ENOBUFS) { + device_printf(sc->aue_dev, "tx list init failed\n"); + return; + } + + /* Init RX ring. */ + if (usb_ether_rx_list_init(sc, &sc->aue_cdata, + sc->aue_udev) == ENOBUFS) { + device_printf(sc->aue_dev, "rx list init failed\n"); + return; + } + + + /* Load the multicast filter. */ + aue_setmulti(sc); + + /* Enable RX and TX */ + aue_csr_write_1(sc, AUE_CTL0, AUE_CTL0_RXSTAT_APPEND | AUE_CTL0_RX_ENB); + AUE_SETBIT(sc, AUE_CTL0, AUE_CTL0_TX_ENB); + AUE_SETBIT(sc, AUE_CTL2, AUE_CTL2_EP3_CLR); + + mii_mediachg(mii); + + /* Open RX and TX pipes. */ + err = usbd_open_pipe(sc->aue_iface, sc->aue_ed[AUE_ENDPT_RX], + USBD_EXCLUSIVE_USE, &sc->aue_ep[AUE_ENDPT_RX]); + if (err) { + device_printf(sc->aue_dev, "open rx pipe failed: %s\n", + usbd_errstr(err)); + return; + } + err = usbd_open_pipe(sc->aue_iface, sc->aue_ed[AUE_ENDPT_TX], + USBD_EXCLUSIVE_USE, &sc->aue_ep[AUE_ENDPT_TX]); + if (err) { + device_printf(sc->aue_dev, "open tx pipe failed: %s\n", + usbd_errstr(err)); + return; + } + + + /* Start up the receive pipe. */ + for (i = 0; i < UE_RX_LIST_CNT; i++) { + c = &sc->aue_cdata.ue_rx_chain[i]; + usbd_setup_xfer(c->ue_xfer, sc->aue_ep[AUE_ENDPT_RX], + c, mtod(c->ue_mbuf, char *), UE_BUFSZ, + USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT, aue_rxeof); + usbd_transfer(c->ue_xfer); + } + + ifp->if_drv_flags |= IFF_DRV_RUNNING; + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + + callout_init(&sc->aue_tick_callout, CALLOUT_MPSAFE); + (void) callout_reset(&sc->aue_tick_callout, hz, aue_tick, sc); + return; +} + +/* + * Set media options. + */ +static int +aue_ifmedia_upd(struct ifnet *ifp) +{ + struct aue_softc *sc = ifp->if_softc; + struct mii_data *mii = GET_MII(sc); + + sc->aue_link = 0; + if (mii->mii_instance) { + struct mii_softc *miisc; + LIST_FOREACH(miisc, &mii->mii_phys, mii_list) + mii_phy_reset(miisc); + } + mii_mediachg(mii); + sc->aue_link = 1; + + return (0); +} + +/* + * Report current media status. + */ +static void +aue_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr) +{ + struct aue_softc *sc = ifp->if_softc; + struct mii_data *mii = GET_MII(sc); + + mii_pollstat(mii); + ifmr->ifm_active = mii->mii_media_active; + ifmr->ifm_status = mii->mii_media_status; + + return; +} + +static int +aue_ioctl(struct ifnet *ifp, u_long command, caddr_t data) +{ + struct aue_softc *sc = ifp->if_softc; + struct ifreq *ifr = (struct ifreq *)data; + struct mii_data *mii; + int error = 0; + + /* + * This prevents recursion in the interface while it's + * being torn down. + */ + if (sc->aue_dying) + return(0); + + AUE_GIANTLOCK(); + + switch(command) { + case SIOCSIFFLAGS: + AUE_SXLOCK(sc); + if (ifp->if_flags & IFF_UP) { + if (ifp->if_drv_flags & IFF_DRV_RUNNING && + ifp->if_flags & IFF_PROMISC && + !(sc->aue_if_flags & IFF_PROMISC)) { + AUE_SETBIT(sc, AUE_CTL2, AUE_CTL2_RX_PROMISC); + } else if (ifp->if_drv_flags & IFF_DRV_RUNNING && + !(ifp->if_flags & IFF_PROMISC) && + sc->aue_if_flags & IFF_PROMISC) { + AUE_CLRBIT(sc, AUE_CTL2, AUE_CTL2_RX_PROMISC); + } else if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + aue_init_body(sc); + } + sc->aue_dying = 0; + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + aue_stop(sc); + } + sc->aue_if_flags = ifp->if_flags; + AUE_SXUNLOCK(sc); + break; + case SIOCADDMULTI: + case SIOCDELMULTI: + AUE_SXLOCK(sc); + aue_setmulti(sc); + AUE_SXUNLOCK(sc); + break; + case SIOCGIFMEDIA: + case SIOCSIFMEDIA: + AUE_SXLOCK(sc); + mii = GET_MII(sc); + error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, command); + AUE_SXUNLOCK(sc); + break; + default: + error = ether_ioctl(ifp, command, data); + break; + } + + AUE_GIANTUNLOCK(); + + return (error); +} + +static void +aue_watchdog(struct aue_softc *sc) +{ + struct ifnet *ifp = sc->aue_ifp; + struct ue_chain *c; + usbd_status stat; + + AUE_SXASSERTLOCKED(sc); + ifp->if_oerrors++; + device_printf(sc->aue_dev, "watchdog timeout\n"); + + c = &sc->aue_cdata.ue_tx_chain[0]; + usbd_get_xfer_status(c->ue_xfer, NULL, NULL, NULL, &stat); + c->ue_status = stat; + aue_txeof_thread(sc); + + if (!IFQ_IS_EMPTY(&ifp->if_snd)) + aue_start_thread(sc); + return; +} + +/* + * Stop the adapter and free any mbufs allocated to the + * RX and TX lists. + */ +static void +aue_stop(struct aue_softc *sc) +{ + usbd_status err; + struct ifnet *ifp; + + AUE_SXASSERTLOCKED(sc); + ifp = sc->aue_ifp; + sc->aue_timer = 0; + + aue_csr_write_1(sc, AUE_CTL0, 0); + aue_csr_write_1(sc, AUE_CTL1, 0); + aue_reset(sc); + sc->aue_dying = 1; + + /* Stop transfers. */ + if (sc->aue_ep[AUE_ENDPT_RX] != NULL) { + err = usbd_abort_pipe(sc->aue_ep[AUE_ENDPT_RX]); + if (err) { + device_printf(sc->aue_dev, + "abort rx pipe failed: %s\n", usbd_errstr(err)); + } + err = usbd_close_pipe(sc->aue_ep[AUE_ENDPT_RX]); + if (err) { + device_printf(sc->aue_dev, + "close rx pipe failed: %s\n", usbd_errstr(err)); + } + sc->aue_ep[AUE_ENDPT_RX] = NULL; + } + + if (sc->aue_ep[AUE_ENDPT_TX] != NULL) { + err = usbd_abort_pipe(sc->aue_ep[AUE_ENDPT_TX]); + if (err) { + device_printf(sc->aue_dev, + "abort tx pipe failed: %s\n", usbd_errstr(err)); + } + err = usbd_close_pipe(sc->aue_ep[AUE_ENDPT_TX]); + if (err) { + device_printf(sc->aue_dev, + "close tx pipe failed: %s\n", usbd_errstr(err)); + } + sc->aue_ep[AUE_ENDPT_TX] = NULL; + } + +#ifdef AUE_INTR_PIPE + if (sc->aue_ep[AUE_ENDPT_INTR] != NULL) { + err = usbd_abort_pipe(sc->aue_ep[AUE_ENDPT_INTR]); + if (err) { + device_printf(sc->aue_dev, + "abort intr pipe failed: %s\n", usbd_errstr(err)); + } + err = usbd_close_pipe(sc->aue_ep[AUE_ENDPT_INTR]); + if (err) { + device_printf(sc->aue_dev, + "close intr pipe failed: %s\n", usbd_errstr(err)); + } + sc->aue_ep[AUE_ENDPT_INTR] = NULL; + } +#endif + + /* Free RX resources. */ + usb_ether_rx_list_free(&sc->aue_cdata); + /* Free TX resources. */ + usb_ether_tx_list_free(&sc->aue_cdata); + +#ifdef AUE_INTR_PIPE + free(sc->aue_cdata.ue_ibuf, M_USBDEV); + sc->aue_cdata.ue_ibuf = NULL; +#endif + + sc->aue_link = 0; + + ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); + + return; +} + +/* + * Stop all chip I/O so that the kernel's probe routines don't + * get confused by errant DMAs when rebooting. + */ +static int +aue_shutdown(device_t dev) +{ + struct aue_softc *sc; + + sc = device_get_softc(dev); + AUE_SXLOCK(sc); + sc->aue_dying++; + aue_reset(sc); + aue_stop(sc); + AUE_SXUNLOCK(sc); + + return (0); +} + +static void +aue_task_sched(struct aue_softc *sc, int task) +{ + + AUE_LOCK(sc); + sc->aue_deferedtasks |= task; + usb_ether_task_enqueue(&sc->aue_taskqueue, &sc->aue_task); + AUE_UNLOCK(sc); +} + +/* + * We defer all interrupt operations to this function. + * + * This allows us to do more complex operations, such as synchronous + * usb io that normally would not be allowed from interrupt context. + */ +static void +aue_task(void *arg, int pending) +{ + struct aue_softc *sc = arg; + int tasks; + + for ( ;; ) { + AUE_LOCK(sc); + tasks = sc->aue_deferedtasks; + sc->aue_deferedtasks = 0; + AUE_UNLOCK(sc); + + if (tasks == 0) + break; + + AUE_GIANTLOCK(); // XXX: usb not giant safe + AUE_SXLOCK(sc); + if (sc->aue_dying) { + AUE_SXUNLOCK(sc); + break; + } + if ((tasks & AUE_TASK_TICK) != 0) { + aue_tick_thread(sc); + } + if ((tasks & AUE_TASK_START) != 0) { + aue_start_thread(sc); + } + if ((tasks & AUE_TASK_RXSTART) != 0) { + aue_rxstart_thread(sc); + } + if ((tasks & AUE_TASK_RXEOF) != 0) { + aue_rxeof_thread(sc); + } + if ((tasks & AUE_TASK_TXEOF) != 0) { + aue_txeof_thread(sc); + } + AUE_SXUNLOCK(sc); + AUE_GIANTUNLOCK(); // XXX: usb not giant safe + } +} + diff --git a/sys/legacy/dev/usb/if_auereg.h b/sys/legacy/dev/usb/if_auereg.h new file mode 100644 index 0000000..18cc0f4 --- /dev/null +++ b/sys/legacy/dev/usb/if_auereg.h @@ -0,0 +1,294 @@ +/*- + * Copyright (c) 1997, 1998, 1999 + * Bill Paul <wpaul@ee.columbia.edu>. All rights reserved. + * + * Copyright (c) 2006 + * Alfred Perlstein <alfred@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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * Register definitions for ADMtek Pegasus AN986 USB to Ethernet + * chip. The Pegasus uses a total of four USB endpoints: the control + * endpoint (0), a bulk read endpoint for receiving packets (1), + * a bulk write endpoint for sending packets (2) and an interrupt + * endpoint for passing RX and TX status (3). Endpoint 0 is used + * to read and write the ethernet module's registers. All registers + * are 8 bits wide. + * + * Packet transfer is done in 64 byte chunks. The last chunk in a + * transfer is denoted by having a length less that 64 bytes. For + * the RX case, the data includes an optional RX status word. + */ + +#ifndef AUEREG_H +#define AUEREG_H + +#define AUE_UR_READREG 0xF0 +#define AUE_UR_WRITEREG 0xF1 + +#define AUE_CONFIG_NO 1 +#define AUE_IFACE_IDX 0 + +/* + * Note that while the ADMtek technically has four + * endpoints, the control endpoint (endpoint 0) is + * regarded as special by the USB code and drivers + * don't have direct access to it. (We access it + * using usbd_do_request() when reading/writing + * registers.) Consequently, our endpoint indexes + * don't match those in the ADMtek Pegasus manual: + * we consider the RX data endpoint to be index 0 + * and work up from there. + */ +#define AUE_ENDPT_RX 0x0 +#define AUE_ENDPT_TX 0x1 +#define AUE_ENDPT_INTR 0x2 +#define AUE_ENDPT_MAX 0x3 + +#define AUE_INTR_PKTLEN 0x8 + +#define AUE_CTL0 0x00 +#define AUE_CTL1 0x01 +#define AUE_CTL2 0x02 +#define AUE_MAR0 0x08 +#define AUE_MAR1 0x09 +#define AUE_MAR2 0x0A +#define AUE_MAR3 0x0B +#define AUE_MAR4 0x0C +#define AUE_MAR5 0x0D +#define AUE_MAR6 0x0E +#define AUE_MAR7 0x0F +#define AUE_MAR AUE_MAR0 +#define AUE_PAR0 0x10 +#define AUE_PAR1 0x11 +#define AUE_PAR2 0x12 +#define AUE_PAR3 0x13 +#define AUE_PAR4 0x14 +#define AUE_PAR5 0x15 +#define AUE_PAR AUE_PAR0 +#define AUE_PAUSE0 0x18 +#define AUE_PAUSE1 0x19 +#define AUE_PAUSE AUE_PAUSE0 +#define AUE_RX_FLOWCTL_CNT 0x1A +#define AUE_RX_FLOWCTL_FIFO 0x1B +#define AUE_REG_1D 0x1D +#define AUE_EE_REG 0x20 +#define AUE_EE_DATA0 0x21 +#define AUE_EE_DATA1 0x22 +#define AUE_EE_DATA AUE_EE_DATA0 +#define AUE_EE_CTL 0x23 +#define AUE_PHY_ADDR 0x25 +#define AUE_PHY_DATA0 0x26 +#define AUE_PHY_DATA1 0x27 +#define AUE_PHY_DATA AUE_PHY_DATA0 +#define AUE_PHY_CTL 0x28 +#define AUE_USB_STS 0x2A +#define AUE_TXSTAT0 0x2B +#define AUE_TXSTAT1 0x2C +#define AUE_TXSTAT AUE_TXSTAT0 +#define AUE_RXSTAT 0x2D +#define AUE_PKTLOST0 0x2E +#define AUE_PKTLOST1 0x2F +#define AUE_PKTLOST AUE_PKTLOST0 + +#define AUE_REG_7B 0x7B +#define AUE_GPIO0 0x7E +#define AUE_GPIO1 0x7F +#define AUE_REG_81 0x81 + +#define AUE_CTL0_INCLUDE_RXCRC 0x01 +#define AUE_CTL0_ALLMULTI 0x02 +#define AUE_CTL0_STOP_BACKOFF 0x04 +#define AUE_CTL0_RXSTAT_APPEND 0x08 +#define AUE_CTL0_WAKEON_ENB 0x10 +#define AUE_CTL0_RXPAUSE_ENB 0x20 +#define AUE_CTL0_RX_ENB 0x40 +#define AUE_CTL0_TX_ENB 0x80 + +#define AUE_CTL1_HOMELAN 0x04 +#define AUE_CTL1_RESETMAC 0x08 +#define AUE_CTL1_SPEEDSEL 0x10 /* 0 = 10mbps, 1 = 100mbps */ +#define AUE_CTL1_DUPLEX 0x20 /* 0 = half, 1 = full */ +#define AUE_CTL1_DELAYHOME 0x40 + +#define AUE_CTL2_EP3_CLR 0x01 /* reading EP3 clrs status regs */ +#define AUE_CTL2_RX_BADFRAMES 0x02 +#define AUE_CTL2_RX_PROMISC 0x04 +#define AUE_CTL2_LOOPBACK 0x08 +#define AUE_CTL2_EEPROMWR_ENB 0x10 +#define AUE_CTL2_EEPROM_LOAD 0x20 + +#define AUE_EECTL_WRITE 0x01 +#define AUE_EECTL_READ 0x02 +#define AUE_EECTL_DONE 0x04 + +#define AUE_PHYCTL_PHYREG 0x1F +#define AUE_PHYCTL_WRITE 0x20 +#define AUE_PHYCTL_READ 0x40 +#define AUE_PHYCTL_DONE 0x80 + +#define AUE_USBSTS_SUSPEND 0x01 +#define AUE_USBSTS_RESUME 0x02 + +#define AUE_TXSTAT0_JABTIMO 0x04 +#define AUE_TXSTAT0_CARLOSS 0x08 +#define AUE_TXSTAT0_NOCARRIER 0x10 +#define AUE_TXSTAT0_LATECOLL 0x20 +#define AUE_TXSTAT0_EXCESSCOLL 0x40 +#define AUE_TXSTAT0_UNDERRUN 0x80 + +#define AUE_TXSTAT1_PKTCNT 0x0F +#define AUE_TXSTAT1_FIFO_EMPTY 0x40 +#define AUE_TXSTAT1_FIFO_FULL 0x80 + +#define AUE_RXSTAT_OVERRUN 0x01 +#define AUE_RXSTAT_PAUSE 0x02 + +#define AUE_GPIO_IN0 0x01 +#define AUE_GPIO_OUT0 0x02 +#define AUE_GPIO_SEL0 0x04 +#define AUE_GPIO_IN1 0x08 +#define AUE_GPIO_OUT1 0x10 +#define AUE_GPIO_SEL1 0x20 + +struct aue_intrpkt { + u_int8_t aue_txstat0; + u_int8_t aue_txstat1; + u_int8_t aue_rxstat; + u_int8_t aue_rxlostpkt0; + u_int8_t aue_rxlostpkt1; + u_int8_t aue_wakeupstat; + u_int8_t aue_rsvd; +}; + +struct aue_rxpkt { + u_int16_t aue_pktlen; + u_int8_t aue_rxstat; +}; + +#define AUE_RXSTAT_MCAST 0x01 +#define AUE_RXSTAT_GIANT 0x02 +#define AUE_RXSTAT_RUNT 0x04 +#define AUE_RXSTAT_CRCERR 0x08 +#define AUE_RXSTAT_DRIBBLE 0x10 +#define AUE_RXSTAT_MASK 0x1E + +#define AUE_INC(x, y) (x) = (x + 1) % y + +struct aue_softc { +#if defined(__FreeBSD__) +#define GET_MII(sc) (device_get_softc((sc)->aue_miibus)) +#elif defined(__NetBSD__) +#define GET_MII(sc) (&(sc)->aue_mii) +#elif defined(__OpenBSD__) +#define GET_MII(sc) (&(sc)->aue_mii) +#endif + struct ifnet *aue_ifp; + device_t aue_dev; + device_t aue_miibus; + usbd_device_handle aue_udev; + usbd_interface_handle aue_iface; + u_int16_t aue_vendor; + u_int16_t aue_product; + int aue_ed[AUE_ENDPT_MAX]; + usbd_pipe_handle aue_ep[AUE_ENDPT_MAX]; + int aue_unit; + u_int8_t aue_link; + int aue_timer; + int aue_if_flags; + struct ue_cdata aue_cdata; + struct callout aue_tick_callout; + struct usb_taskqueue aue_taskqueue; + struct task aue_task; + struct mtx aue_mtx; + struct sx aue_sx; + u_int16_t aue_flags; + char aue_dying; + struct timeval aue_rx_notice; + struct usb_qdat aue_qdat; + int aue_deferedtasks; +}; + +#if 0 +/* + * Some debug code to make sure we don't take a blocking lock in + * interrupt context. + */ +#include <sys/types.h> +#include <sys/proc.h> +#include <sys/kdb.h> + +#define AUE_DUMPSTATE(tag) aue_dumpstate(__func__, tag) + +static inline void +aue_dumpstate(const char *func, const char *tag) +{ + if ((curthread->td_pflags & TDP_NOSLEEPING) || + (curthread->td_pflags & TDP_ITHREAD)) { + kdb_backtrace(); + printf("%s: %s sleep: %sok ithread: %s\n", func, tag, + curthread->td_pflags & TDP_NOSLEEPING ? "not" : "", + curthread->td_pflags & TDP_ITHREAD ? "yes" : "no"); + } +} +#else +#define AUE_DUMPSTATE(tag) +#endif + +#define AUE_LOCK(_sc) mtx_lock(&(_sc)->aue_mtx) +#define AUE_UNLOCK(_sc) mtx_unlock(&(_sc)->aue_mtx) +#define AUE_SXLOCK(_sc) \ + do { AUE_DUMPSTATE("sxlock"); sx_xlock(&(_sc)->aue_sx); } while(0) +#define AUE_SXUNLOCK(_sc) sx_xunlock(&(_sc)->aue_sx) +#define AUE_SXASSERTLOCKED(_sc) sx_assert(&(_sc)->aue_sx, SX_XLOCKED) +#define AUE_SXASSERTUNLOCKED(_sc) sx_assert(&(_sc)->aue_sx, SX_UNLOCKED) + +#define AUE_TIMEOUT 1000 +#define AUE_MIN_FRAMELEN 60 +#define AUE_INTR_INTERVAL 100 /* ms */ + +/* + * These bits are used to notify the task about pending events. + * The names correspond to the interrupt context routines that would + * be normally called. (example: AUE_TASK_WATCHDOG -> aue_watchdog()) + */ +#define AUE_TASK_WATCHDOG 0x0001 +#define AUE_TASK_TICK 0x0002 +#define AUE_TASK_START 0x0004 +#define AUE_TASK_RXSTART 0x0008 +#define AUE_TASK_RXEOF 0x0010 +#define AUE_TASK_TXEOF 0x0020 + +#define AUE_GIANTLOCK() mtx_lock(&Giant); +#define AUE_GIANTUNLOCK() mtx_unlock(&Giant); + +#endif /* !AUEREG_H */ diff --git a/sys/legacy/dev/usb/if_axe.c b/sys/legacy/dev/usb/if_axe.c new file mode 100644 index 0000000..65da32b --- /dev/null +++ b/sys/legacy/dev/usb/if_axe.c @@ -0,0 +1,1428 @@ +/*- + * Copyright (c) 1997, 1998, 1999, 2000-2003 + * Bill Paul <wpaul@windriver.com>. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * ASIX Electronics AX88172/AX88178/AX88778 USB 2.0 ethernet driver. + * Used in the LinkSys USB200M and various other adapters. + * + * Manuals available from: + * http://www.asix.com.tw/datasheet/mac/Ax88172.PDF + * Note: you need the manual for the AX88170 chip (USB 1.x ethernet + * controller) to find the definitions for the RX control register. + * http://www.asix.com.tw/datasheet/mac/Ax88170.PDF + * + * Written by Bill Paul <wpaul@windriver.com> + * Senior Engineer + * Wind River Systems + */ + +/* + * The AX88172 provides USB ethernet supports at 10 and 100Mbps. + * It uses an external PHY (reference designs use a RealTek chip), + * and has a 64-bit multicast hash filter. There is some information + * missing from the manual which one needs to know in order to make + * the chip function: + * + * - You must set bit 7 in the RX control register, otherwise the + * chip won't receive any packets. + * - You must initialize all 3 IPG registers, or you won't be able + * to send any packets. + * + * Note that this device appears to only support loading the station + * address via autload from the EEPROM (i.e. there's no way to manaully + * set it). + * + * (Adam Weinberger wanted me to name this driver if_gir.c.) + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/endian.h> +#include <sys/sockio.h> +#include <sys/mbuf.h> +#include <sys/malloc.h> +#include <sys/lock.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/socket.h> +#include <sys/sx.h> + +#include <net/if.h> +#include <net/if_arp.h> +#include <net/ethernet.h> +#include <net/if_dl.h> +#include <net/if_media.h> +#include <net/if_types.h> + +#include <net/bpf.h> + +#include <sys/bus.h> +#include <machine/bus.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usbdivar.h> +#include "usbdevs.h" +#include <dev/usb/usb_ethersubr.h> + +#include <dev/mii/mii.h> +#include <dev/mii/miivar.h> + +/* "device miibus" required. See GENERIC if you get errors here. */ +#include "miibus_if.h" + +/* + * AXE_178_MAX_FRAME_BURST + * max frame burst size for Ax88178 and Ax88772 + * 0 2048 bytes + * 1 4096 bytes + * 2 8192 bytes + * 3 16384 bytes + * use the largest your system can handle without usb stalling. + * + * NB: 88772 parts appear to generate lots of input errors with + * a 2K rx buffer and 8K is only slightly faster than 4K on an + * EHCI port on a T42 so change at your own risk. + */ +#define AXE_178_MAX_FRAME_BURST 1 + +#include <dev/usb/if_axereg.h> + +/* + * Various supported device vendors/products. + */ +const struct axe_type axe_devs[] = { + { { USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_UF200}, 0 }, + { { USB_VENDOR_ACERCM, USB_PRODUCT_ACERCM_EP1427X2}, 0 }, + { { USB_VENDOR_APPLE, USB_PRODUCT_APPLE_ETHERNET}, AX772 }, + { { USB_VENDOR_ASIX, USB_PRODUCT_ASIX_AX88172}, 0 }, + { { USB_VENDOR_ASIX, USB_PRODUCT_ASIX_AX88772}, AX772 }, + { { USB_VENDOR_ASIX, USB_PRODUCT_ASIX_AX88178}, AX178 }, + { { USB_VENDOR_ATEN, USB_PRODUCT_ATEN_UC210T}, 0 }, + { { USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5D5055 }, AX178 }, + { { USB_VENDOR_BILLIONTON, USB_PRODUCT_BILLIONTON_USB2AR}, 0}, + { { USB_VENDOR_CISCOLINKSYS, USB_PRODUCT_CISCOLINKSYS_USB200MV2}, AX772 }, + { { USB_VENDOR_COREGA, USB_PRODUCT_COREGA_FETHER_USB2_TX }, 0}, + { { USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DUBE100}, 0 }, + { { USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DUBE100B1 }, AX772 }, + { { USB_VENDOR_GOODWAY, USB_PRODUCT_GOODWAY_GWUSB2E}, 0 }, + { { USB_VENDOR_IODATA, USB_PRODUCT_IODATA_ETGUS2 }, AX178 }, + { { USB_VENDOR_JVC, USB_PRODUCT_JVC_MP_PRX1}, 0 }, + { { USB_VENDOR_LINKSYS2, USB_PRODUCT_LINKSYS2_USB200M}, 0 }, + { { USB_VENDOR_LINKSYS4, USB_PRODUCT_LINKSYS4_USB1000 }, AX178 }, + { { USB_VENDOR_MELCO, USB_PRODUCT_MELCO_LUAU2KTX}, 0 }, + { { USB_VENDOR_NETGEAR, USB_PRODUCT_NETGEAR_FA120}, 0 }, + { { USB_VENDOR_OQO, USB_PRODUCT_OQO_ETHER01PLUS }, AX772 }, + { { USB_VENDOR_PLANEX3, USB_PRODUCT_PLANEX3_GU1000T }, AX178 }, + { { USB_VENDOR_SYSTEMTALKS, USB_PRODUCT_SYSTEMTALKS_SGCX2UL}, 0 }, + { { USB_VENDOR_SITECOM, USB_PRODUCT_SITECOM_LN029}, 0 }, + { { USB_VENDOR_SITECOMEU, USB_PRODUCT_SITECOMEU_LN028 }, AX178 } +}; + +#define axe_lookup(v, p) ((const struct axe_type *)usb_lookup(axe_devs, v, p)) + +static device_probe_t axe_match; +static device_attach_t axe_attach; +static device_detach_t axe_detach; +static device_shutdown_t axe_shutdown; +static miibus_readreg_t axe_miibus_readreg; +static miibus_writereg_t axe_miibus_writereg; +static miibus_statchg_t axe_miibus_statchg; + +static int axe_encap(struct axe_softc *, struct mbuf *, int); +static void axe_rxeof(usbd_xfer_handle, usbd_private_handle, usbd_status); +static void axe_txeof(usbd_xfer_handle, usbd_private_handle, usbd_status); +static void axe_tick(void *); +static void axe_tick_task(void *); +static void axe_start(struct ifnet *); +static int axe_ioctl(struct ifnet *, u_long, caddr_t); +static void axe_init(void *); +static void axe_stop(struct axe_softc *); +static void axe_watchdog(struct ifnet *); +static int axe_cmd(struct axe_softc *, int, int, int, void *); +static int axe_ifmedia_upd(struct ifnet *); +static void axe_ifmedia_sts(struct ifnet *, struct ifmediareq *); + +static void axe_setmulti(struct axe_softc *); + +static device_method_t axe_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, axe_match), + DEVMETHOD(device_attach, axe_attach), + DEVMETHOD(device_detach, axe_detach), + DEVMETHOD(device_shutdown, axe_shutdown), + + /* bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + DEVMETHOD(bus_driver_added, bus_generic_driver_added), + + /* MII interface */ + DEVMETHOD(miibus_readreg, axe_miibus_readreg), + DEVMETHOD(miibus_writereg, axe_miibus_writereg), + DEVMETHOD(miibus_statchg, axe_miibus_statchg), + + { 0, 0 } +}; + +static driver_t axe_driver = { + "axe", + axe_methods, + sizeof(struct axe_softc) +}; + +static devclass_t axe_devclass; + +DRIVER_MODULE(axe, uhub, axe_driver, axe_devclass, usbd_driver_load, 0); +DRIVER_MODULE(miibus, axe, miibus_driver, miibus_devclass, 0, 0); +MODULE_DEPEND(axe, usb, 1, 1, 1); +MODULE_DEPEND(axe, miibus, 1, 1, 1); + +static int +axe_cmd(struct axe_softc *sc, int cmd, int index, int val, void *buf) +{ + usb_device_request_t req; + usbd_status err; + + AXE_SLEEPLOCKASSERT(sc); + if (sc->axe_dying) + return(0); + + if (AXE_CMD_DIR(cmd)) + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + else + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = AXE_CMD_CMD(cmd); + USETW(req.wValue, val); + USETW(req.wIndex, index); + USETW(req.wLength, AXE_CMD_LEN(cmd)); + + err = usbd_do_request(sc->axe_udev, &req, buf); + + if (err) + return(-1); + + return(0); +} + +static int +axe_miibus_readreg(device_t dev, int phy, int reg) +{ + struct axe_softc *sc = device_get_softc(dev); + usbd_status err; + u_int16_t val; + + if (sc->axe_dying) + return(0); + + AXE_SLEEPLOCKASSERT(sc); +#ifdef notdef + /* + * The chip tells us the MII address of any supported + * PHYs attached to the chip, so only read from those. + */ + + if (sc->axe_phyaddrs[0] != AXE_NOPHY && phy != sc->axe_phyaddrs[0]) + return (0); + + if (sc->axe_phyaddrs[1] != AXE_NOPHY && phy != sc->axe_phyaddrs[1]) + return (0); +#endif + if (sc->axe_phyaddrs[0] != 0xFF && sc->axe_phyaddrs[0] != phy) + return (0); + + AXE_LOCK(sc); + axe_cmd(sc, AXE_CMD_MII_OPMODE_SW, 0, 0, NULL); + err = axe_cmd(sc, AXE_CMD_MII_READ_REG, reg, phy, (void *)&val); + axe_cmd(sc, AXE_CMD_MII_OPMODE_HW, 0, 0, NULL); + AXE_UNLOCK(sc); + + if (err) { + device_printf(sc->axe_dev, "read PHY failed\n"); + return(-1); + } + + if (val && val != 0xffff) + sc->axe_phyaddrs[0] = phy; + + return (le16toh(val)); +} + +static int +axe_miibus_writereg(device_t dev, int phy, int reg, int val) +{ + struct axe_softc *sc = device_get_softc(dev); + usbd_status err; + + if (sc->axe_dying) + return(0); + + AXE_SLEEPLOCKASSERT(sc); + AXE_LOCK(sc); + axe_cmd(sc, AXE_CMD_MII_OPMODE_SW, 0, 0, NULL); + val = htole32(val); + err = axe_cmd(sc, AXE_CMD_MII_WRITE_REG, reg, phy, (void *)&val); + axe_cmd(sc, AXE_CMD_MII_OPMODE_HW, 0, 0, NULL); + AXE_UNLOCK(sc); + + if (err) { + device_printf(sc->axe_dev, "write PHY failed\n"); + return(-1); + } + + return (0); +} + +static void +axe_miibus_statchg(device_t dev) +{ + struct axe_softc *sc = device_get_softc(dev); + struct mii_data *mii = GET_MII(sc); + int val, err; + + val = (mii->mii_media_active & IFM_GMASK) == IFM_FDX ? + AXE_MEDIA_FULL_DUPLEX : 0; + if (sc->axe_flags & (AX178|AX772)) { + val |= AXE_178_MEDIA_RX_EN | AXE_178_MEDIA_MAGIC; + + switch (IFM_SUBTYPE(mii->mii_media_active)) { + case IFM_1000_T: + val |= AXE_178_MEDIA_GMII | AXE_178_MEDIA_ENCK; + break; + case IFM_100_TX: + val |= AXE_178_MEDIA_100TX; + break; + case IFM_10_T: + /* doesn't need to be handled */ + break; + } + } + err = axe_cmd(sc, AXE_CMD_WRITE_MEDIA, 0, val, NULL); + if (err) + device_printf(dev, "media change failed, error %d\n", err); +} + +/* + * Set media options. + */ +static int +axe_ifmedia_upd(struct ifnet *ifp) +{ + struct axe_softc *sc = ifp->if_softc; + struct mii_data *mii = GET_MII(sc); + + sc->axe_link = 0; + if (mii->mii_instance) { + struct mii_softc *miisc; + LIST_FOREACH(miisc, &mii->mii_phys, mii_list) + mii_phy_reset(miisc); + } + mii_mediachg(mii); + + return (0); +} + +/* + * Report current media status. + */ +static void +axe_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr) +{ + struct axe_softc *sc = ifp->if_softc; + struct mii_data *mii = GET_MII(sc); + + mii_pollstat(mii); + ifmr->ifm_active = mii->mii_media_active; + ifmr->ifm_status = mii->mii_media_status; + + return; +} + +static void +axe_setmulti(struct axe_softc *sc) +{ + struct ifnet *ifp; + struct ifmultiaddr *ifma; + u_int32_t h = 0; + u_int16_t rxmode; + u_int8_t hashtbl[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + + ifp = sc->axe_ifp; + + AXE_LOCK(sc); + axe_cmd(sc, AXE_CMD_RXCTL_READ, 0, 0, (void *)&rxmode); + rxmode = le16toh(rxmode); + + if (ifp->if_flags & IFF_ALLMULTI || ifp->if_flags & IFF_PROMISC) { + rxmode |= AXE_RXCMD_ALLMULTI; + axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, rxmode, NULL); + AXE_UNLOCK(sc); + return; + } else + rxmode &= ~AXE_RXCMD_ALLMULTI; + + IF_ADDR_LOCK(ifp); + TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) + { + if (ifma->ifma_addr->sa_family != AF_LINK) + continue; + h = ether_crc32_be(LLADDR((struct sockaddr_dl *) + ifma->ifma_addr), ETHER_ADDR_LEN) >> 26; + hashtbl[h / 8] |= 1 << (h % 8); + } + IF_ADDR_UNLOCK(ifp); + + axe_cmd(sc, AXE_CMD_WRITE_MCAST, 0, 0, (void *)&hashtbl); + axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, rxmode, NULL); + AXE_UNLOCK(sc); + + return; +} + +static void +axe_ax88178_init(struct axe_softc *sc) +{ + int gpio0 = 0, phymode = 0; + u_int16_t eeprom; + + axe_cmd(sc, AXE_CMD_SROM_WR_ENABLE, 0, 0, NULL); + /* XXX magic */ + axe_cmd(sc, AXE_CMD_SROM_READ, 0, 0x0017, &eeprom); + eeprom = le16toh(eeprom); + axe_cmd(sc, AXE_CMD_SROM_WR_DISABLE, 0, 0, NULL); + + /* if EEPROM is invalid we have to use to GPIO0 */ + if (eeprom == 0xffff) { + phymode = 0; + gpio0 = 1; + } else { + phymode = eeprom & 7; + gpio0 = (eeprom & 0x80) ? 0 : 1; + } + + axe_cmd(sc, AXE_CMD_WRITE_GPIO, 0, 0x008c, NULL); + usbd_delay_ms(sc->axe_udev, 40); + if ((eeprom >> 8) != 1) { + axe_cmd(sc, AXE_CMD_WRITE_GPIO, 0, 0x003c, NULL); + usbd_delay_ms(sc->axe_udev, 30); + + axe_cmd(sc, AXE_CMD_WRITE_GPIO, 0, 0x001c, NULL); + usbd_delay_ms(sc->axe_udev, 300); + + axe_cmd(sc, AXE_CMD_WRITE_GPIO, 0, 0x003c, NULL); + usbd_delay_ms(sc->axe_udev, 30); + } else { + axe_cmd(sc, AXE_CMD_WRITE_GPIO, 0, 0x0004, NULL); + usbd_delay_ms(sc->axe_udev, 30); + axe_cmd(sc, AXE_CMD_WRITE_GPIO, 0, 0x000c, NULL); + usbd_delay_ms(sc->axe_udev, 30); + } + + /* soft reset */ + axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, 0, NULL); + usbd_delay_ms(sc->axe_udev, 150); + axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, + AXE_SW_RESET_PRL | AXE_178_RESET_MAGIC, NULL); + usbd_delay_ms(sc->axe_udev, 150); + axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, 0, NULL); +} + +static void +axe_ax88772_init(struct axe_softc *sc) +{ + axe_cmd(sc, AXE_CMD_WRITE_GPIO, 0, 0x00b0, NULL); + usbd_delay_ms(sc->axe_udev, 40); + + if (sc->axe_phyaddrs[1] == AXE_INTPHY) { + /* ask for embedded PHY */ + axe_cmd(sc, AXE_CMD_SW_PHY_SELECT, 0, 0x01, NULL); + usbd_delay_ms(sc->axe_udev, 10); + + /* power down and reset state, pin reset state */ + axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, AXE_SW_RESET_CLEAR, NULL); + usbd_delay_ms(sc->axe_udev, 60); + + /* power down/reset state, pin operating state */ + axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, + AXE_SW_RESET_IPPD | AXE_SW_RESET_PRL, NULL); + usbd_delay_ms(sc->axe_udev, 150); + + /* power up, reset */ + axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, AXE_SW_RESET_PRL, NULL); + + /* power up, operating */ + axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, + AXE_SW_RESET_IPRL | AXE_SW_RESET_PRL, NULL); + } else { + /* ask for external PHY */ + axe_cmd(sc, AXE_CMD_SW_PHY_SELECT, 0, 0x00, NULL); + usbd_delay_ms(sc->axe_udev, 10); + + /* power down/reset state, pin operating state */ + axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, + AXE_SW_RESET_IPPD | AXE_SW_RESET_PRL, NULL); + } + + usbd_delay_ms(sc->axe_udev, 150); + axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, 0, NULL); +} + +static void +axe_reset(struct axe_softc *sc) +{ + if (sc->axe_dying) + return; + + if (usbd_set_config_no(sc->axe_udev, AXE_CONFIG_NO, 1) || + usbd_device2interface_handle(sc->axe_udev, AXE_IFACE_IDX, + &sc->axe_iface)) { + device_printf(sc->axe_dev, "getting interface handle failed\n"); + } + + /* Wait a little while for the chip to get its brains in order. */ + DELAY(1000); + return; +} + +/* + * Probe for a AX88172 chip. + */ +static int +axe_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + + if (!uaa->iface) + return(UMATCH_NONE); + return (axe_lookup(uaa->vendor, uaa->product) != NULL ? + UMATCH_VENDOR_PRODUCT : UMATCH_NONE); +} + +/* + * Attach the interface. Allocate softc structures, do ifmedia + * setup and ethernet/BPF attach. + */ +static int +axe_attach(device_t self) +{ + struct axe_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + const struct axe_type *type; + u_char eaddr[ETHER_ADDR_LEN]; + struct ifnet *ifp; + usb_interface_descriptor_t *id; + usb_endpoint_descriptor_t *ed; + int i; + + sc->axe_udev = uaa->device; + sc->axe_dev = self; + type = axe_lookup(uaa->vendor, uaa->product); + if (type != NULL) + sc->axe_flags = type->axe_flags; + + if (usbd_set_config_no(sc->axe_udev, AXE_CONFIG_NO, 1)) { + device_printf(sc->axe_dev, "getting interface handle failed\n"); + return ENXIO; + } + + usb_init_task(&sc->axe_tick_task, axe_tick_task, sc); + + if (usbd_device2interface_handle(uaa->device, + AXE_IFACE_IDX, &sc->axe_iface)) { + device_printf(sc->axe_dev, "getting interface handle failed\n"); + return ENXIO; + } + + sc->axe_boundary = 64; + if (sc->axe_flags & (AX178|AX772)) { + if (sc->axe_udev->speed == USB_SPEED_HIGH) { + sc->axe_bufsz = AXE_178_MAX_BUFSZ; + sc->axe_boundary = 512; + } else + sc->axe_bufsz = AXE_178_MIN_BUFSZ; + } else + sc->axe_bufsz = AXE_172_BUFSZ; +{ /* XXX debug */ +device_printf(sc->axe_dev, "%s, bufsz %d, boundary %d\n", + sc->axe_flags & AX178 ? "AX88178" : + sc->axe_flags & AX772 ? "AX88772" : "AX88172", + sc->axe_bufsz, sc->axe_boundary); +} + + id = usbd_get_interface_descriptor(sc->axe_iface); + + /* Find endpoints. */ + for (i = 0; i < id->bNumEndpoints; i++) { + ed = usbd_interface2endpoint_descriptor(sc->axe_iface, i); + if (!ed) { + device_printf(sc->axe_dev, "couldn't get ep %d\n", i); + return ENXIO; + } + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + sc->axe_ed[AXE_ENDPT_RX] = ed->bEndpointAddress; + } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + sc->axe_ed[AXE_ENDPT_TX] = ed->bEndpointAddress; + } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_INTERRUPT) { + sc->axe_ed[AXE_ENDPT_INTR] = ed->bEndpointAddress; + } + } + + mtx_init(&sc->axe_mtx, device_get_nameunit(self), MTX_NETWORK_LOCK, + MTX_DEF | MTX_RECURSE); + sx_init(&sc->axe_sleeplock, device_get_nameunit(self)); + AXE_SLEEPLOCK(sc); + AXE_LOCK(sc); + + /* We need the PHYID for the init dance in some cases */ + axe_cmd(sc, AXE_CMD_READ_PHYID, 0, 0, (void *)&sc->axe_phyaddrs); + + if (sc->axe_flags & AX178) + axe_ax88178_init(sc); + else if (sc->axe_flags & AX772) + axe_ax88772_init(sc); + + /* + * Get station address. + */ + if (sc->axe_flags & (AX178|AX772)) + axe_cmd(sc, AXE_178_CMD_READ_NODEID, 0, 0, &eaddr); + else + axe_cmd(sc, AXE_172_CMD_READ_NODEID, 0, 0, &eaddr); + + /* + * Fetch IPG values. + */ + axe_cmd(sc, AXE_CMD_READ_IPG012, 0, 0, (void *)&sc->axe_ipgs); + + /* + * Work around broken adapters that appear to lie about + * their PHY addresses. + */ + sc->axe_phyaddrs[0] = sc->axe_phyaddrs[1] = 0xFF; + + ifp = sc->axe_ifp = if_alloc(IFT_ETHER); + if (ifp == NULL) { + device_printf(sc->axe_dev, "can not if_alloc()\n"); + AXE_UNLOCK(sc); + AXE_SLEEPUNLOCK(sc); + sx_destroy(&sc->axe_sleeplock); + mtx_destroy(&sc->axe_mtx); + return ENXIO; + } + ifp->if_softc = sc; + if_initname(ifp, "axe", device_get_unit(sc->axe_dev)); + ifp->if_mtu = ETHERMTU; + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST | + IFF_NEEDSGIANT; + ifp->if_ioctl = axe_ioctl; + ifp->if_start = axe_start; + ifp->if_watchdog = axe_watchdog; + ifp->if_init = axe_init; + IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN); + ifp->if_snd.ifq_drv_maxlen = IFQ_MAXLEN; + IFQ_SET_READY(&ifp->if_snd); + + if (mii_phy_probe(self, &sc->axe_miibus, + axe_ifmedia_upd, axe_ifmedia_sts)) { + device_printf(sc->axe_dev, "MII without any PHY!\n"); + if_free(ifp); + AXE_UNLOCK(sc); + AXE_SLEEPUNLOCK(sc); + sx_destroy(&sc->axe_sleeplock); + mtx_destroy(&sc->axe_mtx); + return ENXIO; + } + + /* + * Call MI attach routine. + */ + + ether_ifattach(ifp, eaddr); + callout_handle_init(&sc->axe_stat_ch); + usb_register_netisr(); + + sc->axe_dying = 0; + + AXE_UNLOCK(sc); + AXE_SLEEPUNLOCK(sc); + + return 0; +} + +static int +axe_detach(device_t dev) +{ + struct axe_softc *sc; + struct ifnet *ifp; + + sc = device_get_softc(dev); + AXE_LOCK(sc); + ifp = sc->axe_ifp; + + sc->axe_dying = 1; + untimeout(axe_tick, sc, sc->axe_stat_ch); + usb_rem_task(sc->axe_udev, &sc->axe_tick_task); + + ether_ifdetach(ifp); + if_free(ifp); + + if (sc->axe_ep[AXE_ENDPT_TX] != NULL) + usbd_abort_pipe(sc->axe_ep[AXE_ENDPT_TX]); + if (sc->axe_ep[AXE_ENDPT_RX] != NULL) + usbd_abort_pipe(sc->axe_ep[AXE_ENDPT_RX]); + if (sc->axe_ep[AXE_ENDPT_INTR] != NULL) + usbd_abort_pipe(sc->axe_ep[AXE_ENDPT_INTR]); + + AXE_UNLOCK(sc); + sx_destroy(&sc->axe_sleeplock); + mtx_destroy(&sc->axe_mtx); + + return(0); +} + +static int +axe_rx_list_init(struct axe_softc *sc) +{ + struct axe_cdata *cd; + struct axe_chain *c; + int i; + + cd = &sc->axe_cdata; + for (i = 0; i < AXE_RX_LIST_CNT; i++) { + c = &cd->axe_rx_chain[i]; + c->axe_sc = sc; + c->axe_idx = i; + c->axe_mbuf = NULL; + if (c->axe_xfer == NULL) { + c->axe_xfer = usbd_alloc_xfer(sc->axe_udev); + if (c->axe_xfer == NULL) + return (ENOBUFS); + c->axe_buf = usbd_alloc_buffer(c->axe_xfer, + sc->axe_bufsz); + if (c->axe_buf == NULL) { + usbd_free_xfer(c->axe_xfer); + return (ENOBUFS); + } + } + } + + return (0); +} + +static void +axe_rx_list_free(struct axe_softc *sc) +{ + int i; + + for (i = 0; i < AXE_RX_LIST_CNT; i++) { + if (sc->axe_cdata.axe_rx_chain[i].axe_mbuf != NULL) { + m_freem(sc->axe_cdata.axe_rx_chain[i].axe_mbuf); + sc->axe_cdata.axe_rx_chain[i].axe_mbuf = NULL; + } + if (sc->axe_cdata.axe_rx_chain[i].axe_xfer != NULL) { + usbd_free_xfer(sc->axe_cdata.axe_rx_chain[i].axe_xfer); + sc->axe_cdata.axe_rx_chain[i].axe_xfer = NULL; + } + } +} + +static int +axe_tx_list_init(struct axe_softc *sc) +{ + struct axe_cdata *cd; + struct axe_chain *c; + int i; + + cd = &sc->axe_cdata; + for (i = 0; i < AXE_TX_LIST_CNT; i++) { + c = &cd->axe_tx_chain[i]; + c->axe_sc = sc; + c->axe_idx = i; + c->axe_mbuf = NULL; + if (c->axe_xfer == NULL) { + c->axe_xfer = usbd_alloc_xfer(sc->axe_udev); + if (c->axe_xfer == NULL) + return (ENOBUFS); + c->axe_buf = usbd_alloc_buffer(c->axe_xfer, + sc->axe_bufsz); + if (c->axe_buf == NULL) { + usbd_free_xfer(c->axe_xfer); + return (ENOBUFS); + } + } + } + + return (0); +} + +static void +axe_tx_list_free(struct axe_softc *sc) +{ + int i; + + /* Free TX resources. */ + for (i = 0; i < AXE_TX_LIST_CNT; i++) { + if (sc->axe_cdata.axe_tx_chain[i].axe_mbuf != NULL) { + m_freem(sc->axe_cdata.axe_tx_chain[i].axe_mbuf); + sc->axe_cdata.axe_tx_chain[i].axe_mbuf = NULL; + } + if (sc->axe_cdata.axe_tx_chain[i].axe_xfer != NULL) { + usbd_free_xfer(sc->axe_cdata.axe_tx_chain[i].axe_xfer); + sc->axe_cdata.axe_tx_chain[i].axe_xfer = NULL; + } + } +} + +/* + * A frame has been uploaded: pass the resulting mbuf chain up to + * the higher level protocols. + */ +static void +axe_rxeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + struct axe_softc *sc; + struct axe_chain *c = (struct axe_chain *) priv; + struct mbuf *m; + u_char *buf; + struct ifnet *ifp; + struct axe_sframe_hdr *hdr; + int total_len = 0; + int pktlen = 0; + + sc = c->axe_sc; + AXE_LOCK(sc); + ifp = sc->axe_ifp; + + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + AXE_UNLOCK(sc); + return; + } + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) { + AXE_UNLOCK(sc); + return; + } + if (usbd_ratecheck(&sc->axe_rx_notice)) + device_printf(sc->axe_dev, "usb error on rx: %s\n", + usbd_errstr(status)); + if (status == USBD_STALLED) + usbd_clear_endpoint_stall(sc->axe_ep[AXE_ENDPT_RX]); + goto done; + } + + usbd_get_xfer_status(xfer, NULL, NULL, &total_len, NULL); + + buf = c->axe_buf; + + do { + if (sc->axe_flags & (AX178|AX772)) { + if (total_len < sizeof(struct axe_sframe_hdr)) { + ifp->if_ierrors++; + goto done; + } + if ((pktlen % 2) != 0) + pktlen++; + buf += pktlen; + + hdr = (struct axe_sframe_hdr *) buf; + total_len -= sizeof(struct axe_sframe_hdr); + if ((hdr->len ^ hdr->ilen) != 0xffff) { + ifp->if_ierrors++; + goto done; + } + pktlen = le16toh(hdr->len); + if (pktlen > total_len) { + ifp->if_ierrors++; + goto done; + } + + buf += sizeof(struct axe_sframe_hdr); + total_len -= pktlen + (pktlen % 2); + } else { + pktlen = total_len; + total_len = 0; + } + + if (pktlen < sizeof(struct ether_header)) { + ifp->if_ierrors++; + goto done; + } + m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + if (m == NULL) { + ifp->if_ierrors++; + goto done; + } + m->m_data += ETHER_ALIGN; + memcpy(mtod(m, void *), buf, pktlen); + m->m_pkthdr.len = m->m_len = pktlen; + m->m_pkthdr.rcvif = ifp; + + ifp->if_input(ifp, m); + ifp->if_ipackets++; + } while (total_len > 0); + /* fall thru... */ +done: + /* Setup new transfer. */ + usbd_setup_xfer(xfer, sc->axe_ep[AXE_ENDPT_RX], + c, c->axe_buf, sc->axe_bufsz, USBD_SHORT_XFER_OK | USBD_NO_COPY, + USBD_NO_TIMEOUT, axe_rxeof); + usbd_transfer(xfer); + AXE_UNLOCK(sc); + + return; +} + +/* + * A frame was downloaded to the chip. It's safe for us to clean up + * the list buffers. + */ + +static void +axe_txeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + struct axe_softc *sc; + struct axe_chain *c; + struct ifnet *ifp; + usbd_status err; + + c = priv; + sc = c->axe_sc; + AXE_LOCK(sc); + ifp = sc->axe_ifp; + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) { + AXE_UNLOCK(sc); + return; + } + device_printf(sc->axe_dev, "usb error on tx: %s\n", + usbd_errstr(status)); + if (status == USBD_STALLED) + usbd_clear_endpoint_stall(sc->axe_ep[AXE_ENDPT_TX]); + AXE_UNLOCK(sc); + return; + } + + ifp->if_timer = 0; + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + usbd_get_xfer_status(c->axe_xfer, NULL, NULL, NULL, &err); + + if (c->axe_mbuf != NULL) { + m_freem(c->axe_mbuf); + c->axe_mbuf = NULL; + } + + if (err) + ifp->if_oerrors++; + else + ifp->if_opackets++; + + AXE_UNLOCK(sc); + + if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) + axe_start(ifp); + + return; +} + +static void +axe_tick(void *xsc) +{ + struct axe_softc *sc = xsc; + + if (sc == NULL) + return; + if (sc->axe_dying) + return; + + /* Perform periodic stuff in process context */ + usb_add_task(sc->axe_udev, &sc->axe_tick_task, USB_TASKQ_DRIVER); +} + +static void +axe_tick_task(void *xsc) +{ + struct axe_softc *sc; + struct ifnet *ifp; + struct mii_data *mii; + + sc = xsc; + + if (sc == NULL) + return; + + AXE_SLEEPLOCK(sc); + AXE_LOCK(sc); + + ifp = sc->axe_ifp; + mii = GET_MII(sc); + if (mii == NULL) { + AXE_UNLOCK(sc); + AXE_SLEEPUNLOCK(sc); + return; + } + + mii_tick(mii); + if (!sc->axe_link && mii->mii_media_status & IFM_ACTIVE && + IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE) { + sc->axe_link++; + if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) + axe_start(ifp); + } + + sc->axe_stat_ch = timeout(axe_tick, sc, hz); + + AXE_UNLOCK(sc); + AXE_SLEEPUNLOCK(sc); + + return; +} + +static int +axe_encap(struct axe_softc *sc, struct mbuf *m, int idx) +{ + struct axe_chain *c; + usbd_status err; + struct axe_sframe_hdr hdr; + int length; + + c = &sc->axe_cdata.axe_tx_chain[idx]; + + /* + * Copy the mbuf data into a contiguous buffer, leaving two + * bytes at the beginning to hold the frame length. + */ + if (sc->axe_flags & (AX178|AX772)) { + hdr.len = htole16(m->m_pkthdr.len); + hdr.ilen = ~hdr.len; + + memcpy(c->axe_buf, &hdr, sizeof(hdr)); + length = sizeof(hdr); + + m_copydata(m, 0, m->m_pkthdr.len, c->axe_buf + length); + length += m->m_pkthdr.len; + + if ((length % sc->axe_boundary) == 0) { + hdr.len = 0; + hdr.ilen = 0xffff; + memcpy(c->axe_buf + length, &hdr, sizeof(hdr)); + length += sizeof(hdr); + } + } else { + m_copydata(m, 0, m->m_pkthdr.len, c->axe_buf); + length = m->m_pkthdr.len; + } + c->axe_mbuf = m; + + usbd_setup_xfer(c->axe_xfer, sc->axe_ep[AXE_ENDPT_TX], + c, c->axe_buf, length, USBD_FORCE_SHORT_XFER, 10000, axe_txeof); + + /* Transmit */ + err = usbd_transfer(c->axe_xfer); + if (err != USBD_IN_PROGRESS) { + /* XXX probably don't want to sleep here */ + AXE_SLEEPLOCK(sc); + axe_stop(sc); + AXE_SLEEPUNLOCK(sc); + return(EIO); + } + + sc->axe_cdata.axe_tx_cnt++; + + return(0); +} + +static void +axe_start(struct ifnet *ifp) +{ + struct axe_softc *sc; + struct mbuf *m_head = NULL; + + sc = ifp->if_softc; + AXE_LOCK(sc); + + if (!sc->axe_link) { + AXE_UNLOCK(sc); + return; + } + + if (ifp->if_drv_flags & IFF_DRV_OACTIVE) { + AXE_UNLOCK(sc); + return; + } + + IFQ_DRV_DEQUEUE(&ifp->if_snd, m_head); + if (m_head == NULL) { + AXE_UNLOCK(sc); + return; + } + + if (axe_encap(sc, m_head, 0)) { + IFQ_DRV_PREPEND(&ifp->if_snd, m_head); + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + AXE_UNLOCK(sc); + return; + } + + /* + * If there's a BPF listener, bounce a copy of this frame + * to him. + */ + BPF_MTAP(ifp, m_head); + + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + + /* + * Set a timeout in case the chip goes out to lunch. + */ + ifp->if_timer = 5; + AXE_UNLOCK(sc); + + return; +} + +static void +axe_init(void *xsc) +{ + struct axe_softc *sc = xsc; + struct ifnet *ifp = sc->axe_ifp; + struct axe_chain *c; + usbd_status err; + int i; + int rxmode; + + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + return; + + AXE_SLEEPLOCK(sc); + AXE_LOCK(sc); + + /* + * Cancel pending I/O and free all RX/TX buffers. + */ + + axe_reset(sc); + +#ifdef notdef + /* Set MAC address */ + axe_mac(sc, IF_LLADDR(sc->axe_ifp), 1); +#endif + + /* Enable RX logic. */ + + /* Init TX ring. */ + if (axe_tx_list_init(sc) == ENOBUFS) { + device_printf(sc->axe_dev, "tx list init failed\n"); + AXE_UNLOCK(sc); + AXE_SLEEPUNLOCK(sc); + return; + } + + /* Init RX ring. */ + if (axe_rx_list_init(sc) == ENOBUFS) { + device_printf(sc->axe_dev, "rx list init failed\n"); + AXE_UNLOCK(sc); + AXE_SLEEPUNLOCK(sc); + return; + } + + /* Set transmitter IPG values */ + if (sc->axe_flags & (AX178|AX772)) { + axe_cmd(sc, AXE_178_CMD_WRITE_IPG012, sc->axe_ipgs[2], + (sc->axe_ipgs[1]<<8) | sc->axe_ipgs[0], NULL); + } else { + axe_cmd(sc, AXE_172_CMD_WRITE_IPG0, 0, sc->axe_ipgs[0], NULL); + axe_cmd(sc, AXE_172_CMD_WRITE_IPG1, 0, sc->axe_ipgs[1], NULL); + axe_cmd(sc, AXE_172_CMD_WRITE_IPG2, 0, sc->axe_ipgs[2], NULL); + } + + /* Enable receiver, set RX mode */ + rxmode = AXE_RXCMD_MULTICAST|AXE_RXCMD_ENABLE; + if (sc->axe_flags & (AX178|AX772)) { + if (sc->axe_bufsz == AXE_178_MAX_BUFSZ) + rxmode |= AXE_178_RXCMD_MFB; + } else + rxmode |= AXE_172_RXCMD_UNICAST; + + /* If we want promiscuous mode, set the allframes bit. */ + if (ifp->if_flags & IFF_PROMISC) + rxmode |= AXE_RXCMD_PROMISC; + + if (ifp->if_flags & IFF_BROADCAST) + rxmode |= AXE_RXCMD_BROADCAST; + + axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, rxmode, NULL); + + /* Load the multicast filter. */ + axe_setmulti(sc); + + /* Open RX and TX pipes. */ + err = usbd_open_pipe(sc->axe_iface, sc->axe_ed[AXE_ENDPT_RX], + USBD_EXCLUSIVE_USE, &sc->axe_ep[AXE_ENDPT_RX]); + if (err) { + device_printf(sc->axe_dev, "open rx pipe failed: %s\n", + usbd_errstr(err)); + AXE_UNLOCK(sc); + AXE_SLEEPUNLOCK(sc); + return; + } + + err = usbd_open_pipe(sc->axe_iface, sc->axe_ed[AXE_ENDPT_TX], + USBD_EXCLUSIVE_USE, &sc->axe_ep[AXE_ENDPT_TX]); + if (err) { + device_printf(sc->axe_dev, "open tx pipe failed: %s\n", + usbd_errstr(err)); + AXE_UNLOCK(sc); + AXE_SLEEPUNLOCK(sc); + return; + } + + /* Start up the receive pipe. */ + for (i = 0; i < AXE_RX_LIST_CNT; i++) { + c = &sc->axe_cdata.axe_rx_chain[i]; + usbd_setup_xfer(c->axe_xfer, sc->axe_ep[AXE_ENDPT_RX], + c, c->axe_buf, sc->axe_bufsz, + USBD_SHORT_XFER_OK | USBD_NO_COPY, + USBD_NO_TIMEOUT, axe_rxeof); + usbd_transfer(c->axe_xfer); + } + + ifp->if_drv_flags |= IFF_DRV_RUNNING; + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + + AXE_UNLOCK(sc); + AXE_SLEEPUNLOCK(sc); + + sc->axe_stat_ch = timeout(axe_tick, sc, hz); + + return; +} + +static int +axe_ioctl(struct ifnet *ifp, u_long command, caddr_t data) +{ + struct axe_softc *sc = ifp->if_softc; + struct ifreq *ifr = (struct ifreq *)data; + struct mii_data *mii; + u_int16_t rxmode; + int error = 0; + + switch(command) { + case SIOCSIFFLAGS: + if (ifp->if_flags & IFF_UP) { + if (ifp->if_drv_flags & IFF_DRV_RUNNING && + ifp->if_flags & IFF_PROMISC && + !(sc->axe_if_flags & IFF_PROMISC)) { + AXE_SLEEPLOCK(sc); + AXE_LOCK(sc); + axe_cmd(sc, AXE_CMD_RXCTL_READ, + 0, 0, (void *)&rxmode); + rxmode = le16toh(rxmode); + rxmode |= AXE_RXCMD_PROMISC; + axe_cmd(sc, AXE_CMD_RXCTL_WRITE, + 0, rxmode, NULL); + AXE_UNLOCK(sc); + axe_setmulti(sc); + AXE_SLEEPUNLOCK(sc); + } else if (ifp->if_drv_flags & IFF_DRV_RUNNING && + !(ifp->if_flags & IFF_PROMISC) && + sc->axe_if_flags & IFF_PROMISC) { + AXE_SLEEPLOCK(sc); + AXE_LOCK(sc); + axe_cmd(sc, AXE_CMD_RXCTL_READ, + 0, 0, (void *)&rxmode); + rxmode = le16toh(rxmode); + rxmode &= ~AXE_RXCMD_PROMISC; + axe_cmd(sc, AXE_CMD_RXCTL_WRITE, + 0, rxmode, NULL); + AXE_UNLOCK(sc); + axe_setmulti(sc); + AXE_SLEEPUNLOCK(sc); + } else if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) + axe_init(sc); + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + AXE_SLEEPLOCK(sc); + axe_stop(sc); + AXE_SLEEPUNLOCK(sc); + } + } + sc->axe_if_flags = ifp->if_flags; + error = 0; + break; + case SIOCADDMULTI: + case SIOCDELMULTI: + AXE_SLEEPLOCK(sc); + axe_setmulti(sc); + AXE_SLEEPUNLOCK(sc); + error = 0; + break; + case SIOCGIFMEDIA: + case SIOCSIFMEDIA: + AXE_SLEEPLOCK(sc); + mii = GET_MII(sc); + error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, command); + AXE_SLEEPUNLOCK(sc); + break; + + default: + error = ether_ioctl(ifp, command, data); + break; + } + + return(error); +} + +static void +axe_watchdog(struct ifnet *ifp) +{ + struct axe_softc *sc; + struct axe_chain *c; + usbd_status stat; + + sc = ifp->if_softc; + AXE_LOCK(sc); + + ifp->if_oerrors++; + device_printf(sc->axe_dev, "watchdog timeout\n"); + + c = &sc->axe_cdata.axe_tx_chain[0]; + usbd_get_xfer_status(c->axe_xfer, NULL, NULL, NULL, &stat); + axe_txeof(c->axe_xfer, c, stat); + + AXE_UNLOCK(sc); + + if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd)) + axe_start(ifp); + + return; +} + +/* + * Stop the adapter and free any mbufs allocated to the + * RX and TX lists. + */ +static void +axe_stop(struct axe_softc *sc) +{ + usbd_status err; + struct ifnet *ifp; + + AXE_SLEEPLOCKASSERT(sc); + AXE_LOCK(sc); + + ifp = sc->axe_ifp; + ifp->if_timer = 0; + + untimeout(axe_tick, sc, sc->axe_stat_ch); + + /* Stop transfers. */ + if (sc->axe_ep[AXE_ENDPT_RX] != NULL) { + err = usbd_abort_pipe(sc->axe_ep[AXE_ENDPT_RX]); + if (err) { + device_printf(sc->axe_dev, "abort rx pipe failed: %s\n", + usbd_errstr(err)); + } + err = usbd_close_pipe(sc->axe_ep[AXE_ENDPT_RX]); + if (err) { + device_printf(sc->axe_dev, "close rx pipe failed: %s\n", + usbd_errstr(err)); + } + sc->axe_ep[AXE_ENDPT_RX] = NULL; + } + + if (sc->axe_ep[AXE_ENDPT_TX] != NULL) { + err = usbd_abort_pipe(sc->axe_ep[AXE_ENDPT_TX]); + if (err) { + device_printf(sc->axe_dev, "abort tx pipe failed: %s\n", + usbd_errstr(err)); + } + err = usbd_close_pipe(sc->axe_ep[AXE_ENDPT_TX]); + if (err) { + device_printf(sc->axe_dev, "close tx pipe failed: %s\n", + usbd_errstr(err)); + } + sc->axe_ep[AXE_ENDPT_TX] = NULL; + } + + if (sc->axe_ep[AXE_ENDPT_INTR] != NULL) { + err = usbd_abort_pipe(sc->axe_ep[AXE_ENDPT_INTR]); + if (err) { + device_printf(sc->axe_dev, + "abort intr pipe failed: %s\n", usbd_errstr(err)); + } + err = usbd_close_pipe(sc->axe_ep[AXE_ENDPT_INTR]); + if (err) { + device_printf(sc->axe_dev, + "close intr pipe failed: %s\n", usbd_errstr(err)); + } + sc->axe_ep[AXE_ENDPT_INTR] = NULL; + } + + axe_reset(sc); + + /* Free RX resources. */ + axe_rx_list_free(sc); + /* Free TX resources. */ + axe_tx_list_free(sc); + + ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); + sc->axe_link = 0; + AXE_UNLOCK(sc); + + return; +} + +/* + * Stop all chip I/O so that the kernel's probe routines don't + * get confused by errant DMAs when rebooting. + */ +static int +axe_shutdown(device_t dev) +{ + struct axe_softc *sc; + + sc = device_get_softc(dev); + + AXE_SLEEPLOCK(sc); + axe_stop(sc); + AXE_SLEEPUNLOCK(sc); + + return (0); +} diff --git a/sys/legacy/dev/usb/if_axereg.h b/sys/legacy/dev/usb/if_axereg.h new file mode 100644 index 0000000..4503b19 --- /dev/null +++ b/sys/legacy/dev/usb/if_axereg.h @@ -0,0 +1,254 @@ +/*- + * Copyright (c) 1997, 1998, 1999, 2000-2003 + * Bill Paul <wpaul@windriver.com>. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * Definitions for the ASIX Electronics AX88172 to ethernet controller. + */ + + +/* + * Vendor specific commands + * ASIX conveniently doesn't document the 'set NODEID' command in their + * datasheet (thanks a lot guys). + * To make handling these commands easier, I added some extra data + * which is decided by the axe_cmd() routine. Commands are encoded + * in 16 bites, with the format: LDCC. L and D are both nibbles in + * the high byte. L represents the data length (0 to 15) and D + * represents the direction (0 for vendor read, 1 for vendor write). + * CC is the command byte, as specified in the manual. + */ + +#define AXE_CMD_DIR(x) (((x) & 0x0F00) >> 8) +#define AXE_CMD_LEN(x) (((x) & 0xF000) >> 12) +#define AXE_CMD_CMD(x) ((x) & 0x00FF) + +#define AXE_172_CMD_READ_RXTX_SRAM 0x2002 +#define AXE_182_CMD_READ_RXTX_SRAM 0x8002 +#define AXE_172_CMD_WRITE_RX_SRAM 0x0103 +#define AXE_172_CMD_WRITE_TX_SRAM 0x0104 +#define AXE_182_CMD_WRITE_RXTX_SRAM 0x8103 +#define AXE_CMD_MII_OPMODE_SW 0x0106 +#define AXE_CMD_MII_READ_REG 0x2007 +#define AXE_CMD_MII_WRITE_REG 0x2108 +#define AXE_CMD_MII_READ_OPMODE 0x1009 +#define AXE_CMD_MII_OPMODE_HW 0x010A +#define AXE_CMD_SROM_READ 0x200B +#define AXE_CMD_SROM_WRITE 0x010C +#define AXE_CMD_SROM_WR_ENABLE 0x010D +#define AXE_CMD_SROM_WR_DISABLE 0x010E +#define AXE_CMD_RXCTL_READ 0x200F +#define AXE_CMD_RXCTL_WRITE 0x0110 +#define AXE_CMD_READ_IPG012 0x3011 +#define AXE_172_CMD_WRITE_IPG0 0x0112 +#define AXE_172_CMD_WRITE_IPG1 0x0113 +#define AXE_172_CMD_WRITE_IPG2 0x0114 +#define AXE_178_CMD_WRITE_IPG012 0x0112 +#define AXE_CMD_READ_MCAST 0x8015 +#define AXE_CMD_WRITE_MCAST 0x8116 +#define AXE_172_CMD_READ_NODEID 0x6017 +#define AXE_172_CMD_WRITE_NODEID 0x6118 +#define AXE_178_CMD_READ_NODEID 0x6013 +#define AXE_178_CMD_WRITE_NODEID 0x6114 +#define AXE_CMD_READ_PHYID 0x2019 +#define AXE_172_CMD_READ_MEDIA 0x101A +#define AXE_178_CMD_READ_MEDIA 0x201A +#define AXE_CMD_WRITE_MEDIA 0x011B +#define AXE_CMD_READ_MONITOR_MODE 0x101C +#define AXE_CMD_WRITE_MONITOR_MODE 0x011D +#define AXE_CMD_READ_GPIO 0x101E +#define AXE_CMD_WRITE_GPIO 0x011F +#define AXE_CMD_SW_RESET_REG 0x0120 +#define AXE_CMD_SW_PHY_STATUS 0x0021 +#define AXE_CMD_SW_PHY_SELECT 0x0122 + +#define AXE_SW_RESET_CLEAR 0x00 +#define AXE_SW_RESET_RR 0x01 +#define AXE_SW_RESET_RT 0x02 +#define AXE_SW_RESET_PRTE 0x04 +#define AXE_SW_RESET_PRL 0x08 +#define AXE_SW_RESET_BZ 0x10 +#define AXE_SW_RESET_IPRL 0x20 +#define AXE_SW_RESET_IPPD 0x40 + +/* AX88178 documentation says to always write this bit... */ +#define AXE_178_RESET_MAGIC 0x40 + +#define AXE_178_MEDIA_GMII 0x0001 +#define AXE_MEDIA_FULL_DUPLEX 0x0002 +#define AXE_172_MEDIA_TX_ABORT_ALLOW 0x0004 +/* AX88178/88772 documentation says to always write 1 to bit 2 */ +#define AXE_178_MEDIA_MAGIC 0x0004 +/* AX88772 documentation says to always write 0 to bit 3 */ +#define AXE_178_MEDIA_ENCK 0x0008 +#define AXE_172_MEDIA_FLOW_CONTROL_EN 0x0010 +#define AXE_178_MEDIA_RXFLOW_CONTROL_EN 0x0010 +#define AXE_178_MEDIA_TXFLOW_CONTROL_EN 0x0020 +#define AXE_178_MEDIA_JUMBO_EN 0x0040 +#define AXE_178_MEDIA_LTPF_ONLY 0x0080 +#define AXE_178_MEDIA_RX_EN 0x0100 +#define AXE_178_MEDIA_100TX 0x0200 +#define AXE_178_MEDIA_SBP 0x0800 +#define AXE_178_MEDIA_SUPERMAC 0x1000 + +#define AXE_RXCMD_PROMISC 0x0001 +#define AXE_RXCMD_ALLMULTI 0x0002 +#define AXE_172_RXCMD_UNICAST 0x0004 +#define AXE_178_RXCMD_KEEP_INVALID_CRC 0x0004 +#define AXE_RXCMD_BROADCAST 0x0008 +#define AXE_RXCMD_MULTICAST 0x0010 +#define AXE_178_RXCMD_AP 0x0020 +#define AXE_RXCMD_ENABLE 0x0080 +#define AXE_178_RXCMD_MFB_2048 0x0000 /* 2K max frame burst */ +#define AXE_178_RXCMD_MFB_4096 0x0100 /* 4K max frame burst */ +#define AXE_178_RXCMD_MFB_8192 0x0200 /* 8K max frame burst */ +#define AXE_178_RXCMD_MFB_16384 0x0300 /* 16K max frame burst*/ + +#define AXE_NOPHY 0xE0 +#define AXE_INTPHY 0x10 + +#define AXE_TIMEOUT 1000 +#define AXE_172_BUFSZ 1536 +#define AXE_178_MIN_BUFSZ 2048 +#define AXE_MIN_FRAMELEN 60 +#define AXE_RX_FRAMES 1 +#define AXE_TX_FRAMES 1 + +#if AXE_178_MAX_FRAME_BURST == 0 +#define AXE_178_RXCMD_MFB AXE_178_RXCMD_MFB_2048 +#define AXE_178_MAX_BUFSZ 2048 +#elif AXE_178_MAX_FRAME_BURST == 1 +#define AXE_178_RXCMD_MFB AXE_178_RXCMD_MFB_4096 +#define AXE_178_MAX_BUFSZ 4096 +#elif AXE_178_MAX_FRAME_BURST == 2 +#define AXE_178_RXCMD_MFB AXE_178_RXCMD_MFB_8192 +#define AXE_178_MAX_BUFSZ 8192 +#else +#define AXE_178_RXCMD_MFB AXE_178_RXCMD_MFB_16384 +#define AXE_178_MAX_BUFSZ 16384 +#endif + +#define AXE_RX_LIST_CNT 1 +#define AXE_TX_LIST_CNT 1 + +struct axe_chain { + struct axe_softc *axe_sc; + usbd_xfer_handle axe_xfer; + char *axe_buf; + struct mbuf *axe_mbuf; + int axe_accum; + int axe_idx; +}; + +struct axe_cdata { + struct axe_chain axe_tx_chain[AXE_TX_LIST_CNT]; + struct axe_chain axe_rx_chain[AXE_RX_LIST_CNT]; + int axe_tx_prod; + int axe_tx_cons; + int axe_tx_cnt; + int axe_rx_prod; +}; + +#define AXE_CTL_READ 0x01 +#define AXE_CTL_WRITE 0x02 + +#define AXE_CONFIG_NO 1 +#define AXE_IFACE_IDX 0 + +/* + * The interrupt endpoint is currently unused + * by the ASIX part. + */ +#define AXE_ENDPT_RX 0x0 +#define AXE_ENDPT_TX 0x1 +#define AXE_ENDPT_INTR 0x2 +#define AXE_ENDPT_MAX 0x3 + +struct axe_sframe_hdr { + uint16_t len; + uint16_t ilen; +} __packed; + +struct axe_type { + struct usb_devno axe_dev; + uint32_t axe_flags; +#define AX172 0x0000 /* AX88172 */ +#define AX178 0x0001 /* AX88178 */ +#define AX772 0x0002 /* AX88772 */ +}; + +#define AXE_INC(x, y) (x) = (x + 1) % y + +struct axe_softc { +#if defined(__FreeBSD__) +#define GET_MII(sc) (device_get_softc((sc)->axe_miibus)) +#elif defined(__NetBSD__) +#define GET_MII(sc) (&(sc)->axe_mii) +#elif defined(__OpenBSD__) +#define GET_MII(sc) (&(sc)->axe_mii) +#endif + struct ifnet *axe_ifp; + device_t axe_miibus; + device_t axe_dev; + usbd_device_handle axe_udev; + usbd_interface_handle axe_iface; + u_int16_t axe_vendor; + u_int16_t axe_product; + u_int16_t axe_flags; + int axe_ed[AXE_ENDPT_MAX]; + usbd_pipe_handle axe_ep[AXE_ENDPT_MAX]; + int axe_if_flags; + struct axe_cdata axe_cdata; + struct callout_handle axe_stat_ch; + struct mtx axe_mtx; + struct sx axe_sleeplock; + char axe_dying; + int axe_link; + unsigned char axe_ipgs[3]; + unsigned char axe_phyaddrs[2]; + struct timeval axe_rx_notice; + struct usb_task axe_tick_task; + int axe_bufsz; + int axe_boundary; +}; + +#if 0 +#define AXE_LOCK(_sc) mtx_lock(&(_sc)->axe_mtx) +#define AXE_UNLOCK(_sc) mtx_unlock(&(_sc)->axe_mtx) +#else +#define AXE_LOCK(_sc) +#define AXE_UNLOCK(_sc) +#endif +#define AXE_SLEEPLOCK(_sc) sx_xlock(&(_sc)->axe_sleeplock) +#define AXE_SLEEPUNLOCK(_sc) sx_xunlock(&(_sc)->axe_sleeplock) +#define AXE_SLEEPLOCKASSERT(_sc) sx_assert(&(_sc)->axe_sleeplock, SX_XLOCKED) diff --git a/sys/legacy/dev/usb/if_cdce.c b/sys/legacy/dev/usb/if_cdce.c new file mode 100644 index 0000000..d41a54f --- /dev/null +++ b/sys/legacy/dev/usb/if_cdce.c @@ -0,0 +1,771 @@ +/* $NetBSD: if_cdce.c,v 1.4 2004/10/24 12:50:54 augustss Exp $ */ + +/* + * Copyright (c) 1997, 1998, 1999, 2000-2003 Bill Paul <wpaul@windriver.com> + * Copyright (c) 2003-2005 Craig Boston + * Copyright (c) 2004 Daniel Hartmeier + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul, THE VOICES IN HIS HEAD OR + * THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * USB Communication Device Class (Ethernet Networking Control Model) + * http://www.usb.org/developers/devclass_docs/usbcdc11.pdf + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/sockio.h> +#include <sys/mbuf.h> +#include <sys/malloc.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/socket.h> +#include <sys/endian.h> + +#include <net/if.h> +#include <net/if_arp.h> +#include <net/ethernet.h> +#include <net/if_types.h> +#include <net/if_media.h> + +#include <net/bpf.h> + +#include <sys/bus.h> +#include <machine/bus.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usbdivar.h> +#include <dev/usb/usb_ethersubr.h> + +#include <dev/usb/usbcdc.h> +#include "usbdevs.h" +#include <dev/usb/if_cdcereg.h> + +static device_probe_t cdce_match; +static device_attach_t cdce_attach; +static device_detach_t cdce_detach; +static device_shutdown_t cdce_shutdown; + +static device_method_t cdce_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, cdce_match), + DEVMETHOD(device_attach, cdce_attach), + DEVMETHOD(device_detach, cdce_detach), + DEVMETHOD(device_shutdown, cdce_shutdown), + + { 0, 0 } +}; + +static driver_t cdce_driver = { + "cdce", + cdce_methods, + sizeof(struct cdce_softc) +}; + +static devclass_t cdce_devclass; + +DRIVER_MODULE(cdce, uhub, cdce_driver, cdce_devclass, usbd_driver_load, 0); +MODULE_VERSION(cdce, 0); +MODULE_DEPEND(cdce, usb, 1, 1, 1); + +static int cdce_encap(struct cdce_softc *, struct mbuf *, int); +static void cdce_rxeof(usbd_xfer_handle, usbd_private_handle, usbd_status); +static void cdce_txeof(usbd_xfer_handle, usbd_private_handle, usbd_status); +static void cdce_start(struct ifnet *); +static int cdce_ioctl(struct ifnet *, u_long, caddr_t); +static void cdce_init(void *); +static void cdce_reset(struct cdce_softc *); +static void cdce_stop(struct cdce_softc *); +static void cdce_rxstart(struct ifnet *); +static int cdce_ifmedia_upd(struct ifnet *ifp); +static void cdce_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr); + +static const struct cdce_type cdce_devs[] = { + {{ USB_VENDOR_ACERLABS, USB_PRODUCT_ACERLABS_M5632 }, CDCE_NO_UNION }, + {{ USB_VENDOR_AMBIT, USB_PRODUCT_AMBIT_NTL_250 }, CDCE_NO_UNION }, + {{ USB_VENDOR_COMPAQ, USB_PRODUCT_COMPAQ_IPAQLINUX }, CDCE_NO_UNION }, + {{ USB_VENDOR_GMATE, USB_PRODUCT_GMATE_YP3X00 }, CDCE_NO_UNION }, + {{ USB_VENDOR_MOTOROLA2, USB_PRODUCT_MOTOROLA2_USBLAN }, CDCE_ZAURUS | CDCE_NO_UNION }, + {{ USB_VENDOR_MOTOROLA2, USB_PRODUCT_MOTOROLA2_USBLAN2 }, CDCE_ZAURUS | CDCE_NO_UNION }, + {{ USB_VENDOR_NETCHIP, USB_PRODUCT_NETCHIP_ETHERNETGADGET }, CDCE_NO_UNION }, + {{ USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_PL2501 }, CDCE_NO_UNION }, + {{ USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SL5500 }, CDCE_ZAURUS }, + {{ USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SL5600 }, CDCE_ZAURUS | CDCE_NO_UNION }, + {{ USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SLA300 }, CDCE_ZAURUS | CDCE_NO_UNION }, + {{ USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SLC700 }, CDCE_ZAURUS | CDCE_NO_UNION }, + {{ USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SLC750 }, CDCE_ZAURUS | CDCE_NO_UNION }, +}; +#define cdce_lookup(v, p) ((const struct cdce_type *)usb_lookup(cdce_devs, v, p)) + +static int +cdce_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + usb_interface_descriptor_t *id; + + if (uaa->iface == NULL) + return (UMATCH_NONE); + + id = usbd_get_interface_descriptor(uaa->iface); + if (id == NULL) + return (UMATCH_NONE); + + if (cdce_lookup(uaa->vendor, uaa->product) != NULL) + return (UMATCH_VENDOR_PRODUCT); + + if (id->bInterfaceClass == UICLASS_CDC && id->bInterfaceSubClass == + UISUBCLASS_ETHERNET_NETWORKING_CONTROL_MODEL) + return (UMATCH_IFACECLASS_GENERIC); + + return (UMATCH_NONE); +} + +static int +cdce_attach(device_t self) +{ + struct cdce_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + struct ifnet *ifp; + usbd_device_handle dev = uaa->device; + const struct cdce_type *t; + usb_interface_descriptor_t *id; + usb_endpoint_descriptor_t *ed; + const usb_cdc_union_descriptor_t *ud; + usb_config_descriptor_t *cd; + int data_ifcno; + int i, j, numalts; + u_char eaddr[ETHER_ADDR_LEN]; + const usb_cdc_ethernet_descriptor_t *ue; + char eaddr_str[USB_MAX_STRING_LEN]; + + sc->cdce_dev = self; + sc->cdce_udev = uaa->device; + + t = cdce_lookup(uaa->vendor, uaa->product); + if (t) + sc->cdce_flags = t->cdce_flags; + + if (sc->cdce_flags & CDCE_NO_UNION) + sc->cdce_data_iface = uaa->iface; + else { + ud = (const usb_cdc_union_descriptor_t *)usb_find_desc(sc->cdce_udev, + UDESC_CS_INTERFACE, UDESCSUB_CDC_UNION); + if (ud == NULL) { + device_printf(sc->cdce_dev, "no union descriptor\n"); + return ENXIO; + } + data_ifcno = ud->bSlaveInterface[0]; + + for (i = 0; i < uaa->nifaces; i++) { + if (uaa->ifaces[i] != NULL) { + id = usbd_get_interface_descriptor( + uaa->ifaces[i]); + if (id != NULL && id->bInterfaceNumber == + data_ifcno) { + sc->cdce_data_iface = uaa->ifaces[i]; + uaa->ifaces[i] = NULL; + } + } + } + } + + if (sc->cdce_data_iface == NULL) { + device_printf(sc->cdce_dev, "no data interface\n"); + return ENXIO; + } + + /* + * <quote> + * The Data Class interface of a networking device shall have a minimum + * of two interface settings. The first setting (the default interface + * setting) includes no endpoints and therefore no networking traffic is + * exchanged whenever the default interface setting is selected. One or + * more additional interface settings are used for normal operation, and + * therefore each includes a pair of endpoints (one IN, and one OUT) to + * exchange network traffic. Select an alternate interface setting to + * initialize the network aspects of the device and to enable the + * exchange of network traffic. + * </quote> + * + * Some devices, most notably cable modems, include interface settings + * that have no IN or OUT endpoint, therefore loop through the list of all + * available interface settings looking for one with both IN and OUT + * endpoints. + */ + id = usbd_get_interface_descriptor(sc->cdce_data_iface); + cd = usbd_get_config_descriptor(sc->cdce_udev); + numalts = usbd_get_no_alts(cd, id->bInterfaceNumber); + + for (j = 0; j < numalts; j++) { + if (usbd_set_interface(sc->cdce_data_iface, j)) { + device_printf(sc->cdce_dev, + "setting alternate interface failed\n"); + return ENXIO; + } + /* Find endpoints. */ + id = usbd_get_interface_descriptor(sc->cdce_data_iface); + sc->cdce_bulkin_no = sc->cdce_bulkout_no = -1; + for (i = 0; i < id->bNumEndpoints; i++) { + ed = usbd_interface2endpoint_descriptor(sc->cdce_data_iface, i); + if (!ed) { + device_printf(sc->cdce_dev, + "could not read endpoint descriptor\n"); + return ENXIO; + } + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + sc->cdce_bulkin_no = ed->bEndpointAddress; + } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + sc->cdce_bulkout_no = ed->bEndpointAddress; + } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_INTERRUPT) { + /* XXX: CDC spec defines an interrupt pipe, but it is not + * needed for simple host-to-host applications. */ + } else { + device_printf(sc->cdce_dev, + "unexpected endpoint\n"); + } + } + /* If we found something, try and use it... */ + if ((sc->cdce_bulkin_no != -1) && (sc->cdce_bulkout_no != -1)) + break; + } + + if (sc->cdce_bulkin_no == -1) { + device_printf(sc->cdce_dev, "could not find data bulk in\n"); + return ENXIO; + } + if (sc->cdce_bulkout_no == -1 ) { + device_printf(sc->cdce_dev, "could not find data bulk out\n"); + return ENXIO; + } + + mtx_init(&sc->cdce_mtx, device_get_nameunit(sc->cdce_dev), MTX_NETWORK_LOCK, + MTX_DEF | MTX_RECURSE); + ifmedia_init(&sc->cdce_ifmedia, 0, cdce_ifmedia_upd, cdce_ifmedia_sts); + CDCE_LOCK(sc); + + ue = (const usb_cdc_ethernet_descriptor_t *)usb_find_desc(dev, + UDESC_INTERFACE, UDESCSUB_CDC_ENF); + if (!ue || usbd_get_string(dev, ue->iMacAddress, eaddr_str, + sizeof(eaddr_str))) { + /* Fake MAC address */ + device_printf(sc->cdce_dev, "faking MAC address\n"); + eaddr[0]= 0x2a; + memcpy(&eaddr[1], &ticks, sizeof(u_int32_t)); + eaddr[5] = (u_int8_t)device_get_unit(sc->cdce_dev); + } else { + int i; + + memset(eaddr, 0, ETHER_ADDR_LEN); + for (i = 0; i < ETHER_ADDR_LEN * 2; i++) { + int c = eaddr_str[i]; + + if ('0' <= c && c <= '9') + c -= '0'; + else + c -= 'A' - 10; + c &= 0xf; + if (c % 2 == 0) + c <<= 4; + eaddr[i / 2] |= c; + } + } + + ifp = GET_IFP(sc) = if_alloc(IFT_ETHER); + if (ifp == NULL) { + device_printf(sc->cdce_dev, "can not if_alloc()\n"); + CDCE_UNLOCK(sc); + mtx_destroy(&sc->cdce_mtx); + return ENXIO; + } + ifp->if_softc = sc; + if_initname(ifp, "cdce", device_get_unit(sc->cdce_dev)); + ifp->if_mtu = ETHERMTU; + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST | + IFF_NEEDSGIANT; + ifp->if_ioctl = cdce_ioctl; + ifp->if_output = ether_output; + ifp->if_start = cdce_start; + ifp->if_init = cdce_init; + ifp->if_baudrate = 11000000; + ifp->if_snd.ifq_maxlen = IFQ_MAXLEN; + + sc->q.ifp = ifp; + sc->q.if_rxstart = cdce_rxstart; + + /* No IFM type for 11Mbps USB, so go with 10baseT */ + ifmedia_add(&sc->cdce_ifmedia, IFM_ETHER | IFM_10_T, 0, 0); + ifmedia_set(&sc->cdce_ifmedia, IFM_ETHER | IFM_10_T); + + ether_ifattach(ifp, eaddr); + usb_register_netisr(); + + CDCE_UNLOCK(sc); + + usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, sc->cdce_udev, + sc->cdce_dev); + + return 0; +} + +static int +cdce_detach(device_t self) +{ + struct cdce_softc *sc = device_get_softc(self); + struct ifnet *ifp; + + CDCE_LOCK(sc); + sc->cdce_dying = 1; + ifp = GET_IFP(sc); + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + cdce_shutdown(sc->cdce_dev); + + ether_ifdetach(ifp); + if_free(ifp); + ifmedia_removeall(&sc->cdce_ifmedia); + CDCE_UNLOCK(sc); + mtx_destroy(&sc->cdce_mtx); + + usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->cdce_udev, + sc->cdce_dev); + + return (0); +} + +static void +cdce_start(struct ifnet *ifp) +{ + struct cdce_softc *sc; + struct mbuf *m_head = NULL; + + sc = ifp->if_softc; + CDCE_LOCK(sc); + + + if (sc->cdce_dying || + ifp->if_drv_flags & IFF_DRV_OACTIVE || + !(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + CDCE_UNLOCK(sc); + return; + } + + IF_DEQUEUE(&ifp->if_snd, m_head); + if (m_head == NULL) { + CDCE_UNLOCK(sc); + return; + } + + if (cdce_encap(sc, m_head, 0)) { + IF_PREPEND(&ifp->if_snd, m_head); + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + CDCE_UNLOCK(sc); + return; + } + + BPF_MTAP(ifp, m_head); + + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + + CDCE_UNLOCK(sc); + + return; +} + +static int +cdce_encap(struct cdce_softc *sc, struct mbuf *m, int idx) +{ + struct ue_chain *c; + usbd_status err; + int extra = 0; + + c = &sc->cdce_cdata.ue_tx_chain[idx]; + + m_copydata(m, 0, m->m_pkthdr.len, c->ue_buf); + if (sc->cdce_flags & CDCE_ZAURUS) { + /* Zaurus wants a 32-bit CRC appended to every frame */ + u_int32_t crc; + + crc = htole32(crc32(c->ue_buf, m->m_pkthdr.len)); + bcopy(&crc, c->ue_buf + m->m_pkthdr.len, 4); + extra = 4; + } + c->ue_mbuf = m; + + usbd_setup_xfer(c->ue_xfer, sc->cdce_bulkout_pipe, c, c->ue_buf, + m->m_pkthdr.len + extra, 0, 10000, cdce_txeof); + err = usbd_transfer(c->ue_xfer); + if (err != USBD_IN_PROGRESS) { + cdce_stop(sc); + return (EIO); + } + + sc->cdce_cdata.ue_tx_cnt++; + + return (0); +} + +static void +cdce_stop(struct cdce_softc *sc) +{ + usbd_status err; + struct ifnet *ifp; + + CDCE_LOCK(sc); + + cdce_reset(sc); + + ifp = GET_IFP(sc); + ifp->if_timer = 0; + + if (sc->cdce_bulkin_pipe != NULL) { + err = usbd_abort_pipe(sc->cdce_bulkin_pipe); + if (err) + device_printf(sc->cdce_dev, + "abort rx pipe failed: %s\n", usbd_errstr(err)); + err = usbd_close_pipe(sc->cdce_bulkin_pipe); + if (err) + device_printf(sc->cdce_dev, + "close rx pipe failed: %s\n", usbd_errstr(err)); + sc->cdce_bulkin_pipe = NULL; + } + + if (sc->cdce_bulkout_pipe != NULL) { + err = usbd_abort_pipe(sc->cdce_bulkout_pipe); + if (err) + device_printf(sc->cdce_dev, + "abort tx pipe failed: %s\n", usbd_errstr(err)); + err = usbd_close_pipe(sc->cdce_bulkout_pipe); + if (err) + device_printf(sc->cdce_dev, + "close tx pipe failed: %s\n", usbd_errstr(err)); + sc->cdce_bulkout_pipe = NULL; + } + + usb_ether_rx_list_free(&sc->cdce_cdata); + usb_ether_tx_list_free(&sc->cdce_cdata); + + ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); + CDCE_UNLOCK(sc); + + return; +} + +static int +cdce_shutdown(device_t dev) +{ + struct cdce_softc *sc; + + sc = device_get_softc(dev); + cdce_stop(sc); + + return (0); +} + +static int +cdce_ioctl(struct ifnet *ifp, u_long command, caddr_t data) +{ + struct cdce_softc *sc = ifp->if_softc; + struct ifreq *ifr = (struct ifreq *)data; + int error = 0; + + if (sc->cdce_dying) + return (ENXIO); + + switch(command) { + case SIOCSIFFLAGS: + if (ifp->if_flags & IFF_UP) { + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) + cdce_init(sc); + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + cdce_stop(sc); + } + error = 0; + break; + + case SIOCSIFMEDIA: + case SIOCGIFMEDIA: + error = ifmedia_ioctl(ifp, ifr, &sc->cdce_ifmedia, command); + break; + + default: + error = ether_ioctl(ifp, command, data); + break; + } + + return (error); +} + +static void +cdce_reset(struct cdce_softc *sc) +{ + /* XXX Maybe reset the bulk pipes here? */ + return; +} + +static void +cdce_init(void *xsc) +{ + struct cdce_softc *sc = xsc; + struct ifnet *ifp = GET_IFP(sc); + struct ue_chain *c; + usbd_status err; + int i; + + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + return; + + CDCE_LOCK(sc); + cdce_reset(sc); + + if (usb_ether_tx_list_init(sc, &sc->cdce_cdata, + sc->cdce_udev) == ENOBUFS) { + device_printf(sc->cdce_dev, "tx list init failed\n"); + CDCE_UNLOCK(sc); + return; + } + + if (usb_ether_rx_list_init(sc, &sc->cdce_cdata, + sc->cdce_udev) == ENOBUFS) { + device_printf(sc->cdce_dev, "rx list init failed\n"); + CDCE_UNLOCK(sc); + return; + } + + /* Maybe set multicast / broadcast here??? */ + + err = usbd_open_pipe(sc->cdce_data_iface, sc->cdce_bulkin_no, + USBD_EXCLUSIVE_USE, &sc->cdce_bulkin_pipe); + if (err) { + device_printf(sc->cdce_dev, "open rx pipe failed: %s\n", + usbd_errstr(err)); + CDCE_UNLOCK(sc); + return; + } + + err = usbd_open_pipe(sc->cdce_data_iface, sc->cdce_bulkout_no, + USBD_EXCLUSIVE_USE, &sc->cdce_bulkout_pipe); + if (err) { + device_printf(sc->cdce_dev, "open tx pipe failed: %s\n", + usbd_errstr(err)); + CDCE_UNLOCK(sc); + return; + } + + for (i = 0; i < UE_RX_LIST_CNT; i++) { + c = &sc->cdce_cdata.ue_rx_chain[i]; + usbd_setup_xfer(c->ue_xfer, sc->cdce_bulkin_pipe, c, + mtod(c->ue_mbuf, char *), UE_BUFSZ, USBD_SHORT_XFER_OK, + USBD_NO_TIMEOUT, cdce_rxeof); + usbd_transfer(c->ue_xfer); + } + + ifp->if_drv_flags |= IFF_DRV_RUNNING; + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + + CDCE_UNLOCK(sc); + + return; +} + +static void +cdce_rxeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + struct ue_chain *c = priv; + struct cdce_softc *sc = c->ue_sc; + struct ifnet *ifp; + struct mbuf *m; + int total_len = 0; + + CDCE_LOCK(sc); + ifp = GET_IFP(sc); + + if (sc->cdce_dying || !(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + CDCE_UNLOCK(sc); + return; + } + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) { + CDCE_UNLOCK(sc); + return; + } + if (sc->cdce_rxeof_errors == 0) + device_printf(sc->cdce_dev, "usb error on rx: %s\n", + usbd_errstr(status)); + if (status == USBD_STALLED) + usbd_clear_endpoint_stall_async(sc->cdce_bulkin_pipe); + DELAY(sc->cdce_rxeof_errors * 10000); + sc->cdce_rxeof_errors++; + goto done; + } + + sc->cdce_rxeof_errors = 0; + + usbd_get_xfer_status(xfer, NULL, NULL, &total_len, NULL); + + if (sc->cdce_flags & CDCE_ZAURUS) + total_len -= 4; /* Strip off CRC added by Zaurus */ + + m = c->ue_mbuf; + + if (total_len < sizeof(struct ether_header)) { + ifp->if_ierrors++; + goto done; + } + + ifp->if_ipackets++; + m->m_pkthdr.rcvif = (struct ifnet *)&sc->q; + m->m_pkthdr.len = m->m_len = total_len; + + /* Put the packet on the special USB input queue. */ + usb_ether_input(m); + CDCE_UNLOCK(sc); + + return; + +done: + /* Setup new transfer. */ + usbd_setup_xfer(c->ue_xfer, sc->cdce_bulkin_pipe, c, + mtod(c->ue_mbuf, char *), + UE_BUFSZ, USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT, + cdce_rxeof); + usbd_transfer(c->ue_xfer); + CDCE_UNLOCK(sc); + + return; +} + +static void +cdce_txeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + struct ue_chain *c = priv; + struct cdce_softc *sc = c->ue_sc; + struct ifnet *ifp; + usbd_status err; + + CDCE_LOCK(sc); + ifp = GET_IFP(sc); + + if (sc->cdce_dying || + !(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + CDCE_UNLOCK(sc); + return; + } + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) { + CDCE_UNLOCK(sc); + return; + } + ifp->if_oerrors++; + device_printf(sc->cdce_dev, "usb error on tx: %s\n", + usbd_errstr(status)); + if (status == USBD_STALLED) + usbd_clear_endpoint_stall_async(sc->cdce_bulkout_pipe); + CDCE_UNLOCK(sc); + return; + } + + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + usbd_get_xfer_status(c->ue_xfer, NULL, NULL, NULL, &err); + + if (c->ue_mbuf != NULL) { + c->ue_mbuf->m_pkthdr.rcvif = ifp; + usb_tx_done(c->ue_mbuf); + c->ue_mbuf = NULL; + } + + if (err) + ifp->if_oerrors++; + else + ifp->if_opackets++; + + CDCE_UNLOCK(sc); + + return; +} + +static void +cdce_rxstart(struct ifnet *ifp) +{ + struct cdce_softc *sc; + struct ue_chain *c; + + sc = ifp->if_softc; + CDCE_LOCK(sc); + + if (sc->cdce_dying || !(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + CDCE_UNLOCK(sc); + return; + } + + c = &sc->cdce_cdata.ue_rx_chain[sc->cdce_cdata.ue_rx_prod]; + + c->ue_mbuf = usb_ether_newbuf(); + if (c->ue_mbuf == NULL) { + device_printf(sc->cdce_dev, "no memory for rx list " + "-- packet dropped!\n"); + ifp->if_ierrors++; + CDCE_UNLOCK(sc); + return; + } + + usbd_setup_xfer(c->ue_xfer, sc->cdce_bulkin_pipe, c, + mtod(c->ue_mbuf, char *), UE_BUFSZ, USBD_SHORT_XFER_OK, + USBD_NO_TIMEOUT, cdce_rxeof); + usbd_transfer(c->ue_xfer); + + CDCE_UNLOCK(sc); + return; +} + +static int +cdce_ifmedia_upd(struct ifnet *ifp) +{ + + /* no-op, cdce has only 1 possible media type */ + return 0; +} + +static void +cdce_ifmedia_sts(struct ifnet * const ifp, struct ifmediareq *req) +{ + + req->ifm_status = IFM_AVALID | IFM_ACTIVE; + req->ifm_active = IFM_ETHER | IFM_10_T; +} diff --git a/sys/legacy/dev/usb/if_cdcereg.h b/sys/legacy/dev/usb/if_cdcereg.h new file mode 100644 index 0000000..e658eab --- /dev/null +++ b/sys/legacy/dev/usb/if_cdcereg.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2003-2005 Craig Boston + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul, THE VOICES IN HIS HEAD OR + * THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _USB_IF_CDCEREG_H_ +#define _USB_IF_CDCEREG_H_ + +struct cdce_type { + struct usb_devno cdce_dev; + u_int16_t cdce_flags; +#define CDCE_ZAURUS 1 +#define CDCE_NO_UNION 2 +}; + +struct cdce_softc { + struct ifnet *cdce_ifp; +#define GET_IFP(sc) ((sc)->cdce_ifp) + struct ifmedia cdce_ifmedia; + + usbd_device_handle cdce_udev; + usbd_interface_handle cdce_data_iface; + int cdce_bulkin_no; + usbd_pipe_handle cdce_bulkin_pipe; + int cdce_bulkout_no; + usbd_pipe_handle cdce_bulkout_pipe; + char cdce_dying; + device_t cdce_dev; + + struct ue_cdata cdce_cdata; + struct timeval cdce_rx_notice; + int cdce_rxeof_errors; + + u_int16_t cdce_flags; + + struct mtx cdce_mtx; + + struct usb_qdat q; +}; + +/* We are still under Giant */ +#if 0 +#define CDCE_LOCK(_sc) mtx_lock(&(_sc)->cdce_mtx) +#define CDCE_UNLOCK(_sc) mtx_unlock(&(_sc)->cdce_mtx) +#else +#define CDCE_LOCK(_sc) +#define CDCE_UNLOCK(_sc) +#endif + +#endif /* _USB_IF_CDCEREG_H_ */ diff --git a/sys/legacy/dev/usb/if_cue.c b/sys/legacy/dev/usb/if_cue.c new file mode 100644 index 0000000..cd3a543 --- /dev/null +++ b/sys/legacy/dev/usb/if_cue.c @@ -0,0 +1,1074 @@ +/*- + * Copyright (c) 1997, 1998, 1999, 2000 + * Bill Paul <wpaul@ee.columbia.edu>. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * CATC USB-EL1210A USB to ethernet driver. Used in the CATC Netmate + * adapters and others. + * + * Written by Bill Paul <wpaul@ee.columbia.edu> + * Electrical Engineering Department + * Columbia University, New York City + */ + +/* + * The CATC USB-EL1210A provides USB ethernet support at 10Mbps. The + * RX filter uses a 512-bit multicast hash table, single perfect entry + * for the station address, and promiscuous mode. Unlike the ADMtek + * and KLSI chips, the CATC ASIC supports read and write combining + * mode where multiple packets can be transfered using a single bulk + * transaction, which helps performance a great deal. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/sockio.h> +#include <sys/mbuf.h> +#include <sys/malloc.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/socket.h> + +#include <net/if.h> +#include <net/if_arp.h> +#include <net/ethernet.h> +#include <net/if_dl.h> +#include <net/if_types.h> + +#include <net/bpf.h> + +#include <sys/bus.h> +#include <machine/bus.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usbdivar.h> +#include "usbdevs.h" +#include <dev/usb/usb_ethersubr.h> + +#include <dev/usb/if_cuereg.h> + +/* + * Various supported device vendors/products. + */ +static struct cue_type cue_devs[] = { + { USB_VENDOR_CATC, USB_PRODUCT_CATC_NETMATE }, + { USB_VENDOR_CATC, USB_PRODUCT_CATC_NETMATE2 }, + { USB_VENDOR_SMARTBRIDGES, USB_PRODUCT_SMARTBRIDGES_SMARTLINK }, + /* Belkin F5U111 adapter covered by NETMATE entry */ + { 0, 0 } +}; + +static device_probe_t cue_match; +static device_attach_t cue_attach; +static device_detach_t cue_detach; +static device_shutdown_t cue_shutdown; + +static int cue_encap(struct cue_softc *, struct mbuf *, int); +static void cue_rxeof(usbd_xfer_handle, usbd_private_handle, usbd_status); +static void cue_txeof(usbd_xfer_handle, usbd_private_handle, usbd_status); +static void cue_tick(void *); +static void cue_rxstart(struct ifnet *); +static void cue_start(struct ifnet *); +static int cue_ioctl(struct ifnet *, u_long, caddr_t); +static void cue_init(void *); +static void cue_stop(struct cue_softc *); +static void cue_watchdog(struct ifnet *); + +static void cue_setmulti(struct cue_softc *); +static uint32_t cue_mchash(const uint8_t *); +static void cue_reset(struct cue_softc *); + +static int cue_csr_read_1(struct cue_softc *, int); +static int cue_csr_write_1(struct cue_softc *, int, int); +static int cue_csr_read_2(struct cue_softc *, int); +#ifdef notdef +static int cue_csr_write_2(struct cue_softc *, int, int); +#endif +static int cue_mem(struct cue_softc *, int, int, void *, int); +static int cue_getmac(struct cue_softc *, void *); + +static device_method_t cue_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, cue_match), + DEVMETHOD(device_attach, cue_attach), + DEVMETHOD(device_detach, cue_detach), + DEVMETHOD(device_shutdown, cue_shutdown), + + { 0, 0 } +}; + +static driver_t cue_driver = { + "cue", + cue_methods, + sizeof(struct cue_softc) +}; + +static devclass_t cue_devclass; + +DRIVER_MODULE(cue, uhub, cue_driver, cue_devclass, usbd_driver_load, 0); +MODULE_DEPEND(cue, usb, 1, 1, 1); +MODULE_DEPEND(cue, ether, 1, 1, 1); + +#define CUE_SETBIT(sc, reg, x) \ + cue_csr_write_1(sc, reg, cue_csr_read_1(sc, reg) | (x)) + +#define CUE_CLRBIT(sc, reg, x) \ + cue_csr_write_1(sc, reg, cue_csr_read_1(sc, reg) & ~(x)) + +static int +cue_csr_read_1(struct cue_softc *sc, int reg) +{ + usb_device_request_t req; + usbd_status err; + u_int8_t val = 0; + + if (sc->cue_dying) + return(0); + + CUE_LOCK(sc); + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = CUE_CMD_READREG; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, 1); + + err = usbd_do_request(sc->cue_udev, &req, &val); + + CUE_UNLOCK(sc); + + if (err) + return(0); + + return(val); +} + +static int +cue_csr_read_2(struct cue_softc *sc, int reg) +{ + usb_device_request_t req; + usbd_status err; + u_int16_t val = 0; + + if (sc->cue_dying) + return(0); + + CUE_LOCK(sc); + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = CUE_CMD_READREG; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, 2); + + err = usbd_do_request(sc->cue_udev, &req, &val); + + CUE_UNLOCK(sc); + + if (err) + return(0); + + return(val); +} + +static int +cue_csr_write_1(struct cue_softc *sc, int reg, int val) +{ + usb_device_request_t req; + usbd_status err; + + if (sc->cue_dying) + return(0); + + CUE_LOCK(sc); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = CUE_CMD_WRITEREG; + USETW(req.wValue, val); + USETW(req.wIndex, reg); + USETW(req.wLength, 0); + + err = usbd_do_request(sc->cue_udev, &req, NULL); + + CUE_UNLOCK(sc); + + if (err) + return(-1); + + return(0); +} + +#ifdef notdef +static int +cue_csr_write_2(struct cue_softc *sc, int reg, int val) +{ + usb_device_request_t req; + usbd_status err; + + if (sc->cue_dying) + return(0); + + CUE_LOCK(sc); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = CUE_CMD_WRITEREG; + USETW(req.wValue, val); + USETW(req.wIndex, reg); + USETW(req.wLength, 0); + + err = usbd_do_request(sc->cue_udev, &req, NULL); + + CUE_UNLOCK(sc); + + if (err) + return(-1); + + return(0); +} +#endif + +static int +cue_mem(struct cue_softc *sc, int cmd, int addr, void *buf, int len) +{ + usb_device_request_t req; + usbd_status err; + + if (sc->cue_dying) + return(0); + + CUE_LOCK(sc); + + if (cmd == CUE_CMD_READSRAM) + req.bmRequestType = UT_READ_VENDOR_DEVICE; + else + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = cmd; + USETW(req.wValue, 0); + USETW(req.wIndex, addr); + USETW(req.wLength, len); + + err = usbd_do_request(sc->cue_udev, &req, buf); + + CUE_UNLOCK(sc); + + if (err) + return(-1); + + return(0); +} + +static int +cue_getmac(struct cue_softc *sc, void *buf) +{ + usb_device_request_t req; + usbd_status err; + + if (sc->cue_dying) + return(0); + + CUE_LOCK(sc); + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = CUE_CMD_GET_MACADDR; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, ETHER_ADDR_LEN); + + err = usbd_do_request(sc->cue_udev, &req, buf); + + CUE_UNLOCK(sc); + + if (err) { + device_printf(sc->cue_dev, "read MAC address failed\n"); + return(-1); + } + + return(0); +} + +#define CUE_BITS 9 + +static uint32_t +cue_mchash(const uint8_t *addr) +{ + uint32_t crc; + + /* Compute CRC for the address value. */ + crc = ether_crc32_le(addr, ETHER_ADDR_LEN); + + return (crc & ((1 << CUE_BITS) - 1)); +} + +static void +cue_setmulti(struct cue_softc *sc) +{ + struct ifnet *ifp; + struct ifmultiaddr *ifma; + u_int32_t h = 0, i; + + ifp = sc->cue_ifp; + + if (ifp->if_flags & IFF_ALLMULTI || ifp->if_flags & IFF_PROMISC) { + for (i = 0; i < CUE_MCAST_TABLE_LEN; i++) + sc->cue_mctab[i] = 0xFF; + cue_mem(sc, CUE_CMD_WRITESRAM, CUE_MCAST_TABLE_ADDR, + &sc->cue_mctab, CUE_MCAST_TABLE_LEN); + return; + } + + /* first, zot all the existing hash bits */ + for (i = 0; i < CUE_MCAST_TABLE_LEN; i++) + sc->cue_mctab[i] = 0; + + /* now program new ones */ + IF_ADDR_LOCK(ifp); + TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) + { + if (ifma->ifma_addr->sa_family != AF_LINK) + continue; + h = cue_mchash(LLADDR((struct sockaddr_dl *)ifma->ifma_addr)); + sc->cue_mctab[h >> 3] |= 1 << (h & 0x7); + } + IF_ADDR_UNLOCK(ifp); + + /* + * Also include the broadcast address in the filter + * so we can receive broadcast frames. + */ + if (ifp->if_flags & IFF_BROADCAST) { + h = cue_mchash(ifp->if_broadcastaddr); + sc->cue_mctab[h >> 3] |= 1 << (h & 0x7); + } + + cue_mem(sc, CUE_CMD_WRITESRAM, CUE_MCAST_TABLE_ADDR, + &sc->cue_mctab, CUE_MCAST_TABLE_LEN); + + return; +} + +static void +cue_reset(struct cue_softc *sc) +{ + usb_device_request_t req; + usbd_status err; + + if (sc->cue_dying) + return; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = CUE_CMD_RESET; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + err = usbd_do_request(sc->cue_udev, &req, NULL); + if (err) + device_printf(sc->cue_dev, "reset failed\n"); + + /* Wait a little while for the chip to get its brains in order. */ + DELAY(1000); + return; +} + +/* + * Probe for a Pegasus chip. + */ +static int +cue_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + struct cue_type *t; + + if (!uaa->iface) + return(UMATCH_NONE); + + t = cue_devs; + while(t->cue_vid) { + if (uaa->vendor == t->cue_vid && + uaa->product == t->cue_did) { + return(UMATCH_VENDOR_PRODUCT); + } + t++; + } + + return(UMATCH_NONE); +} + +/* + * Attach the interface. Allocate softc structures, do ifmedia + * setup and ethernet/BPF attach. + */ +static int +cue_attach(device_t self) +{ + struct cue_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + u_char eaddr[ETHER_ADDR_LEN]; + struct ifnet *ifp; + usb_interface_descriptor_t *id; + usb_endpoint_descriptor_t *ed; + int i; + + sc->cue_dev = self; + sc->cue_iface = uaa->iface; + sc->cue_udev = uaa->device; + + if (usbd_set_config_no(sc->cue_udev, CUE_CONFIG_NO, 0)) { + device_printf(sc->cue_dev, "getting interface handle failed\n"); + return ENXIO; + } + + id = usbd_get_interface_descriptor(uaa->iface); + + /* Find endpoints. */ + for (i = 0; i < id->bNumEndpoints; i++) { + ed = usbd_interface2endpoint_descriptor(uaa->iface, i); + if (!ed) { + device_printf(sc->cue_dev, "couldn't get ep %d\n", i); + return ENXIO; + } + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + sc->cue_ed[CUE_ENDPT_RX] = ed->bEndpointAddress; + } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + sc->cue_ed[CUE_ENDPT_TX] = ed->bEndpointAddress; + } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_INTERRUPT) { + sc->cue_ed[CUE_ENDPT_INTR] = ed->bEndpointAddress; + } + } + + mtx_init(&sc->cue_mtx, device_get_nameunit(self), MTX_NETWORK_LOCK, + MTX_DEF | MTX_RECURSE); + CUE_LOCK(sc); + +#ifdef notdef + /* Reset the adapter. */ + cue_reset(sc); +#endif + /* + * Get station address. + */ + cue_getmac(sc, &eaddr); + + ifp = sc->cue_ifp = if_alloc(IFT_ETHER); + if (ifp == NULL) { + device_printf(sc->cue_dev, "can not if_alloc()\n"); + CUE_UNLOCK(sc); + mtx_destroy(&sc->cue_mtx); + return ENXIO; + } + ifp->if_softc = sc; + if_initname(ifp, "cue", device_get_unit(sc->cue_dev)); + ifp->if_mtu = ETHERMTU; + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST | + IFF_NEEDSGIANT; + ifp->if_ioctl = cue_ioctl; + ifp->if_start = cue_start; + ifp->if_watchdog = cue_watchdog; + ifp->if_init = cue_init; + ifp->if_baudrate = 10000000; + ifp->if_snd.ifq_maxlen = IFQ_MAXLEN; + + sc->cue_qdat.ifp = ifp; + sc->cue_qdat.if_rxstart = cue_rxstart; + + /* + * Call MI attach routine. + */ + ether_ifattach(ifp, eaddr); + callout_handle_init(&sc->cue_stat_ch); + usb_register_netisr(); + sc->cue_dying = 0; + + CUE_UNLOCK(sc); + return 0; +} + +static int +cue_detach(device_t dev) +{ + struct cue_softc *sc; + struct ifnet *ifp; + + sc = device_get_softc(dev); + CUE_LOCK(sc); + ifp = sc->cue_ifp; + + sc->cue_dying = 1; + untimeout(cue_tick, sc, sc->cue_stat_ch); + ether_ifdetach(ifp); + if_free(ifp); + + if (sc->cue_ep[CUE_ENDPT_TX] != NULL) + usbd_abort_pipe(sc->cue_ep[CUE_ENDPT_TX]); + if (sc->cue_ep[CUE_ENDPT_RX] != NULL) + usbd_abort_pipe(sc->cue_ep[CUE_ENDPT_RX]); + if (sc->cue_ep[CUE_ENDPT_INTR] != NULL) + usbd_abort_pipe(sc->cue_ep[CUE_ENDPT_INTR]); + + CUE_UNLOCK(sc); + mtx_destroy(&sc->cue_mtx); + + return(0); +} + +static void +cue_rxstart(struct ifnet *ifp) +{ + struct cue_softc *sc; + struct ue_chain *c; + + sc = ifp->if_softc; + CUE_LOCK(sc); + c = &sc->cue_cdata.ue_rx_chain[sc->cue_cdata.ue_rx_prod]; + + c->ue_mbuf = usb_ether_newbuf(); + if (c->ue_mbuf == NULL) { + device_printf(sc->cue_dev, "no memory for rx list " + "-- packet dropped!\n"); + ifp->if_ierrors++; + CUE_UNLOCK(sc); + return; + } + + /* Setup new transfer. */ + usbd_setup_xfer(c->ue_xfer, sc->cue_ep[CUE_ENDPT_RX], + c, mtod(c->ue_mbuf, char *), UE_BUFSZ, USBD_SHORT_XFER_OK, + USBD_NO_TIMEOUT, cue_rxeof); + usbd_transfer(c->ue_xfer); + CUE_UNLOCK(sc); + + return; +} + +/* + * A frame has been uploaded: pass the resulting mbuf chain up to + * the higher level protocols. + */ +static void +cue_rxeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + struct cue_softc *sc; + struct ue_chain *c; + struct mbuf *m; + struct ifnet *ifp; + int total_len = 0; + u_int16_t len; + + c = priv; + sc = c->ue_sc; + CUE_LOCK(sc); + ifp = sc->cue_ifp; + + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + CUE_UNLOCK(sc); + return; + } + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) { + CUE_UNLOCK(sc); + return; + } + if (usbd_ratecheck(&sc->cue_rx_notice)) + device_printf(sc->cue_dev, "usb error on rx: %s\n", + usbd_errstr(status)); + if (status == USBD_STALLED) + usbd_clear_endpoint_stall(sc->cue_ep[CUE_ENDPT_RX]); + goto done; + } + + usbd_get_xfer_status(xfer, NULL, NULL, &total_len, NULL); + + m = c->ue_mbuf; + len = *mtod(m, u_int16_t *); + + /* No errors; receive the packet. */ + total_len = len; + + if (len < sizeof(struct ether_header)) { + ifp->if_ierrors++; + goto done; + } + + ifp->if_ipackets++; + m_adj(m, sizeof(u_int16_t)); + m->m_pkthdr.rcvif = (void *)&sc->cue_qdat; + m->m_pkthdr.len = m->m_len = total_len; + + /* Put the packet on the special USB input queue. */ + usb_ether_input(m); + CUE_UNLOCK(sc); + + return; +done: + /* Setup new transfer. */ + usbd_setup_xfer(c->ue_xfer, sc->cue_ep[CUE_ENDPT_RX], + c, mtod(c->ue_mbuf, char *), UE_BUFSZ, USBD_SHORT_XFER_OK, + USBD_NO_TIMEOUT, cue_rxeof); + usbd_transfer(c->ue_xfer); + CUE_UNLOCK(sc); + + return; +} + +/* + * A frame was downloaded to the chip. It's safe for us to clean up + * the list buffers. + */ + +static void +cue_txeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + struct cue_softc *sc; + struct ue_chain *c; + struct ifnet *ifp; + usbd_status err; + + c = priv; + sc = c->ue_sc; + CUE_LOCK(sc); + ifp = sc->cue_ifp; + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) { + CUE_UNLOCK(sc); + return; + } + device_printf(sc->cue_dev, "usb error on tx: %s\n", + usbd_errstr(status)); + if (status == USBD_STALLED) + usbd_clear_endpoint_stall(sc->cue_ep[CUE_ENDPT_TX]); + CUE_UNLOCK(sc); + return; + } + + ifp->if_timer = 0; + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + usbd_get_xfer_status(c->ue_xfer, NULL, NULL, NULL, &err); + + if (c->ue_mbuf != NULL) { + c->ue_mbuf->m_pkthdr.rcvif = ifp; + usb_tx_done(c->ue_mbuf); + c->ue_mbuf = NULL; + } + + if (err) + ifp->if_oerrors++; + else + ifp->if_opackets++; + + CUE_UNLOCK(sc); + + return; +} + +static void +cue_tick(void *xsc) +{ + struct cue_softc *sc; + struct ifnet *ifp; + + sc = xsc; + + if (sc == NULL) + return; + + CUE_LOCK(sc); + + ifp = sc->cue_ifp; + + ifp->if_collisions += cue_csr_read_2(sc, CUE_TX_SINGLECOLL); + ifp->if_collisions += cue_csr_read_2(sc, CUE_TX_MULTICOLL); + ifp->if_collisions += cue_csr_read_2(sc, CUE_TX_EXCESSCOLL); + + if (cue_csr_read_2(sc, CUE_RX_FRAMEERR)) + ifp->if_ierrors++; + + sc->cue_stat_ch = timeout(cue_tick, sc, hz); + + CUE_UNLOCK(sc); + + return; +} + +static int +cue_encap(struct cue_softc *sc, struct mbuf *m, int idx) +{ + int total_len; + struct ue_chain *c; + usbd_status err; + + c = &sc->cue_cdata.ue_tx_chain[idx]; + + /* + * Copy the mbuf data into a contiguous buffer, leaving two + * bytes at the beginning to hold the frame length. + */ + m_copydata(m, 0, m->m_pkthdr.len, c->ue_buf + 2); + c->ue_mbuf = m; + + total_len = m->m_pkthdr.len + 2; + + /* The first two bytes are the frame length */ + c->ue_buf[0] = (u_int8_t)m->m_pkthdr.len; + c->ue_buf[1] = (u_int8_t)(m->m_pkthdr.len >> 8); + + usbd_setup_xfer(c->ue_xfer, sc->cue_ep[CUE_ENDPT_TX], + c, c->ue_buf, total_len, 0, 10000, cue_txeof); + + /* Transmit */ + err = usbd_transfer(c->ue_xfer); + if (err != USBD_IN_PROGRESS) { + cue_stop(sc); + return(EIO); + } + + sc->cue_cdata.ue_tx_cnt++; + + return(0); +} + +static void +cue_start(struct ifnet *ifp) +{ + struct cue_softc *sc; + struct mbuf *m_head = NULL; + + sc = ifp->if_softc; + CUE_LOCK(sc); + + if (ifp->if_drv_flags & IFF_DRV_OACTIVE) { + CUE_UNLOCK(sc); + return; + } + + IF_DEQUEUE(&ifp->if_snd, m_head); + if (m_head == NULL) { + CUE_UNLOCK(sc); + return; + } + + if (cue_encap(sc, m_head, 0)) { + IF_PREPEND(&ifp->if_snd, m_head); + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + CUE_UNLOCK(sc); + return; + } + + /* + * If there's a BPF listener, bounce a copy of this frame + * to him. + */ + BPF_MTAP(ifp, m_head); + + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + + /* + * Set a timeout in case the chip goes out to lunch. + */ + ifp->if_timer = 5; + CUE_UNLOCK(sc); + + return; +} + +static void +cue_init(void *xsc) +{ + struct cue_softc *sc = xsc; + struct ifnet *ifp = sc->cue_ifp; + struct ue_chain *c; + usbd_status err; + int i; + + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + return; + + CUE_LOCK(sc); + + /* + * Cancel pending I/O and free all RX/TX buffers. + */ +#ifdef foo + cue_reset(sc); +#endif + + /* Set MAC address */ + for (i = 0; i < ETHER_ADDR_LEN; i++) + cue_csr_write_1(sc, CUE_PAR0 - i, IF_LLADDR(sc->cue_ifp)[i]); + + /* Enable RX logic. */ + cue_csr_write_1(sc, CUE_ETHCTL, CUE_ETHCTL_RX_ON|CUE_ETHCTL_MCAST_ON); + + /* If we want promiscuous mode, set the allframes bit. */ + if (ifp->if_flags & IFF_PROMISC) { + CUE_SETBIT(sc, CUE_ETHCTL, CUE_ETHCTL_PROMISC); + } else { + CUE_CLRBIT(sc, CUE_ETHCTL, CUE_ETHCTL_PROMISC); + } + + /* Init TX ring. */ + if (usb_ether_tx_list_init(sc, &sc->cue_cdata, + sc->cue_udev) == ENOBUFS) { + device_printf(sc->cue_dev, "tx list init failed\n"); + CUE_UNLOCK(sc); + return; + } + + /* Init RX ring. */ + if (usb_ether_rx_list_init(sc, &sc->cue_cdata, + sc->cue_udev) == ENOBUFS) { + device_printf(sc->cue_dev, "rx list init failed\n"); + CUE_UNLOCK(sc); + return; + } + + /* Load the multicast filter. */ + cue_setmulti(sc); + + /* + * Set the number of RX and TX buffers that we want + * to reserve inside the ASIC. + */ + cue_csr_write_1(sc, CUE_RX_BUFPKTS, CUE_RX_FRAMES); + cue_csr_write_1(sc, CUE_TX_BUFPKTS, CUE_TX_FRAMES); + + /* Set advanced operation modes. */ + cue_csr_write_1(sc, CUE_ADVANCED_OPMODES, + CUE_AOP_EMBED_RXLEN|0x01); /* 1 wait state */ + + /* Program the LED operation. */ + cue_csr_write_1(sc, CUE_LEDCTL, CUE_LEDCTL_FOLLOW_LINK); + + /* Open RX and TX pipes. */ + err = usbd_open_pipe(sc->cue_iface, sc->cue_ed[CUE_ENDPT_RX], + USBD_EXCLUSIVE_USE, &sc->cue_ep[CUE_ENDPT_RX]); + if (err) { + device_printf(sc->cue_dev, "open rx pipe failed: %s\n", + usbd_errstr(err)); + CUE_UNLOCK(sc); + return; + } + err = usbd_open_pipe(sc->cue_iface, sc->cue_ed[CUE_ENDPT_TX], + USBD_EXCLUSIVE_USE, &sc->cue_ep[CUE_ENDPT_TX]); + if (err) { + device_printf(sc->cue_dev, "open tx pipe failed: %s\n", + usbd_errstr(err)); + CUE_UNLOCK(sc); + return; + } + + /* Start up the receive pipe. */ + for (i = 0; i < UE_RX_LIST_CNT; i++) { + c = &sc->cue_cdata.ue_rx_chain[i]; + usbd_setup_xfer(c->ue_xfer, sc->cue_ep[CUE_ENDPT_RX], + c, mtod(c->ue_mbuf, char *), UE_BUFSZ, + USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT, cue_rxeof); + usbd_transfer(c->ue_xfer); + } + + ifp->if_drv_flags |= IFF_DRV_RUNNING; + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + + CUE_UNLOCK(sc); + + sc->cue_stat_ch = timeout(cue_tick, sc, hz); + + return; +} + +static int +cue_ioctl(struct ifnet *ifp, u_long command, caddr_t data) +{ + struct cue_softc *sc = ifp->if_softc; + int error = 0; + + CUE_LOCK(sc); + + switch(command) { + case SIOCSIFFLAGS: + if (ifp->if_flags & IFF_UP) { + if (ifp->if_drv_flags & IFF_DRV_RUNNING && + ifp->if_flags & IFF_PROMISC && + !(sc->cue_if_flags & IFF_PROMISC)) { + CUE_SETBIT(sc, CUE_ETHCTL, CUE_ETHCTL_PROMISC); + cue_setmulti(sc); + } else if (ifp->if_drv_flags & IFF_DRV_RUNNING && + !(ifp->if_flags & IFF_PROMISC) && + sc->cue_if_flags & IFF_PROMISC) { + CUE_CLRBIT(sc, CUE_ETHCTL, CUE_ETHCTL_PROMISC); + cue_setmulti(sc); + } else if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) + cue_init(sc); + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + cue_stop(sc); + } + sc->cue_if_flags = ifp->if_flags; + error = 0; + break; + case SIOCADDMULTI: + case SIOCDELMULTI: + cue_setmulti(sc); + error = 0; + break; + default: + error = ether_ioctl(ifp, command, data); + break; + } + + CUE_UNLOCK(sc); + + return(error); +} + +static void +cue_watchdog(struct ifnet *ifp) +{ + struct cue_softc *sc; + struct ue_chain *c; + usbd_status stat; + + sc = ifp->if_softc; + CUE_LOCK(sc); + + ifp->if_oerrors++; + device_printf(sc->cue_dev, "watchdog timeout\n"); + + c = &sc->cue_cdata.ue_tx_chain[0]; + usbd_get_xfer_status(c->ue_xfer, NULL, NULL, NULL, &stat); + cue_txeof(c->ue_xfer, c, stat); + + if (ifp->if_snd.ifq_head != NULL) + cue_start(ifp); + CUE_UNLOCK(sc); + + return; +} + +/* + * Stop the adapter and free any mbufs allocated to the + * RX and TX lists. + */ +static void +cue_stop(struct cue_softc *sc) +{ + usbd_status err; + struct ifnet *ifp; + + CUE_LOCK(sc); + + ifp = sc->cue_ifp; + ifp->if_timer = 0; + + cue_csr_write_1(sc, CUE_ETHCTL, 0); + cue_reset(sc); + untimeout(cue_tick, sc, sc->cue_stat_ch); + + /* Stop transfers. */ + if (sc->cue_ep[CUE_ENDPT_RX] != NULL) { + err = usbd_abort_pipe(sc->cue_ep[CUE_ENDPT_RX]); + if (err) { + device_printf(sc->cue_dev, "abort rx pipe failed: %s\n", + usbd_errstr(err)); + } + err = usbd_close_pipe(sc->cue_ep[CUE_ENDPT_RX]); + if (err) { + device_printf(sc->cue_dev, "close rx pipe failed: %s\n", + usbd_errstr(err)); + } + sc->cue_ep[CUE_ENDPT_RX] = NULL; + } + + if (sc->cue_ep[CUE_ENDPT_TX] != NULL) { + err = usbd_abort_pipe(sc->cue_ep[CUE_ENDPT_TX]); + if (err) { + device_printf(sc->cue_dev, "abort tx pipe failed: %s\n", + usbd_errstr(err)); + } + err = usbd_close_pipe(sc->cue_ep[CUE_ENDPT_TX]); + if (err) { + device_printf(sc->cue_dev, "close tx pipe failed: %s\n", + usbd_errstr(err)); + } + sc->cue_ep[CUE_ENDPT_TX] = NULL; + } + + if (sc->cue_ep[CUE_ENDPT_INTR] != NULL) { + err = usbd_abort_pipe(sc->cue_ep[CUE_ENDPT_INTR]); + if (err) { + device_printf(sc->cue_dev, "abort intr pipe failed: %s\n", + usbd_errstr(err)); + } + err = usbd_close_pipe(sc->cue_ep[CUE_ENDPT_INTR]); + if (err) { + device_printf(sc->cue_dev, "close intr pipe failed: %s\n", + usbd_errstr(err)); + } + sc->cue_ep[CUE_ENDPT_INTR] = NULL; + } + + /* Free RX resources. */ + usb_ether_rx_list_free(&sc->cue_cdata); + /* Free TX resources. */ + usb_ether_tx_list_free(&sc->cue_cdata); + + ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); + CUE_UNLOCK(sc); + + return; +} + +/* + * Stop all chip I/O so that the kernel's probe routines don't + * get confused by errant DMAs when rebooting. + */ +static int +cue_shutdown(device_t dev) +{ + struct cue_softc *sc; + + sc = device_get_softc(dev); + + CUE_LOCK(sc); + cue_reset(sc); + cue_stop(sc); + CUE_UNLOCK(sc); + + return (0); +} diff --git a/sys/legacy/dev/usb/if_cuereg.h b/sys/legacy/dev/usb/if_cuereg.h new file mode 100644 index 0000000..4c81653 --- /dev/null +++ b/sys/legacy/dev/usb/if_cuereg.h @@ -0,0 +1,168 @@ +/*- + * Copyright (c) 1997, 1998, 1999, 2000 + * Bill Paul <wpaul@ee.columbia.edu>. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * Definitions for the CATC Netmate II USB to ethernet controller. + */ + + +/* + * Vendor specific control commands. + */ +#define CUE_CMD_RESET 0xF4 +#define CUE_CMD_GET_MACADDR 0xF2 +#define CUE_CMD_WRITEREG 0xFA +#define CUE_CMD_READREG 0xFB +#define CUE_CMD_READSRAM 0xF1 +#define CUE_CMD_WRITESRAM 0xFC + +/* + * Internal registers + */ +#define CUE_TX_BUFCNT 0x20 +#define CUE_RX_BUFCNT 0x21 +#define CUE_ADVANCED_OPMODES 0x22 +#define CUE_TX_BUFPKTS 0x23 +#define CUE_RX_BUFPKTS 0x24 +#define CUE_RX_MAXCHAIN 0x25 + +#define CUE_ETHCTL 0x60 +#define CUE_ETHSTS 0x61 +#define CUE_PAR5 0x62 +#define CUE_PAR4 0x63 +#define CUE_PAR3 0x64 +#define CUE_PAR2 0x65 +#define CUE_PAR1 0x66 +#define CUE_PAR0 0x67 + +/* Error counters, all 16 bits wide. */ +#define CUE_TX_SINGLECOLL 0x69 +#define CUE_TX_MULTICOLL 0x6B +#define CUE_TX_EXCESSCOLL 0x6D +#define CUE_RX_FRAMEERR 0x6F + +#define CUE_LEDCTL 0x81 + +/* Advenced operating mode register */ +#define CUE_AOP_SRAMWAITS 0x03 +#define CUE_AOP_EMBED_RXLEN 0x08 +#define CUE_AOP_RXCOMBINE 0x10 +#define CUE_AOP_TXCOMBINE 0x20 +#define CUE_AOP_EVEN_PKT_READS 0x40 +#define CUE_AOP_LOOPBK 0x80 + +/* Ethernet control register */ +#define CUE_ETHCTL_RX_ON 0x01 +#define CUE_ETHCTL_LINK_POLARITY 0x02 +#define CUE_ETHCTL_LINK_FORCE_OK 0x04 +#define CUE_ETHCTL_MCAST_ON 0x08 +#define CUE_ETHCTL_PROMISC 0x10 + +/* Ethernet status register */ +#define CUE_ETHSTS_NO_CARRIER 0x01 +#define CUE_ETHSTS_LATECOLL 0x02 +#define CUE_ETHSTS_EXCESSCOLL 0x04 +#define CUE_ETHSTS_TXBUF_AVAIL 0x08 +#define CUE_ETHSTS_BAD_POLARITY 0x10 +#define CUE_ETHSTS_LINK_OK 0x20 + +/* LED control register */ +#define CUE_LEDCTL_BLINK_1X 0x00 +#define CUE_LEDCTL_BLINK_2X 0x01 +#define CUE_LEDCTL_BLINK_QUARTER_ON 0x02 +#define CUE_LEDCTL_BLINK_QUARTER_OFF 0x03 +#define CUE_LEDCTL_OFF 0x04 +#define CUE_LEDCTL_FOLLOW_LINK 0x08 + +/* + * Address in ASIC's internal SRAM where the + * multicast hash table lives. The table is 64 bytes long, + * giving us a 512-bit table. We have to set the bit that + * corresponds to the broadcast address in order to enable + * reception of broadcast frames. + */ +#define CUE_MCAST_TABLE_ADDR 0xFA80 +#define CUE_MCAST_TABLE_LEN 64 + +#define CUE_TIMEOUT 1000 +#define CUE_MIN_FRAMELEN 60 +#define CUE_RX_FRAMES 1 +#define CUE_TX_FRAMES 1 + +#define CUE_CTL_READ 0x01 +#define CUE_CTL_WRITE 0x02 + +#define CUE_CONFIG_NO 1 + +/* + * The interrupt endpoint is currently unused + * by the KLSI part. + */ +#define CUE_ENDPT_RX 0x0 +#define CUE_ENDPT_TX 0x1 +#define CUE_ENDPT_INTR 0x2 +#define CUE_ENDPT_MAX 0x3 + +struct cue_type { + u_int16_t cue_vid; + u_int16_t cue_did; +}; + +#define CUE_INC(x, y) (x) = (x + 1) % y + +struct cue_softc { + struct ifnet *cue_ifp; + device_t cue_dev; + usbd_device_handle cue_udev; + usbd_interface_handle cue_iface; + int cue_ed[CUE_ENDPT_MAX]; + usbd_pipe_handle cue_ep[CUE_ENDPT_MAX]; + u_int8_t cue_mctab[CUE_MCAST_TABLE_LEN]; + int cue_if_flags; + u_int16_t cue_rxfilt; + struct ue_cdata cue_cdata; + struct callout_handle cue_stat_ch; + struct mtx cue_mtx; + char cue_dying; + struct timeval cue_rx_notice; + struct usb_qdat cue_qdat; +}; + +#if 0 +#define CUE_LOCK(_sc) mtx_lock(&(_sc)->cue_mtx) +#define CUE_UNLOCK(_sc) mtx_unlock(&(_sc)->cue_mtx) +#else +#define CUE_LOCK(_sc) +#define CUE_UNLOCK(_sc) +#endif diff --git a/sys/legacy/dev/usb/if_kue.c b/sys/legacy/dev/usb/if_kue.c new file mode 100644 index 0000000..f68eaa8 --- /dev/null +++ b/sys/legacy/dev/usb/if_kue.c @@ -0,0 +1,1024 @@ +/*- + * Copyright (c) 1997, 1998, 1999, 2000 + * Bill Paul <wpaul@ee.columbia.edu>. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * Kawasaki LSI KL5KUSB101B USB to ethernet adapter driver. + * + * Written by Bill Paul <wpaul@ee.columbia.edu> + * Electrical Engineering Department + * Columbia University, New York City + */ + +/* + * The KLSI USB to ethernet adapter chip contains an USB serial interface, + * ethernet MAC and embedded microcontroller (called the QT Engine). + * The chip must have firmware loaded into it before it will operate. + * Packets are passed between the chip and host via bulk transfers. + * There is an interrupt endpoint mentioned in the software spec, however + * it's currently unused. This device is 10Mbps half-duplex only, hence + * there is no media selection logic. The MAC supports a 128 entry + * multicast filter, though the exact size of the filter can depend + * on the firmware. Curiously, while the software spec describes various + * ethernet statistics counters, my sample adapter and firmware combination + * claims not to support any statistics counters at all. + * + * Note that once we load the firmware in the device, we have to be + * careful not to load it again: if you restart your computer but + * leave the adapter attached to the USB controller, it may remain + * powered on and retain its firmware. In this case, we don't need + * to load the firmware a second time. + * + * Special thanks to Rob Furr for providing an ADS Technologies + * adapter for development and testing. No monkeys were harmed during + * the development of this driver. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/sockio.h> +#include <sys/mbuf.h> +#include <sys/malloc.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/socket.h> + +#include <net/if.h> +#include <net/if_arp.h> +#include <net/ethernet.h> +#include <net/if_dl.h> +#include <net/if_media.h> +#include <net/if_types.h> + +#include <net/bpf.h> + +#include <sys/bus.h> +#include <machine/bus.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usbdivar.h> +#include "usbdevs.h" +#include <dev/usb/usb_ethersubr.h> + +#include <dev/usb/if_kuereg.h> +#include <dev/usb/kue_fw.h> + +MODULE_DEPEND(kue, usb, 1, 1, 1); +MODULE_DEPEND(kue, ether, 1, 1, 1); + +/* + * Various supported device vendors/products. + */ +static struct kue_type kue_devs[] = { + { USB_VENDOR_3COM, USB_PRODUCT_3COM_3C19250 }, + { USB_VENDOR_3COM, USB_PRODUCT_3COM_3C460 }, + { USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_URE450 }, + { USB_VENDOR_ADS, USB_PRODUCT_ADS_UBS10BT }, + { USB_VENDOR_ADS, USB_PRODUCT_ADS_UBS10BTX }, + { USB_VENDOR_AOX, USB_PRODUCT_AOX_USB101 }, + { USB_VENDOR_ASANTE, USB_PRODUCT_ASANTE_EA }, + { USB_VENDOR_ATEN, USB_PRODUCT_ATEN_UC10T }, + { USB_VENDOR_ATEN, USB_PRODUCT_ATEN_DSB650C }, + { USB_VENDOR_COREGA, USB_PRODUCT_COREGA_ETHER_USB_T }, + { USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DSB650C }, + { USB_VENDOR_ENTREGA, USB_PRODUCT_ENTREGA_E45 }, + { USB_VENDOR_ENTREGA, USB_PRODUCT_ENTREGA_XX1 }, + { USB_VENDOR_ENTREGA, USB_PRODUCT_ENTREGA_XX2 }, + { USB_VENDOR_IODATA, USB_PRODUCT_IODATA_USBETT }, + { USB_VENDOR_JATON, USB_PRODUCT_JATON_EDA }, + { USB_VENDOR_KINGSTON, USB_PRODUCT_KINGSTON_XX1 }, + { USB_VENDOR_KLSI, USB_PRODUCT_AOX_USB101 }, + { USB_VENDOR_KLSI, USB_PRODUCT_KLSI_DUH3E10BT }, + { USB_VENDOR_KLSI, USB_PRODUCT_KLSI_DUH3E10BTN }, + { USB_VENDOR_LINKSYS, USB_PRODUCT_LINKSYS_USB10T }, + { USB_VENDOR_MOBILITY, USB_PRODUCT_MOBILITY_EA }, + { USB_VENDOR_NETGEAR, USB_PRODUCT_NETGEAR_EA101 }, + { USB_VENDOR_NETGEAR, USB_PRODUCT_NETGEAR_EA101X }, + { USB_VENDOR_PERACOM, USB_PRODUCT_PERACOM_ENET }, + { USB_VENDOR_PERACOM, USB_PRODUCT_PERACOM_ENET2 }, + { USB_VENDOR_PERACOM, USB_PRODUCT_PERACOM_ENET3 }, + { USB_VENDOR_PORTGEAR, USB_PRODUCT_PORTGEAR_EA8 }, + { USB_VENDOR_PORTGEAR, USB_PRODUCT_PORTGEAR_EA9 }, + { USB_VENDOR_PORTSMITH, USB_PRODUCT_PORTSMITH_EEA }, + { USB_VENDOR_SHARK, USB_PRODUCT_SHARK_PA }, + { USB_VENDOR_SILICOM, USB_PRODUCT_SILICOM_U2E }, + { USB_VENDOR_SILICOM, USB_PRODUCT_SILICOM_GPE }, + { USB_VENDOR_SMC, USB_PRODUCT_SMC_2102USB }, + { 0, 0 } +}; + +static device_probe_t kue_match; +static device_attach_t kue_attach; +static device_detach_t kue_detach; +static device_shutdown_t kue_shutdown; +static int kue_encap(struct kue_softc *, struct mbuf *, int); +static void kue_rxeof(usbd_xfer_handle, usbd_private_handle, usbd_status); +static void kue_txeof(usbd_xfer_handle, usbd_private_handle, usbd_status); +static void kue_start(struct ifnet *); +static void kue_rxstart(struct ifnet *); +static int kue_ioctl(struct ifnet *, u_long, caddr_t); +static void kue_init(void *); +static void kue_stop(struct kue_softc *); +static void kue_watchdog(struct ifnet *); + +static void kue_setmulti(struct kue_softc *); +static void kue_reset(struct kue_softc *); + +static usbd_status kue_do_request(usbd_device_handle, + usb_device_request_t *, void *); +static usbd_status kue_ctl(struct kue_softc *, int, u_int8_t, + u_int16_t, char *, int); +static usbd_status kue_setword(struct kue_softc *, u_int8_t, u_int16_t); +static int kue_load_fw(struct kue_softc *); + +static device_method_t kue_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, kue_match), + DEVMETHOD(device_attach, kue_attach), + DEVMETHOD(device_detach, kue_detach), + DEVMETHOD(device_shutdown, kue_shutdown), + + { 0, 0 } +}; + +static driver_t kue_driver = { + "kue", + kue_methods, + sizeof(struct kue_softc) +}; + +static devclass_t kue_devclass; + +DRIVER_MODULE(kue, uhub, kue_driver, kue_devclass, usbd_driver_load, 0); + +/* + * We have a custom do_request function which is almost like the + * regular do_request function, except it has a much longer timeout. + * Why? Because we need to make requests over the control endpoint + * to download the firmware to the device, which can take longer + * than the default timeout. + */ +static usbd_status +kue_do_request(usbd_device_handle dev, usb_device_request_t *req, void *data) +{ + usbd_xfer_handle xfer; + usbd_status err; + + xfer = usbd_alloc_xfer(dev); + usbd_setup_default_xfer(xfer, dev, 0, 500000, req, + data, UGETW(req->wLength), USBD_SHORT_XFER_OK, 0); + err = usbd_sync_transfer(xfer); + usbd_free_xfer(xfer); + return(err); +} + +static usbd_status +kue_setword(struct kue_softc *sc, u_int8_t breq, u_int16_t word) +{ + usbd_device_handle dev; + usb_device_request_t req; + usbd_status err; + + if (sc->kue_dying) + return(USBD_NORMAL_COMPLETION); + + dev = sc->kue_udev; + + KUE_LOCK(sc); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + + req.bRequest = breq; + USETW(req.wValue, word); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + + err = kue_do_request(dev, &req, NULL); + + KUE_UNLOCK(sc); + + return(err); +} + +static usbd_status +kue_ctl(struct kue_softc *sc, int rw, u_int8_t breq, u_int16_t val, + char *data, int len) +{ + usbd_device_handle dev; + usb_device_request_t req; + usbd_status err; + + dev = sc->kue_udev; + + if (sc->kue_dying) + return(USBD_NORMAL_COMPLETION); + + KUE_LOCK(sc); + + if (rw == KUE_CTL_WRITE) + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + else + req.bmRequestType = UT_READ_VENDOR_DEVICE; + + req.bRequest = breq; + USETW(req.wValue, val); + USETW(req.wIndex, 0); + USETW(req.wLength, len); + + err = kue_do_request(dev, &req, data); + + KUE_UNLOCK(sc); + + return(err); +} + +static int +kue_load_fw(struct kue_softc *sc) +{ + usbd_status err; + usb_device_descriptor_t *dd; + int hwrev; + + dd = &sc->kue_udev->ddesc; + hwrev = UGETW(dd->bcdDevice); + + /* + * First, check if we even need to load the firmware. + * If the device was still attached when the system was + * rebooted, it may already have firmware loaded in it. + * If this is the case, we don't need to do it again. + * And in fact, if we try to load it again, we'll hang, + * so we have to avoid this condition if we don't want + * to look stupid. + * + * We can test this quickly by checking the bcdRevision + * code. The NIC will return a different revision code if + * it's probed while the firmware is still loaded and + * running. + */ + if (hwrev == 0x0202) + return(0); + + /* Load code segment */ + err = kue_ctl(sc, KUE_CTL_WRITE, KUE_CMD_SEND_SCAN, + 0, kue_code_seg, sizeof(kue_code_seg)); + if (err) { + device_printf(sc->kue_dev, "failed to load code segment: %s\n", + usbd_errstr(err)); + return(ENXIO); + } + + /* Load fixup segment */ + err = kue_ctl(sc, KUE_CTL_WRITE, KUE_CMD_SEND_SCAN, + 0, kue_fix_seg, sizeof(kue_fix_seg)); + if (err) { + device_printf(sc->kue_dev, "failed to load fixup segment: %s\n", + usbd_errstr(err)); + return(ENXIO); + } + + /* Send trigger command. */ + err = kue_ctl(sc, KUE_CTL_WRITE, KUE_CMD_SEND_SCAN, + 0, kue_trig_seg, sizeof(kue_trig_seg)); + if (err) { + device_printf(sc->kue_dev, "failed to load trigger segment: %s\n", + usbd_errstr(err)); + return(ENXIO); + } + + return(0); +} + +static void +kue_setmulti(struct kue_softc *sc) +{ + struct ifnet *ifp; + struct ifmultiaddr *ifma; + int i = 0; + + ifp = sc->kue_ifp; + + if (ifp->if_flags & IFF_ALLMULTI || ifp->if_flags & IFF_PROMISC) { + sc->kue_rxfilt |= KUE_RXFILT_ALLMULTI; + sc->kue_rxfilt &= ~KUE_RXFILT_MULTICAST; + kue_setword(sc, KUE_CMD_SET_PKT_FILTER, sc->kue_rxfilt); + return; + } + + sc->kue_rxfilt &= ~KUE_RXFILT_ALLMULTI; + + IF_ADDR_LOCK(ifp); + TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) + { + if (ifma->ifma_addr->sa_family != AF_LINK) + continue; + /* + * If there are too many addresses for the + * internal filter, switch over to allmulti mode. + */ + if (i == KUE_MCFILTCNT(sc)) + break; + bcopy(LLADDR((struct sockaddr_dl *)ifma->ifma_addr), + KUE_MCFILT(sc, i), ETHER_ADDR_LEN); + i++; + } + IF_ADDR_UNLOCK(ifp); + + if (i == KUE_MCFILTCNT(sc)) + sc->kue_rxfilt |= KUE_RXFILT_ALLMULTI; + else { + sc->kue_rxfilt |= KUE_RXFILT_MULTICAST; + kue_ctl(sc, KUE_CTL_WRITE, KUE_CMD_SET_MCAST_FILTERS, + i, sc->kue_mcfilters, i * ETHER_ADDR_LEN); + } + + kue_setword(sc, KUE_CMD_SET_PKT_FILTER, sc->kue_rxfilt); + + return; +} + +/* + * Issue a SET_CONFIGURATION command to reset the MAC. This should be + * done after the firmware is loaded into the adapter in order to + * bring it into proper operation. + */ +static void +kue_reset(struct kue_softc *sc) +{ + if (usbd_set_config_no(sc->kue_udev, KUE_CONFIG_NO, 0) || + usbd_device2interface_handle(sc->kue_udev, KUE_IFACE_IDX, + &sc->kue_iface)) { + device_printf(sc->kue_dev, "getting interface handle failed\n"); + } + + /* Wait a little while for the chip to get its brains in order. */ + DELAY(1000); + return; +} + +/* + * Probe for a KLSI chip. + */ +static int +kue_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + struct kue_type *t; + + if (!uaa->iface) + return(UMATCH_NONE); + + t = kue_devs; + while (t->kue_vid) { + if (uaa->vendor == t->kue_vid && uaa->product == t->kue_did) + return (UMATCH_VENDOR_PRODUCT); + t++; + } + return (UMATCH_NONE); +} + +/* + * Attach the interface. Allocate softc structures, do + * setup and ethernet/BPF attach. + */ +static int +kue_attach(device_t self) +{ + struct kue_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + struct ifnet *ifp; + usbd_status err; + usb_interface_descriptor_t *id; + usb_endpoint_descriptor_t *ed; + int i; + + sc->kue_dev = self; + sc->kue_iface = uaa->iface; + sc->kue_udev = uaa->device; + + id = usbd_get_interface_descriptor(uaa->iface); + + /* Find endpoints. */ + for (i = 0; i < id->bNumEndpoints; i++) { + ed = usbd_interface2endpoint_descriptor(uaa->iface, i); + if (!ed) { + device_printf(sc->kue_dev, "couldn't get ep %d\n", i); + return ENXIO; + } + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + sc->kue_ed[KUE_ENDPT_RX] = ed->bEndpointAddress; + } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + sc->kue_ed[KUE_ENDPT_TX] = ed->bEndpointAddress; + } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_INTERRUPT) { + sc->kue_ed[KUE_ENDPT_INTR] = ed->bEndpointAddress; + } + } + + mtx_init(&sc->kue_mtx, device_get_nameunit(self), MTX_NETWORK_LOCK, + MTX_DEF | MTX_RECURSE); + KUE_LOCK(sc); + + /* Load the firmware into the NIC. */ + if (kue_load_fw(sc)) { + KUE_UNLOCK(sc); + mtx_destroy(&sc->kue_mtx); + return ENXIO; + } + + /* Reset the adapter. */ + kue_reset(sc); + + /* Read ethernet descriptor */ + err = kue_ctl(sc, KUE_CTL_READ, KUE_CMD_GET_ETHER_DESCRIPTOR, + 0, (char *)&sc->kue_desc, sizeof(sc->kue_desc)); + + sc->kue_mcfilters = malloc(KUE_MCFILTCNT(sc) * ETHER_ADDR_LEN, + M_USBDEV, M_NOWAIT); + + ifp = sc->kue_ifp = if_alloc(IFT_ETHER); + if (ifp == NULL) { + device_printf(sc->kue_dev, "can not if_alloc()\n"); + KUE_UNLOCK(sc); + mtx_destroy(&sc->kue_mtx); + return ENXIO; + } + ifp->if_softc = sc; + if_initname(ifp, "kue", device_get_unit(sc->kue_dev)); + ifp->if_mtu = ETHERMTU; + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST | + IFF_NEEDSGIANT; + ifp->if_ioctl = kue_ioctl; + ifp->if_start = kue_start; + ifp->if_watchdog = kue_watchdog; + ifp->if_init = kue_init; + ifp->if_baudrate = 10000000; + ifp->if_snd.ifq_maxlen = IFQ_MAXLEN; + + sc->kue_qdat.ifp = ifp; + sc->kue_qdat.if_rxstart = kue_rxstart; + + /* + * Call MI attach routine. + */ + ether_ifattach(ifp, sc->kue_desc.kue_macaddr); + usb_register_netisr(); + sc->kue_dying = 0; + + KUE_UNLOCK(sc); + + return 0; +} + +static int +kue_detach(device_t dev) +{ + struct kue_softc *sc; + struct ifnet *ifp; + + sc = device_get_softc(dev); + KUE_LOCK(sc); + ifp = sc->kue_ifp; + + sc->kue_dying = 1; + + if (ifp != NULL) { + ether_ifdetach(ifp); + if_free(ifp); + } + + if (sc->kue_ep[KUE_ENDPT_TX] != NULL) + usbd_abort_pipe(sc->kue_ep[KUE_ENDPT_TX]); + if (sc->kue_ep[KUE_ENDPT_RX] != NULL) + usbd_abort_pipe(sc->kue_ep[KUE_ENDPT_RX]); + if (sc->kue_ep[KUE_ENDPT_INTR] != NULL) + usbd_abort_pipe(sc->kue_ep[KUE_ENDPT_INTR]); + + if (sc->kue_mcfilters != NULL) + free(sc->kue_mcfilters, M_USBDEV); + + KUE_UNLOCK(sc); + mtx_destroy(&sc->kue_mtx); + + return(0); +} + +static void +kue_rxstart(struct ifnet *ifp) +{ + struct kue_softc *sc; + struct ue_chain *c; + + sc = ifp->if_softc; + KUE_LOCK(sc); + c = &sc->kue_cdata.ue_rx_chain[sc->kue_cdata.ue_rx_prod]; + + c->ue_mbuf = usb_ether_newbuf(); + if (c->ue_mbuf == NULL) { + device_printf(sc->kue_dev, "no memory for rx list " + "-- packet dropped!\n"); + ifp->if_ierrors++; + KUE_UNLOCK(sc); + return; + } + + /* Setup new transfer. */ + usbd_setup_xfer(c->ue_xfer, sc->kue_ep[KUE_ENDPT_RX], + c, mtod(c->ue_mbuf, char *), UE_BUFSZ, USBD_SHORT_XFER_OK, + USBD_NO_TIMEOUT, kue_rxeof); + usbd_transfer(c->ue_xfer); + + KUE_UNLOCK(sc); + + return; +} + +/* + * A frame has been uploaded: pass the resulting mbuf chain up to + * the higher level protocols. + */ +static void kue_rxeof(usbd_xfer_handle xfer, usbd_private_handle priv, + usbd_status status) +{ + struct kue_softc *sc; + struct ue_chain *c; + struct mbuf *m; + struct ifnet *ifp; + int total_len = 0; + u_int16_t len; + + c = priv; + sc = c->ue_sc; + KUE_LOCK(sc); + ifp = sc->kue_ifp; + + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + KUE_UNLOCK(sc); + return; + } + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) { + KUE_UNLOCK(sc); + return; + } + if (usbd_ratecheck(&sc->kue_rx_notice)) + device_printf(sc->kue_dev, "usb error on rx: %s\n", + usbd_errstr(status)); + if (status == USBD_STALLED) + usbd_clear_endpoint_stall(sc->kue_ep[KUE_ENDPT_RX]); + goto done; + } + + usbd_get_xfer_status(xfer, NULL, NULL, &total_len, NULL); + m = c->ue_mbuf; + if (total_len <= 1) + goto done; + + len = *mtod(m, u_int16_t *); + m_adj(m, sizeof(u_int16_t)); + + /* No errors; receive the packet. */ + total_len = len; + + if (len < sizeof(struct ether_header)) { + ifp->if_ierrors++; + goto done; + } + + ifp->if_ipackets++; + m->m_pkthdr.rcvif = (void *)&sc->kue_qdat; + m->m_pkthdr.len = m->m_len = total_len; + + /* Put the packet on the special USB input queue. */ + usb_ether_input(m); + KUE_UNLOCK(sc); + + return; +done: + + /* Setup new transfer. */ + usbd_setup_xfer(c->ue_xfer, sc->kue_ep[KUE_ENDPT_RX], + c, mtod(c->ue_mbuf, char *), UE_BUFSZ, USBD_SHORT_XFER_OK, + USBD_NO_TIMEOUT, kue_rxeof); + usbd_transfer(c->ue_xfer); + KUE_UNLOCK(sc); + + return; +} + +/* + * A frame was downloaded to the chip. It's safe for us to clean up + * the list buffers. + */ + +static void +kue_txeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + struct kue_softc *sc; + struct ue_chain *c; + struct ifnet *ifp; + usbd_status err; + + c = priv; + sc = c->ue_sc; + KUE_LOCK(sc); + + ifp = sc->kue_ifp; + ifp->if_timer = 0; + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) { + KUE_UNLOCK(sc); + return; + } + device_printf(sc->kue_dev, "usb error on tx: %s\n", + usbd_errstr(status)); + if (status == USBD_STALLED) + usbd_clear_endpoint_stall(sc->kue_ep[KUE_ENDPT_TX]); + KUE_UNLOCK(sc); + return; + } + + usbd_get_xfer_status(c->ue_xfer, NULL, NULL, NULL, &err); + + if (c->ue_mbuf != NULL) { + c->ue_mbuf->m_pkthdr.rcvif = ifp; + usb_tx_done(c->ue_mbuf); + c->ue_mbuf = NULL; + } + + if (err) + ifp->if_oerrors++; + else + ifp->if_opackets++; + + KUE_UNLOCK(sc); + + return; +} + +static int +kue_encap(struct kue_softc *sc, struct mbuf *m, int idx) +{ + int total_len; + struct ue_chain *c; + usbd_status err; + + c = &sc->kue_cdata.ue_tx_chain[idx]; + + /* + * Copy the mbuf data into a contiguous buffer, leaving two + * bytes at the beginning to hold the frame length. + */ + m_copydata(m, 0, m->m_pkthdr.len, c->ue_buf + 2); + c->ue_mbuf = m; + + total_len = m->m_pkthdr.len + 2; + total_len += 64 - (total_len % 64); + + /* Frame length is specified in the first 2 bytes of the buffer. */ + c->ue_buf[0] = (u_int8_t)m->m_pkthdr.len; + c->ue_buf[1] = (u_int8_t)(m->m_pkthdr.len >> 8); + + usbd_setup_xfer(c->ue_xfer, sc->kue_ep[KUE_ENDPT_TX], + c, c->ue_buf, total_len, 0, 10000, kue_txeof); + + /* Transmit */ + err = usbd_transfer(c->ue_xfer); + if (err != USBD_IN_PROGRESS) { + kue_stop(sc); + return(EIO); + } + + sc->kue_cdata.ue_tx_cnt++; + + return(0); +} + +static void +kue_start(struct ifnet *ifp) +{ + struct kue_softc *sc; + struct mbuf *m_head = NULL; + + sc = ifp->if_softc; + KUE_LOCK(sc); + + if (ifp->if_drv_flags & IFF_DRV_OACTIVE) { + KUE_UNLOCK(sc); + return; + } + + IF_DEQUEUE(&ifp->if_snd, m_head); + if (m_head == NULL) { + KUE_UNLOCK(sc); + return; + } + + if (kue_encap(sc, m_head, 0)) { + IF_PREPEND(&ifp->if_snd, m_head); + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + KUE_UNLOCK(sc); + return; + } + + /* + * If there's a BPF listener, bounce a copy of this frame + * to him. + */ + BPF_MTAP(ifp, m_head); + + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + + /* + * Set a timeout in case the chip goes out to lunch. + */ + ifp->if_timer = 5; + KUE_UNLOCK(sc); + + return; +} + +static void +kue_init(void *xsc) +{ + struct kue_softc *sc = xsc; + struct ifnet *ifp = sc->kue_ifp; + struct ue_chain *c; + usbd_status err; + int i; + + KUE_LOCK(sc); + + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + KUE_UNLOCK(sc); + return; + } + + /* Set MAC address */ + kue_ctl(sc, KUE_CTL_WRITE, KUE_CMD_SET_MAC, + 0, IF_LLADDR(sc->kue_ifp), ETHER_ADDR_LEN); + + sc->kue_rxfilt = KUE_RXFILT_UNICAST|KUE_RXFILT_BROADCAST; + + /* If we want promiscuous mode, set the allframes bit. */ + if (ifp->if_flags & IFF_PROMISC) + sc->kue_rxfilt |= KUE_RXFILT_PROMISC; + + kue_setword(sc, KUE_CMD_SET_PKT_FILTER, sc->kue_rxfilt); + + /* I'm not sure how to tune these. */ +#ifdef notdef + /* + * Leave this one alone for now; setting it + * wrong causes lockups on some machines/controllers. + */ + kue_setword(sc, KUE_CMD_SET_SOFS, 1); +#endif + kue_setword(sc, KUE_CMD_SET_URB_SIZE, 64); + + /* Init TX ring. */ + if (usb_ether_tx_list_init(sc, &sc->kue_cdata, + sc->kue_udev) == ENOBUFS) { + device_printf(sc->kue_dev, "tx list init failed\n"); + KUE_UNLOCK(sc); + return; + } + + /* Init RX ring. */ + if (usb_ether_rx_list_init(sc, &sc->kue_cdata, + sc->kue_udev) == ENOBUFS) { + device_printf(sc->kue_dev, "rx list init failed\n"); + KUE_UNLOCK(sc); + return; + } + + /* Load the multicast filter. */ + kue_setmulti(sc); + + /* Open RX and TX pipes. */ + err = usbd_open_pipe(sc->kue_iface, sc->kue_ed[KUE_ENDPT_RX], + USBD_EXCLUSIVE_USE, &sc->kue_ep[KUE_ENDPT_RX]); + if (err) { + device_printf(sc->kue_dev, "open rx pipe failed: %s\n", + usbd_errstr(err)); + KUE_UNLOCK(sc); + return; + } + + err = usbd_open_pipe(sc->kue_iface, sc->kue_ed[KUE_ENDPT_TX], + USBD_EXCLUSIVE_USE, &sc->kue_ep[KUE_ENDPT_TX]); + if (err) { + device_printf(sc->kue_dev, "open tx pipe failed: %s\n", + usbd_errstr(err)); + KUE_UNLOCK(sc); + return; + } + + /* Start up the receive pipe. */ + for (i = 0; i < UE_RX_LIST_CNT; i++) { + c = &sc->kue_cdata.ue_rx_chain[i]; + usbd_setup_xfer(c->ue_xfer, sc->kue_ep[KUE_ENDPT_RX], + c, mtod(c->ue_mbuf, char *), UE_BUFSZ, + USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT, kue_rxeof); + usbd_transfer(c->ue_xfer); + } + + ifp->if_drv_flags |= IFF_DRV_RUNNING; + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + + KUE_UNLOCK(sc); + + return; +} + +static int +kue_ioctl(struct ifnet *ifp, u_long command, caddr_t data) +{ + struct kue_softc *sc = ifp->if_softc; + int error = 0; + + KUE_LOCK(sc); + + switch(command) { + case SIOCSIFFLAGS: + if (ifp->if_flags & IFF_UP) { + if (ifp->if_drv_flags & IFF_DRV_RUNNING && + ifp->if_flags & IFF_PROMISC && + !(sc->kue_if_flags & IFF_PROMISC)) { + sc->kue_rxfilt |= KUE_RXFILT_PROMISC; + kue_setword(sc, KUE_CMD_SET_PKT_FILTER, + sc->kue_rxfilt); + } else if (ifp->if_drv_flags & IFF_DRV_RUNNING && + !(ifp->if_flags & IFF_PROMISC) && + sc->kue_if_flags & IFF_PROMISC) { + sc->kue_rxfilt &= ~KUE_RXFILT_PROMISC; + kue_setword(sc, KUE_CMD_SET_PKT_FILTER, + sc->kue_rxfilt); + } else if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) + kue_init(sc); + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + kue_stop(sc); + } + sc->kue_if_flags = ifp->if_flags; + error = 0; + break; + case SIOCADDMULTI: + case SIOCDELMULTI: + kue_setmulti(sc); + error = 0; + break; + default: + error = ether_ioctl(ifp, command, data); + break; + } + + KUE_UNLOCK(sc); + + return(error); +} + +static void +kue_watchdog(struct ifnet *ifp) +{ + struct kue_softc *sc; + struct ue_chain *c; + usbd_status stat; + + sc = ifp->if_softc; + KUE_LOCK(sc); + ifp->if_oerrors++; + device_printf(sc->kue_dev, "watchdog timeout\n"); + + c = &sc->kue_cdata.ue_tx_chain[0]; + usbd_get_xfer_status(c->ue_xfer, NULL, NULL, NULL, &stat); + kue_txeof(c->ue_xfer, c, stat); + + if (ifp->if_snd.ifq_head != NULL) + kue_start(ifp); + KUE_UNLOCK(sc); + + return; +} + +/* + * Stop the adapter and free any mbufs allocated to the + * RX and TX lists. + */ +static void +kue_stop(struct kue_softc *sc) +{ + usbd_status err; + struct ifnet *ifp; + + KUE_LOCK(sc); + ifp = sc->kue_ifp; + ifp->if_timer = 0; + + /* Stop transfers. */ + if (sc->kue_ep[KUE_ENDPT_RX] != NULL) { + err = usbd_abort_pipe(sc->kue_ep[KUE_ENDPT_RX]); + if (err) { + device_printf(sc->kue_dev, "abort rx pipe failed: %s\n", + usbd_errstr(err)); + } + err = usbd_close_pipe(sc->kue_ep[KUE_ENDPT_RX]); + if (err) { + device_printf(sc->kue_dev, "close rx pipe failed: %s\n", + usbd_errstr(err)); + } + sc->kue_ep[KUE_ENDPT_RX] = NULL; + } + + if (sc->kue_ep[KUE_ENDPT_TX] != NULL) { + err = usbd_abort_pipe(sc->kue_ep[KUE_ENDPT_TX]); + if (err) { + device_printf(sc->kue_dev, "abort tx pipe failed: %s\n", + usbd_errstr(err)); + } + err = usbd_close_pipe(sc->kue_ep[KUE_ENDPT_TX]); + if (err) { + device_printf(sc->kue_dev, "close tx pipe failed: %s\n", + usbd_errstr(err)); + } + sc->kue_ep[KUE_ENDPT_TX] = NULL; + } + + if (sc->kue_ep[KUE_ENDPT_INTR] != NULL) { + err = usbd_abort_pipe(sc->kue_ep[KUE_ENDPT_INTR]); + if (err) { + device_printf(sc->kue_dev, "abort intr pipe failed: %s\n", + usbd_errstr(err)); + } + err = usbd_close_pipe(sc->kue_ep[KUE_ENDPT_INTR]); + if (err) { + device_printf(sc->kue_dev, "close intr pipe failed: %s\n", + usbd_errstr(err)); + } + sc->kue_ep[KUE_ENDPT_INTR] = NULL; + } + + /* Free RX resources. */ + usb_ether_rx_list_free(&sc->kue_cdata); + /* Free TX resources. */ + usb_ether_tx_list_free(&sc->kue_cdata); + + ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); + KUE_UNLOCK(sc); + + return; +} + +/* + * Stop all chip I/O so that the kernel's probe routines don't + * get confused by errant DMAs when rebooting. + */ +static int +kue_shutdown(device_t dev) +{ + struct kue_softc *sc; + + sc = device_get_softc(dev); + + kue_stop(sc); + + return (0); +} diff --git a/sys/legacy/dev/usb/if_kuereg.h b/sys/legacy/dev/usb/if_kuereg.h new file mode 100644 index 0000000..595eaa7 --- /dev/null +++ b/sys/legacy/dev/usb/if_kuereg.h @@ -0,0 +1,161 @@ +/*- + * Copyright (c) 1997, 1998, 1999, 2000 + * Bill Paul <wpaul@ee.columbia.edu>. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * Definitions for the KLSI KL5KUSB101B USB to ethernet controller. + * The KLSI part is controlled via vendor control requests, the structure + * of which depend a bit on the firmware running on the internal + * microcontroller. The one exception is the 'send scan data' command, + * which is used to load the firmware. + */ + +#define KUE_CMD_GET_ETHER_DESCRIPTOR 0x00 +#define KUE_CMD_SET_MCAST_FILTERS 0x01 +#define KUE_CMD_SET_PKT_FILTER 0x02 +#define KUE_CMD_GET_ETHERSTATS 0x03 +#define KUE_CMD_GET_GPIO 0x04 +#define KUE_CMD_SET_GPIO 0x05 +#define KUE_CMD_SET_MAC 0x06 +#define KUE_CMD_GET_MAC 0x07 +#define KUE_CMD_SET_URB_SIZE 0x08 +#define KUE_CMD_SET_SOFS 0x09 +#define KUE_CMD_SET_EVEN_PKTS 0x0A +#define KUE_CMD_SEND_SCAN 0xFF + +struct kue_ether_desc { + u_int8_t kue_len; + u_int8_t kue_rsvd0; + u_int8_t kue_rsvd1; + u_int8_t kue_macaddr[ETHER_ADDR_LEN]; + u_int8_t kue_etherstats[4]; + u_int8_t kue_maxseg[2]; + u_int8_t kue_mcastfilt[2]; + u_int8_t kue_rsvd2; +}; + +#define KUE_ETHERSTATS(x) \ + (*(u_int32_t *)&(x)->kue_desc.kue_etherstats) +#define KUE_MAXSEG(x) \ + (*(u_int16_t *)&(x)->kue_desc.kue_maxseg) +#define KUE_MCFILTCNT(x) \ + ((*(u_int16_t *)&(x)->kue_desc.kue_mcastfilt) & 0x7FFF) +#define KUE_MCFILT(x, y) \ + (char *)&(sc->kue_mcfilters[y * ETHER_ADDR_LEN]) + +#define KUE_STAT_TX_OK 0x00000001 +#define KUE_STAT_RX_OK 0x00000002 +#define KUE_STAT_TX_ERR 0x00000004 +#define KUE_STAT_RX_ERR 0x00000008 +#define KUE_STAT_RX_NOBUF 0x00000010 +#define KUE_STAT_TX_UCAST_BYTES 0x00000020 +#define KUE_STAT_TX_UCAST_FRAMES 0x00000040 +#define KUE_STAT_TX_MCAST_BYTES 0x00000080 +#define KUE_STAT_TX_MCAST_FRAMES 0x00000100 +#define KUE_STAT_TX_BCAST_BYTES 0x00000200 +#define KUE_STAT_TX_BCAST_FRAMES 0x00000400 +#define KUE_STAT_RX_UCAST_BYTES 0x00000800 +#define KUE_STAT_RX_UCAST_FRAMES 0x00001000 +#define KUE_STAT_RX_MCAST_BYTES 0x00002000 +#define KUE_STAT_RX_MCAST_FRAMES 0x00004000 +#define KUE_STAT_RX_BCAST_BYTES 0x00008000 +#define KUE_STAT_RX_BCAST_FRAMES 0x00010000 +#define KUE_STAT_RX_CRCERR 0x00020000 +#define KUE_STAT_TX_QUEUE_LENGTH 0x00040000 +#define KUE_STAT_RX_ALIGNERR 0x00080000 +#define KUE_STAT_TX_SINGLECOLL 0x00100000 +#define KUE_STAT_TX_MULTICOLL 0x00200000 +#define KUE_STAT_TX_DEFERRED 0x00400000 +#define KUE_STAT_TX_MAXCOLLS 0x00800000 +#define KUE_STAT_RX_OVERRUN 0x01000000 +#define KUE_STAT_TX_UNDERRUN 0x02000000 +#define KUE_STAT_TX_SQE_ERR 0x04000000 +#define KUE_STAT_TX_CARRLOSS 0x08000000 +#define KUE_STAT_RX_LATECOLL 0x10000000 + +#define KUE_RXFILT_PROMISC 0x0001 +#define KUE_RXFILT_ALLMULTI 0x0002 +#define KUE_RXFILT_UNICAST 0x0004 +#define KUE_RXFILT_BROADCAST 0x0008 +#define KUE_RXFILT_MULTICAST 0x0010 + +#define KUE_TIMEOUT 1000 +#define KUE_MIN_FRAMELEN 60 + +#define KUE_CTL_READ 0x01 +#define KUE_CTL_WRITE 0x02 + +#define KUE_CONFIG_NO 1 +#define KUE_IFACE_IDX 0 + +/* + * The interrupt endpoint is currently unused + * by the KLSI part. + */ +#define KUE_ENDPT_RX 0x0 +#define KUE_ENDPT_TX 0x1 +#define KUE_ENDPT_INTR 0x2 +#define KUE_ENDPT_MAX 0x3 + +struct kue_type { + u_int16_t kue_vid; + u_int16_t kue_did; +}; + +#define KUE_INC(x, y) (x) = (x + 1) % y + +struct kue_softc { + struct ifnet *kue_ifp; + device_t kue_dev; + usbd_device_handle kue_udev; + usbd_interface_handle kue_iface; + struct kue_ether_desc kue_desc; + int kue_ed[KUE_ENDPT_MAX]; + usbd_pipe_handle kue_ep[KUE_ENDPT_MAX]; + int kue_if_flags; + u_int16_t kue_rxfilt; + u_int8_t *kue_mcfilters; + struct ue_cdata kue_cdata; + struct mtx kue_mtx; + char kue_dying; + struct timeval kue_rx_notice; + struct usb_qdat kue_qdat; +}; + +#if 0 +#define KUE_LOCK(_sc) mtx_lock(&(_sc)->kue_mtx) +#define KUE_UNLOCK(_sc) mtx_unlock(&(_sc)->kue_mtx) +#else +#define KUE_LOCK(_sc) +#define KUE_UNLOCK(_sc) +#endif diff --git a/sys/legacy/dev/usb/if_rue.c b/sys/legacy/dev/usb/if_rue.c new file mode 100644 index 0000000..4449f7d --- /dev/null +++ b/sys/legacy/dev/usb/if_rue.c @@ -0,0 +1,1393 @@ +/*- + * Copyright (c) 2001-2003, Shunsuke Akiyama <akiyama@FreeBSD.org>. + * Copyright (c) 1997, 1998, 1999, 2000 Bill Paul <wpaul@ee.columbia.edu>. + * 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. + */ +/*- + * Copyright (c) 1997, 1998, 1999, 2000 + * Bill Paul <wpaul@ee.columbia.edu>. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * RealTek RTL8150 USB to fast ethernet controller driver. + * Datasheet is available from + * ftp://ftp.realtek.com.tw/lancard/data_sheet/8150/. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/sockio.h> +#include <sys/mbuf.h> +#include <sys/malloc.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/socket.h> +#include <sys/sysctl.h> + +#include <net/if.h> +#include <net/if_arp.h> +#include <net/ethernet.h> +#include <net/if_dl.h> +#include <net/if_media.h> +#include <net/if_types.h> + +#include <net/bpf.h> + +#include <sys/bus.h> +#include <machine/bus.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usbdivar.h> +#include "usbdevs.h" +#include <dev/usb/usb_ethersubr.h> + +#include <dev/mii/mii.h> +#include <dev/mii/miivar.h> + +#include <dev/usb/if_ruereg.h> + +/* "device miibus" required. See GENERIC if you get errors here. */ +#include "miibus_if.h" + +#ifdef USB_DEBUG +static int ruedebug = 0; +SYSCTL_NODE(_hw_usb, OID_AUTO, rue, CTLFLAG_RW, 0, "USB rue"); +SYSCTL_INT(_hw_usb_rue, OID_AUTO, debug, CTLFLAG_RW, + &ruedebug, 0, "rue debug level"); + +#define DPRINTFN(n, x) do { \ + if (ruedebug > (n)) \ + printf x; \ + } while (0); +#else +#define DPRINTFN(n, x) +#endif +#define DPRINTF(x) DPRINTFN(0, x) + +/* + * Various supported device vendors/products. + */ + +static struct rue_type rue_devs[] = { + { USB_VENDOR_MELCO, USB_PRODUCT_MELCO_LUAKTX }, + { USB_VENDOR_REALTEK, USB_PRODUCT_REALTEK_USBKR100 }, + { 0, 0 } +}; + +static device_probe_t rue_match; +static device_attach_t rue_attach; +static device_detach_t rue_detach; +static device_shutdown_t rue_shutdown; +static miibus_readreg_t rue_miibus_readreg; +static miibus_writereg_t rue_miibus_writereg; +static miibus_statchg_t rue_miibus_statchg; + +static int rue_encap(struct rue_softc *, struct mbuf *, int); +#ifdef RUE_INTR_PIPE +static void rue_intr(usbd_xfer_handle, usbd_private_handle, usbd_status); +#endif +static void rue_rxeof(usbd_xfer_handle, usbd_private_handle, usbd_status); +static void rue_txeof(usbd_xfer_handle, usbd_private_handle, usbd_status); +static void rue_tick(void *); +static void rue_tick_task(void *); +static void rue_rxstart(struct ifnet *); +static void rue_start(struct ifnet *); +static int rue_ioctl(struct ifnet *, u_long, caddr_t); +static void rue_init(void *); +static void rue_stop(struct rue_softc *); +static void rue_watchdog(struct ifnet *); +static int rue_ifmedia_upd(struct ifnet *); +static void rue_ifmedia_sts(struct ifnet *, struct ifmediareq *); + +static void rue_setmulti(struct rue_softc *); +static void rue_reset(struct rue_softc *); + +static int rue_read_mem(struct rue_softc *, u_int16_t, void *, u_int16_t); +static int rue_write_mem(struct rue_softc *, u_int16_t, void *, u_int16_t); +static int rue_csr_read_1(struct rue_softc *, int); +static int rue_csr_write_1(struct rue_softc *, int, u_int8_t); +static int rue_csr_read_2(struct rue_softc *, int); +static int rue_csr_write_2(struct rue_softc *, int, u_int16_t); +static int rue_csr_write_4(struct rue_softc *, int, u_int32_t); + +static device_method_t rue_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, rue_match), + DEVMETHOD(device_attach, rue_attach), + DEVMETHOD(device_detach, rue_detach), + DEVMETHOD(device_shutdown, rue_shutdown), + + /* Bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + DEVMETHOD(bus_driver_added, bus_generic_driver_added), + + /* MII interface */ + DEVMETHOD(miibus_readreg, rue_miibus_readreg), + DEVMETHOD(miibus_writereg, rue_miibus_writereg), + DEVMETHOD(miibus_statchg, rue_miibus_statchg), + + { 0, 0 } +}; + +static driver_t rue_driver = { + "rue", + rue_methods, + sizeof(struct rue_softc) +}; + +static devclass_t rue_devclass; + +DRIVER_MODULE(rue, uhub, rue_driver, rue_devclass, usbd_driver_load, 0); +DRIVER_MODULE(miibus, rue, miibus_driver, miibus_devclass, 0, 0); +MODULE_DEPEND(rue, usb, 1, 1, 1); +MODULE_DEPEND(rue, ether, 1, 1, 1); +MODULE_DEPEND(rue, miibus, 1, 1, 1); + +#define RUE_SETBIT(sc, reg, x) \ + rue_csr_write_1(sc, reg, rue_csr_read_1(sc, reg) | (x)) + +#define RUE_CLRBIT(sc, reg, x) \ + rue_csr_write_1(sc, reg, rue_csr_read_1(sc, reg) & ~(x)) + +#define RUE_SETBIT_2(sc, reg, x) \ + rue_csr_write_2(sc, reg, rue_csr_read_2(sc, reg) | (x)) + +#define RUE_CLRBIT_2(sc, reg, x) \ + rue_csr_write_2(sc, reg, rue_csr_read_2(sc, reg) & ~(x)) + +static int +rue_read_mem(struct rue_softc *sc, u_int16_t addr, void *buf, u_int16_t len) +{ + usb_device_request_t req; + usbd_status err; + + if (sc->rue_dying) + return (0); + + RUE_LOCK(sc); + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = UR_SET_ADDRESS; + USETW(req.wValue, addr); + USETW(req.wIndex, 0); + USETW(req.wLength, len); + + err = usbd_do_request(sc->rue_udev, &req, buf); + + RUE_UNLOCK(sc); + + if (err) { + device_printf(sc->rue_dev, "control pipe read failed: %s\n", + usbd_errstr(err)); + return (-1); + } + + return (0); +} + +static int +rue_write_mem(struct rue_softc *sc, u_int16_t addr, void *buf, u_int16_t len) +{ + usb_device_request_t req; + usbd_status err; + + if (sc->rue_dying) + return (0); + + RUE_LOCK(sc); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UR_SET_ADDRESS; + USETW(req.wValue, addr); + USETW(req.wIndex, 0); + USETW(req.wLength, len); + + err = usbd_do_request(sc->rue_udev, &req, buf); + + RUE_UNLOCK(sc); + + if (err) { + device_printf(sc->rue_dev, "control pipe write failed: %s\n", + usbd_errstr(err)); + return (-1); + } + + return (0); +} + +static int +rue_csr_read_1(struct rue_softc *sc, int reg) +{ + int err; + u_int8_t val = 0; + + err = rue_read_mem(sc, reg, &val, 1); + + if (err) + return (0); + + return (val); +} + +static int +rue_csr_read_2(struct rue_softc *sc, int reg) +{ + int err; + u_int16_t val = 0; + uWord w; + + USETW(w, val); + err = rue_read_mem(sc, reg, &w, 2); + val = UGETW(w); + + if (err) + return (0); + + return (val); +} + +static int +rue_csr_write_1(struct rue_softc *sc, int reg, u_int8_t val) +{ + int err; + + err = rue_write_mem(sc, reg, &val, 1); + + if (err) + return (-1); + + return (0); +} + +static int +rue_csr_write_2(struct rue_softc *sc, int reg, u_int16_t val) +{ + int err; + uWord w; + + USETW(w, val); + err = rue_write_mem(sc, reg, &w, 2); + + if (err) + return (-1); + + return (0); +} + +static int +rue_csr_write_4(struct rue_softc *sc, int reg, u_int32_t val) +{ + int err; + uDWord dw; + + USETDW(dw, val); + err = rue_write_mem(sc, reg, &dw, 4); + + if (err) + return (-1); + + return (0); +} + +static int +rue_miibus_readreg(device_t dev, int phy, int reg) +{ + struct rue_softc *sc = device_get_softc(dev); + int rval; + int ruereg; + + if (phy != 0) /* RTL8150 supports PHY == 0, only */ + return (0); + + switch (reg) { + case MII_BMCR: + ruereg = RUE_BMCR; + break; + case MII_BMSR: + ruereg = RUE_BMSR; + break; + case MII_ANAR: + ruereg = RUE_ANAR; + break; + case MII_ANER: + ruereg = RUE_AER; + break; + case MII_ANLPAR: + ruereg = RUE_ANLP; + break; + case MII_PHYIDR1: + case MII_PHYIDR2: + return (0); + break; + default: + if (RUE_REG_MIN <= reg && reg <= RUE_REG_MAX) { + rval = rue_csr_read_1(sc, reg); + return (rval); + } + device_printf(sc->rue_dev, "bad phy register\n"); + return (0); + } + + rval = rue_csr_read_2(sc, ruereg); + + return (rval); +} + +static int +rue_miibus_writereg(device_t dev, int phy, int reg, int data) +{ + struct rue_softc *sc = device_get_softc(dev); + int ruereg; + + if (phy != 0) /* RTL8150 supports PHY == 0, only */ + return (0); + + switch (reg) { + case MII_BMCR: + ruereg = RUE_BMCR; + break; + case MII_BMSR: + ruereg = RUE_BMSR; + break; + case MII_ANAR: + ruereg = RUE_ANAR; + break; + case MII_ANER: + ruereg = RUE_AER; + break; + case MII_ANLPAR: + ruereg = RUE_ANLP; + break; + case MII_PHYIDR1: + case MII_PHYIDR2: + return (0); + break; + default: + if (RUE_REG_MIN <= reg && reg <= RUE_REG_MAX) { + rue_csr_write_1(sc, reg, data); + return (0); + } + device_printf(sc->rue_dev, "bad phy register\n"); + return (0); + } + rue_csr_write_2(sc, ruereg, data); + + return (0); +} + +static void +rue_miibus_statchg(device_t dev) +{ + /* + * When the code below is enabled the card starts doing weird + * things after link going from UP to DOWN and back UP. + * + * Looks like some of register writes below messes up PHY + * interface. + * + * No visible regressions were found after commenting this code + * out, so that disable it for good. + */ +#if 0 + struct rue_softc *sc = device_get_softc(dev); + struct mii_data *mii = GET_MII(sc); + int bmcr; + + RUE_CLRBIT(sc, RUE_CR, (RUE_CR_RE | RUE_CR_TE)); + + bmcr = rue_csr_read_2(sc, RUE_BMCR); + + if (IFM_SUBTYPE(mii->mii_media_active) == IFM_100_TX) + bmcr |= RUE_BMCR_SPD_SET; + else + bmcr &= ~RUE_BMCR_SPD_SET; + + if ((mii->mii_media_active & IFM_GMASK) == IFM_FDX) + bmcr |= RUE_BMCR_DUPLEX; + else + bmcr &= ~RUE_BMCR_DUPLEX; + + rue_csr_write_2(sc, RUE_BMCR, bmcr); + + RUE_SETBIT(sc, RUE_CR, (RUE_CR_RE | RUE_CR_TE)); +#endif +} + +/* + * Program the 64-bit multicast hash filter. + */ + +static void +rue_setmulti(struct rue_softc *sc) +{ + struct ifnet *ifp; + int h = 0; + u_int32_t hashes[2] = { 0, 0 }; + struct ifmultiaddr *ifma; + u_int32_t rxcfg; + int mcnt = 0; + + ifp = sc->rue_ifp; + + rxcfg = rue_csr_read_2(sc, RUE_RCR); + + if (ifp->if_flags & IFF_ALLMULTI || ifp->if_flags & IFF_PROMISC) { + rxcfg |= (RUE_RCR_AAM | RUE_RCR_AAP); + rxcfg &= ~RUE_RCR_AM; + rue_csr_write_2(sc, RUE_RCR, rxcfg); + rue_csr_write_4(sc, RUE_MAR0, 0xFFFFFFFF); + rue_csr_write_4(sc, RUE_MAR4, 0xFFFFFFFF); + return; + } + + /* first, zot all the existing hash bits */ + rue_csr_write_4(sc, RUE_MAR0, 0); + rue_csr_write_4(sc, RUE_MAR4, 0); + + /* now program new ones */ + IF_ADDR_LOCK(ifp); + TAILQ_FOREACH (ifma, &ifp->if_multiaddrs, ifma_link) + { + if (ifma->ifma_addr->sa_family != AF_LINK) + continue; + h = ether_crc32_be(LLADDR((struct sockaddr_dl *) + ifma->ifma_addr), ETHER_ADDR_LEN) >> 26; + if (h < 32) + hashes[0] |= (1 << h); + else + hashes[1] |= (1 << (h - 32)); + mcnt++; + } + IF_ADDR_UNLOCK(ifp); + + if (mcnt) + rxcfg |= RUE_RCR_AM; + else + rxcfg &= ~RUE_RCR_AM; + + rxcfg &= ~(RUE_RCR_AAM | RUE_RCR_AAP); + + rue_csr_write_2(sc, RUE_RCR, rxcfg); + rue_csr_write_4(sc, RUE_MAR0, hashes[0]); + rue_csr_write_4(sc, RUE_MAR4, hashes[1]); +} + +static void +rue_reset(struct rue_softc *sc) +{ + int i; + + rue_csr_write_1(sc, RUE_CR, RUE_CR_SOFT_RST); + + for (i = 0; i < RUE_TIMEOUT; i++) { + DELAY(500); + if (!(rue_csr_read_1(sc, RUE_CR) & RUE_CR_SOFT_RST)) + break; + } + if (i == RUE_TIMEOUT) + device_printf(sc->rue_dev, "reset never completed!\n"); + + DELAY(10000); +} + +/* + * Probe for a RTL8150 chip. + */ + +static int +rue_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + struct rue_type *t; + + if (uaa->iface == NULL) + return (UMATCH_NONE); + + t = rue_devs; + while (t->rue_vid) { + if (uaa->vendor == t->rue_vid && + uaa->product == t->rue_did) { + return (UMATCH_VENDOR_PRODUCT); + } + t++; + } + + return (UMATCH_NONE); +} + +/* + * Attach the interface. Allocate softc structures, do ifmedia + * setup and ethernet/BPF attach. + */ + +static int +rue_attach(device_t self) +{ + struct rue_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + u_char eaddr[ETHER_ADDR_LEN]; + struct ifnet *ifp; + usbd_interface_handle iface; + usbd_status err; + usb_interface_descriptor_t *id; + usb_endpoint_descriptor_t *ed; + int i; + struct rue_type *t; + + sc->rue_dev = self; + sc->rue_udev = uaa->device; + + if (usbd_set_config_no(sc->rue_udev, RUE_CONFIG_NO, 0)) { + device_printf(sc->rue_dev, "getting interface handle failed\n"); + goto error; + } + + usb_init_task(&sc->rue_tick_task, rue_tick_task, sc); + + err = usbd_device2interface_handle(uaa->device, RUE_IFACE_IDX, &iface); + if (err) { + device_printf(sc->rue_dev, "getting interface handle failed\n"); + goto error; + } + + sc->rue_iface = iface; + + t = rue_devs; + while (t->rue_vid) { + if (uaa->vendor == t->rue_vid && + uaa->product == t->rue_did) { + sc->rue_info = t; + break; + } + t++; + } + + id = usbd_get_interface_descriptor(sc->rue_iface); + + /* Find endpoints */ + for (i = 0; i < id->bNumEndpoints; i++) { + ed = usbd_interface2endpoint_descriptor(iface, i); + if (ed == NULL) { + device_printf(sc->rue_dev, "couldn't get ep %d\n", i); + goto error; + } + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + sc->rue_ed[RUE_ENDPT_RX] = ed->bEndpointAddress; + } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + sc->rue_ed[RUE_ENDPT_TX] = ed->bEndpointAddress; + } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_INTERRUPT) { + sc->rue_ed[RUE_ENDPT_INTR] = ed->bEndpointAddress; + } + } + + mtx_init(&sc->rue_mtx, device_get_nameunit(self), MTX_NETWORK_LOCK, + MTX_DEF | MTX_RECURSE); + RUE_LOCK(sc); + + /* Reset the adapter */ + rue_reset(sc); + + /* Get station address from the EEPROM */ + err = rue_read_mem(sc, RUE_EEPROM_IDR0, + (caddr_t)&eaddr, ETHER_ADDR_LEN); + if (err) { + device_printf(sc->rue_dev, "couldn't get station address\n"); + goto error1; + } + + ifp = sc->rue_ifp = if_alloc(IFT_ETHER); + if (ifp == NULL) { + device_printf(sc->rue_dev, "can not if_alloc()\n"); + goto error1; + } + ifp->if_softc = sc; + if_initname(ifp, "rue", device_get_unit(sc->rue_dev)); + ifp->if_mtu = ETHERMTU; + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST | + IFF_NEEDSGIANT; + ifp->if_ioctl = rue_ioctl; + ifp->if_start = rue_start; + ifp->if_watchdog = rue_watchdog; + ifp->if_init = rue_init; + ifp->if_snd.ifq_maxlen = IFQ_MAXLEN; + + /* MII setup */ + if (mii_phy_probe(self, &sc->rue_miibus, + rue_ifmedia_upd, rue_ifmedia_sts)) { + device_printf(sc->rue_dev, "MII without any PHY!\n"); + goto error2; + } + + sc->rue_qdat.ifp = ifp; + sc->rue_qdat.if_rxstart = rue_rxstart; + + /* Call MI attach routine */ + ether_ifattach(ifp, eaddr); + callout_handle_init(&sc->rue_stat_ch); + usb_register_netisr(); + sc->rue_dying = 0; + + RUE_UNLOCK(sc); + return 0; + + error2: + if_free(ifp); + error1: + RUE_UNLOCK(sc); + mtx_destroy(&sc->rue_mtx); + error: + return ENXIO; +} + +static int +rue_detach(device_t dev) +{ + struct rue_softc *sc; + struct ifnet *ifp; + + sc = device_get_softc(dev); + RUE_LOCK(sc); + ifp = sc->rue_ifp; + + sc->rue_dying = 1; + untimeout(rue_tick, sc, sc->rue_stat_ch); + usb_rem_task(sc->rue_udev, &sc->rue_tick_task); + ether_ifdetach(ifp); + if_free(ifp); + + if (sc->rue_ep[RUE_ENDPT_TX] != NULL) + usbd_abort_pipe(sc->rue_ep[RUE_ENDPT_TX]); + if (sc->rue_ep[RUE_ENDPT_RX] != NULL) + usbd_abort_pipe(sc->rue_ep[RUE_ENDPT_RX]); +#ifdef RUE_INTR_PIPE + if (sc->rue_ep[RUE_ENDPT_INTR] != NULL) + usbd_abort_pipe(sc->rue_ep[RUE_ENDPT_INTR]); +#endif + + RUE_UNLOCK(sc); + mtx_destroy(&sc->rue_mtx); + + return (0); +} + +#ifdef RUE_INTR_PIPE +static void +rue_intr(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + struct rue_softc *sc = priv; + struct ifnet *ifp; + struct rue_intrpkt *p; + + RUE_LOCK(sc); + ifp = sc->rue_ifp; + + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + RUE_UNLOCK(sc); + return; + } + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) { + RUE_UNLOCK(sc); + return; + } + device_printf(sc->rue_dev, "usb error on intr: %s\n", + usbd_errstr(status)); + if (status == USBD_STALLED) + usbd_clear_endpoint_stall(sc->rue_ep[RUE_ENDPT_INTR]); + RUE_UNLOCK(sc); + return; + } + + usbd_get_xfer_status(xfer, NULL, (void **)&p, NULL, NULL); + + ifp->if_ierrors += p->rue_rxlost_cnt; + ifp->if_ierrors += p->rue_crcerr_cnt; + ifp->if_collisions += p->rue_col_cnt; + + RUE_UNLOCK(sc); +} +#endif + +static void +rue_rxstart(struct ifnet *ifp) +{ + struct rue_softc *sc; + struct ue_chain *c; + + sc = ifp->if_softc; + RUE_LOCK(sc); + c = &sc->rue_cdata.ue_rx_chain[sc->rue_cdata.ue_rx_prod]; + + c->ue_mbuf = usb_ether_newbuf(); + if (c->ue_mbuf == NULL) { + printf("%s: no memory for rx list " + "-- packet dropped!\n", device_get_nameunit(sc->rue_dev)); + ifp->if_ierrors++; + RUE_UNLOCK(sc); + return; + } + + /* Setup new transfer. */ + usbd_setup_xfer(c->ue_xfer, sc->rue_ep[RUE_ENDPT_RX], + c, mtod(c->ue_mbuf, char *), UE_BUFSZ, USBD_SHORT_XFER_OK, + USBD_NO_TIMEOUT, rue_rxeof); + usbd_transfer(c->ue_xfer); + + RUE_UNLOCK(sc); +} + +/* + * A frame has been uploaded: pass the resulting mbuf chain up to + * the higher level protocols. + */ + +static void +rue_rxeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + struct ue_chain *c = priv; + struct rue_softc *sc = c->ue_sc; + struct mbuf *m; + struct ifnet *ifp; + int total_len = 0; + struct rue_rxpkt r; + + if (sc->rue_dying) + return; + RUE_LOCK(sc); + ifp = sc->rue_ifp; + + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + RUE_UNLOCK(sc); + return; + } + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) { + RUE_UNLOCK(sc); + return; + } + if (usbd_ratecheck(&sc->rue_rx_notice)) + device_printf(sc->rue_dev, "usb error on rx: %s\n", + usbd_errstr(status)); + if (status == USBD_STALLED) + usbd_clear_endpoint_stall(sc->rue_ep[RUE_ENDPT_RX]); + goto done; + } + + usbd_get_xfer_status(xfer, NULL, NULL, &total_len, NULL); + + if (total_len <= ETHER_CRC_LEN) { + ifp->if_ierrors++; + goto done; + } + + m = c->ue_mbuf; + bcopy(mtod(m, char *) + total_len - 4, (char *)&r, sizeof (r)); + + /* Check recieve packet was valid or not */ + if ((r.rue_rxstat & RUE_RXSTAT_VALID) == 0) { + ifp->if_ierrors++; + goto done; + } + + /* No errors; receive the packet. */ + total_len -= ETHER_CRC_LEN; + + ifp->if_ipackets++; + m->m_pkthdr.rcvif = (void *)&sc->rue_qdat; + m->m_pkthdr.len = m->m_len = total_len; + + /* Put the packet on the special USB input queue. */ + usb_ether_input(m); + + RUE_UNLOCK(sc); + return; + + done: + /* Setup new transfer. */ + usbd_setup_xfer(xfer, sc->rue_ep[RUE_ENDPT_RX], + c, mtod(c->ue_mbuf, char *), UE_BUFSZ, + USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT, rue_rxeof); + usbd_transfer(xfer); + RUE_UNLOCK(sc); +} + +/* + * A frame was downloaded to the chip. It's safe for us to clean up + * the list buffers. + */ + +static void +rue_txeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + struct ue_chain *c = priv; + struct rue_softc *sc = c->ue_sc; + struct ifnet *ifp; + usbd_status err; + + RUE_LOCK(sc); + + ifp = sc->rue_ifp; + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) { + RUE_UNLOCK(sc); + return; + } + device_printf(sc->rue_dev, "usb error on tx: %s\n", + usbd_errstr(status)); + if (status == USBD_STALLED) + usbd_clear_endpoint_stall(sc->rue_ep[RUE_ENDPT_TX]); + RUE_UNLOCK(sc); + return; + } + + ifp->if_timer = 0; + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + usbd_get_xfer_status(c->ue_xfer, NULL, NULL, NULL, &err); + + if (c->ue_mbuf != NULL) { + c->ue_mbuf->m_pkthdr.rcvif = ifp; + usb_tx_done(c->ue_mbuf); + c->ue_mbuf = NULL; + } + + if (err) + ifp->if_oerrors++; + else + ifp->if_opackets++; + + RUE_UNLOCK(sc); +} + +static void +rue_tick(void *xsc) +{ + struct rue_softc *sc = xsc; + + if (sc == NULL) + return; + if (sc->rue_dying) + return; + + /* Perform periodic stuff in process context */ + usb_add_task(sc->rue_udev, &sc->rue_tick_task, USB_TASKQ_DRIVER); +} + +static void +rue_tick_task(void *xsc) +{ + struct rue_softc *sc = xsc; + struct ifnet *ifp; + struct mii_data *mii; + + if (sc == NULL) + return; + + RUE_LOCK(sc); + + ifp = sc->rue_ifp; + mii = GET_MII(sc); + if (mii == NULL) { + RUE_UNLOCK(sc); + return; + } + + mii_tick(mii); + if (!sc->rue_link && mii->mii_media_status & IFM_ACTIVE && + IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE) { + sc->rue_link++; + if (ifp->if_snd.ifq_head != NULL) + rue_start(ifp); + } + + sc->rue_stat_ch = timeout(rue_tick, sc, hz); + + RUE_UNLOCK(sc); +} + +static int +rue_encap(struct rue_softc *sc, struct mbuf *m, int idx) +{ + int total_len; + struct ue_chain *c; + usbd_status err; + + c = &sc->rue_cdata.ue_tx_chain[idx]; + + /* + * Copy the mbuf data into a contiguous buffer + */ + m_copydata(m, 0, m->m_pkthdr.len, c->ue_buf); + c->ue_mbuf = m; + + total_len = m->m_pkthdr.len; + + /* + * This is an undocumented behavior. + * RTL8150 chip doesn't send frame length smaller than + * RUE_MIN_FRAMELEN (60) byte packet. + */ + if (total_len < RUE_MIN_FRAMELEN) + total_len = RUE_MIN_FRAMELEN; + + usbd_setup_xfer(c->ue_xfer, sc->rue_ep[RUE_ENDPT_TX], + c, c->ue_buf, total_len, USBD_FORCE_SHORT_XFER, + 10000, rue_txeof); + + /* Transmit */ + err = usbd_transfer(c->ue_xfer); + if (err != USBD_IN_PROGRESS) { + rue_stop(sc); + return (EIO); + } + + sc->rue_cdata.ue_tx_cnt++; + + return (0); +} + +static void +rue_start(struct ifnet *ifp) +{ + struct rue_softc *sc = ifp->if_softc; + struct mbuf *m_head = NULL; + + RUE_LOCK(sc); + + if (!sc->rue_link) { + RUE_UNLOCK(sc); + return; + } + + if (ifp->if_drv_flags & IFF_DRV_OACTIVE) { + RUE_UNLOCK(sc); + return; + } + + IF_DEQUEUE(&ifp->if_snd, m_head); + if (m_head == NULL) { + RUE_UNLOCK(sc); + return; + } + + if (rue_encap(sc, m_head, 0)) { + IF_PREPEND(&ifp->if_snd, m_head); + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + RUE_UNLOCK(sc); + return; + } + + /* + * If there's a BPF listener, bounce a copy of this frame + * to him. + */ + BPF_MTAP(ifp, m_head); + + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + + /* + * Set a timeout in case the chip goes out to lunch. + */ + ifp->if_timer = 5; + + RUE_UNLOCK(sc); +} + +static void +rue_init(void *xsc) +{ + struct rue_softc *sc = xsc; + struct ifnet *ifp = sc->rue_ifp; + struct mii_data *mii = GET_MII(sc); + struct ue_chain *c; + usbd_status err; + int i; + int rxcfg; + + RUE_LOCK(sc); + + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + RUE_UNLOCK(sc); + return; + } + + /* + * Cancel pending I/O and free all RX/TX buffers. + */ + rue_reset(sc); + + /* Set MAC address */ + rue_write_mem(sc, RUE_IDR0, IF_LLADDR(sc->rue_ifp), + ETHER_ADDR_LEN); + + /* Init TX ring. */ + if (usb_ether_tx_list_init(sc, &sc->rue_cdata, + sc->rue_udev) == ENOBUFS) { + device_printf(sc->rue_dev, "tx list init failed\n"); + RUE_UNLOCK(sc); + return; + } + + /* Init RX ring. */ + if (usb_ether_rx_list_init(sc, &sc->rue_cdata, + sc->rue_udev) == ENOBUFS) { + device_printf(sc->rue_dev, "rx list init failed\n"); + RUE_UNLOCK(sc); + return; + } + +#ifdef RUE_INTR_PIPE + sc->rue_cdata.ue_ibuf = malloc(RUE_INTR_PKTLEN, M_USBDEV, M_NOWAIT); +#endif + + /* + * Set the initial TX and RX configuration. + */ + rue_csr_write_1(sc, RUE_TCR, RUE_TCR_CONFIG); + + rxcfg = RUE_RCR_CONFIG; + + /* Set capture broadcast bit to capture broadcast frames. */ + if (ifp->if_flags & IFF_BROADCAST) + rxcfg |= RUE_RCR_AB; + else + rxcfg &= ~RUE_RCR_AB; + + /* If we want promiscuous mode, set the allframes bit. */ + if (ifp->if_flags & IFF_PROMISC) + rxcfg |= RUE_RCR_AAP; + else + rxcfg &= ~RUE_RCR_AAP; + + rue_csr_write_2(sc, RUE_RCR, rxcfg); + + /* Load the multicast filter. */ + rue_setmulti(sc); + + /* Enable RX and TX */ + rue_csr_write_1(sc, RUE_CR, (RUE_CR_TE | RUE_CR_RE | RUE_CR_EP3CLREN)); + + mii_mediachg(mii); + + /* Open RX and TX pipes. */ + err = usbd_open_pipe(sc->rue_iface, sc->rue_ed[RUE_ENDPT_RX], + USBD_EXCLUSIVE_USE, &sc->rue_ep[RUE_ENDPT_RX]); + if (err) { + device_printf(sc->rue_dev, "open rx pipe failed: %s\n", + usbd_errstr(err)); + RUE_UNLOCK(sc); + return; + } + err = usbd_open_pipe(sc->rue_iface, sc->rue_ed[RUE_ENDPT_TX], + USBD_EXCLUSIVE_USE, &sc->rue_ep[RUE_ENDPT_TX]); + if (err) { + device_printf(sc->rue_dev, "open tx pipe failed: %s\n", + usbd_errstr(err)); + RUE_UNLOCK(sc); + return; + } + +#ifdef RUE_INTR_PIPE + err = usbd_open_pipe_intr(sc->rue_iface, sc->rue_ed[RUE_ENDPT_INTR], + USBD_SHORT_XFER_OK, + &sc->rue_ep[RUE_ENDPT_INTR], sc, + sc->rue_cdata.ue_ibuf, RUE_INTR_PKTLEN, + rue_intr, RUE_INTR_INTERVAL); + if (err) { + device_printf(sc->rue_dev, "open intr pipe failed: %s\n", + usbd_errstr(err)); + RUE_UNLOCK(sc); + return; + } +#endif + + /* Start up the receive pipe. */ + for (i = 0; i < UE_RX_LIST_CNT; i++) { + c = &sc->rue_cdata.ue_rx_chain[i]; + usbd_setup_xfer(c->ue_xfer, sc->rue_ep[RUE_ENDPT_RX], + c, mtod(c->ue_mbuf, char *), UE_BUFSZ, + USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT, rue_rxeof); + usbd_transfer(c->ue_xfer); + } + + ifp->if_drv_flags |= IFF_DRV_RUNNING; + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + + sc->rue_stat_ch = timeout(rue_tick, sc, hz); + + RUE_UNLOCK(sc); +} + +/* + * Set media options. + */ + +static int +rue_ifmedia_upd(struct ifnet *ifp) +{ + struct rue_softc *sc = ifp->if_softc; + struct mii_data *mii = GET_MII(sc); + + sc->rue_link = 0; + if (mii->mii_instance) { + struct mii_softc *miisc; + LIST_FOREACH (miisc, &mii->mii_phys, mii_list) + mii_phy_reset(miisc); + } + mii_mediachg(mii); + + return (0); +} + +/* + * Report current media status. + */ + +static void +rue_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr) +{ + struct rue_softc *sc = ifp->if_softc; + struct mii_data *mii = GET_MII(sc); + + mii_pollstat(mii); + ifmr->ifm_active = mii->mii_media_active; + ifmr->ifm_status = mii->mii_media_status; +} + +static int +rue_ioctl(struct ifnet *ifp, u_long command, caddr_t data) +{ + struct rue_softc *sc = ifp->if_softc; + struct ifreq *ifr = (struct ifreq *)data; + struct mii_data *mii; + int error = 0; + + RUE_LOCK(sc); + + switch (command) { + case SIOCSIFFLAGS: + if (ifp->if_flags & IFF_UP) { + if (ifp->if_drv_flags & IFF_DRV_RUNNING && + ifp->if_flags & IFF_PROMISC && + !(sc->rue_if_flags & IFF_PROMISC)) { + RUE_SETBIT_2(sc, RUE_RCR, + (RUE_RCR_AAM | RUE_RCR_AAP)); + rue_setmulti(sc); + } else if (ifp->if_drv_flags & IFF_DRV_RUNNING && + !(ifp->if_flags & IFF_PROMISC) && + sc->rue_if_flags & IFF_PROMISC) { + RUE_CLRBIT_2(sc, RUE_RCR, + (RUE_RCR_AAM | RUE_RCR_AAP)); + rue_setmulti(sc); + } else if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) + rue_init(sc); + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + rue_stop(sc); + } + sc->rue_if_flags = ifp->if_flags; + error = 0; + break; + case SIOCADDMULTI: + case SIOCDELMULTI: + rue_setmulti(sc); + error = 0; + break; + case SIOCGIFMEDIA: + case SIOCSIFMEDIA: + mii = GET_MII(sc); + error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, command); + break; + default: + error = ether_ioctl(ifp, command, data); + break; + } + + RUE_UNLOCK(sc); + + return (error); +} + +static void +rue_watchdog(struct ifnet *ifp) +{ + struct rue_softc *sc = ifp->if_softc; + struct ue_chain *c; + usbd_status stat; + + RUE_LOCK(sc); + + ifp->if_oerrors++; + device_printf(sc->rue_dev, "watchdog timeout\n"); + + c = &sc->rue_cdata.ue_tx_chain[0]; + usbd_get_xfer_status(c->ue_xfer, NULL, NULL, NULL, &stat); + rue_txeof(c->ue_xfer, c, stat); + + if (ifp->if_snd.ifq_head != NULL) + rue_start(ifp); + + RUE_UNLOCK(sc); +} + +/* + * Stop the adapter and free any mbufs allocated to the + * RX and TX lists. + */ + +static void +rue_stop(struct rue_softc *sc) +{ + usbd_status err; + struct ifnet *ifp; + + RUE_LOCK(sc); + + ifp = sc->rue_ifp; + ifp->if_timer = 0; + + rue_csr_write_1(sc, RUE_CR, 0x00); + rue_reset(sc); + + untimeout(rue_tick, sc, sc->rue_stat_ch); + + /* Stop transfers. */ + if (sc->rue_ep[RUE_ENDPT_RX] != NULL) { + err = usbd_abort_pipe(sc->rue_ep[RUE_ENDPT_RX]); + if (err) { + device_printf(sc->rue_dev, "abort rx pipe failed: %s\n", + usbd_errstr(err)); + } + err = usbd_close_pipe(sc->rue_ep[RUE_ENDPT_RX]); + if (err) { + device_printf(sc->rue_dev, "close rx pipe failed: %s\n", + usbd_errstr(err)); + } + sc->rue_ep[RUE_ENDPT_RX] = NULL; + } + + if (sc->rue_ep[RUE_ENDPT_TX] != NULL) { + err = usbd_abort_pipe(sc->rue_ep[RUE_ENDPT_TX]); + if (err) { + device_printf(sc->rue_dev, "abort tx pipe failed: %s\n", + usbd_errstr(err)); + } + err = usbd_close_pipe(sc->rue_ep[RUE_ENDPT_TX]); + if (err) { + device_printf(sc->rue_dev, "close tx pipe failed: %s\n", + usbd_errstr(err)); + } + sc->rue_ep[RUE_ENDPT_TX] = NULL; + } + +#ifdef RUE_INTR_PIPE + if (sc->rue_ep[RUE_ENDPT_INTR] != NULL) { + err = usbd_abort_pipe(sc->rue_ep[RUE_ENDPT_INTR]); + if (err) { + device_printf(sc->rue_dev, "abort intr pipe failed: %s\n", + usbd_errstr(err)); + } + err = usbd_close_pipe(sc->rue_ep[RUE_ENDPT_INTR]); + if (err) { + device_printf(sc->rue_dev, "close intr pipe failed: %s\n", + usbd_errstr(err)); + } + sc->rue_ep[RUE_ENDPT_INTR] = NULL; + } +#endif + + /* Free RX resources. */ + usb_ether_rx_list_free(&sc->rue_cdata); + /* Free TX resources. */ + usb_ether_tx_list_free(&sc->rue_cdata); + +#ifdef RUE_INTR_PIPE + free(sc->rue_cdata.ue_ibuf, M_USBDEV); + sc->rue_cdata.ue_ibuf = NULL; +#endif + + sc->rue_link = 0; + + ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); + + RUE_UNLOCK(sc); +} + +/* + * Stop all chip I/O so that the kernel's probe routines don't + * get confused by errant DMAs when rebooting. + */ + +static int +rue_shutdown(device_t dev) +{ + struct rue_softc *sc; + + sc = device_get_softc(dev); + + sc->rue_dying++; + RUE_LOCK(sc); + rue_reset(sc); + rue_stop(sc); + RUE_UNLOCK(sc); + + return (0); +} diff --git a/sys/legacy/dev/usb/if_ruereg.h b/sys/legacy/dev/usb/if_ruereg.h new file mode 100644 index 0000000..da6470f --- /dev/null +++ b/sys/legacy/dev/usb/if_ruereg.h @@ -0,0 +1,226 @@ +/*- + * Copyright (c) 2001-2003, Shunsuke Akiyama <akiyama@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$ + */ + +#ifndef _IF_RUEREG_H_ +#define _IF_RUEREG_H_ + +#define RUE_INTR_PIPE 1 /* Use INTR PIPE */ + +#define RUE_CONFIG_NO 1 +#define RUE_IFACE_IDX 0 + +#define RUE_ENDPT_RX 0x0 +#define RUE_ENDPT_TX 0x1 +#define RUE_ENDPT_INTR 0x2 +#define RUE_ENDPT_MAX 0x3 + +#define RUE_INTR_PKTLEN 0x8 + +#define RUE_TIMEOUT 1000 +#define ETHER_ALIGN 2 +#define RUE_MIN_FRAMELEN 60 +#define RUE_INTR_INTERVAL 100 /* ms */ + +/* + * Registers + */ + +#define RUE_IDR0 0x0120 +#define RUE_IDR1 0x0121 +#define RUE_IDR2 0x0122 +#define RUE_IDR3 0x0123 +#define RUE_IDR4 0x0124 +#define RUE_IDR5 0x0125 + +#define RUE_MAR0 0x0126 +#define RUE_MAR1 0x0127 +#define RUE_MAR2 0x0128 +#define RUE_MAR3 0x0129 +#define RUE_MAR4 0x012A +#define RUE_MAR5 0x012B +#define RUE_MAR6 0x012C +#define RUE_MAR7 0x012D + +#define RUE_CR 0x012E /* B, R/W */ +#define RUE_CR_SOFT_RST 0x10 +#define RUE_CR_RE 0x08 +#define RUE_CR_TE 0x04 +#define RUE_CR_EP3CLREN 0x02 + +#define RUE_TCR 0x012F /* B, R/W */ +#define RUE_TCR_TXRR1 0x80 +#define RUE_TCR_TXRR0 0x40 +#define RUE_TCR_IFG1 0x10 +#define RUE_TCR_IFG0 0x08 +#define RUE_TCR_NOCRC 0x01 +#define RUE_TCR_CONFIG (RUE_TCR_TXRR1|RUE_TCR_TXRR0|RUE_TCR_IFG1|RUE_TCR_IFG0) + +#define RUE_RCR 0x0130 /* W, R/W */ +#define RUE_RCR_TAIL 0x80 +#define RUE_RCR_AER 0x40 +#define RUE_RCR_AR 0x20 +#define RUE_RCR_AM 0x10 +#define RUE_RCR_AB 0x08 +#define RUE_RCR_AD 0x04 +#define RUE_RCR_AAM 0x02 +#define RUE_RCR_AAP 0x01 +#define RUE_RCR_CONFIG (RUE_RCR_TAIL|RUE_RCR_AD) + +#define RUE_TSR 0x0132 +#define RUE_RSR 0x0133 +#define RUE_CON0 0x0135 +#define RUE_CON1 0x0136 +#define RUE_MSR 0x0137 +#define RUE_PHYADD 0x0138 +#define RUE_PHYDAT 0x0139 + +#define RUE_PHYCNT 0x013B /* B, R/W */ +#define RUE_PHYCNT_PHYOWN 0x40 +#define RUE_PHYCNT_RWCR 0x20 + +#define RUE_GPPC 0x013D +#define RUE_WAKECNT 0x013E + +#define RUE_BMCR 0x0140 +#define RUE_BMCR_SPD_SET 0x2000 +#define RUE_BMCR_DUPLEX 0x0100 + +#define RUE_BMSR 0x0142 + +#define RUE_ANAR 0x0144 /* W, R/W */ +#define RUE_ANAR_PAUSE 0x0400 + +#define RUE_ANLP 0x0146 /* W, R/O */ +#define RUE_ANLP_PAUSE 0x0400 + +#define RUE_AER 0x0148 + +#define RUE_NWAYT 0x014A +#define RUE_CSCR 0x014C + +#define RUE_CRC0 0x014E +#define RUE_CRC1 0x0150 +#define RUE_CRC2 0x0152 +#define RUE_CRC3 0x0154 +#define RUE_CRC4 0x0156 + +#define RUE_BYTEMASK0 0x0158 +#define RUE_BYTEMASK1 0x0160 +#define RUE_BYTEMASK2 0x0168 +#define RUE_BYTEMASK3 0x0170 +#define RUE_BYTEMASK4 0x0178 + +#define RUE_PHY1 0x0180 +#define RUE_PHY2 0x0184 + +#define RUE_TW1 0x0186 + +#define RUE_REG_MIN 0x0120 +#define RUE_REG_MAX 0x0189 + +/* + * EEPROM address declarations + */ + +#define RUE_EEPROM_BASE 0x1200 + +#define RUE_EEPROM_IDR0 (RUE_EEPROM_BASE + 0x02) +#define RUE_EEPROM_IDR1 (RUE_EEPROM_BASE + 0x03) +#define RUE_EEPROM_IDR2 (RUE_EEPROM_BASE + 0x03) +#define RUE_EEPROM_IDR3 (RUE_EEPROM_BASE + 0x03) +#define RUE_EEPROM_IDR4 (RUE_EEPROM_BASE + 0x03) +#define RUE_EEPROM_IDR5 (RUE_EEPROM_BASE + 0x03) + +#define RUE_EEPROM_INTERVAL (RUE_EEPROM_BASE + 0x17) + +struct rue_intrpkt { + u_int8_t rue_tsr; + u_int8_t rue_rsr; + u_int8_t rue_gep_msr; + u_int8_t rue_waksr; + u_int8_t rue_txok_cnt; + u_int8_t rue_rxlost_cnt; + u_int8_t rue_crcerr_cnt; + u_int8_t rue_col_cnt; +}; + +struct rue_rxpkt { + u_int16_t rue_pktlen : 12; + u_int16_t rue_rxstat : 4; +}; + +#define RUE_RXSTAT_VALID 0x01 +#define RUE_RXSTAT_RUNT 0x02 +#define RUE_RXSTAT_PMATCH 0x04 +#define RUE_RXSTAT_MCAST 0x08 + +#define RUE_RXSTAT_MASK RUE_RXSTAT_VALID + +struct rue_type { + u_int16_t rue_vid; + u_int16_t rue_did; +}; + +struct rue_softc { + struct ifnet *rue_ifp; + device_t rue_dev; + device_t rue_miibus; + usbd_device_handle rue_udev; + usbd_interface_handle rue_iface; + struct rue_type *rue_info; + int rue_ed[RUE_ENDPT_MAX]; + usbd_pipe_handle rue_ep[RUE_ENDPT_MAX]; + int rue_unit; + u_int8_t rue_link; + int rue_if_flags; + struct ue_cdata rue_cdata; + struct callout_handle rue_stat_ch; + struct mtx rue_mtx; + char rue_dying; + struct timeval rue_rx_notice; + struct usb_qdat rue_qdat; + struct usb_task rue_tick_task; +}; + +#if defined(__FreeBSD__) +#define GET_MII(sc) (device_get_softc((sc)->rue_miibus)) +#elif defined(__NetBSD__) +#define GET_MII(sc) (&(sc)->rue_mii) +#elif defined(__OpenBSD__) +#define GET_MII(sc) (&(sc)->rue_mii) +#endif + +#if 0 +#define RUE_LOCK(_sc) mtx_lock(&(_sc)->rue_mtx) +#define RUE_UNLOCK(_sc) mtx_unlock(&(_sc)->rue_mtx) +#else +#define RUE_LOCK(_sc) +#define RUE_UNLOCK(_sc) +#endif + +#endif /* _IF_RUEREG_H_ */ diff --git a/sys/legacy/dev/usb/if_rum.c b/sys/legacy/dev/usb/if_rum.c new file mode 100644 index 0000000..c0c7cb6 --- /dev/null +++ b/sys/legacy/dev/usb/if_rum.c @@ -0,0 +1,2560 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2005-2007 Damien Bergamini <damien.bergamini@free.fr> + * Copyright (c) 2006 Niall O'Higgins <niallo@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/*- + * Ralink Technology RT2501USB/RT2601USB chipset driver + * http://www.ralinktech.com.tw/ + */ + +#include <sys/param.h> +#include <sys/sysctl.h> +#include <sys/sockio.h> +#include <sys/mbuf.h> +#include <sys/kernel.h> +#include <sys/socket.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/endian.h> + +#include <machine/bus.h> +#include <machine/resource.h> +#include <sys/rman.h> + +#include <net/bpf.h> +#include <net/if.h> +#include <net/if_arp.h> +#include <net/ethernet.h> +#include <net/if_dl.h> +#include <net/if_media.h> +#include <net/if_types.h> + +#include <net80211/ieee80211_var.h> +#include <net80211/ieee80211_amrr.h> +#include <net80211/ieee80211_phy.h> +#include <net80211/ieee80211_radiotap.h> +#include <net80211/ieee80211_regdomain.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include "usbdevs.h" + +#include <dev/usb/if_rumreg.h> +#include <dev/usb/if_rumvar.h> +#include <dev/usb/rt2573_ucode.h> + +#ifdef USB_DEBUG +#define DPRINTF(x) do { if (rumdebug > 0) printf x; } while (0) +#define DPRINTFN(n, x) do { if (rumdebug >= (n)) printf x; } while (0) +int rumdebug = 0; +SYSCTL_NODE(_hw_usb, OID_AUTO, rum, CTLFLAG_RW, 0, "USB rum"); +SYSCTL_INT(_hw_usb_rum, OID_AUTO, debug, CTLFLAG_RW, &rumdebug, 0, + "rum debug level"); +#else +#define DPRINTF(x) +#define DPRINTFN(n, x) +#endif + +/* various supported device vendors/products */ +static const struct usb_devno rum_devs[] = { + { USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_HWU54DM }, + { USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_RT2573_2 }, + { USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_RT2573_3 }, + { USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_RT2573_4 }, + { USB_VENDOR_ABOCOM, USB_PRODUCT_ABOCOM_WUG2700 }, + { USB_VENDOR_AMIT, USB_PRODUCT_AMIT_CGWLUSB2GO }, + { USB_VENDOR_ASUS, USB_PRODUCT_ASUS_RT2573_1 }, + { USB_VENDOR_ASUS, USB_PRODUCT_ASUS_RT2573_2 }, + { USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5D7050A }, + { USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5D9050V3 }, + { USB_VENDOR_CISCOLINKSYS, USB_PRODUCT_CISCOLINKSYS_WUSB54GC }, + { USB_VENDOR_CISCOLINKSYS, USB_PRODUCT_CISCOLINKSYS_WUSB54GR }, + { USB_VENDOR_CONCEPTRONIC2, USB_PRODUCT_CONCEPTRONIC2_C54RU2 }, + { USB_VENDOR_COREGA, USB_PRODUCT_COREGA_CGWLUSB2GL }, + { USB_VENDOR_COREGA, USB_PRODUCT_COREGA_CGWLUSB2GPX }, + { USB_VENDOR_DICKSMITH, USB_PRODUCT_DICKSMITH_CWD854F }, + { USB_VENDOR_DICKSMITH, USB_PRODUCT_DICKSMITH_RT2573 }, + { USB_VENDOR_DLINK2, USB_PRODUCT_DLINK2_DWLG122C1 }, + { USB_VENDOR_DLINK2, USB_PRODUCT_DLINK2_WUA1340 }, + { USB_VENDOR_DLINK2, USB_PRODUCT_DLINK2_DWA111 }, + { USB_VENDOR_DLINK2, USB_PRODUCT_DLINK2_DWA110 }, + { USB_VENDOR_GIGABYTE, USB_PRODUCT_GIGABYTE_GNWB01GS }, + { USB_VENDOR_GIGABYTE, USB_PRODUCT_GIGABYTE_GNWI05GS }, + { USB_VENDOR_GIGASET, USB_PRODUCT_GIGASET_RT2573 }, + { USB_VENDOR_GOODWAY, USB_PRODUCT_GOODWAY_RT2573 }, + { USB_VENDOR_GUILLEMOT, USB_PRODUCT_GUILLEMOT_HWGUSB254LB }, + { USB_VENDOR_GUILLEMOT, USB_PRODUCT_GUILLEMOT_HWGUSB254V2AP }, + { USB_VENDOR_HUAWEI3COM, USB_PRODUCT_HUAWEI3COM_WUB320G }, + { USB_VENDOR_MELCO, USB_PRODUCT_MELCO_G54HP }, + { USB_VENDOR_MELCO, USB_PRODUCT_MELCO_SG54HP }, + { USB_VENDOR_MELCO, USB_PRODUCT_MELCO_SG54HG }, + { USB_VENDOR_MSI, USB_PRODUCT_MSI_RT2573_1 }, + { USB_VENDOR_MSI, USB_PRODUCT_MSI_RT2573_2 }, + { USB_VENDOR_MSI, USB_PRODUCT_MSI_RT2573_3 }, + { USB_VENDOR_MSI, USB_PRODUCT_MSI_RT2573_4 }, + { USB_VENDOR_NOVATECH, USB_PRODUCT_NOVATECH_RT2573 }, + { USB_VENDOR_PLANEX2, USB_PRODUCT_PLANEX2_GWUS54HP }, + { USB_VENDOR_PLANEX2, USB_PRODUCT_PLANEX2_GWUS54MINI2 }, + { USB_VENDOR_PLANEX2, USB_PRODUCT_PLANEX2_GWUSMM }, + { USB_VENDOR_QCOM, USB_PRODUCT_QCOM_RT2573 }, + { USB_VENDOR_QCOM, USB_PRODUCT_QCOM_RT2573_2 }, + { USB_VENDOR_RALINK, USB_PRODUCT_RALINK_RT2573 }, + { USB_VENDOR_RALINK, USB_PRODUCT_RALINK_RT2573_2 }, + { USB_VENDOR_RALINK, USB_PRODUCT_RALINK_RT2671 }, + { USB_VENDOR_SITECOMEU, USB_PRODUCT_SITECOMEU_WL113R2 }, + { USB_VENDOR_SITECOMEU, USB_PRODUCT_SITECOMEU_WL172 }, + { USB_VENDOR_SPARKLAN, USB_PRODUCT_SPARKLAN_RT2573 }, + { USB_VENDOR_SURECOM, USB_PRODUCT_SURECOM_RT2573 } +}; + +MODULE_DEPEND(rum, wlan, 1, 1, 1); +MODULE_DEPEND(rum, wlan_amrr, 1, 1, 1); +MODULE_DEPEND(rum, usb, 1, 1, 1); + +static struct ieee80211vap *rum_vap_create(struct ieee80211com *, + const char name[IFNAMSIZ], int unit, int opmode, + int flags, const uint8_t bssid[IEEE80211_ADDR_LEN], + const uint8_t mac[IEEE80211_ADDR_LEN]); +static void rum_vap_delete(struct ieee80211vap *); +static int rum_alloc_tx_list(struct rum_softc *); +static void rum_free_tx_list(struct rum_softc *); +static int rum_alloc_rx_list(struct rum_softc *); +static void rum_free_rx_list(struct rum_softc *); +static void rum_task(void *); +static void rum_scantask(void *); +static int rum_newstate(struct ieee80211vap *, + enum ieee80211_state, int); +static void rum_txeof(usbd_xfer_handle, usbd_private_handle, + usbd_status); +static void rum_rxeof(usbd_xfer_handle, usbd_private_handle, + usbd_status); +static void rum_setup_tx_desc(struct rum_softc *, + struct rum_tx_desc *, uint32_t, uint16_t, int, + int); +static int rum_tx_mgt(struct rum_softc *, struct mbuf *, + struct ieee80211_node *); +static int rum_tx_raw(struct rum_softc *, struct mbuf *, + struct ieee80211_node *, + const struct ieee80211_bpf_params *); +static int rum_tx_data(struct rum_softc *, struct mbuf *, + struct ieee80211_node *); +static void rum_start(struct ifnet *); +static void rum_watchdog(void *); +static int rum_ioctl(struct ifnet *, u_long, caddr_t); +static void rum_eeprom_read(struct rum_softc *, uint16_t, void *, + int); +static uint32_t rum_read(struct rum_softc *, uint16_t); +static void rum_read_multi(struct rum_softc *, uint16_t, void *, + int); +static void rum_write(struct rum_softc *, uint16_t, uint32_t); +static void rum_write_multi(struct rum_softc *, uint16_t, void *, + size_t); +static void rum_bbp_write(struct rum_softc *, uint8_t, uint8_t); +static uint8_t rum_bbp_read(struct rum_softc *, uint8_t); +static void rum_rf_write(struct rum_softc *, uint8_t, uint32_t); +static void rum_select_antenna(struct rum_softc *); +static void rum_enable_mrr(struct rum_softc *); +static void rum_set_txpreamble(struct rum_softc *); +static void rum_set_basicrates(struct rum_softc *); +static void rum_select_band(struct rum_softc *, + struct ieee80211_channel *); +static void rum_set_chan(struct rum_softc *, + struct ieee80211_channel *); +static void rum_enable_tsf_sync(struct rum_softc *); +static void rum_update_slot(struct ifnet *); +static void rum_set_bssid(struct rum_softc *, const uint8_t *); +static void rum_set_macaddr(struct rum_softc *, const uint8_t *); +static void rum_update_promisc(struct rum_softc *); +static const char *rum_get_rf(int); +static void rum_read_eeprom(struct rum_softc *); +static int rum_bbp_init(struct rum_softc *); +static void rum_init_locked(struct rum_softc *); +static void rum_init(void *); +static void rum_stop(void *); +static int rum_load_microcode(struct rum_softc *, const u_char *, + size_t); +static int rum_prepare_beacon(struct rum_softc *, + struct ieee80211vap *); +static int rum_raw_xmit(struct ieee80211_node *, struct mbuf *, + const struct ieee80211_bpf_params *); +static struct ieee80211_node *rum_node_alloc(struct ieee80211vap *, + const uint8_t mac[IEEE80211_ADDR_LEN]); +static void rum_newassoc(struct ieee80211_node *, int); +static void rum_scan_start(struct ieee80211com *); +static void rum_scan_end(struct ieee80211com *); +static void rum_set_channel(struct ieee80211com *); +static int rum_get_rssi(struct rum_softc *, uint8_t); +static void rum_amrr_start(struct rum_softc *, + struct ieee80211_node *); +static void rum_amrr_timeout(void *); +static void rum_amrr_update(usbd_xfer_handle, usbd_private_handle, + usbd_status); + +static const struct { + uint32_t reg; + uint32_t val; +} rum_def_mac[] = { + { RT2573_TXRX_CSR0, 0x025fb032 }, + { RT2573_TXRX_CSR1, 0x9eaa9eaf }, + { RT2573_TXRX_CSR2, 0x8a8b8c8d }, + { RT2573_TXRX_CSR3, 0x00858687 }, + { RT2573_TXRX_CSR7, 0x2e31353b }, + { RT2573_TXRX_CSR8, 0x2a2a2a2c }, + { RT2573_TXRX_CSR15, 0x0000000f }, + { RT2573_MAC_CSR6, 0x00000fff }, + { RT2573_MAC_CSR8, 0x016c030a }, + { RT2573_MAC_CSR10, 0x00000718 }, + { RT2573_MAC_CSR12, 0x00000004 }, + { RT2573_MAC_CSR13, 0x00007f00 }, + { RT2573_SEC_CSR0, 0x00000000 }, + { RT2573_SEC_CSR1, 0x00000000 }, + { RT2573_SEC_CSR5, 0x00000000 }, + { RT2573_PHY_CSR1, 0x000023b0 }, + { RT2573_PHY_CSR5, 0x00040a06 }, + { RT2573_PHY_CSR6, 0x00080606 }, + { RT2573_PHY_CSR7, 0x00000408 }, + { RT2573_AIFSN_CSR, 0x00002273 }, + { RT2573_CWMIN_CSR, 0x00002344 }, + { RT2573_CWMAX_CSR, 0x000034aa } +}; + +static const struct { + uint8_t reg; + uint8_t val; +} rum_def_bbp[] = { + { 3, 0x80 }, + { 15, 0x30 }, + { 17, 0x20 }, + { 21, 0xc8 }, + { 22, 0x38 }, + { 23, 0x06 }, + { 24, 0xfe }, + { 25, 0x0a }, + { 26, 0x0d }, + { 32, 0x0b }, + { 34, 0x12 }, + { 37, 0x07 }, + { 39, 0xf8 }, + { 41, 0x60 }, + { 53, 0x10 }, + { 54, 0x18 }, + { 60, 0x10 }, + { 61, 0x04 }, + { 62, 0x04 }, + { 75, 0xfe }, + { 86, 0xfe }, + { 88, 0xfe }, + { 90, 0x0f }, + { 99, 0x00 }, + { 102, 0x16 }, + { 107, 0x04 } +}; + +static const struct rfprog { + uint8_t chan; + uint32_t r1, r2, r3, r4; +} rum_rf5226[] = { + { 1, 0x00b03, 0x001e1, 0x1a014, 0x30282 }, + { 2, 0x00b03, 0x001e1, 0x1a014, 0x30287 }, + { 3, 0x00b03, 0x001e2, 0x1a014, 0x30282 }, + { 4, 0x00b03, 0x001e2, 0x1a014, 0x30287 }, + { 5, 0x00b03, 0x001e3, 0x1a014, 0x30282 }, + { 6, 0x00b03, 0x001e3, 0x1a014, 0x30287 }, + { 7, 0x00b03, 0x001e4, 0x1a014, 0x30282 }, + { 8, 0x00b03, 0x001e4, 0x1a014, 0x30287 }, + { 9, 0x00b03, 0x001e5, 0x1a014, 0x30282 }, + { 10, 0x00b03, 0x001e5, 0x1a014, 0x30287 }, + { 11, 0x00b03, 0x001e6, 0x1a014, 0x30282 }, + { 12, 0x00b03, 0x001e6, 0x1a014, 0x30287 }, + { 13, 0x00b03, 0x001e7, 0x1a014, 0x30282 }, + { 14, 0x00b03, 0x001e8, 0x1a014, 0x30284 }, + + { 34, 0x00b03, 0x20266, 0x36014, 0x30282 }, + { 38, 0x00b03, 0x20267, 0x36014, 0x30284 }, + { 42, 0x00b03, 0x20268, 0x36014, 0x30286 }, + { 46, 0x00b03, 0x20269, 0x36014, 0x30288 }, + + { 36, 0x00b03, 0x00266, 0x26014, 0x30288 }, + { 40, 0x00b03, 0x00268, 0x26014, 0x30280 }, + { 44, 0x00b03, 0x00269, 0x26014, 0x30282 }, + { 48, 0x00b03, 0x0026a, 0x26014, 0x30284 }, + { 52, 0x00b03, 0x0026b, 0x26014, 0x30286 }, + { 56, 0x00b03, 0x0026c, 0x26014, 0x30288 }, + { 60, 0x00b03, 0x0026e, 0x26014, 0x30280 }, + { 64, 0x00b03, 0x0026f, 0x26014, 0x30282 }, + + { 100, 0x00b03, 0x0028a, 0x2e014, 0x30280 }, + { 104, 0x00b03, 0x0028b, 0x2e014, 0x30282 }, + { 108, 0x00b03, 0x0028c, 0x2e014, 0x30284 }, + { 112, 0x00b03, 0x0028d, 0x2e014, 0x30286 }, + { 116, 0x00b03, 0x0028e, 0x2e014, 0x30288 }, + { 120, 0x00b03, 0x002a0, 0x2e014, 0x30280 }, + { 124, 0x00b03, 0x002a1, 0x2e014, 0x30282 }, + { 128, 0x00b03, 0x002a2, 0x2e014, 0x30284 }, + { 132, 0x00b03, 0x002a3, 0x2e014, 0x30286 }, + { 136, 0x00b03, 0x002a4, 0x2e014, 0x30288 }, + { 140, 0x00b03, 0x002a6, 0x2e014, 0x30280 }, + + { 149, 0x00b03, 0x002a8, 0x2e014, 0x30287 }, + { 153, 0x00b03, 0x002a9, 0x2e014, 0x30289 }, + { 157, 0x00b03, 0x002ab, 0x2e014, 0x30281 }, + { 161, 0x00b03, 0x002ac, 0x2e014, 0x30283 }, + { 165, 0x00b03, 0x002ad, 0x2e014, 0x30285 } +}, rum_rf5225[] = { + { 1, 0x00b33, 0x011e1, 0x1a014, 0x30282 }, + { 2, 0x00b33, 0x011e1, 0x1a014, 0x30287 }, + { 3, 0x00b33, 0x011e2, 0x1a014, 0x30282 }, + { 4, 0x00b33, 0x011e2, 0x1a014, 0x30287 }, + { 5, 0x00b33, 0x011e3, 0x1a014, 0x30282 }, + { 6, 0x00b33, 0x011e3, 0x1a014, 0x30287 }, + { 7, 0x00b33, 0x011e4, 0x1a014, 0x30282 }, + { 8, 0x00b33, 0x011e4, 0x1a014, 0x30287 }, + { 9, 0x00b33, 0x011e5, 0x1a014, 0x30282 }, + { 10, 0x00b33, 0x011e5, 0x1a014, 0x30287 }, + { 11, 0x00b33, 0x011e6, 0x1a014, 0x30282 }, + { 12, 0x00b33, 0x011e6, 0x1a014, 0x30287 }, + { 13, 0x00b33, 0x011e7, 0x1a014, 0x30282 }, + { 14, 0x00b33, 0x011e8, 0x1a014, 0x30284 }, + + { 34, 0x00b33, 0x01266, 0x26014, 0x30282 }, + { 38, 0x00b33, 0x01267, 0x26014, 0x30284 }, + { 42, 0x00b33, 0x01268, 0x26014, 0x30286 }, + { 46, 0x00b33, 0x01269, 0x26014, 0x30288 }, + + { 36, 0x00b33, 0x01266, 0x26014, 0x30288 }, + { 40, 0x00b33, 0x01268, 0x26014, 0x30280 }, + { 44, 0x00b33, 0x01269, 0x26014, 0x30282 }, + { 48, 0x00b33, 0x0126a, 0x26014, 0x30284 }, + { 52, 0x00b33, 0x0126b, 0x26014, 0x30286 }, + { 56, 0x00b33, 0x0126c, 0x26014, 0x30288 }, + { 60, 0x00b33, 0x0126e, 0x26014, 0x30280 }, + { 64, 0x00b33, 0x0126f, 0x26014, 0x30282 }, + + { 100, 0x00b33, 0x0128a, 0x2e014, 0x30280 }, + { 104, 0x00b33, 0x0128b, 0x2e014, 0x30282 }, + { 108, 0x00b33, 0x0128c, 0x2e014, 0x30284 }, + { 112, 0x00b33, 0x0128d, 0x2e014, 0x30286 }, + { 116, 0x00b33, 0x0128e, 0x2e014, 0x30288 }, + { 120, 0x00b33, 0x012a0, 0x2e014, 0x30280 }, + { 124, 0x00b33, 0x012a1, 0x2e014, 0x30282 }, + { 128, 0x00b33, 0x012a2, 0x2e014, 0x30284 }, + { 132, 0x00b33, 0x012a3, 0x2e014, 0x30286 }, + { 136, 0x00b33, 0x012a4, 0x2e014, 0x30288 }, + { 140, 0x00b33, 0x012a6, 0x2e014, 0x30280 }, + + { 149, 0x00b33, 0x012a8, 0x2e014, 0x30287 }, + { 153, 0x00b33, 0x012a9, 0x2e014, 0x30289 }, + { 157, 0x00b33, 0x012ab, 0x2e014, 0x30281 }, + { 161, 0x00b33, 0x012ac, 0x2e014, 0x30283 }, + { 165, 0x00b33, 0x012ad, 0x2e014, 0x30285 } +}; + +static int +rum_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + + if (uaa->iface != NULL) + return UMATCH_NONE; + + return (usb_lookup(rum_devs, uaa->vendor, uaa->product) != NULL) ? + UMATCH_VENDOR_PRODUCT : UMATCH_NONE; +} + +static int +rum_attach(device_t self) +{ + struct rum_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + struct ieee80211com *ic; + struct ifnet *ifp; + const uint8_t *ucode = NULL; + usb_interface_descriptor_t *id; + usb_endpoint_descriptor_t *ed; + usbd_status error; + int i, ntries, size; + uint8_t bands; + uint32_t tmp; + + sc->sc_udev = uaa->device; + sc->sc_dev = self; + + if (usbd_set_config_no(sc->sc_udev, RT2573_CONFIG_NO, 0) != 0) { + device_printf(self, "could not set configuration no\n"); + return ENXIO; + } + + /* get the first interface handle */ + error = usbd_device2interface_handle(sc->sc_udev, RT2573_IFACE_INDEX, + &sc->sc_iface); + if (error != 0) { + device_printf(self, "could not get interface handle\n"); + return ENXIO; + } + + /* + * Find endpoints. + */ + id = usbd_get_interface_descriptor(sc->sc_iface); + + sc->sc_rx_no = sc->sc_tx_no = -1; + for (i = 0; i < id->bNumEndpoints; i++) { + ed = usbd_interface2endpoint_descriptor(sc->sc_iface, i); + if (ed == NULL) { + device_printf(self, + "no endpoint descriptor for iface %d\n", i); + return ENXIO; + } + + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) + sc->sc_rx_no = ed->bEndpointAddress; + else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) + sc->sc_tx_no = ed->bEndpointAddress; + } + if (sc->sc_rx_no == -1 || sc->sc_tx_no == -1) { + device_printf(self, "missing endpoint\n"); + return ENXIO; + } + + ifp = sc->sc_ifp = if_alloc(IFT_IEEE80211); + if (ifp == NULL) { + device_printf(self, "can not if_alloc()\n"); + return ENXIO; + } + ic = ifp->if_l2com; + + mtx_init(&sc->sc_mtx, device_get_nameunit(sc->sc_dev), MTX_NETWORK_LOCK, + MTX_DEF | MTX_RECURSE); + + usb_init_task(&sc->sc_task, rum_task, sc); + usb_init_task(&sc->sc_scantask, rum_scantask, sc); + callout_init(&sc->watchdog_ch, 0); + + /* retrieve RT2573 rev. no */ + for (ntries = 0; ntries < 1000; ntries++) { + if ((tmp = rum_read(sc, RT2573_MAC_CSR0)) != 0) + break; + DELAY(1000); + } + if (ntries == 1000) { + device_printf(self, "timeout waiting for chip to settle\n"); + goto bad; + } + + /* retrieve MAC address and various other things from EEPROM */ + rum_read_eeprom(sc); + + device_printf(self, "MAC/BBP RT2573 (rev 0x%05x), RF %s\n", + tmp, rum_get_rf(sc->rf_rev)); + + ucode = rt2573_ucode; + size = sizeof rt2573_ucode; + error = rum_load_microcode(sc, ucode, size); + if (error != 0) { + device_printf(self, "could not load 8051 microcode\n"); + goto bad; + } + + ifp->if_softc = sc; + if_initname(ifp, "rum", device_get_unit(sc->sc_dev)); + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST | + IFF_NEEDSGIANT; /* USB stack is still under Giant lock */ + ifp->if_init = rum_init; + ifp->if_ioctl = rum_ioctl; + ifp->if_start = rum_start; + IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN); + ifp->if_snd.ifq_drv_maxlen = IFQ_MAXLEN; + IFQ_SET_READY(&ifp->if_snd); + + ic->ic_ifp = ifp; + ic->ic_phytype = IEEE80211_T_OFDM; /* not only, but not used */ + + /* set device capabilities */ + ic->ic_caps = + IEEE80211_C_STA /* station mode supported */ + | IEEE80211_C_IBSS /* IBSS mode supported */ + | IEEE80211_C_MONITOR /* monitor mode supported */ + | IEEE80211_C_HOSTAP /* HostAp mode supported */ + | IEEE80211_C_TXPMGT /* tx power management */ + | IEEE80211_C_SHPREAMBLE /* short preamble supported */ + | IEEE80211_C_SHSLOT /* short slot time supported */ + | IEEE80211_C_BGSCAN /* bg scanning supported */ + | IEEE80211_C_WPA /* 802.11i */ + ; + + bands = 0; + setbit(&bands, IEEE80211_MODE_11B); + setbit(&bands, IEEE80211_MODE_11G); + if (sc->rf_rev == RT2573_RF_5225 || sc->rf_rev == RT2573_RF_5226) + setbit(&bands, IEEE80211_MODE_11A); + ieee80211_init_channels(ic, NULL, &bands); + + ieee80211_ifattach(ic); + ic->ic_newassoc = rum_newassoc; + ic->ic_raw_xmit = rum_raw_xmit; + ic->ic_node_alloc = rum_node_alloc; + ic->ic_scan_start = rum_scan_start; + ic->ic_scan_end = rum_scan_end; + ic->ic_set_channel = rum_set_channel; + + ic->ic_vap_create = rum_vap_create; + ic->ic_vap_delete = rum_vap_delete; + + sc->sc_rates = ieee80211_get_ratetable(ic->ic_curchan); + + bpfattach(ifp, DLT_IEEE802_11_RADIO, + sizeof (struct ieee80211_frame) + sizeof(sc->sc_txtap)); + + sc->sc_rxtap_len = sizeof sc->sc_rxtap; + sc->sc_rxtap.wr_ihdr.it_len = htole16(sc->sc_rxtap_len); + sc->sc_rxtap.wr_ihdr.it_present = htole32(RT2573_RX_RADIOTAP_PRESENT); + + sc->sc_txtap_len = sizeof sc->sc_txtap; + sc->sc_txtap.wt_ihdr.it_len = htole16(sc->sc_txtap_len); + sc->sc_txtap.wt_ihdr.it_present = htole32(RT2573_TX_RADIOTAP_PRESENT); + + if (bootverbose) + ieee80211_announce(ic); + + return 0; +bad: + mtx_destroy(&sc->sc_mtx); + if_free(ifp); + return ENXIO; +} + +static int +rum_detach(device_t self) +{ + struct rum_softc *sc = device_get_softc(self); + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + rum_stop(sc); + bpfdetach(ifp); + ieee80211_ifdetach(ic); + + usb_rem_task(sc->sc_udev, &sc->sc_task); + usb_rem_task(sc->sc_udev, &sc->sc_scantask); + callout_stop(&sc->watchdog_ch); + + if (sc->amrr_xfer != NULL) { + usbd_free_xfer(sc->amrr_xfer); + sc->amrr_xfer = NULL; + } + + if (sc->sc_rx_pipeh != NULL) { + usbd_abort_pipe(sc->sc_rx_pipeh); + usbd_close_pipe(sc->sc_rx_pipeh); + } + if (sc->sc_tx_pipeh != NULL) { + usbd_abort_pipe(sc->sc_tx_pipeh); + usbd_close_pipe(sc->sc_tx_pipeh); + } + + rum_free_rx_list(sc); + rum_free_tx_list(sc); + + if_free(ifp); + mtx_destroy(&sc->sc_mtx); + + return 0; +} + +static struct ieee80211vap * +rum_vap_create(struct ieee80211com *ic, + const char name[IFNAMSIZ], int unit, int opmode, int flags, + const uint8_t bssid[IEEE80211_ADDR_LEN], + const uint8_t mac[IEEE80211_ADDR_LEN]) +{ + struct rum_vap *rvp; + struct ieee80211vap *vap; + + if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */ + return NULL; + rvp = (struct rum_vap *) malloc(sizeof(struct rum_vap), + M_80211_VAP, M_NOWAIT | M_ZERO); + if (rvp == NULL) + return NULL; + vap = &rvp->vap; + /* enable s/w bmiss handling for sta mode */ + ieee80211_vap_setup(ic, vap, name, unit, opmode, + flags | IEEE80211_CLONE_NOBEACONS, bssid, mac); + + /* override state transition machine */ + rvp->newstate = vap->iv_newstate; + vap->iv_newstate = rum_newstate; + + callout_init(&rvp->amrr_ch, 0); + ieee80211_amrr_init(&rvp->amrr, vap, + IEEE80211_AMRR_MIN_SUCCESS_THRESHOLD, + IEEE80211_AMRR_MAX_SUCCESS_THRESHOLD, + 1000 /* 1 sec */); + + /* complete setup */ + ieee80211_vap_attach(vap, ieee80211_media_change, ieee80211_media_status); + ic->ic_opmode = opmode; + return vap; +} + +static void +rum_vap_delete(struct ieee80211vap *vap) +{ + struct rum_vap *rvp = RUM_VAP(vap); + + callout_stop(&rvp->amrr_ch); + ieee80211_amrr_cleanup(&rvp->amrr); + ieee80211_vap_detach(vap); + free(rvp, M_80211_VAP); +} + +static int +rum_alloc_tx_list(struct rum_softc *sc) +{ + struct rum_tx_data *data; + int i, error; + + sc->tx_queued = sc->tx_cur = 0; + + for (i = 0; i < RUM_TX_LIST_COUNT; i++) { + data = &sc->tx_data[i]; + + data->sc = sc; + + data->xfer = usbd_alloc_xfer(sc->sc_udev); + if (data->xfer == NULL) { + device_printf(sc->sc_dev, + "could not allocate tx xfer\n"); + error = ENOMEM; + goto fail; + } + data->buf = usbd_alloc_buffer(data->xfer, + RT2573_TX_DESC_SIZE + MCLBYTES); + if (data->buf == NULL) { + device_printf(sc->sc_dev, + "could not allocate tx buffer\n"); + error = ENOMEM; + goto fail; + } + /* clean Tx descriptor */ + bzero(data->buf, RT2573_TX_DESC_SIZE); + } + + return 0; + +fail: rum_free_tx_list(sc); + return error; +} + +static void +rum_free_tx_list(struct rum_softc *sc) +{ + struct rum_tx_data *data; + int i; + + for (i = 0; i < RUM_TX_LIST_COUNT; i++) { + data = &sc->tx_data[i]; + + if (data->xfer != NULL) { + usbd_free_xfer(data->xfer); + data->xfer = NULL; + } + + if (data->ni != NULL) { + ieee80211_free_node(data->ni); + data->ni = NULL; + } + } +} + +static int +rum_alloc_rx_list(struct rum_softc *sc) +{ + struct rum_rx_data *data; + int i, error; + + for (i = 0; i < RUM_RX_LIST_COUNT; i++) { + data = &sc->rx_data[i]; + + data->sc = sc; + + data->xfer = usbd_alloc_xfer(sc->sc_udev); + if (data->xfer == NULL) { + device_printf(sc->sc_dev, + "could not allocate rx xfer\n"); + error = ENOMEM; + goto fail; + } + if (usbd_alloc_buffer(data->xfer, MCLBYTES) == NULL) { + device_printf(sc->sc_dev, + "could not allocate rx buffer\n"); + error = ENOMEM; + goto fail; + } + + data->m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + if (data->m == NULL) { + device_printf(sc->sc_dev, + "could not allocate rx mbuf\n"); + error = ENOMEM; + goto fail; + } + + data->buf = mtod(data->m, uint8_t *); + } + + return 0; + +fail: rum_free_rx_list(sc); + return error; +} + +static void +rum_free_rx_list(struct rum_softc *sc) +{ + struct rum_rx_data *data; + int i; + + for (i = 0; i < RUM_RX_LIST_COUNT; i++) { + data = &sc->rx_data[i]; + + if (data->xfer != NULL) { + usbd_free_xfer(data->xfer); + data->xfer = NULL; + } + if (data->m != NULL) { + m_freem(data->m); + data->m = NULL; + } + } +} + +static void +rum_task(void *arg) +{ + struct rum_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); + struct rum_vap *rvp = RUM_VAP(vap); + const struct ieee80211_txparam *tp; + enum ieee80211_state ostate; + struct ieee80211_node *ni; + uint32_t tmp; + + ostate = vap->iv_state; + + RUM_LOCK(sc); + + switch (sc->sc_state) { + case IEEE80211_S_INIT: + if (ostate == IEEE80211_S_RUN) { + /* abort TSF synchronization */ + tmp = rum_read(sc, RT2573_TXRX_CSR9); + rum_write(sc, RT2573_TXRX_CSR9, tmp & ~0x00ffffff); + } + break; + + case IEEE80211_S_RUN: + ni = vap->iv_bss; + + if (vap->iv_opmode != IEEE80211_M_MONITOR) { + rum_update_slot(ic->ic_ifp); + rum_enable_mrr(sc); + rum_set_txpreamble(sc); + rum_set_basicrates(sc); + rum_set_bssid(sc, ni->ni_bssid); + } + + if (vap->iv_opmode == IEEE80211_M_HOSTAP || + vap->iv_opmode == IEEE80211_M_IBSS) + rum_prepare_beacon(sc, vap); + + if (vap->iv_opmode != IEEE80211_M_MONITOR) + rum_enable_tsf_sync(sc); + + /* enable automatic rate adaptation */ + tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_bsschan)]; + if (tp->ucastrate == IEEE80211_FIXED_RATE_NONE) + rum_amrr_start(sc, ni); + break; + default: + break; + } + + RUM_UNLOCK(sc); + + IEEE80211_LOCK(ic); + rvp->newstate(vap, sc->sc_state, sc->sc_arg); + if (vap->iv_newstate_cb != NULL) + vap->iv_newstate_cb(vap, sc->sc_state, sc->sc_arg); + IEEE80211_UNLOCK(ic); +} + +static int +rum_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) +{ + struct rum_vap *rvp = RUM_VAP(vap); + struct ieee80211com *ic = vap->iv_ic; + struct rum_softc *sc = ic->ic_ifp->if_softc; + + usb_rem_task(sc->sc_udev, &sc->sc_task); + usb_rem_task(sc->sc_udev, &sc->sc_scantask); + callout_stop(&rvp->amrr_ch); + + /* do it in a process context */ + sc->sc_state = nstate; + sc->sc_arg = arg; + + if (nstate == IEEE80211_S_INIT) { + rvp->newstate(vap, nstate, arg); + return 0; + } else { + usb_add_task(sc->sc_udev, &sc->sc_task, USB_TASKQ_DRIVER); + return EINPROGRESS; + } +} + +static void +rum_txeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + struct rum_tx_data *data = priv; + struct rum_softc *sc = data->sc; + struct ifnet *ifp = sc->sc_ifp; + + if (data->m != NULL && data->m->m_flags & M_TXCB) + ieee80211_process_callback(data->ni, data->m, 0/*XXX*/); + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) + return; + + device_printf(sc->sc_dev, "could not transmit buffer: %s\n", + usbd_errstr(status)); + + if (status == USBD_STALLED) + usbd_clear_endpoint_stall_async(sc->sc_tx_pipeh); + + ifp->if_oerrors++; + return; + } + + m_freem(data->m); + data->m = NULL; + ieee80211_free_node(data->ni); + data->ni = NULL; + + sc->tx_queued--; + ifp->if_opackets++; + + DPRINTFN(10, ("tx done\n")); + + sc->sc_tx_timer = 0; + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + rum_start(ifp); +} + +static void +rum_rxeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + struct rum_rx_data *data = priv; + struct rum_softc *sc = data->sc; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct rum_rx_desc *desc; + struct ieee80211_node *ni; + struct mbuf *mnew, *m; + int len, rssi; + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) + return; + + if (status == USBD_STALLED) + usbd_clear_endpoint_stall_async(sc->sc_rx_pipeh); + goto skip; + } + + usbd_get_xfer_status(xfer, NULL, NULL, &len, NULL); + + if (len < RT2573_RX_DESC_SIZE + sizeof (struct ieee80211_frame_min)) { + DPRINTF(("%s: xfer too short %d\n", + device_get_nameunit(sc->sc_dev), len)); + ifp->if_ierrors++; + goto skip; + } + + desc = (struct rum_rx_desc *)data->buf; + + if (le32toh(desc->flags) & RT2573_RX_CRC_ERROR) { + /* + * This should not happen since we did not request to receive + * those frames when we filled RT2573_TXRX_CSR0. + */ + DPRINTFN(5, ("CRC error\n")); + ifp->if_ierrors++; + goto skip; + } + + mnew = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + if (mnew == NULL) { + ifp->if_ierrors++; + goto skip; + } + + m = data->m; + data->m = mnew; + data->buf = mtod(data->m, uint8_t *); + + /* finalize mbuf */ + m->m_pkthdr.rcvif = ifp; + m->m_data = (caddr_t)(desc + 1); + m->m_pkthdr.len = m->m_len = (le32toh(desc->flags) >> 16) & 0xfff; + + rssi = rum_get_rssi(sc, desc->rssi); + + if (bpf_peers_present(ifp->if_bpf)) { + struct rum_rx_radiotap_header *tap = &sc->sc_rxtap; + + tap->wr_flags = IEEE80211_RADIOTAP_F_FCS; + tap->wr_rate = ieee80211_plcp2rate(desc->rate, + (desc->flags & htole32(RT2573_RX_OFDM)) ? + IEEE80211_T_OFDM : IEEE80211_T_CCK); + tap->wr_chan_freq = htole16(ic->ic_curchan->ic_freq); + tap->wr_chan_flags = htole16(ic->ic_curchan->ic_flags); + tap->wr_antenna = sc->rx_ant; + tap->wr_antsignal = rssi; + + bpf_mtap2(ifp->if_bpf, tap, sc->sc_rxtap_len, m); + } + + ni = ieee80211_find_rxnode(ic, mtod(m, struct ieee80211_frame_min *)); + if (ni != NULL) { + /* Error happened during RSSI conversion. */ + if (rssi < 0) + rssi = -30; /* XXX ignored by net80211 */ + (void) ieee80211_input(ni, m, rssi, RT2573_NOISE_FLOOR, 0); + ieee80211_free_node(ni); + } else + (void) ieee80211_input_all(ic, m, rssi, RT2573_NOISE_FLOOR, 0); + + DPRINTFN(15, ("rx done\n")); + +skip: /* setup a new transfer */ + usbd_setup_xfer(xfer, sc->sc_rx_pipeh, data, data->buf, MCLBYTES, + USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT, rum_rxeof); + usbd_transfer(xfer); +} + +static uint8_t +rum_plcp_signal(int rate) +{ + switch (rate) { + /* OFDM rates (cf IEEE Std 802.11a-1999, pp. 14 Table 80) */ + case 12: return 0xb; + case 18: return 0xf; + case 24: return 0xa; + case 36: return 0xe; + case 48: return 0x9; + case 72: return 0xd; + case 96: return 0x8; + case 108: return 0xc; + + /* CCK rates (NB: not IEEE std, device-specific) */ + case 2: return 0x0; + case 4: return 0x1; + case 11: return 0x2; + case 22: return 0x3; + } + return 0xff; /* XXX unsupported/unknown rate */ +} + +static void +rum_setup_tx_desc(struct rum_softc *sc, struct rum_tx_desc *desc, + uint32_t flags, uint16_t xflags, int len, int rate) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + uint16_t plcp_length; + int remainder; + + desc->flags = htole32(flags); + desc->flags |= htole32(RT2573_TX_VALID); + desc->flags |= htole32(len << 16); + + desc->xflags = htole16(xflags); + + desc->wme = htole16(RT2573_QID(0) | RT2573_AIFSN(2) | + RT2573_LOGCWMIN(4) | RT2573_LOGCWMAX(10)); + + /* setup PLCP fields */ + desc->plcp_signal = rum_plcp_signal(rate); + desc->plcp_service = 4; + + len += IEEE80211_CRC_LEN; + if (ieee80211_rate2phytype(sc->sc_rates, rate) == IEEE80211_T_OFDM) { + desc->flags |= htole32(RT2573_TX_OFDM); + + plcp_length = len & 0xfff; + desc->plcp_length_hi = plcp_length >> 6; + desc->plcp_length_lo = plcp_length & 0x3f; + } else { + plcp_length = (16 * len + rate - 1) / rate; + if (rate == 22) { + remainder = (16 * len) % 22; + if (remainder != 0 && remainder < 7) + desc->plcp_service |= RT2573_PLCP_LENGEXT; + } + desc->plcp_length_hi = plcp_length >> 8; + desc->plcp_length_lo = plcp_length & 0xff; + + if (rate != 2 && (ic->ic_flags & IEEE80211_F_SHPREAMBLE)) + desc->plcp_signal |= 0x08; + } +} + +#define RUM_TX_TIMEOUT 5000 + +static int +rum_sendprot(struct rum_softc *sc, + const struct mbuf *m, struct ieee80211_node *ni, int prot, int rate) +{ + struct ieee80211com *ic = ni->ni_ic; + const struct ieee80211_frame *wh; + struct rum_tx_desc *desc; + struct rum_tx_data *data; + struct mbuf *mprot; + int protrate, ackrate, pktlen, flags, isshort; + uint16_t dur; + usbd_status error; + + KASSERT(prot == IEEE80211_PROT_RTSCTS || prot == IEEE80211_PROT_CTSONLY, + ("protection %d", prot)); + + wh = mtod(m, const struct ieee80211_frame *); + pktlen = m->m_pkthdr.len + IEEE80211_CRC_LEN; + + protrate = ieee80211_ctl_rate(sc->sc_rates, rate); + ackrate = ieee80211_ack_rate(sc->sc_rates, rate); + + isshort = (ic->ic_flags & IEEE80211_F_SHPREAMBLE) != 0; + dur = ieee80211_compute_duration(sc->sc_rates, pktlen, rate, isshort); + + ieee80211_ack_duration(sc->sc_rates, rate, isshort); + flags = RT2573_TX_MORE_FRAG; + if (prot == IEEE80211_PROT_RTSCTS) { + /* NB: CTS is the same size as an ACK */ + dur += ieee80211_ack_duration(sc->sc_rates, rate, isshort); + flags |= RT2573_TX_NEED_ACK; + mprot = ieee80211_alloc_rts(ic, wh->i_addr1, wh->i_addr2, dur); + } else { + mprot = ieee80211_alloc_cts(ic, ni->ni_vap->iv_myaddr, dur); + } + if (mprot == NULL) { + /* XXX stat + msg */ + return ENOBUFS; + } + data = &sc->tx_data[sc->tx_cur]; + desc = (struct rum_tx_desc *)data->buf; + + data->m = mprot; + data->ni = ieee80211_ref_node(ni); + m_copydata(mprot, 0, mprot->m_pkthdr.len, + data->buf + RT2573_TX_DESC_SIZE); + rum_setup_tx_desc(sc, desc, flags, 0, mprot->m_pkthdr.len, protrate); + + usbd_setup_xfer(data->xfer, sc->sc_tx_pipeh, data, data->buf, + /* NB: no roundup necessary */ + RT2573_TX_DESC_SIZE + mprot->m_pkthdr.len, + USBD_FORCE_SHORT_XFER | USBD_NO_COPY, RUM_TX_TIMEOUT, rum_txeof); + + error = usbd_transfer(data->xfer); + if (error != USBD_NORMAL_COMPLETION && error != USBD_IN_PROGRESS) { + data->m = NULL; + data->ni = NULL; + return error; + } + + sc->tx_queued++; + sc->tx_cur = (sc->tx_cur + 1) % RUM_TX_LIST_COUNT; + + return 0; +} + +static int +rum_tx_mgt(struct rum_softc *sc, struct mbuf *m0, struct ieee80211_node *ni) +{ + struct ieee80211vap *vap = ni->ni_vap; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct rum_tx_desc *desc; + struct rum_tx_data *data; + struct ieee80211_frame *wh; + const struct ieee80211_txparam *tp; + struct ieee80211_key *k; + uint32_t flags = 0; + uint16_t dur; + usbd_status error; + int xferlen; + + data = &sc->tx_data[sc->tx_cur]; + data->m = m0; + data->ni = ni; + desc = (struct rum_tx_desc *)data->buf; + + wh = mtod(m0, struct ieee80211_frame *); + if (wh->i_fc[1] & IEEE80211_FC1_WEP) { + k = ieee80211_crypto_encap(ni, m0); + if (k == NULL) { + m_freem(m0); + return ENOBUFS; + } + wh = mtod(m0, struct ieee80211_frame *); + } + + tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)]; + + if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { + flags |= RT2573_TX_NEED_ACK; + + dur = ieee80211_ack_duration(sc->sc_rates, tp->mgmtrate, + ic->ic_flags & IEEE80211_F_SHPREAMBLE); + *(uint16_t *)wh->i_dur = htole16(dur); + + /* tell hardware to add timestamp for probe responses */ + if ((wh->i_fc[0] & + (IEEE80211_FC0_TYPE_MASK | IEEE80211_FC0_SUBTYPE_MASK)) == + (IEEE80211_FC0_TYPE_MGT | IEEE80211_FC0_SUBTYPE_PROBE_RESP)) + flags |= RT2573_TX_TIMESTAMP; + } + + if (bpf_peers_present(ifp->if_bpf)) { + struct rum_tx_radiotap_header *tap = &sc->sc_txtap; + + tap->wt_flags = 0; + tap->wt_rate = tp->mgmtrate; + tap->wt_chan_freq = htole16(ic->ic_curchan->ic_freq); + tap->wt_chan_flags = htole16(ic->ic_curchan->ic_flags); + tap->wt_antenna = sc->tx_ant; + + bpf_mtap2(ifp->if_bpf, tap, sc->sc_txtap_len, m0); + } + + m_copydata(m0, 0, m0->m_pkthdr.len, data->buf + RT2573_TX_DESC_SIZE); + rum_setup_tx_desc(sc, desc, flags, 0, m0->m_pkthdr.len, tp->mgmtrate); + + /* align end on a 4-bytes boundary */ + xferlen = (RT2573_TX_DESC_SIZE + m0->m_pkthdr.len + 3) & ~3; + + /* + * No space left in the last URB to store the extra 4 bytes, force + * sending of another URB. + */ + if ((xferlen % 64) == 0) + xferlen += 4; + + DPRINTFN(10, ("sending mgt frame len=%d rate=%d xfer len=%d\n", + m0->m_pkthdr.len + (int)RT2573_TX_DESC_SIZE, tp->mgmtrate, xferlen)); + + usbd_setup_xfer(data->xfer, sc->sc_tx_pipeh, data, data->buf, xferlen, + USBD_FORCE_SHORT_XFER | USBD_NO_COPY, RUM_TX_TIMEOUT, rum_txeof); + + error = usbd_transfer(data->xfer); + if (error != USBD_NORMAL_COMPLETION && error != USBD_IN_PROGRESS) { + m_freem(m0); + data->m = NULL; + data->ni = NULL; + return error; + } + + sc->tx_queued++; + sc->tx_cur = (sc->tx_cur + 1) % RUM_TX_LIST_COUNT; + + return 0; +} + +static int +rum_tx_raw(struct rum_softc *sc, struct mbuf *m0, struct ieee80211_node *ni, + const struct ieee80211_bpf_params *params) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct rum_tx_desc *desc; + struct rum_tx_data *data; + uint32_t flags; + usbd_status error; + int xferlen, rate; + + KASSERT(params != NULL, ("no raw xmit params")); + + data = &sc->tx_data[sc->tx_cur]; + desc = (struct rum_tx_desc *)data->buf; + + rate = params->ibp_rate0 & IEEE80211_RATE_VAL; + /* XXX validate */ + if (rate == 0) { + m_freem(m0); + return EINVAL; + } + flags = 0; + if ((params->ibp_flags & IEEE80211_BPF_NOACK) == 0) + flags |= RT2573_TX_NEED_ACK; + if (params->ibp_flags & (IEEE80211_BPF_RTS|IEEE80211_BPF_CTS)) { + error = rum_sendprot(sc, m0, ni, + params->ibp_flags & IEEE80211_BPF_RTS ? + IEEE80211_PROT_RTSCTS : IEEE80211_PROT_CTSONLY, + rate); + if (error) { + m_freem(m0); + return error; + } + flags |= RT2573_TX_LONG_RETRY | RT2573_TX_IFS_SIFS; + } + + if (bpf_peers_present(ifp->if_bpf)) { + struct rum_tx_radiotap_header *tap = &sc->sc_txtap; + + tap->wt_flags = 0; + tap->wt_rate = rate; + tap->wt_chan_freq = htole16(ic->ic_curchan->ic_freq); + tap->wt_chan_flags = htole16(ic->ic_curchan->ic_flags); + tap->wt_antenna = sc->tx_ant; + + bpf_mtap2(ifp->if_bpf, tap, sc->sc_txtap_len, m0); + } + + data->m = m0; + data->ni = ni; + + m_copydata(m0, 0, m0->m_pkthdr.len, data->buf + RT2573_TX_DESC_SIZE); + /* XXX need to setup descriptor ourself */ + rum_setup_tx_desc(sc, desc, flags, 0, m0->m_pkthdr.len, rate); + + /* align end on a 4-bytes boundary */ + xferlen = (RT2573_TX_DESC_SIZE + m0->m_pkthdr.len + 3) & ~3; + + /* + * No space left in the last URB to store the extra 4 bytes, force + * sending of another URB. + */ + if ((xferlen % 64) == 0) + xferlen += 4; + + DPRINTFN(10, ("sending raw frame len=%u rate=%u xfer len=%u\n", + m0->m_pkthdr.len, rate, xferlen)); + + usbd_setup_xfer(data->xfer, sc->sc_tx_pipeh, data, data->buf, + xferlen, USBD_FORCE_SHORT_XFER | USBD_NO_COPY, RUM_TX_TIMEOUT, + rum_txeof); + + error = usbd_transfer(data->xfer); + if (error != USBD_NORMAL_COMPLETION && error != USBD_IN_PROGRESS) + return error; + + sc->tx_queued++; + sc->tx_cur = (sc->tx_cur + 1) % RUM_TX_LIST_COUNT; + + return 0; +} + +static int +rum_tx_data(struct rum_softc *sc, struct mbuf *m0, struct ieee80211_node *ni) +{ + struct ieee80211vap *vap = ni->ni_vap; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct rum_tx_desc *desc; + struct rum_tx_data *data; + struct ieee80211_frame *wh; + const struct ieee80211_txparam *tp; + struct ieee80211_key *k; + uint32_t flags = 0; + uint16_t dur; + usbd_status error; + int rate, xferlen; + + wh = mtod(m0, struct ieee80211_frame *); + + tp = &vap->iv_txparms[ieee80211_chan2mode(ni->ni_chan)]; + if (IEEE80211_IS_MULTICAST(wh->i_addr1)) + rate = tp->mcastrate; + else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) + rate = tp->ucastrate; + else { + (void) ieee80211_amrr_choose(ni, &RUM_NODE(ni)->amn); + rate = ni->ni_txrate; + } + + if (wh->i_fc[1] & IEEE80211_FC1_WEP) { + k = ieee80211_crypto_encap(ni, m0); + if (k == NULL) { + m_freem(m0); + return ENOBUFS; + } + + /* packet header may have moved, reset our local pointer */ + wh = mtod(m0, struct ieee80211_frame *); + } + + if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { + int prot = IEEE80211_PROT_NONE; + if (m0->m_pkthdr.len + IEEE80211_CRC_LEN > vap->iv_rtsthreshold) + prot = IEEE80211_PROT_RTSCTS; + else if ((ic->ic_flags & IEEE80211_F_USEPROT) && + ieee80211_rate2phytype(sc->sc_rates, rate) == IEEE80211_T_OFDM) + prot = ic->ic_protmode; + if (prot != IEEE80211_PROT_NONE) { + error = rum_sendprot(sc, m0, ni, prot, rate); + if (error) { + m_freem(m0); + return error; + } + flags |= RT2573_TX_LONG_RETRY | RT2573_TX_IFS_SIFS; + } + } + + data = &sc->tx_data[sc->tx_cur]; + desc = (struct rum_tx_desc *)data->buf; + + data->m = m0; + data->ni = ni; + + if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { + flags |= RT2573_TX_NEED_ACK; + flags |= RT2573_TX_MORE_FRAG; + + dur = ieee80211_ack_duration(sc->sc_rates, rate, + ic->ic_flags & IEEE80211_F_SHPREAMBLE); + *(uint16_t *)wh->i_dur = htole16(dur); + } + + if (bpf_peers_present(ifp->if_bpf)) { + struct rum_tx_radiotap_header *tap = &sc->sc_txtap; + + tap->wt_flags = 0; + tap->wt_rate = rate; + tap->wt_chan_freq = htole16(ic->ic_curchan->ic_freq); + tap->wt_chan_flags = htole16(ic->ic_curchan->ic_flags); + tap->wt_antenna = sc->tx_ant; + + bpf_mtap2(ifp->if_bpf, tap, sc->sc_txtap_len, m0); + } + + m_copydata(m0, 0, m0->m_pkthdr.len, data->buf + RT2573_TX_DESC_SIZE); + rum_setup_tx_desc(sc, desc, flags, 0, m0->m_pkthdr.len, rate); + + /* align end on a 4-bytes boundary */ + xferlen = (RT2573_TX_DESC_SIZE + m0->m_pkthdr.len + 3) & ~3; + + /* + * No space left in the last URB to store the extra 4 bytes, force + * sending of another URB. + */ + if ((xferlen % 64) == 0) + xferlen += 4; + + DPRINTFN(10, ("sending frame len=%d rate=%d xfer len=%d\n", + m0->m_pkthdr.len + (int)RT2573_TX_DESC_SIZE, rate, xferlen)); + + usbd_setup_xfer(data->xfer, sc->sc_tx_pipeh, data, data->buf, xferlen, + USBD_FORCE_SHORT_XFER | USBD_NO_COPY, RUM_TX_TIMEOUT, rum_txeof); + + error = usbd_transfer(data->xfer); + if (error != USBD_NORMAL_COMPLETION && error != USBD_IN_PROGRESS) { + m_freem(m0); + data->m = NULL; + data->ni = NULL; + return error; + } + + sc->tx_queued++; + sc->tx_cur = (sc->tx_cur + 1) % RUM_TX_LIST_COUNT; + + return 0; +} + +static void +rum_start(struct ifnet *ifp) +{ + struct rum_softc *sc = ifp->if_softc; + struct ieee80211_node *ni; + struct mbuf *m; + + for (;;) { + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + if (m == NULL) + break; + if (sc->tx_queued >= RUM_TX_LIST_COUNT-1) { + IFQ_DRV_PREPEND(&ifp->if_snd, m); + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + break; + } + ni = (struct ieee80211_node *) m->m_pkthdr.rcvif; + m = ieee80211_encap(ni, m); + if (m == NULL) { + ieee80211_free_node(ni); + continue; + } + if (rum_tx_data(sc, m, ni) != 0) { + ieee80211_free_node(ni); + ifp->if_oerrors++; + break; + } + sc->sc_tx_timer = 5; + callout_reset(&sc->watchdog_ch, hz, rum_watchdog, sc); + } +} + +static void +rum_watchdog(void *arg) +{ + struct rum_softc *sc = arg; + + RUM_LOCK(sc); + + if (sc->sc_tx_timer > 0) { + if (--sc->sc_tx_timer == 0) { + device_printf(sc->sc_dev, "device timeout\n"); + /*rum_init(ifp); XXX needs a process context! */ + sc->sc_ifp->if_oerrors++; + RUM_UNLOCK(sc); + return; + } + callout_reset(&sc->watchdog_ch, hz, rum_watchdog, sc); + } + + RUM_UNLOCK(sc); +} + +static int +rum_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) +{ + struct rum_softc *sc = ifp->if_softc; + struct ieee80211com *ic = ifp->if_l2com; + struct ifreq *ifr = (struct ifreq *) data; + int error = 0, startall = 0; + + switch (cmd) { + case SIOCSIFFLAGS: + RUM_LOCK(sc); + if (ifp->if_flags & IFF_UP) { + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) { + rum_init(sc); + startall = 1; + } else + rum_update_promisc(sc); + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + rum_stop(sc); + } + RUM_UNLOCK(sc); + if (startall) + ieee80211_start_all(ic); + break; + case SIOCGIFMEDIA: + error = ifmedia_ioctl(ifp, ifr, &ic->ic_media, cmd); + break; + case SIOCGIFADDR: + error = ether_ioctl(ifp, cmd, data); + break; + default: + error = EINVAL; + break; + } + return error; +} + +static void +rum_eeprom_read(struct rum_softc *sc, uint16_t addr, void *buf, int len) +{ + usb_device_request_t req; + usbd_status error; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = RT2573_READ_EEPROM; + USETW(req.wValue, 0); + USETW(req.wIndex, addr); + USETW(req.wLength, len); + + error = usbd_do_request(sc->sc_udev, &req, buf); + if (error != 0) { + device_printf(sc->sc_dev, "could not read EEPROM: %s\n", + usbd_errstr(error)); + } +} + +static uint32_t +rum_read(struct rum_softc *sc, uint16_t reg) +{ + uint32_t val; + + rum_read_multi(sc, reg, &val, sizeof val); + + return le32toh(val); +} + +static void +rum_read_multi(struct rum_softc *sc, uint16_t reg, void *buf, int len) +{ + usb_device_request_t req; + usbd_status error; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = RT2573_READ_MULTI_MAC; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, len); + + error = usbd_do_request(sc->sc_udev, &req, buf); + if (error != 0) { + device_printf(sc->sc_dev, + "could not multi read MAC register: %s\n", + usbd_errstr(error)); + } +} + +static void +rum_write(struct rum_softc *sc, uint16_t reg, uint32_t val) +{ + uint32_t tmp = htole32(val); + + rum_write_multi(sc, reg, &tmp, sizeof tmp); +} + +static void +rum_write_multi(struct rum_softc *sc, uint16_t reg, void *buf, size_t len) +{ + usb_device_request_t req; + usbd_status error; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = RT2573_WRITE_MULTI_MAC; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, len); + + error = usbd_do_request(sc->sc_udev, &req, buf); + if (error != 0) { + device_printf(sc->sc_dev, + "could not multi write MAC register: %s\n", + usbd_errstr(error)); + } +} + +static void +rum_bbp_write(struct rum_softc *sc, uint8_t reg, uint8_t val) +{ + uint32_t tmp; + int ntries; + + for (ntries = 0; ntries < 5; ntries++) { + if (!(rum_read(sc, RT2573_PHY_CSR3) & RT2573_BBP_BUSY)) + break; + } + if (ntries == 5) { + device_printf(sc->sc_dev, "could not write to BBP\n"); + return; + } + + tmp = RT2573_BBP_BUSY | (reg & 0x7f) << 8 | val; + rum_write(sc, RT2573_PHY_CSR3, tmp); +} + +static uint8_t +rum_bbp_read(struct rum_softc *sc, uint8_t reg) +{ + uint32_t val; + int ntries; + + for (ntries = 0; ntries < 5; ntries++) { + if (!(rum_read(sc, RT2573_PHY_CSR3) & RT2573_BBP_BUSY)) + break; + } + if (ntries == 5) { + device_printf(sc->sc_dev, "could not read BBP\n"); + return 0; + } + + val = RT2573_BBP_BUSY | RT2573_BBP_READ | reg << 8; + rum_write(sc, RT2573_PHY_CSR3, val); + + for (ntries = 0; ntries < 100; ntries++) { + val = rum_read(sc, RT2573_PHY_CSR3); + if (!(val & RT2573_BBP_BUSY)) + return val & 0xff; + DELAY(1); + } + + device_printf(sc->sc_dev, "could not read BBP\n"); + return 0; +} + +static void +rum_rf_write(struct rum_softc *sc, uint8_t reg, uint32_t val) +{ + uint32_t tmp; + int ntries; + + for (ntries = 0; ntries < 5; ntries++) { + if (!(rum_read(sc, RT2573_PHY_CSR4) & RT2573_RF_BUSY)) + break; + } + if (ntries == 5) { + device_printf(sc->sc_dev, "could not write to RF\n"); + return; + } + + tmp = RT2573_RF_BUSY | RT2573_RF_20BIT | (val & 0xfffff) << 2 | + (reg & 3); + rum_write(sc, RT2573_PHY_CSR4, tmp); + + /* remember last written value in sc */ + sc->rf_regs[reg] = val; + + DPRINTFN(15, ("RF R[%u] <- 0x%05x\n", reg & 3, val & 0xfffff)); +} + +static void +rum_select_antenna(struct rum_softc *sc) +{ + uint8_t bbp4, bbp77; + uint32_t tmp; + + bbp4 = rum_bbp_read(sc, 4); + bbp77 = rum_bbp_read(sc, 77); + + /* TBD */ + + /* make sure Rx is disabled before switching antenna */ + tmp = rum_read(sc, RT2573_TXRX_CSR0); + rum_write(sc, RT2573_TXRX_CSR0, tmp | RT2573_DISABLE_RX); + + rum_bbp_write(sc, 4, bbp4); + rum_bbp_write(sc, 77, bbp77); + + rum_write(sc, RT2573_TXRX_CSR0, tmp); +} + +/* + * Enable multi-rate retries for frames sent at OFDM rates. + * In 802.11b/g mode, allow fallback to CCK rates. + */ +static void +rum_enable_mrr(struct rum_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + uint32_t tmp; + + tmp = rum_read(sc, RT2573_TXRX_CSR4); + + tmp &= ~RT2573_MRR_CCK_FALLBACK; + if (!IEEE80211_IS_CHAN_5GHZ(ic->ic_bsschan)) + tmp |= RT2573_MRR_CCK_FALLBACK; + tmp |= RT2573_MRR_ENABLED; + + rum_write(sc, RT2573_TXRX_CSR4, tmp); +} + +static void +rum_set_txpreamble(struct rum_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + uint32_t tmp; + + tmp = rum_read(sc, RT2573_TXRX_CSR4); + + tmp &= ~RT2573_SHORT_PREAMBLE; + if (ic->ic_flags & IEEE80211_F_SHPREAMBLE) + tmp |= RT2573_SHORT_PREAMBLE; + + rum_write(sc, RT2573_TXRX_CSR4, tmp); +} + +static void +rum_set_basicrates(struct rum_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + /* update basic rate set */ + if (ic->ic_curmode == IEEE80211_MODE_11B) { + /* 11b basic rates: 1, 2Mbps */ + rum_write(sc, RT2573_TXRX_CSR5, 0x3); + } else if (IEEE80211_IS_CHAN_5GHZ(ic->ic_bsschan)) { + /* 11a basic rates: 6, 12, 24Mbps */ + rum_write(sc, RT2573_TXRX_CSR5, 0x150); + } else { + /* 11b/g basic rates: 1, 2, 5.5, 11Mbps */ + rum_write(sc, RT2573_TXRX_CSR5, 0xf); + } +} + +/* + * Reprogram MAC/BBP to switch to a new band. Values taken from the reference + * driver. + */ +static void +rum_select_band(struct rum_softc *sc, struct ieee80211_channel *c) +{ + uint8_t bbp17, bbp35, bbp96, bbp97, bbp98, bbp104; + uint32_t tmp; + + /* update all BBP registers that depend on the band */ + bbp17 = 0x20; bbp96 = 0x48; bbp104 = 0x2c; + bbp35 = 0x50; bbp97 = 0x48; bbp98 = 0x48; + if (IEEE80211_IS_CHAN_5GHZ(c)) { + bbp17 += 0x08; bbp96 += 0x10; bbp104 += 0x0c; + bbp35 += 0x10; bbp97 += 0x10; bbp98 += 0x10; + } + if ((IEEE80211_IS_CHAN_2GHZ(c) && sc->ext_2ghz_lna) || + (IEEE80211_IS_CHAN_5GHZ(c) && sc->ext_5ghz_lna)) { + bbp17 += 0x10; bbp96 += 0x10; bbp104 += 0x10; + } + + sc->bbp17 = bbp17; + rum_bbp_write(sc, 17, bbp17); + rum_bbp_write(sc, 96, bbp96); + rum_bbp_write(sc, 104, bbp104); + + if ((IEEE80211_IS_CHAN_2GHZ(c) && sc->ext_2ghz_lna) || + (IEEE80211_IS_CHAN_5GHZ(c) && sc->ext_5ghz_lna)) { + rum_bbp_write(sc, 75, 0x80); + rum_bbp_write(sc, 86, 0x80); + rum_bbp_write(sc, 88, 0x80); + } + + rum_bbp_write(sc, 35, bbp35); + rum_bbp_write(sc, 97, bbp97); + rum_bbp_write(sc, 98, bbp98); + + tmp = rum_read(sc, RT2573_PHY_CSR0); + tmp &= ~(RT2573_PA_PE_2GHZ | RT2573_PA_PE_5GHZ); + if (IEEE80211_IS_CHAN_2GHZ(c)) + tmp |= RT2573_PA_PE_2GHZ; + else + tmp |= RT2573_PA_PE_5GHZ; + rum_write(sc, RT2573_PHY_CSR0, tmp); +} + +static void +rum_set_chan(struct rum_softc *sc, struct ieee80211_channel *c) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + const struct rfprog *rfprog; + uint8_t bbp3, bbp94 = RT2573_BBPR94_DEFAULT; + int8_t power; + u_int i, chan; + + chan = ieee80211_chan2ieee(ic, c); + if (chan == 0 || chan == IEEE80211_CHAN_ANY) + return; + + /* select the appropriate RF settings based on what EEPROM says */ + rfprog = (sc->rf_rev == RT2573_RF_5225 || + sc->rf_rev == RT2573_RF_2527) ? rum_rf5225 : rum_rf5226; + + /* find the settings for this channel (we know it exists) */ + for (i = 0; rfprog[i].chan != chan; i++); + + power = sc->txpow[i]; + if (power < 0) { + bbp94 += power; + power = 0; + } else if (power > 31) { + bbp94 += power - 31; + power = 31; + } + + /* + * If we are switching from the 2GHz band to the 5GHz band or + * vice-versa, BBP registers need to be reprogrammed. + */ + if (c->ic_flags != ic->ic_curchan->ic_flags) { + rum_select_band(sc, c); + rum_select_antenna(sc); + } + ic->ic_curchan = c; + + rum_rf_write(sc, RT2573_RF1, rfprog[i].r1); + rum_rf_write(sc, RT2573_RF2, rfprog[i].r2); + rum_rf_write(sc, RT2573_RF3, rfprog[i].r3 | power << 7); + rum_rf_write(sc, RT2573_RF4, rfprog[i].r4 | sc->rffreq << 10); + + rum_rf_write(sc, RT2573_RF1, rfprog[i].r1); + rum_rf_write(sc, RT2573_RF2, rfprog[i].r2); + rum_rf_write(sc, RT2573_RF3, rfprog[i].r3 | power << 7 | 1); + rum_rf_write(sc, RT2573_RF4, rfprog[i].r4 | sc->rffreq << 10); + + rum_rf_write(sc, RT2573_RF1, rfprog[i].r1); + rum_rf_write(sc, RT2573_RF2, rfprog[i].r2); + rum_rf_write(sc, RT2573_RF3, rfprog[i].r3 | power << 7); + rum_rf_write(sc, RT2573_RF4, rfprog[i].r4 | sc->rffreq << 10); + + DELAY(10); + + /* enable smart mode for MIMO-capable RFs */ + bbp3 = rum_bbp_read(sc, 3); + + bbp3 &= ~RT2573_SMART_MODE; + if (sc->rf_rev == RT2573_RF_5225 || sc->rf_rev == RT2573_RF_2527) + bbp3 |= RT2573_SMART_MODE; + + rum_bbp_write(sc, 3, bbp3); + + if (bbp94 != RT2573_BBPR94_DEFAULT) + rum_bbp_write(sc, 94, bbp94); +} + +/* + * Enable TSF synchronization and tell h/w to start sending beacons for IBSS + * and HostAP operating modes. + */ +static void +rum_enable_tsf_sync(struct rum_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); + uint32_t tmp; + + if (vap->iv_opmode != IEEE80211_M_STA) { + /* + * Change default 16ms TBTT adjustment to 8ms. + * Must be done before enabling beacon generation. + */ + rum_write(sc, RT2573_TXRX_CSR10, 1 << 12 | 8); + } + + tmp = rum_read(sc, RT2573_TXRX_CSR9) & 0xff000000; + + /* set beacon interval (in 1/16ms unit) */ + tmp |= vap->iv_bss->ni_intval * 16; + + tmp |= RT2573_TSF_TICKING | RT2573_ENABLE_TBTT; + if (vap->iv_opmode == IEEE80211_M_STA) + tmp |= RT2573_TSF_MODE(1); + else + tmp |= RT2573_TSF_MODE(2) | RT2573_GENERATE_BEACON; + + rum_write(sc, RT2573_TXRX_CSR9, tmp); +} + +static void +rum_update_slot(struct ifnet *ifp) +{ + struct rum_softc *sc = ifp->if_softc; + struct ieee80211com *ic = ifp->if_l2com; + uint8_t slottime; + uint32_t tmp; + + slottime = (ic->ic_flags & IEEE80211_F_SHSLOT) ? 9 : 20; + + tmp = rum_read(sc, RT2573_MAC_CSR9); + tmp = (tmp & ~0xff) | slottime; + rum_write(sc, RT2573_MAC_CSR9, tmp); + + DPRINTF(("setting slot time to %uus\n", slottime)); +} + +static void +rum_set_bssid(struct rum_softc *sc, const uint8_t *bssid) +{ + uint32_t tmp; + + tmp = bssid[0] | bssid[1] << 8 | bssid[2] << 16 | bssid[3] << 24; + rum_write(sc, RT2573_MAC_CSR4, tmp); + + tmp = bssid[4] | bssid[5] << 8 | RT2573_ONE_BSSID << 16; + rum_write(sc, RT2573_MAC_CSR5, tmp); +} + +static void +rum_set_macaddr(struct rum_softc *sc, const uint8_t *addr) +{ + uint32_t tmp; + + tmp = addr[0] | addr[1] << 8 | addr[2] << 16 | addr[3] << 24; + rum_write(sc, RT2573_MAC_CSR2, tmp); + + tmp = addr[4] | addr[5] << 8 | 0xff << 16; + rum_write(sc, RT2573_MAC_CSR3, tmp); +} + +static void +rum_update_promisc(struct rum_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + uint32_t tmp; + + tmp = rum_read(sc, RT2573_TXRX_CSR0); + + tmp &= ~RT2573_DROP_NOT_TO_ME; + if (!(ifp->if_flags & IFF_PROMISC)) + tmp |= RT2573_DROP_NOT_TO_ME; + + rum_write(sc, RT2573_TXRX_CSR0, tmp); + + DPRINTF(("%s promiscuous mode\n", (ifp->if_flags & IFF_PROMISC) ? + "entering" : "leaving")); +} + +static const char * +rum_get_rf(int rev) +{ + switch (rev) { + case RT2573_RF_2527: return "RT2527 (MIMO XR)"; + case RT2573_RF_2528: return "RT2528"; + case RT2573_RF_5225: return "RT5225 (MIMO XR)"; + case RT2573_RF_5226: return "RT5226"; + default: return "unknown"; + } +} + +static void +rum_read_eeprom(struct rum_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + uint16_t val; +#ifdef RUM_DEBUG + int i; +#endif + + /* read MAC address */ + rum_eeprom_read(sc, RT2573_EEPROM_ADDRESS, ic->ic_myaddr, 6); + + rum_eeprom_read(sc, RT2573_EEPROM_ANTENNA, &val, 2); + val = le16toh(val); + sc->rf_rev = (val >> 11) & 0x1f; + sc->hw_radio = (val >> 10) & 0x1; + sc->rx_ant = (val >> 4) & 0x3; + sc->tx_ant = (val >> 2) & 0x3; + sc->nb_ant = val & 0x3; + + DPRINTF(("RF revision=%d\n", sc->rf_rev)); + + rum_eeprom_read(sc, RT2573_EEPROM_CONFIG2, &val, 2); + val = le16toh(val); + sc->ext_5ghz_lna = (val >> 6) & 0x1; + sc->ext_2ghz_lna = (val >> 4) & 0x1; + + DPRINTF(("External 2GHz LNA=%d\nExternal 5GHz LNA=%d\n", + sc->ext_2ghz_lna, sc->ext_5ghz_lna)); + + rum_eeprom_read(sc, RT2573_EEPROM_RSSI_2GHZ_OFFSET, &val, 2); + val = le16toh(val); + if ((val & 0xff) != 0xff) + sc->rssi_2ghz_corr = (int8_t)(val & 0xff); /* signed */ + + /* Only [-10, 10] is valid */ + if (sc->rssi_2ghz_corr < -10 || sc->rssi_2ghz_corr > 10) + sc->rssi_2ghz_corr = 0; + + rum_eeprom_read(sc, RT2573_EEPROM_RSSI_5GHZ_OFFSET, &val, 2); + val = le16toh(val); + if ((val & 0xff) != 0xff) + sc->rssi_5ghz_corr = (int8_t)(val & 0xff); /* signed */ + + /* Only [-10, 10] is valid */ + if (sc->rssi_5ghz_corr < -10 || sc->rssi_5ghz_corr > 10) + sc->rssi_5ghz_corr = 0; + + if (sc->ext_2ghz_lna) + sc->rssi_2ghz_corr -= 14; + if (sc->ext_5ghz_lna) + sc->rssi_5ghz_corr -= 14; + + DPRINTF(("RSSI 2GHz corr=%d\nRSSI 5GHz corr=%d\n", + sc->rssi_2ghz_corr, sc->rssi_5ghz_corr)); + + rum_eeprom_read(sc, RT2573_EEPROM_FREQ_OFFSET, &val, 2); + val = le16toh(val); + if ((val & 0xff) != 0xff) + sc->rffreq = val & 0xff; + + DPRINTF(("RF freq=%d\n", sc->rffreq)); + + /* read Tx power for all a/b/g channels */ + rum_eeprom_read(sc, RT2573_EEPROM_TXPOWER, sc->txpow, 14); + /* XXX default Tx power for 802.11a channels */ + memset(sc->txpow + 14, 24, sizeof (sc->txpow) - 14); +#ifdef RUM_DEBUG + for (i = 0; i < 14; i++) + DPRINTF(("Channel=%d Tx power=%d\n", i + 1, sc->txpow[i])); +#endif + + /* read default values for BBP registers */ + rum_eeprom_read(sc, RT2573_EEPROM_BBP_BASE, sc->bbp_prom, 2 * 16); +#ifdef RUM_DEBUG + for (i = 0; i < 14; i++) { + if (sc->bbp_prom[i].reg == 0 || sc->bbp_prom[i].reg == 0xff) + continue; + DPRINTF(("BBP R%d=%02x\n", sc->bbp_prom[i].reg, + sc->bbp_prom[i].val)); + } +#endif +} + +static int +rum_bbp_init(struct rum_softc *sc) +{ +#define N(a) (sizeof (a) / sizeof ((a)[0])) + int i, ntries; + + /* wait for BBP to be ready */ + for (ntries = 0; ntries < 100; ntries++) { + const uint8_t val = rum_bbp_read(sc, 0); + if (val != 0 && val != 0xff) + break; + DELAY(1000); + } + if (ntries == 100) { + device_printf(sc->sc_dev, "timeout waiting for BBP\n"); + return EIO; + } + + /* initialize BBP registers to default values */ + for (i = 0; i < N(rum_def_bbp); i++) + rum_bbp_write(sc, rum_def_bbp[i].reg, rum_def_bbp[i].val); + + /* write vendor-specific BBP values (from EEPROM) */ + for (i = 0; i < 16; i++) { + if (sc->bbp_prom[i].reg == 0 || sc->bbp_prom[i].reg == 0xff) + continue; + rum_bbp_write(sc, sc->bbp_prom[i].reg, sc->bbp_prom[i].val); + } + + return 0; +#undef N +} + +static void +rum_init_locked(struct rum_softc *sc) +{ +#define N(a) (sizeof (a) / sizeof ((a)[0])) + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct rum_rx_data *data; + uint32_t tmp; + usbd_status error; + int i, ntries; + + rum_stop(sc); + + /* initialize MAC registers to default values */ + for (i = 0; i < N(rum_def_mac); i++) + rum_write(sc, rum_def_mac[i].reg, rum_def_mac[i].val); + + /* set host ready */ + rum_write(sc, RT2573_MAC_CSR1, 3); + rum_write(sc, RT2573_MAC_CSR1, 0); + + /* wait for BBP/RF to wakeup */ + for (ntries = 0; ntries < 1000; ntries++) { + if (rum_read(sc, RT2573_MAC_CSR12) & 8) + break; + rum_write(sc, RT2573_MAC_CSR12, 4); /* force wakeup */ + DELAY(1000); + } + if (ntries == 1000) { + device_printf(sc->sc_dev, + "timeout waiting for BBP/RF to wakeup\n"); + goto fail; + } + + if ((error = rum_bbp_init(sc)) != 0) + goto fail; + + /* select default channel */ + rum_select_band(sc, ic->ic_curchan); + rum_select_antenna(sc); + rum_set_chan(sc, ic->ic_curchan); + + /* clear STA registers */ + rum_read_multi(sc, RT2573_STA_CSR0, sc->sta, sizeof sc->sta); + + IEEE80211_ADDR_COPY(ic->ic_myaddr, IF_LLADDR(ifp)); + rum_set_macaddr(sc, ic->ic_myaddr); + + /* initialize ASIC */ + rum_write(sc, RT2573_MAC_CSR1, 4); + + /* + * Allocate xfer for AMRR statistics requests. + */ + sc->amrr_xfer = usbd_alloc_xfer(sc->sc_udev); + if (sc->amrr_xfer == NULL) { + device_printf(sc->sc_dev, "could not allocate AMRR xfer\n"); + goto fail; + } + + /* + * Open Tx and Rx USB bulk pipes. + */ + error = usbd_open_pipe(sc->sc_iface, sc->sc_tx_no, USBD_EXCLUSIVE_USE, + &sc->sc_tx_pipeh); + if (error != 0) { + device_printf(sc->sc_dev, "could not open Tx pipe: %s\n", + usbd_errstr(error)); + goto fail; + } + error = usbd_open_pipe(sc->sc_iface, sc->sc_rx_no, USBD_EXCLUSIVE_USE, + &sc->sc_rx_pipeh); + if (error != 0) { + device_printf(sc->sc_dev, "could not open Rx pipe: %s\n", + usbd_errstr(error)); + goto fail; + } + + /* + * Allocate Tx and Rx xfer queues. + */ + error = rum_alloc_tx_list(sc); + if (error != 0) { + device_printf(sc->sc_dev, "could not allocate Tx list\n"); + goto fail; + } + error = rum_alloc_rx_list(sc); + if (error != 0) { + device_printf(sc->sc_dev, "could not allocate Rx list\n"); + goto fail; + } + + /* + * Start up the receive pipe. + */ + for (i = 0; i < RUM_RX_LIST_COUNT; i++) { + data = &sc->rx_data[i]; + + usbd_setup_xfer(data->xfer, sc->sc_rx_pipeh, data, data->buf, + MCLBYTES, USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT, rum_rxeof); + usbd_transfer(data->xfer); + } + + /* update Rx filter */ + tmp = rum_read(sc, RT2573_TXRX_CSR0) & 0xffff; + + tmp |= RT2573_DROP_PHY_ERROR | RT2573_DROP_CRC_ERROR; + if (ic->ic_opmode != IEEE80211_M_MONITOR) { + tmp |= RT2573_DROP_CTL | RT2573_DROP_VER_ERROR | + RT2573_DROP_ACKCTS; + if (ic->ic_opmode != IEEE80211_M_HOSTAP) + tmp |= RT2573_DROP_TODS; + if (!(ifp->if_flags & IFF_PROMISC)) + tmp |= RT2573_DROP_NOT_TO_ME; + } + rum_write(sc, RT2573_TXRX_CSR0, tmp); + + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + ifp->if_drv_flags |= IFF_DRV_RUNNING; + return; + +fail: rum_stop(sc); +#undef N +} + +static void +rum_init(void *priv) +{ + struct rum_softc *sc = priv; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + RUM_LOCK(sc); + rum_init_locked(sc); + RUM_UNLOCK(sc); + + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + ieee80211_start_all(ic); /* start all vap's */ +} + +static void +rum_stop(void *priv) +{ + struct rum_softc *sc = priv; + struct ifnet *ifp = sc->sc_ifp; + uint32_t tmp; + + sc->sc_tx_timer = 0; + ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); + + /* disable Rx */ + tmp = rum_read(sc, RT2573_TXRX_CSR0); + rum_write(sc, RT2573_TXRX_CSR0, tmp | RT2573_DISABLE_RX); + + /* reset ASIC */ + rum_write(sc, RT2573_MAC_CSR1, 3); + rum_write(sc, RT2573_MAC_CSR1, 0); + + if (sc->amrr_xfer != NULL) { + usbd_free_xfer(sc->amrr_xfer); + sc->amrr_xfer = NULL; + } + + if (sc->sc_rx_pipeh != NULL) { + usbd_abort_pipe(sc->sc_rx_pipeh); + usbd_close_pipe(sc->sc_rx_pipeh); + sc->sc_rx_pipeh = NULL; + } + if (sc->sc_tx_pipeh != NULL) { + usbd_abort_pipe(sc->sc_tx_pipeh); + usbd_close_pipe(sc->sc_tx_pipeh); + sc->sc_tx_pipeh = NULL; + } + + rum_free_rx_list(sc); + rum_free_tx_list(sc); +} + +static int +rum_load_microcode(struct rum_softc *sc, const u_char *ucode, size_t size) +{ + usb_device_request_t req; + uint16_t reg = RT2573_MCU_CODE_BASE; + usbd_status error; + + /* copy firmware image into NIC */ + for (; size >= 4; reg += 4, ucode += 4, size -= 4) + rum_write(sc, reg, UGETDW(ucode)); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = RT2573_MCU_CNTL; + USETW(req.wValue, RT2573_MCU_RUN); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + + error = usbd_do_request(sc->sc_udev, &req, NULL); + if (error != 0) { + device_printf(sc->sc_dev, "could not run firmware: %s\n", + usbd_errstr(error)); + } + return error; +} + +static int +rum_prepare_beacon(struct rum_softc *sc, struct ieee80211vap *vap) +{ + struct ieee80211com *ic = vap->iv_ic; + const struct ieee80211_txparam *tp; + struct rum_tx_desc desc; + struct mbuf *m0; + + m0 = ieee80211_beacon_alloc(vap->iv_bss, &RUM_VAP(vap)->bo); + if (m0 == NULL) { + return ENOBUFS; + } + + tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_bsschan)]; + rum_setup_tx_desc(sc, &desc, RT2573_TX_TIMESTAMP, RT2573_TX_HWSEQ, + m0->m_pkthdr.len, tp->mgmtrate); + + /* copy the first 24 bytes of Tx descriptor into NIC memory */ + rum_write_multi(sc, RT2573_HW_BEACON_BASE0, (uint8_t *)&desc, 24); + + /* copy beacon header and payload into NIC memory */ + rum_write_multi(sc, RT2573_HW_BEACON_BASE0 + 24, mtod(m0, uint8_t *), + m0->m_pkthdr.len); + + m_freem(m0); + + return 0; +} + +static int +rum_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, + const struct ieee80211_bpf_params *params) +{ + struct ifnet *ifp = ni->ni_ic->ic_ifp; + struct rum_softc *sc = ifp->if_softc; + + /* prevent management frames from being sent if we're not ready */ + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + m_freem(m); + ieee80211_free_node(ni); + return ENETDOWN; + } + if (sc->tx_queued >= RUM_TX_LIST_COUNT-1) { + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + m_freem(m); + ieee80211_free_node(ni); + return EIO; + } + + ifp->if_opackets++; + + if (params == NULL) { + /* + * Legacy path; interpret frame contents to decide + * precisely how to send the frame. + */ + if (rum_tx_mgt(sc, m, ni) != 0) + goto bad; + } else { + /* + * Caller supplied explicit parameters to use in + * sending the frame. + */ + if (rum_tx_raw(sc, m, ni, params) != 0) + goto bad; + } + sc->sc_tx_timer = 5; + callout_reset(&sc->watchdog_ch, hz, rum_watchdog, sc); + + return 0; +bad: + ifp->if_oerrors++; + ieee80211_free_node(ni); + return EIO; +} + +static void +rum_amrr_start(struct rum_softc *sc, struct ieee80211_node *ni) +{ + struct ieee80211vap *vap = ni->ni_vap; + struct rum_vap *rvp = RUM_VAP(vap); + + /* clear statistic registers (STA_CSR0 to STA_CSR5) */ + rum_read_multi(sc, RT2573_STA_CSR0, sc->sta, sizeof sc->sta); + + ieee80211_amrr_node_init(&rvp->amrr, &RUM_NODE(ni)->amn, ni); + + callout_reset(&rvp->amrr_ch, hz, rum_amrr_timeout, vap); +} + +static void +rum_amrr_timeout(void *arg) +{ + struct ieee80211vap *vap = arg; + struct rum_softc *sc = vap->iv_ic->ic_ifp->if_softc; + usb_device_request_t req; + + /* + * Asynchronously read statistic registers (cleared by read). + */ + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = RT2573_READ_MULTI_MAC; + USETW(req.wValue, 0); + USETW(req.wIndex, RT2573_STA_CSR0); + USETW(req.wLength, sizeof sc->sta); + + usbd_setup_default_xfer(sc->amrr_xfer, sc->sc_udev, vap, + USBD_DEFAULT_TIMEOUT, &req, sc->sta, sizeof sc->sta, 0, + rum_amrr_update); + (void)usbd_transfer(sc->amrr_xfer); +} + +static void +rum_amrr_update(usbd_xfer_handle xfer, usbd_private_handle priv, + usbd_status status) +{ + struct ieee80211vap *vap = priv; + struct rum_vap *rvp = RUM_VAP(vap); + struct ifnet *ifp = vap->iv_ic->ic_ifp; + struct rum_softc *sc = ifp->if_softc; + int ok, fail; + + if (status != USBD_NORMAL_COMPLETION) { + device_printf(sc->sc_dev, "could not retrieve Tx statistics - " + "cancelling automatic rate control\n"); + return; + } + + ok = (le32toh(sc->sta[4]) >> 16) + /* TX ok w/o retry */ + (le32toh(sc->sta[5]) & 0xffff); /* TX ok w/ retry */ + fail = (le32toh(sc->sta[5]) >> 16); /* TX retry-fail count */ + + ieee80211_amrr_tx_update(&RUM_NODE(vap->iv_bss)->amn, + ok+fail, ok, (le32toh(sc->sta[5]) & 0xffff) + fail); + + ifp->if_oerrors += fail; /* count TX retry-fail as Tx errors */ + + callout_reset(&rvp->amrr_ch, hz, rum_amrr_timeout, vap); +} + +/* ARGUSED */ +static struct ieee80211_node * +rum_node_alloc(struct ieee80211vap *vap __unused, + const uint8_t mac[IEEE80211_ADDR_LEN] __unused) +{ + struct rum_node *rn; + + rn = malloc(sizeof(struct rum_node), M_80211_NODE, M_NOWAIT | M_ZERO); + return rn != NULL ? &rn->ni : NULL; +} + +static void +rum_newassoc(struct ieee80211_node *ni, int isnew) +{ + struct ieee80211vap *vap = ni->ni_vap; + + ieee80211_amrr_node_init(&RUM_VAP(vap)->amrr, &RUM_NODE(ni)->amn, ni); +} + +static void +rum_scan_start(struct ieee80211com *ic) +{ + struct rum_softc *sc = ic->ic_ifp->if_softc; + + usb_rem_task(sc->sc_udev, &sc->sc_scantask); + + /* do it in a process context */ + sc->sc_scan_action = RUM_SCAN_START; + usb_add_task(sc->sc_udev, &sc->sc_scantask, USB_TASKQ_DRIVER); +} + +static void +rum_scan_end(struct ieee80211com *ic) +{ + struct rum_softc *sc = ic->ic_ifp->if_softc; + + usb_rem_task(sc->sc_udev, &sc->sc_scantask); + + /* do it in a process context */ + sc->sc_scan_action = RUM_SCAN_END; + usb_add_task(sc->sc_udev, &sc->sc_scantask, USB_TASKQ_DRIVER); +} + +static void +rum_set_channel(struct ieee80211com *ic) +{ + struct rum_softc *sc = ic->ic_ifp->if_softc; + + usb_rem_task(sc->sc_udev, &sc->sc_scantask); + + /* do it in a process context */ + sc->sc_scan_action = RUM_SET_CHANNEL; + usb_add_task(sc->sc_udev, &sc->sc_scantask, USB_TASKQ_DRIVER); + + sc->sc_rates = ieee80211_get_ratetable(ic->ic_curchan); +} + +static void +rum_scantask(void *arg) +{ + struct rum_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); + uint32_t tmp; + + RUM_LOCK(sc); + + switch (sc->sc_scan_action) { + case RUM_SCAN_START: + /* abort TSF synchronization */ + tmp = rum_read(sc, RT2573_TXRX_CSR9); + rum_write(sc, RT2573_TXRX_CSR9, tmp & ~0x00ffffff); + rum_set_bssid(sc, ifp->if_broadcastaddr); + break; + + case RUM_SCAN_END: + rum_enable_tsf_sync(sc); + /* XXX keep local copy */ + rum_set_bssid(sc, vap->iv_bss->ni_bssid); + break; + + case RUM_SET_CHANNEL: + mtx_lock(&Giant); + rum_set_chan(sc, ic->ic_curchan); + mtx_unlock(&Giant); + break; + + default: + panic("unknown scan action %d\n", sc->sc_scan_action); + /* NEVER REACHED */ + break; + } + + RUM_UNLOCK(sc); +} + +static int +rum_get_rssi(struct rum_softc *sc, uint8_t raw) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + int lna, agc, rssi; + + lna = (raw >> 5) & 0x3; + agc = raw & 0x1f; + + if (lna == 0) { + /* + * No RSSI mapping + * + * NB: Since RSSI is relative to noise floor, -1 is + * adequate for caller to know error happened. + */ + return -1; + } + + rssi = (2 * agc) - RT2573_NOISE_FLOOR; + + if (IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan)) { + rssi += sc->rssi_2ghz_corr; + + if (lna == 1) + rssi -= 64; + else if (lna == 2) + rssi -= 74; + else if (lna == 3) + rssi -= 90; + } else { + rssi += sc->rssi_5ghz_corr; + + if (!sc->ext_5ghz_lna && lna != 1) + rssi += 4; + + if (lna == 1) + rssi -= 64; + else if (lna == 2) + rssi -= 86; + else if (lna == 3) + rssi -= 100; + } + return rssi; +} + +static device_method_t rum_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, rum_match), + DEVMETHOD(device_attach, rum_attach), + DEVMETHOD(device_detach, rum_detach), + + { 0, 0 } +}; + +static driver_t rum_driver = { + "rum", + rum_methods, + sizeof(struct rum_softc) +}; + +static devclass_t rum_devclass; + +DRIVER_MODULE(rum, uhub, rum_driver, rum_devclass, usbd_driver_load, 0); diff --git a/sys/legacy/dev/usb/if_rumreg.h b/sys/legacy/dev/usb/if_rumreg.h new file mode 100644 index 0000000..75a51bc --- /dev/null +++ b/sys/legacy/dev/usb/if_rumreg.h @@ -0,0 +1,235 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2005, 2006 Damien Bergamini <damien.bergamini@free.fr> + * Copyright (c) 2006 Niall O'Higgins <niallo@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define RT2573_NOISE_FLOOR -95 + +#define RT2573_TX_DESC_SIZE (sizeof (struct rum_tx_desc)) +#define RT2573_RX_DESC_SIZE (sizeof (struct rum_rx_desc)) + +#define RT2573_CONFIG_NO 1 +#define RT2573_IFACE_INDEX 0 + +#define RT2573_MCU_CNTL 0x01 +#define RT2573_WRITE_MAC 0x02 +#define RT2573_READ_MAC 0x03 +#define RT2573_WRITE_MULTI_MAC 0x06 +#define RT2573_READ_MULTI_MAC 0x07 +#define RT2573_READ_EEPROM 0x09 +#define RT2573_WRITE_LED 0x0a + +/* + * Control and status registers. + */ +#define RT2573_AIFSN_CSR 0x0400 +#define RT2573_CWMIN_CSR 0x0404 +#define RT2573_CWMAX_CSR 0x0408 +#define RT2573_MCU_CODE_BASE 0x0800 +#define RT2573_HW_BEACON_BASE0 0x2400 +#define RT2573_MAC_CSR0 0x3000 +#define RT2573_MAC_CSR1 0x3004 +#define RT2573_MAC_CSR2 0x3008 +#define RT2573_MAC_CSR3 0x300c +#define RT2573_MAC_CSR4 0x3010 +#define RT2573_MAC_CSR5 0x3014 +#define RT2573_MAC_CSR6 0x3018 +#define RT2573_MAC_CSR7 0x301c +#define RT2573_MAC_CSR8 0x3020 +#define RT2573_MAC_CSR9 0x3024 +#define RT2573_MAC_CSR10 0x3028 +#define RT2573_MAC_CSR11 0x302c +#define RT2573_MAC_CSR12 0x3030 +#define RT2573_MAC_CSR13 0x3034 +#define RT2573_MAC_CSR14 0x3038 +#define RT2573_MAC_CSR15 0x303c +#define RT2573_TXRX_CSR0 0x3040 +#define RT2573_TXRX_CSR1 0x3044 +#define RT2573_TXRX_CSR2 0x3048 +#define RT2573_TXRX_CSR3 0x304c +#define RT2573_TXRX_CSR4 0x3050 +#define RT2573_TXRX_CSR5 0x3054 +#define RT2573_TXRX_CSR6 0x3058 +#define RT2573_TXRX_CSR7 0x305c +#define RT2573_TXRX_CSR8 0x3060 +#define RT2573_TXRX_CSR9 0x3064 +#define RT2573_TXRX_CSR10 0x3068 +#define RT2573_TXRX_CSR11 0x306c +#define RT2573_TXRX_CSR12 0x3070 +#define RT2573_TXRX_CSR13 0x3074 +#define RT2573_TXRX_CSR14 0x3078 +#define RT2573_TXRX_CSR15 0x307c +#define RT2573_PHY_CSR0 0x3080 +#define RT2573_PHY_CSR1 0x3084 +#define RT2573_PHY_CSR2 0x3088 +#define RT2573_PHY_CSR3 0x308c +#define RT2573_PHY_CSR4 0x3090 +#define RT2573_PHY_CSR5 0x3094 +#define RT2573_PHY_CSR6 0x3098 +#define RT2573_PHY_CSR7 0x309c +#define RT2573_SEC_CSR0 0x30a0 +#define RT2573_SEC_CSR1 0x30a4 +#define RT2573_SEC_CSR2 0x30a8 +#define RT2573_SEC_CSR3 0x30ac +#define RT2573_SEC_CSR4 0x30b0 +#define RT2573_SEC_CSR5 0x30b4 +#define RT2573_STA_CSR0 0x30c0 +#define RT2573_STA_CSR1 0x30c4 +#define RT2573_STA_CSR2 0x30c8 +#define RT2573_STA_CSR3 0x30cc +#define RT2573_STA_CSR4 0x30d0 +#define RT2573_STA_CSR5 0x30d4 + + +/* possible flags for register RT2573_MAC_CSR1 */ +#define RT2573_RESET_ASIC (1 << 0) +#define RT2573_RESET_BBP (1 << 1) +#define RT2573_HOST_READY (1 << 2) + +/* possible flags for register MAC_CSR5 */ +#define RT2573_ONE_BSSID 3 + +/* possible flags for register TXRX_CSR0 */ +/* Tx filter flags are in the low 16 bits */ +#define RT2573_AUTO_TX_SEQ (1 << 15) +/* Rx filter flags are in the high 16 bits */ +#define RT2573_DISABLE_RX (1 << 16) +#define RT2573_DROP_CRC_ERROR (1 << 17) +#define RT2573_DROP_PHY_ERROR (1 << 18) +#define RT2573_DROP_CTL (1 << 19) +#define RT2573_DROP_NOT_TO_ME (1 << 20) +#define RT2573_DROP_TODS (1 << 21) +#define RT2573_DROP_VER_ERROR (1 << 22) +#define RT2573_DROP_MULTICAST (1 << 23) +#define RT2573_DROP_BROADCAST (1 << 24) +#define RT2573_DROP_ACKCTS (1 << 25) + +/* possible flags for register TXRX_CSR4 */ +#define RT2573_SHORT_PREAMBLE (1 << 18) +#define RT2573_MRR_ENABLED (1 << 19) +#define RT2573_MRR_CCK_FALLBACK (1 << 22) + +/* possible flags for register TXRX_CSR9 */ +#define RT2573_TSF_TICKING (1 << 16) +#define RT2573_TSF_MODE(x) (((x) & 0x3) << 17) +/* TBTT stands for Target Beacon Transmission Time */ +#define RT2573_ENABLE_TBTT (1 << 19) +#define RT2573_GENERATE_BEACON (1 << 20) + +/* possible flags for register PHY_CSR0 */ +#define RT2573_PA_PE_2GHZ (1 << 16) +#define RT2573_PA_PE_5GHZ (1 << 17) + +/* possible flags for register PHY_CSR3 */ +#define RT2573_BBP_READ (1 << 15) +#define RT2573_BBP_BUSY (1 << 16) +/* possible flags for register PHY_CSR4 */ +#define RT2573_RF_20BIT (20 << 24) +#define RT2573_RF_BUSY (1 << 31) + +/* LED values */ +#define RT2573_LED_RADIO (1 << 8) +#define RT2573_LED_G (1 << 9) +#define RT2573_LED_A (1 << 10) +#define RT2573_LED_ON 0x1e1e +#define RT2573_LED_OFF 0x0 + +#define RT2573_MCU_RUN (1 << 3) + +#define RT2573_SMART_MODE (1 << 0) + +#define RT2573_BBPR94_DEFAULT 6 + +#define RT2573_BBP_WRITE (1 << 15) + +/* dual-band RF */ +#define RT2573_RF_5226 1 +#define RT2573_RF_5225 3 +/* single-band RF */ +#define RT2573_RF_2528 2 +#define RT2573_RF_2527 4 + +#define RT2573_BBP_VERSION 0 + +struct rum_tx_desc { + uint32_t flags; +#define RT2573_TX_BURST (1 << 0) +#define RT2573_TX_VALID (1 << 1) +#define RT2573_TX_MORE_FRAG (1 << 2) +#define RT2573_TX_NEED_ACK (1 << 3) +#define RT2573_TX_TIMESTAMP (1 << 4) +#define RT2573_TX_OFDM (1 << 5) +#define RT2573_TX_IFS_SIFS (1 << 6) +#define RT2573_TX_LONG_RETRY (1 << 7) + + uint16_t wme; +#define RT2573_QID(v) (v) +#define RT2573_AIFSN(v) ((v) << 4) +#define RT2573_LOGCWMIN(v) ((v) << 8) +#define RT2573_LOGCWMAX(v) ((v) << 12) + + uint16_t xflags; +#define RT2573_TX_HWSEQ (1 << 12) + + uint8_t plcp_signal; + uint8_t plcp_service; +#define RT2573_PLCP_LENGEXT 0x80 + + uint8_t plcp_length_lo; + uint8_t plcp_length_hi; + + uint32_t iv; + uint32_t eiv; + + uint8_t offset; + uint8_t qid; + uint8_t txpower; +#define RT2573_DEFAULT_TXPOWER 0 + + uint8_t reserved; +} __packed; + +struct rum_rx_desc { + uint32_t flags; +#define RT2573_RX_BUSY (1 << 0) +#define RT2573_RX_DROP (1 << 1) +#define RT2573_RX_CRC_ERROR (1 << 6) +#define RT2573_RX_OFDM (1 << 7) + + uint8_t rate; + uint8_t rssi; + uint8_t reserved1; + uint8_t offset; + uint32_t iv; + uint32_t eiv; + uint32_t reserved2[2]; +} __packed; + +#define RT2573_RF1 0 +#define RT2573_RF2 2 +#define RT2573_RF3 1 +#define RT2573_RF4 3 + +#define RT2573_EEPROM_MACBBP 0x0000 +#define RT2573_EEPROM_ADDRESS 0x0004 +#define RT2573_EEPROM_ANTENNA 0x0020 +#define RT2573_EEPROM_CONFIG2 0x0022 +#define RT2573_EEPROM_BBP_BASE 0x0026 +#define RT2573_EEPROM_TXPOWER 0x0046 +#define RT2573_EEPROM_FREQ_OFFSET 0x005e +#define RT2573_EEPROM_RSSI_2GHZ_OFFSET 0x009a +#define RT2573_EEPROM_RSSI_5GHZ_OFFSET 0x009c diff --git a/sys/legacy/dev/usb/if_rumvar.h b/sys/legacy/dev/usb/if_rumvar.h new file mode 100644 index 0000000..59980c0 --- /dev/null +++ b/sys/legacy/dev/usb/if_rumvar.h @@ -0,0 +1,161 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2005, 2006 Damien Bergamini <damien.bergamini@free.fr> + * Copyright (c) 2006 Niall O'Higgins <niallo@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define RUM_RX_LIST_COUNT 1 +#define RUM_TX_LIST_COUNT 8 + +struct rum_rx_radiotap_header { + struct ieee80211_radiotap_header wr_ihdr; + uint8_t wr_flags; + uint8_t wr_rate; + uint16_t wr_chan_freq; + uint16_t wr_chan_flags; + uint8_t wr_antenna; + uint8_t wr_antsignal; +}; + +#define RT2573_RX_RADIOTAP_PRESENT \ + ((1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_RATE) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL) | \ + (1 << IEEE80211_RADIOTAP_ANTENNA) | \ + (1 << IEEE80211_RADIOTAP_DB_ANTSIGNAL)) + +struct rum_tx_radiotap_header { + struct ieee80211_radiotap_header wt_ihdr; + uint8_t wt_flags; + uint8_t wt_rate; + uint16_t wt_chan_freq; + uint16_t wt_chan_flags; + uint8_t wt_antenna; +}; + +#define RT2573_TX_RADIOTAP_PRESENT \ + ((1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_RATE) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL) | \ + (1 << IEEE80211_RADIOTAP_ANTENNA)) + +struct rum_softc; + +struct rum_tx_data { + struct rum_softc *sc; + usbd_xfer_handle xfer; + uint8_t *buf; + struct mbuf *m; + struct ieee80211_node *ni; +}; + +struct rum_rx_data { + struct rum_softc *sc; + usbd_xfer_handle xfer; + uint8_t *buf; + struct mbuf *m; +}; + +struct rum_node { + struct ieee80211_node ni; + struct ieee80211_amrr_node amn; +}; +#define RUM_NODE(ni) ((struct rum_node *)(ni)) + +struct rum_vap { + struct ieee80211vap vap; + struct ieee80211_beacon_offsets bo; + struct ieee80211_amrr amrr; + struct callout amrr_ch; + + int (*newstate)(struct ieee80211vap *, + enum ieee80211_state, int); +}; +#define RUM_VAP(vap) ((struct rum_vap *)(vap)) + +struct rum_softc { + struct ifnet *sc_ifp; + const struct ieee80211_rate_table *sc_rates; + + device_t sc_dev; + usbd_device_handle sc_udev; + usbd_interface_handle sc_iface; + + int sc_rx_no; + int sc_tx_no; + + uint8_t rf_rev; + uint8_t rffreq; + + usbd_xfer_handle amrr_xfer; + + usbd_pipe_handle sc_rx_pipeh; + usbd_pipe_handle sc_tx_pipeh; + + enum ieee80211_state sc_state; + int sc_arg; + struct usb_task sc_task; + + struct usb_task sc_scantask; + int sc_scan_action; +#define RUM_SCAN_START 0 +#define RUM_SCAN_END 1 +#define RUM_SET_CHANNEL 2 + + struct rum_rx_data rx_data[RUM_RX_LIST_COUNT]; + struct rum_tx_data tx_data[RUM_TX_LIST_COUNT]; + int tx_queued; + int tx_cur; + + struct mtx sc_mtx; + + struct callout watchdog_ch; + + int sc_tx_timer; + + uint32_t sta[6]; + uint32_t rf_regs[4]; + uint8_t txpow[44]; + + struct { + uint8_t val; + uint8_t reg; + } __packed bbp_prom[16]; + + int hw_radio; + int rx_ant; + int tx_ant; + int nb_ant; + int ext_2ghz_lna; + int ext_5ghz_lna; + int rssi_2ghz_corr; + int rssi_5ghz_corr; + uint8_t bbp17; + + struct rum_rx_radiotap_header sc_rxtap; + int sc_rxtap_len; + + struct rum_tx_radiotap_header sc_txtap; + int sc_txtap_len; +}; + +#if 0 +#define RUM_LOCK(sc) mtx_lock(&(sc)->sc_mtx) +#define RUM_UNLOCK(sc) mtx_unlock(&(sc)->sc_mtx) +#else +#define RUM_LOCK(sc) do { ((sc) = (sc)); mtx_lock(&Giant); } while (0) +#define RUM_UNLOCK(sc) mtx_unlock(&Giant) +#endif diff --git a/sys/legacy/dev/usb/if_udav.c b/sys/legacy/dev/usb/if_udav.c new file mode 100644 index 0000000..e13170e --- /dev/null +++ b/sys/legacy/dev/usb/if_udav.c @@ -0,0 +1,1962 @@ +/* $NetBSD: if_udav.c,v 1.2 2003/09/04 15:17:38 tsutsui Exp $ */ +/* $nabe: if_udav.c,v 1.3 2003/08/21 16:57:19 nabe Exp $ */ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2003 + * Shingo WATANABE <nabe@nabechan.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. + * 3. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +/* + * DM9601(DAVICOM USB to Ethernet MAC Controller with Integrated 10/100 PHY) + * The spec can be found at the following url. + * http://www.davicom.com.tw/big5/download/Data%20Sheet/DM9601-DS-P01-930914.pdf + */ + +/* + * TODO: + * Interrupt Endpoint support + * External PHYs + * powerhook() support? + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include "opt_inet.h" +#if defined(__NetBSD__) +#include "opt_ns.h" +#endif +#if defined(__NetBSD__) +#include "bpfilter.h" +#endif +#if defined(__FreeBSD__) +#define NBPFILTER 1 +#endif +#if defined(__NetBSD__) +#include "rnd.h" +#endif + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/lock.h> +#include <sys/mbuf.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/socket.h> +#if defined(__FreeBSD__) +#include <sys/types.h> +#include <sys/lockmgr.h> +#include <sys/sockio.h> +#endif + +#if defined(__NetBSD__) +#include <sys/device.h> +#endif + +#if defined(NRND) && NRND > 0 +#include <sys/rnd.h> +#endif + +#include <net/if.h> +#include <net/if_arp.h> +#include <net/if_dl.h> +#include <net/if_media.h> +#include <net/ethernet.h> +#include <net/if_types.h> + +#if NBPFILTER > 0 +#include <net/bpf.h> +#endif +#if defined(__NetBSD__) +#ifndef BPF_MTAP +#define BPF_MTAP(_ifp, _m) do { \ + if ((_ifp)->if_bpf)) { \ + bpf_mtap((_ifp)->if_bpf, (_m)) ; \ + } \ +} while (0) +#endif +#endif + +#if defined(__NetBSD__) +#include <net/if_ether.h> +#ifdef INET +#include <netinet/in.h> +#include <netinet/if_inarp.h> +#endif /* INET */ +#elif defined(__FreeBSD__) /* defined(__NetBSD__) */ +#include <netinet/in.h> +#include <netinet/if_ether.h> +#endif /* defined(__FreeBSD__) */ + +#if defined(__NetBSD__) +#ifdef NS +#include <netns/ns.h> +#include <netns/ns_if.h> +#endif +#endif /* defined (__NetBSD__) */ + +#include <sys/bus.h> +#include <machine/bus.h> + +#include <dev/mii/mii.h> +#include <dev/mii/miivar.h> + +#include <dev/usb/usb_port.h> +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include "usbdevs.h" +#include <dev/usb/usbdivar.h> +#include <dev/usb/usb_ethersubr.h> + +#include <dev/usb/if_udavreg.h> + +#if defined(__FreeBSD__) +MODULE_DEPEND(udav, usb, 1, 1, 1); +MODULE_DEPEND(udav, ether, 1, 1, 1); +MODULE_DEPEND(udav, miibus, 1, 1, 1); +#endif + +/* "device miibus" required. See GENERIC if you get errors here. */ +#include "miibus_if.h" + +#if !defined(__FreeBSD__) +/* Function declarations */ +USB_DECLARE_DRIVER(udav); +#endif + +#if defined(__FreeBSD__) +static device_probe_t udav_match; +static device_attach_t udav_attach; +static device_detach_t udav_detach; +static device_shutdown_t udav_shutdown; +static miibus_readreg_t udav_miibus_readreg; +static miibus_writereg_t udav_miibus_writereg; +static miibus_statchg_t udav_miibus_statchg; +#endif + +static int udav_openpipes(struct udav_softc *); +static void udav_start(struct ifnet *); +static int udav_send(struct udav_softc *, struct mbuf *, int); +static void udav_txeof(usbd_xfer_handle, usbd_private_handle, usbd_status); +#if defined(__FreeBSD__) +static void udav_rxstart(struct ifnet *ifp); +#endif +static void udav_rxeof(usbd_xfer_handle, usbd_private_handle, usbd_status); +static void udav_tick(void *); +static void udav_tick_task(void *); +static int udav_ioctl(struct ifnet *, u_long, caddr_t); +static void udav_stop_task(struct udav_softc *); +static void udav_stop(struct ifnet *, int); +static void udav_watchdog(struct ifnet *); +static int udav_ifmedia_change(struct ifnet *); +static void udav_ifmedia_status(struct ifnet *, struct ifmediareq *); +static void udav_lock_mii(struct udav_softc *); +static void udav_unlock_mii(struct udav_softc *); +#if defined(__NetBSD__) +static int udav_miibus_readreg(device_t, int, int); +static void udav_miibus_writereg(device_t, int, int, int); +static void udav_miibus_statchg(device_t); +static int udav_init(struct ifnet *); +#elif defined(__FreeBSD__) +static void udav_init(void *); +#endif +static void udav_setmulti(struct udav_softc *); +static void udav_reset(struct udav_softc *); + +static int udav_csr_read(struct udav_softc *, int, void *, int); +static int udav_csr_write(struct udav_softc *, int, void *, int); +static int udav_csr_read1(struct udav_softc *, int); +static int udav_csr_write1(struct udav_softc *, int, unsigned char); + +#if 0 +static int udav_mem_read(struct udav_softc *, int, void *, int); +static int udav_mem_write(struct udav_softc *, int, void *, int); +static int udav_mem_write1(struct udav_softc *, int, unsigned char); +#endif + +#if defined(__FreeBSD__) +static device_method_t udav_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, udav_match), + DEVMETHOD(device_attach, udav_attach), + DEVMETHOD(device_detach, udav_detach), + DEVMETHOD(device_shutdown, udav_shutdown), + + /* bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + DEVMETHOD(bus_driver_added, bus_generic_driver_added), + + /* MII interface */ + DEVMETHOD(miibus_readreg, udav_miibus_readreg), + DEVMETHOD(miibus_writereg, udav_miibus_writereg), + DEVMETHOD(miibus_statchg, udav_miibus_statchg), + + { 0, 0 } +}; + +static driver_t udav_driver = { + "udav", + udav_methods, + sizeof(struct udav_softc) +}; + +static devclass_t udav_devclass; + +DRIVER_MODULE(udav, uhub, udav_driver, udav_devclass, usbd_driver_load, 0); +DRIVER_MODULE(miibus, udav, miibus_driver, miibus_devclass, 0, 0); + +#endif /* defined(__FreeBSD__) */ + +/* Macros */ +#ifdef UDAV_DEBUG +#define DPRINTF(x) if (udavdebug) printf x +#define DPRINTFN(n,x) if (udavdebug >= (n)) printf x +int udavdebug = 0; +#else +#define DPRINTF(x) +#define DPRINTFN(n,x) +#endif + +#define delay(d) DELAY(d) + +#define UDAV_SETBIT(sc, reg, x) \ + udav_csr_write1(sc, reg, udav_csr_read1(sc, reg) | (x)) + +#define UDAV_CLRBIT(sc, reg, x) \ + udav_csr_write1(sc, reg, udav_csr_read1(sc, reg) & ~(x)) + +static const struct udav_type { + struct usb_devno udav_dev; + u_int16_t udav_flags; +#define UDAV_EXT_PHY 0x0001 +} udav_devs [] = { + /* Corega USB-TXC */ + {{ USB_VENDOR_COREGA, USB_PRODUCT_COREGA_FETHER_USB_TXC }, 0}, + /* ShanTou ST268 USB NIC */ + {{ USB_VENDOR_SHANTOU, USB_PRODUCT_SHANTOU_ST268 }, 0}, + /* ShanTou DM9601 USB NIC */ + {{ USB_VENDOR_SHANTOU, USB_PRODUCT_SHANTOU_DM9601}, 0}, +}; +#define udav_lookup(v, p) ((const struct udav_type *)usb_lookup(udav_devs, v, p)) + + +/* Probe */ +static int +udav_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + + if (uaa->iface != NULL) + return (UMATCH_NONE); + + return (udav_lookup(uaa->vendor, uaa->product) != NULL ? + UMATCH_VENDOR_PRODUCT : UMATCH_NONE); +} + +/* Attach */ +static int +udav_attach(device_t self) +{ + USB_ATTACH_START(udav, sc, uaa); + usbd_device_handle dev = uaa->device; + usbd_interface_handle iface; + usbd_status err; + usb_interface_descriptor_t *id; + usb_endpoint_descriptor_t *ed; + const char *devname ; + struct ifnet *ifp; +#if defined(__NetBSD__) + struct mii_data *mii; +#endif + u_char eaddr[ETHER_ADDR_LEN]; + int i; +#if defined(__NetBSD__) + int s; +#endif + + sc->sc_dev = self; + devname = device_get_nameunit(self); + /* Move the device into the configured state. */ + err = usbd_set_config_no(dev, UDAV_CONFIG_NO, 1); + if (err) { + printf("%s: setting config no failed\n", devname); + goto bad; + } + + usb_init_task(&sc->sc_tick_task, udav_tick_task, sc); + lockinit(&sc->sc_mii_lock, PZERO, "udavmii", 0, 0); + usb_init_task(&sc->sc_stop_task, (void (*)(void *)) udav_stop_task, sc); + + /* get control interface */ + err = usbd_device2interface_handle(dev, UDAV_IFACE_INDEX, &iface); + if (err) { + printf("%s: failed to get interface, err=%s\n", devname, + usbd_errstr(err)); + goto bad; + } + + sc->sc_udev = dev; + sc->sc_ctl_iface = iface; + sc->sc_flags = udav_lookup(uaa->vendor, uaa->product)->udav_flags; + + /* get interface descriptor */ + id = usbd_get_interface_descriptor(sc->sc_ctl_iface); + + /* find endpoints */ + sc->sc_bulkin_no = sc->sc_bulkout_no = sc->sc_intrin_no = -1; + for (i = 0; i < id->bNumEndpoints; i++) { + ed = usbd_interface2endpoint_descriptor(sc->sc_ctl_iface, i); + if (ed == NULL) { + printf("%s: couldn't get endpoint %d\n", devname, i); + goto bad; + } + if ((ed->bmAttributes & UE_XFERTYPE) == UE_BULK && + UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN) + sc->sc_bulkin_no = ed->bEndpointAddress; /* RX */ + else if ((ed->bmAttributes & UE_XFERTYPE) == UE_BULK && + UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT) + sc->sc_bulkout_no = ed->bEndpointAddress; /* TX */ + else if ((ed->bmAttributes & UE_XFERTYPE) == UE_INTERRUPT && + UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN) + sc->sc_intrin_no = ed->bEndpointAddress; /* Status */ + } + + if (sc->sc_bulkin_no == -1 || sc->sc_bulkout_no == -1 || + sc->sc_intrin_no == -1) { + printf("%s: missing endpoint\n", devname); + goto bad; + } + +#if defined(__FreeBSD__) + mtx_init(&sc->sc_mtx, device_get_nameunit(self), MTX_NETWORK_LOCK, + MTX_DEF | MTX_RECURSE); +#endif +#if defined(__NetBSD__) + s = splnet(); +#elif defined(__FreeBSD__) + UDAV_LOCK(sc); +#endif + + /* reset the adapter */ + udav_reset(sc); + + /* Get Ethernet Address */ + err = udav_csr_read(sc, UDAV_PAR, (void *)eaddr, ETHER_ADDR_LEN); + if (err) { + printf("%s: read MAC address failed\n", devname); +#if defined(__NetBSD__) + splx(s); +#elif defined(__FreeBSD__) + UDAV_UNLOCK(sc); + mtx_destroy(&sc->sc_mtx); +#endif + goto bad; + } + + /* Print Ethernet Address */ + printf("%s: Ethernet address %s\n", devname, ether_sprintf(eaddr)); + + /* initialize interface infomation */ +#if defined(__FreeBSD__) + ifp = GET_IFP(sc) = if_alloc(IFT_ETHER); + if (ifp == NULL) { + printf("%s: can not if_alloc\n", devname); + UDAV_UNLOCK(sc); + mtx_destroy(&sc->sc_mtx); + goto bad; + } +#else + ifp = GET_IFP(sc); +#endif + ifp->if_softc = sc; + ifp->if_mtu = ETHERMTU; +#if defined(__NetBSD__) + strncpy(ifp->if_xname, devname, IFNAMSIZ); +#elif defined(__FreeBSD__) + if_initname(ifp, "udav", device_get_unit(self)); +#endif + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST | + IFF_NEEDSGIANT; + ifp->if_start = udav_start; + ifp->if_ioctl = udav_ioctl; + ifp->if_watchdog = udav_watchdog; + ifp->if_init = udav_init; +#if defined(__NetBSD__) + ifp->if_stop = udav_stop; +#endif +#if defined(__FreeBSD__) + ifp->if_snd.ifq_maxlen = IFQ_MAXLEN; +#endif +#if defined(__NetBSD__) + IFQ_SET_READY(&ifp->if_snd); +#endif + + +#if defined(__NetBSD__) + /* + * Do ifmedia setup. + */ + mii = &sc->sc_mii; + mii->mii_ifp = ifp; + mii->mii_readreg = udav_miibus_readreg; + mii->mii_writereg = udav_miibus_writereg; + mii->mii_statchg = udav_miibus_statchg; + mii->mii_flags = MIIF_AUTOTSLEEP; + ifmedia_init(&mii->mii_media, 0, + udav_ifmedia_change, udav_ifmedia_status); + mii_attach(self, mii, 0xffffffff, MII_PHY_ANY, MII_OFFSET_ANY, 0); + if (LIST_FIRST(&mii->mii_phys) == NULL) { + ifmedia_add(&mii->mii_media, IFM_ETHER | IFM_NONE, 0, NULL); + ifmedia_set(&mii->mii_media, IFM_ETHER | IFM_NONE); + } else + ifmedia_set(&mii->mii_media, IFM_ETHER | IFM_AUTO); + + /* attach the interface */ + if_attach(ifp); + Ether_ifattach(ifp, eaddr); +#elif defined(__FreeBSD__) + if (mii_phy_probe(self, &sc->sc_miibus, + udav_ifmedia_change, udav_ifmedia_status)) { + printf("%s: MII without any PHY!\n", device_get_nameunit(sc->sc_dev)); + if_free(ifp); + UDAV_UNLOCK(sc); + mtx_destroy(&sc->sc_mtx); + return ENXIO; + } + + sc->sc_qdat.ifp = ifp; + sc->sc_qdat.if_rxstart = udav_rxstart; + + /* + * Call MI attach routine. + */ + + ether_ifattach(ifp, eaddr); +#endif + +#if defined(NRND) && NRND > 0 + rnd_attach_source(&sc->rnd_source, devname, RND_TYPE_NET, 0); +#endif + + usb_callout_init(sc->sc_stat_ch); +#if defined(__FreeBSD__) + usb_register_netisr(); +#endif + sc->sc_attached = 1; +#if defined(__NetBSD__) + splx(s); +#elif defined(__FreeBSD__) + UDAV_UNLOCK(sc); +#endif + + usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, dev, sc->sc_dev); + + return 0; + + bad: + sc->sc_dying = 1; + return ENXIO; +} + +/* detach */ +static int +udav_detach(device_t self) +{ + USB_DETACH_START(udav, sc); + struct ifnet *ifp = GET_IFP(sc); +#if defined(__NetBSD__) + int s; +#endif + + DPRINTF(("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); + + /* Detached before attached finished */ + if (!sc->sc_attached) + return (0); + + UDAV_LOCK(sc); + + usb_uncallout(sc->sc_stat_ch, udav_tick, sc); + + /* Remove any pending tasks */ + usb_rem_task(sc->sc_udev, &sc->sc_tick_task); + usb_rem_task(sc->sc_udev, &sc->sc_stop_task); + +#if defined(__NetBSD__) + s = splusb(); +#elif defined(__FreeBSD__) + UDAV_LOCK(sc); +#endif + + if (--sc->sc_refcnt >= 0) { + /* Wait for processes to go away */ + usb_detach_wait(sc->sc_dev); + } +#if defined(__FreeBSD__) + if (ifp->if_drv_flags & IFF_DRV_RUNNING) +#else + if (ifp->if_flags & IFF_RUNNING) +#endif + udav_stop(GET_IFP(sc), 1); + +#if defined(NRND) && NRND > 0 + rnd_detach_source(&sc->rnd_source); +#endif +#if defined(__NetBSD__) + mii_detach(&sc->sc_mii, MII_PHY_ANY, MII_OFFSET_ANY); + ifmedia_delete_instance(&sc->sc_mii.mii_media, IFM_INST_ANY); +#endif + ether_ifdetach(ifp); +#if defined(__NetBSD__) + if_detach(ifp); +#endif +#if defined(__FreeBSD__) + if_free(ifp); +#endif + +#ifdef DIAGNOSTIC + if (sc->sc_pipe_tx != NULL) + printf("%s: detach has active tx endpoint.\n", + device_get_nameunit(sc->sc_dev)); + if (sc->sc_pipe_rx != NULL) + printf("%s: detach has active rx endpoint.\n", + device_get_nameunit(sc->sc_dev)); + if (sc->sc_pipe_intr != NULL) + printf("%s: detach has active intr endpoint.\n", + device_get_nameunit(sc->sc_dev)); +#endif + sc->sc_attached = 0; + +#if defined(__NetBSD__) + splx(s); +#elif defined(__FreeBSD__) + UDAV_UNLOCK(sc); +#endif + +#if defined(__FreeBSD__) + mtx_destroy(&sc->sc_mtx); +#endif + + usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_udev, sc->sc_dev); + return (0); +} + +#if 0 +/* read memory */ +static int +udav_mem_read(struct udav_softc *sc, int offset, void *buf, int len) +{ + usb_device_request_t req; + usbd_status err; + + if (sc == NULL) + return (0); + + DPRINTFN(0x200, + ("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); + + if (sc->sc_dying) + return (0); + + offset &= 0xffff; + len &= 0xff; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = UDAV_REQ_MEM_READ; + USETW(req.wValue, 0x0000); + USETW(req.wIndex, offset); + USETW(req.wLength, len); + + sc->sc_refcnt++; + err = usbd_do_request(sc->sc_udev, &req, buf); + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); + if (err) { + DPRINTF(("%s: %s: read failed. off=%04x, err=%d\n", + device_get_nameunit(sc->sc_dev), __func__, offset, err)); + } + + return (err); +} + +/* write memory */ +static int +udav_mem_write(struct udav_softc *sc, int offset, void *buf, int len) +{ + usb_device_request_t req; + usbd_status err; + + if (sc == NULL) + return (0); + + DPRINTFN(0x200, + ("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); + + if (sc->sc_dying) + return (0); + + offset &= 0xffff; + len &= 0xff; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UDAV_REQ_MEM_WRITE; + USETW(req.wValue, 0x0000); + USETW(req.wIndex, offset); + USETW(req.wLength, len); + + sc->sc_refcnt++; + err = usbd_do_request(sc->sc_udev, &req, buf); + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); + if (err) { + DPRINTF(("%s: %s: write failed. off=%04x, err=%d\n", + device_get_nameunit(sc->sc_dev), __func__, offset, err)); + } + + return (err); +} + +/* write memory */ +static int +udav_mem_write1(struct udav_softc *sc, int offset, unsigned char ch) +{ + usb_device_request_t req; + usbd_status err; + + if (sc == NULL) + return (0); + + DPRINTFN(0x200, + ("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); + + if (sc->sc_dying) + return (0); + + offset &= 0xffff; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UDAV_REQ_MEM_WRITE1; + USETW(req.wValue, ch); + USETW(req.wIndex, offset); + USETW(req.wLength, 0x0000); + + sc->sc_refcnt++; + err = usbd_do_request(sc->sc_udev, &req, NULL); + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); + if (err) { + DPRINTF(("%s: %s: write failed. off=%04x, err=%d\n", + device_get_nameunit(sc->sc_dev), __func__, offset, err)); + } + + return (err); +} +#endif + +/* read register(s) */ +static int +udav_csr_read(struct udav_softc *sc, int offset, void *buf, int len) +{ + usb_device_request_t req; + usbd_status err; + + if (sc == NULL) + return (0); + + DPRINTFN(0x200, + ("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); + + if (sc->sc_dying) + return (0); + + offset &= 0xff; + len &= 0xff; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = UDAV_REQ_REG_READ; + USETW(req.wValue, 0x0000); + USETW(req.wIndex, offset); + USETW(req.wLength, len); + + sc->sc_refcnt++; + err = usbd_do_request(sc->sc_udev, &req, buf); + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); + if (err) { + DPRINTF(("%s: %s: read failed. off=%04x, err=%d\n", + device_get_nameunit(sc->sc_dev), __func__, offset, err)); + } + + return (err); +} + +/* write register(s) */ +static int +udav_csr_write(struct udav_softc *sc, int offset, void *buf, int len) +{ + usb_device_request_t req; + usbd_status err; + + if (sc == NULL) + return (0); + + DPRINTFN(0x200, + ("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); + + if (sc->sc_dying) + return (0); + + offset &= 0xff; + len &= 0xff; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UDAV_REQ_REG_WRITE; + USETW(req.wValue, 0x0000); + USETW(req.wIndex, offset); + USETW(req.wLength, len); + + sc->sc_refcnt++; + err = usbd_do_request(sc->sc_udev, &req, buf); + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); + if (err) { + DPRINTF(("%s: %s: write failed. off=%04x, err=%d\n", + device_get_nameunit(sc->sc_dev), __func__, offset, err)); + } + + return (err); +} + +static int +udav_csr_read1(struct udav_softc *sc, int offset) +{ + u_int8_t val = 0; + + if (sc == NULL) + return (0); + + DPRINTFN(0x200, + ("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); + + if (sc->sc_dying) + return (0); + + return (udav_csr_read(sc, offset, &val, 1) ? 0 : val); +} + +/* write a register */ +static int +udav_csr_write1(struct udav_softc *sc, int offset, unsigned char ch) +{ + usb_device_request_t req; + usbd_status err; + + if (sc == NULL) + return (0); + + DPRINTFN(0x200, + ("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); + + if (sc->sc_dying) + return (0); + + offset &= 0xff; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UDAV_REQ_REG_WRITE1; + USETW(req.wValue, ch); + USETW(req.wIndex, offset); + USETW(req.wLength, 0x0000); + + sc->sc_refcnt++; + err = usbd_do_request(sc->sc_udev, &req, NULL); + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); + if (err) { + DPRINTF(("%s: %s: write failed. off=%04x, err=%d\n", + device_get_nameunit(sc->sc_dev), __func__, offset, err)); + } + + return (err); +} + +#if defined(__NetBSD__) +static int +udav_init(struct ifnet *ifp) +#elif defined(__FreeBSD__) +static void +udav_init(void *xsc) +#endif +{ +#if defined(__NetBSD__) + struct udav_softc *sc = ifp->if_softc; +#elif defined(__FreeBSD__) + struct udav_softc *sc = (struct udav_softc *)xsc; + struct ifnet *ifp = GET_IFP(sc); +#endif + struct mii_data *mii = GET_MII(sc); + u_char *eaddr; +#if defined(__NetBSD__) + int s; +#endif + + DPRINTF(("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); + + if (sc->sc_dying) +#if defined(__NetBSD__) + return (EIO); +#elif defined(__FreeBSD__) + return ; +#endif + +#if defined(__NetBSD__) + s = splnet(); +#elif defined(__FreeBSD__) + UDAV_LOCK(sc); +#endif + + /* Cancel pending I/O and free all TX/RX buffers */ + udav_stop(ifp, 1); + +#if defined(__NetBSD__) + eaddr = LLADDR(ifp->if_sadl); +#elif defined(__FreeBSD__) + eaddr = IF_LLADDR(ifp); +#endif + udav_csr_write(sc, UDAV_PAR, eaddr, ETHER_ADDR_LEN); + + /* Initialize network control register */ + /* Disable loopback */ + UDAV_CLRBIT(sc, UDAV_NCR, UDAV_NCR_LBK0 | UDAV_NCR_LBK1); + + /* Initialize RX control register */ + UDAV_SETBIT(sc, UDAV_RCR, UDAV_RCR_DIS_LONG | UDAV_RCR_DIS_CRC); + + /* If we want promiscuous mode, accept all physical frames. */ + if (ifp->if_flags & IFF_PROMISC) + UDAV_SETBIT(sc, UDAV_RCR, UDAV_RCR_ALL|UDAV_RCR_PRMSC); + else + UDAV_CLRBIT(sc, UDAV_RCR, UDAV_RCR_ALL|UDAV_RCR_PRMSC); + + /* Initialize transmit ring */ + if (usb_ether_tx_list_init(sc, &sc->sc_cdata, + sc->sc_udev) == ENOBUFS) { + printf("%s: tx list init failed\n", device_get_nameunit(sc->sc_dev)); +#if defined(__NetBSD__) + splx(s); + return (EIO); +#elif defined(__FreeBSD__) + UDAV_UNLOCK(sc); + return ; +#endif + + } + + /* Initialize receive ring */ + if (usb_ether_rx_list_init(sc, &sc->sc_cdata, + sc->sc_udev) == ENOBUFS) { + printf("%s: rx list init failed\n", device_get_nameunit(sc->sc_dev)); +#if defined(__NetBSD__) + splx(s); + return (EIO); +#elif defined(__FreeBSD__) + UDAV_UNLOCK(sc); + return ; +#endif + } + + /* Load the multicast filter */ + udav_setmulti(sc); + + /* Enable RX */ + UDAV_SETBIT(sc, UDAV_RCR, UDAV_RCR_RXEN); + + /* clear POWER_DOWN state of internal PHY */ + UDAV_SETBIT(sc, UDAV_GPCR, UDAV_GPCR_GEP_CNTL0); + UDAV_CLRBIT(sc, UDAV_GPR, UDAV_GPR_GEPIO0); + + mii_mediachg(mii); + + if (sc->sc_pipe_tx == NULL || sc->sc_pipe_rx == NULL) { + if (udav_openpipes(sc)) { +#if defined(__NetBSD__) + splx(s); + return (EIO); +#elif defined(__FreeBSD__) + UDAV_UNLOCK(sc); + return ; +#endif + } + } + +#if defined(__FreeBSD__) + ifp->if_drv_flags |= IFF_DRV_RUNNING; + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; +#else + ifp->if_flags |= IFF_RUNNING; + ifp->if_flags &= ~IFF_OACTIVE; +#endif + +#if defined(__NetBSD__) + splx(s); +#elif defined(__FreeBSD__) + UDAV_UNLOCK(sc); +#endif + + usb_callout(sc->sc_stat_ch, hz, udav_tick, sc); + +#if defined(__NetBSD__) + return (0); +#elif defined(__FreeBSD__) + return ; +#endif +} + +static void +udav_reset(struct udav_softc *sc) +{ + int i; + + DPRINTF(("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); + + if (sc->sc_dying) + return; + + /* Select PHY */ +#if 1 + /* + * XXX: force select internal phy. + * external phy routines are not tested. + */ + UDAV_CLRBIT(sc, UDAV_NCR, UDAV_NCR_EXT_PHY); +#else + if (sc->sc_flags & UDAV_EXT_PHY) { + UDAV_SETBIT(sc, UDAV_NCR, UDAV_NCR_EXT_PHY); + } else { + UDAV_CLRBIT(sc, UDAV_NCR, UDAV_NCR_EXT_PHY); + } +#endif + + UDAV_SETBIT(sc, UDAV_NCR, UDAV_NCR_RST); + + for (i = 0; i < UDAV_TX_TIMEOUT; i++) { + if (!(udav_csr_read1(sc, UDAV_NCR) & UDAV_NCR_RST)) + break; + delay(10); /* XXX */ + } + delay(10000); /* XXX */ +} + +#if defined(__NetBSD__) || defined(__OpenBSD__) +int +udav_activate(device_t self, enum devact act) +{ + struct udav_softc *sc = (struct udav_softc *)self; + + DPRINTF(("%s: %s: enter, act=%d\n", device_get_nameunit(sc->sc_dev), + __func__, act)); + switch (act) { + case DVACT_ACTIVATE: + return (EOPNOTSUPP); + break; + + case DVACT_DEACTIVATE: + if_deactivate(&sc->sc_ec.ec_if); + sc->sc_dying = 1; + break; + } + return (0); +} +#endif + +#define UDAV_BITS 6 + +#define UDAV_CALCHASH(addr) \ + (ether_crc32_le((addr), ETHER_ADDR_LEN) & ((1 << UDAV_BITS) - 1)) + +static void +udav_setmulti(struct udav_softc *sc) +{ + struct ifnet *ifp; +#if defined(__NetBSD__) + struct ether_multi *enm; + struct ether_multistep step; +#elif defined(__FreeBSD__) + struct ifmultiaddr *ifma; +#endif + u_int8_t hashes[8]; + int h = 0; + + DPRINTF(("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); + + if (sc->sc_dying) + return; + + ifp = GET_IFP(sc); + + if (ifp->if_flags & IFF_PROMISC) { + UDAV_SETBIT(sc, UDAV_RCR, UDAV_RCR_ALL|UDAV_RCR_PRMSC); + return; + } else if (ifp->if_flags & IFF_ALLMULTI) { +#if defined(__NetBSD__) + allmulti: +#endif + ifp->if_flags |= IFF_ALLMULTI; + UDAV_SETBIT(sc, UDAV_RCR, UDAV_RCR_ALL); + UDAV_CLRBIT(sc, UDAV_RCR, UDAV_RCR_PRMSC); + return; + } + + /* first, zot all the existing hash bits */ + memset(hashes, 0x00, sizeof(hashes)); + hashes[7] |= 0x80; /* broadcast address */ + udav_csr_write(sc, UDAV_MAR, hashes, sizeof(hashes)); + + /* now program new ones */ +#if defined(__NetBSD__) + ETHER_FIRST_MULTI(step, &sc->sc_ec, enm); + while (enm != NULL) { + if (memcmp(enm->enm_addrlo, enm->enm_addrhi, + ETHER_ADDR_LEN) != 0) + goto allmulti; + + h = UDAV_CALCHASH(enm->enm_addrlo); + hashes[h>>3] |= 1 << (h & 0x7); + ETHER_NEXT_MULTI(step, enm); + } +#elif defined(__FreeBSD__) + IF_ADDR_LOCK(ifp); + TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) + { + if (ifma->ifma_addr->sa_family != AF_LINK) + continue; + h = UDAV_CALCHASH(LLADDR((struct sockaddr_dl *) + ifma->ifma_addr)); + hashes[h>>3] |= 1 << (h & 0x7); + } + IF_ADDR_UNLOCK(ifp); +#endif + + /* disable all multicast */ + ifp->if_flags &= ~IFF_ALLMULTI; + UDAV_CLRBIT(sc, UDAV_RCR, UDAV_RCR_ALL); + + /* write hash value to the register */ + udav_csr_write(sc, UDAV_MAR, hashes, sizeof(hashes)); +} + +static int +udav_openpipes(struct udav_softc *sc) +{ + struct ue_chain *c; + usbd_status err; + int i; + int error = 0; + + if (sc->sc_dying) + return (EIO); + + sc->sc_refcnt++; + + /* Open RX pipe */ + err = usbd_open_pipe(sc->sc_ctl_iface, sc->sc_bulkin_no, + USBD_EXCLUSIVE_USE, &sc->sc_pipe_rx); + if (err) { + printf("%s: open rx pipe failed: %s\n", + device_get_nameunit(sc->sc_dev), usbd_errstr(err)); + error = EIO; + goto done; + } + + /* Open TX pipe */ + err = usbd_open_pipe(sc->sc_ctl_iface, sc->sc_bulkout_no, + USBD_EXCLUSIVE_USE, &sc->sc_pipe_tx); + if (err) { + printf("%s: open tx pipe failed: %s\n", + device_get_nameunit(sc->sc_dev), usbd_errstr(err)); + error = EIO; + goto done; + } + +#if 0 + /* XXX: interrupt endpoint is not yet supported */ + /* Open Interrupt pipe */ + err = usbd_open_pipe_intr(sc->sc_ctl_iface, sc->sc_intrin_no, + USBD_EXCLUSIVE_USE, &sc->sc_pipe_intr, sc, + &sc->sc_cdata.ue_ibuf, UDAV_INTR_PKGLEN, + udav_intr, UDAV_INTR_INTERVAL); + if (err) { + printf("%s: open intr pipe failed: %s\n", + device_get_nameunit(sc->sc_dev), usbd_errstr(err)); + error = EIO; + goto done; + } +#endif + + + /* Start up the receive pipe. */ + for (i = 0; i < UE_RX_LIST_CNT; i++) { + c = &sc->sc_cdata.ue_rx_chain[i]; + usbd_setup_xfer(c->ue_xfer, sc->sc_pipe_rx, + c, c->ue_buf, UE_BUFSZ, + USBD_SHORT_XFER_OK | USBD_NO_COPY, + USBD_NO_TIMEOUT, udav_rxeof); + (void)usbd_transfer(c->ue_xfer); + DPRINTF(("%s: %s: start read\n", device_get_nameunit(sc->sc_dev), + __func__)); + } + + done: + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); + + return (error); +} + +static void +udav_start(struct ifnet *ifp) +{ + struct udav_softc *sc = ifp->if_softc; + struct mbuf *m_head = NULL; + + DPRINTF(("%s: %s: enter, link=%d\n", device_get_nameunit(sc->sc_dev), + __func__, sc->sc_link)); + + if (sc->sc_dying) + return; + + if (!sc->sc_link) + return; + +#if defined(__FreeBSD__) + if (ifp->if_drv_flags & IFF_DRV_OACTIVE) +#else + if (ifp->if_flags & IFF_OACTIVE) +#endif + return; +#if defined(__NetBSD__) + IFQ_POLL(&ifp->if_snd, m_head); +#elif defined(__FreeBSD__) + IF_DEQUEUE(&ifp->if_snd, m_head); +#endif + if (m_head == NULL) + return; + + if (udav_send(sc, m_head, 0)) { +#if defined(__FreeBSD__) + IF_PREPEND(&ifp->if_snd, m_head); + ifp->if_drv_flags |= IFF_DRV_OACTIVE; +#else + ifp->if_flags |= IFF_OACTIVE; +#endif + return; + } + +#if defined(__NetBSD__) + IFQ_DEQUEUE(&ifp->if_snd, m_head); +#endif + +#if NBPFILTER > 0 + BPF_MTAP(ifp, m_head); +#endif + +#if defined(__FreeBSD__) + ifp->if_drv_flags |= IFF_DRV_OACTIVE; +#else + ifp->if_flags |= IFF_OACTIVE; +#endif + + /* Set a timeout in case the chip goes out to lunch. */ + ifp->if_timer = 5; +} + +static int +udav_send(struct udav_softc *sc, struct mbuf *m, int idx) +{ + int total_len; + struct ue_chain *c; + usbd_status err; + + DPRINTF(("%s: %s: enter\n", device_get_nameunit(sc->sc_dev),__func__)); + + c = &sc->sc_cdata.ue_tx_chain[idx]; + + /* Copy the mbuf data into a contiguous buffer */ + /* first 2 bytes are packet length */ + m_copydata(m, 0, m->m_pkthdr.len, c->ue_buf + 2); + c->ue_mbuf = m; + total_len = m->m_pkthdr.len; + if (total_len < UDAV_MIN_FRAME_LEN) { + memset(c->ue_buf + 2 + total_len, 0, + UDAV_MIN_FRAME_LEN - total_len); + total_len = UDAV_MIN_FRAME_LEN; + } + + /* Frame length is specified in the first 2bytes of the buffer */ + c->ue_buf[0] = (u_int8_t)total_len; + c->ue_buf[1] = (u_int8_t)(total_len >> 8); + total_len += 2; + + usbd_setup_xfer(c->ue_xfer, sc->sc_pipe_tx, c, c->ue_buf, total_len, + USBD_FORCE_SHORT_XFER | USBD_NO_COPY, + UDAV_TX_TIMEOUT, udav_txeof); + + /* Transmit */ + sc->sc_refcnt++; + err = usbd_transfer(c->ue_xfer); + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); + if (err != USBD_IN_PROGRESS) { + printf("%s: udav_send error=%s\n", device_get_nameunit(sc->sc_dev), + usbd_errstr(err)); + /* Stop the interface */ + usb_add_task(sc->sc_udev, &sc->sc_stop_task, USB_TASKQ_DRIVER); + return (EIO); + } + + DPRINTF(("%s: %s: send %d bytes\n", device_get_nameunit(sc->sc_dev), + __func__, total_len)); + + sc->sc_cdata.ue_tx_cnt++; + + return (0); +} + +static void +udav_txeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + struct ue_chain *c = priv; + struct udav_softc *sc = c->ue_sc; + struct ifnet *ifp = GET_IFP(sc); +#if defined(__NetBSD__) + int s; +#endif + + if (sc->sc_dying) + return; + +#if defined(__NetBSD__) + s = splnet(); +#elif defined(__FreeBSD__) + UDAV_LOCK(sc); +#endif + + DPRINTF(("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); + + ifp->if_timer = 0; +#if defined(__FreeBSD__) + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; +#else + ifp->if_flags &= ~IFF_OACTIVE; +#endif + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) { +#if defined(__NetBSD__) + splx(s); +#elif defined(__FreeBSD__) + UDAV_UNLOCK(sc); +#endif + return; + } + ifp->if_oerrors++; + printf("%s: usb error on tx: %s\n", device_get_nameunit(sc->sc_dev), + usbd_errstr(status)); + if (status == USBD_STALLED) { + sc->sc_refcnt++; + usbd_clear_endpoint_stall(sc->sc_pipe_tx); + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); + } +#if defined(__NetBSD__) + splx(s); +#elif defined(__FreeBSD__) + UDAV_UNLOCK(sc); +#endif + return; + } + + ifp->if_opackets++; + + m_freem(c->ue_mbuf); + c->ue_mbuf = NULL; + +#if defined(__NetBSD__) + if (IFQ_IS_EMPTY(&ifp->if_snd) == 0) +#elif defined(__FreeBSD__) + if ( ifp->if_snd.ifq_head != NULL ) +#endif + udav_start(ifp); + +#if defined(__NetBSD__) + splx(s); +#elif defined(__FreeBSD__) + UDAV_UNLOCK(sc); +#endif +} + +static void +udav_rxeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + struct ue_chain *c = priv; + struct udav_softc *sc = c->ue_sc; + struct ifnet *ifp = GET_IFP(sc); + struct mbuf *m; + u_int32_t total_len; + u_int8_t *pktstat; +#if defined(__NetBSD__) + int s; +#endif + + DPRINTF(("%s: %s: enter\n", device_get_nameunit(sc->sc_dev),__func__)); + + if (sc->sc_dying) + return; + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) + return; + sc->sc_rx_errs++; + if (usbd_ratecheck(&sc->sc_rx_notice)) { + printf("%s: %u usb errors on rx: %s\n", + device_get_nameunit(sc->sc_dev), sc->sc_rx_errs, + usbd_errstr(status)); + sc->sc_rx_errs = 0; + } + if (status == USBD_STALLED) { + sc->sc_refcnt++; + usbd_clear_endpoint_stall(sc->sc_pipe_rx); + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); + } + goto done; + } + + usbd_get_xfer_status(xfer, NULL, NULL, &total_len, NULL); + + /* copy data to mbuf */ + m = c->ue_mbuf; + memcpy(mtod(m, char *), c->ue_buf, total_len); + + /* first byte in received data */ + pktstat = mtod(m, u_int8_t *); + m_adj(m, sizeof(u_int8_t)); + DPRINTF(("%s: RX Status: 0x%02x\n", device_get_nameunit(sc->sc_dev), *pktstat)); + + total_len = UGETW(mtod(m, u_int8_t *)); + m_adj(m, sizeof(u_int16_t)); + + if (*pktstat & UDAV_RSR_LCS) { + ifp->if_collisions++; + goto done; + } + + if (total_len < sizeof(struct ether_header) || + *pktstat & UDAV_RSR_ERR) { + ifp->if_ierrors++; + goto done; + } + + ifp->if_ipackets++; + total_len -= ETHER_CRC_LEN; + + m->m_pkthdr.len = m->m_len = total_len; +#if defined(__NetBSD__) + m->m_pkthdr.rcvif = ifp; +#elif defined(__FreeBSD__) + m->m_pkthdr.rcvif = (struct ifnet *)&sc->sc_qdat; +#endif + +#if defined(__NetBSD__) + s = splnet(); +#elif defined(__FreeBSD__) + UDAV_LOCK(sc); +#endif + +#if defined(__NetBSD__) + c->ue_mbuf = usb_ether_newbuf(); + if (c->ue_mbuf == NULL) { + printf("%s: no memory for rx list " + "-- packet dropped!\n", device_get_nameunit(sc->sc_dev)); + ifp->if_ierrors++; + goto done1; + } +#endif + +#if NBPFILTER > 0 + BPF_MTAP(ifp, m); +#endif + + DPRINTF(("%s: %s: deliver %d\n", device_get_nameunit(sc->sc_dev), + __func__, m->m_len)); +#if defined(__NetBSD__) + IF_INPUT(ifp, m); +#endif +#if defined(__FreeBSD__) + usb_ether_input(m); + UDAV_UNLOCK(sc); + return ; +#endif + +#if defined(__NetBSD__) + done1: + splx(s); +#elif defined(__FreeBSD__) + UDAV_UNLOCK(sc); +#endif + done: + /* Setup new transfer */ + usbd_setup_xfer(xfer, sc->sc_pipe_rx, c, c->ue_buf, UE_BUFSZ, + USBD_SHORT_XFER_OK | USBD_NO_COPY, + USBD_NO_TIMEOUT, udav_rxeof); + sc->sc_refcnt++; + usbd_transfer(xfer); + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); + + DPRINTF(("%s: %s: start rx\n", device_get_nameunit(sc->sc_dev), __func__)); +} + +#if 0 +static void udav_intr() +{ +} +#endif + +static int +udav_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) +{ + struct udav_softc *sc = ifp->if_softc; + struct ifreq *ifr = (struct ifreq *)data; + struct mii_data *mii; +#if defined(__NetBSD__) + int s; +#endif + int error = 0; + + DPRINTF(("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); + + if (sc->sc_dying) + return (EIO); + +#if defined(__NetBSD__) + s = splnet(); +#elif defined(__FreeBSD__) + UDAV_LOCK(sc); +#endif + + switch (cmd) { +#if defined(__FreeBSD__) + case SIOCSIFFLAGS: + if (ifp->if_flags & IFF_UP) { + if (ifp->if_drv_flags & IFF_DRV_RUNNING && + ifp->if_flags & IFF_PROMISC) { + UDAV_SETBIT(sc, UDAV_RCR, + UDAV_RCR_ALL|UDAV_RCR_PRMSC); + } else if (ifp->if_drv_flags & IFF_DRV_RUNNING && + !(ifp->if_flags & IFF_PROMISC)) { + if (ifp->if_flags & IFF_ALLMULTI) + UDAV_CLRBIT(sc, UDAV_RCR, + UDAV_RCR_PRMSC); + else + UDAV_CLRBIT(sc, UDAV_RCR, + UDAV_RCR_ALL|UDAV_RCR_PRMSC); + } else if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) + udav_init(sc); + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + udav_stop(ifp, 1); + } + error = 0; + break; + case SIOCADDMULTI: + case SIOCDELMULTI: + udav_setmulti(sc); + error = 0; + break; +#endif + case SIOCGIFMEDIA: + case SIOCSIFMEDIA: + mii = GET_MII(sc); + error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, cmd); + break; + + default: + error = ether_ioctl(ifp, cmd, data); +#if defined(__NetBSD__) + if (error == ENETRESET) { + udav_setmulti(sc); + error = 0; + } +#endif + break; + } + +#if defined(__NetBSD__) + splx(s); +#elif defined(__FreeBSD__) + UDAV_UNLOCK(sc); +#endif + + return (error); +} + +static void +udav_watchdog(struct ifnet *ifp) +{ + struct udav_softc *sc = ifp->if_softc; + struct ue_chain *c; + usbd_status stat; +#if defined(__NetBSD__) + int s; +#endif + + DPRINTF(("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); + + ifp->if_oerrors++; + printf("%s: watchdog timeout\n", device_get_nameunit(sc->sc_dev)); + +#if defined(__NetBSD__) + s = splusb(); +#elif defined(__FreeBSD__) + UDAV_LOCK(sc) +#endif + c = &sc->sc_cdata.ue_tx_chain[0]; + usbd_get_xfer_status(c->ue_xfer, NULL, NULL, NULL, &stat); + udav_txeof(c->ue_xfer, c, stat); + +#if defined(__NetBSD__) + if (IFQ_IS_EMPTY(&ifp->if_snd) == 0) +#elif defined(__FreeBSD__) + if ( ifp->if_snd.ifq_head != NULL ) +#endif + udav_start(ifp); +#if defined(__NetBSD__) + splx(s); +#elif defined(__FreeBSD__) + UDAV_UNLOCK(sc); +#endif +} + +static void +udav_stop_task(struct udav_softc *sc) +{ + udav_stop(GET_IFP(sc), 1); +} + +/* Stop the adapter and free any mbufs allocated to the RX and TX lists. */ +static void +udav_stop(struct ifnet *ifp, int disable) +{ + struct udav_softc *sc = ifp->if_softc; + usbd_status err; + + DPRINTF(("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); + + ifp->if_timer = 0; + + udav_reset(sc); + + usb_uncallout(sc->sc_stat_ch, udav_tick, sc); + + /* Stop transfers */ + /* RX endpoint */ + if (sc->sc_pipe_rx != NULL) { + err = usbd_abort_pipe(sc->sc_pipe_rx); + if (err) + printf("%s: abort rx pipe failed: %s\n", + device_get_nameunit(sc->sc_dev), usbd_errstr(err)); + err = usbd_close_pipe(sc->sc_pipe_rx); + if (err) + printf("%s: close rx pipe failed: %s\n", + device_get_nameunit(sc->sc_dev), usbd_errstr(err)); + sc->sc_pipe_rx = NULL; + } + + /* TX endpoint */ + if (sc->sc_pipe_tx != NULL) { + err = usbd_abort_pipe(sc->sc_pipe_tx); + if (err) + printf("%s: abort tx pipe failed: %s\n", + device_get_nameunit(sc->sc_dev), usbd_errstr(err)); + err = usbd_close_pipe(sc->sc_pipe_tx); + if (err) + printf("%s: close tx pipe failed: %s\n", + device_get_nameunit(sc->sc_dev), usbd_errstr(err)); + sc->sc_pipe_tx = NULL; + } + +#if 0 + /* XXX: Interrupt endpoint is not yet supported!! */ + /* Interrupt endpoint */ + if (sc->sc_pipe_intr != NULL) { + err = usbd_abort_pipe(sc->sc_pipe_intr); + if (err) + printf("%s: abort intr pipe failed: %s\n", + device_get_nameunit(sc->sc_dev), usbd_errstr(err)); + err = usbd_close_pipe(sc->sc_pipe_intr); + if (err) + printf("%s: close intr pipe failed: %s\n", + device_get_nameunit(sc->sc_dev), usbd_errstr(err)); + sc->sc_pipe_intr = NULL; + } +#endif + + /* Free RX resources. */ + usb_ether_rx_list_free(&sc->sc_cdata); + /* Free TX resources. */ + usb_ether_tx_list_free(&sc->sc_cdata); + + sc->sc_link = 0; +#if defined(__FreeBSD__) + ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); +#else + ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE); +#endif +} + +/* Set media options */ +static int +udav_ifmedia_change(struct ifnet *ifp) +{ + struct udav_softc *sc = ifp->if_softc; + struct mii_data *mii = GET_MII(sc); + + DPRINTF(("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); + + if (sc->sc_dying) + return (0); + + sc->sc_link = 0; + if (mii->mii_instance) { + struct mii_softc *miisc; + for (miisc = LIST_FIRST(&mii->mii_phys); miisc != NULL; + miisc = LIST_NEXT(miisc, mii_list)) + mii_phy_reset(miisc); + } + + return (mii_mediachg(mii)); +} + +/* Report current media status. */ +static void +udav_ifmedia_status(struct ifnet *ifp, struct ifmediareq *ifmr) +{ + struct udav_softc *sc = ifp->if_softc; + struct mii_data *mii = GET_MII(sc); + + DPRINTF(("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); + + if (sc->sc_dying) + return; + +#if defined(__FreeBSD__) + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) { +#else + if ((ifp->if_flags & IFF_RUNNING) == 0) { +#endif + ifmr->ifm_active = IFM_ETHER | IFM_NONE; + ifmr->ifm_status = 0; + return; + } + + mii_pollstat(mii); + ifmr->ifm_active = mii->mii_media_active; + ifmr->ifm_status = mii->mii_media_status; +} + +static void +udav_tick(void *xsc) +{ + struct udav_softc *sc = xsc; + + if (sc == NULL) + return; + + DPRINTFN(0xff, ("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), + __func__)); + + if (sc->sc_dying) + return; + + /* Perform periodic stuff in process context */ + usb_add_task(sc->sc_udev, &sc->sc_tick_task, USB_TASKQ_DRIVER); +} + +static void +udav_tick_task(void *xsc) +{ + struct udav_softc *sc = xsc; + struct ifnet *ifp; + struct mii_data *mii; +#if defined(__NetBSD__) + int s; +#endif + + if (sc == NULL) + return; + + DPRINTFN(0xff, ("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), + __func__)); + + if (sc->sc_dying) + return; + + ifp = GET_IFP(sc); + mii = GET_MII(sc); + + if (mii == NULL) + return; + +#if defined(__NetBSD__) + s = splnet(); +#elif defined(__FreeBSD__) + UDAV_LOCK(sc); +#endif + + mii_tick(mii); + if (!sc->sc_link) { + mii_pollstat(mii); + if (mii->mii_media_status & IFM_ACTIVE && + IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE) { + DPRINTF(("%s: %s: got link\n", + device_get_nameunit(sc->sc_dev), __func__)); + sc->sc_link++; +#if defined(__NetBSD__) + if (IFQ_IS_EMPTY(&ifp->if_snd) == 0) +#elif defined(__FreeBSD__) + if ( ifp->if_snd.ifq_head != NULL ) +#endif + udav_start(ifp); + } + } + + usb_callout(sc->sc_stat_ch, hz, udav_tick, sc); + +#if defined(__NetBSD__) + splx(s); +#elif defined(__FreeBSD__) + UDAV_UNLOCK(sc); +#endif +} + +/* Get exclusive access to the MII registers */ +static void +udav_lock_mii(struct udav_softc *sc) +{ + DPRINTFN(0xff, ("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), + __func__)); + + sc->sc_refcnt++; + lockmgr(&sc->sc_mii_lock, LK_EXCLUSIVE, NULL); +} + +static void +udav_unlock_mii(struct udav_softc *sc) +{ + DPRINTFN(0xff, ("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), + __func__)); + + lockmgr(&sc->sc_mii_lock, LK_RELEASE, NULL); + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); +} + +static int +udav_miibus_readreg(device_t dev, int phy, int reg) +{ + struct udav_softc *sc; + u_int8_t val[2]; + u_int16_t data16; + + if (dev == NULL) + return (0); + + sc = USBGETSOFTC(dev); + + DPRINTFN(0xff, ("%s: %s: enter, phy=%d reg=0x%04x\n", + device_get_nameunit(sc->sc_dev), __func__, phy, reg)); + + if (sc->sc_dying) { +#ifdef DIAGNOSTIC + printf("%s: %s: dying\n", device_get_nameunit(sc->sc_dev), + __func__); +#endif + return (0); + } + + /* XXX: one PHY only for the internal PHY */ + if (phy != 0) { + DPRINTFN(0xff, ("%s: %s: phy=%d is not supported\n", + device_get_nameunit(sc->sc_dev), __func__, phy)); + return (0); + } + + udav_lock_mii(sc); + + /* select internal PHY and set PHY register address */ + udav_csr_write1(sc, UDAV_EPAR, + UDAV_EPAR_PHY_ADR0 | (reg & UDAV_EPAR_EROA_MASK)); + + /* select PHY operation and start read command */ + udav_csr_write1(sc, UDAV_EPCR, UDAV_EPCR_EPOS | UDAV_EPCR_ERPRR); + + /* XXX: should be wait? */ + + /* end read command */ + UDAV_CLRBIT(sc, UDAV_EPCR, UDAV_EPCR_ERPRR); + + /* retrieve the result from data registers */ + udav_csr_read(sc, UDAV_EPDRL, val, 2); + + udav_unlock_mii(sc); + + data16 = val[0] | (val[1] << 8); + + DPRINTFN(0xff, ("%s: %s: phy=%d reg=0x%04x => 0x%04x\n", + device_get_nameunit(sc->sc_dev), __func__, phy, reg, data16)); + + return (data16); +} + +static int +udav_miibus_writereg(device_t dev, int phy, int reg, int data) +{ + struct udav_softc *sc; + u_int8_t val[2]; + + if (dev == NULL) + return (0); /* XXX real error? */ + + sc = USBGETSOFTC(dev); + + DPRINTFN(0xff, ("%s: %s: enter, phy=%d reg=0x%04x data=0x%04x\n", + device_get_nameunit(sc->sc_dev), __func__, phy, reg, data)); + + if (sc->sc_dying) { +#ifdef DIAGNOSTIC + printf("%s: %s: dying\n", device_get_nameunit(sc->sc_dev), + __func__); +#endif + return (0); /* XXX real error? */ + } + + /* XXX: one PHY only for the internal PHY */ + if (phy != 0) { + DPRINTFN(0xff, ("%s: %s: phy=%d is not supported\n", + device_get_nameunit(sc->sc_dev), __func__, phy)); + return (0); /* XXX real error? */ + } + + udav_lock_mii(sc); + + /* select internal PHY and set PHY register address */ + udav_csr_write1(sc, UDAV_EPAR, + UDAV_EPAR_PHY_ADR0 | (reg & UDAV_EPAR_EROA_MASK)); + + /* put the value to the data registers */ + val[0] = data & 0xff; + val[1] = (data >> 8) & 0xff; + udav_csr_write(sc, UDAV_EPDRL, val, 2); + + /* select PHY operation and start write command */ + udav_csr_write1(sc, UDAV_EPCR, UDAV_EPCR_EPOS | UDAV_EPCR_ERPRW); + + /* XXX: should be wait? */ + + /* end write command */ + UDAV_CLRBIT(sc, UDAV_EPCR, UDAV_EPCR_ERPRW); + + udav_unlock_mii(sc); + + return (0); +} + +static void +udav_miibus_statchg(device_t dev) +{ +#ifdef UDAV_DEBUG + struct udav_softc *sc; + + if (dev == NULL) + return; + + sc = USBGETSOFTC(dev); + DPRINTF(("%s: %s: enter\n", device_get_nameunit(sc->sc_dev), __func__)); +#endif + /* Nothing to do */ +} + +#if defined(__FreeBSD__) +/* + * Stop all chip I/O so that the kernel's probe routines don't + * get confused by errant DMAs when rebooting. + */ +static int +udav_shutdown(device_t dev) +{ + struct udav_softc *sc; + + sc = device_get_softc(dev); + + udav_stop_task(sc); + + return (0); +} + +static void +udav_rxstart(struct ifnet *ifp) +{ + struct udav_softc *sc; + struct ue_chain *c; + + sc = ifp->if_softc; + UDAV_LOCK(sc); + c = &sc->sc_cdata.ue_rx_chain[sc->sc_cdata.ue_rx_prod]; + + c->ue_mbuf = usb_ether_newbuf(); + if (c->ue_mbuf == NULL) { + printf("%s: no memory for rx list " + "-- packet dropped!\n", device_get_nameunit(sc->sc_dev)); + ifp->if_ierrors++; + UDAV_UNLOCK(sc); + return; + } + + /* Setup new transfer. */ + usbd_setup_xfer(c->ue_xfer, sc->sc_pipe_rx, + c, c->ue_buf, UE_BUFSZ, + USBD_SHORT_XFER_OK | USBD_NO_COPY, + USBD_NO_TIMEOUT, udav_rxeof); + usbd_transfer(c->ue_xfer); + + UDAV_UNLOCK(sc); + return; +} +#endif diff --git a/sys/legacy/dev/usb/if_udavreg.h b/sys/legacy/dev/usb/if_udavreg.h new file mode 100644 index 0000000..cb7da28 --- /dev/null +++ b/sys/legacy/dev/usb/if_udavreg.h @@ -0,0 +1,210 @@ +/* $NetBSD: if_udavreg.h,v 1.2 2003/09/04 15:17:39 tsutsui Exp $ */ +/* $nabe: if_udavreg.h,v 1.2 2003/08/21 16:26:40 nabe Exp $ */ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2003 + * Shingo WATANABE <nabe@nabechan.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. + * 3. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#define UDAV_IFACE_INDEX 0 +#define UDAV_CONFIG_NO 1 + +#define UDAV_TX_TIMEOUT 1000 +#define UDAV_TIMEOUT 10000 + +#define ETHER_ALIGN 2 + + +/* Packet length */ +#define UDAV_MIN_FRAME_LEN 60 + +/* Request */ +#define UDAV_REQ_REG_READ 0x00 /* Read from register(s) */ +#define UDAV_REQ_REG_WRITE 0x01 /* Write to register(s) */ +#define UDAV_REQ_REG_WRITE1 0x03 /* Write to a register */ + +#define UDAV_REQ_MEM_READ 0x02 /* Read from memory */ +#define UDAV_REQ_MEM_WRITE 0x05 /* Write to memory */ +#define UDAV_REQ_MEM_WRITE1 0x07 /* Write a byte to memory */ + +/* Registers */ +#define UDAV_NCR 0x00 /* Network Control Register */ +#define UDAV_NCR_EXT_PHY (1<<7) /* Select External PHY */ +#define UDAV_NCR_WAKEEN (1<<6) /* Wakeup Event Enable */ +#define UDAV_NCR_FCOL (1<<4) /* Force Collision Mode */ +#define UDAV_NCR_FDX (1<<3) /* Full-Duplex Mode (RO on Int. PHY) */ +#define UDAV_NCR_LBK1 (1<<2) /* Lookback Mode */ +#define UDAV_NCR_LBK0 (1<<1) /* Lookback Mode */ +#define UDAV_NCR_RST (1<<0) /* Software reset */ + +#define UDAV_RCR 0x05 /* RX Control Register */ +#define UDAV_RCR_WTDIS (1<<6) /* Watchdog Timer Disable */ +#define UDAV_RCR_DIS_LONG (1<<5) /* Discard Long Packet(over 1522Byte) */ +#define UDAV_RCR_DIS_CRC (1<<4) /* Discard CRC Error Packet */ +#define UDAV_RCR_ALL (1<<3) /* Pass All Multicast */ +#define UDAV_RCR_RUNT (1<<2) /* Pass Runt Packet */ +#define UDAV_RCR_PRMSC (1<<1) /* Promiscuous Mode */ +#define UDAV_RCR_RXEN (1<<0) /* RX Enable */ + +#define UDAV_RSR 0x06 /* RX Status Register */ +#define UDAV_RSR_RF (1<<7) /* Runt Frame */ +#define UDAV_RSR_MF (1<<6) /* Multicast Frame */ +#define UDAV_RSR_LCS (1<<5) /* Late Collision Seen */ +#define UDAV_RSR_RWTO (1<<4) /* Receive Watchdog Time-Out */ +#define UDAV_RSR_PLE (1<<3) /* Physical Layer Error */ +#define UDAV_RSR_AE (1<<2) /* Alignment Error */ +#define UDAV_RSR_CE (1<<1) /* CRC Error */ +#define UDAV_RSR_FOE (1<<0) /* FIFO Overflow Error */ +#define UDAV_RSR_ERR (UDAV_RSR_RF | UDAV_RSR_LCS | UDAV_RSR_RWTO |\ + UDAV_RSR_PLE | UDAV_RSR_AE | UDAV_RSR_CE |\ + UDAV_RSR_FOE) + +#define UDAV_EPCR 0x0b /* EEPROM & PHY Control Register */ +#define UDAV_EPCR_REEP (1<<5) /* Reload EEPROM */ +#define UDAV_EPCR_WEP (1<<4) /* Write EEPROM enable */ +#define UDAV_EPCR_EPOS (1<<3) /* EEPROM or PHY Operation Select */ +#define UDAV_EPCR_ERPRR (1<<2) /* EEPROM/PHY Register Read Command */ +#define UDAV_EPCR_ERPRW (1<<1) /* EEPROM/PHY Register Write Command */ +#define UDAV_EPCR_ERRE (1<<0) /* EEPROM/PHY Access Status */ + +#define UDAV_EPAR 0x0c /* EEPROM & PHY Control Register */ +#define UDAV_EPAR_PHY_ADR1 (1<<7) /* PHY Address bit 1 */ +#define UDAV_EPAR_PHY_ADR0 (1<<6) /* PHY Address bit 0 */ +#define UDAV_EPAR_EROA (1<<0) /* EEPROM Word/PHY Register Address */ +#define UDAV_EPAR_EROA_MASK (0x1f) /* [5:0] */ + +#define UDAV_EPDRL 0x0d /* EEPROM & PHY Data Register */ +#define UDAV_EPDRH 0x0e /* EEPROM & PHY Data Register */ + +#define UDAV_PAR0 0x10 /* Ethernet Address, load from EEPROM */ +#define UDAV_PAR1 0x11 /* Ethernet Address, load from EEPROM */ +#define UDAV_PAR2 0x12 /* Ethernet Address, load from EEPROM */ +#define UDAV_PAR3 0x13 /* Ethernet Address, load from EEPROM */ +#define UDAV_PAR4 0x14 /* Ethernet Address, load from EEPROM */ +#define UDAV_PAR5 0x15 /* Ethernet Address, load from EEPROM */ +#define UDAV_PAR UDAV_PAR0 + +#define UDAV_MAR0 0x16 /* Multicast Register */ +#define UDAV_MAR1 0x17 /* Multicast Register */ +#define UDAV_MAR2 0x18 /* Multicast Register */ +#define UDAV_MAR3 0x19 /* Multicast Register */ +#define UDAV_MAR4 0x1a /* Multicast Register */ +#define UDAV_MAR5 0x1b /* Multicast Register */ +#define UDAV_MAR6 0x1c /* Multicast Register */ +#define UDAV_MAR7 0x1d /* Multicast Register */ +#define UDAV_MAR UDAV_MAR0 + +#define UDAV_GPCR 0x1e /* General purpose control register */ +#define UDAV_GPCR_GEP_CNTL6 (1<<6) /* General purpose control 6 */ +#define UDAV_GPCR_GEP_CNTL5 (1<<5) /* General purpose control 5 */ +#define UDAV_GPCR_GEP_CNTL4 (1<<4) /* General purpose control 4 */ +#define UDAV_GPCR_GEP_CNTL3 (1<<3) /* General purpose control 3 */ +#define UDAV_GPCR_GEP_CNTL2 (1<<2) /* General purpose control 2 */ +#define UDAV_GPCR_GEP_CNTL1 (1<<1) /* General purpose control 1 */ +#define UDAV_GPCR_GEP_CNTL0 (1<<0) /* General purpose control 0 */ + +#define UDAV_GPR 0x1f /* General purpose register */ +#define UDAV_GPR_GEPIO6 (1<<6) /* General purpose 6 */ +#define UDAV_GPR_GEPIO5 (1<<5) /* General purpose 5 */ +#define UDAV_GPR_GEPIO4 (1<<4) /* General purpose 4 */ +#define UDAV_GPR_GEPIO3 (1<<3) /* General purpose 3 */ +#define UDAV_GPR_GEPIO2 (1<<2) /* General purpose 2 */ +#define UDAV_GPR_GEPIO1 (1<<1) /* General purpose 1 */ +#define UDAV_GPR_GEPIO0 (1<<0) /* General purpose 0 */ + +#if defined(__FreeBSD__) +#define GET_IFP(sc) ((sc)->sc_ifp) +#elif defined(__OpenBSD__) +#define GET_IFP(sc) (&(sc)->sc_ac.ac_if) +#elif defined(__NetBSD__) +#define GET_IFP(sc) (&(sc)->sc_ec.ec_if) +#endif +#if defined(__FreeBSD__) +#define GET_MII(sc) (device_get_softc((sc)->sc_miibus)) +#else +#define GET_MII(sc) (&(sc)->sc_mii) +#endif + +#if defined(__FreeBSD__) +#if 0 +#define UDAV_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) +#define UDAV_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) +#else +#define UDAV_LOCK(_sc) +#define UDAV_UNLOCK(_sc) +#endif +#endif + +struct udav_softc { +#if defined(__FreeBSD__) + struct ifnet *sc_ifp; +#endif + device_t sc_dev; /* base device */ + usbd_device_handle sc_udev; + + /* USB */ + usbd_interface_handle sc_ctl_iface; + /* int sc_ctl_iface_no; */ + int sc_bulkin_no; /* bulk in endpoint */ + int sc_bulkout_no; /* bulk out endpoint */ + int sc_intrin_no; /* intr in endpoint */ + usbd_pipe_handle sc_pipe_rx; + usbd_pipe_handle sc_pipe_tx; + usbd_pipe_handle sc_pipe_intr; + usb_callout_t sc_stat_ch; + u_int sc_rx_errs; + /* u_int sc_intr_errs; */ + struct timeval sc_rx_notice; + + /* Ethernet */ + +#if defined(__FreeBSD__) + device_t sc_miibus ; + struct mtx sc_mtx ; + struct usb_qdat sc_qdat; +#elif defined(__NetBSD__) + struct ethercom sc_ec; /* ethernet common */ + struct mii_data sc_mii; +#endif + struct lock sc_mii_lock; + int sc_link; +#define sc_media udav_mii.mii_media +#if defined(NRND) && NRND > 0 + rndsource_element_t rnd_source; +#endif + struct ue_cdata sc_cdata; + + int sc_attached; + int sc_dying; + int sc_refcnt; + + struct usb_task sc_tick_task; + struct usb_task sc_stop_task; + + u_int16_t sc_flags; +}; diff --git a/sys/legacy/dev/usb/if_upgt.c b/sys/legacy/dev/usb/if_upgt.c new file mode 100644 index 0000000..9ab226f --- /dev/null +++ b/sys/legacy/dev/usb/if_upgt.c @@ -0,0 +1,2375 @@ +/* $OpenBSD: if_upgt.c,v 1.35 2008/04/16 18:32:15 damien Exp $ */ +/* $FreeBSD$ */ + +/* + * Copyright (c) 2007 Marcus Glocker <mglocker@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/endian.h> +#include <sys/firmware.h> +#include <sys/linker.h> +#include <sys/mbuf.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/socket.h> +#include <sys/sockio.h> +#include <sys/sysctl.h> + +#include <net/if.h> +#include <net/if_arp.h> +#include <net/ethernet.h> +#include <net/if_dl.h> +#include <net/if_media.h> +#include <net/if_types.h> + +#include <sys/bus.h> +#include <machine/bus.h> + +#include <net80211/ieee80211_var.h> +#include <net80211/ieee80211_phy.h> +#include <net80211/ieee80211_radiotap.h> +#include <net80211/ieee80211_regdomain.h> + +#include <net/bpf.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usbdivar.h> +#include "usbdevs.h" +#include <dev/usb/usb_ethersubr.h> + +#include <dev/usb/if_upgtvar.h> + +/* + * Driver for the USB PrismGT devices. + * + * For now just USB 2.0 devices with the GW3887 chipset are supported. + * The driver has been written based on the firmware version 2.13.1.0_LM87. + * + * TODO's: + * - MONITOR mode test. + * - Add HOSTAP mode. + * - Add IBSS mode. + * - Support the USB 1.0 devices (NET2280, ISL3880, ISL3886 chipsets). + * + * Parts of this driver has been influenced by reading the p54u driver + * written by Jean-Baptiste Note <jean-baptiste.note@m4x.org> and + * Sebastien Bourdeauducq <lekernel@prism54.org>. + */ + +SYSCTL_NODE(_hw, OID_AUTO, upgt, CTLFLAG_RD, 0, + "USB PrismGT GW3887 driver parameters"); + +/* + * NB: normally `upgt_txbuf' value can be increased to maximum 6, mininum 1. + * However, we're using just 2 txbufs to protect packet losses in some cases + * so the performance was sacrificed that with this value its speed is about + * 2.1Mb/s. + * + * With setting txbuf value as 6, you can get full speed, 3.0Mb/s, of this + * device but sometimes you'd meet some packet losses then retransmision. + */ +static int upgt_txbuf = UPGT_TX_COUNT; /* # tx buffers to allocate */ +SYSCTL_INT(_hw_upgt, OID_AUTO, txbuf, CTLFLAG_RW, &upgt_txbuf, + 0, "tx buffers allocated"); +TUNABLE_INT("hw.upgt.txbuf", &upgt_txbuf); + +#ifdef UPGT_DEBUG +int upgt_debug = 0; +SYSCTL_INT(_hw_upgt, OID_AUTO, debug, CTLFLAG_RW, &upgt_debug, + 0, "control debugging printfs"); +TUNABLE_INT("hw.upgt.debug", &upgt_debug); +enum { + UPGT_DEBUG_XMIT = 0x00000001, /* basic xmit operation */ + UPGT_DEBUG_RECV = 0x00000002, /* basic recv operation */ + UPGT_DEBUG_RESET = 0x00000004, /* reset processing */ + UPGT_DEBUG_INTR = 0x00000008, /* INTR */ + UPGT_DEBUG_TX_PROC = 0x00000010, /* tx ISR proc */ + UPGT_DEBUG_RX_PROC = 0x00000020, /* rx ISR proc */ + UPGT_DEBUG_STATE = 0x00000040, /* 802.11 state transitions */ + UPGT_DEBUG_STAT = 0x00000080, /* statistic */ + UPGT_DEBUG_FW = 0x00000100, /* firmware */ + UPGT_DEBUG_ANY = 0xffffffff +}; +#define DPRINTF(sc, m, fmt, ...) do { \ + if (sc->sc_debug & (m)) \ + printf(fmt, __VA_ARGS__); \ +} while (0) +#else +#define DPRINTF(sc, m, fmt, ...) do { \ + (void) sc; \ +} while (0) +#endif + +/* + * Prototypes. + */ +static device_probe_t upgt_match; +static device_attach_t upgt_attach; +static device_detach_t upgt_detach; +static int upgt_alloc_tx(struct upgt_softc *); +static int upgt_alloc_rx(struct upgt_softc *); +static int upgt_alloc_cmd(struct upgt_softc *); +static int upgt_attach_hook(device_t); +static int upgt_device_reset(struct upgt_softc *); +static int upgt_bulk_xmit(struct upgt_softc *, struct upgt_data *, + usbd_pipe_handle, uint32_t *, int); +static int upgt_fw_verify(struct upgt_softc *); +static int upgt_mem_init(struct upgt_softc *); +static int upgt_fw_load(struct upgt_softc *); +static int upgt_fw_copy(const uint8_t *, char *, int); +static uint32_t upgt_crc32_le(const void *, size_t); +static void upgt_rxeof(usbd_xfer_handle, usbd_private_handle, usbd_status); +static void upgt_txeof(usbd_xfer_handle, usbd_private_handle, usbd_status); +static int upgt_eeprom_read(struct upgt_softc *); +static int upgt_eeprom_parse(struct upgt_softc *); +static void upgt_eeprom_parse_hwrx(struct upgt_softc *, uint8_t *); +static void upgt_eeprom_parse_freq3(struct upgt_softc *, uint8_t *, int); +static void upgt_eeprom_parse_freq4(struct upgt_softc *, uint8_t *, int); +static void upgt_eeprom_parse_freq6(struct upgt_softc *, uint8_t *, int); +static uint32_t upgt_chksum_le(const uint32_t *, size_t); +static void upgt_tx_done(struct upgt_softc *, uint8_t *); +static void upgt_rx(struct upgt_softc *, uint8_t *, int); +static void upgt_init(void *); +static void upgt_init_locked(struct upgt_softc *); +static int upgt_ioctl(struct ifnet *, u_long, caddr_t); +static void upgt_start(struct ifnet *); +static int upgt_raw_xmit(struct ieee80211_node *, struct mbuf *, + const struct ieee80211_bpf_params *); +static void upgt_scan_start(struct ieee80211com *); +static void upgt_scan_end(struct ieee80211com *); +static void upgt_set_channel(struct ieee80211com *); +static struct ieee80211vap *upgt_vap_create(struct ieee80211com *, + const char name[IFNAMSIZ], int unit, int opmode, + int flags, const uint8_t bssid[IEEE80211_ADDR_LEN], + const uint8_t mac[IEEE80211_ADDR_LEN]); +static void upgt_vap_delete(struct ieee80211vap *); +static void upgt_update_mcast(struct ifnet *); +static uint8_t upgt_rx_rate(struct upgt_softc *, const int); +static void upgt_set_multi(void *); +static void upgt_stop(struct upgt_softc *, int); +static void upgt_setup_rates(struct ieee80211vap *, struct ieee80211com *); +static int upgt_set_macfilter(struct upgt_softc *, uint8_t); +static int upgt_newstate(struct ieee80211vap *, enum ieee80211_state, int); +static void upgt_task(void *); +static void upgt_scantask(void *); +static void upgt_set_chan(struct upgt_softc *, struct ieee80211_channel *); +static void upgt_set_led(struct upgt_softc *, int); +static void upgt_set_led_blink(void *); +static void upgt_tx_task(void *); +static int upgt_get_stats(struct upgt_softc *); +static void upgt_mem_free(struct upgt_softc *, uint32_t); +static uint32_t upgt_mem_alloc(struct upgt_softc *); +static void upgt_free_tx(struct upgt_softc *); +static void upgt_free_rx(struct upgt_softc *); +static void upgt_free_cmd(struct upgt_softc *); +static void upgt_watchdog(void *); + +static const char *upgt_fwname = "upgt-gw3887"; + +static const struct usb_devno upgt_devs_2[] = { + /* version 2 devices */ + { USB_VENDOR_ACCTON, USB_PRODUCT_ACCTON_PRISM_GT }, + { USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5D7050 }, + { USB_VENDOR_CONCEPTRONIC, USB_PRODUCT_CONCEPTRONIC_PRISM_GT }, + { USB_VENDOR_DELL, USB_PRODUCT_DELL_PRISM_GT_1 }, + { USB_VENDOR_DELL, USB_PRODUCT_DELL_PRISM_GT_2 }, + { USB_VENDOR_FSC, USB_PRODUCT_FSC_E5400 }, + { USB_VENDOR_GLOBESPAN, USB_PRODUCT_GLOBESPAN_PRISM_GT_1 }, + { USB_VENDOR_GLOBESPAN, USB_PRODUCT_GLOBESPAN_PRISM_GT_2 }, + { USB_VENDOR_INTERSIL, USB_PRODUCT_INTERSIL_PRISM_GT }, + { USB_VENDOR_SMC, USB_PRODUCT_SMC_2862WG }, + { USB_VENDOR_WISTRONNEWEB, USB_PRODUCT_WISTRONNEWEB_UR045G }, + { USB_VENDOR_XYRATEX, USB_PRODUCT_XYRATEX_PRISM_GT_1 }, + { USB_VENDOR_XYRATEX, USB_PRODUCT_XYRATEX_PRISM_GT_2 }, + { USB_VENDOR_ZCOM, USB_PRODUCT_ZCOM_XG703A } +}; + +static int +upgt_match(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (!uaa->iface) + return UMATCH_NONE; + + if (usb_lookup(upgt_devs_2, uaa->vendor, uaa->product) != NULL) + return (UMATCH_VENDOR_PRODUCT); + + return (UMATCH_NONE); +} + +static int +upgt_attach(device_t dev) +{ + int i; + struct upgt_softc *sc = device_get_softc(dev); + struct usb_attach_arg *uaa = device_get_ivars(dev); + usb_endpoint_descriptor_t *ed; + usb_interface_descriptor_t *id; + usbd_status error; + + sc->sc_dev = dev; + sc->sc_udev = uaa->device; +#ifdef UPGT_DEBUG + sc->sc_debug = upgt_debug; +#endif + + /* set configuration number */ + if (usbd_set_config_no(sc->sc_udev, UPGT_CONFIG_NO, 0) != 0) { + device_printf(dev, "could not set configuration no!\n"); + return ENXIO; + } + + /* get the first interface handle */ + error = usbd_device2interface_handle(sc->sc_udev, UPGT_IFACE_INDEX, + &sc->sc_iface); + if (error != 0) { + device_printf(dev, "could not get interface handle!\n"); + return ENXIO; + } + + /* find endpoints */ + id = usbd_get_interface_descriptor(sc->sc_iface); + sc->sc_rx_no = sc->sc_tx_no = -1; + for (i = 0; i < id->bNumEndpoints; i++) { + ed = usbd_interface2endpoint_descriptor(sc->sc_iface, i); + if (ed == NULL) { + device_printf(dev, + "no endpoint descriptor for iface %d!\n", i); + return ENXIO; + } + + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) + sc->sc_tx_no = ed->bEndpointAddress; + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) + sc->sc_rx_no = ed->bEndpointAddress; + + /* + * 0x01 TX pipe + * 0x81 RX pipe + * + * Deprecated scheme (not used with fw version >2.5.6.x): + * 0x02 TX MGMT pipe + * 0x82 TX MGMT pipe + */ + if (sc->sc_tx_no != -1 && sc->sc_rx_no != -1) + break; + } + if (sc->sc_rx_no == -1 || sc->sc_tx_no == -1) { + device_printf(dev, "missing endpoint!\n"); + return ENXIO; + } + + /* + * Open TX and RX USB bulk pipes. + */ + error = usbd_open_pipe(sc->sc_iface, sc->sc_tx_no, USBD_EXCLUSIVE_USE, + &sc->sc_tx_pipeh); + if (error != 0) { + device_printf(dev, "could not open TX pipe: %s!\n", + usbd_errstr(error)); + goto fail; + } + error = usbd_open_pipe(sc->sc_iface, sc->sc_rx_no, USBD_EXCLUSIVE_USE, + &sc->sc_rx_pipeh); + if (error != 0) { + device_printf(dev, "could not open RX pipe: %s!\n", + usbd_errstr(error)); + goto fail; + } + + /* Allocate TX, RX, and CMD xfers. */ + if (upgt_alloc_tx(sc) != 0) + goto fail; + if (upgt_alloc_rx(sc) != 0) + goto fail; + if (upgt_alloc_cmd(sc) != 0) + goto fail; + + /* We need the firmware loaded to complete the attach. */ + return upgt_attach_hook(dev); + +fail: + device_printf(dev, "%s failed!\n", __func__); + return ENXIO; +} + +static int +upgt_attach_hook(device_t dev) +{ + struct ieee80211com *ic; + struct ifnet *ifp; + struct upgt_softc *sc = device_get_softc(dev); + struct upgt_data *data_rx = &sc->rx_data; + uint8_t bands; + usbd_status error; + + ifp = sc->sc_ifp = if_alloc(IFT_IEEE80211); + if (ifp == NULL) { + device_printf(dev, "can not if_alloc()\n"); + return ENXIO; + } + + mtx_init(&sc->sc_mtx, device_get_nameunit(dev), MTX_NETWORK_LOCK, + MTX_DEF | MTX_RECURSE); + usb_init_task(&sc->sc_mcasttask, upgt_set_multi, sc); + usb_init_task(&sc->sc_scantask, upgt_scantask, sc); + usb_init_task(&sc->sc_task, upgt_task, sc); + usb_init_task(&sc->sc_task_tx, upgt_tx_task, sc); + callout_init(&sc->sc_led_ch, 0); + callout_init(&sc->sc_watchdog_ch, 0); + + /* Initialize the device. */ + if (upgt_device_reset(sc) != 0) + goto fail; + + /* Verify the firmware. */ + if (upgt_fw_verify(sc) != 0) + goto fail; + + /* Calculate device memory space. */ + if (sc->sc_memaddr_frame_start == 0 || sc->sc_memaddr_frame_end == 0) { + device_printf(dev, + "could not find memory space addresses on FW!\n"); + goto fail; + } + sc->sc_memaddr_frame_end -= UPGT_MEMSIZE_RX + 1; + sc->sc_memaddr_rx_start = sc->sc_memaddr_frame_end + 1; + + DPRINTF(sc, UPGT_DEBUG_FW, "memory address frame start=0x%08x\n", + sc->sc_memaddr_frame_start); + DPRINTF(sc, UPGT_DEBUG_FW, "memory address frame end=0x%08x\n", + sc->sc_memaddr_frame_end); + DPRINTF(sc, UPGT_DEBUG_FW, "memory address rx start=0x%08x\n", + sc->sc_memaddr_rx_start); + + upgt_mem_init(sc); + + /* Load the firmware. */ + if (upgt_fw_load(sc) != 0) + goto fail; + + /* Startup the RX pipe. */ + usbd_setup_xfer(data_rx->xfer, sc->sc_rx_pipeh, data_rx, data_rx->buf, + MCLBYTES, USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT, upgt_rxeof); + error = usbd_transfer(data_rx->xfer); + if (error != 0 && error != USBD_IN_PROGRESS) { + device_printf(dev, "could not queue RX transfer!\n"); + goto fail; + } + usbd_delay_ms(sc->sc_udev, 100); + + /* Read the whole EEPROM content and parse it. */ + if (upgt_eeprom_read(sc) != 0) + goto fail; + if (upgt_eeprom_parse(sc) != 0) + goto fail; + + /* Setup the 802.11 device. */ + ifp->if_softc = sc; + if_initname(ifp, "upgt", device_get_unit(sc->sc_dev)); + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST | + IFF_NEEDSGIANT; /* USB stack is still under Giant lock */ + ifp->if_init = upgt_init; + ifp->if_ioctl = upgt_ioctl; + ifp->if_start = upgt_start; + IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN); + IFQ_SET_READY(&ifp->if_snd); + + ic = ifp->if_l2com; + ic->ic_ifp = ifp; + ic->ic_phytype = IEEE80211_T_OFDM; /* not only, but not used */ + ic->ic_opmode = IEEE80211_M_STA; + /* set device capabilities */ + ic->ic_caps = + IEEE80211_C_STA /* station mode */ + | IEEE80211_C_MONITOR /* monitor mode */ + | IEEE80211_C_SHPREAMBLE /* short preamble supported */ + | IEEE80211_C_SHSLOT /* short slot time supported */ + | IEEE80211_C_BGSCAN /* capable of bg scanning */ + | IEEE80211_C_WPA /* 802.11i */ + ; + + bands = 0; + setbit(&bands, IEEE80211_MODE_11B); + setbit(&bands, IEEE80211_MODE_11G); + ieee80211_init_channels(ic, NULL, &bands); + + ieee80211_ifattach(ic); + ic->ic_raw_xmit = upgt_raw_xmit; + ic->ic_scan_start = upgt_scan_start; + ic->ic_scan_end = upgt_scan_end; + ic->ic_set_channel = upgt_set_channel; + + ic->ic_vap_create = upgt_vap_create; + ic->ic_vap_delete = upgt_vap_delete; + ic->ic_update_mcast = upgt_update_mcast; + + bpfattach(ifp, DLT_IEEE802_11_RADIO, + sizeof(struct ieee80211_frame) + sizeof(sc->sc_txtap)); + sc->sc_rxtap_len = sizeof(sc->sc_rxtap); + sc->sc_rxtap.wr_ihdr.it_len = htole16(sc->sc_rxtap_len); + sc->sc_rxtap.wr_ihdr.it_present = htole32(UPGT_RX_RADIOTAP_PRESENT); + sc->sc_txtap_len = sizeof(sc->sc_txtap); + sc->sc_txtap.wt_ihdr.it_len = htole16(sc->sc_txtap_len); + sc->sc_txtap.wt_ihdr.it_present = htole32(UPGT_TX_RADIOTAP_PRESENT); + + if (bootverbose) + ieee80211_announce(ic); + + usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, sc->sc_udev, sc->sc_dev); + return 0; +fail: + device_printf(dev, "%s failed!\n", __func__); + mtx_destroy(&sc->sc_mtx); + if_free(ifp); + return ENXIO; +} + +static void +upgt_tx_task(void *arg) +{ + struct upgt_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211_frame *wh; + struct ieee80211_key *k; + struct upgt_data *data_tx; + struct upgt_lmac_mem *mem; + struct upgt_lmac_tx_desc *txdesc; + struct mbuf *m; + uint32_t addr; + int len, i; + usbd_status error; + + upgt_set_led(sc, UPGT_LED_BLINK); + + UPGT_LOCK(sc); + for (i = 0; i < upgt_txbuf; i++) { + data_tx = &sc->tx_data[i]; + if (data_tx->m == NULL) + continue; + + m = data_tx->m; + addr = data_tx->addr + UPGT_MEMSIZE_FRAME_HEAD; + + /* + * Software crypto. + */ + wh = mtod(m, struct ieee80211_frame *); + if (wh->i_fc[1] & IEEE80211_FC1_WEP) { + k = ieee80211_crypto_encap(data_tx->ni, m); + if (k == NULL) { + device_printf(sc->sc_dev, + "ieee80211_crypto_encap returns NULL.\n"); + goto done; + } + + /* in case packet header moved, reset pointer */ + wh = mtod(m, struct ieee80211_frame *); + } + + /* + * Transmit the URB containing the TX data. + */ + bzero(data_tx->buf, MCLBYTES); + + mem = (struct upgt_lmac_mem *)data_tx->buf; + mem->addr = htole32(addr); + + txdesc = (struct upgt_lmac_tx_desc *)(mem + 1); + + if ((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) == + IEEE80211_FC0_TYPE_MGT) { + /* mgmt frames */ + txdesc->header1.flags = UPGT_H1_FLAGS_TX_MGMT; + /* always send mgmt frames at lowest rate (DS1) */ + memset(txdesc->rates, 0x10, sizeof(txdesc->rates)); + } else { + /* data frames */ + txdesc->header1.flags = UPGT_H1_FLAGS_TX_DATA; + bcopy(sc->sc_cur_rateset, txdesc->rates, + sizeof(txdesc->rates)); + } + txdesc->header1.type = UPGT_H1_TYPE_TX_DATA; + txdesc->header1.len = htole16(m->m_pkthdr.len); + txdesc->header2.reqid = htole32(data_tx->addr); + txdesc->header2.type = htole16(UPGT_H2_TYPE_TX_ACK_YES); + txdesc->header2.flags = htole16(UPGT_H2_FLAGS_TX_ACK_YES); + txdesc->type = htole32(UPGT_TX_DESC_TYPE_DATA); + txdesc->pad3[0] = UPGT_TX_DESC_PAD3_SIZE; + + if (bpf_peers_present(ifp->if_bpf)) { + struct upgt_tx_radiotap_header *tap = &sc->sc_txtap; + + tap->wt_flags = 0; + tap->wt_rate = 0; /* XXX where to get from? */ + tap->wt_chan_freq = htole16(ic->ic_curchan->ic_freq); + tap->wt_chan_flags = htole16(ic->ic_curchan->ic_flags); + + bpf_mtap2(ifp->if_bpf, tap, sc->sc_txtap_len, m); + } + + /* copy frame below our TX descriptor header */ + m_copydata(m, 0, m->m_pkthdr.len, + data_tx->buf + (sizeof(*mem) + sizeof(*txdesc))); + /* calculate frame size */ + len = sizeof(*mem) + sizeof(*txdesc) + m->m_pkthdr.len; + /* we need to align the frame to a 4 byte boundary */ + len = (len + 3) & ~3; + /* calculate frame checksum */ + mem->chksum = upgt_chksum_le((uint32_t *)txdesc, + len - sizeof(*mem)); + /* we do not need the mbuf anymore */ + m_freem(m); + data_tx->m = NULL; + + DPRINTF(sc, UPGT_DEBUG_XMIT, "%s: TX start data sending\n", + __func__); + KASSERT(len <= MCLBYTES, ("mbuf is small for saving data")); + + usbd_setup_xfer(data_tx->xfer, sc->sc_tx_pipeh, data_tx, + data_tx->buf, len, USBD_FORCE_SHORT_XFER | USBD_NO_COPY, + UPGT_USB_TIMEOUT, upgt_txeof); + UPGT_UNLOCK(sc); + mtx_lock(&Giant); + error = usbd_transfer(data_tx->xfer); + mtx_unlock(&Giant); + UPGT_LOCK(sc); + if (error != 0 && error != USBD_IN_PROGRESS) { + device_printf(sc->sc_dev, + "could not transmit TX data URB!\n"); + goto done; + } + + DPRINTF(sc, UPGT_DEBUG_XMIT, "TX sent (%d bytes)\n", len); + } +done: + UPGT_UNLOCK(sc); + /* + * If we don't regulary read the device statistics, the RX queue + * will stall. It's strange, but it works, so we keep reading + * the statistics here. *shrug* + */ + (void)upgt_get_stats(sc); +} + +static void +upgt_txeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + struct upgt_data *data_tx = priv; + struct upgt_softc *sc = data_tx->sc; + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) + return; + if (status == USBD_STALLED) { + usbd_clear_endpoint_stall_async(sc->sc_rx_pipeh); + return; + } + + device_printf(sc->sc_dev, "TX warning(%s)\n", + usbd_errstr(status)); + } +} + +static int +upgt_get_stats(struct upgt_softc *sc) +{ + struct upgt_data *data_cmd = &sc->cmd_data; + struct upgt_lmac_mem *mem; + struct upgt_lmac_stats *stats; + int len; + + /* + * Transmit the URB containing the CMD data. + */ + bzero(data_cmd->buf, MCLBYTES); + + mem = (struct upgt_lmac_mem *)data_cmd->buf; + mem->addr = htole32(sc->sc_memaddr_frame_start + + UPGT_MEMSIZE_FRAME_HEAD); + + stats = (struct upgt_lmac_stats *)(mem + 1); + + stats->header1.flags = 0; + stats->header1.type = UPGT_H1_TYPE_CTRL; + stats->header1.len = htole16( + sizeof(struct upgt_lmac_stats) - sizeof(struct upgt_lmac_header)); + + stats->header2.reqid = htole32(sc->sc_memaddr_frame_start); + stats->header2.type = htole16(UPGT_H2_TYPE_STATS); + stats->header2.flags = 0; + + len = sizeof(*mem) + sizeof(*stats); + + mem->chksum = upgt_chksum_le((uint32_t *)stats, + len - sizeof(*mem)); + + if (upgt_bulk_xmit(sc, data_cmd, sc->sc_tx_pipeh, &len, 0) != 0) { + device_printf(sc->sc_dev, + "could not transmit statistics CMD data URB!\n"); + return (EIO); + } + + return (0); +} + +static int +upgt_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) +{ + struct upgt_softc *sc = ifp->if_softc; + struct ieee80211com *ic = ifp->if_l2com; + struct ifreq *ifr = (struct ifreq *) data; + int error = 0, startall = 0; + + switch (cmd) { + case SIOCSIFFLAGS: + mtx_lock(&Giant); + if (ifp->if_flags & IFF_UP) { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + if ((ifp->if_flags ^ sc->sc_if_flags) & + (IFF_ALLMULTI | IFF_PROMISC)) + upgt_set_multi(sc); + } else { + upgt_init(sc); + startall = 1; + } + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + upgt_stop(sc, 1); + } + sc->sc_if_flags = ifp->if_flags; + if (startall) + ieee80211_start_all(ic); + mtx_unlock(&Giant); + break; + case SIOCGIFMEDIA: + error = ifmedia_ioctl(ifp, ifr, &ic->ic_media, cmd); + break; + case SIOCGIFADDR: + error = ether_ioctl(ifp, cmd, data); + break; + default: + error = EINVAL; + break; + } + return error; +} + +static void +upgt_stop(struct upgt_softc *sc, int disable) +{ + struct ifnet *ifp = sc->sc_ifp; + + /* abort and close TX / RX pipes */ + if (sc->sc_tx_pipeh != NULL) + usbd_abort_pipe(sc->sc_tx_pipeh); + if (sc->sc_rx_pipeh != NULL) + usbd_abort_pipe(sc->sc_rx_pipeh); + + /* device down */ + sc->sc_tx_timer = 0; + ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); +} + +static void +upgt_task(void *arg) +{ + struct upgt_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); + struct upgt_vap *uvp = UPGT_VAP(vap); + + DPRINTF(sc, UPGT_DEBUG_STATE, "%s: %s -> %s\n", __func__, + ieee80211_state_name[vap->iv_state], + ieee80211_state_name[sc->sc_state]); + + switch (sc->sc_state) { + case IEEE80211_S_INIT: + /* do not accept any frames if the device is down */ + UPGT_LOCK(sc); + upgt_set_macfilter(sc, sc->sc_state); + UPGT_UNLOCK(sc); + upgt_set_led(sc, UPGT_LED_OFF); + break; + case IEEE80211_S_SCAN: + upgt_set_chan(sc, ic->ic_curchan); + break; + case IEEE80211_S_AUTH: + upgt_set_chan(sc, ic->ic_curchan); + break; + case IEEE80211_S_ASSOC: + break; + case IEEE80211_S_RUN: + UPGT_LOCK(sc); + upgt_set_macfilter(sc, sc->sc_state); + UPGT_UNLOCK(sc); + upgt_set_led(sc, UPGT_LED_ON); + break; + default: + break; + } + + IEEE80211_LOCK(ic); + uvp->newstate(vap, sc->sc_state, sc->sc_arg); + if (vap->iv_newstate_cb != NULL) + vap->iv_newstate_cb(vap, sc->sc_state, sc->sc_arg); + IEEE80211_UNLOCK(ic); +} + +static void +upgt_set_led(struct upgt_softc *sc, int action) +{ + struct upgt_data *data_cmd = &sc->cmd_data; + struct upgt_lmac_mem *mem; + struct upgt_lmac_led *led; + int len; + + /* + * Transmit the URB containing the CMD data. + */ + bzero(data_cmd->buf, MCLBYTES); + + mem = (struct upgt_lmac_mem *)data_cmd->buf; + mem->addr = htole32(sc->sc_memaddr_frame_start + + UPGT_MEMSIZE_FRAME_HEAD); + + led = (struct upgt_lmac_led *)(mem + 1); + + led->header1.flags = UPGT_H1_FLAGS_TX_NO_CALLBACK; + led->header1.type = UPGT_H1_TYPE_CTRL; + led->header1.len = htole16( + sizeof(struct upgt_lmac_led) - + sizeof(struct upgt_lmac_header)); + + led->header2.reqid = htole32(sc->sc_memaddr_frame_start); + led->header2.type = htole16(UPGT_H2_TYPE_LED); + led->header2.flags = 0; + + switch (action) { + case UPGT_LED_OFF: + led->mode = htole16(UPGT_LED_MODE_SET); + led->action_fix = 0; + led->action_tmp = htole16(UPGT_LED_ACTION_OFF); + led->action_tmp_dur = 0; + break; + case UPGT_LED_ON: + led->mode = htole16(UPGT_LED_MODE_SET); + led->action_fix = 0; + led->action_tmp = htole16(UPGT_LED_ACTION_ON); + led->action_tmp_dur = 0; + break; + case UPGT_LED_BLINK: + if (sc->sc_state != IEEE80211_S_RUN) + return; + if (sc->sc_led_blink) + /* previous blink was not finished */ + return; + led->mode = htole16(UPGT_LED_MODE_SET); + led->action_fix = htole16(UPGT_LED_ACTION_OFF); + led->action_tmp = htole16(UPGT_LED_ACTION_ON); + led->action_tmp_dur = htole16(UPGT_LED_ACTION_TMP_DUR); + /* lock blink */ + sc->sc_led_blink = 1; + callout_reset(&sc->sc_led_ch, hz, upgt_set_led_blink, sc); + break; + default: + return; + } + + len = sizeof(*mem) + sizeof(*led); + + mem->chksum = upgt_chksum_le((uint32_t *)led, + len - sizeof(*mem)); + + if (upgt_bulk_xmit(sc, data_cmd, sc->sc_tx_pipeh, &len, 0) != 0) + device_printf(sc->sc_dev, "could not transmit led CMD URB!\n"); +} + +static void +upgt_set_led_blink(void *arg) +{ + struct upgt_softc *sc = arg; + + /* blink finished, we are ready for a next one */ + sc->sc_led_blink = 0; +} + +static void +upgt_init(void *priv) +{ + struct upgt_softc *sc = priv; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + UPGT_LOCK(sc); + upgt_init_locked(sc); + UPGT_UNLOCK(sc); + + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + ieee80211_start_all(ic); /* start all vap's */ +} + +static void +upgt_init_locked(struct upgt_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + IEEE80211_ADDR_COPY(ic->ic_myaddr, IF_LLADDR(ifp)); + DPRINTF(sc, UPGT_DEBUG_RESET, "setting MAC address to %s\n", + ether_sprintf(ic->ic_myaddr)); + + upgt_set_macfilter(sc, IEEE80211_S_SCAN); + + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + ifp->if_drv_flags |= IFF_DRV_RUNNING; +} + +static int +upgt_set_macfilter(struct upgt_softc *sc, uint8_t state) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); + struct ieee80211_node *ni = vap->iv_bss; + struct upgt_data *data_cmd = &sc->cmd_data; + struct upgt_lmac_mem *mem; + struct upgt_lmac_filter *filter; + int len; + uint8_t broadcast[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + + /* + * Transmit the URB containing the CMD data. + */ + bzero(data_cmd->buf, MCLBYTES); + + mem = (struct upgt_lmac_mem *)data_cmd->buf; + mem->addr = htole32(sc->sc_memaddr_frame_start + + UPGT_MEMSIZE_FRAME_HEAD); + + filter = (struct upgt_lmac_filter *)(mem + 1); + + filter->header1.flags = UPGT_H1_FLAGS_TX_NO_CALLBACK; + filter->header1.type = UPGT_H1_TYPE_CTRL; + filter->header1.len = htole16( + sizeof(struct upgt_lmac_filter) - + sizeof(struct upgt_lmac_header)); + + filter->header2.reqid = htole32(sc->sc_memaddr_frame_start); + filter->header2.type = htole16(UPGT_H2_TYPE_MACFILTER); + filter->header2.flags = 0; + + switch (state) { + case IEEE80211_S_INIT: + DPRINTF(sc, UPGT_DEBUG_STATE, "%s: set MAC filter to INIT\n", + __func__); + filter->type = htole16(UPGT_FILTER_TYPE_RESET); + break; + case IEEE80211_S_SCAN: + DPRINTF(sc, UPGT_DEBUG_STATE, + "set MAC filter to SCAN (bssid %s)\n", + ether_sprintf(broadcast)); + filter->type = htole16(UPGT_FILTER_TYPE_NONE); + IEEE80211_ADDR_COPY(filter->dst, ic->ic_myaddr); + IEEE80211_ADDR_COPY(filter->src, broadcast); + filter->unknown1 = htole16(UPGT_FILTER_UNKNOWN1); + filter->rxaddr = htole32(sc->sc_memaddr_rx_start); + filter->unknown2 = htole16(UPGT_FILTER_UNKNOWN2); + filter->rxhw = htole32(sc->sc_eeprom_hwrx); + filter->unknown3 = htole16(UPGT_FILTER_UNKNOWN3); + break; + case IEEE80211_S_RUN: + /* XXX monitor mode isn't tested yet. */ + if (vap->iv_opmode == IEEE80211_M_MONITOR) { + filter->type = htole16(UPGT_FILTER_TYPE_MONITOR); + IEEE80211_ADDR_COPY(filter->dst, ic->ic_myaddr); + IEEE80211_ADDR_COPY(filter->src, ni->ni_bssid); + filter->unknown1 = htole16(UPGT_FILTER_MONITOR_UNKNOWN1); + filter->rxaddr = htole32(sc->sc_memaddr_rx_start); + filter->unknown2 = htole16(UPGT_FILTER_MONITOR_UNKNOWN2); + filter->rxhw = htole32(sc->sc_eeprom_hwrx); + filter->unknown3 = htole16(UPGT_FILTER_MONITOR_UNKNOWN3); + } else { + DPRINTF(sc, UPGT_DEBUG_STATE, + "set MAC filter to RUN (bssid %s)\n", + ether_sprintf(ni->ni_bssid)); + filter->type = htole16(UPGT_FILTER_TYPE_STA); + IEEE80211_ADDR_COPY(filter->dst, ic->ic_myaddr); + IEEE80211_ADDR_COPY(filter->src, ni->ni_bssid); + filter->unknown1 = htole16(UPGT_FILTER_UNKNOWN1); + filter->rxaddr = htole32(sc->sc_memaddr_rx_start); + filter->unknown2 = htole16(UPGT_FILTER_UNKNOWN2); + filter->rxhw = htole32(sc->sc_eeprom_hwrx); + filter->unknown3 = htole16(UPGT_FILTER_UNKNOWN3); + } + break; + default: + device_printf(sc->sc_dev, + "MAC filter does not know that state!\n"); + break; + } + + len = sizeof(*mem) + sizeof(*filter); + + mem->chksum = upgt_chksum_le((uint32_t *)filter, + len - sizeof(*mem)); + + UPGT_UNLOCK(sc); + if (upgt_bulk_xmit(sc, data_cmd, sc->sc_tx_pipeh, &len, 0) != 0) { + device_printf(sc->sc_dev, + "could not transmit macfilter CMD data URB!\n"); + UPGT_LOCK(sc); + return (EIO); + } + UPGT_LOCK(sc); + + return (0); +} + +static void +upgt_setup_rates(struct ieee80211vap *vap, struct ieee80211com *ic) +{ + struct ifnet *ifp = ic->ic_ifp; + struct upgt_softc *sc = ifp->if_softc; + const struct ieee80211_txparam *tp; + + /* + * 0x01 = OFMD6 0x10 = DS1 + * 0x04 = OFDM9 0x11 = DS2 + * 0x06 = OFDM12 0x12 = DS5 + * 0x07 = OFDM18 0x13 = DS11 + * 0x08 = OFDM24 + * 0x09 = OFDM36 + * 0x0a = OFDM48 + * 0x0b = OFDM54 + */ + const uint8_t rateset_auto_11b[] = + { 0x13, 0x13, 0x12, 0x11, 0x11, 0x10, 0x10, 0x10 }; + const uint8_t rateset_auto_11g[] = + { 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x04, 0x01 }; + const uint8_t rateset_fix_11bg[] = + { 0x10, 0x11, 0x12, 0x13, 0x01, 0x04, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b }; + + tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)]; + + /* XXX */ + if (tp->ucastrate == IEEE80211_FIXED_RATE_NONE) { + /* + * Automatic rate control is done by the device. + * We just pass the rateset from which the device + * will pickup a rate. + */ + if (ic->ic_curmode == IEEE80211_MODE_11B) + bcopy(rateset_auto_11b, sc->sc_cur_rateset, + sizeof(sc->sc_cur_rateset)); + if (ic->ic_curmode == IEEE80211_MODE_11G || + ic->ic_curmode == IEEE80211_MODE_AUTO) + bcopy(rateset_auto_11g, sc->sc_cur_rateset, + sizeof(sc->sc_cur_rateset)); + } else { + /* set a fixed rate */ + memset(sc->sc_cur_rateset, rateset_fix_11bg[tp->ucastrate], + sizeof(sc->sc_cur_rateset)); + } +} + +static void +upgt_set_multi(void *arg) +{ + struct upgt_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + + if (!(ifp->if_flags & IFF_UP)) + return; + + /* + * XXX don't know how to set a device. Lack of docs. Just try to set + * IFF_ALLMULTI flag here. + */ + IF_ADDR_LOCK(ifp); + ifp->if_flags |= IFF_ALLMULTI; + IF_ADDR_UNLOCK(ifp); +} + +static void +upgt_start(struct ifnet *ifp) +{ + struct upgt_softc *sc = ifp->if_softc; + struct upgt_data *data_tx; + struct ieee80211_node *ni; + struct mbuf *m; + int i; + + UPGT_LOCK(sc); + for (i = 0; i < upgt_txbuf; i++) { + data_tx = &sc->tx_data[i]; + if (data_tx->use == 1) + continue; + + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + if (m == NULL) + break; + + ni = (struct ieee80211_node *)m->m_pkthdr.rcvif; + m = ieee80211_encap(ni, m); + if (m == NULL) { + ieee80211_free_node(ni); + ifp->if_oerrors++; + continue; + } + + if ((data_tx->addr = upgt_mem_alloc(sc)) == 0) { + device_printf(sc->sc_dev, "no free prism memory!\n"); + UPGT_UNLOCK(sc); + return; + } + data_tx->ni = ni; + data_tx->m = m; + data_tx->use = 1; + sc->tx_queued++; + } + + if (sc->tx_queued > 0) { + DPRINTF(sc, UPGT_DEBUG_XMIT, "tx_queued=%d\n", sc->tx_queued); + + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + sc->sc_tx_timer = 5; + callout_reset(&sc->sc_watchdog_ch, hz, upgt_watchdog, sc); + /* process the TX queue in process context */ + usb_rem_task(sc->sc_udev, &sc->sc_task_tx); + usb_add_task(sc->sc_udev, &sc->sc_task_tx, USB_TASKQ_DRIVER); + } + UPGT_UNLOCK(sc); +} + +static int +upgt_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, + const struct ieee80211_bpf_params *params) +{ + struct ieee80211com *ic = ni->ni_ic; + struct ifnet *ifp = ic->ic_ifp; + struct upgt_softc *sc = ifp->if_softc; + struct upgt_data *data_tx = NULL; + int i; + + /* prevent management frames from being sent if we're not ready */ + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + m_freem(m); + ieee80211_free_node(ni); + return ENETDOWN; + } + + UPGT_LOCK(sc); + if (sc->tx_queued >= upgt_txbuf) { + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + m_freem(m); + ieee80211_free_node(ni); + UPGT_UNLOCK(sc); + return ENOBUFS; /* XXX */ + } + + ifp->if_opackets++; + + /* choose a unused buffer. */ + for (i = 0; i < upgt_txbuf; i++) { + data_tx = &sc->tx_data[i]; + if (data_tx->use == 0) + break; + } + KASSERT(data_tx != NULL, ("data_tx is NULL")); + KASSERT(data_tx->use == 0, ("no empty TX queue")); + if ((data_tx->addr = upgt_mem_alloc(sc)) == 0) { + device_printf(sc->sc_dev, "no free prism memory!\n"); + UPGT_UNLOCK(sc); + return ENOBUFS; + } + + if (bpf_peers_present(ifp->if_bpf)) { + struct upgt_tx_radiotap_header *tap = &sc->sc_txtap; + + tap->wt_flags = 0; + tap->wt_rate = 0; /* TODO: where to get from? */ + tap->wt_chan_freq = htole16(ic->ic_curchan->ic_freq); + tap->wt_chan_flags = htole16(ic->ic_curchan->ic_flags); + + bpf_mtap2(ifp->if_bpf, tap, sc->sc_txtap_len, m); + } + + data_tx->ni = ni; + data_tx->m = m; + data_tx->use = 1; + sc->tx_queued++; + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + UPGT_UNLOCK(sc); + + sc->sc_tx_timer = 5; + callout_reset(&sc->sc_watchdog_ch, hz, upgt_watchdog, sc); + usb_rem_task(sc->sc_udev, &sc->sc_task_tx); + usb_add_task(sc->sc_udev, &sc->sc_task_tx, USB_TASKQ_DRIVER); + + return 0; +} + +static void +upgt_watchdog(void *arg) +{ + struct upgt_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + + if (sc->sc_tx_timer > 0) { + if (--sc->sc_tx_timer == 0) { + device_printf(sc->sc_dev, "watchdog timeout\n"); + /* upgt_init(ifp); XXX needs a process context ? */ + ifp->if_oerrors++; + return; + } + callout_reset(&sc->sc_watchdog_ch, hz, upgt_watchdog, sc); + } +} + +static uint32_t +upgt_mem_alloc(struct upgt_softc *sc) +{ + int i; + + for (i = 0; i < sc->sc_memory.pages; i++) { + if (sc->sc_memory.page[i].used == 0) { + sc->sc_memory.page[i].used = 1; + return (sc->sc_memory.page[i].addr); + } + } + + return (0); +} + +static void +upgt_scantask(void *arg) +{ + struct upgt_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + switch (sc->sc_scan_action) { + case UPGT_SET_CHANNEL: + upgt_set_chan(sc, ic->ic_curchan); + break; + default: + device_printf(sc->sc_dev, "unknown scan action %d\n", + sc->sc_scan_action); + break; + } +} + +static void +upgt_scan_start(struct ieee80211com *ic) +{ + /* do nothing. */ +} + +static void +upgt_scan_end(struct ieee80211com *ic) +{ + /* do nothing. */ +} + +static void +upgt_set_channel(struct ieee80211com *ic) +{ + struct upgt_softc *sc = ic->ic_ifp->if_softc; + + usb_rem_task(sc->sc_udev, &sc->sc_scantask); + + /* do it in a process context */ + sc->sc_scan_action = UPGT_SET_CHANNEL; + usb_add_task(sc->sc_udev, &sc->sc_scantask, USB_TASKQ_DRIVER); +} + +static void +upgt_set_chan(struct upgt_softc *sc, struct ieee80211_channel *c) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct upgt_data *data_cmd = &sc->cmd_data; + struct upgt_lmac_mem *mem; + struct upgt_lmac_channel *chan; + int len, channel; + + channel = ieee80211_chan2ieee(ic, c); + if (channel == 0 || channel == IEEE80211_CHAN_ANY) { + /* XXX should NEVER happen */ + device_printf(sc->sc_dev, + "%s: invalid channel %x\n", __func__, channel); + return; + } + + DPRINTF(sc, UPGT_DEBUG_STATE, "%s: channel %d\n", __func__, channel); + + /* + * Transmit the URB containing the CMD data. + */ + bzero(data_cmd->buf, MCLBYTES); + + mem = (struct upgt_lmac_mem *)data_cmd->buf; + mem->addr = htole32(sc->sc_memaddr_frame_start + + UPGT_MEMSIZE_FRAME_HEAD); + + chan = (struct upgt_lmac_channel *)(mem + 1); + + chan->header1.flags = UPGT_H1_FLAGS_TX_NO_CALLBACK; + chan->header1.type = UPGT_H1_TYPE_CTRL; + chan->header1.len = htole16( + sizeof(struct upgt_lmac_channel) - sizeof(struct upgt_lmac_header)); + + chan->header2.reqid = htole32(sc->sc_memaddr_frame_start); + chan->header2.type = htole16(UPGT_H2_TYPE_CHANNEL); + chan->header2.flags = 0; + + chan->unknown1 = htole16(UPGT_CHANNEL_UNKNOWN1); + chan->unknown2 = htole16(UPGT_CHANNEL_UNKNOWN2); + chan->freq6 = sc->sc_eeprom_freq6[channel]; + chan->settings = sc->sc_eeprom_freq6_settings; + chan->unknown3 = UPGT_CHANNEL_UNKNOWN3; + + bcopy(&sc->sc_eeprom_freq3[channel].data, chan->freq3_1, + sizeof(chan->freq3_1)); + bcopy(&sc->sc_eeprom_freq4[channel], chan->freq4, + sizeof(sc->sc_eeprom_freq4[channel])); + bcopy(&sc->sc_eeprom_freq3[channel].data, chan->freq3_2, + sizeof(chan->freq3_2)); + + len = sizeof(*mem) + sizeof(*chan); + + mem->chksum = upgt_chksum_le((uint32_t *)chan, + len - sizeof(*mem)); + + if (upgt_bulk_xmit(sc, data_cmd, sc->sc_tx_pipeh, &len, 0) != 0) + device_printf(sc->sc_dev, + "could not transmit channel CMD data URB!\n"); +} + +static struct ieee80211vap * +upgt_vap_create(struct ieee80211com *ic, + const char name[IFNAMSIZ], int unit, int opmode, int flags, + const uint8_t bssid[IEEE80211_ADDR_LEN], + const uint8_t mac[IEEE80211_ADDR_LEN]) +{ + struct upgt_vap *uvp; + struct ieee80211vap *vap; + + if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */ + return NULL; + uvp = (struct upgt_vap *) malloc(sizeof(struct upgt_vap), + M_80211_VAP, M_NOWAIT | M_ZERO); + if (uvp == NULL) + return NULL; + vap = &uvp->vap; + /* enable s/w bmiss handling for sta mode */ + ieee80211_vap_setup(ic, vap, name, unit, opmode, + flags | IEEE80211_CLONE_NOBEACONS, bssid, mac); + + /* override state transition machine */ + uvp->newstate = vap->iv_newstate; + vap->iv_newstate = upgt_newstate; + + /* setup device rates */ + upgt_setup_rates(vap, ic); + + /* complete setup */ + ieee80211_vap_attach(vap, ieee80211_media_change, + ieee80211_media_status); + ic->ic_opmode = opmode; + return vap; +} + +static int +upgt_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) +{ + struct upgt_vap *uvp = UPGT_VAP(vap); + struct ieee80211com *ic = vap->iv_ic; + struct upgt_softc *sc = ic->ic_ifp->if_softc; + + usb_rem_task(sc->sc_udev, &sc->sc_task); + + /* do it in a process context */ + sc->sc_state = nstate; + sc->sc_arg = arg; + + if (nstate == IEEE80211_S_INIT) { + uvp->newstate(vap, nstate, arg); + return 0; + } else { + usb_add_task(sc->sc_udev, &sc->sc_task, USB_TASKQ_DRIVER); + return EINPROGRESS; + } +} + +static void +upgt_vap_delete(struct ieee80211vap *vap) +{ + struct upgt_vap *uvp = UPGT_VAP(vap); + + ieee80211_vap_detach(vap); + free(uvp, M_80211_VAP); +} + +static void +upgt_update_mcast(struct ifnet *ifp) +{ + struct upgt_softc *sc = ifp->if_softc; + + usb_add_task(sc->sc_udev, &sc->sc_mcasttask, USB_TASKQ_DRIVER); +} + +static int +upgt_eeprom_parse(struct upgt_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct upgt_eeprom_header *eeprom_header; + struct upgt_eeprom_option *eeprom_option; + uint16_t option_len; + uint16_t option_type; + uint16_t preamble_len; + int option_end = 0; + + /* calculate eeprom options start offset */ + eeprom_header = (struct upgt_eeprom_header *)sc->sc_eeprom; + preamble_len = le16toh(eeprom_header->preamble_len); + eeprom_option = (struct upgt_eeprom_option *)(sc->sc_eeprom + + (sizeof(struct upgt_eeprom_header) + preamble_len)); + + while (!option_end) { + /* the eeprom option length is stored in words */ + option_len = + (le16toh(eeprom_option->len) - 1) * sizeof(uint16_t); + option_type = + le16toh(eeprom_option->type); + + switch (option_type) { + case UPGT_EEPROM_TYPE_NAME: + DPRINTF(sc, UPGT_DEBUG_FW, + "EEPROM name len=%d\n", option_len); + break; + case UPGT_EEPROM_TYPE_SERIAL: + DPRINTF(sc, UPGT_DEBUG_FW, + "EEPROM serial len=%d\n", option_len); + break; + case UPGT_EEPROM_TYPE_MAC: + DPRINTF(sc, UPGT_DEBUG_FW, + "EEPROM mac len=%d\n", option_len); + + IEEE80211_ADDR_COPY(ic->ic_myaddr, eeprom_option->data); + break; + case UPGT_EEPROM_TYPE_HWRX: + DPRINTF(sc, UPGT_DEBUG_FW, + "EEPROM hwrx len=%d\n", option_len); + + upgt_eeprom_parse_hwrx(sc, eeprom_option->data); + break; + case UPGT_EEPROM_TYPE_CHIP: + DPRINTF(sc, UPGT_DEBUG_FW, + "EEPROM chip len=%d\n", option_len); + break; + case UPGT_EEPROM_TYPE_FREQ3: + DPRINTF(sc, UPGT_DEBUG_FW, + "EEPROM freq3 len=%d\n", option_len); + + upgt_eeprom_parse_freq3(sc, eeprom_option->data, + option_len); + break; + case UPGT_EEPROM_TYPE_FREQ4: + DPRINTF(sc, UPGT_DEBUG_FW, + "EEPROM freq4 len=%d\n", option_len); + + upgt_eeprom_parse_freq4(sc, eeprom_option->data, + option_len); + break; + case UPGT_EEPROM_TYPE_FREQ5: + DPRINTF(sc, UPGT_DEBUG_FW, + "EEPROM freq5 len=%d\n", option_len); + break; + case UPGT_EEPROM_TYPE_FREQ6: + DPRINTF(sc, UPGT_DEBUG_FW, + "EEPROM freq6 len=%d\n", option_len); + + upgt_eeprom_parse_freq6(sc, eeprom_option->data, + option_len); + break; + case UPGT_EEPROM_TYPE_END: + DPRINTF(sc, UPGT_DEBUG_FW, + "EEPROM end len=%d\n", option_len); + option_end = 1; + break; + case UPGT_EEPROM_TYPE_OFF: + DPRINTF(sc, UPGT_DEBUG_FW, + "%s: EEPROM off without end option!\n", __func__); + return (EIO); + default: + DPRINTF(sc, UPGT_DEBUG_FW, + "EEPROM unknown type 0x%04x len=%d\n", + option_type, option_len); + break; + } + + /* jump to next EEPROM option */ + eeprom_option = (struct upgt_eeprom_option *) + (eeprom_option->data + option_len); + } + + return (0); +} + +static void +upgt_eeprom_parse_freq3(struct upgt_softc *sc, uint8_t *data, int len) +{ + struct upgt_eeprom_freq3_header *freq3_header; + struct upgt_lmac_freq3 *freq3; + int i, elements, flags; + unsigned channel; + + freq3_header = (struct upgt_eeprom_freq3_header *)data; + freq3 = (struct upgt_lmac_freq3 *)(freq3_header + 1); + + flags = freq3_header->flags; + elements = freq3_header->elements; + + DPRINTF(sc, UPGT_DEBUG_FW, "flags=0x%02x elements=%d\n", + flags, elements); + + for (i = 0; i < elements; i++) { + channel = ieee80211_mhz2ieee(le16toh(freq3[i].freq), 0); + if (!(channel >= 0 && channel < IEEE80211_CHAN_MAX)) + continue; + + sc->sc_eeprom_freq3[channel] = freq3[i]; + + DPRINTF(sc, UPGT_DEBUG_FW, "frequence=%d, channel=%d\n", + le16toh(sc->sc_eeprom_freq3[channel].freq), channel); + } +} + +void +upgt_eeprom_parse_freq4(struct upgt_softc *sc, uint8_t *data, int len) +{ + struct upgt_eeprom_freq4_header *freq4_header; + struct upgt_eeprom_freq4_1 *freq4_1; + struct upgt_eeprom_freq4_2 *freq4_2; + int i, j, elements, settings, flags; + unsigned channel; + + freq4_header = (struct upgt_eeprom_freq4_header *)data; + freq4_1 = (struct upgt_eeprom_freq4_1 *)(freq4_header + 1); + flags = freq4_header->flags; + elements = freq4_header->elements; + settings = freq4_header->settings; + + /* we need this value later */ + sc->sc_eeprom_freq6_settings = freq4_header->settings; + + DPRINTF(sc, UPGT_DEBUG_FW, "flags=0x%02x elements=%d settings=%d\n", + flags, elements, settings); + + for (i = 0; i < elements; i++) { + channel = ieee80211_mhz2ieee(le16toh(freq4_1[i].freq), 0); + if (!(channel >= 0 && channel < IEEE80211_CHAN_MAX)) + continue; + + freq4_2 = (struct upgt_eeprom_freq4_2 *)freq4_1[i].data; + for (j = 0; j < settings; j++) { + sc->sc_eeprom_freq4[channel][j].cmd = freq4_2[j]; + sc->sc_eeprom_freq4[channel][j].pad = 0; + } + + DPRINTF(sc, UPGT_DEBUG_FW, "frequence=%d, channel=%d\n", + le16toh(freq4_1[i].freq), channel); + } +} + +void +upgt_eeprom_parse_freq6(struct upgt_softc *sc, uint8_t *data, int len) +{ + struct upgt_lmac_freq6 *freq6; + int i, elements; + unsigned channel; + + freq6 = (struct upgt_lmac_freq6 *)data; + elements = len / sizeof(struct upgt_lmac_freq6); + + DPRINTF(sc, UPGT_DEBUG_FW, "elements=%d\n", elements); + + for (i = 0; i < elements; i++) { + channel = ieee80211_mhz2ieee(le16toh(freq6[i].freq), 0); + if (!(channel >= 0 && channel < IEEE80211_CHAN_MAX)) + continue; + + sc->sc_eeprom_freq6[channel] = freq6[i]; + + DPRINTF(sc, UPGT_DEBUG_FW, "frequence=%d, channel=%d\n", + le16toh(sc->sc_eeprom_freq6[channel].freq), channel); + } +} + +static void +upgt_eeprom_parse_hwrx(struct upgt_softc *sc, uint8_t *data) +{ + struct upgt_eeprom_option_hwrx *option_hwrx; + + option_hwrx = (struct upgt_eeprom_option_hwrx *)data; + + sc->sc_eeprom_hwrx = option_hwrx->rxfilter - UPGT_EEPROM_RX_CONST; + + DPRINTF(sc, UPGT_DEBUG_FW, "hwrx option value=0x%04x\n", + sc->sc_eeprom_hwrx); +} + +static int +upgt_eeprom_read(struct upgt_softc *sc) +{ + struct upgt_data *data_cmd = &sc->cmd_data; + struct upgt_lmac_mem *mem; + struct upgt_lmac_eeprom *eeprom; + int offset, block, len; + + offset = 0; + block = UPGT_EEPROM_BLOCK_SIZE; + while (offset < UPGT_EEPROM_SIZE) { + DPRINTF(sc, UPGT_DEBUG_FW, + "request EEPROM block (offset=%d, len=%d)\n", offset, block); + + /* + * Transmit the URB containing the CMD data. + */ + bzero(data_cmd->buf, MCLBYTES); + + mem = (struct upgt_lmac_mem *)data_cmd->buf; + mem->addr = htole32(sc->sc_memaddr_frame_start + + UPGT_MEMSIZE_FRAME_HEAD); + + eeprom = (struct upgt_lmac_eeprom *)(mem + 1); + eeprom->header1.flags = 0; + eeprom->header1.type = UPGT_H1_TYPE_CTRL; + eeprom->header1.len = htole16(( + sizeof(struct upgt_lmac_eeprom) - + sizeof(struct upgt_lmac_header)) + block); + + eeprom->header2.reqid = htole32(sc->sc_memaddr_frame_start); + eeprom->header2.type = htole16(UPGT_H2_TYPE_EEPROM); + eeprom->header2.flags = 0; + + eeprom->offset = htole16(offset); + eeprom->len = htole16(block); + + len = sizeof(*mem) + sizeof(*eeprom) + block; + + mem->chksum = upgt_chksum_le((uint32_t *)eeprom, + len - sizeof(*mem)); + + if (upgt_bulk_xmit(sc, data_cmd, sc->sc_tx_pipeh, &len, + USBD_FORCE_SHORT_XFER) != 0) { + device_printf(sc->sc_dev, + "could not transmit EEPROM data URB!\n"); + return (EIO); + } + if (tsleep(sc, 0, "eeprom_request", UPGT_USB_TIMEOUT)) { + device_printf(sc->sc_dev, + "timeout while waiting for EEPROM data!\n"); + return (EIO); + } + + offset += block; + if (UPGT_EEPROM_SIZE - offset < block) + block = UPGT_EEPROM_SIZE - offset; + } + + return (0); +} + +/* + * The firmware awaits a checksum for each frame we send to it. + * The algorithm used therefor is uncommon but somehow similar to CRC32. + */ +static uint32_t +upgt_chksum_le(const uint32_t *buf, size_t size) +{ + int i; + uint32_t crc = 0; + + for (i = 0; i < size; i += sizeof(uint32_t)) { + crc = htole32(crc ^ *buf++); + crc = htole32((crc >> 5) ^ (crc << 3)); + } + + return (crc); +} + +static void +upgt_rxeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + struct upgt_data *data_rx = priv; + struct upgt_softc *sc = data_rx->sc; + int len; + struct upgt_lmac_header *header; + struct upgt_lmac_eeprom *eeprom; + uint8_t h1_type; + uint16_t h2_type; + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) + return; + if (status == USBD_STALLED) + usbd_clear_endpoint_stall_async(sc->sc_rx_pipeh); + goto skip; + } + usbd_get_xfer_status(xfer, NULL, NULL, &len, NULL); + + /* + * Check what type of frame came in. + */ + header = (struct upgt_lmac_header *)(data_rx->buf + 4); + + h1_type = header->header1.type; + h2_type = le16toh(header->header2.type); + + if (h1_type == UPGT_H1_TYPE_CTRL && h2_type == UPGT_H2_TYPE_EEPROM) { + eeprom = (struct upgt_lmac_eeprom *)(data_rx->buf + 4); + uint16_t eeprom_offset = le16toh(eeprom->offset); + uint16_t eeprom_len = le16toh(eeprom->len); + + DPRINTF(sc, UPGT_DEBUG_FW, + "received EEPROM block (offset=%d, len=%d)\n", + eeprom_offset, eeprom_len); + + bcopy(data_rx->buf + sizeof(struct upgt_lmac_eeprom) + 4, + sc->sc_eeprom + eeprom_offset, eeprom_len); + + /* EEPROM data has arrived in time, wakeup tsleep() */ + wakeup(sc); + } else if (h1_type == UPGT_H1_TYPE_CTRL && + h2_type == UPGT_H2_TYPE_TX_DONE) { + DPRINTF(sc, UPGT_DEBUG_XMIT, "%s: received 802.11 TX done\n", + __func__); + upgt_tx_done(sc, data_rx->buf + 4); + } else if (h1_type == UPGT_H1_TYPE_RX_DATA || + h1_type == UPGT_H1_TYPE_RX_DATA_MGMT) { + DPRINTF(sc, UPGT_DEBUG_RECV, "%s: received 802.11 RX data\n", + __func__); + upgt_rx(sc, data_rx->buf + 4, le16toh(header->header1.len)); + } else if (h1_type == UPGT_H1_TYPE_CTRL && + h2_type == UPGT_H2_TYPE_STATS) { + DPRINTF(sc, UPGT_DEBUG_STAT, "%s: received statistic data\n", + __func__); + /* TODO: what could we do with the statistic data? */ + } else { + /* ignore unknown frame types */ + DPRINTF(sc, UPGT_DEBUG_INTR, + "received unknown frame type 0x%02x\n", + header->header1.type); + } + +skip: /* setup new transfer */ + usbd_setup_xfer(xfer, sc->sc_rx_pipeh, data_rx, data_rx->buf, MCLBYTES, + USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT, upgt_rxeof); + (void)usbd_transfer(xfer); +} + +static void +upgt_rx(struct upgt_softc *sc, uint8_t *data, int pkglen) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct upgt_lmac_rx_desc *rxdesc; + struct ieee80211_node *ni; + struct mbuf *m; + int nf; + + /* + * don't pass packets to the ieee80211 framework if the driver isn't + * RUNNING. + */ + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) + return; + + /* access RX packet descriptor */ + rxdesc = (struct upgt_lmac_rx_desc *)data; + + /* create mbuf which is suitable for strict alignment archs */ + KASSERT((pkglen + ETHER_ALIGN) < MCLBYTES, + ("A current mbuf storage is small (%d)", pkglen + ETHER_ALIGN)); + m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + if (m == NULL) { + device_printf(sc->sc_dev, "could not create RX mbuf!\n"); + return; + } + m_adj(m, ETHER_ALIGN); + bcopy(rxdesc->data, mtod(m, char *), pkglen); + /* trim FCS */ + m->m_len = m->m_pkthdr.len = pkglen - IEEE80211_CRC_LEN; + m->m_pkthdr.rcvif = ifp; + + if (bpf_peers_present(ifp->if_bpf)) { + struct upgt_rx_radiotap_header *tap = &sc->sc_rxtap; + + tap->wr_flags = 0; + tap->wr_rate = upgt_rx_rate(sc, rxdesc->rate); + tap->wr_chan_freq = htole16(ic->ic_curchan->ic_freq); + tap->wr_chan_flags = htole16(ic->ic_curchan->ic_flags); + tap->wr_antsignal = rxdesc->rssi; + + bpf_mtap2(ifp->if_bpf, tap, sc->sc_rxtap_len, m); + } + ifp->if_ipackets++; + + nf = -95; /* XXX */ + ni = ieee80211_find_rxnode(ic, mtod(m, struct ieee80211_frame_min *)); + if (ni != NULL) { + (void)ieee80211_input(ni, m, rxdesc->rssi, nf, 0); + ieee80211_free_node(ni); + } else + (void)ieee80211_input_all(ic, m, rxdesc->rssi, nf, 0); + + DPRINTF(sc, UPGT_DEBUG_RX_PROC, "%s: RX done\n", __func__); +} + +static uint8_t +upgt_rx_rate(struct upgt_softc *sc, const int rate) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + static const uint8_t cck_upgt2rate[4] = { 2, 4, 11, 22 }; + static const uint8_t ofdm_upgt2rate[12] = + { 2, 4, 11, 22, 12, 18, 24, 36, 48, 72, 96, 108 }; + + if (ic->ic_curmode == IEEE80211_MODE_11B && + !(rate < 0 || rate > 3)) + return cck_upgt2rate[rate & 0xf]; + + if (ic->ic_curmode == IEEE80211_MODE_11G && + !(rate < 0 || rate > 11)) + return ofdm_upgt2rate[rate & 0xf]; + + return (0); +} + +static void +upgt_tx_done(struct upgt_softc *sc, uint8_t *data) +{ + struct ifnet *ifp = sc->sc_ifp; + struct upgt_lmac_tx_done_desc *desc; + int i; + + desc = (struct upgt_lmac_tx_done_desc *)data; + + UPGT_LOCK(sc); + for (i = 0; i < upgt_txbuf; i++) { + struct upgt_data *data_tx = &sc->tx_data[i]; + + if (data_tx->addr == le32toh(desc->header2.reqid)) { + upgt_mem_free(sc, data_tx->addr); + ieee80211_free_node(data_tx->ni); + data_tx->ni = NULL; + data_tx->addr = 0; + data_tx->m = NULL; + data_tx->use = 0; + + sc->tx_queued--; + ifp->if_opackets++; + + DPRINTF(sc, UPGT_DEBUG_TX_PROC, + "TX done: memaddr=0x%08x, status=0x%04x, rssi=%d, ", + le32toh(desc->header2.reqid), + le16toh(desc->status), le16toh(desc->rssi)); + DPRINTF(sc, UPGT_DEBUG_TX_PROC, "seq=%d\n", + le16toh(desc->seq)); + break; + } + } + if (sc->tx_queued == 0) { + /* TX queued was processed, continue */ + sc->sc_tx_timer = 0; + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + UPGT_UNLOCK(sc); + upgt_start(ifp); + return; + } + UPGT_UNLOCK(sc); +} + +static void +upgt_mem_free(struct upgt_softc *sc, uint32_t addr) +{ + int i; + + for (i = 0; i < sc->sc_memory.pages; i++) { + if (sc->sc_memory.page[i].addr == addr) { + sc->sc_memory.page[i].used = 0; + return; + } + } + + device_printf(sc->sc_dev, + "could not free memory address 0x%08x!\n", addr); +} + +static int +upgt_fw_load(struct upgt_softc *sc) +{ + const struct firmware *fw; + struct upgt_data *data_cmd = &sc->cmd_data; + struct upgt_data *data_rx = &sc->rx_data; + struct upgt_fw_x2_header *x2; + char start_fwload_cmd[] = { 0x3c, 0x0d }; + int error = 0, offset, bsize, n, i, len; + uint32_t crc32; + + fw = firmware_get(upgt_fwname); + if (fw == NULL) { + device_printf(sc->sc_dev, "could not read microcode %s!\n", + upgt_fwname); + return EIO; + } + + /* send firmware start load command */ + len = sizeof(start_fwload_cmd); + bcopy(start_fwload_cmd, data_cmd->buf, len); + if (upgt_bulk_xmit(sc, data_cmd, sc->sc_tx_pipeh, &len, 0) != 0) { + device_printf(sc->sc_dev, + "could not send start_firmware_load command!\n"); + error = EIO; + goto fail; + } + + /* send X2 header */ + len = sizeof(struct upgt_fw_x2_header); + x2 = (struct upgt_fw_x2_header *)data_cmd->buf; + bcopy(UPGT_X2_SIGNATURE, x2->signature, UPGT_X2_SIGNATURE_SIZE); + x2->startaddr = htole32(UPGT_MEMADDR_FIRMWARE_START); + x2->len = htole32(fw->datasize); + x2->crc = upgt_crc32_le((uint8_t *)data_cmd->buf + + UPGT_X2_SIGNATURE_SIZE, + sizeof(struct upgt_fw_x2_header) - UPGT_X2_SIGNATURE_SIZE - + sizeof(uint32_t)); + if (upgt_bulk_xmit(sc, data_cmd, sc->sc_tx_pipeh, &len, 0) != 0) { + device_printf(sc->sc_dev, + "could not send firmware X2 header!\n"); + error = EIO; + goto fail; + } + + /* download firmware */ + for (offset = 0; offset < fw->datasize; offset += bsize) { + if (fw->datasize - offset > UPGT_FW_BLOCK_SIZE) + bsize = UPGT_FW_BLOCK_SIZE; + else + bsize = fw->datasize - offset; + + n = upgt_fw_copy((const uint8_t *)fw->data + offset, + data_cmd->buf, bsize); + + DPRINTF(sc, UPGT_DEBUG_FW, "FW offset=%d, read=%d, sent=%d\n", + offset, n, bsize); + + if (upgt_bulk_xmit(sc, data_cmd, sc->sc_tx_pipeh, &bsize, 0) + != 0) { + device_printf(sc->sc_dev, + "error while downloading firmware block!\n"); + error = EIO; + goto fail; + } + + bsize = n; + } + DPRINTF(sc, UPGT_DEBUG_FW, "%s: firmware downloaded\n", __func__); + + /* load firmware */ + crc32 = upgt_crc32_le(fw->data, fw->datasize); + *((uint32_t *)(data_cmd->buf) ) = crc32; + *((uint8_t *)(data_cmd->buf) + 4) = 'g'; + *((uint8_t *)(data_cmd->buf) + 5) = '\r'; + len = 6; + if (upgt_bulk_xmit(sc, data_cmd, sc->sc_tx_pipeh, &len, 0) != 0) { + device_printf(sc->sc_dev, + "could not send load_firmware command!\n"); + error = EIO; + goto fail; + } + + for (i = 0; i < UPGT_FIRMWARE_TIMEOUT; i++) { + len = UPGT_FW_BLOCK_SIZE; + bzero(data_rx->buf, MCLBYTES); + if (upgt_bulk_xmit(sc, data_rx, sc->sc_rx_pipeh, &len, + USBD_SHORT_XFER_OK) != 0) { + device_printf(sc->sc_dev, + "could not read firmware response!\n"); + error = EIO; + goto fail; + } + + if (memcmp(data_rx->buf, "OK", 2) == 0) + break; /* firmware load was successful */ + } + if (i == UPGT_FIRMWARE_TIMEOUT) { + device_printf(sc->sc_dev, "firmware load failed!\n"); + error = EIO; + } + + DPRINTF(sc, UPGT_DEBUG_FW, "%s: firmware loaded\n", __func__); +fail: + firmware_put(fw, FIRMWARE_UNLOAD); + return (error); +} + +static uint32_t +upgt_crc32_le(const void *buf, size_t size) +{ + uint32_t crc; + + crc = ether_crc32_le(buf, size); + + /* apply final XOR value as common for CRC-32 */ + crc = htole32(crc ^ 0xffffffffU); + + return (crc); +} + +/* + * While copying the version 2 firmware, we need to replace two characters: + * + * 0x7e -> 0x7d 0x5e + * 0x7d -> 0x7d 0x5d + */ +static int +upgt_fw_copy(const uint8_t *src, char *dst, int size) +{ + int i, j; + + for (i = 0, j = 0; i < size && j < size; i++) { + switch (src[i]) { + case 0x7e: + dst[j] = 0x7d; + j++; + dst[j] = 0x5e; + j++; + break; + case 0x7d: + dst[j] = 0x7d; + j++; + dst[j] = 0x5d; + j++; + break; + default: + dst[j] = src[i]; + j++; + break; + } + } + + return (i); +} + +static int +upgt_mem_init(struct upgt_softc *sc) +{ + int i; + + for (i = 0; i < UPGT_MEMORY_MAX_PAGES; i++) { + sc->sc_memory.page[i].used = 0; + + if (i == 0) { + /* + * The first memory page is always reserved for + * command data. + */ + sc->sc_memory.page[i].addr = + sc->sc_memaddr_frame_start + MCLBYTES; + } else { + sc->sc_memory.page[i].addr = + sc->sc_memory.page[i - 1].addr + MCLBYTES; + } + + if (sc->sc_memory.page[i].addr + MCLBYTES >= + sc->sc_memaddr_frame_end) + break; + + DPRINTF(sc, UPGT_DEBUG_FW, "memory address page %d=0x%08x\n", + i, sc->sc_memory.page[i].addr); + } + + sc->sc_memory.pages = i; + if (upgt_txbuf > sc->sc_memory.pages) + + DPRINTF(sc, UPGT_DEBUG_FW, "memory pages=%d\n", sc->sc_memory.pages); + return (0); +} + +static int +upgt_fw_verify(struct upgt_softc *sc) +{ + const struct firmware *fw; + const struct upgt_fw_bra_option *bra_opt; + const struct upgt_fw_bra_descr *descr; + const uint8_t *p; + const uint32_t *uc; + uint32_t bra_option_type, bra_option_len; + int offset, bra_end = 0, error = 0; + + fw = firmware_get(upgt_fwname); + if (fw == NULL) { + device_printf(sc->sc_dev, "could not read microcode %s!\n", + upgt_fwname); + return EIO; + } + + /* + * Seek to beginning of Boot Record Area (BRA). + */ + for (offset = 0; offset < fw->datasize; offset += sizeof(*uc)) { + uc = (const uint32_t *)((const uint8_t *)fw->data + offset); + if (*uc == 0) + break; + } + for (; offset < fw->datasize; offset += sizeof(*uc)) { + uc = (const uint32_t *)((const uint8_t *)fw->data + offset); + if (*uc != 0) + break; + } + if (offset == fw->datasize) { + device_printf(sc->sc_dev, + "firmware Boot Record Area not found!\n"); + error = EIO; + goto fail; + } + + DPRINTF(sc, UPGT_DEBUG_FW, + "firmware Boot Record Area found at offset %d\n", offset); + + /* + * Parse Boot Record Area (BRA) options. + */ + while (offset < fw->datasize && bra_end == 0) { + /* get current BRA option */ + p = (const uint8_t *)fw->data + offset; + bra_opt = (const struct upgt_fw_bra_option *)p; + bra_option_type = le32toh(bra_opt->type); + bra_option_len = le32toh(bra_opt->len) * sizeof(*uc); + + switch (bra_option_type) { + case UPGT_BRA_TYPE_FW: + DPRINTF(sc, UPGT_DEBUG_FW, "UPGT_BRA_TYPE_FW len=%d\n", + bra_option_len); + + if (bra_option_len != UPGT_BRA_FWTYPE_SIZE) { + device_printf(sc->sc_dev, + "wrong UPGT_BRA_TYPE_FW len!\n"); + error = EIO; + goto fail; + } + if (memcmp(UPGT_BRA_FWTYPE_LM86, bra_opt->data, + bra_option_len) == 0) { + sc->sc_fw_type = UPGT_FWTYPE_LM86; + break; + } + if (memcmp(UPGT_BRA_FWTYPE_LM87, bra_opt->data, + bra_option_len) == 0) { + sc->sc_fw_type = UPGT_FWTYPE_LM87; + break; + } + device_printf(sc->sc_dev, + "unsupported firmware type!\n"); + error = EIO; + goto fail; + case UPGT_BRA_TYPE_VERSION: + DPRINTF(sc, UPGT_DEBUG_FW, + "UPGT_BRA_TYPE_VERSION len=%d\n", bra_option_len); + break; + case UPGT_BRA_TYPE_DEPIF: + DPRINTF(sc, UPGT_DEBUG_FW, + "UPGT_BRA_TYPE_DEPIF len=%d\n", bra_option_len); + break; + case UPGT_BRA_TYPE_EXPIF: + DPRINTF(sc, UPGT_DEBUG_FW, + "UPGT_BRA_TYPE_EXPIF len=%d\n", bra_option_len); + break; + case UPGT_BRA_TYPE_DESCR: + DPRINTF(sc, UPGT_DEBUG_FW, + "UPGT_BRA_TYPE_DESCR len=%d\n", bra_option_len); + + descr = (const struct upgt_fw_bra_descr *)bra_opt->data; + + sc->sc_memaddr_frame_start = + le32toh(descr->memaddr_space_start); + sc->sc_memaddr_frame_end = + le32toh(descr->memaddr_space_end); + + DPRINTF(sc, UPGT_DEBUG_FW, + "memory address space start=0x%08x\n", + sc->sc_memaddr_frame_start); + DPRINTF(sc, UPGT_DEBUG_FW, + "memory address space end=0x%08x\n", + sc->sc_memaddr_frame_end); + break; + case UPGT_BRA_TYPE_END: + DPRINTF(sc, UPGT_DEBUG_FW, "UPGT_BRA_TYPE_END len=%d\n", + bra_option_len); + bra_end = 1; + break; + default: + DPRINTF(sc, UPGT_DEBUG_FW, "unknown BRA option len=%d\n", + bra_option_len); + error = EIO; + goto fail; + } + + /* jump to next BRA option */ + offset += sizeof(struct upgt_fw_bra_option) + bra_option_len; + } + + DPRINTF(sc, UPGT_DEBUG_FW, "%s: firmware verified", __func__); +fail: + firmware_put(fw, FIRMWARE_UNLOAD); + return (error); +} + +static int +upgt_bulk_xmit(struct upgt_softc *sc, struct upgt_data *data, + usbd_pipe_handle pipeh, uint32_t *size, int flags) +{ + usbd_status status; + + mtx_lock(&Giant); + status = usbd_bulk_transfer(data->xfer, pipeh, + USBD_NO_COPY | flags, UPGT_USB_TIMEOUT, data->buf, size, + "upgt_bulk_xmit"); + if (status != USBD_NORMAL_COMPLETION) { + device_printf(sc->sc_dev, "%s: error %s!\n", + __func__, usbd_errstr(status)); + mtx_unlock(&Giant); + return (EIO); + } + mtx_unlock(&Giant); + + return (0); +} + +static int +upgt_device_reset(struct upgt_softc *sc) +{ + struct upgt_data *data_cmd = &sc->cmd_data; + char init_cmd[] = { 0x7e, 0x7e, 0x7e, 0x7e }; + int len; + + len = sizeof(init_cmd); + bcopy(init_cmd, data_cmd->buf, len); + if (upgt_bulk_xmit(sc, data_cmd, sc->sc_tx_pipeh, &len, 0) != 0) { + device_printf(sc->sc_dev, + "could not send device init string!\n"); + return (EIO); + } + usbd_delay_ms(sc->sc_udev, 100); + + DPRINTF(sc, UPGT_DEBUG_FW, "%s: device initialized\n", __func__); + return (0); +} + +static int +upgt_alloc_tx(struct upgt_softc *sc) +{ + int i; + + sc->tx_queued = 0; + + for (i = 0; i < upgt_txbuf; i++) { + struct upgt_data *data_tx = &sc->tx_data[i]; + + data_tx->sc = sc; + data_tx->xfer = usbd_alloc_xfer(sc->sc_udev); + if (data_tx->xfer == NULL) { + device_printf(sc->sc_dev, + "could not allocate TX xfer!\n"); + return (ENOMEM); + } + + data_tx->buf = usbd_alloc_buffer(data_tx->xfer, MCLBYTES); + if (data_tx->buf == NULL) { + device_printf(sc->sc_dev, + "could not allocate TX buffer!\n"); + return (ENOMEM); + } + + bzero(data_tx->buf, MCLBYTES); + } + + return (0); +} + +static int +upgt_alloc_rx(struct upgt_softc *sc) +{ + struct upgt_data *data_rx = &sc->rx_data; + + data_rx->sc = sc; + data_rx->xfer = usbd_alloc_xfer(sc->sc_udev); + if (data_rx->xfer == NULL) { + device_printf(sc->sc_dev, "could not allocate RX xfer!\n"); + return (ENOMEM); + } + + data_rx->buf = usbd_alloc_buffer(data_rx->xfer, MCLBYTES); + if (data_rx->buf == NULL) { + device_printf(sc->sc_dev, "could not allocate RX buffer!\n"); + return (ENOMEM); + } + + bzero(data_rx->buf, MCLBYTES); + + return (0); +} + +static int +upgt_alloc_cmd(struct upgt_softc *sc) +{ + struct upgt_data *data_cmd = &sc->cmd_data; + + data_cmd->sc = sc; + data_cmd->xfer = usbd_alloc_xfer(sc->sc_udev); + if (data_cmd->xfer == NULL) { + device_printf(sc->sc_dev, "could not allocate RX xfer!\n"); + return (ENOMEM); + } + + data_cmd->buf = usbd_alloc_buffer(data_cmd->xfer, MCLBYTES); + if (data_cmd->buf == NULL) { + device_printf(sc->sc_dev, "could not allocate RX buffer!\n"); + return (ENOMEM); + } + + bzero(data_cmd->buf, MCLBYTES); + + return (0); +} + +static int +upgt_detach(device_t dev) +{ + struct upgt_softc *sc = device_get_softc(dev); + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + if (!device_is_attached(dev)) + return 0; + + upgt_stop(sc, 1); + + /* abort and close TX / RX pipes */ + if (sc->sc_tx_pipeh != NULL) + usbd_close_pipe(sc->sc_tx_pipeh); + if (sc->sc_rx_pipeh != NULL) + usbd_close_pipe(sc->sc_rx_pipeh); + + mtx_destroy(&sc->sc_mtx); + usb_rem_task(sc->sc_udev, &sc->sc_mcasttask); + usb_rem_task(sc->sc_udev, &sc->sc_scantask); + usb_rem_task(sc->sc_udev, &sc->sc_task); + usb_rem_task(sc->sc_udev, &sc->sc_task_tx); + callout_stop(&sc->sc_led_ch); + callout_stop(&sc->sc_watchdog_ch); + + /* free xfers */ + upgt_free_tx(sc); + upgt_free_rx(sc); + upgt_free_cmd(sc); + + bpfdetach(ifp); + ieee80211_ifdetach(ic); + if_free(ifp); + + usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_udev, sc->sc_dev); + + return 0; +} + +static void +upgt_free_rx(struct upgt_softc *sc) +{ + struct upgt_data *data_rx = &sc->rx_data; + + if (data_rx->xfer != NULL) { + usbd_free_xfer(data_rx->xfer); + data_rx->xfer = NULL; + } + + data_rx->ni = NULL; +} + +static void +upgt_free_tx(struct upgt_softc *sc) +{ + int i; + + for (i = 0; i < upgt_txbuf; i++) { + struct upgt_data *data_tx = &sc->tx_data[i]; + + if (data_tx->xfer != NULL) { + usbd_free_xfer(data_tx->xfer); + data_tx->xfer = NULL; + } + + data_tx->ni = NULL; + } +} + +static void +upgt_free_cmd(struct upgt_softc *sc) +{ + struct upgt_data *data_cmd = &sc->cmd_data; + + if (data_cmd->xfer != NULL) { + usbd_free_xfer(data_cmd->xfer); + data_cmd->xfer = NULL; + } +} + +static device_method_t upgt_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, upgt_match), + DEVMETHOD(device_attach, upgt_attach), + DEVMETHOD(device_detach, upgt_detach), + + { 0, 0 } +}; + +static driver_t upgt_driver = { + "upgt", + upgt_methods, + sizeof(struct upgt_softc) +}; + +static devclass_t upgt_devclass; + +DRIVER_MODULE(if_upgt, uhub, upgt_driver, upgt_devclass, usbd_driver_load, 0); +MODULE_VERSION(if_upgt, 1); +MODULE_DEPEND(if_upgt, usb, 1, 1, 1); +MODULE_DEPEND(if_upgt, wlan, 1, 1, 1); +MODULE_DEPEND(if_upgt, upgtfw_fw, 1, 1, 1); diff --git a/sys/legacy/dev/usb/if_upgtvar.h b/sys/legacy/dev/usb/if_upgtvar.h new file mode 100644 index 0000000..298fb63 --- /dev/null +++ b/sys/legacy/dev/usb/if_upgtvar.h @@ -0,0 +1,462 @@ +/* $OpenBSD: if_upgtvar.h,v 1.14 2008/02/02 13:48:44 mglocker Exp $ */ +/* $FreeBSD$ */ + +/* + * Copyright (c) 2007 Marcus Glocker <mglocker@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +struct upgt_softc; + +/* + * General values. + */ +#define UPGT_IFACE_INDEX 0 +#define UPGT_CONFIG_NO 1 +#define UPGT_USB_TIMEOUT 1000 +#define UPGT_FIRMWARE_TIMEOUT 10 + +#define UPGT_MEMADDR_FIRMWARE_START 0x00020000 /* 512 bytes large */ +#define UPGT_MEMSIZE_FRAME_HEAD 0x0070 +#define UPGT_MEMSIZE_RX 0x3500 + +#define UPGT_TX_COUNT 2 + +/* device flags */ +#define UPGT_DEVICE_ATTACHED (1 << 0) + +/* leds */ +#define UPGT_LED_OFF 0 +#define UPGT_LED_ON 1 +#define UPGT_LED_BLINK 2 + +/* + * Firmware. + */ +#define UPGT_FW_BLOCK_SIZE 512 + +#define UPGT_BRA_FWTYPE_SIZE 4 +#define UPGT_BRA_FWTYPE_LM86 "LM86" +#define UPGT_BRA_FWTYPE_LM87 "LM87" +enum upgt_fw_type { + UPGT_FWTYPE_LM86, + UPGT_FWTYPE_LM87 +}; + +#define UPGT_BRA_TYPE_FW 0x80000001 +#define UPGT_BRA_TYPE_VERSION 0x80000002 +#define UPGT_BRA_TYPE_DEPIF 0x80000003 +#define UPGT_BRA_TYPE_EXPIF 0x80000004 +#define UPGT_BRA_TYPE_DESCR 0x80000101 +#define UPGT_BRA_TYPE_END 0xff0000ff +struct upgt_fw_bra_option { + uint32_t type; + uint32_t len; + uint8_t data[]; +} __packed; + +struct upgt_fw_bra_descr { + uint32_t unknown1; + uint32_t memaddr_space_start; + uint32_t memaddr_space_end; + uint32_t unknown2; + uint32_t unknown3; + uint8_t rates[20]; +} __packed; + +#define UPGT_X2_SIGNATURE_SIZE 4 +#define UPGT_X2_SIGNATURE "x2 " +struct upgt_fw_x2_header { + uint8_t signature[4]; + uint32_t startaddr; + uint32_t len; + uint32_t crc; +} __packed; + +/* + * EEPROM. + */ +#define UPGT_EEPROM_SIZE 8192 +#define UPGT_EEPROM_BLOCK_SIZE 1020 + +struct upgt_eeprom_header { + /* 14 bytes */ + uint32_t magic; + uint16_t pad1; + uint16_t preamble_len; + uint32_t pad2; + /* data */ +} __packed; + +#define UPGT_EEPROM_TYPE_END 0x0000 +#define UPGT_EEPROM_TYPE_NAME 0x0001 +#define UPGT_EEPROM_TYPE_SERIAL 0x0003 +#define UPGT_EEPROM_TYPE_MAC 0x0101 +#define UPGT_EEPROM_TYPE_HWRX 0x1001 +#define UPGT_EEPROM_TYPE_CHIP 0x1002 +#define UPGT_EEPROM_TYPE_FREQ3 0x1903 +#define UPGT_EEPROM_TYPE_FREQ4 0x1904 +#define UPGT_EEPROM_TYPE_FREQ5 0x1905 +#define UPGT_EEPROM_TYPE_FREQ6 0x1906 +#define UPGT_EEPROM_TYPE_OFF 0xffff +struct upgt_eeprom_option { + uint16_t len; + uint16_t type; + uint8_t data[]; + /* data */ +} __packed; + +#define UPGT_EEPROM_RX_CONST 0x88 +struct upgt_eeprom_option_hwrx { + uint32_t pad1; + uint8_t rxfilter; + uint8_t pad2[15]; +} __packed; + +struct upgt_eeprom_freq3_header { + uint8_t flags; + uint8_t elements; +} __packed; + +struct upgt_eeprom_freq4_header { + uint8_t flags; + uint8_t elements; + uint8_t settings; + uint8_t type; +} __packed; + +struct upgt_eeprom_freq4_1 { + uint16_t freq; + uint8_t data[50]; +} __packed; + +struct upgt_eeprom_freq4_2 { + uint16_t head; + uint8_t subtails[4]; + uint8_t tail; +} __packed; + +/* + * LMAC protocol. + */ +struct upgt_lmac_mem { + uint32_t addr; + uint32_t chksum; +} __packed; + +#define UPGT_H1_FLAGS_TX_MGMT 0x00 /* for TX: mgmt frame */ +#define UPGT_H1_FLAGS_TX_NO_CALLBACK 0x01 /* for TX: no USB callback */ +#define UPGT_H1_FLAGS_TX_DATA 0x10 /* for TX: data frame */ +#define UPGT_H1_TYPE_RX_DATA 0x00 /* 802.11 RX data frame */ +#define UPGT_H1_TYPE_RX_DATA_MGMT 0x04 /* 802.11 RX mgmt frame */ +#define UPGT_H1_TYPE_TX_DATA 0x40 /* 802.11 TX data frame */ +#define UPGT_H1_TYPE_CTRL 0x80 /* control frame */ +struct upgt_lmac_h1 { + /* 4 bytes */ + uint8_t flags; + uint8_t type; + uint16_t len; +} __packed; + +#define UPGT_H2_TYPE_TX_ACK_NO 0x0000 +#define UPGT_H2_TYPE_TX_ACK_YES 0x0001 +#define UPGT_H2_TYPE_MACFILTER 0x0000 +#define UPGT_H2_TYPE_CHANNEL 0x0001 +#define UPGT_H2_TYPE_TX_DONE 0x0008 +#define UPGT_H2_TYPE_STATS 0x000a +#define UPGT_H2_TYPE_EEPROM 0x000c +#define UPGT_H2_TYPE_LED 0x000d +#define UPGT_H2_FLAGS_TX_ACK_NO 0x0101 +#define UPGT_H2_FLAGS_TX_ACK_YES 0x0707 +struct upgt_lmac_h2 { + /* 8 bytes */ + uint32_t reqid; + uint16_t type; + uint16_t flags; +} __packed; + +struct upgt_lmac_header { + /* 12 bytes */ + struct upgt_lmac_h1 header1; + struct upgt_lmac_h2 header2; +} __packed; + +struct upgt_lmac_eeprom { + /* 16 bytes */ + struct upgt_lmac_h1 header1; + struct upgt_lmac_h2 header2; + uint16_t offset; + uint16_t len; + /* data */ +} __packed; + +#define UPGT_FILTER_TYPE_NONE 0x0000 +#define UPGT_FILTER_TYPE_STA 0x0001 +#define UPGT_FILTER_TYPE_IBSS 0x0002 +#define UPGT_FILTER_TYPE_HOSTAP 0x0004 +#define UPGT_FILTER_TYPE_MONITOR 0x0010 +#define UPGT_FILTER_TYPE_RESET 0x0020 +#define UPGT_FILTER_UNKNOWN1 0x0002 +#define UPGT_FILTER_UNKNOWN2 0x0ca8 +#define UPGT_FILTER_UNKNOWN3 0xffff +#define UPGT_FILTER_MONITOR_UNKNOWN1 0x0000 +#define UPGT_FILTER_MONITOR_UNKNOWN2 0x0000 +#define UPGT_FILTER_MONITOR_UNKNOWN3 0x0000 +struct upgt_lmac_filter { + struct upgt_lmac_h1 header1; + struct upgt_lmac_h2 header2; + /* 32 bytes */ + uint16_t type; + uint8_t dst[IEEE80211_ADDR_LEN]; + uint8_t src[IEEE80211_ADDR_LEN]; + uint16_t unknown1; + uint32_t rxaddr; + uint16_t unknown2; + uint32_t rxhw; + uint16_t unknown3; + uint32_t unknown4; +} __packed; + +/* frequence 3 data */ +struct upgt_lmac_freq3 { + uint16_t freq; + uint8_t data[6]; +} __packed; + +/* frequence 4 data */ +struct upgt_lmac_freq4 { + struct upgt_eeprom_freq4_2 cmd; + uint8_t pad; +}; + +/* frequence 6 data */ +struct upgt_lmac_freq6 { + uint16_t freq; + uint8_t data[8]; +} __packed; + +#define UPGT_CHANNEL_UNKNOWN1 0x0001 +#define UPGT_CHANNEL_UNKNOWN2 0x0000 +#define UPGT_CHANNEL_UNKNOWN3 0x48 +struct upgt_lmac_channel { + struct upgt_lmac_h1 header1; + struct upgt_lmac_h2 header2; + /* 112 bytes */ + uint16_t unknown1; + uint16_t unknown2; + uint8_t pad1[20]; + struct upgt_lmac_freq6 freq6; + uint8_t settings; + uint8_t unknown3; + uint8_t freq3_1[4]; + struct upgt_lmac_freq4 freq4[8]; + uint8_t freq3_2[4]; + uint32_t pad2; +} __packed; + +#define UPGT_LED_MODE_SET 0x0003 +#define UPGT_LED_ACTION_OFF 0x0002 +#define UPGT_LED_ACTION_ON 0x0003 +#define UPGT_LED_ACTION_TMP_DUR 100 /* ms */ +struct upgt_lmac_led { + struct upgt_lmac_h1 header1; + struct upgt_lmac_h2 header2; + uint16_t mode; + uint16_t action_fix; + uint16_t action_tmp; + uint16_t action_tmp_dur; +} __packed; + +struct upgt_lmac_stats { + struct upgt_lmac_h1 header1; + struct upgt_lmac_h2 header2; + uint8_t data[76]; +} __packed; + +struct upgt_lmac_rx_desc { + struct upgt_lmac_h1 header1; + /* 16 bytes */ + uint16_t freq; + uint8_t unknown1; + uint8_t rate; + uint8_t rssi; + uint8_t pad; + uint16_t unknown2; + uint32_t timestamp; + uint32_t unknown3; + uint8_t data[]; +} __packed; + +#define UPGT_TX_DESC_KEY_EXISTS 0x01 +struct upgt_lmac_tx_desc_wep { + uint8_t key_exists; + uint8_t key_len; + uint8_t key_val[16]; +} __packed; + +#define UPGT_TX_DESC_TYPE_BEACON 0x00000000 +#define UPGT_TX_DESC_TYPE_PROBE 0x00000001 +#define UPGT_TX_DESC_TYPE_MGMT 0x00000002 +#define UPGT_TX_DESC_TYPE_DATA 0x00000004 +#define UPGT_TX_DESC_PAD3_SIZE 2 +struct upgt_lmac_tx_desc { + struct upgt_lmac_h1 header1; + struct upgt_lmac_h2 header2; + uint8_t rates[8]; + uint16_t pad1; + struct upgt_lmac_tx_desc_wep wep_key; + uint32_t type; + uint32_t pad2; + uint32_t unknown1; + uint32_t unknown2; + uint8_t pad3[2]; + /* 802.11 frame data */ +} __packed; + +#define UPGT_TX_DONE_DESC_STATUS_OK 0x0001 +struct upgt_lmac_tx_done_desc { + struct upgt_lmac_h1 header1; + struct upgt_lmac_h2 header2; + uint16_t status; + uint16_t rssi; + uint16_t seq; + uint16_t unknown; +} __packed; + +/* + * USB xfers. + */ +struct upgt_data { + struct upgt_softc *sc; + usbd_xfer_handle xfer; + uint8_t *buf; + struct ieee80211_node *ni; + struct mbuf *m; + uint32_t addr; + uint8_t use; +}; + +/* + * Prism memory. + */ +struct upgt_memory_page { + uint8_t used; + uint32_t addr; +} __packed; + +#define UPGT_MEMORY_MAX_PAGES 8 +struct upgt_memory { + uint8_t pages; + struct upgt_memory_page page[UPGT_MEMORY_MAX_PAGES]; +} __packed; + +/* + * BPF + */ +struct upgt_rx_radiotap_header { + struct ieee80211_radiotap_header wr_ihdr; + uint8_t wr_flags; + uint8_t wr_rate; + uint16_t wr_chan_freq; + uint16_t wr_chan_flags; + int8_t wr_antsignal; +} __packed; + +#define UPGT_RX_RADIOTAP_PRESENT \ + ((1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_RATE) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL) | \ + (1 << IEEE80211_RADIOTAP_DB_ANTSIGNAL)) + +struct upgt_tx_radiotap_header { + struct ieee80211_radiotap_header wt_ihdr; + uint8_t wt_flags; + uint8_t wt_rate; + uint16_t wt_chan_freq; + uint16_t wt_chan_flags; +} __packed; + +#define UPGT_TX_RADIOTAP_PRESENT \ + ((1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_RATE) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL)) + +struct upgt_vap { + struct ieee80211vap vap; + int (*newstate)(struct ieee80211vap *, + enum ieee80211_state, int); +}; +#define UPGT_VAP(vap) ((struct upgt_vap *)(vap)) + +struct upgt_softc { + device_t sc_dev; + struct ifnet *sc_ifp; + usbd_device_handle sc_udev; + usbd_interface_handle sc_iface; + struct mtx sc_mtx; + int sc_if_flags; + int sc_debug; + + struct usb_task sc_mcasttask; + struct usb_task sc_task; + struct usb_task sc_scantask; +#define UPGT_SET_CHANNEL 2 + int sc_scan_action; + enum ieee80211_state sc_state; + int sc_arg; + int sc_led_blink; + struct callout sc_led_ch; + uint8_t sc_cur_rateset[8]; + + /* watchdog */ + int sc_tx_timer; + struct callout sc_watchdog_ch; + + /* Firmware. */ + int sc_fw_type; + /* memory addresses on device */ + uint32_t sc_memaddr_frame_start; + uint32_t sc_memaddr_frame_end; + uint32_t sc_memaddr_rx_start; + struct upgt_memory sc_memory; + + /* data which we found in the EEPROM */ + uint8_t sc_eeprom[UPGT_EEPROM_SIZE]; + uint16_t sc_eeprom_hwrx; + struct upgt_lmac_freq3 sc_eeprom_freq3[IEEE80211_CHAN_MAX]; + struct upgt_lmac_freq4 sc_eeprom_freq4[IEEE80211_CHAN_MAX][8]; + struct upgt_lmac_freq6 sc_eeprom_freq6[IEEE80211_CHAN_MAX]; + uint8_t sc_eeprom_freq6_settings; + + /* RX/TX */ + int sc_rx_no; + int sc_tx_no; + usbd_pipe_handle sc_rx_pipeh; + usbd_pipe_handle sc_tx_pipeh; + struct upgt_data tx_data[UPGT_TX_COUNT]; + struct upgt_data rx_data; + struct upgt_data cmd_data; + int tx_queued; + struct usb_task sc_task_tx; + + /* BPF */ + struct upgt_rx_radiotap_header sc_rxtap; + int sc_rxtap_len; + + struct upgt_tx_radiotap_header sc_txtap; + int sc_txtap_len; +}; + +#define UPGT_LOCK(sc) mtx_lock(&(sc)->sc_mtx) +#define UPGT_UNLOCK(sc) mtx_unlock(&(sc)->sc_mtx) diff --git a/sys/legacy/dev/usb/if_ural.c b/sys/legacy/dev/usb/if_ural.c new file mode 100644 index 0000000..4595761 --- /dev/null +++ b/sys/legacy/dev/usb/if_ural.c @@ -0,0 +1,2505 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2005, 2006 + * Damien Bergamini <damien.bergamini@free.fr> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/*- + * Ralink Technology RT2500USB chipset driver + * http://www.ralinktech.com/ + */ + +#include <sys/param.h> +#include <sys/sysctl.h> +#include <sys/sockio.h> +#include <sys/mbuf.h> +#include <sys/kernel.h> +#include <sys/socket.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/endian.h> + +#include <machine/bus.h> +#include <machine/resource.h> +#include <sys/rman.h> + +#include <net/bpf.h> +#include <net/if.h> +#include <net/if_arp.h> +#include <net/ethernet.h> +#include <net/if_dl.h> +#include <net/if_media.h> +#include <net/if_types.h> + +#include <net80211/ieee80211_var.h> +#include <net80211/ieee80211_amrr.h> +#include <net80211/ieee80211_phy.h> +#include <net80211/ieee80211_radiotap.h> +#include <net80211/ieee80211_regdomain.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include "usbdevs.h" + +#include <dev/usb/if_uralreg.h> +#include <dev/usb/if_uralvar.h> + +#ifdef USB_DEBUG +#define DPRINTF(x) do { if (uraldebug > 0) printf x; } while (0) +#define DPRINTFN(n, x) do { if (uraldebug >= (n)) printf x; } while (0) +int uraldebug = 0; +SYSCTL_NODE(_hw_usb, OID_AUTO, ural, CTLFLAG_RW, 0, "USB ural"); +SYSCTL_INT(_hw_usb_ural, OID_AUTO, debug, CTLFLAG_RW, &uraldebug, 0, + "ural debug level"); +#else +#define DPRINTF(x) +#define DPRINTFN(n, x) +#endif + +#define URAL_RSSI(rssi) \ + ((rssi) > (RAL_NOISE_FLOOR + RAL_RSSI_CORR) ? \ + ((rssi) - (RAL_NOISE_FLOOR + RAL_RSSI_CORR)) : 0) + +/* various supported device vendors/products */ +static const struct usb_devno ural_devs[] = { + { USB_VENDOR_ASUS, USB_PRODUCT_ASUS_WL167G }, + { USB_VENDOR_ASUS, USB_PRODUCT_RALINK_RT2570 }, + { USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5D7050 }, + { USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5D7051 }, + { USB_VENDOR_CONCEPTRONIC2, USB_PRODUCT_CONCEPTRONIC2_C54RU }, + { USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DWLG122 }, + { USB_VENDOR_GIGABYTE, USB_PRODUCT_GIGABYTE_GNWBKG }, + { USB_VENDOR_GIGABYTE, USB_PRODUCT_GIGABYTE_GN54G }, + { USB_VENDOR_GUILLEMOT, USB_PRODUCT_GUILLEMOT_HWGUSB254 }, + { USB_VENDOR_CISCOLINKSYS, USB_PRODUCT_CISCOLINKSYS_WUSB54G }, + { USB_VENDOR_CISCOLINKSYS, USB_PRODUCT_CISCOLINKSYS_WUSB54GP }, + { USB_VENDOR_CISCOLINKSYS, USB_PRODUCT_CISCOLINKSYS_HU200TS }, + { USB_VENDOR_MELCO, USB_PRODUCT_MELCO_KG54 }, + { USB_VENDOR_MELCO, USB_PRODUCT_MELCO_KG54AI }, + { USB_VENDOR_MELCO, USB_PRODUCT_MELCO_KG54YB }, + { USB_VENDOR_MELCO, USB_PRODUCT_MELCO_NINWIFI }, + { USB_VENDOR_MSI, USB_PRODUCT_MSI_RT2570 }, + { USB_VENDOR_MSI, USB_PRODUCT_MSI_RT2570_2 }, + { USB_VENDOR_MSI, USB_PRODUCT_MSI_RT2570_3 }, + { USB_VENDOR_NOVATECH, USB_PRODUCT_NOVATECH_NV902 }, + { USB_VENDOR_RALINK, USB_PRODUCT_RALINK_RT2570 }, + { USB_VENDOR_RALINK, USB_PRODUCT_RALINK_RT2570_2 }, + { USB_VENDOR_RALINK, USB_PRODUCT_RALINK_RT2570_3 }, + { USB_VENDOR_SIEMENS2, USB_PRODUCT_SIEMENS2_WL54G }, + { USB_VENDOR_SMC, USB_PRODUCT_SMC_2862WG }, + { USB_VENDOR_SPHAIRON, USB_PRODUCT_SPHAIRON_UB801R}, + { USB_VENDOR_SURECOM, USB_PRODUCT_SURECOM_RT2570 }, + { USB_VENDOR_VTECH, USB_PRODUCT_VTECH_RT2570 }, + { USB_VENDOR_ZINWELL, USB_PRODUCT_ZINWELL_RT2570 } +}; + +MODULE_DEPEND(ural, wlan, 1, 1, 1); +MODULE_DEPEND(ural, wlan_amrr, 1, 1, 1); +MODULE_DEPEND(ural, usb, 1, 1, 1); + +static struct ieee80211vap *ural_vap_create(struct ieee80211com *, + const char name[IFNAMSIZ], int unit, int opmode, + int flags, const uint8_t bssid[IEEE80211_ADDR_LEN], + const uint8_t mac[IEEE80211_ADDR_LEN]); +static void ural_vap_delete(struct ieee80211vap *); +static int ural_alloc_tx_list(struct ural_softc *); +static void ural_free_tx_list(struct ural_softc *); +static int ural_alloc_rx_list(struct ural_softc *); +static void ural_free_rx_list(struct ural_softc *); +static void ural_task(void *); +static void ural_scantask(void *); +static int ural_newstate(struct ieee80211vap *, + enum ieee80211_state, int); +static void ural_txeof(usbd_xfer_handle, usbd_private_handle, + usbd_status); +static void ural_rxeof(usbd_xfer_handle, usbd_private_handle, + usbd_status); +static void ural_setup_tx_desc(struct ural_softc *, + struct ural_tx_desc *, uint32_t, int, int); +static int ural_tx_bcn(struct ural_softc *, struct mbuf *, + struct ieee80211_node *); +static int ural_tx_mgt(struct ural_softc *, struct mbuf *, + struct ieee80211_node *); +static int ural_tx_data(struct ural_softc *, struct mbuf *, + struct ieee80211_node *); +static void ural_start(struct ifnet *); +static void ural_watchdog(void *); +static int ural_ioctl(struct ifnet *, u_long, caddr_t); +static void ural_set_testmode(struct ural_softc *); +static void ural_eeprom_read(struct ural_softc *, uint16_t, void *, + int); +static uint16_t ural_read(struct ural_softc *, uint16_t); +static void ural_read_multi(struct ural_softc *, uint16_t, void *, + int); +static void ural_write(struct ural_softc *, uint16_t, uint16_t); +static void ural_write_multi(struct ural_softc *, uint16_t, void *, + int) __unused; +static void ural_bbp_write(struct ural_softc *, uint8_t, uint8_t); +static uint8_t ural_bbp_read(struct ural_softc *, uint8_t); +static void ural_rf_write(struct ural_softc *, uint8_t, uint32_t); +static struct ieee80211_node *ural_node_alloc(struct ieee80211vap *, + const uint8_t mac[IEEE80211_ADDR_LEN]); +static void ural_newassoc(struct ieee80211_node *, int); +static void ural_scan_start(struct ieee80211com *); +static void ural_scan_end(struct ieee80211com *); +static void ural_set_channel(struct ieee80211com *); +static void ural_set_chan(struct ural_softc *, + struct ieee80211_channel *); +static void ural_disable_rf_tune(struct ural_softc *); +static void ural_enable_tsf_sync(struct ural_softc *); +static void ural_update_slot(struct ifnet *); +static void ural_set_txpreamble(struct ural_softc *); +static void ural_set_basicrates(struct ural_softc *, + const struct ieee80211_channel *); +static void ural_set_bssid(struct ural_softc *, const uint8_t *); +static void ural_set_macaddr(struct ural_softc *, uint8_t *); +static void ural_update_promisc(struct ural_softc *); +static const char *ural_get_rf(int); +static void ural_read_eeprom(struct ural_softc *); +static int ural_bbp_init(struct ural_softc *); +static void ural_set_txantenna(struct ural_softc *, int); +static void ural_set_rxantenna(struct ural_softc *, int); +static void ural_init_locked(struct ural_softc *); +static void ural_init(void *); +static void ural_stop(void *); +static int ural_raw_xmit(struct ieee80211_node *, struct mbuf *, + const struct ieee80211_bpf_params *); +static void ural_amrr_start(struct ural_softc *, + struct ieee80211_node *); +static void ural_amrr_timeout(void *); +static void ural_amrr_update(usbd_xfer_handle, usbd_private_handle, + usbd_status status); + +/* + * Default values for MAC registers; values taken from the reference driver. + */ +static const struct { + uint16_t reg; + uint16_t val; +} ural_def_mac[] = { + { RAL_TXRX_CSR5, 0x8c8d }, + { RAL_TXRX_CSR6, 0x8b8a }, + { RAL_TXRX_CSR7, 0x8687 }, + { RAL_TXRX_CSR8, 0x0085 }, + { RAL_MAC_CSR13, 0x1111 }, + { RAL_MAC_CSR14, 0x1e11 }, + { RAL_TXRX_CSR21, 0xe78f }, + { RAL_MAC_CSR9, 0xff1d }, + { RAL_MAC_CSR11, 0x0002 }, + { RAL_MAC_CSR22, 0x0053 }, + { RAL_MAC_CSR15, 0x0000 }, + { RAL_MAC_CSR8, 0x0780 }, + { RAL_TXRX_CSR19, 0x0000 }, + { RAL_TXRX_CSR18, 0x005a }, + { RAL_PHY_CSR2, 0x0000 }, + { RAL_TXRX_CSR0, 0x1ec0 }, + { RAL_PHY_CSR4, 0x000f } +}; + +/* + * Default values for BBP registers; values taken from the reference driver. + */ +static const struct { + uint8_t reg; + uint8_t val; +} ural_def_bbp[] = { + { 3, 0x02 }, + { 4, 0x19 }, + { 14, 0x1c }, + { 15, 0x30 }, + { 16, 0xac }, + { 17, 0x48 }, + { 18, 0x18 }, + { 19, 0xff }, + { 20, 0x1e }, + { 21, 0x08 }, + { 22, 0x08 }, + { 23, 0x08 }, + { 24, 0x80 }, + { 25, 0x50 }, + { 26, 0x08 }, + { 27, 0x23 }, + { 30, 0x10 }, + { 31, 0x2b }, + { 32, 0xb9 }, + { 34, 0x12 }, + { 35, 0x50 }, + { 39, 0xc4 }, + { 40, 0x02 }, + { 41, 0x60 }, + { 53, 0x10 }, + { 54, 0x18 }, + { 56, 0x08 }, + { 57, 0x10 }, + { 58, 0x08 }, + { 61, 0x60 }, + { 62, 0x10 }, + { 75, 0xff } +}; + +/* + * Default values for RF register R2 indexed by channel numbers. + */ +static const uint32_t ural_rf2522_r2[] = { + 0x307f6, 0x307fb, 0x30800, 0x30805, 0x3080a, 0x3080f, 0x30814, + 0x30819, 0x3081e, 0x30823, 0x30828, 0x3082d, 0x30832, 0x3083e +}; + +static const uint32_t ural_rf2523_r2[] = { + 0x00327, 0x00328, 0x00329, 0x0032a, 0x0032b, 0x0032c, 0x0032d, + 0x0032e, 0x0032f, 0x00340, 0x00341, 0x00342, 0x00343, 0x00346 +}; + +static const uint32_t ural_rf2524_r2[] = { + 0x00327, 0x00328, 0x00329, 0x0032a, 0x0032b, 0x0032c, 0x0032d, + 0x0032e, 0x0032f, 0x00340, 0x00341, 0x00342, 0x00343, 0x00346 +}; + +static const uint32_t ural_rf2525_r2[] = { + 0x20327, 0x20328, 0x20329, 0x2032a, 0x2032b, 0x2032c, 0x2032d, + 0x2032e, 0x2032f, 0x20340, 0x20341, 0x20342, 0x20343, 0x20346 +}; + +static const uint32_t ural_rf2525_hi_r2[] = { + 0x2032f, 0x20340, 0x20341, 0x20342, 0x20343, 0x20344, 0x20345, + 0x20346, 0x20347, 0x20348, 0x20349, 0x2034a, 0x2034b, 0x2034e +}; + +static const uint32_t ural_rf2525e_r2[] = { + 0x2044d, 0x2044e, 0x2044f, 0x20460, 0x20461, 0x20462, 0x20463, + 0x20464, 0x20465, 0x20466, 0x20467, 0x20468, 0x20469, 0x2046b +}; + +static const uint32_t ural_rf2526_hi_r2[] = { + 0x0022a, 0x0022b, 0x0022b, 0x0022c, 0x0022c, 0x0022d, 0x0022d, + 0x0022e, 0x0022e, 0x0022f, 0x0022d, 0x00240, 0x00240, 0x00241 +}; + +static const uint32_t ural_rf2526_r2[] = { + 0x00226, 0x00227, 0x00227, 0x00228, 0x00228, 0x00229, 0x00229, + 0x0022a, 0x0022a, 0x0022b, 0x0022b, 0x0022c, 0x0022c, 0x0022d +}; + +/* + * For dual-band RF, RF registers R1 and R4 also depend on channel number; + * values taken from the reference driver. + */ +static const struct { + uint8_t chan; + uint32_t r1; + uint32_t r2; + uint32_t r4; +} ural_rf5222[] = { + { 1, 0x08808, 0x0044d, 0x00282 }, + { 2, 0x08808, 0x0044e, 0x00282 }, + { 3, 0x08808, 0x0044f, 0x00282 }, + { 4, 0x08808, 0x00460, 0x00282 }, + { 5, 0x08808, 0x00461, 0x00282 }, + { 6, 0x08808, 0x00462, 0x00282 }, + { 7, 0x08808, 0x00463, 0x00282 }, + { 8, 0x08808, 0x00464, 0x00282 }, + { 9, 0x08808, 0x00465, 0x00282 }, + { 10, 0x08808, 0x00466, 0x00282 }, + { 11, 0x08808, 0x00467, 0x00282 }, + { 12, 0x08808, 0x00468, 0x00282 }, + { 13, 0x08808, 0x00469, 0x00282 }, + { 14, 0x08808, 0x0046b, 0x00286 }, + + { 36, 0x08804, 0x06225, 0x00287 }, + { 40, 0x08804, 0x06226, 0x00287 }, + { 44, 0x08804, 0x06227, 0x00287 }, + { 48, 0x08804, 0x06228, 0x00287 }, + { 52, 0x08804, 0x06229, 0x00287 }, + { 56, 0x08804, 0x0622a, 0x00287 }, + { 60, 0x08804, 0x0622b, 0x00287 }, + { 64, 0x08804, 0x0622c, 0x00287 }, + + { 100, 0x08804, 0x02200, 0x00283 }, + { 104, 0x08804, 0x02201, 0x00283 }, + { 108, 0x08804, 0x02202, 0x00283 }, + { 112, 0x08804, 0x02203, 0x00283 }, + { 116, 0x08804, 0x02204, 0x00283 }, + { 120, 0x08804, 0x02205, 0x00283 }, + { 124, 0x08804, 0x02206, 0x00283 }, + { 128, 0x08804, 0x02207, 0x00283 }, + { 132, 0x08804, 0x02208, 0x00283 }, + { 136, 0x08804, 0x02209, 0x00283 }, + { 140, 0x08804, 0x0220a, 0x00283 }, + + { 149, 0x08808, 0x02429, 0x00281 }, + { 153, 0x08808, 0x0242b, 0x00281 }, + { 157, 0x08808, 0x0242d, 0x00281 }, + { 161, 0x08808, 0x0242f, 0x00281 } +}; + +static device_probe_t ural_match; +static device_attach_t ural_attach; +static device_detach_t ural_detach; + +static device_method_t ural_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ural_match), + DEVMETHOD(device_attach, ural_attach), + DEVMETHOD(device_detach, ural_detach), + + { 0, 0 } +}; + +static driver_t ural_driver = { + "ural", + ural_methods, + sizeof(struct ural_softc) +}; + +static devclass_t ural_devclass; + +DRIVER_MODULE(ural, uhub, ural_driver, ural_devclass, usbd_driver_load, 0); + +static int +ural_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + + if (uaa->iface != NULL) + return UMATCH_NONE; + + return (usb_lookup(ural_devs, uaa->vendor, uaa->product) != NULL) ? + UMATCH_VENDOR_PRODUCT : UMATCH_NONE; +} + +static int +ural_attach(device_t self) +{ + struct ural_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + struct ifnet *ifp; + struct ieee80211com *ic; + usb_interface_descriptor_t *id; + usb_endpoint_descriptor_t *ed; + usbd_status error; + int i; + uint8_t bands; + + sc->sc_udev = uaa->device; + sc->sc_dev = self; + + if (usbd_set_config_no(sc->sc_udev, RAL_CONFIG_NO, 0) != 0) { + device_printf(self, "could not set configuration no\n"); + return ENXIO; + } + + /* get the first interface handle */ + error = usbd_device2interface_handle(sc->sc_udev, RAL_IFACE_INDEX, + &sc->sc_iface); + if (error != 0) { + device_printf(self, "could not get interface handle\n"); + return ENXIO; + } + + /* + * Find endpoints. + */ + id = usbd_get_interface_descriptor(sc->sc_iface); + + sc->sc_rx_no = sc->sc_tx_no = -1; + for (i = 0; i < id->bNumEndpoints; i++) { + ed = usbd_interface2endpoint_descriptor(sc->sc_iface, i); + if (ed == NULL) { + device_printf(self, "no endpoint descriptor for %d\n", + i); + return ENXIO; + } + + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) + sc->sc_rx_no = ed->bEndpointAddress; + else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) + sc->sc_tx_no = ed->bEndpointAddress; + } + if (sc->sc_rx_no == -1 || sc->sc_tx_no == -1) { + device_printf(self, "missing endpoint\n"); + return ENXIO; + } + + ifp = sc->sc_ifp = if_alloc(IFT_IEEE80211); + if (ifp == NULL) { + device_printf(sc->sc_dev, "can not if_alloc()\n"); + return ENXIO; + } + ic = ifp->if_l2com; + + mtx_init(&sc->sc_mtx, device_get_nameunit(sc->sc_dev), MTX_NETWORK_LOCK, + MTX_DEF | MTX_RECURSE); + + usb_init_task(&sc->sc_task, ural_task, sc); + usb_init_task(&sc->sc_scantask, ural_scantask, sc); + callout_init(&sc->watchdog_ch, 0); + + /* retrieve RT2570 rev. no */ + sc->asic_rev = ural_read(sc, RAL_MAC_CSR0); + + /* retrieve MAC address and various other things from EEPROM */ + ural_read_eeprom(sc); + + device_printf(sc->sc_dev, "MAC/BBP RT2570 (rev 0x%02x), RF %s\n", + sc->asic_rev, ural_get_rf(sc->rf_rev)); + + ifp->if_softc = sc; + if_initname(ifp, "ural", device_get_unit(sc->sc_dev)); + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST | + IFF_NEEDSGIANT; /* USB stack is still under Giant lock */ + ifp->if_init = ural_init; + ifp->if_ioctl = ural_ioctl; + ifp->if_start = ural_start; + IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN); + ifp->if_snd.ifq_drv_maxlen = IFQ_MAXLEN; + IFQ_SET_READY(&ifp->if_snd); + + ic->ic_ifp = ifp; + ic->ic_phytype = IEEE80211_T_OFDM; /* not only, but not used */ + + /* set device capabilities */ + ic->ic_caps = + IEEE80211_C_STA /* station mode supported */ + | IEEE80211_C_IBSS /* IBSS mode supported */ + | IEEE80211_C_MONITOR /* monitor mode supported */ + | IEEE80211_C_HOSTAP /* HostAp mode supported */ + | IEEE80211_C_TXPMGT /* tx power management */ + | IEEE80211_C_SHPREAMBLE /* short preamble supported */ + | IEEE80211_C_SHSLOT /* short slot time supported */ + | IEEE80211_C_BGSCAN /* bg scanning supported */ + | IEEE80211_C_WPA /* 802.11i */ + ; + + bands = 0; + setbit(&bands, IEEE80211_MODE_11B); + setbit(&bands, IEEE80211_MODE_11G); + if (sc->rf_rev == RAL_RF_5222) + setbit(&bands, IEEE80211_MODE_11A); + ieee80211_init_channels(ic, NULL, &bands); + + ieee80211_ifattach(ic); + ic->ic_newassoc = ural_newassoc; + ic->ic_raw_xmit = ural_raw_xmit; + ic->ic_node_alloc = ural_node_alloc; + ic->ic_scan_start = ural_scan_start; + ic->ic_scan_end = ural_scan_end; + ic->ic_set_channel = ural_set_channel; + + ic->ic_vap_create = ural_vap_create; + ic->ic_vap_delete = ural_vap_delete; + + sc->sc_rates = ieee80211_get_ratetable(ic->ic_curchan); + + bpfattach(ifp, DLT_IEEE802_11_RADIO, + sizeof (struct ieee80211_frame) + sizeof(sc->sc_txtap)); + + sc->sc_rxtap_len = sizeof sc->sc_rxtap; + sc->sc_rxtap.wr_ihdr.it_len = htole16(sc->sc_rxtap_len); + sc->sc_rxtap.wr_ihdr.it_present = htole32(RAL_RX_RADIOTAP_PRESENT); + + sc->sc_txtap_len = sizeof sc->sc_txtap; + sc->sc_txtap.wt_ihdr.it_len = htole16(sc->sc_txtap_len); + sc->sc_txtap.wt_ihdr.it_present = htole32(RAL_TX_RADIOTAP_PRESENT); + + if (bootverbose) + ieee80211_announce(ic); + + return 0; +} + +static int +ural_detach(device_t self) +{ + struct ural_softc *sc = device_get_softc(self); + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + ural_stop(sc); + bpfdetach(ifp); + ieee80211_ifdetach(ic); + + usb_rem_task(sc->sc_udev, &sc->sc_task); + usb_rem_task(sc->sc_udev, &sc->sc_scantask); + callout_stop(&sc->watchdog_ch); + + if (sc->amrr_xfer != NULL) { + usbd_free_xfer(sc->amrr_xfer); + sc->amrr_xfer = NULL; + } + + if (sc->sc_rx_pipeh != NULL) { + usbd_abort_pipe(sc->sc_rx_pipeh); + usbd_close_pipe(sc->sc_rx_pipeh); + } + + if (sc->sc_tx_pipeh != NULL) { + usbd_abort_pipe(sc->sc_tx_pipeh); + usbd_close_pipe(sc->sc_tx_pipeh); + } + + ural_free_rx_list(sc); + ural_free_tx_list(sc); + + if_free(ifp); + mtx_destroy(&sc->sc_mtx); + + return 0; +} + +static struct ieee80211vap * +ural_vap_create(struct ieee80211com *ic, + const char name[IFNAMSIZ], int unit, int opmode, int flags, + const uint8_t bssid[IEEE80211_ADDR_LEN], + const uint8_t mac[IEEE80211_ADDR_LEN]) +{ + struct ural_vap *uvp; + struct ieee80211vap *vap; + + if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */ + return NULL; + uvp = (struct ural_vap *) malloc(sizeof(struct ural_vap), + M_80211_VAP, M_NOWAIT | M_ZERO); + if (uvp == NULL) + return NULL; + vap = &uvp->vap; + /* enable s/w bmiss handling for sta mode */ + ieee80211_vap_setup(ic, vap, name, unit, opmode, + flags | IEEE80211_CLONE_NOBEACONS, bssid, mac); + + /* override state transition machine */ + uvp->newstate = vap->iv_newstate; + vap->iv_newstate = ural_newstate; + + callout_init(&uvp->amrr_ch, 0); + ieee80211_amrr_init(&uvp->amrr, vap, + IEEE80211_AMRR_MIN_SUCCESS_THRESHOLD, + IEEE80211_AMRR_MAX_SUCCESS_THRESHOLD, + 1000 /* 1 sec */); + + /* complete setup */ + ieee80211_vap_attach(vap, ieee80211_media_change, ieee80211_media_status); + ic->ic_opmode = opmode; + return vap; +} + +static void +ural_vap_delete(struct ieee80211vap *vap) +{ + struct ural_vap *uvp = URAL_VAP(vap); + + callout_stop(&uvp->amrr_ch); + ieee80211_amrr_cleanup(&uvp->amrr); + ieee80211_vap_detach(vap); + free(uvp, M_80211_VAP); +} + +static int +ural_alloc_tx_list(struct ural_softc *sc) +{ + struct ural_tx_data *data; + int i, error; + + sc->tx_queued = sc->tx_cur = 0; + + for (i = 0; i < RAL_TX_LIST_COUNT; i++) { + data = &sc->tx_data[i]; + + data->sc = sc; + + data->xfer = usbd_alloc_xfer(sc->sc_udev); + if (data->xfer == NULL) { + device_printf(sc->sc_dev, + "could not allocate tx xfer\n"); + error = ENOMEM; + goto fail; + } + + data->buf = usbd_alloc_buffer(data->xfer, + RAL_TX_DESC_SIZE + MCLBYTES); + if (data->buf == NULL) { + device_printf(sc->sc_dev, + "could not allocate tx buffer\n"); + error = ENOMEM; + goto fail; + } + } + + return 0; + +fail: ural_free_tx_list(sc); + return error; +} + +static void +ural_free_tx_list(struct ural_softc *sc) +{ + struct ural_tx_data *data; + int i; + + for (i = 0; i < RAL_TX_LIST_COUNT; i++) { + data = &sc->tx_data[i]; + + if (data->xfer != NULL) { + usbd_free_xfer(data->xfer); + data->xfer = NULL; + } + + if (data->ni != NULL) { + ieee80211_free_node(data->ni); + data->ni = NULL; + } + } +} + +static int +ural_alloc_rx_list(struct ural_softc *sc) +{ + struct ural_rx_data *data; + int i, error; + + for (i = 0; i < RAL_RX_LIST_COUNT; i++) { + data = &sc->rx_data[i]; + + data->sc = sc; + + data->xfer = usbd_alloc_xfer(sc->sc_udev); + if (data->xfer == NULL) { + device_printf(sc->sc_dev, + "could not allocate rx xfer\n"); + error = ENOMEM; + goto fail; + } + + if (usbd_alloc_buffer(data->xfer, MCLBYTES) == NULL) { + device_printf(sc->sc_dev, + "could not allocate rx buffer\n"); + error = ENOMEM; + goto fail; + } + + data->m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + if (data->m == NULL) { + device_printf(sc->sc_dev, + "could not allocate rx mbuf\n"); + error = ENOMEM; + goto fail; + } + + data->buf = mtod(data->m, uint8_t *); + } + + return 0; + +fail: ural_free_rx_list(sc); + return error; +} + +static void +ural_free_rx_list(struct ural_softc *sc) +{ + struct ural_rx_data *data; + int i; + + for (i = 0; i < RAL_RX_LIST_COUNT; i++) { + data = &sc->rx_data[i]; + + if (data->xfer != NULL) { + usbd_free_xfer(data->xfer); + data->xfer = NULL; + } + + if (data->m != NULL) { + m_freem(data->m); + data->m = NULL; + } + } +} + +static void +ural_task(void *xarg) +{ + struct ural_softc *sc = xarg; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); + struct ural_vap *uvp = URAL_VAP(vap); + const struct ieee80211_txparam *tp; + enum ieee80211_state ostate; + struct ieee80211_node *ni; + struct mbuf *m; + + ostate = vap->iv_state; + + RAL_LOCK(sc); + switch (sc->sc_state) { + case IEEE80211_S_INIT: + if (ostate == IEEE80211_S_RUN) { + /* abort TSF synchronization */ + ural_write(sc, RAL_TXRX_CSR19, 0); + + /* force tx led to stop blinking */ + ural_write(sc, RAL_MAC_CSR20, 0); + } + break; + + case IEEE80211_S_RUN: + ni = vap->iv_bss; + + if (vap->iv_opmode != IEEE80211_M_MONITOR) { + ural_update_slot(ic->ic_ifp); + ural_set_txpreamble(sc); + ural_set_basicrates(sc, ic->ic_bsschan); + ural_set_bssid(sc, ni->ni_bssid); + } + + if (vap->iv_opmode == IEEE80211_M_HOSTAP || + vap->iv_opmode == IEEE80211_M_IBSS) { + m = ieee80211_beacon_alloc(ni, &uvp->bo); + if (m == NULL) { + device_printf(sc->sc_dev, + "could not allocate beacon\n"); + return; + } + + if (ural_tx_bcn(sc, m, ni) != 0) { + device_printf(sc->sc_dev, + "could not send beacon\n"); + return; + } + } + + /* make tx led blink on tx (controlled by ASIC) */ + ural_write(sc, RAL_MAC_CSR20, 1); + + if (vap->iv_opmode != IEEE80211_M_MONITOR) + ural_enable_tsf_sync(sc); + + /* enable automatic rate adaptation */ + tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_bsschan)]; + if (tp->ucastrate == IEEE80211_FIXED_RATE_NONE) + ural_amrr_start(sc, ni); + + break; + + default: + break; + } + + RAL_UNLOCK(sc); + + IEEE80211_LOCK(ic); + uvp->newstate(vap, sc->sc_state, sc->sc_arg); + if (vap->iv_newstate_cb != NULL) + vap->iv_newstate_cb(vap, sc->sc_state, sc->sc_arg); + IEEE80211_UNLOCK(ic); +} + +static void +ural_scantask(void *arg) +{ + struct ural_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); + + RAL_LOCK(sc); + if (sc->sc_scan_action == URAL_SCAN_START) { + /* abort TSF synchronization */ + ural_write(sc, RAL_TXRX_CSR19, 0); + ural_set_bssid(sc, ifp->if_broadcastaddr); + } else if (sc->sc_scan_action == URAL_SET_CHANNEL) { + mtx_lock(&Giant); + ural_set_chan(sc, ic->ic_curchan); + mtx_unlock(&Giant); + } else { + ural_enable_tsf_sync(sc); + /* XXX keep local copy */ + ural_set_bssid(sc, vap->iv_bss->ni_bssid); + } + RAL_UNLOCK(sc); +} + +static int +ural_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) +{ + struct ural_vap *uvp = URAL_VAP(vap); + struct ieee80211com *ic = vap->iv_ic; + struct ural_softc *sc = ic->ic_ifp->if_softc; + + callout_stop(&uvp->amrr_ch); + + /* do it in a process context */ + sc->sc_state = nstate; + sc->sc_arg = arg; + + usb_rem_task(sc->sc_udev, &sc->sc_task); + if (nstate == IEEE80211_S_INIT) { + uvp->newstate(vap, nstate, arg); + return 0; + } else { + usb_add_task(sc->sc_udev, &sc->sc_task, USB_TASKQ_DRIVER); + return EINPROGRESS; + } +} + +#define RAL_RXTX_TURNAROUND 5 /* us */ + +static void +ural_txeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + struct ural_tx_data *data = priv; + struct ural_softc *sc = data->sc; + struct ifnet *ifp = sc->sc_ifp; + + if (data->m->m_flags & M_TXCB) + ieee80211_process_callback(data->ni, data->m, + status == USBD_NORMAL_COMPLETION ? 0 : ETIMEDOUT); + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) + return; + + device_printf(sc->sc_dev, "could not transmit buffer: %s\n", + usbd_errstr(status)); + + if (status == USBD_STALLED) + usbd_clear_endpoint_stall_async(sc->sc_rx_pipeh); + + ifp->if_oerrors++; + /* XXX mbuf leak? */ + return; + } + + m_freem(data->m); + data->m = NULL; + ieee80211_free_node(data->ni); + data->ni = NULL; + + sc->tx_queued--; + ifp->if_opackets++; + + DPRINTFN(10, ("tx done\n")); + + sc->sc_tx_timer = 0; + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + ural_start(ifp); +} + +static void +ural_rxeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + struct ural_rx_data *data = priv; + struct ural_softc *sc = data->sc; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ural_rx_desc *desc; + struct ieee80211_node *ni; + struct mbuf *mnew, *m; + int len, rssi; + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) + return; + + if (status == USBD_STALLED) + usbd_clear_endpoint_stall_async(sc->sc_rx_pipeh); + goto skip; + } + + usbd_get_xfer_status(xfer, NULL, NULL, &len, NULL); + + if (len < RAL_RX_DESC_SIZE + IEEE80211_MIN_LEN) { + DPRINTF(("%s: xfer too short %d\n", device_get_nameunit(sc->sc_dev), + len)); + ifp->if_ierrors++; + goto skip; + } + + /* rx descriptor is located at the end */ + desc = (struct ural_rx_desc *)(data->buf + len - RAL_RX_DESC_SIZE); + + if ((le32toh(desc->flags) & RAL_RX_PHY_ERROR) || + (le32toh(desc->flags) & RAL_RX_CRC_ERROR)) { + /* + * This should not happen since we did not request to receive + * those frames when we filled RAL_TXRX_CSR2. + */ + DPRINTFN(5, ("PHY or CRC error\n")); + ifp->if_ierrors++; + goto skip; + } + + mnew = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + if (mnew == NULL) { + ifp->if_ierrors++; + goto skip; + } + + m = data->m; + data->m = mnew; + data->buf = mtod(data->m, uint8_t *); + + /* finalize mbuf */ + m->m_pkthdr.rcvif = ifp; + m->m_pkthdr.len = m->m_len = (le32toh(desc->flags) >> 16) & 0xfff; + + if (bpf_peers_present(ifp->if_bpf)) { + struct ural_rx_radiotap_header *tap = &sc->sc_rxtap; + + tap->wr_flags = IEEE80211_RADIOTAP_F_FCS; + tap->wr_rate = ieee80211_plcp2rate(desc->rate, + (desc->flags & htole32(RAL_RX_OFDM)) ? + IEEE80211_T_OFDM : IEEE80211_T_CCK); + tap->wr_chan_freq = htole16(ic->ic_curchan->ic_freq); + tap->wr_chan_flags = htole16(ic->ic_curchan->ic_flags); + tap->wr_antenna = sc->rx_ant; + tap->wr_antsignal = URAL_RSSI(desc->rssi); + + bpf_mtap2(ifp->if_bpf, tap, sc->sc_rxtap_len, m); + } + + /* Strip trailing 802.11 MAC FCS. */ + m_adj(m, -IEEE80211_CRC_LEN); + + rssi = URAL_RSSI(desc->rssi); + ni = ieee80211_find_rxnode(ic, mtod(m, struct ieee80211_frame_min *)); + if (ni != NULL) { + (void) ieee80211_input(ni, m, rssi, RAL_NOISE_FLOOR, 0); + ieee80211_free_node(ni); + } else + (void) ieee80211_input_all(ic, m, rssi, RAL_NOISE_FLOOR, 0); + + DPRINTFN(15, ("rx done\n")); + +skip: /* setup a new transfer */ + usbd_setup_xfer(xfer, sc->sc_rx_pipeh, data, data->buf, MCLBYTES, + USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT, ural_rxeof); + usbd_transfer(xfer); +} + +static uint8_t +ural_plcp_signal(int rate) +{ + switch (rate) { + /* OFDM rates (cf IEEE Std 802.11a-1999, pp. 14 Table 80) */ + case 12: return 0xb; + case 18: return 0xf; + case 24: return 0xa; + case 36: return 0xe; + case 48: return 0x9; + case 72: return 0xd; + case 96: return 0x8; + case 108: return 0xc; + + /* CCK rates (NB: not IEEE std, device-specific) */ + case 2: return 0x0; + case 4: return 0x1; + case 11: return 0x2; + case 22: return 0x3; + } + return 0xff; /* XXX unsupported/unknown rate */ +} + +static void +ural_setup_tx_desc(struct ural_softc *sc, struct ural_tx_desc *desc, + uint32_t flags, int len, int rate) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + uint16_t plcp_length; + int remainder; + + desc->flags = htole32(flags); + desc->flags |= htole32(RAL_TX_NEWSEQ); + desc->flags |= htole32(len << 16); + + desc->wme = htole16(RAL_AIFSN(2) | RAL_LOGCWMIN(3) | RAL_LOGCWMAX(5)); + desc->wme |= htole16(RAL_IVOFFSET(sizeof (struct ieee80211_frame))); + + /* setup PLCP fields */ + desc->plcp_signal = ural_plcp_signal(rate); + desc->plcp_service = 4; + + len += IEEE80211_CRC_LEN; + if (ieee80211_rate2phytype(sc->sc_rates, rate) == IEEE80211_T_OFDM) { + desc->flags |= htole32(RAL_TX_OFDM); + + plcp_length = len & 0xfff; + desc->plcp_length_hi = plcp_length >> 6; + desc->plcp_length_lo = plcp_length & 0x3f; + } else { + plcp_length = (16 * len + rate - 1) / rate; + if (rate == 22) { + remainder = (16 * len) % 22; + if (remainder != 0 && remainder < 7) + desc->plcp_service |= RAL_PLCP_LENGEXT; + } + desc->plcp_length_hi = plcp_length >> 8; + desc->plcp_length_lo = plcp_length & 0xff; + + if (rate != 2 && (ic->ic_flags & IEEE80211_F_SHPREAMBLE)) + desc->plcp_signal |= 0x08; + } + + desc->iv = 0; + desc->eiv = 0; +} + +#define RAL_TX_TIMEOUT 5000 + +static int +ural_tx_bcn(struct ural_softc *sc, struct mbuf *m0, struct ieee80211_node *ni) +{ + struct ieee80211vap *vap = ni->ni_vap; + struct ieee80211com *ic = ni->ni_ic; + const struct ieee80211_txparam *tp; + struct ural_tx_desc *desc; + usbd_xfer_handle xfer; + uint8_t cmd; + usbd_status error; + uint8_t *buf; + int xferlen; + + tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_bsschan)]; + + xfer = usbd_alloc_xfer(sc->sc_udev); + if (xfer == NULL) + return ENOMEM; + + /* xfer length needs to be a multiple of two! */ + xferlen = (RAL_TX_DESC_SIZE + m0->m_pkthdr.len + 1) & ~1; + + buf = usbd_alloc_buffer(xfer, xferlen); + if (buf == NULL) { + usbd_free_xfer(xfer); + return ENOMEM; + } + + cmd = 0; + usbd_setup_xfer(xfer, sc->sc_tx_pipeh, NULL, &cmd, sizeof cmd, + USBD_FORCE_SHORT_XFER, RAL_TX_TIMEOUT, NULL); + + error = usbd_sync_transfer(xfer); + if (error != 0) { + usbd_free_xfer(xfer); + return error; + } + + desc = (struct ural_tx_desc *)buf; + + m_copydata(m0, 0, m0->m_pkthdr.len, buf + RAL_TX_DESC_SIZE); + ural_setup_tx_desc(sc, desc, RAL_TX_IFS_NEWBACKOFF | RAL_TX_TIMESTAMP, + m0->m_pkthdr.len, tp->mgmtrate); + + DPRINTFN(10, ("sending beacon frame len=%u rate=%u xfer len=%u\n", + m0->m_pkthdr.len, tp->mgmtrate, xferlen)); + + usbd_setup_xfer(xfer, sc->sc_tx_pipeh, NULL, buf, xferlen, + USBD_FORCE_SHORT_XFER | USBD_NO_COPY, RAL_TX_TIMEOUT, NULL); + + error = usbd_sync_transfer(xfer); + usbd_free_xfer(xfer); + + return error; +} + +static int +ural_tx_mgt(struct ural_softc *sc, struct mbuf *m0, struct ieee80211_node *ni) +{ + struct ieee80211vap *vap = ni->ni_vap; + struct ieee80211com *ic = ni->ni_ic; + struct ifnet *ifp = sc->sc_ifp; + const struct ieee80211_txparam *tp; + struct ural_tx_desc *desc; + struct ural_tx_data *data; + struct ieee80211_frame *wh; + struct ieee80211_key *k; + uint32_t flags; + uint16_t dur; + usbd_status error; + int xferlen; + + data = &sc->tx_data[sc->tx_cur]; + desc = (struct ural_tx_desc *)data->buf; + + tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)]; + + wh = mtod(m0, struct ieee80211_frame *); + if (wh->i_fc[1] & IEEE80211_FC1_WEP) { + k = ieee80211_crypto_encap(ni, m0); + if (k == NULL) { + m_freem(m0); + return ENOBUFS; + } + wh = mtod(m0, struct ieee80211_frame *); + } + + data->m = m0; + data->ni = ni; + + flags = 0; + if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { + flags |= RAL_TX_ACK; + + dur = ieee80211_ack_duration(sc->sc_rates, tp->mgmtrate, + ic->ic_flags & IEEE80211_F_SHPREAMBLE); + *(uint16_t *)wh->i_dur = htole16(dur); + + /* tell hardware to add timestamp for probe responses */ + if ((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) == + IEEE80211_FC0_TYPE_MGT && + (wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK) == + IEEE80211_FC0_SUBTYPE_PROBE_RESP) + flags |= RAL_TX_TIMESTAMP; + } + + if (bpf_peers_present(ifp->if_bpf)) { + struct ural_tx_radiotap_header *tap = &sc->sc_txtap; + + tap->wt_flags = 0; + tap->wt_rate = tp->mgmtrate; + tap->wt_chan_freq = htole16(ic->ic_curchan->ic_freq); + tap->wt_chan_flags = htole16(ic->ic_curchan->ic_flags); + tap->wt_antenna = sc->tx_ant; + + bpf_mtap2(ifp->if_bpf, tap, sc->sc_txtap_len, m0); + } + + m_copydata(m0, 0, m0->m_pkthdr.len, data->buf + RAL_TX_DESC_SIZE); + ural_setup_tx_desc(sc, desc, flags, m0->m_pkthdr.len, tp->mgmtrate); + + /* align end on a 2-bytes boundary */ + xferlen = (RAL_TX_DESC_SIZE + m0->m_pkthdr.len + 1) & ~1; + + /* + * No space left in the last URB to store the extra 2 bytes, force + * sending of another URB. + */ + if ((xferlen % 64) == 0) + xferlen += 2; + + DPRINTFN(10, ("sending mgt frame len=%u rate=%u xfer len=%u\n", + m0->m_pkthdr.len, tp->mgmtrate, xferlen)); + + usbd_setup_xfer(data->xfer, sc->sc_tx_pipeh, data, data->buf, + xferlen, USBD_FORCE_SHORT_XFER | USBD_NO_COPY, RAL_TX_TIMEOUT, + ural_txeof); + + error = usbd_transfer(data->xfer); + if (error != USBD_NORMAL_COMPLETION && error != USBD_IN_PROGRESS) { + m_freem(m0); + data->m = NULL; + data->ni = NULL; + return error; + } + + sc->tx_queued++; + sc->tx_cur = (sc->tx_cur + 1) % RAL_TX_LIST_COUNT; + + return 0; +} + +static int +ural_sendprot(struct ural_softc *sc, + const struct mbuf *m, struct ieee80211_node *ni, int prot, int rate) +{ + struct ieee80211com *ic = ni->ni_ic; + const struct ieee80211_frame *wh; + struct ural_tx_desc *desc; + struct ural_tx_data *data; + struct mbuf *mprot; + int protrate, ackrate, pktlen, flags, isshort; + uint16_t dur; + usbd_status error; + + KASSERT(prot == IEEE80211_PROT_RTSCTS || prot == IEEE80211_PROT_CTSONLY, + ("protection %d", prot)); + + wh = mtod(m, const struct ieee80211_frame *); + pktlen = m->m_pkthdr.len + IEEE80211_CRC_LEN; + + protrate = ieee80211_ctl_rate(sc->sc_rates, rate); + ackrate = ieee80211_ack_rate(sc->sc_rates, rate); + + isshort = (ic->ic_flags & IEEE80211_F_SHPREAMBLE) != 0; + dur = ieee80211_compute_duration(sc->sc_rates, pktlen, rate, isshort); + + ieee80211_ack_duration(sc->sc_rates, rate, isshort); + flags = RAL_TX_RETRY(7); + if (prot == IEEE80211_PROT_RTSCTS) { + /* NB: CTS is the same size as an ACK */ + dur += ieee80211_ack_duration(sc->sc_rates, rate, isshort); + flags |= RAL_TX_ACK; + mprot = ieee80211_alloc_rts(ic, wh->i_addr1, wh->i_addr2, dur); + } else { + mprot = ieee80211_alloc_cts(ic, ni->ni_vap->iv_myaddr, dur); + } + if (mprot == NULL) { + /* XXX stat + msg */ + return ENOBUFS; + } + data = &sc->tx_data[sc->tx_cur]; + desc = (struct ural_tx_desc *)data->buf; + + data->m = mprot; + data->ni = ieee80211_ref_node(ni); + m_copydata(mprot, 0, mprot->m_pkthdr.len, data->buf + RAL_TX_DESC_SIZE); + ural_setup_tx_desc(sc, desc, flags, mprot->m_pkthdr.len, protrate); + + usbd_setup_xfer(data->xfer, sc->sc_tx_pipeh, data, data->buf, + /* NB: no roundup necessary */ + RAL_TX_DESC_SIZE + mprot->m_pkthdr.len, + USBD_FORCE_SHORT_XFER | USBD_NO_COPY, RAL_TX_TIMEOUT, ural_txeof); + + error = usbd_transfer(data->xfer); + if (error != USBD_NORMAL_COMPLETION && error != USBD_IN_PROGRESS) { + data->m = NULL; + data->ni = NULL; + return error; + } + + sc->tx_queued++; + sc->tx_cur = (sc->tx_cur + 1) % RAL_TX_LIST_COUNT; + + return 0; +} + +static int +ural_tx_raw(struct ural_softc *sc, struct mbuf *m0, struct ieee80211_node *ni, + const struct ieee80211_bpf_params *params) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ural_tx_desc *desc; + struct ural_tx_data *data; + uint32_t flags; + usbd_status error; + int xferlen, rate; + + KASSERT(params != NULL, ("no raw xmit params")); + + data = &sc->tx_data[sc->tx_cur]; + desc = (struct ural_tx_desc *)data->buf; + + rate = params->ibp_rate0 & IEEE80211_RATE_VAL; + /* XXX validate */ + if (rate == 0) { + m_freem(m0); + return EINVAL; + } + flags = 0; + if ((params->ibp_flags & IEEE80211_BPF_NOACK) == 0) + flags |= RAL_TX_ACK; + if (params->ibp_flags & (IEEE80211_BPF_RTS|IEEE80211_BPF_CTS)) { + error = ural_sendprot(sc, m0, ni, + params->ibp_flags & IEEE80211_BPF_RTS ? + IEEE80211_PROT_RTSCTS : IEEE80211_PROT_CTSONLY, + rate); + if (error) { + m_freem(m0); + return error; + } + flags |= RAL_TX_IFS_SIFS; + } + + if (bpf_peers_present(ifp->if_bpf)) { + struct ural_tx_radiotap_header *tap = &sc->sc_txtap; + + tap->wt_flags = 0; + tap->wt_rate = rate; + tap->wt_chan_freq = htole16(ic->ic_curchan->ic_freq); + tap->wt_chan_flags = htole16(ic->ic_curchan->ic_flags); + tap->wt_antenna = sc->tx_ant; + + bpf_mtap2(ifp->if_bpf, tap, sc->sc_txtap_len, m0); + } + + data->m = m0; + data->ni = ni; + + m_copydata(m0, 0, m0->m_pkthdr.len, data->buf + RAL_TX_DESC_SIZE); + /* XXX need to setup descriptor ourself */ + ural_setup_tx_desc(sc, desc, flags, m0->m_pkthdr.len, rate); + + /* align end on a 2-bytes boundary */ + xferlen = (RAL_TX_DESC_SIZE + m0->m_pkthdr.len + 1) & ~1; + + /* + * No space left in the last URB to store the extra 2 bytes, force + * sending of another URB. + */ + if ((xferlen % 64) == 0) + xferlen += 2; + + DPRINTFN(10, ("sending raw frame len=%u rate=%u xfer len=%u\n", + m0->m_pkthdr.len, rate, xferlen)); + + usbd_setup_xfer(data->xfer, sc->sc_tx_pipeh, data, data->buf, + xferlen, USBD_FORCE_SHORT_XFER | USBD_NO_COPY, RAL_TX_TIMEOUT, + ural_txeof); + + error = usbd_transfer(data->xfer); + if (error != USBD_NORMAL_COMPLETION && error != USBD_IN_PROGRESS) { + m_freem(m0); + data->m = NULL; + data->ni = NULL; + return error; + } + + sc->tx_queued++; + sc->tx_cur = (sc->tx_cur + 1) % RAL_TX_LIST_COUNT; + + return 0; +} + +static int +ural_tx_data(struct ural_softc *sc, struct mbuf *m0, struct ieee80211_node *ni) +{ + struct ieee80211vap *vap = ni->ni_vap; + struct ieee80211com *ic = ni->ni_ic; + struct ifnet *ifp = sc->sc_ifp; + struct ural_tx_desc *desc; + struct ural_tx_data *data; + struct ieee80211_frame *wh; + const struct ieee80211_txparam *tp; + struct ieee80211_key *k; + uint32_t flags = 0; + uint16_t dur; + usbd_status error; + int xferlen, rate; + + wh = mtod(m0, struct ieee80211_frame *); + + tp = &vap->iv_txparms[ieee80211_chan2mode(ni->ni_chan)]; + if (IEEE80211_IS_MULTICAST(wh->i_addr1)) + rate = tp->mcastrate; + else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) + rate = tp->ucastrate; + else + rate = ni->ni_txrate; + + if (wh->i_fc[1] & IEEE80211_FC1_WEP) { + k = ieee80211_crypto_encap(ni, m0); + if (k == NULL) { + m_freem(m0); + return ENOBUFS; + } + /* packet header may have moved, reset our local pointer */ + wh = mtod(m0, struct ieee80211_frame *); + } + + if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { + int prot = IEEE80211_PROT_NONE; + if (m0->m_pkthdr.len + IEEE80211_CRC_LEN > vap->iv_rtsthreshold) + prot = IEEE80211_PROT_RTSCTS; + else if ((ic->ic_flags & IEEE80211_F_USEPROT) && + ieee80211_rate2phytype(sc->sc_rates, rate) == IEEE80211_T_OFDM) + prot = ic->ic_protmode; + if (prot != IEEE80211_PROT_NONE) { + error = ural_sendprot(sc, m0, ni, prot, rate); + if (error) { + m_freem(m0); + return error; + } + flags |= RAL_TX_IFS_SIFS; + } + } + + data = &sc->tx_data[sc->tx_cur]; + desc = (struct ural_tx_desc *)data->buf; + + data->m = m0; + data->ni = ni; + + if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { + flags |= RAL_TX_ACK; + flags |= RAL_TX_RETRY(7); + + dur = ieee80211_ack_duration(sc->sc_rates, rate, + ic->ic_flags & IEEE80211_F_SHPREAMBLE); + *(uint16_t *)wh->i_dur = htole16(dur); + } + + if (bpf_peers_present(ifp->if_bpf)) { + struct ural_tx_radiotap_header *tap = &sc->sc_txtap; + + tap->wt_flags = 0; + tap->wt_rate = rate; + tap->wt_chan_freq = htole16(ic->ic_curchan->ic_freq); + tap->wt_chan_flags = htole16(ic->ic_curchan->ic_flags); + tap->wt_antenna = sc->tx_ant; + + bpf_mtap2(ifp->if_bpf, tap, sc->sc_txtap_len, m0); + } + + m_copydata(m0, 0, m0->m_pkthdr.len, data->buf + RAL_TX_DESC_SIZE); + ural_setup_tx_desc(sc, desc, flags, m0->m_pkthdr.len, rate); + + /* align end on a 2-bytes boundary */ + xferlen = (RAL_TX_DESC_SIZE + m0->m_pkthdr.len + 1) & ~1; + + /* + * No space left in the last URB to store the extra 2 bytes, force + * sending of another URB. + */ + if ((xferlen % 64) == 0) + xferlen += 2; + + DPRINTFN(10, ("sending data frame len=%u rate=%u xfer len=%u\n", + m0->m_pkthdr.len, rate, xferlen)); + + usbd_setup_xfer(data->xfer, sc->sc_tx_pipeh, data, data->buf, + xferlen, USBD_FORCE_SHORT_XFER | USBD_NO_COPY, RAL_TX_TIMEOUT, + ural_txeof); + + error = usbd_transfer(data->xfer); + if (error != USBD_NORMAL_COMPLETION && error != USBD_IN_PROGRESS) { + m_freem(m0); + data->m = NULL; + data->ni = NULL; + return error; + } + + sc->tx_queued++; + sc->tx_cur = (sc->tx_cur + 1) % RAL_TX_LIST_COUNT; + + return 0; +} + +static void +ural_start(struct ifnet *ifp) +{ + struct ural_softc *sc = ifp->if_softc; + struct ieee80211_node *ni; + struct mbuf *m; + + for (;;) { + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + if (m == NULL) + break; + if (sc->tx_queued >= RAL_TX_LIST_COUNT-1) { + IFQ_DRV_PREPEND(&ifp->if_snd, m); + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + break; + } + ni = (struct ieee80211_node *) m->m_pkthdr.rcvif; + m = ieee80211_encap(ni, m); + if (m == NULL) { + ieee80211_free_node(ni); + continue; + } + if (ural_tx_data(sc, m, ni) != 0) { + ieee80211_free_node(ni); + ifp->if_oerrors++; + break; + } + sc->sc_tx_timer = 5; + callout_reset(&sc->watchdog_ch, hz, ural_watchdog, sc); + } +} + +static void +ural_watchdog(void *arg) +{ + struct ural_softc *sc = (struct ural_softc *)arg; + + RAL_LOCK(sc); + + if (sc->sc_tx_timer > 0) { + if (--sc->sc_tx_timer == 0) { + device_printf(sc->sc_dev, "device timeout\n"); + /*ural_init(sc); XXX needs a process context! */ + sc->sc_ifp->if_oerrors++; + RAL_UNLOCK(sc); + return; + } + callout_reset(&sc->watchdog_ch, hz, ural_watchdog, sc); + } + + RAL_UNLOCK(sc); +} + +static int +ural_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) +{ + struct ural_softc *sc = ifp->if_softc; + struct ieee80211com *ic = ifp->if_l2com; + struct ifreq *ifr = (struct ifreq *) data; + int error = 0, startall = 0; + + switch (cmd) { + case SIOCSIFFLAGS: + RAL_LOCK(sc); + if (ifp->if_flags & IFF_UP) { + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) { + ural_init_locked(sc); + startall = 1; + } else + ural_update_promisc(sc); + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + ural_stop(sc); + } + RAL_UNLOCK(sc); + if (startall) + ieee80211_start_all(ic); + break; + case SIOCGIFMEDIA: + case SIOCSIFMEDIA: + error = ifmedia_ioctl(ifp, ifr, &ic->ic_media, cmd); + break; + default: + error = ether_ioctl(ifp, cmd, data); + break; + } + return error; +} + +static void +ural_set_testmode(struct ural_softc *sc) +{ + usb_device_request_t req; + usbd_status error; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = RAL_VENDOR_REQUEST; + USETW(req.wValue, 4); + USETW(req.wIndex, 1); + USETW(req.wLength, 0); + + error = usbd_do_request(sc->sc_udev, &req, NULL); + if (error != 0) { + device_printf(sc->sc_dev, "could not set test mode: %s\n", + usbd_errstr(error)); + } +} + +static void +ural_eeprom_read(struct ural_softc *sc, uint16_t addr, void *buf, int len) +{ + usb_device_request_t req; + usbd_status error; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = RAL_READ_EEPROM; + USETW(req.wValue, 0); + USETW(req.wIndex, addr); + USETW(req.wLength, len); + + error = usbd_do_request(sc->sc_udev, &req, buf); + if (error != 0) { + device_printf(sc->sc_dev, "could not read EEPROM: %s\n", + usbd_errstr(error)); + } +} + +static uint16_t +ural_read(struct ural_softc *sc, uint16_t reg) +{ + usb_device_request_t req; + usbd_status error; + uint16_t val; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = RAL_READ_MAC; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, sizeof (uint16_t)); + + error = usbd_do_request(sc->sc_udev, &req, &val); + if (error != 0) { + device_printf(sc->sc_dev, "could not read MAC register: %s\n", + usbd_errstr(error)); + return 0; + } + + return le16toh(val); +} + +static void +ural_read_multi(struct ural_softc *sc, uint16_t reg, void *buf, int len) +{ + usb_device_request_t req; + usbd_status error; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = RAL_READ_MULTI_MAC; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, len); + + error = usbd_do_request(sc->sc_udev, &req, buf); + if (error != 0) { + device_printf(sc->sc_dev, "could not read MAC register: %s\n", + usbd_errstr(error)); + } +} + +static void +ural_write(struct ural_softc *sc, uint16_t reg, uint16_t val) +{ + usb_device_request_t req; + usbd_status error; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = RAL_WRITE_MAC; + USETW(req.wValue, val); + USETW(req.wIndex, reg); + USETW(req.wLength, 0); + + error = usbd_do_request(sc->sc_udev, &req, NULL); + if (error != 0) { + device_printf(sc->sc_dev, "could not write MAC register: %s\n", + usbd_errstr(error)); + } +} + +static void +ural_write_multi(struct ural_softc *sc, uint16_t reg, void *buf, int len) +{ + usb_device_request_t req; + usbd_status error; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = RAL_WRITE_MULTI_MAC; + USETW(req.wValue, 0); + USETW(req.wIndex, reg); + USETW(req.wLength, len); + + error = usbd_do_request(sc->sc_udev, &req, buf); + if (error != 0) { + device_printf(sc->sc_dev, "could not write MAC register: %s\n", + usbd_errstr(error)); + } +} + +static void +ural_bbp_write(struct ural_softc *sc, uint8_t reg, uint8_t val) +{ + uint16_t tmp; + int ntries; + + for (ntries = 0; ntries < 5; ntries++) { + if (!(ural_read(sc, RAL_PHY_CSR8) & RAL_BBP_BUSY)) + break; + } + if (ntries == 5) { + device_printf(sc->sc_dev, "could not write to BBP\n"); + return; + } + + tmp = reg << 8 | val; + ural_write(sc, RAL_PHY_CSR7, tmp); +} + +static uint8_t +ural_bbp_read(struct ural_softc *sc, uint8_t reg) +{ + uint16_t val; + int ntries; + + val = RAL_BBP_WRITE | reg << 8; + ural_write(sc, RAL_PHY_CSR7, val); + + for (ntries = 0; ntries < 5; ntries++) { + if (!(ural_read(sc, RAL_PHY_CSR8) & RAL_BBP_BUSY)) + break; + } + if (ntries == 5) { + device_printf(sc->sc_dev, "could not read BBP\n"); + return 0; + } + + return ural_read(sc, RAL_PHY_CSR7) & 0xff; +} + +static void +ural_rf_write(struct ural_softc *sc, uint8_t reg, uint32_t val) +{ + uint32_t tmp; + int ntries; + + for (ntries = 0; ntries < 5; ntries++) { + if (!(ural_read(sc, RAL_PHY_CSR10) & RAL_RF_LOBUSY)) + break; + } + if (ntries == 5) { + device_printf(sc->sc_dev, "could not write to RF\n"); + return; + } + + tmp = RAL_RF_BUSY | RAL_RF_20BIT | (val & 0xfffff) << 2 | (reg & 0x3); + ural_write(sc, RAL_PHY_CSR9, tmp & 0xffff); + ural_write(sc, RAL_PHY_CSR10, tmp >> 16); + + /* remember last written value in sc */ + sc->rf_regs[reg] = val; + + DPRINTFN(15, ("RF R[%u] <- 0x%05x\n", reg & 0x3, val & 0xfffff)); +} + +/* ARGUSED */ +static struct ieee80211_node * +ural_node_alloc(struct ieee80211vap *vap __unused, + const uint8_t mac[IEEE80211_ADDR_LEN] __unused) +{ + struct ural_node *un; + + un = malloc(sizeof(struct ural_node), M_80211_NODE, M_NOWAIT | M_ZERO); + return un != NULL ? &un->ni : NULL; +} + +static void +ural_newassoc(struct ieee80211_node *ni, int isnew) +{ + struct ieee80211vap *vap = ni->ni_vap; + + ieee80211_amrr_node_init(&URAL_VAP(vap)->amrr, &URAL_NODE(ni)->amn, ni); +} + +static void +ural_scan_start(struct ieee80211com *ic) +{ + struct ural_softc *sc = ic->ic_ifp->if_softc; + + usb_rem_task(sc->sc_udev, &sc->sc_scantask); + + /* do it in a process context */ + sc->sc_scan_action = URAL_SCAN_START; + usb_add_task(sc->sc_udev, &sc->sc_scantask, USB_TASKQ_DRIVER); + +} + +static void +ural_scan_end(struct ieee80211com *ic) +{ + struct ural_softc *sc = ic->ic_ifp->if_softc; + + usb_rem_task(sc->sc_udev, &sc->sc_scantask); + + /* do it in a process context */ + sc->sc_scan_action = URAL_SCAN_END; + usb_add_task(sc->sc_udev, &sc->sc_scantask, USB_TASKQ_DRIVER); + +} + +static void +ural_set_channel(struct ieee80211com *ic) +{ + + struct ural_softc *sc = ic->ic_ifp->if_softc; + + usb_rem_task(sc->sc_udev, &sc->sc_scantask); + + /* do it in a process context */ + sc->sc_scan_action = URAL_SET_CHANNEL; + usb_add_task(sc->sc_udev, &sc->sc_scantask, USB_TASKQ_DRIVER); + + sc->sc_rates = ieee80211_get_ratetable(ic->ic_curchan); +} + +static void +ural_set_chan(struct ural_softc *sc, struct ieee80211_channel *c) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + uint8_t power, tmp; + u_int i, chan; + + chan = ieee80211_chan2ieee(ic, c); + if (chan == 0 || chan == IEEE80211_CHAN_ANY) + return; + + if (IEEE80211_IS_CHAN_2GHZ(c)) + power = min(sc->txpow[chan - 1], 31); + else + power = 31; + + /* adjust txpower using ifconfig settings */ + power -= (100 - ic->ic_txpowlimit) / 8; + + DPRINTFN(2, ("setting channel to %u, txpower to %u\n", chan, power)); + + switch (sc->rf_rev) { + case RAL_RF_2522: + ural_rf_write(sc, RAL_RF1, 0x00814); + ural_rf_write(sc, RAL_RF2, ural_rf2522_r2[chan - 1]); + ural_rf_write(sc, RAL_RF3, power << 7 | 0x00040); + break; + + case RAL_RF_2523: + ural_rf_write(sc, RAL_RF1, 0x08804); + ural_rf_write(sc, RAL_RF2, ural_rf2523_r2[chan - 1]); + ural_rf_write(sc, RAL_RF3, power << 7 | 0x38044); + ural_rf_write(sc, RAL_RF4, (chan == 14) ? 0x00280 : 0x00286); + break; + + case RAL_RF_2524: + ural_rf_write(sc, RAL_RF1, 0x0c808); + ural_rf_write(sc, RAL_RF2, ural_rf2524_r2[chan - 1]); + ural_rf_write(sc, RAL_RF3, power << 7 | 0x00040); + ural_rf_write(sc, RAL_RF4, (chan == 14) ? 0x00280 : 0x00286); + break; + + case RAL_RF_2525: + ural_rf_write(sc, RAL_RF1, 0x08808); + ural_rf_write(sc, RAL_RF2, ural_rf2525_hi_r2[chan - 1]); + ural_rf_write(sc, RAL_RF3, power << 7 | 0x18044); + ural_rf_write(sc, RAL_RF4, (chan == 14) ? 0x00280 : 0x00286); + + ural_rf_write(sc, RAL_RF1, 0x08808); + ural_rf_write(sc, RAL_RF2, ural_rf2525_r2[chan - 1]); + ural_rf_write(sc, RAL_RF3, power << 7 | 0x18044); + ural_rf_write(sc, RAL_RF4, (chan == 14) ? 0x00280 : 0x00286); + break; + + case RAL_RF_2525E: + ural_rf_write(sc, RAL_RF1, 0x08808); + ural_rf_write(sc, RAL_RF2, ural_rf2525e_r2[chan - 1]); + ural_rf_write(sc, RAL_RF3, power << 7 | 0x18044); + ural_rf_write(sc, RAL_RF4, (chan == 14) ? 0x00286 : 0x00282); + break; + + case RAL_RF_2526: + ural_rf_write(sc, RAL_RF2, ural_rf2526_hi_r2[chan - 1]); + ural_rf_write(sc, RAL_RF4, (chan & 1) ? 0x00386 : 0x00381); + ural_rf_write(sc, RAL_RF1, 0x08804); + + ural_rf_write(sc, RAL_RF2, ural_rf2526_r2[chan - 1]); + ural_rf_write(sc, RAL_RF3, power << 7 | 0x18044); + ural_rf_write(sc, RAL_RF4, (chan & 1) ? 0x00386 : 0x00381); + break; + + /* dual-band RF */ + case RAL_RF_5222: + for (i = 0; ural_rf5222[i].chan != chan; i++); + + ural_rf_write(sc, RAL_RF1, ural_rf5222[i].r1); + ural_rf_write(sc, RAL_RF2, ural_rf5222[i].r2); + ural_rf_write(sc, RAL_RF3, power << 7 | 0x00040); + ural_rf_write(sc, RAL_RF4, ural_rf5222[i].r4); + break; + } + + if (ic->ic_opmode != IEEE80211_M_MONITOR && + (ic->ic_flags & IEEE80211_F_SCAN) == 0) { + /* set Japan filter bit for channel 14 */ + tmp = ural_bbp_read(sc, 70); + + tmp &= ~RAL_JAPAN_FILTER; + if (chan == 14) + tmp |= RAL_JAPAN_FILTER; + + ural_bbp_write(sc, 70, tmp); + + /* clear CRC errors */ + ural_read(sc, RAL_STA_CSR0); + + DELAY(10000); + ural_disable_rf_tune(sc); + } + + /* XXX doesn't belong here */ + /* update basic rate set */ + ural_set_basicrates(sc, c); +} + +/* + * Disable RF auto-tuning. + */ +static void +ural_disable_rf_tune(struct ural_softc *sc) +{ + uint32_t tmp; + + if (sc->rf_rev != RAL_RF_2523) { + tmp = sc->rf_regs[RAL_RF1] & ~RAL_RF1_AUTOTUNE; + ural_rf_write(sc, RAL_RF1, tmp); + } + + tmp = sc->rf_regs[RAL_RF3] & ~RAL_RF3_AUTOTUNE; + ural_rf_write(sc, RAL_RF3, tmp); + + DPRINTFN(2, ("disabling RF autotune\n")); +} + +/* + * Refer to IEEE Std 802.11-1999 pp. 123 for more information on TSF + * synchronization. + */ +static void +ural_enable_tsf_sync(struct ural_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); + uint16_t logcwmin, preload, tmp; + + /* first, disable TSF synchronization */ + ural_write(sc, RAL_TXRX_CSR19, 0); + + tmp = (16 * vap->iv_bss->ni_intval) << 4; + ural_write(sc, RAL_TXRX_CSR18, tmp); + + logcwmin = (ic->ic_opmode == IEEE80211_M_IBSS) ? 2 : 0; + preload = (ic->ic_opmode == IEEE80211_M_IBSS) ? 320 : 6; + tmp = logcwmin << 12 | preload; + ural_write(sc, RAL_TXRX_CSR20, tmp); + + /* finally, enable TSF synchronization */ + tmp = RAL_ENABLE_TSF | RAL_ENABLE_TBCN; + if (ic->ic_opmode == IEEE80211_M_STA) + tmp |= RAL_ENABLE_TSF_SYNC(1); + else + tmp |= RAL_ENABLE_TSF_SYNC(2) | RAL_ENABLE_BEACON_GENERATOR; + ural_write(sc, RAL_TXRX_CSR19, tmp); + + DPRINTF(("enabling TSF synchronization\n")); +} + +static void +ural_update_slot(struct ifnet *ifp) +{ + struct ural_softc *sc = ifp->if_softc; + struct ieee80211com *ic = ifp->if_l2com; + uint16_t slottime, sifs, eifs; + + slottime = (ic->ic_flags & IEEE80211_F_SHSLOT) ? 9 : 20; + + /* + * These settings may sound a bit inconsistent but this is what the + * reference driver does. + */ + if (ic->ic_curmode == IEEE80211_MODE_11B) { + sifs = 16 - RAL_RXTX_TURNAROUND; + eifs = 364; + } else { + sifs = 10 - RAL_RXTX_TURNAROUND; + eifs = 64; + } + + ural_write(sc, RAL_MAC_CSR10, slottime); + ural_write(sc, RAL_MAC_CSR11, sifs); + ural_write(sc, RAL_MAC_CSR12, eifs); +} + +static void +ural_set_txpreamble(struct ural_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + uint16_t tmp; + + tmp = ural_read(sc, RAL_TXRX_CSR10); + + tmp &= ~RAL_SHORT_PREAMBLE; + if (ic->ic_flags & IEEE80211_F_SHPREAMBLE) + tmp |= RAL_SHORT_PREAMBLE; + + ural_write(sc, RAL_TXRX_CSR10, tmp); +} + +static void +ural_set_basicrates(struct ural_softc *sc, const struct ieee80211_channel *c) +{ + /* XXX wrong, take from rate set */ + /* update basic rate set */ + if (IEEE80211_IS_CHAN_5GHZ(c)) { + /* 11a basic rates: 6, 12, 24Mbps */ + ural_write(sc, RAL_TXRX_CSR11, 0x150); + } else if (IEEE80211_IS_CHAN_ANYG(c)) { + /* 11g basic rates: 1, 2, 5.5, 11, 6, 12, 24Mbps */ + ural_write(sc, RAL_TXRX_CSR11, 0x15f); + } else { + /* 11b basic rates: 1, 2Mbps */ + ural_write(sc, RAL_TXRX_CSR11, 0x3); + } +} + +static void +ural_set_bssid(struct ural_softc *sc, const uint8_t *bssid) +{ + uint16_t tmp; + + tmp = bssid[0] | bssid[1] << 8; + ural_write(sc, RAL_MAC_CSR5, tmp); + + tmp = bssid[2] | bssid[3] << 8; + ural_write(sc, RAL_MAC_CSR6, tmp); + + tmp = bssid[4] | bssid[5] << 8; + ural_write(sc, RAL_MAC_CSR7, tmp); + + DPRINTF(("setting BSSID to %6D\n", bssid, ":")); +} + +static void +ural_set_macaddr(struct ural_softc *sc, uint8_t *addr) +{ + uint16_t tmp; + + tmp = addr[0] | addr[1] << 8; + ural_write(sc, RAL_MAC_CSR2, tmp); + + tmp = addr[2] | addr[3] << 8; + ural_write(sc, RAL_MAC_CSR3, tmp); + + tmp = addr[4] | addr[5] << 8; + ural_write(sc, RAL_MAC_CSR4, tmp); + + DPRINTF(("setting MAC address to %6D\n", addr, ":")); +} + +static void +ural_update_promisc(struct ural_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + uint32_t tmp; + + tmp = ural_read(sc, RAL_TXRX_CSR2); + + tmp &= ~RAL_DROP_NOT_TO_ME; + if (!(ifp->if_flags & IFF_PROMISC)) + tmp |= RAL_DROP_NOT_TO_ME; + + ural_write(sc, RAL_TXRX_CSR2, tmp); + + DPRINTF(("%s promiscuous mode\n", (ifp->if_flags & IFF_PROMISC) ? + "entering" : "leaving")); +} + +static const char * +ural_get_rf(int rev) +{ + switch (rev) { + case RAL_RF_2522: return "RT2522"; + case RAL_RF_2523: return "RT2523"; + case RAL_RF_2524: return "RT2524"; + case RAL_RF_2525: return "RT2525"; + case RAL_RF_2525E: return "RT2525e"; + case RAL_RF_2526: return "RT2526"; + case RAL_RF_5222: return "RT5222"; + default: return "unknown"; + } +} + +static void +ural_read_eeprom(struct ural_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + uint16_t val; + + ural_eeprom_read(sc, RAL_EEPROM_CONFIG0, &val, 2); + val = le16toh(val); + sc->rf_rev = (val >> 11) & 0x7; + sc->hw_radio = (val >> 10) & 0x1; + sc->led_mode = (val >> 6) & 0x7; + sc->rx_ant = (val >> 4) & 0x3; + sc->tx_ant = (val >> 2) & 0x3; + sc->nb_ant = val & 0x3; + + /* read MAC address */ + ural_eeprom_read(sc, RAL_EEPROM_ADDRESS, ic->ic_myaddr, 6); + + /* read default values for BBP registers */ + ural_eeprom_read(sc, RAL_EEPROM_BBP_BASE, sc->bbp_prom, 2 * 16); + + /* read Tx power for all b/g channels */ + ural_eeprom_read(sc, RAL_EEPROM_TXPOWER, sc->txpow, 14); +} + +static int +ural_bbp_init(struct ural_softc *sc) +{ +#define N(a) (sizeof (a) / sizeof ((a)[0])) + int i, ntries; + + /* wait for BBP to be ready */ + for (ntries = 0; ntries < 100; ntries++) { + if (ural_bbp_read(sc, RAL_BBP_VERSION) != 0) + break; + DELAY(1000); + } + if (ntries == 100) { + device_printf(sc->sc_dev, "timeout waiting for BBP\n"); + return EIO; + } + + /* initialize BBP registers to default values */ + for (i = 0; i < N(ural_def_bbp); i++) + ural_bbp_write(sc, ural_def_bbp[i].reg, ural_def_bbp[i].val); + +#if 0 + /* initialize BBP registers to values stored in EEPROM */ + for (i = 0; i < 16; i++) { + if (sc->bbp_prom[i].reg == 0xff) + continue; + ural_bbp_write(sc, sc->bbp_prom[i].reg, sc->bbp_prom[i].val); + } +#endif + + return 0; +#undef N +} + +static void +ural_set_txantenna(struct ural_softc *sc, int antenna) +{ + uint16_t tmp; + uint8_t tx; + + tx = ural_bbp_read(sc, RAL_BBP_TX) & ~RAL_BBP_ANTMASK; + if (antenna == 1) + tx |= RAL_BBP_ANTA; + else if (antenna == 2) + tx |= RAL_BBP_ANTB; + else + tx |= RAL_BBP_DIVERSITY; + + /* need to force I/Q flip for RF 2525e, 2526 and 5222 */ + if (sc->rf_rev == RAL_RF_2525E || sc->rf_rev == RAL_RF_2526 || + sc->rf_rev == RAL_RF_5222) + tx |= RAL_BBP_FLIPIQ; + + ural_bbp_write(sc, RAL_BBP_TX, tx); + + /* update values in PHY_CSR5 and PHY_CSR6 */ + tmp = ural_read(sc, RAL_PHY_CSR5) & ~0x7; + ural_write(sc, RAL_PHY_CSR5, tmp | (tx & 0x7)); + + tmp = ural_read(sc, RAL_PHY_CSR6) & ~0x7; + ural_write(sc, RAL_PHY_CSR6, tmp | (tx & 0x7)); +} + +static void +ural_set_rxantenna(struct ural_softc *sc, int antenna) +{ + uint8_t rx; + + rx = ural_bbp_read(sc, RAL_BBP_RX) & ~RAL_BBP_ANTMASK; + if (antenna == 1) + rx |= RAL_BBP_ANTA; + else if (antenna == 2) + rx |= RAL_BBP_ANTB; + else + rx |= RAL_BBP_DIVERSITY; + + /* need to force no I/Q flip for RF 2525e and 2526 */ + if (sc->rf_rev == RAL_RF_2525E || sc->rf_rev == RAL_RF_2526) + rx &= ~RAL_BBP_FLIPIQ; + + ural_bbp_write(sc, RAL_BBP_RX, rx); +} + +static void +ural_init_locked(struct ural_softc *sc) +{ +#define N(a) (sizeof (a) / sizeof ((a)[0])) + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ural_rx_data *data; + uint16_t tmp; + usbd_status error; + int i, ntries; + + ural_set_testmode(sc); + ural_write(sc, 0x308, 0x00f0); /* XXX magic */ + + ural_stop(sc); + + /* initialize MAC registers to default values */ + for (i = 0; i < N(ural_def_mac); i++) + ural_write(sc, ural_def_mac[i].reg, ural_def_mac[i].val); + + /* wait for BBP and RF to wake up (this can take a long time!) */ + for (ntries = 0; ntries < 100; ntries++) { + tmp = ural_read(sc, RAL_MAC_CSR17); + if ((tmp & (RAL_BBP_AWAKE | RAL_RF_AWAKE)) == + (RAL_BBP_AWAKE | RAL_RF_AWAKE)) + break; + DELAY(1000); + } + if (ntries == 100) { + device_printf(sc->sc_dev, + "timeout waiting for BBP/RF to wakeup\n"); + goto fail; + } + + /* we're ready! */ + ural_write(sc, RAL_MAC_CSR1, RAL_HOST_READY); + + /* set basic rate set (will be updated later) */ + ural_write(sc, RAL_TXRX_CSR11, 0x15f); + + if (ural_bbp_init(sc) != 0) + goto fail; + + ural_set_chan(sc, ic->ic_curchan); + + /* clear statistic registers (STA_CSR0 to STA_CSR10) */ + ural_read_multi(sc, RAL_STA_CSR0, sc->sta, sizeof sc->sta); + + ural_set_txantenna(sc, sc->tx_ant); + ural_set_rxantenna(sc, sc->rx_ant); + + IEEE80211_ADDR_COPY(ic->ic_myaddr, IF_LLADDR(ifp)); + ural_set_macaddr(sc, ic->ic_myaddr); + + /* + * Allocate xfer for AMRR statistics requests. + */ + sc->amrr_xfer = usbd_alloc_xfer(sc->sc_udev); + if (sc->amrr_xfer == NULL) { + device_printf(sc->sc_dev, "could not allocate AMRR xfer\n"); + goto fail; + } + + /* + * Open Tx and Rx USB bulk pipes. + */ + error = usbd_open_pipe(sc->sc_iface, sc->sc_tx_no, USBD_EXCLUSIVE_USE, + &sc->sc_tx_pipeh); + if (error != 0) { + device_printf(sc->sc_dev, "could not open Tx pipe: %s\n", + usbd_errstr(error)); + goto fail; + } + + error = usbd_open_pipe(sc->sc_iface, sc->sc_rx_no, USBD_EXCLUSIVE_USE, + &sc->sc_rx_pipeh); + if (error != 0) { + device_printf(sc->sc_dev, "could not open Rx pipe: %s\n", + usbd_errstr(error)); + goto fail; + } + + /* + * Allocate Tx and Rx xfer queues. + */ + error = ural_alloc_tx_list(sc); + if (error != 0) { + device_printf(sc->sc_dev, "could not allocate Tx list\n"); + goto fail; + } + + error = ural_alloc_rx_list(sc); + if (error != 0) { + device_printf(sc->sc_dev, "could not allocate Rx list\n"); + goto fail; + } + + /* + * Start up the receive pipe. + */ + for (i = 0; i < RAL_RX_LIST_COUNT; i++) { + data = &sc->rx_data[i]; + + usbd_setup_xfer(data->xfer, sc->sc_rx_pipeh, data, data->buf, + MCLBYTES, USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT, ural_rxeof); + usbd_transfer(data->xfer); + } + + /* kick Rx */ + tmp = RAL_DROP_PHY | RAL_DROP_CRC; + if (ic->ic_opmode != IEEE80211_M_MONITOR) { + tmp |= RAL_DROP_CTL | RAL_DROP_BAD_VERSION; + if (ic->ic_opmode != IEEE80211_M_HOSTAP) + tmp |= RAL_DROP_TODS; + if (!(ifp->if_flags & IFF_PROMISC)) + tmp |= RAL_DROP_NOT_TO_ME; + } + ural_write(sc, RAL_TXRX_CSR2, tmp); + + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + ifp->if_drv_flags |= IFF_DRV_RUNNING; + return; + +fail: ural_stop(sc); +#undef N +} + +static void +ural_init(void *priv) +{ + struct ural_softc *sc = priv; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + RAL_LOCK(sc); + ural_init_locked(sc); + RAL_UNLOCK(sc); + + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + ieee80211_start_all(ic); /* start all vap's */ +} + +static void +ural_stop(void *priv) +{ + struct ural_softc *sc = priv; + struct ifnet *ifp = sc->sc_ifp; + + sc->sc_tx_timer = 0; + ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); + + /* disable Rx */ + ural_write(sc, RAL_TXRX_CSR2, RAL_DISABLE_RX); + + /* reset ASIC and BBP (but won't reset MAC registers!) */ + ural_write(sc, RAL_MAC_CSR1, RAL_RESET_ASIC | RAL_RESET_BBP); + ural_write(sc, RAL_MAC_CSR1, 0); + + if (sc->amrr_xfer != NULL) { + usbd_free_xfer(sc->amrr_xfer); + sc->amrr_xfer = NULL; + } + + if (sc->sc_rx_pipeh != NULL) { + usbd_abort_pipe(sc->sc_rx_pipeh); + usbd_close_pipe(sc->sc_rx_pipeh); + sc->sc_rx_pipeh = NULL; + } + + if (sc->sc_tx_pipeh != NULL) { + usbd_abort_pipe(sc->sc_tx_pipeh); + usbd_close_pipe(sc->sc_tx_pipeh); + sc->sc_tx_pipeh = NULL; + } + + ural_free_rx_list(sc); + ural_free_tx_list(sc); +} + +static int +ural_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, + const struct ieee80211_bpf_params *params) +{ + struct ieee80211com *ic = ni->ni_ic; + struct ifnet *ifp = ic->ic_ifp; + struct ural_softc *sc = ifp->if_softc; + + /* prevent management frames from being sent if we're not ready */ + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + m_freem(m); + ieee80211_free_node(ni); + return ENETDOWN; + } + if (sc->tx_queued >= RAL_TX_LIST_COUNT) { + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + m_freem(m); + ieee80211_free_node(ni); + return EIO; + } + + ifp->if_opackets++; + + if (params == NULL) { + /* + * Legacy path; interpret frame contents to decide + * precisely how to send the frame. + */ + if (ural_tx_mgt(sc, m, ni) != 0) + goto bad; + } else { + /* + * Caller supplied explicit parameters to use in + * sending the frame. + */ + if (ural_tx_raw(sc, m, ni, params) != 0) + goto bad; + } + sc->sc_tx_timer = 5; + callout_reset(&sc->watchdog_ch, hz, ural_watchdog, sc); + + return 0; +bad: + ifp->if_oerrors++; + ieee80211_free_node(ni); + return EIO; /* XXX */ +} + +static void +ural_amrr_start(struct ural_softc *sc, struct ieee80211_node *ni) +{ + struct ieee80211vap *vap = ni->ni_vap; + struct ural_vap *uvp = URAL_VAP(vap); + + /* clear statistic registers (STA_CSR0 to STA_CSR10) */ + ural_read_multi(sc, RAL_STA_CSR0, sc->sta, sizeof sc->sta); + + ieee80211_amrr_node_init(&uvp->amrr, &URAL_NODE(ni)->amn, ni); + + callout_reset(&uvp->amrr_ch, hz, ural_amrr_timeout, vap); +} + +static void +ural_amrr_timeout(void *arg) +{ + struct ieee80211vap *vap = arg; + struct ural_softc *sc = vap->iv_ic->ic_ifp->if_softc; + usb_device_request_t req; + + /* + * Asynchronously read statistic registers (cleared by read). + */ + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = RAL_READ_MULTI_MAC; + USETW(req.wValue, 0); + USETW(req.wIndex, RAL_STA_CSR0); + USETW(req.wLength, sizeof sc->sta); + + usbd_setup_default_xfer(sc->amrr_xfer, sc->sc_udev, vap, + USBD_DEFAULT_TIMEOUT, &req, sc->sta, sizeof sc->sta, 0, + ural_amrr_update); + (void)usbd_transfer(sc->amrr_xfer); +} + +static void +ural_amrr_update(usbd_xfer_handle xfer, usbd_private_handle priv, + usbd_status status) +{ + struct ieee80211vap *vap = priv; + struct ural_vap *uvp = URAL_VAP(vap); + struct ifnet *ifp = vap->iv_ic->ic_ifp; + struct ural_softc *sc = ifp->if_softc; + struct ieee80211_node *ni = vap->iv_bss; + int ok, fail; + + if (status != USBD_NORMAL_COMPLETION) { + device_printf(sc->sc_dev, "could not retrieve Tx statistics - " + "cancelling automatic rate control\n"); + return; + } + + ok = sc->sta[7] + /* TX ok w/o retry */ + sc->sta[8]; /* TX ok w/ retry */ + fail = sc->sta[9]; /* TX retry-fail count */ + + ieee80211_amrr_tx_update(&URAL_NODE(ni)->amn, + ok+fail, ok, sc->sta[8] + fail); + (void) ieee80211_amrr_choose(ni, &URAL_NODE(ni)->amn); + + ifp->if_oerrors += fail; /* count TX retry-fail as Tx errors */ + + callout_reset(&uvp->amrr_ch, hz, ural_amrr_timeout, vap); +} diff --git a/sys/legacy/dev/usb/if_uralreg.h b/sys/legacy/dev/usb/if_uralreg.h new file mode 100644 index 0000000..428089f --- /dev/null +++ b/sys/legacy/dev/usb/if_uralreg.h @@ -0,0 +1,210 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2005, 2006 + * Damien Bergamini <damien.bergamini@free.fr> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define RAL_NOISE_FLOOR -95 +#define RAL_RSSI_CORR 120 + +#define RAL_RX_DESC_SIZE (sizeof (struct ural_rx_desc)) +#define RAL_TX_DESC_SIZE (sizeof (struct ural_tx_desc)) + +#define RAL_CONFIG_NO 1 +#define RAL_IFACE_INDEX 0 + +#define RAL_VENDOR_REQUEST 0x01 +#define RAL_WRITE_MAC 0x02 +#define RAL_READ_MAC 0x03 +#define RAL_WRITE_MULTI_MAC 0x06 +#define RAL_READ_MULTI_MAC 0x07 +#define RAL_READ_EEPROM 0x09 + +/* + * MAC registers. + */ +#define RAL_MAC_CSR0 0x0400 /* ASIC Version */ +#define RAL_MAC_CSR1 0x0402 /* System control */ +#define RAL_MAC_CSR2 0x0404 /* MAC addr0 */ +#define RAL_MAC_CSR3 0x0406 /* MAC addr1 */ +#define RAL_MAC_CSR4 0x0408 /* MAC addr2 */ +#define RAL_MAC_CSR5 0x040a /* BSSID0 */ +#define RAL_MAC_CSR6 0x040c /* BSSID1 */ +#define RAL_MAC_CSR7 0x040e /* BSSID2 */ +#define RAL_MAC_CSR8 0x0410 /* Max frame length */ +#define RAL_MAC_CSR9 0x0412 /* Timer control */ +#define RAL_MAC_CSR10 0x0414 /* Slot time */ +#define RAL_MAC_CSR11 0x0416 /* IFS */ +#define RAL_MAC_CSR12 0x0418 /* EIFS */ +#define RAL_MAC_CSR13 0x041a /* Power mode0 */ +#define RAL_MAC_CSR14 0x041c /* Power mode1 */ +#define RAL_MAC_CSR15 0x041e /* Power saving transition0 */ +#define RAL_MAC_CSR16 0x0420 /* Power saving transition1 */ +#define RAL_MAC_CSR17 0x0422 /* Power state control */ +#define RAL_MAC_CSR18 0x0424 /* Auto wake-up control */ +#define RAL_MAC_CSR19 0x0426 /* GPIO control */ +#define RAL_MAC_CSR20 0x0428 /* LED control0 */ +#define RAL_MAC_CSR22 0x042c /* XXX not documented */ + +/* + * Tx/Rx Registers. + */ +#define RAL_TXRX_CSR0 0x0440 /* Security control */ +#define RAL_TXRX_CSR2 0x0444 /* Rx control */ +#define RAL_TXRX_CSR5 0x044a /* CCK Tx BBP ID0 */ +#define RAL_TXRX_CSR6 0x044c /* CCK Tx BBP ID1 */ +#define RAL_TXRX_CSR7 0x044e /* OFDM Tx BBP ID0 */ +#define RAL_TXRX_CSR8 0x0450 /* OFDM Tx BBP ID1 */ +#define RAL_TXRX_CSR10 0x0454 /* Auto responder control */ +#define RAL_TXRX_CSR11 0x0456 /* Auto responder basic rate */ +#define RAL_TXRX_CSR18 0x0464 /* Beacon interval */ +#define RAL_TXRX_CSR19 0x0466 /* Beacon/sync control */ +#define RAL_TXRX_CSR20 0x0468 /* Beacon alignment */ +#define RAL_TXRX_CSR21 0x046a /* XXX not documented */ + +/* + * Security registers. + */ +#define RAL_SEC_CSR0 0x0480 /* Shared key 0, word 0 */ + +/* + * PHY registers. + */ +#define RAL_PHY_CSR2 0x04c4 /* Tx MAC configuration */ +#define RAL_PHY_CSR4 0x04c8 /* Interface configuration */ +#define RAL_PHY_CSR5 0x04ca /* BBP Pre-Tx CCK */ +#define RAL_PHY_CSR6 0x04cc /* BBP Pre-Tx OFDM */ +#define RAL_PHY_CSR7 0x04ce /* BBP serial control */ +#define RAL_PHY_CSR8 0x04d0 /* BBP serial status */ +#define RAL_PHY_CSR9 0x04d2 /* RF serial control0 */ +#define RAL_PHY_CSR10 0x04d4 /* RF serial control1 */ + +/* + * Statistics registers. + */ +#define RAL_STA_CSR0 0x04e0 /* FCS error */ + + +#define RAL_DISABLE_RX (1 << 0) +#define RAL_DROP_CRC (1 << 1) +#define RAL_DROP_PHY (1 << 2) +#define RAL_DROP_CTL (1 << 3) +#define RAL_DROP_NOT_TO_ME (1 << 4) +#define RAL_DROP_TODS (1 << 5) +#define RAL_DROP_BAD_VERSION (1 << 6) +#define RAL_DROP_MULTICAST (1 << 9) +#define RAL_DROP_BROADCAST (1 << 10) + +#define RAL_SHORT_PREAMBLE (1 << 2) + +#define RAL_RESET_ASIC (1 << 0) +#define RAL_RESET_BBP (1 << 1) +#define RAL_HOST_READY (1 << 2) + +#define RAL_ENABLE_TSF (1 << 0) +#define RAL_ENABLE_TSF_SYNC(x) (((x) & 0x3) << 1) +#define RAL_ENABLE_TBCN (1 << 3) +#define RAL_ENABLE_BEACON_GENERATOR (1 << 4) + +#define RAL_RF_AWAKE (3 << 7) +#define RAL_BBP_AWAKE (3 << 5) + +#define RAL_BBP_WRITE (1 << 15) +#define RAL_BBP_BUSY (1 << 0) + +#define RAL_RF1_AUTOTUNE 0x08000 +#define RAL_RF3_AUTOTUNE 0x00040 + +#define RAL_RF_2522 0x00 +#define RAL_RF_2523 0x01 +#define RAL_RF_2524 0x02 +#define RAL_RF_2525 0x03 +#define RAL_RF_2525E 0x04 +#define RAL_RF_2526 0x05 +/* dual-band RF */ +#define RAL_RF_5222 0x10 + +#define RAL_BBP_VERSION 0 +#define RAL_BBP_TX 2 +#define RAL_BBP_RX 14 + +#define RAL_BBP_ANTA 0x00 +#define RAL_BBP_DIVERSITY 0x01 +#define RAL_BBP_ANTB 0x02 +#define RAL_BBP_ANTMASK 0x03 +#define RAL_BBP_FLIPIQ 0x04 + +#define RAL_JAPAN_FILTER 0x08 + +struct ural_tx_desc { + uint32_t flags; +#define RAL_TX_RETRY(x) ((x) << 4) +#define RAL_TX_MORE_FRAG (1 << 8) +#define RAL_TX_ACK (1 << 9) +#define RAL_TX_TIMESTAMP (1 << 10) +#define RAL_TX_OFDM (1 << 11) +#define RAL_TX_NEWSEQ (1 << 12) + +#define RAL_TX_IFS_MASK 0x00006000 +#define RAL_TX_IFS_BACKOFF (0 << 13) +#define RAL_TX_IFS_SIFS (1 << 13) +#define RAL_TX_IFS_NEWBACKOFF (2 << 13) +#define RAL_TX_IFS_NONE (3 << 13) + + uint16_t wme; +#define RAL_LOGCWMAX(x) (((x) & 0xf) << 12) +#define RAL_LOGCWMIN(x) (((x) & 0xf) << 8) +#define RAL_AIFSN(x) (((x) & 0x3) << 6) +#define RAL_IVOFFSET(x) (((x) & 0x3f)) + + uint16_t reserved1; + uint8_t plcp_signal; + uint8_t plcp_service; +#define RAL_PLCP_LENGEXT 0x80 + + uint8_t plcp_length_lo; + uint8_t plcp_length_hi; + uint32_t iv; + uint32_t eiv; +} __packed; + +struct ural_rx_desc { + uint32_t flags; +#define RAL_RX_CRC_ERROR (1 << 5) +#define RAL_RX_OFDM (1 << 6) +#define RAL_RX_PHY_ERROR (1 << 7) + + uint8_t rssi; + uint8_t rate; + uint16_t reserved; + + uint32_t iv; + uint32_t eiv; +} __packed; + +#define RAL_RF_LOBUSY (1 << 15) +#define RAL_RF_BUSY (1 << 31) +#define RAL_RF_20BIT (20 << 24) + +#define RAL_RF1 0 +#define RAL_RF2 2 +#define RAL_RF3 1 +#define RAL_RF4 3 + +#define RAL_EEPROM_ADDRESS 0x0004 +#define RAL_EEPROM_TXPOWER 0x003c +#define RAL_EEPROM_CONFIG0 0x0016 +#define RAL_EEPROM_BBP_BASE 0x001c diff --git a/sys/legacy/dev/usb/if_uralvar.h b/sys/legacy/dev/usb/if_uralvar.h new file mode 100644 index 0000000..39aef9e --- /dev/null +++ b/sys/legacy/dev/usb/if_uralvar.h @@ -0,0 +1,157 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2005 + * Damien Bergamini <damien.bergamini@free.fr> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define RAL_RX_LIST_COUNT 1 +#define RAL_TX_LIST_COUNT 8 + +#define URAL_SCAN_START 1 +#define URAL_SCAN_END 2 +#define URAL_SET_CHANNEL 3 + + +struct ural_rx_radiotap_header { + struct ieee80211_radiotap_header wr_ihdr; + uint8_t wr_flags; + uint8_t wr_rate; + uint16_t wr_chan_freq; + uint16_t wr_chan_flags; + uint8_t wr_antenna; + uint8_t wr_antsignal; +}; + +#define RAL_RX_RADIOTAP_PRESENT \ + ((1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_RATE) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL) | \ + (1 << IEEE80211_RADIOTAP_ANTENNA) | \ + (1 << IEEE80211_RADIOTAP_DB_ANTSIGNAL)) + +struct ural_tx_radiotap_header { + struct ieee80211_radiotap_header wt_ihdr; + uint8_t wt_flags; + uint8_t wt_rate; + uint16_t wt_chan_freq; + uint16_t wt_chan_flags; + uint8_t wt_antenna; +}; + +#define RAL_TX_RADIOTAP_PRESENT \ + ((1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_RATE) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL) | \ + (1 << IEEE80211_RADIOTAP_ANTENNA)) + +struct ural_softc; + +struct ural_tx_data { + struct ural_softc *sc; + usbd_xfer_handle xfer; + uint8_t *buf; + struct mbuf *m; + struct ieee80211_node *ni; +}; + +struct ural_rx_data { + struct ural_softc *sc; + usbd_xfer_handle xfer; + uint8_t *buf; + struct mbuf *m; +}; + +struct ural_node { + struct ieee80211_node ni; + struct ieee80211_amrr_node amn; +}; +#define URAL_NODE(ni) ((struct ural_node *)(ni)) + +struct ural_vap { + struct ieee80211vap vap; + struct ieee80211_beacon_offsets bo; + struct ieee80211_amrr amrr; + struct callout amrr_ch; + + int (*newstate)(struct ieee80211vap *, + enum ieee80211_state, int); +}; +#define URAL_VAP(vap) ((struct ural_vap *)(vap)) + +struct ural_softc { + struct ifnet *sc_ifp; + device_t sc_dev; + usbd_device_handle sc_udev; + usbd_interface_handle sc_iface; + + const struct ieee80211_rate_table *sc_rates; + + int sc_rx_no; + int sc_tx_no; + + uint32_t asic_rev; + uint8_t rf_rev; + + usbd_xfer_handle amrr_xfer; + + usbd_pipe_handle sc_rx_pipeh; + usbd_pipe_handle sc_tx_pipeh; + + enum ieee80211_state sc_state; + int sc_arg; + int sc_scan_action; /* should be an enum */ + struct usb_task sc_task; + struct usb_task sc_scantask; + + struct ural_rx_data rx_data[RAL_RX_LIST_COUNT]; + struct ural_tx_data tx_data[RAL_TX_LIST_COUNT]; + int tx_queued; + int tx_cur; + + struct mtx sc_mtx; + + struct callout watchdog_ch; + int sc_tx_timer; + + uint16_t sta[11]; + uint32_t rf_regs[4]; + uint8_t txpow[14]; + + struct { + uint8_t val; + uint8_t reg; + } __packed bbp_prom[16]; + + int led_mode; + int hw_radio; + int rx_ant; + int tx_ant; + int nb_ant; + + struct ural_rx_radiotap_header sc_rxtap; + int sc_rxtap_len; + + struct ural_tx_radiotap_header sc_txtap; + int sc_txtap_len; +}; + +#if 0 +#define RAL_LOCK(sc) mtx_lock(&(sc)->sc_mtx) +#define RAL_UNLOCK(sc) mtx_unlock(&(sc)->sc_mtx) +#else +#define RAL_LOCK(sc) do { ((sc) = (sc)); mtx_lock(&Giant); } while (0) +#define RAL_UNLOCK(sc) mtx_unlock(&Giant) +#endif diff --git a/sys/legacy/dev/usb/if_urtw.c b/sys/legacy/dev/usb/if_urtw.c new file mode 100644 index 0000000..d1a3a84 --- /dev/null +++ b/sys/legacy/dev/usb/if_urtw.c @@ -0,0 +1,3324 @@ +/*- + * Copyright (c) 2008 Weongyo Jeong <weongyo@FreeBSD.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); +#include <sys/param.h> +#include <sys/sockio.h> +#include <sys/sysctl.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/mbuf.h> +#include <sys/kernel.h> +#include <sys/socket.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/endian.h> +#include <sys/kdb.h> + +#include <machine/bus.h> +#include <machine/resource.h> +#include <sys/rman.h> + +#include <net/bpf.h> +#include <net/if.h> +#include <net/if_arp.h> +#include <net/ethernet.h> +#include <net/if_dl.h> +#include <net/if_media.h> +#include <net/if_types.h> + +#ifdef INET +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/in_var.h> +#include <netinet/if_ether.h> +#include <netinet/ip.h> +#endif + +#include <net80211/ieee80211_var.h> +#include <net80211/ieee80211_regdomain.h> +#include <net80211/ieee80211_radiotap.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include "usbdevs.h" + +#include <dev/usb/if_urtwreg.h> +#include <dev/usb/if_urtwvar.h> + +SYSCTL_NODE(_hw_usb, OID_AUTO, urtw, CTLFLAG_RW, 0, "USB Realtek 8187L"); +#ifdef URTW_DEBUG +int urtw_debug = 0; +SYSCTL_INT(_hw_usb_urtw, OID_AUTO, debug, CTLFLAG_RW, &urtw_debug, 0, + "control debugging printfs"); +TUNABLE_INT("hw.usb.urtw.debug", &urtw_debug); +enum { + URTW_DEBUG_XMIT = 0x00000001, /* basic xmit operation */ + URTW_DEBUG_RECV = 0x00000002, /* basic recv operation */ + URTW_DEBUG_RESET = 0x00000004, /* reset processing */ + URTW_DEBUG_TX_PROC = 0x00000008, /* tx ISR proc */ + URTW_DEBUG_RX_PROC = 0x00000010, /* rx ISR proc */ + URTW_DEBUG_STATE = 0x00000020, /* 802.11 state transitions */ + URTW_DEBUG_STAT = 0x00000040, /* statistic */ + URTW_DEBUG_ANY = 0xffffffff +}; +#define DPRINTF(sc, m, fmt, ...) do { \ + if (sc->sc_debug & (m)) \ + printf(fmt, __VA_ARGS__); \ +} while (0) +#else +#define DPRINTF(sc, m, fmt, ...) do { \ + (void) sc; \ +} while (0) +#endif +int urtw_preamble_mode = URTW_PREAMBLE_MODE_LONG; +SYSCTL_INT(_hw_usb_urtw, OID_AUTO, preamble_mode, CTLFLAG_RW, + &urtw_preamble_mode, 0, "set the preable mode (long or short)"); +TUNABLE_INT("hw.usb.urtw.preamble_mode", &urtw_preamble_mode); + +/* recognized device vendors/products */ +static const struct usb_devno urtw_devs[] = { +#define URTW_DEV(v,p) { USB_VENDOR_##v, USB_PRODUCT_##v##_##p } + URTW_DEV(REALTEK, RTL8187), + URTW_DEV(NETGEAR, WG111V2) +#undef URTW_DEV +}; + +#define urtw_read8_m(sc, val, data) do { \ + error = urtw_read8_c(sc, val, data); \ + if (error != 0) \ + goto fail; \ +} while (0) +#define urtw_write8_m(sc, val, data) do { \ + error = urtw_write8_c(sc, val, data); \ + if (error != 0) \ + goto fail; \ +} while (0) +#define urtw_read16_m(sc, val, data) do { \ + error = urtw_read16_c(sc, val, data); \ + if (error != 0) \ + goto fail; \ +} while (0) +#define urtw_write16_m(sc, val, data) do { \ + error = urtw_write16_c(sc, val, data); \ + if (error != 0) \ + goto fail; \ +} while (0) +#define urtw_read32_m(sc, val, data) do { \ + error = urtw_read32_c(sc, val, data); \ + if (error != 0) \ + goto fail; \ +} while (0) +#define urtw_write32_m(sc, val, data) do { \ + error = urtw_write32_c(sc, val, data); \ + if (error != 0) \ + goto fail; \ +} while (0) +#define urtw_8187_write_phy_ofdm(sc, val, data) do { \ + error = urtw_8187_write_phy_ofdm_c(sc, val, data); \ + if (error != 0) \ + goto fail; \ +} while (0) +#define urtw_8187_write_phy_cck(sc, val, data) do { \ + error = urtw_8187_write_phy_cck_c(sc, val, data); \ + if (error != 0) \ + goto fail; \ +} while (0) +#define urtw_8225_write(sc, val, data) do { \ + error = urtw_8225_write_c(sc, val, data); \ + if (error != 0) \ + goto fail; \ +} while (0) + +struct urtw_pair { + uint32_t reg; + uint32_t val; +}; + +static uint8_t urtw_8225_agc[] = { + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9d, 0x9c, 0x9b, + 0x9a, 0x99, 0x98, 0x97, 0x96, 0x95, 0x94, 0x93, 0x92, 0x91, 0x90, + 0x8f, 0x8e, 0x8d, 0x8c, 0x8b, 0x8a, 0x89, 0x88, 0x87, 0x86, 0x85, + 0x84, 0x83, 0x82, 0x81, 0x80, 0x3f, 0x3e, 0x3d, 0x3c, 0x3b, 0x3a, + 0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x30, 0x2f, + 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, + 0x23, 0x22, 0x21, 0x20, 0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, + 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x0f, 0x0e, + 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, + 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 +}; + +static uint32_t urtw_8225_channel[] = { + 0x0000, /* dummy channel 0 */ + 0x085c, /* 1 */ + 0x08dc, /* 2 */ + 0x095c, /* 3 */ + 0x09dc, /* 4 */ + 0x0a5c, /* 5 */ + 0x0adc, /* 6 */ + 0x0b5c, /* 7 */ + 0x0bdc, /* 8 */ + 0x0c5c, /* 9 */ + 0x0cdc, /* 10 */ + 0x0d5c, /* 11 */ + 0x0ddc, /* 12 */ + 0x0e5c, /* 13 */ + 0x0f72, /* 14 */ +}; + +static uint8_t urtw_8225_gain[] = { + 0x23, 0x88, 0x7c, 0xa5, /* -82dbm */ + 0x23, 0x88, 0x7c, 0xb5, /* -82dbm */ + 0x23, 0x88, 0x7c, 0xc5, /* -82dbm */ + 0x33, 0x80, 0x79, 0xc5, /* -78dbm */ + 0x43, 0x78, 0x76, 0xc5, /* -74dbm */ + 0x53, 0x60, 0x73, 0xc5, /* -70dbm */ + 0x63, 0x58, 0x70, 0xc5, /* -66dbm */ +}; + +static struct urtw_pair urtw_8225_rf_part1[] = { + { 0x00, 0x0067 }, { 0x01, 0x0fe0 }, { 0x02, 0x044d }, { 0x03, 0x0441 }, + { 0x04, 0x0486 }, { 0x05, 0x0bc0 }, { 0x06, 0x0ae6 }, { 0x07, 0x082a }, + { 0x08, 0x001f }, { 0x09, 0x0334 }, { 0x0a, 0x0fd4 }, { 0x0b, 0x0391 }, + { 0x0c, 0x0050 }, { 0x0d, 0x06db }, { 0x0e, 0x0029 }, { 0x0f, 0x0914 }, +}; + +static struct urtw_pair urtw_8225_rf_part2[] = { + { 0x00, 0x01 }, { 0x01, 0x02 }, { 0x02, 0x42 }, { 0x03, 0x00 }, + { 0x04, 0x00 }, { 0x05, 0x00 }, { 0x06, 0x40 }, { 0x07, 0x00 }, + { 0x08, 0x40 }, { 0x09, 0xfe }, { 0x0a, 0x09 }, { 0x0b, 0x80 }, + { 0x0c, 0x01 }, { 0x0e, 0xd3 }, { 0x0f, 0x38 }, { 0x10, 0x84 }, + { 0x11, 0x06 }, { 0x12, 0x20 }, { 0x13, 0x20 }, { 0x14, 0x00 }, + { 0x15, 0x40 }, { 0x16, 0x00 }, { 0x17, 0x40 }, { 0x18, 0xef }, + { 0x19, 0x19 }, { 0x1a, 0x20 }, { 0x1b, 0x76 }, { 0x1c, 0x04 }, + { 0x1e, 0x95 }, { 0x1f, 0x75 }, { 0x20, 0x1f }, { 0x21, 0x27 }, + { 0x22, 0x16 }, { 0x24, 0x46 }, { 0x25, 0x20 }, { 0x26, 0x90 }, + { 0x27, 0x88 } +}; + +static struct urtw_pair urtw_8225_rf_part3[] = { + { 0x00, 0x98 }, { 0x03, 0x20 }, { 0x04, 0x7e }, { 0x05, 0x12 }, + { 0x06, 0xfc }, { 0x07, 0x78 }, { 0x08, 0x2e }, { 0x10, 0x9b }, + { 0x11, 0x88 }, { 0x12, 0x47 }, { 0x13, 0xd0 }, { 0x19, 0x00 }, + { 0x1a, 0xa0 }, { 0x1b, 0x08 }, { 0x40, 0x86 }, { 0x41, 0x8d }, + { 0x42, 0x15 }, { 0x43, 0x18 }, { 0x44, 0x1f }, { 0x45, 0x1e }, + { 0x46, 0x1a }, { 0x47, 0x15 }, { 0x48, 0x10 }, { 0x49, 0x0a }, + { 0x4a, 0x05 }, { 0x4b, 0x02 }, { 0x4c, 0x05 } +}; + +static uint16_t urtw_8225_rxgain[] = { + 0x0400, 0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0408, 0x0409, + 0x040a, 0x040b, 0x0502, 0x0503, 0x0504, 0x0505, 0x0540, 0x0541, + 0x0542, 0x0543, 0x0544, 0x0545, 0x0580, 0x0581, 0x0582, 0x0583, + 0x0584, 0x0585, 0x0588, 0x0589, 0x058a, 0x058b, 0x0643, 0x0644, + 0x0645, 0x0680, 0x0681, 0x0682, 0x0683, 0x0684, 0x0685, 0x0688, + 0x0689, 0x068a, 0x068b, 0x068c, 0x0742, 0x0743, 0x0744, 0x0745, + 0x0780, 0x0781, 0x0782, 0x0783, 0x0784, 0x0785, 0x0788, 0x0789, + 0x078a, 0x078b, 0x078c, 0x078d, 0x0790, 0x0791, 0x0792, 0x0793, + 0x0794, 0x0795, 0x0798, 0x0799, 0x079a, 0x079b, 0x079c, 0x079d, + 0x07a0, 0x07a1, 0x07a2, 0x07a3, 0x07a4, 0x07a5, 0x07a8, 0x07a9, + 0x07aa, 0x07ab, 0x07ac, 0x07ad, 0x07b0, 0x07b1, 0x07b2, 0x07b3, + 0x07b4, 0x07b5, 0x07b8, 0x07b9, 0x07ba, 0x07bb, 0x07bb +}; + +static uint8_t urtw_8225_threshold[] = { + 0x8d, 0x8d, 0x8d, 0x8d, 0x9d, 0xad, 0xbd, +}; + +static uint8_t urtw_8225_tx_gain_cck_ofdm[] = { + 0x02, 0x06, 0x0e, 0x1e, 0x3e, 0x7e +}; + +static uint8_t urtw_8225_txpwr_cck[] = { + 0x18, 0x17, 0x15, 0x11, 0x0c, 0x08, 0x04, 0x02, + 0x1b, 0x1a, 0x17, 0x13, 0x0e, 0x09, 0x04, 0x02, + 0x1f, 0x1e, 0x1a, 0x15, 0x10, 0x0a, 0x05, 0x02, + 0x22, 0x21, 0x1d, 0x18, 0x11, 0x0b, 0x06, 0x02, + 0x26, 0x25, 0x21, 0x1b, 0x14, 0x0d, 0x06, 0x03, + 0x2b, 0x2a, 0x25, 0x1e, 0x16, 0x0e, 0x07, 0x03 +}; + +static uint8_t urtw_8225_txpwr_cck_ch14[] = { + 0x18, 0x17, 0x15, 0x0c, 0x00, 0x00, 0x00, 0x00, + 0x1b, 0x1a, 0x17, 0x0e, 0x00, 0x00, 0x00, 0x00, + 0x1f, 0x1e, 0x1a, 0x0f, 0x00, 0x00, 0x00, 0x00, + 0x22, 0x21, 0x1d, 0x11, 0x00, 0x00, 0x00, 0x00, + 0x26, 0x25, 0x21, 0x13, 0x00, 0x00, 0x00, 0x00, + 0x2b, 0x2a, 0x25, 0x15, 0x00, 0x00, 0x00, 0x00 +}; + +static uint8_t urtw_8225_txpwr_ofdm[]={ + 0x80, 0x90, 0xa2, 0xb5, 0xcb, 0xe4 +}; + +static uint8_t urtw_8225v2_gain_bg[]={ + 0x23, 0x15, 0xa5, /* -82-1dbm */ + 0x23, 0x15, 0xb5, /* -82-2dbm */ + 0x23, 0x15, 0xc5, /* -82-3dbm */ + 0x33, 0x15, 0xc5, /* -78dbm */ + 0x43, 0x15, 0xc5, /* -74dbm */ + 0x53, 0x15, 0xc5, /* -70dbm */ + 0x63, 0x15, 0xc5, /* -66dbm */ +}; + +static struct urtw_pair urtw_8225v2_rf_part1[] = { + { 0x00, 0x02bf }, { 0x01, 0x0ee0 }, { 0x02, 0x044d }, { 0x03, 0x0441 }, + { 0x04, 0x08c3 }, { 0x05, 0x0c72 }, { 0x06, 0x00e6 }, { 0x07, 0x082a }, + { 0x08, 0x003f }, { 0x09, 0x0335 }, { 0x0a, 0x09d4 }, { 0x0b, 0x07bb }, + { 0x0c, 0x0850 }, { 0x0d, 0x0cdf }, { 0x0e, 0x002b }, { 0x0f, 0x0114 } +}; + +static struct urtw_pair urtw_8225v2_rf_part2[] = { + { 0x00, 0x01 }, { 0x01, 0x02 }, { 0x02, 0x42 }, { 0x03, 0x00 }, + { 0x04, 0x00 }, { 0x05, 0x00 }, { 0x06, 0x40 }, { 0x07, 0x00 }, + { 0x08, 0x40 }, { 0x09, 0xfe }, { 0x0a, 0x08 }, { 0x0b, 0x80 }, + { 0x0c, 0x01 }, { 0x0d, 0x43 }, { 0x0e, 0xd3 }, { 0x0f, 0x38 }, + { 0x10, 0x84 }, { 0x11, 0x07 }, { 0x12, 0x20 }, { 0x13, 0x20 }, + { 0x14, 0x00 }, { 0x15, 0x40 }, { 0x16, 0x00 }, { 0x17, 0x40 }, + { 0x18, 0xef }, { 0x19, 0x19 }, { 0x1a, 0x20 }, { 0x1b, 0x15 }, + { 0x1c, 0x04 }, { 0x1d, 0xc5 }, { 0x1e, 0x95 }, { 0x1f, 0x75 }, + { 0x20, 0x1f }, { 0x21, 0x17 }, { 0x22, 0x16 }, { 0x23, 0x80 }, + { 0x24, 0x46 }, { 0x25, 0x00 }, { 0x26, 0x90 }, { 0x27, 0x88 } +}; + +static struct urtw_pair urtw_8225v2_rf_part3[] = { + { 0x00, 0x98 }, { 0x03, 0x20 }, { 0x04, 0x7e }, { 0x05, 0x12 }, + { 0x06, 0xfc }, { 0x07, 0x78 }, { 0x08, 0x2e }, { 0x09, 0x11 }, + { 0x0a, 0x17 }, { 0x0b, 0x11 }, { 0x10, 0x9b }, { 0x11, 0x88 }, + { 0x12, 0x47 }, { 0x13, 0xd0 }, { 0x19, 0x00 }, { 0x1a, 0xa0 }, + { 0x1b, 0x08 }, { 0x1d, 0x00 }, { 0x40, 0x86 }, { 0x41, 0x9d }, + { 0x42, 0x15 }, { 0x43, 0x18 }, { 0x44, 0x36 }, { 0x45, 0x35 }, + { 0x46, 0x2e }, { 0x47, 0x25 }, { 0x48, 0x1c }, { 0x49, 0x12 }, + { 0x4a, 0x09 }, { 0x4b, 0x04 }, { 0x4c, 0x05 } +}; + +static uint16_t urtw_8225v2_rxgain[] = { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0008, 0x0009, + 0x000a, 0x000b, 0x0102, 0x0103, 0x0104, 0x0105, 0x0140, 0x0141, + 0x0142, 0x0143, 0x0144, 0x0145, 0x0180, 0x0181, 0x0182, 0x0183, + 0x0184, 0x0185, 0x0188, 0x0189, 0x018a, 0x018b, 0x0243, 0x0244, + 0x0245, 0x0280, 0x0281, 0x0282, 0x0283, 0x0284, 0x0285, 0x0288, + 0x0289, 0x028a, 0x028b, 0x028c, 0x0342, 0x0343, 0x0344, 0x0345, + 0x0380, 0x0381, 0x0382, 0x0383, 0x0384, 0x0385, 0x0388, 0x0389, + 0x038a, 0x038b, 0x038c, 0x038d, 0x0390, 0x0391, 0x0392, 0x0393, + 0x0394, 0x0395, 0x0398, 0x0399, 0x039a, 0x039b, 0x039c, 0x039d, + 0x03a0, 0x03a1, 0x03a2, 0x03a3, 0x03a4, 0x03a5, 0x03a8, 0x03a9, + 0x03aa, 0x03ab, 0x03ac, 0x03ad, 0x03b0, 0x03b1, 0x03b2, 0x03b3, + 0x03b4, 0x03b5, 0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bb +}; + +static uint8_t urtw_8225v2_tx_gain_cck_ofdm[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, + 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, + 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, +}; + +static uint8_t urtw_8225v2_txpwr_cck[] = { + 0x36, 0x35, 0x2e, 0x25, 0x1c, 0x12, 0x09, 0x04 +}; + +static uint8_t urtw_8225v2_txpwr_cck_ch14[] = { + 0x36, 0x35, 0x2e, 0x1b, 0x00, 0x00, 0x00, 0x00 +}; + +static struct urtw_pair urtw_ratetable[] = { + { 2, 0 }, { 4, 1 }, { 11, 2 }, { 12, 4 }, { 18, 5 }, + { 22, 3 }, { 24, 6 }, { 36, 7 }, { 48, 8 }, { 72, 9 }, + { 96, 10 }, { 108, 11 } +}; + +static struct ieee80211vap *urtw_vap_create(struct ieee80211com *, + const char name[IFNAMSIZ], int unit, int opmode, + int flags, const uint8_t bssid[IEEE80211_ADDR_LEN], + const uint8_t mac[IEEE80211_ADDR_LEN]); +static void urtw_vap_delete(struct ieee80211vap *); +static void urtw_init(void *); +static void urtw_stop(struct ifnet *, int); +static int urtw_ioctl(struct ifnet *, u_long, caddr_t); +static void urtw_start(struct ifnet *); +static int urtw_alloc_rx_data_list(struct urtw_softc *); +static int urtw_alloc_tx_data_list(struct urtw_softc *); +static void urtw_free_data_list(struct urtw_softc *, + usbd_pipe_handle, usbd_pipe_handle, + struct urtw_data data[], int); +static int urtw_raw_xmit(struct ieee80211_node *, struct mbuf *, + const struct ieee80211_bpf_params *); +static void urtw_scan_start(struct ieee80211com *); +static void urtw_scan_end(struct ieee80211com *); +static void urtw_set_channel(struct ieee80211com *); +static void urtw_update_mcast(struct ifnet *); +static void urtw_rxeof(usbd_xfer_handle, usbd_private_handle, + usbd_status); +static int urtw_tx_start(struct urtw_softc *, + struct ieee80211_node *, struct mbuf *, int); +static void urtw_txeof_low(usbd_xfer_handle, usbd_private_handle, + usbd_status); +static void urtw_txeof_normal(usbd_xfer_handle, + usbd_private_handle, usbd_status); +static int urtw_newstate(struct ieee80211vap *, + enum ieee80211_state, int); +static void urtw_ledtask(void *); +static void urtw_ledusbtask(void *); +static void urtw_ctxtask(void *); +static void urtw_task(void *); +static void urtw_watchdog(void *); +static void urtw_set_multi(void *); +static int urtw_isbmode(uint16_t); +static uint16_t urtw_rate2rtl(int); +static uint16_t urtw_rtl2rate(int); +static usbd_status urtw_set_rate(struct urtw_softc *); +static usbd_status urtw_update_msr(struct urtw_softc *); +static usbd_status urtw_read8_c(struct urtw_softc *, int, uint8_t *); +static usbd_status urtw_read16_c(struct urtw_softc *, int, uint16_t *); +static usbd_status urtw_read32_c(struct urtw_softc *, int, uint32_t *); +static usbd_status urtw_write8_c(struct urtw_softc *, int, uint8_t); +static usbd_status urtw_write16_c(struct urtw_softc *, int, uint16_t); +static usbd_status urtw_write32_c(struct urtw_softc *, int, uint32_t); +static usbd_status urtw_eprom_cs(struct urtw_softc *, int); +static usbd_status urtw_eprom_ck(struct urtw_softc *); +static usbd_status urtw_eprom_sendbits(struct urtw_softc *, int16_t *, + int); +static usbd_status urtw_eprom_read32(struct urtw_softc *, uint32_t, + uint32_t *); +static usbd_status urtw_eprom_readbit(struct urtw_softc *, int16_t *); +static usbd_status urtw_eprom_writebit(struct urtw_softc *, int16_t); +static usbd_status urtw_get_macaddr(struct urtw_softc *); +static usbd_status urtw_get_txpwr(struct urtw_softc *); +static usbd_status urtw_get_rfchip(struct urtw_softc *); +static usbd_status urtw_led_init(struct urtw_softc *); +static usbd_status urtw_8185_rf_pins_enable(struct urtw_softc *); +static usbd_status urtw_8185_tx_antenna(struct urtw_softc *, uint8_t); +static usbd_status urtw_8187_write_phy(struct urtw_softc *, uint8_t, + uint32_t); +static usbd_status urtw_8187_write_phy_ofdm_c(struct urtw_softc *, + uint8_t, uint32_t); +static usbd_status urtw_8187_write_phy_cck_c(struct urtw_softc *, uint8_t, + uint32_t); +static usbd_status urtw_8225_setgain(struct urtw_softc *, int16_t); +static usbd_status urtw_8225_usb_init(struct urtw_softc *); +static usbd_status urtw_8225_write_c(struct urtw_softc *, uint8_t, + uint16_t); +static usbd_status urtw_8225_write_s16(struct urtw_softc *, uint8_t, int, + uint16_t *); +static usbd_status urtw_8225_read(struct urtw_softc *, uint8_t, + uint32_t *); +static usbd_status urtw_8225_rf_init(struct urtw_softc *); +static usbd_status urtw_8225_rf_set_chan(struct urtw_softc *, int); +static usbd_status urtw_8225_rf_set_sens(struct urtw_softc *, int); +static usbd_status urtw_8225_set_txpwrlvl(struct urtw_softc *, int); +static usbd_status urtw_8225v2_rf_init(struct urtw_softc *); +static usbd_status urtw_8225v2_rf_set_chan(struct urtw_softc *, int); +static usbd_status urtw_8225v2_set_txpwrlvl(struct urtw_softc *, int); +static usbd_status urtw_8225v2_setgain(struct urtw_softc *, int16_t); +static usbd_status urtw_8225_isv2(struct urtw_softc *, int *); +static usbd_status urtw_read8e(struct urtw_softc *, int, uint8_t *); +static usbd_status urtw_write8e(struct urtw_softc *, int, uint8_t); +static usbd_status urtw_8180_set_anaparam(struct urtw_softc *, uint32_t); +static usbd_status urtw_8185_set_anaparam2(struct urtw_softc *, uint32_t); +static usbd_status urtw_open_pipes(struct urtw_softc *); +static usbd_status urtw_close_pipes(struct urtw_softc *); +static usbd_status urtw_intr_enable(struct urtw_softc *); +static usbd_status urtw_intr_disable(struct urtw_softc *); +static usbd_status urtw_reset(struct urtw_softc *); +static usbd_status urtw_led_on(struct urtw_softc *, int); +static usbd_status urtw_led_ctl(struct urtw_softc *, int); +static usbd_status urtw_led_blink(struct urtw_softc *); +static usbd_status urtw_led_mode0(struct urtw_softc *, int); +static usbd_status urtw_led_mode1(struct urtw_softc *, int); +static usbd_status urtw_led_mode2(struct urtw_softc *, int); +static usbd_status urtw_led_mode3(struct urtw_softc *, int); +static usbd_status urtw_rx_setconf(struct urtw_softc *); +static usbd_status urtw_rx_enable(struct urtw_softc *); +static usbd_status urtw_tx_enable(struct urtw_softc *sc); + +static int +urtw_match(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + const struct usb_devno *ud; + + if (uaa->iface != NULL) + return UMATCH_NONE; + ud = usb_lookup(urtw_devs, uaa->vendor, uaa->product); + + return (ud != NULL ? UMATCH_VENDOR_PRODUCT : UMATCH_NONE); +} + +static int +urtw_attach(device_t dev) +{ + int ret = 0; + struct urtw_softc *sc = device_get_softc(dev); + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct ieee80211com *ic; + struct ifnet *ifp; + uint8_t bands; + uint32_t data; + usbd_status error; + + sc->sc_dev = dev; + sc->sc_udev = uaa->device; +#ifdef URTW_DEBUG + sc->sc_debug = urtw_debug; +#endif + + mtx_init(&sc->sc_mtx, device_get_nameunit(sc->sc_dev), MTX_NETWORK_LOCK, + MTX_DEF); + callout_init(&sc->sc_led_ch, 0); + callout_init(&sc->sc_watchdog_ch, 0); + usb_init_task(&sc->sc_ledtask, urtw_ledusbtask, sc); + usb_init_task(&sc->sc_ctxtask, urtw_ctxtask, sc); + usb_init_task(&sc->sc_task, urtw_task, sc); + + urtw_read32_m(sc, URTW_RX, &data); + sc->sc_epromtype = (data & URTW_RX_9356SEL) ? URTW_EEPROM_93C56 : + URTW_EEPROM_93C46; + + error = urtw_get_rfchip(sc); + if (error != 0) + goto fail; + error = urtw_get_macaddr(sc); + if (error != 0) + goto fail; + error = urtw_get_txpwr(sc); + if (error != 0) + goto fail; + error = urtw_led_init(sc); + if (error != 0) + goto fail; + + sc->sc_rts_retry = URTW_DEFAULT_RTS_RETRY; + sc->sc_tx_retry = URTW_DEFAULT_TX_RETRY; + sc->sc_currate = 3; + sc->sc_preamble_mode = urtw_preamble_mode; + + ifp = sc->sc_ifp = if_alloc(IFT_IEEE80211); + if (ifp == NULL) { + device_printf(sc->sc_dev, "can not allocate ifnet\n"); + ret = ENXIO; + goto fail; + } + + ifp->if_softc = sc; + if_initname(ifp, "urtw", device_get_unit(sc->sc_dev)); + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST | + IFF_NEEDSGIANT; /* USB stack is still under Giant lock */ + ifp->if_init = urtw_init; + ifp->if_ioctl = urtw_ioctl; + ifp->if_start = urtw_start; + /* XXX URTW_TX_DATA_LIST_COUNT */ + IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN); + ifp->if_snd.ifq_drv_maxlen = IFQ_MAXLEN; + IFQ_SET_READY(&ifp->if_snd); + + ic = ifp->if_l2com; + ic->ic_ifp = ifp; + ic->ic_phytype = IEEE80211_T_OFDM; /* not only, but not used */ + ic->ic_opmode = IEEE80211_M_STA; /* default to BSS mode */ + + /* set device capabilities */ + ic->ic_caps = + IEEE80211_C_STA | /* station mode */ + IEEE80211_C_MONITOR | /* monitor mode supported */ + IEEE80211_C_TXPMGT | /* tx power management */ + IEEE80211_C_SHPREAMBLE | /* short preamble supported */ + IEEE80211_C_SHSLOT | /* short slot time supported */ + IEEE80211_C_BGSCAN | /* capable of bg scanning */ + IEEE80211_C_WPA; /* 802.11i */ + + IEEE80211_ADDR_COPY(ic->ic_myaddr, sc->sc_bssid); + + bands = 0; + setbit(&bands, IEEE80211_MODE_11B); + setbit(&bands, IEEE80211_MODE_11G); + ieee80211_init_channels(ic, NULL, &bands); + + ieee80211_ifattach(ic); + ic->ic_raw_xmit = urtw_raw_xmit; + ic->ic_scan_start = urtw_scan_start; + ic->ic_scan_end = urtw_scan_end; + ic->ic_set_channel = urtw_set_channel; + + ic->ic_vap_create = urtw_vap_create; + ic->ic_vap_delete = urtw_vap_delete; + ic->ic_update_mcast = urtw_update_mcast; + + bpfattach(ifp, DLT_IEEE802_11_RADIO, + sizeof (struct ieee80211_frame) + sizeof(sc->sc_txtap)); + + sc->sc_rxtap_len = sizeof sc->sc_rxtap; + sc->sc_rxtap.wr_ihdr.it_len = htole16(sc->sc_rxtap_len); + sc->sc_rxtap.wr_ihdr.it_present = htole32(URTW_RX_RADIOTAP_PRESENT); + + sc->sc_txtap_len = sizeof sc->sc_txtap; + sc->sc_txtap.wt_ihdr.it_len = htole16(sc->sc_txtap_len); + sc->sc_txtap.wt_ihdr.it_present = htole32(URTW_TX_RADIOTAP_PRESENT); + + if (bootverbose) + ieee80211_announce(ic); + + usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, sc->sc_udev, sc->sc_dev); +fail: + return (ret); +} + +static usbd_status +urtw_open_pipes(struct urtw_softc *sc) +{ + usbd_status error; + + /* + * NB: there is no way to distinguish each pipes so we need to hardcode + * pipe numbers + */ + + /* tx pipe - low priority packets */ + error = usbd_open_pipe(sc->sc_iface, 0x2, USBD_EXCLUSIVE_USE, + &sc->sc_txpipe_low); + if (error != 0) { + device_printf(sc->sc_dev, "could not open Tx low pipe: %s\n", + usbd_errstr(error)); + goto fail; + } + /* tx pipe - normal priority packets */ + error = usbd_open_pipe(sc->sc_iface, 0x3, USBD_EXCLUSIVE_USE, + &sc->sc_txpipe_normal); + if (error != 0) { + device_printf(sc->sc_dev, "could not open Tx normal pipe: %s\n", + usbd_errstr(error)); + goto fail; + } + /* rx pipe */ + error = usbd_open_pipe(sc->sc_iface, 0x81, USBD_EXCLUSIVE_USE, + &sc->sc_rxpipe); + if (error != 0) { + device_printf(sc->sc_dev, "could not open Rx pipe: %s\n", + usbd_errstr(error)); + goto fail; + } + + return (0); +fail: + (void)urtw_close_pipes(sc); + return (error); +} + +static usbd_status +urtw_close_pipes(struct urtw_softc *sc) +{ + usbd_status error = 0; + + if (sc->sc_rxpipe != NULL) { + error = usbd_close_pipe(sc->sc_rxpipe); + if (error != 0) + goto fail; + sc->sc_rxpipe = NULL; + } + if (sc->sc_txpipe_low != NULL) { + error = usbd_close_pipe(sc->sc_txpipe_low); + if (error != 0) + goto fail; + sc->sc_txpipe_low = NULL; + } + if (sc->sc_txpipe_normal != NULL) { + error = usbd_close_pipe(sc->sc_txpipe_normal); + if (error != 0) + goto fail; + sc->sc_txpipe_normal = NULL; + } +fail: + return (error); +} + +static int +urtw_alloc_data_list(struct urtw_softc *sc, struct urtw_data data[], + int ndata, int maxsz, int fillmbuf) +{ + int i, error; + + for (i = 0; i < ndata; i++) { + struct urtw_data *dp = &data[i]; + + dp->sc = sc; + dp->xfer = usbd_alloc_xfer(sc->sc_udev); + if (dp->xfer == NULL) { + device_printf(sc->sc_dev, "could not allocate xfer\n"); + error = ENOMEM; + goto fail; + } + if (fillmbuf) { + dp->m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + if (dp->m == NULL) { + device_printf(sc->sc_dev, + "could not allocate rx mbuf\n"); + error = ENOMEM; + goto fail; + } + dp->buf = mtod(dp->m, uint8_t *); + } else { + dp->m = NULL; + dp->buf = usbd_alloc_buffer(dp->xfer, maxsz); + if (dp->buf == NULL) { + device_printf(sc->sc_dev, + "could not allocate buffer\n"); + error = ENOMEM; + goto fail; + } + if (((unsigned long)dp->buf) % 4) + device_printf(sc->sc_dev, + "warn: unaligned buffer %p\n", dp->buf); + } + dp->ni = NULL; + } + + return 0; + +fail: urtw_free_data_list(sc, NULL, NULL, data, ndata); + return error; +} + +static void +urtw_free_data_list(struct urtw_softc *sc, usbd_pipe_handle pipe1, + usbd_pipe_handle pipe2, struct urtw_data data[], int ndata) +{ + int i; + + /* make sure no transfers are pending */ + if (pipe1 != NULL) + usbd_abort_pipe(pipe1); + if (pipe2 != NULL) + usbd_abort_pipe(pipe2); + + for (i = 0; i < ndata; i++) { + struct urtw_data *dp = &data[i]; + + if (dp->xfer != NULL) { + usbd_free_xfer(dp->xfer); + dp->xfer = NULL; + } + if (dp->m != NULL) { + m_freem(dp->m); + dp->m = NULL; + } + if (dp->ni != NULL) { + ieee80211_free_node(dp->ni); + dp->ni = NULL; + } + } +} + +static int +urtw_alloc_rx_data_list(struct urtw_softc *sc) +{ + + return urtw_alloc_data_list(sc, + sc->sc_rxdata, URTW_RX_DATA_LIST_COUNT, MCLBYTES, 1 /* mbufs */); +} + +static void +urtw_free_rx_data_list(struct urtw_softc *sc) +{ + + urtw_free_data_list(sc, sc->sc_rxpipe, NULL, sc->sc_rxdata, + URTW_RX_DATA_LIST_COUNT); +} + +static int +urtw_alloc_tx_data_list(struct urtw_softc *sc) +{ + + return urtw_alloc_data_list(sc, + sc->sc_txdata, URTW_TX_DATA_LIST_COUNT, URTW_TX_MAXSIZE, + 0 /* no mbufs */); +} + +static void +urtw_free_tx_data_list(struct urtw_softc *sc) +{ + + urtw_free_data_list(sc, sc->sc_txpipe_low, sc->sc_txpipe_normal, + sc->sc_txdata, URTW_TX_DATA_LIST_COUNT); +} + +static usbd_status +urtw_led_init(struct urtw_softc *sc) +{ + uint32_t rev; + usbd_status error; + + urtw_read8_m(sc, URTW_PSR, &sc->sc_psr); + error = urtw_eprom_read32(sc, URTW_EPROM_SWREV, &rev); + if (error != 0) + goto fail; + + switch (rev & URTW_EPROM_CID_MASK) { + case URTW_EPROM_CID_ALPHA0: + sc->sc_strategy = URTW_SW_LED_MODE1; + break; + case URTW_EPROM_CID_SERCOMM_PS: + sc->sc_strategy = URTW_SW_LED_MODE3; + break; + case URTW_EPROM_CID_HW_LED: + sc->sc_strategy = URTW_HW_LED; + break; + case URTW_EPROM_CID_RSVD0: + case URTW_EPROM_CID_RSVD1: + default: + sc->sc_strategy = URTW_SW_LED_MODE0; + break; + } + + sc->sc_gpio_ledpin = URTW_LED_PIN_GPIO0; + +fail: + return (error); +} + +/* XXX why we should allocalte memory buffer instead of using memory stack? */ +static usbd_status +urtw_8225_write_s16(struct urtw_softc *sc, uint8_t addr, int index, + uint16_t *data) +{ + uint8_t *buf; + uint16_t data16; + usb_device_request_t *req; + usbd_status error = 0; + + data16 = *data; + req = (usb_device_request_t *)malloc(sizeof(usb_device_request_t), + M_80211_VAP, M_NOWAIT | M_ZERO); + if (req == NULL) { + device_printf(sc->sc_dev, "could not allocate a memory\n"); + goto fail0; + } + buf = (uint8_t *)malloc(2, M_80211_VAP, M_NOWAIT | M_ZERO); + if (req == NULL) { + device_printf(sc->sc_dev, "could not allocate a memory\n"); + goto fail1; + } + + req->bmRequestType = UT_WRITE_VENDOR_DEVICE; + req->bRequest = URTW_8187_SETREGS_REQ; + USETW(req->wValue, addr); + USETW(req->wIndex, index); + USETW(req->wLength, sizeof(uint16_t)); + buf[0] = (data16 & 0x00ff); + buf[1] = (data16 & 0xff00) >> 8; + + error = usbd_do_request(sc->sc_udev, req, buf); + + free(buf, M_80211_VAP); +fail1: free(req, M_80211_VAP); +fail0: return (error); +} + +static usbd_status +urtw_8225_read(struct urtw_softc *sc, uint8_t addr, uint32_t *data) +{ + int i; + int16_t bit; + uint8_t rlen = 12, wlen = 6; + uint16_t o1, o2, o3, tmp; + uint32_t d2w = ((uint32_t)(addr & 0x1f)) << 27; + uint32_t mask = 0x80000000, value = 0; + usbd_status error; + + urtw_read16_m(sc, URTW_RF_PINS_OUTPUT, &o1); + urtw_read16_m(sc, URTW_RF_PINS_ENABLE, &o2); + urtw_read16_m(sc, URTW_RF_PINS_SELECT, &o3); + urtw_write16_m(sc, URTW_RF_PINS_ENABLE, o2 | URTW_RF_PINS_MAGIC4); + urtw_write16_m(sc, URTW_RF_PINS_SELECT, o3 | URTW_RF_PINS_MAGIC4); + o1 &= ~URTW_RF_PINS_MAGIC4; + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, o1 | URTW_BB_HOST_BANG_EN); + DELAY(5); + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, o1); + DELAY(5); + + for (i = 0; i < (wlen / 2); i++, mask = mask >> 1) { + bit = ((d2w & mask) != 0) ? 1 : 0; + + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, bit | o1); + DELAY(2); + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, bit | o1 | + URTW_BB_HOST_BANG_CLK); + DELAY(2); + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, bit | o1 | + URTW_BB_HOST_BANG_CLK); + DELAY(2); + mask = mask >> 1; + if (i == 2) + break; + bit = ((d2w & mask) != 0) ? 1 : 0; + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, bit | o1 | + URTW_BB_HOST_BANG_CLK); + DELAY(2); + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, bit | o1 | + URTW_BB_HOST_BANG_CLK); + DELAY(2); + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, bit | o1); + DELAY(1); + } + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, bit | o1 | URTW_BB_HOST_BANG_RW | + URTW_BB_HOST_BANG_CLK); + DELAY(2); + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, bit | o1 | URTW_BB_HOST_BANG_RW); + DELAY(2); + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, o1 | URTW_BB_HOST_BANG_RW); + DELAY(2); + + mask = 0x800; + for (i = 0; i < rlen; i++, mask = mask >> 1) { + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, + o1 | URTW_BB_HOST_BANG_RW); + DELAY(2); + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, + o1 | URTW_BB_HOST_BANG_RW | URTW_BB_HOST_BANG_CLK); + DELAY(2); + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, + o1 | URTW_BB_HOST_BANG_RW | URTW_BB_HOST_BANG_CLK); + DELAY(2); + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, + o1 | URTW_BB_HOST_BANG_RW | URTW_BB_HOST_BANG_CLK); + DELAY(2); + + urtw_read16_m(sc, URTW_RF_PINS_INPUT, &tmp); + value |= ((tmp & URTW_BB_HOST_BANG_CLK) ? mask : 0); + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, + o1 | URTW_BB_HOST_BANG_RW); + DELAY(2); + } + + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, o1 | URTW_BB_HOST_BANG_EN | + URTW_BB_HOST_BANG_RW); + DELAY(2); + + urtw_write16_m(sc, URTW_RF_PINS_ENABLE, o2); + urtw_write16_m(sc, URTW_RF_PINS_SELECT, o3); + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, URTW_RF_PINS_OUTPUT_MAGIC1); + + if (data != NULL) + *data = value; +fail: + return (error); +} + +static usbd_status +urtw_8225_write_c(struct urtw_softc *sc, uint8_t addr, uint16_t data) +{ + uint16_t d80, d82, d84; + usbd_status error; + + urtw_read16_m(sc, URTW_RF_PINS_OUTPUT, &d80); + d80 &= URTW_RF_PINS_MAGIC1; + urtw_read16_m(sc, URTW_RF_PINS_ENABLE, &d82); + urtw_read16_m(sc, URTW_RF_PINS_SELECT, &d84); + d84 &= URTW_RF_PINS_MAGIC2; + urtw_write16_m(sc, URTW_RF_PINS_ENABLE, d82 | URTW_RF_PINS_MAGIC3); + urtw_write16_m(sc, URTW_RF_PINS_SELECT, d84 | URTW_RF_PINS_MAGIC3); + DELAY(10); + + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, d80 | URTW_BB_HOST_BANG_EN); + DELAY(2); + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, d80); + DELAY(10); + + error = urtw_8225_write_s16(sc, addr, 0x8225, &data); + if (error != 0) + goto fail; + + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, d80 | URTW_BB_HOST_BANG_EN); + DELAY(10); + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, d80 | URTW_BB_HOST_BANG_EN); + urtw_write16_m(sc, URTW_RF_PINS_SELECT, d84); + usbd_delay_ms(sc->sc_udev, 2); +fail: + return (error); +} + +static usbd_status +urtw_8225_isv2(struct urtw_softc *sc, int *ret) +{ + uint32_t data; + usbd_status error; + + *ret = 1; + + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, URTW_RF_PINS_MAGIC5); + urtw_write16_m(sc, URTW_RF_PINS_SELECT, URTW_RF_PINS_MAGIC5); + urtw_write16_m(sc, URTW_RF_PINS_ENABLE, URTW_RF_PINS_MAGIC5); + usbd_delay_ms(sc->sc_udev, 500); + + urtw_8225_write(sc, URTW_8225_ADDR_0_MAGIC, + URTW_8225_ADDR_0_DATA_MAGIC1); + + error = urtw_8225_read(sc, URTW_8225_ADDR_8_MAGIC, &data); + if (error != 0) + goto fail; + if (data != URTW_8225_ADDR_8_DATA_MAGIC1) + *ret = 0; + else { + error = urtw_8225_read(sc, URTW_8225_ADDR_9_MAGIC, &data); + if (error != 0) + goto fail; + if (data != URTW_8225_ADDR_9_DATA_MAGIC1) + *ret = 0; + } + + urtw_8225_write(sc, URTW_8225_ADDR_0_MAGIC, + URTW_8225_ADDR_0_DATA_MAGIC2); +fail: + return (error); +} + +static usbd_status +urtw_get_rfchip(struct urtw_softc *sc) +{ + int ret; + uint32_t data; + usbd_status error; + + error = urtw_eprom_read32(sc, URTW_EPROM_RFCHIPID, &data); + if (error != 0) + goto fail; + switch (data & 0xff) { + case URTW_EPROM_RFCHIPID_RTL8225U: + error = urtw_8225_isv2(sc, &ret); + if (error != 0) + goto fail; + if (ret == 0) { + sc->sc_rf_init = urtw_8225_rf_init; + sc->sc_rf_set_sens = urtw_8225_rf_set_sens; + sc->sc_rf_set_chan = urtw_8225_rf_set_chan; + } else { + sc->sc_rf_init = urtw_8225v2_rf_init; + sc->sc_rf_set_chan = urtw_8225v2_rf_set_chan; + } + sc->sc_max_sens = URTW_8225_RF_MAX_SENS; + sc->sc_sens = URTW_8225_RF_DEF_SENS; + break; + default: + panic("unsupported RF chip %d\n", data & 0xff); + /* never reach */ + } + +fail: + return (error); +} + +static usbd_status +urtw_get_txpwr(struct urtw_softc *sc) +{ + int i, j; + uint32_t data; + usbd_status error; + + error = urtw_eprom_read32(sc, URTW_EPROM_TXPW_BASE, &data); + if (error != 0) + goto fail; + sc->sc_txpwr_cck_base = data & 0xf; + sc->sc_txpwr_ofdm_base = (data >> 4) & 0xf; + + for (i = 1, j = 0; i < 6; i += 2, j++) { + error = urtw_eprom_read32(sc, URTW_EPROM_TXPW0 + j, &data); + if (error != 0) + goto fail; + sc->sc_txpwr_cck[i] = data & 0xf; + sc->sc_txpwr_cck[i + 1] = (data & 0xf00) >> 8; + sc->sc_txpwr_ofdm[i] = (data & 0xf0) >> 4; + sc->sc_txpwr_ofdm[i + 1] = (data & 0xf000) >> 12; + } + for (i = 1, j = 0; i < 4; i += 2, j++) { + error = urtw_eprom_read32(sc, URTW_EPROM_TXPW1 + j, &data); + if (error != 0) + goto fail; + sc->sc_txpwr_cck[i + 6] = data & 0xf; + sc->sc_txpwr_cck[i + 6 + 1] = (data & 0xf00) >> 8; + sc->sc_txpwr_ofdm[i + 6] = (data & 0xf0) >> 4; + sc->sc_txpwr_ofdm[i + 6 + 1] = (data & 0xf000) >> 12; + } + for (i = 1, j = 0; i < 4; i += 2, j++) { + error = urtw_eprom_read32(sc, URTW_EPROM_TXPW2 + j, &data); + if (error != 0) + goto fail; + sc->sc_txpwr_cck[i + 6 + 4] = data & 0xf; + sc->sc_txpwr_cck[i + 6 + 4 + 1] = (data & 0xf00) >> 8; + sc->sc_txpwr_ofdm[i + 6 + 4] = (data & 0xf0) >> 4; + sc->sc_txpwr_ofdm[i + 6 + 4 + 1] = (data & 0xf000) >> 12; + } +fail: + return (error); +} + +static usbd_status +urtw_get_macaddr(struct urtw_softc *sc) +{ + uint32_t data; + usbd_status error; + + error = urtw_eprom_read32(sc, URTW_EPROM_MACADDR, &data); + if (error != 0) + goto fail; + sc->sc_bssid[0] = data & 0xff; + sc->sc_bssid[1] = (data & 0xff00) >> 8; + error = urtw_eprom_read32(sc, URTW_EPROM_MACADDR + 1, &data); + if (error != 0) + goto fail; + sc->sc_bssid[2] = data & 0xff; + sc->sc_bssid[3] = (data & 0xff00) >> 8; + error = urtw_eprom_read32(sc, URTW_EPROM_MACADDR + 2, &data); + if (error != 0) + goto fail; + sc->sc_bssid[4] = data & 0xff; + sc->sc_bssid[5] = (data & 0xff00) >> 8; +fail: + return (error); +} + +static usbd_status +urtw_eprom_read32(struct urtw_softc *sc, uint32_t addr, uint32_t *data) +{ +#define URTW_READCMD_LEN 3 + int addrlen, i; + int16_t addrstr[8], data16, readcmd[] = { 1, 1, 0 }; + usbd_status error; + + /* NB: make sure the buffer is initialized */ + *data = 0; + + /* enable EPROM programming */ + urtw_write8_m(sc, URTW_EPROM_CMD, URTW_EPROM_CMD_PROGRAM_MODE); + DELAY(URTW_EPROM_DELAY); + + error = urtw_eprom_cs(sc, URTW_EPROM_ENABLE); + if (error != 0) + goto fail; + error = urtw_eprom_ck(sc); + if (error != 0) + goto fail; + error = urtw_eprom_sendbits(sc, readcmd, URTW_READCMD_LEN); + if (error != 0) + goto fail; + if (sc->sc_epromtype == URTW_EEPROM_93C56) { + addrlen = 8; + addrstr[0] = addr & (1 << 7); + addrstr[1] = addr & (1 << 6); + addrstr[2] = addr & (1 << 5); + addrstr[3] = addr & (1 << 4); + addrstr[4] = addr & (1 << 3); + addrstr[5] = addr & (1 << 2); + addrstr[6] = addr & (1 << 1); + addrstr[7] = addr & (1 << 0); + } else { + addrlen=6; + addrstr[0] = addr & (1 << 5); + addrstr[1] = addr & (1 << 4); + addrstr[2] = addr & (1 << 3); + addrstr[3] = addr & (1 << 2); + addrstr[4] = addr & (1 << 1); + addrstr[5] = addr & (1 << 0); + } + error = urtw_eprom_sendbits(sc, addrstr, addrlen); + if (error != 0) + goto fail; + + error = urtw_eprom_writebit(sc, 0); + if (error != 0) + goto fail; + + for (i = 0; i < 16; i++) { + error = urtw_eprom_ck(sc); + if (error != 0) + goto fail; + error = urtw_eprom_readbit(sc, &data16); + if (error != 0) + goto fail; + + (*data) |= (data16 << (15 - i)); + } + + error = urtw_eprom_cs(sc, URTW_EPROM_DISABLE); + if (error != 0) + goto fail; + error = urtw_eprom_ck(sc); + if (error != 0) + goto fail; + + /* now disable EPROM programming */ + urtw_write8_m(sc, URTW_EPROM_CMD, URTW_EPROM_CMD_NORMAL_MODE); +fail: + return (error); +#undef URTW_READCMD_LEN +} + +static usbd_status +urtw_eprom_readbit(struct urtw_softc *sc, int16_t *data) +{ + uint8_t data8; + usbd_status error; + + urtw_read8_m(sc, URTW_EPROM_CMD, &data8); + *data = (data8 & URTW_EPROM_READBIT) ? 1 : 0; + DELAY(URTW_EPROM_DELAY); + +fail: + return (error); +} + +static usbd_status +urtw_eprom_sendbits(struct urtw_softc *sc, int16_t *buf, int buflen) +{ + int i = 0; + usbd_status error = 0; + + for (i = 0; i < buflen; i++) { + error = urtw_eprom_writebit(sc, buf[i]); + if (error != 0) + goto fail; + error = urtw_eprom_ck(sc); + if (error != 0) + goto fail; + } +fail: + return (error); +} + +static usbd_status +urtw_eprom_writebit(struct urtw_softc *sc, int16_t bit) +{ + uint8_t data; + usbd_status error; + + urtw_read8_m(sc, URTW_EPROM_CMD, &data); + if (bit != 0) + urtw_write8_m(sc, URTW_EPROM_CMD, data | URTW_EPROM_WRITEBIT); + else + urtw_write8_m(sc, URTW_EPROM_CMD, data & ~URTW_EPROM_WRITEBIT); + DELAY(URTW_EPROM_DELAY); +fail: + return (error); +} + +static usbd_status +urtw_eprom_ck(struct urtw_softc *sc) +{ + uint8_t data; + usbd_status error; + + /* masking */ + urtw_read8_m(sc, URTW_EPROM_CMD, &data); + urtw_write8_m(sc, URTW_EPROM_CMD, data | URTW_EPROM_CK); + DELAY(URTW_EPROM_DELAY); + /* unmasking */ + urtw_read8_m(sc, URTW_EPROM_CMD, &data); + urtw_write8_m(sc, URTW_EPROM_CMD, data & ~URTW_EPROM_CK); + DELAY(URTW_EPROM_DELAY); +fail: + return (error); +} + +static usbd_status +urtw_eprom_cs(struct urtw_softc *sc, int able) +{ + uint8_t data; + usbd_status error; + + urtw_read8_m(sc, URTW_EPROM_CMD, &data); + if (able == URTW_EPROM_ENABLE) + urtw_write8_m(sc, URTW_EPROM_CMD, data | URTW_EPROM_CS); + else + urtw_write8_m(sc, URTW_EPROM_CMD, data & ~URTW_EPROM_CS); + DELAY(URTW_EPROM_DELAY); +fail: + return (error); +} + +static usbd_status +urtw_read8_c(struct urtw_softc *sc, int val, uint8_t *data) +{ + usb_device_request_t req; + usbd_status error; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = URTW_8187_GETREGS_REQ; + USETW(req.wValue, val | 0xff00); + USETW(req.wIndex, 0); + USETW(req.wLength, sizeof(uint8_t)); + + error = usbd_do_request(sc->sc_udev, &req, data); + return (error); +} + +static usbd_status +urtw_read8e(struct urtw_softc *sc, int val, uint8_t *data) +{ + usb_device_request_t req; + usbd_status error; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = URTW_8187_GETREGS_REQ; + USETW(req.wValue, val | 0xfe00); + USETW(req.wIndex, 0); + USETW(req.wLength, sizeof(uint8_t)); + + error = usbd_do_request(sc->sc_udev, &req, data); + return (error); +} + +static usbd_status +urtw_read16_c(struct urtw_softc *sc, int val, uint16_t *data) +{ + usb_device_request_t req; + usbd_status error; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = URTW_8187_GETREGS_REQ; + USETW(req.wValue, val | 0xff00); + USETW(req.wIndex, 0); + USETW(req.wLength, sizeof(uint16_t)); + + error = usbd_do_request(sc->sc_udev, &req, data); + return (error); +} + +static usbd_status +urtw_read32_c(struct urtw_softc *sc, int val, uint32_t *data) +{ + usb_device_request_t req; + usbd_status error; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = URTW_8187_GETREGS_REQ; + USETW(req.wValue, val | 0xff00); + USETW(req.wIndex, 0); + USETW(req.wLength, sizeof(uint32_t)); + + error = usbd_do_request(sc->sc_udev, &req, data); + return (error); +} + +static usbd_status +urtw_write8_c(struct urtw_softc *sc, int val, uint8_t data) +{ + usb_device_request_t req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = URTW_8187_SETREGS_REQ; + USETW(req.wValue, val | 0xff00); + USETW(req.wIndex, 0); + USETW(req.wLength, sizeof(uint8_t)); + + return (usbd_do_request(sc->sc_udev, &req, &data)); +} + +static usbd_status +urtw_write8e(struct urtw_softc *sc, int val, uint8_t data) +{ + usb_device_request_t req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = URTW_8187_SETREGS_REQ; + USETW(req.wValue, val | 0xfe00); + USETW(req.wIndex, 0); + USETW(req.wLength, sizeof(uint8_t)); + + return (usbd_do_request(sc->sc_udev, &req, &data)); +} + +static usbd_status +urtw_write16_c(struct urtw_softc *sc, int val, uint16_t data) +{ + usb_device_request_t req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = URTW_8187_SETREGS_REQ; + USETW(req.wValue, val | 0xff00); + USETW(req.wIndex, 0); + USETW(req.wLength, sizeof(uint16_t)); + + return (usbd_do_request(sc->sc_udev, &req, &data)); +} + +static usbd_status +urtw_write32_c(struct urtw_softc *sc, int val, uint32_t data) +{ + usb_device_request_t req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = URTW_8187_SETREGS_REQ; + USETW(req.wValue, val | 0xff00); + USETW(req.wIndex, 0); + USETW(req.wLength, sizeof(uint32_t)); + + return (usbd_do_request(sc->sc_udev, &req, &data)); +} + +static int +urtw_detach(device_t dev) +{ + struct urtw_softc *sc = device_get_softc(dev); + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + if (!device_is_attached(dev)) + return 0; + + urtw_stop(ifp, 1); + + callout_drain(&sc->sc_led_ch); + callout_drain(&sc->sc_watchdog_ch); + usb_rem_task(sc->sc_udev, &sc->sc_ledtask); + usb_rem_task(sc->sc_udev, &sc->sc_ctxtask); + usb_rem_task(sc->sc_udev, &sc->sc_task); + + /* abort and free xfers */ + urtw_free_tx_data_list(sc); + urtw_free_rx_data_list(sc); + urtw_close_pipes(sc); + + bpfdetach(ifp); + ieee80211_ifdetach(ic); + if_free(ifp); + mtx_destroy(&sc->sc_mtx); + + usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_udev, sc->sc_dev); + + return (0); +} + +static struct ieee80211vap * +urtw_vap_create(struct ieee80211com *ic, + const char name[IFNAMSIZ], int unit, int opmode, int flags, + const uint8_t bssid[IEEE80211_ADDR_LEN], + const uint8_t mac[IEEE80211_ADDR_LEN]) +{ + struct urtw_vap *uvp; + struct ieee80211vap *vap; + + if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */ + return (NULL); + uvp = (struct urtw_vap *) malloc(sizeof(struct urtw_vap), + M_80211_VAP, M_NOWAIT | M_ZERO); + if (uvp == NULL) + return (NULL); + vap = &uvp->vap; + /* enable s/w bmiss handling for sta mode */ + ieee80211_vap_setup(ic, vap, name, unit, opmode, + flags | IEEE80211_CLONE_NOBEACONS, bssid, mac); + + /* override state transition machine */ + uvp->newstate = vap->iv_newstate; + vap->iv_newstate = urtw_newstate; + + /* complete setup */ + ieee80211_vap_attach(vap, ieee80211_media_change, + ieee80211_media_status); + ic->ic_opmode = opmode; + return (vap); +} + +static void +urtw_vap_delete(struct ieee80211vap *vap) +{ + struct urtw_vap *uvp = URTW_VAP(vap); + + ieee80211_vap_detach(vap); + free(uvp, M_80211_VAP); +} + +static usbd_status +urtw_set_mode(struct urtw_softc *sc, uint32_t mode) +{ + uint8_t data; + usbd_status error; + + urtw_read8_m(sc, URTW_EPROM_CMD, &data); + data = (data & ~URTW_EPROM_CMD_MASK) | (mode << URTW_EPROM_CMD_SHIFT); + data = data & ~(URTW_EPROM_CS | URTW_EPROM_CK); + urtw_write8_m(sc, URTW_EPROM_CMD, data); +fail: + return (error); +} + +static usbd_status +urtw_8180_set_anaparam(struct urtw_softc *sc, uint32_t val) +{ + uint8_t data; + usbd_status error; + + error = urtw_set_mode(sc, URTW_EPROM_CMD_CONFIG); + if (error) + goto fail; + + urtw_read8_m(sc, URTW_CONFIG3, &data); + urtw_write8_m(sc, URTW_CONFIG3, data | URTW_CONFIG3_ANAPARAM_WRITE); + urtw_write32_m(sc, URTW_ANAPARAM, val); + urtw_read8_m(sc, URTW_CONFIG3, &data); + urtw_write8_m(sc, URTW_CONFIG3, data & ~URTW_CONFIG3_ANAPARAM_WRITE); + + error = urtw_set_mode(sc, URTW_EPROM_CMD_NORMAL); + if (error) + goto fail; +fail: + return (error); +} + +static usbd_status +urtw_8185_set_anaparam2(struct urtw_softc *sc, uint32_t val) +{ + uint8_t data; + usbd_status error; + + error = urtw_set_mode(sc, URTW_EPROM_CMD_CONFIG); + if (error) + goto fail; + + urtw_read8_m(sc, URTW_CONFIG3, &data); + urtw_write8_m(sc, URTW_CONFIG3, data | URTW_CONFIG3_ANAPARAM_WRITE); + urtw_write32_m(sc, URTW_ANAPARAM2, val); + urtw_read8_m(sc, URTW_CONFIG3, &data); + urtw_write8_m(sc, URTW_CONFIG3, data & ~URTW_CONFIG3_ANAPARAM_WRITE); + + error = urtw_set_mode(sc, URTW_EPROM_CMD_NORMAL); + if (error) + goto fail; +fail: + return (error); +} + +static usbd_status +urtw_intr_disable(struct urtw_softc *sc) +{ + usbd_status error; + + urtw_write16_m(sc, URTW_INTR_MASK, 0); +fail: + return (error); +} + +static usbd_status +urtw_reset(struct urtw_softc *sc) +{ + uint8_t data; + usbd_status error; + + error = urtw_8180_set_anaparam(sc, URTW_8225_ANAPARAM_ON); + if (error) + goto fail; + error = urtw_8185_set_anaparam2(sc, URTW_8225_ANAPARAM2_ON); + if (error) + goto fail; + + error = urtw_intr_disable(sc); + if (error) + goto fail; + usbd_delay_ms(sc->sc_udev, 100); + + error = urtw_write8e(sc, 0x18, 0x10); + if (error != 0) + goto fail; + error = urtw_write8e(sc, 0x18, 0x11); + if (error != 0) + goto fail; + error = urtw_write8e(sc, 0x18, 0x00); + if (error != 0) + goto fail; + usbd_delay_ms(sc->sc_udev, 100); + + urtw_read8_m(sc, URTW_CMD, &data); + data = (data & 0x2) | URTW_CMD_RST; + urtw_write8_m(sc, URTW_CMD, data); + usbd_delay_ms(sc->sc_udev, 100); + + urtw_read8_m(sc, URTW_CMD, &data); + if (data & URTW_CMD_RST) { + device_printf(sc->sc_dev, "reset timeout\n"); + goto fail; + } + + error = urtw_set_mode(sc, URTW_EPROM_CMD_LOAD); + if (error) + goto fail; + usbd_delay_ms(sc->sc_udev, 100); + + error = urtw_8180_set_anaparam(sc, URTW_8225_ANAPARAM_ON); + if (error) + goto fail; + error = urtw_8185_set_anaparam2(sc, URTW_8225_ANAPARAM2_ON); + if (error) + goto fail; +fail: + return (error); +} + +static usbd_status +urtw_led_on(struct urtw_softc *sc, int type) +{ + usbd_status error; + + if (type == URTW_LED_GPIO) { + switch (sc->sc_gpio_ledpin) { + case URTW_LED_PIN_GPIO0: + urtw_write8_m(sc, URTW_GPIO, 0x01); + urtw_write8_m(sc, URTW_GP_ENABLE, 0x00); + break; + default: + panic("unsupported LED PIN type 0x%x", + sc->sc_gpio_ledpin); + /* never reach */ + } + } else { + panic("unsupported LED type 0x%x", type); + /* never reach */ + } + + sc->sc_gpio_ledon = 1; +fail: + return (error); +} + +static usbd_status +urtw_led_off(struct urtw_softc *sc, int type) +{ + usbd_status error; + + if (type == URTW_LED_GPIO) { + switch (sc->sc_gpio_ledpin) { + case URTW_LED_PIN_GPIO0: + urtw_write8_m(sc, URTW_GPIO, URTW_GPIO_DATA_MAGIC1); + urtw_write8_m(sc, + URTW_GP_ENABLE, URTW_GP_ENABLE_DATA_MAGIC1); + break; + default: + panic("unsupported LED PIN type 0x%x", + sc->sc_gpio_ledpin); + /* never reach */ + } + } else { + panic("unsupported LED type 0x%x", type); + /* never reach */ + } + + sc->sc_gpio_ledon = 0; + +fail: + return (error); +} + +static usbd_status +urtw_led_mode0(struct urtw_softc *sc, int mode) +{ + + switch (mode) { + case URTW_LED_CTL_POWER_ON: + sc->sc_gpio_ledstate = URTW_LED_POWER_ON_BLINK; + break; + case URTW_LED_CTL_TX: + if (sc->sc_gpio_ledinprogress == 1) + return (0); + + sc->sc_gpio_ledstate = URTW_LED_BLINK_NORMAL; + sc->sc_gpio_blinktime = 2; + break; + case URTW_LED_CTL_LINK: + sc->sc_gpio_ledstate = URTW_LED_ON; + break; + default: + panic("unsupported LED mode 0x%x", mode); + /* never reach */ + } + + switch (sc->sc_gpio_ledstate) { + case URTW_LED_ON: + if (sc->sc_gpio_ledinprogress != 0) + break; + urtw_led_on(sc, URTW_LED_GPIO); + break; + case URTW_LED_BLINK_NORMAL: + if (sc->sc_gpio_ledinprogress != 0) + break; + sc->sc_gpio_ledinprogress = 1; + sc->sc_gpio_blinkstate = (sc->sc_gpio_ledon != 0) ? + URTW_LED_OFF : URTW_LED_ON; + callout_reset(&sc->sc_led_ch, hz, urtw_ledtask, sc); + break; + case URTW_LED_POWER_ON_BLINK: + urtw_led_on(sc, URTW_LED_GPIO); + usbd_delay_ms(sc->sc_udev, 100); + urtw_led_off(sc, URTW_LED_GPIO); + break; + default: + panic("unknown LED status 0x%x", sc->sc_gpio_ledstate); + /* never reach */ + } + return (0); +} + +static usbd_status +urtw_led_mode1(struct urtw_softc *sc, int mode) +{ + + return (USBD_INVAL); +} + +static usbd_status +urtw_led_mode2(struct urtw_softc *sc, int mode) +{ + + return (USBD_INVAL); +} + +static usbd_status +urtw_led_mode3(struct urtw_softc *sc, int mode) +{ + + return (USBD_INVAL); +} + +static usbd_status +urtw_led_blink(struct urtw_softc *sc) +{ + uint8_t ing = 0; + usbd_status error; + + if (sc->sc_gpio_blinkstate == URTW_LED_ON) + error = urtw_led_on(sc, URTW_LED_GPIO); + else + error = urtw_led_off(sc, URTW_LED_GPIO); + sc->sc_gpio_blinktime--; + if (sc->sc_gpio_blinktime == 0) + ing = 1; + else { + if (sc->sc_gpio_ledstate != URTW_LED_BLINK_NORMAL && + sc->sc_gpio_ledstate != URTW_LED_BLINK_SLOWLY && + sc->sc_gpio_ledstate != URTW_LED_BLINK_CM3) + ing = 1; + } + if (ing == 1) { + if (sc->sc_gpio_ledstate == URTW_LED_ON && + sc->sc_gpio_ledon == 0) + error = urtw_led_on(sc, URTW_LED_GPIO); + else if (sc->sc_gpio_ledstate == URTW_LED_OFF && + sc->sc_gpio_ledon == 1) + error = urtw_led_off(sc, URTW_LED_GPIO); + + sc->sc_gpio_blinktime = 0; + sc->sc_gpio_ledinprogress = 0; + return (0); + } + + sc->sc_gpio_blinkstate = (sc->sc_gpio_blinkstate != URTW_LED_ON) ? + URTW_LED_ON : URTW_LED_OFF; + + switch (sc->sc_gpio_ledstate) { + case URTW_LED_BLINK_NORMAL: + callout_reset(&sc->sc_led_ch, hz, urtw_ledtask, sc); + break; + default: + panic("unknown LED status 0x%x", sc->sc_gpio_ledstate); + /* never reach */ + } + return (0); +} + +static void +urtw_ledusbtask(void *arg) +{ + struct urtw_softc *sc = arg; + + if (sc->sc_strategy != URTW_SW_LED_MODE0) + panic("could not process a LED strategy 0x%x", sc->sc_strategy); + + urtw_led_blink(sc); +} + +static void +urtw_ledtask(void *arg) +{ + struct urtw_softc *sc = arg; + + /* + * NB: to change a status of the led we need at least a sleep so we + * can't do it here + */ + usb_add_task(sc->sc_udev, &sc->sc_ledtask, USB_TASKQ_DRIVER); +} + +static usbd_status +urtw_led_ctl(struct urtw_softc *sc, int mode) +{ + usbd_status error = 0; + + switch (sc->sc_strategy) { + case URTW_SW_LED_MODE0: + error = urtw_led_mode0(sc, mode); + break; + case URTW_SW_LED_MODE1: + error = urtw_led_mode1(sc, mode); + break; + case URTW_SW_LED_MODE2: + error = urtw_led_mode2(sc, mode); + break; + case URTW_SW_LED_MODE3: + error = urtw_led_mode3(sc, mode); + break; + default: + panic("unsupported LED mode %d\n", sc->sc_strategy); + /* never reach */ + } + + return (error); +} + +static usbd_status +urtw_update_msr(struct urtw_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + uint8_t data; + usbd_status error; + + urtw_read8_m(sc, URTW_MSR, &data); + data &= ~URTW_MSR_LINK_MASK; + + if (sc->sc_state == IEEE80211_S_RUN) { + switch (ic->ic_opmode) { + case IEEE80211_M_STA: + case IEEE80211_M_MONITOR: + data |= URTW_MSR_LINK_STA; + break; + case IEEE80211_M_IBSS: + data |= URTW_MSR_LINK_ADHOC; + break; + case IEEE80211_M_HOSTAP: + data |= URTW_MSR_LINK_HOSTAP; + break; + default: + panic("unsupported operation mode 0x%x\n", + ic->ic_opmode); + /* never reach */ + } + } else + data |= URTW_MSR_LINK_NONE; + + urtw_write8_m(sc, URTW_MSR, data); +fail: + return (error); +} + +static uint16_t +urtw_rate2rtl(int rate) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + int i; + + for (i = 0; i < N(urtw_ratetable); i++) { + if (rate == urtw_ratetable[i].reg) + return urtw_ratetable[i].val; + } + + return (3); +#undef N +} + +static uint16_t +urtw_rtl2rate(int rate) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + int i; + + for (i = 0; i < N(urtw_ratetable); i++) { + if (rate == urtw_ratetable[i].val) + return urtw_ratetable[i].reg; + } + + return (0); +#undef N +} + +static usbd_status +urtw_set_rate(struct urtw_softc *sc) +{ + int i, basic_rate, min_rr_rate, max_rr_rate; + uint16_t data; + usbd_status error; + + basic_rate = urtw_rate2rtl(48); + min_rr_rate = urtw_rate2rtl(12); + max_rr_rate = urtw_rate2rtl(48); + + urtw_write8_m(sc, URTW_RESP_RATE, + max_rr_rate << URTW_RESP_MAX_RATE_SHIFT | + min_rr_rate << URTW_RESP_MIN_RATE_SHIFT); + + urtw_read16_m(sc, URTW_BRSR, &data); + data &= ~URTW_BRSR_MBR_8185; + + for (i = 0; i <= basic_rate; i++) + data |= (1 << i); + + urtw_write16_m(sc, URTW_BRSR, data); +fail: + return (error); +} + +static usbd_status +urtw_intr_enable(struct urtw_softc *sc) +{ + usbd_status error; + + urtw_write16_m(sc, URTW_INTR_MASK, 0xffff); +fail: + return (error); +} + +static usbd_status +urtw_adapter_start(struct urtw_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + usbd_status error; + + error = urtw_reset(sc); + if (error) + goto fail; + + urtw_write8_m(sc, URTW_ADDR_MAGIC1, 0); + urtw_write8_m(sc, URTW_GPIO, 0); + + /* for led */ + urtw_write8_m(sc, URTW_ADDR_MAGIC1, 4); + error = urtw_led_ctl(sc, URTW_LED_CTL_POWER_ON); + if (error != 0) + goto fail; + + error = urtw_set_mode(sc, URTW_EPROM_CMD_CONFIG); + if (error) + goto fail; + /* applying MAC address again. */ + urtw_write32_m(sc, URTW_MAC0, ((uint32_t *)ic->ic_myaddr)[0]); + urtw_write16_m(sc, URTW_MAC4, ((uint32_t *)ic->ic_myaddr)[1] & 0xffff); + error = urtw_set_mode(sc, URTW_EPROM_CMD_NORMAL); + if (error) + goto fail; + + error = urtw_update_msr(sc); + if (error) + goto fail; + + urtw_write32_m(sc, URTW_INT_TIMEOUT, 0); + urtw_write8_m(sc, URTW_WPA_CONFIG, 0); + urtw_write8_m(sc, URTW_RATE_FALLBACK, 0x81); + error = urtw_set_rate(sc); + if (error != 0) + goto fail; + + error = sc->sc_rf_init(sc); + if (error != 0) + goto fail; + if (sc->sc_rf_set_sens != NULL) + sc->sc_rf_set_sens(sc, sc->sc_sens); + + /* XXX correct? to call write16 */ + urtw_write16_m(sc, URTW_PSR, 1); + urtw_write16_m(sc, URTW_ADDR_MAGIC2, 0x10); + urtw_write8_m(sc, URTW_TALLY_SEL, 0x80); + urtw_write8_m(sc, URTW_ADDR_MAGIC3, 0x60); + /* XXX correct? to call write16 */ + urtw_write16_m(sc, URTW_PSR, 0); + urtw_write8_m(sc, URTW_ADDR_MAGIC1, 4); + + error = urtw_intr_enable(sc); + if (error != 0) + goto fail; + +fail: + return (error); +} + +static usbd_status +urtw_rx_setconf(struct urtw_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + uint32_t data; + usbd_status error; + + urtw_read32_m(sc, URTW_RX, &data); + data = data &~ URTW_RX_FILTER_MASK; +#if 0 + data = data | URTW_RX_FILTER_CTL; +#endif + data = data | URTW_RX_FILTER_MNG | URTW_RX_FILTER_DATA; + data = data | URTW_RX_FILTER_BCAST | URTW_RX_FILTER_MCAST; + + if (ic->ic_opmode == IEEE80211_M_MONITOR) { + data = data | URTW_RX_FILTER_ICVERR; + data = data | URTW_RX_FILTER_PWR; + } + if (sc->sc_crcmon == 1 && ic->ic_opmode == IEEE80211_M_MONITOR) + data = data | URTW_RX_FILTER_CRCERR; + + if (ic->ic_opmode == IEEE80211_M_MONITOR || + (ifp->if_flags & (IFF_ALLMULTI | IFF_PROMISC))) { + data = data | URTW_RX_FILTER_ALLMAC; + } else { + data = data | URTW_RX_FILTER_NICMAC; + data = data | URTW_RX_CHECK_BSSID; + } + + data = data &~ URTW_RX_FIFO_THRESHOLD_MASK; + data = data | URTW_RX_FIFO_THRESHOLD_NONE | URTW_RX_AUTORESETPHY; + data = data &~ URTW_MAX_RX_DMA_MASK; + data = data | URTW_MAX_RX_DMA_2048 | URTW_RCR_ONLYERLPKT; + + urtw_write32_m(sc, URTW_RX, data); +fail: + return (error); +} + +static usbd_status +urtw_rx_enable(struct urtw_softc *sc) +{ + int i; + struct urtw_data *rxdata; + uint8_t data; + usbd_status error; + + /* + * Start up the receive pipe. + */ + for (i = 0; i < URTW_RX_DATA_LIST_COUNT; i++) { + rxdata = &sc->sc_rxdata[i]; + + usbd_setup_xfer(rxdata->xfer, sc->sc_rxpipe, rxdata, + rxdata->buf, MCLBYTES, USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT, + urtw_rxeof); + error = usbd_transfer(rxdata->xfer); + if (error != USBD_IN_PROGRESS && error != 0) { + device_printf(sc->sc_dev, + "could not queue Rx transfer\n"); + goto fail; + } + } + + error = urtw_rx_setconf(sc); + if (error != 0) + goto fail; + + urtw_read8_m(sc, URTW_CMD, &data); + urtw_write8_m(sc, URTW_CMD, data | URTW_CMD_RX_ENABLE); +fail: + return (error); +} + +static usbd_status +urtw_tx_enable(struct urtw_softc *sc) +{ + uint8_t data8; + uint32_t data; + usbd_status error; + + urtw_read8_m(sc, URTW_CW_CONF, &data8); + data8 &= ~(URTW_CW_CONF_PERPACKET_CW | URTW_CW_CONF_PERPACKET_RETRY); + urtw_write8_m(sc, URTW_CW_CONF, data8); + + urtw_read8_m(sc, URTW_TX_AGC_CTL, &data8); + data8 &= ~URTW_TX_AGC_CTL_PERPACKET_GAIN; + data8 &= ~URTW_TX_AGC_CTL_PERPACKET_ANTSEL; + data8 &= ~URTW_TX_AGC_CTL_FEEDBACK_ANT; + urtw_write8_m(sc, URTW_TX_AGC_CTL, data8); + + urtw_read32_m(sc, URTW_TX_CONF, &data); + data &= ~URTW_TX_LOOPBACK_MASK; + data |= URTW_TX_LOOPBACK_NONE; + data &= ~(URTW_TX_DPRETRY_MASK | URTW_TX_RTSRETRY_MASK); + data |= sc->sc_tx_retry << URTW_TX_DPRETRY_SHIFT; + data |= sc->sc_rts_retry << URTW_TX_RTSRETRY_SHIFT; + data &= ~(URTW_TX_NOCRC | URTW_TX_MXDMA_MASK); + data |= URTW_TX_MXDMA_2048 | URTW_TX_CWMIN | URTW_TX_DISCW; + data &= ~URTW_TX_SWPLCPLEN; + data |= URTW_TX_NOICV; + urtw_write32_m(sc, URTW_TX_CONF, data); + + urtw_read8_m(sc, URTW_CMD, &data8); + urtw_write8_m(sc, URTW_CMD, data8 | URTW_CMD_TX_ENABLE); +fail: + return (error); +} + +static void +urtw_init(void *arg) +{ + int ret; + struct urtw_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + usbd_status error; + + urtw_stop(ifp, 0); + + error = urtw_adapter_start(sc); + if (error != 0) + goto fail; + + /* reset softc variables */ + sc->sc_txidx = sc->sc_tx_low_queued = sc->sc_tx_normal_queued = 0; + sc->sc_txtimer = 0; + + if (!(sc->sc_flags & URTW_INIT_ONCE)) { + error = usbd_set_config_no(sc->sc_udev, URTW_CONFIG_NO, 0); + if (error != 0) { + device_printf(sc->sc_dev, + "could not set configuration no\n"); + goto fail; + } + /* get the first interface handle */ + error = usbd_device2interface_handle(sc->sc_udev, + URTW_IFACE_INDEX, &sc->sc_iface); + if (error != 0) { + device_printf(sc->sc_dev, + "could not get interface handle\n"); + goto fail; + } + error = urtw_open_pipes(sc); + if (error != 0) + goto fail; + ret = urtw_alloc_rx_data_list(sc); + if (error != 0) + goto fail; + ret = urtw_alloc_tx_data_list(sc); + if (error != 0) + goto fail; + sc->sc_flags |= URTW_INIT_ONCE; + } + + error = urtw_rx_enable(sc); + if (error != 0) + goto fail; + error = urtw_tx_enable(sc); + if (error != 0) + goto fail; + + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + ifp->if_drv_flags |= IFF_DRV_RUNNING; + + callout_reset(&sc->sc_watchdog_ch, hz, urtw_watchdog, sc); +fail: + return; +} + +static void +urtw_set_multi(void *arg) +{ + struct urtw_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + + if (!(ifp->if_flags & IFF_UP)) + return; + + /* + * XXX don't know how to set a device. Lack of docs. Just try to set + * IFF_ALLMULTI flag here. + */ + IF_ADDR_LOCK(ifp); + ifp->if_flags |= IFF_ALLMULTI; + IF_ADDR_UNLOCK(ifp); +} + +static int +urtw_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) +{ + struct urtw_softc *sc = ifp->if_softc; + struct ieee80211com *ic = ifp->if_l2com; + struct ifreq *ifr = (struct ifreq *) data; + int error = 0, startall = 0; + + switch (cmd) { + case SIOCSIFFLAGS: + mtx_lock(&Giant); + if (ifp->if_flags & IFF_UP) { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + if ((ifp->if_flags ^ sc->sc_if_flags) & + (IFF_ALLMULTI | IFF_PROMISC)) + urtw_set_multi(sc); + } else { + urtw_init(ifp->if_softc); + startall = 1; + } + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + urtw_stop(ifp, 1); + } + sc->sc_if_flags = ifp->if_flags; + mtx_unlock(&Giant); + if (startall) + ieee80211_start_all(ic); + break; + case SIOCGIFMEDIA: + error = ifmedia_ioctl(ifp, ifr, &ic->ic_media, cmd); + break; + case SIOCGIFADDR: + error = ether_ioctl(ifp, cmd, data); + break; + default: + error = EINVAL; + break; + } + + return error; +} + +static void +urtw_start(struct ifnet *ifp) +{ + struct urtw_softc *sc = ifp->if_softc; + struct ieee80211_node *ni; + struct mbuf *m; + + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) + return; + + URTW_LOCK(sc); + for (;;) { + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + if (m == NULL) + break; + if (sc->sc_tx_low_queued >= URTW_TX_DATA_LIST_COUNT || + sc->sc_tx_normal_queued >= URTW_TX_DATA_LIST_COUNT) { + IFQ_DRV_PREPEND(&ifp->if_snd, m); + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + break; + } + + ni = (struct ieee80211_node *)m->m_pkthdr.rcvif; + m->m_pkthdr.rcvif = NULL; + m = ieee80211_encap(ni, m); + if (m == NULL) { + ieee80211_free_node(ni); + ifp->if_oerrors++; + continue; + } + + if (urtw_tx_start(sc, ni, m, URTW_PRIORITY_NORMAL) != 0) { + ieee80211_free_node(ni); + ifp->if_oerrors++; + break; + } + + sc->sc_txtimer = 5; + } + URTW_UNLOCK(sc); +} + +static void +urtw_txeof_low(usbd_xfer_handle xfer, usbd_private_handle priv, + usbd_status status) +{ + struct urtw_data *data = priv; + struct urtw_softc *sc = data->sc; + struct ifnet *ifp = sc->sc_ifp; + struct mbuf *m; + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) + return; + + device_printf(sc->sc_dev, "could not transmit buffer: %s\n", + usbd_errstr(status)); + + if (status == USBD_STALLED) + usbd_clear_endpoint_stall_async(sc->sc_txpipe_low); + + ifp->if_oerrors++; + return; + } + + /* + * Do any tx complete callback. Note this must be done before releasing + * the node reference. + */ + m = data->m; + if (m != NULL && m->m_flags & M_TXCB) { + ieee80211_process_callback(data->ni, m, 0); /* XXX status? */ + m_freem(m); + data->m = NULL; + } + + ieee80211_free_node(data->ni); + data->ni = NULL; + + sc->sc_txtimer = 0; + ifp->if_opackets++; + + URTW_LOCK(sc); + sc->sc_tx_low_queued--; + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + URTW_UNLOCK(sc); + + urtw_start(ifp); +} + +static void +urtw_txeof_normal(usbd_xfer_handle xfer, usbd_private_handle priv, + usbd_status status) +{ + struct urtw_data *data = priv; + struct urtw_softc *sc = data->sc; + struct ifnet *ifp = sc->sc_ifp; + struct mbuf *m; + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) + return; + + device_printf(sc->sc_dev, "could not transmit buffer: %s\n", + usbd_errstr(status)); + + if (status == USBD_STALLED) + usbd_clear_endpoint_stall_async(sc->sc_txpipe_normal); + + ifp->if_oerrors++; + return; + } + + /* + * Do any tx complete callback. Note this must be done before releasing + * the node reference. + */ + m = data->m; + if (m != NULL && m->m_flags & M_TXCB) { + ieee80211_process_callback(data->ni, m, 0); /* XXX status? */ + m_freem(m); + data->m = NULL; + } + + ieee80211_free_node(data->ni); + data->ni = NULL; + + sc->sc_txtimer = 0; + ifp->if_opackets++; + + URTW_LOCK(sc); + sc->sc_tx_normal_queued--; + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + URTW_UNLOCK(sc); + + urtw_start(ifp); +} + +static int +urtw_tx_start(struct urtw_softc *sc, struct ieee80211_node *ni, struct mbuf *m0, + int prior) +{ + int xferlen; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211_frame *wh = mtod(m0, struct ieee80211_frame *); + struct ieee80211_key *k; + const struct ieee80211_txparam *tp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211vap *vap = ni->ni_vap; + struct urtw_data *data; + usbd_status error; + + URTW_ASSERT_LOCKED(sc); + + /* + * Software crypto. + */ + if (wh->i_fc[1] & IEEE80211_FC1_WEP) { + k = ieee80211_crypto_encap(ni, m0); + if (k == NULL) { + device_printf(sc->sc_dev, + "ieee80211_crypto_encap returns NULL.\n"); + /* XXX we don't expect the fragmented frames */ + m_freem(m0); + return (ENOBUFS); + } + + /* in case packet header moved, reset pointer */ + wh = mtod(m0, struct ieee80211_frame *); + } + + if (bpf_peers_present(ifp->if_bpf)) { + struct urtw_tx_radiotap_header *tap = &sc->sc_txtap; + + /* XXX Are variables correct? */ + tap->wt_flags = 0; + tap->wt_chan_freq = htole16(ic->ic_curchan->ic_freq); + tap->wt_chan_flags = htole16(ic->ic_curchan->ic_flags); + + bpf_mtap2(ifp->if_bpf, tap, sc->sc_txtap_len, m0); + } + + xferlen = m0->m_pkthdr.len + 4 * 3; + if((0 == xferlen % 64) || (0 == xferlen % 512)) + xferlen += 1; + + data = &sc->sc_txdata[sc->sc_txidx]; + sc->sc_txidx = (sc->sc_txidx + 1) % URTW_TX_DATA_LIST_COUNT; + + bzero(data->buf, URTW_TX_MAXSIZE); + data->buf[0] = m0->m_pkthdr.len & 0xff; + data->buf[1] = (m0->m_pkthdr.len & 0x0f00) >> 8; + data->buf[1] |= (1 << 7); + + if ((ic->ic_flags & IEEE80211_F_SHPREAMBLE) && + (ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_PREAMBLE) && + (sc->sc_preamble_mode == URTW_PREAMBLE_MODE_SHORT) && + (sc->sc_currate != 0)) + data->buf[2] |= 1; + if ((m0->m_pkthdr.len > vap->iv_rtsthreshold) && + prior == URTW_PRIORITY_LOW) + panic("TODO tx."); + if (wh->i_fc[1] & IEEE80211_FC1_MORE_FRAG) + data->buf[2] |= (1 << 1); + /* RTS rate - 10 means we use a basic rate. */ + data->buf[2] |= (urtw_rate2rtl(2) << 3); + /* + * XXX currently TX rate control depends on the rate value of + * RX descriptor because I don't know how to we can control TX rate + * in more smart way. Please fix me you find a thing. + */ + data->buf[3] = sc->sc_currate; + if (prior == URTW_PRIORITY_NORMAL) { + tp = &vap->iv_txparms[ieee80211_chan2mode(ni->ni_chan)]; + if (IEEE80211_IS_MULTICAST(wh->i_addr1)) + data->buf[3] = urtw_rate2rtl(tp->mcastrate); + else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) + data->buf[3] = urtw_rate2rtl(tp->ucastrate); + } + data->buf[8] = 3; /* CW minimum */ + data->buf[8] |= (7 << 4); /* CW maximum */ + data->buf[9] |= 11; /* retry limitation */ + + m_copydata(m0, 0, m0->m_pkthdr.len, (uint8_t *)&data->buf[12]); + data->ni = ni; + data->m = m0; + + usbd_setup_xfer(data->xfer, + (prior == URTW_PRIORITY_LOW) ? sc->sc_txpipe_low : + sc->sc_txpipe_normal, data, data->buf, xferlen, + USBD_FORCE_SHORT_XFER | USBD_NO_COPY, URTW_DATA_TIMEOUT, + (prior == URTW_PRIORITY_LOW) ? urtw_txeof_low : urtw_txeof_normal); + error = usbd_transfer(data->xfer); + if (error != USBD_IN_PROGRESS && error != USBD_NORMAL_COMPLETION) { + device_printf(sc->sc_dev, "could not send frame: %s\n", + usbd_errstr(error)); + return EIO; + } + + error = urtw_led_ctl(sc, URTW_LED_CTL_TX); + if (error != 0) + device_printf(sc->sc_dev, "could not control LED (%d)\n", error); + + if (prior == URTW_PRIORITY_LOW) + sc->sc_tx_low_queued++; + else + sc->sc_tx_normal_queued++; + + return (0); +} + +static int +urtw_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, + const struct ieee80211_bpf_params *params) +{ + struct ieee80211com *ic = ni->ni_ic; + struct ifnet *ifp = ic->ic_ifp; + struct urtw_softc *sc = ifp->if_softc; + + /* prevent management frames from being sent if we're not ready */ + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + m_freem(m); + ieee80211_free_node(ni); + return ENETDOWN; + } + URTW_LOCK(sc); + if (sc->sc_tx_low_queued >= URTW_TX_DATA_LIST_COUNT || + sc->sc_tx_normal_queued >= URTW_TX_DATA_LIST_COUNT) { + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + m_freem(m); + ieee80211_free_node(ni); + URTW_UNLOCK(sc); + return (ENOBUFS); /* XXX */ + } + + ifp->if_opackets++; + if (urtw_tx_start(sc, ni, m, URTW_PRIORITY_LOW) != 0) { + ieee80211_free_node(ni); + ifp->if_oerrors++; + URTW_UNLOCK(sc); + return (EIO); + } + + sc->sc_txtimer = 5; + URTW_UNLOCK(sc); + return (0); +} + +static void +urtw_scan_start(struct ieee80211com *ic) +{ + + /* XXX do nothing? */ +} + +static void +urtw_scan_end(struct ieee80211com *ic) +{ + + /* XXX do nothing? */ +} + +static void +urtw_set_channel(struct ieee80211com *ic) +{ + struct urtw_softc *sc = ic->ic_ifp->if_softc; + struct ifnet *ifp = sc->sc_ifp; + + /* + * if the user set a channel explicitly using ifconfig(8) this function + * can be called earlier than we're expected that in some cases the + * initialization would be failed if setting a channel is called before + * the init have done. + */ + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) + return; + + sc->sc_ctxarg = URTW_SET_CHANNEL; + usb_add_task(sc->sc_udev, &sc->sc_ctxtask, USB_TASKQ_DRIVER); +} + +static void +urtw_update_mcast(struct ifnet *ifp) +{ + + /* XXX do nothing? */ +} + +static usbd_status +urtw_8225_usb_init(struct urtw_softc *sc) +{ + uint8_t data; + usbd_status error; + + urtw_write8_m(sc, URTW_RF_PINS_SELECT + 1, 0); + urtw_write8_m(sc, URTW_GPIO, 0); + error = urtw_read8e(sc, 0x53, &data); + if (error) + goto fail; + error = urtw_write8e(sc, 0x53, data | (1 << 7)); + if (error) + goto fail; + urtw_write8_m(sc, URTW_RF_PINS_SELECT + 1, 4); + urtw_write8_m(sc, URTW_GPIO, 0x20); + urtw_write8_m(sc, URTW_GP_ENABLE, 0); + + urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, 0x80); + urtw_write16_m(sc, URTW_RF_PINS_SELECT, 0x80); + urtw_write16_m(sc, URTW_RF_PINS_ENABLE, 0x80); + + usbd_delay_ms(sc->sc_udev, 500); +fail: + return (error); +} + +static usbd_status +urtw_8185_rf_pins_enable(struct urtw_softc *sc) +{ + usbd_status error = 0; + + urtw_write16_m(sc, URTW_RF_PINS_ENABLE, 0x1ff7); +fail: + return (error); +} + +static usbd_status +urtw_8187_write_phy(struct urtw_softc *sc, uint8_t addr, uint32_t data) +{ + uint32_t phyw; + usbd_status error; + + phyw = ((data << 8) | (addr | 0x80)); + urtw_write8_m(sc, URTW_PHY_MAGIC4, ((phyw & 0xff000000) >> 24)); + urtw_write8_m(sc, URTW_PHY_MAGIC3, ((phyw & 0x00ff0000) >> 16)); + urtw_write8_m(sc, URTW_PHY_MAGIC2, ((phyw & 0x0000ff00) >> 8)); + urtw_write8_m(sc, URTW_PHY_MAGIC1, ((phyw & 0x000000ff))); + usbd_delay_ms(sc->sc_udev, 1); +fail: + return (error); +} + +static usbd_status +urtw_8187_write_phy_ofdm_c(struct urtw_softc *sc, uint8_t addr, uint32_t data) +{ + + data = data & 0xff; + return urtw_8187_write_phy(sc, addr, data); +} + +static usbd_status +urtw_8187_write_phy_cck_c(struct urtw_softc *sc, uint8_t addr, uint32_t data) +{ + + data = data & 0xff; + return urtw_8187_write_phy(sc, addr, data | 0x10000); +} + +static usbd_status +urtw_8225_setgain(struct urtw_softc *sc, int16_t gain) +{ + usbd_status error; + + urtw_8187_write_phy_ofdm(sc, 0x0d, urtw_8225_gain[gain * 4]); + urtw_8187_write_phy_ofdm(sc, 0x1b, urtw_8225_gain[gain * 4 + 2]); + urtw_8187_write_phy_ofdm(sc, 0x1d, urtw_8225_gain[gain * 4 + 3]); + urtw_8187_write_phy_ofdm(sc, 0x23, urtw_8225_gain[gain * 4 + 1]); +fail: + return (error); +} + +static usbd_status +urtw_8225_set_txpwrlvl(struct urtw_softc *sc, int chan) +{ + int i, idx, set; + uint8_t *cck_pwltable; + uint8_t cck_pwrlvl_max, ofdm_pwrlvl_min, ofdm_pwrlvl_max; + uint8_t cck_pwrlvl = sc->sc_txpwr_cck[chan] & 0xff; + uint8_t ofdm_pwrlvl = sc->sc_txpwr_ofdm[chan] & 0xff; + usbd_status error; + + cck_pwrlvl_max = 11; + ofdm_pwrlvl_max = 25; /* 12 -> 25 */ + ofdm_pwrlvl_min = 10; + + /* CCK power setting */ + cck_pwrlvl = (cck_pwrlvl > cck_pwrlvl_max) ? cck_pwrlvl_max : cck_pwrlvl; + idx = cck_pwrlvl % 6; + set = cck_pwrlvl / 6; + cck_pwltable = (chan == 14) ? urtw_8225_txpwr_cck_ch14 : + urtw_8225_txpwr_cck; + + urtw_write8_m(sc, URTW_TX_GAIN_CCK, + urtw_8225_tx_gain_cck_ofdm[set] >> 1); + for (i = 0; i < 8; i++) { + urtw_8187_write_phy_cck(sc, 0x44 + i, + cck_pwltable[idx * 8 + i]); + } + usbd_delay_ms(sc->sc_udev, 1); + + /* OFDM power setting */ + ofdm_pwrlvl = (ofdm_pwrlvl > (ofdm_pwrlvl_max - ofdm_pwrlvl_min)) ? + ofdm_pwrlvl_max : ofdm_pwrlvl + ofdm_pwrlvl_min; + ofdm_pwrlvl = (ofdm_pwrlvl > 35) ? 35 : ofdm_pwrlvl; + + idx = ofdm_pwrlvl % 6; + set = ofdm_pwrlvl / 6; + + error = urtw_8185_set_anaparam2(sc, URTW_8225_ANAPARAM2_ON); + if (error) + goto fail; + urtw_8187_write_phy_ofdm(sc, 2, 0x42); + urtw_8187_write_phy_ofdm(sc, 6, 0); + urtw_8187_write_phy_ofdm(sc, 8, 0); + + urtw_write8_m(sc, URTW_TX_GAIN_OFDM, + urtw_8225_tx_gain_cck_ofdm[set] >> 1); + urtw_8187_write_phy_ofdm(sc, 0x5, urtw_8225_txpwr_ofdm[idx]); + urtw_8187_write_phy_ofdm(sc, 0x7, urtw_8225_txpwr_ofdm[idx]); + usbd_delay_ms(sc->sc_udev, 1); +fail: + return (error); +} + +static usbd_status +urtw_8185_tx_antenna(struct urtw_softc *sc, uint8_t ant) +{ + usbd_status error; + + urtw_write8_m(sc, URTW_TX_ANTENNA, ant); + usbd_delay_ms(sc->sc_udev, 1); +fail: + return (error); +} + +static usbd_status +urtw_8225_rf_init(struct urtw_softc *sc) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + int i; + uint16_t data; + usbd_status error; + + error = urtw_8180_set_anaparam(sc, URTW_8225_ANAPARAM_ON); + if (error) + goto fail; + + error = urtw_8225_usb_init(sc); + if (error) + goto fail; + + urtw_write32_m(sc, URTW_RF_TIMING, 0x000a8008); + urtw_read16_m(sc, URTW_BRSR, &data); /* XXX ??? */ + urtw_write16_m(sc, URTW_BRSR, 0xffff); + urtw_write32_m(sc, URTW_RF_PARA, 0x100044); + + error = urtw_set_mode(sc, URTW_EPROM_CMD_CONFIG); + if (error) + goto fail; + urtw_write8_m(sc, URTW_CONFIG3, 0x44); + error = urtw_set_mode(sc, URTW_EPROM_CMD_NORMAL); + if (error) + goto fail; + + error = urtw_8185_rf_pins_enable(sc); + if (error) + goto fail; + usbd_delay_ms(sc->sc_udev, 1000); + + for (i = 0; i < N(urtw_8225_rf_part1); i++) { + urtw_8225_write(sc, urtw_8225_rf_part1[i].reg, + urtw_8225_rf_part1[i].val); + usbd_delay_ms(sc->sc_udev, 1); + } + usbd_delay_ms(sc->sc_udev, 100); + urtw_8225_write(sc, + URTW_8225_ADDR_2_MAGIC, URTW_8225_ADDR_2_DATA_MAGIC1); + usbd_delay_ms(sc->sc_udev, 200); + urtw_8225_write(sc, + URTW_8225_ADDR_2_MAGIC, URTW_8225_ADDR_2_DATA_MAGIC2); + usbd_delay_ms(sc->sc_udev, 200); + urtw_8225_write(sc, + URTW_8225_ADDR_0_MAGIC, URTW_8225_ADDR_0_DATA_MAGIC3); + + for (i = 0; i < 95; i++) { + urtw_8225_write(sc, URTW_8225_ADDR_1_MAGIC, (uint8_t)(i + 1)); + urtw_8225_write(sc, URTW_8225_ADDR_2_MAGIC, urtw_8225_rxgain[i]); + } + + urtw_8225_write(sc, + URTW_8225_ADDR_0_MAGIC, URTW_8225_ADDR_0_DATA_MAGIC4); + urtw_8225_write(sc, + URTW_8225_ADDR_0_MAGIC, URTW_8225_ADDR_0_DATA_MAGIC5); + + for (i = 0; i < 128; i++) { + urtw_8187_write_phy_ofdm(sc, 0xb, urtw_8225_agc[i]); + usbd_delay_ms(sc->sc_udev, 1); + urtw_8187_write_phy_ofdm(sc, 0xa, (uint8_t)i + 0x80); + usbd_delay_ms(sc->sc_udev, 1); + } + + for (i = 0; i < N(urtw_8225_rf_part2); i++) { + urtw_8187_write_phy_ofdm(sc, urtw_8225_rf_part2[i].reg, + urtw_8225_rf_part2[i].val); + usbd_delay_ms(sc->sc_udev, 1); + } + + error = urtw_8225_setgain(sc, 4); + if (error) + goto fail; + + for (i = 0; i < N(urtw_8225_rf_part3); i++) { + urtw_8187_write_phy_cck(sc, urtw_8225_rf_part3[i].reg, + urtw_8225_rf_part3[i].val); + usbd_delay_ms(sc->sc_udev, 1); + } + + urtw_write8_m(sc, URTW_ADDR_MAGIC4, 0x0d); + + error = urtw_8225_set_txpwrlvl(sc, 1); + if (error) + goto fail; + + urtw_8187_write_phy_cck(sc, 0x10, 0x9b); + usbd_delay_ms(sc->sc_udev, 1); + urtw_8187_write_phy_ofdm(sc, 0x26, 0x90); + usbd_delay_ms(sc->sc_udev, 1); + + /* TX ant A, 0x0 for B */ + error = urtw_8185_tx_antenna(sc, 0x3); + if (error) + goto fail; + urtw_write32_m(sc, URTW_ADDR_MAGIC5, 0x3dc00002); + + error = urtw_8225_rf_set_chan(sc, 1); +fail: + return (error); +#undef N +} + +static usbd_status +urtw_8225_rf_set_chan(struct urtw_softc *sc, int chan) +{ + struct ieee80211com *ic = sc->sc_ifp->if_l2com; + struct ieee80211_channel *c = ic->ic_curchan; + usbd_status error; + + error = urtw_8225_set_txpwrlvl(sc, chan); + if (error) + goto fail; + urtw_8225_write(sc, URTW_8225_ADDR_7_MAGIC, urtw_8225_channel[chan]); + usbd_delay_ms(sc->sc_udev, 10); + + urtw_write8_m(sc, URTW_SIFS, 0x22); + + if (sc->sc_state == IEEE80211_S_ASSOC && + ic->ic_flags & IEEE80211_F_SHSLOT) + urtw_write8_m(sc, URTW_SLOT, 0x9); + else + urtw_write8_m(sc, URTW_SLOT, 0x14); + + if (IEEE80211_IS_CHAN_G(c)) { + /* for G */ + urtw_write8_m(sc, URTW_DIFS, 0x14); + urtw_write8_m(sc, URTW_EIFS, 0x5b - 0x14); + urtw_write8_m(sc, URTW_CW_VAL, 0x73); + } else { + /* for B */ + urtw_write8_m(sc, URTW_DIFS, 0x24); + urtw_write8_m(sc, URTW_EIFS, 0x5b - 0x24); + urtw_write8_m(sc, URTW_CW_VAL, 0xa5); + } + +fail: + return (error); +} + +static usbd_status +urtw_8225_rf_set_sens(struct urtw_softc *sc, int sens) +{ + usbd_status error; + + if (sens < 0 || sens > 6) + return -1; + + if (sens > 4) + urtw_8225_write(sc, + URTW_8225_ADDR_C_MAGIC, URTW_8225_ADDR_C_DATA_MAGIC1); + else + urtw_8225_write(sc, + URTW_8225_ADDR_C_MAGIC, URTW_8225_ADDR_C_DATA_MAGIC2); + + sens = 6 - sens; + error = urtw_8225_setgain(sc, sens); + if (error) + goto fail; + + urtw_8187_write_phy_cck(sc, 0x41, urtw_8225_threshold[sens]); + +fail: + return (error); +} + +static void +urtw_stop(struct ifnet *ifp, int disable) +{ + struct urtw_softc *sc = ifp->if_softc; + + ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); + + callout_stop(&sc->sc_led_ch); + callout_stop(&sc->sc_watchdog_ch); + + if (sc->sc_rxpipe != NULL) + usbd_abort_pipe(sc->sc_rxpipe); + if (sc->sc_txpipe_low != NULL) + usbd_abort_pipe(sc->sc_txpipe_low); + if (sc->sc_txpipe_normal != NULL) + usbd_abort_pipe(sc->sc_txpipe_normal); +} + +static int +urtw_isbmode(uint16_t rate) +{ + + rate = urtw_rtl2rate(rate); + + return ((rate <= 22 && rate != 12 && rate != 18) || + rate == 44) ? (1) : (0); +} + +static void +urtw_rxeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + int actlen, flen, len, nf, rssi; + struct ieee80211_frame *wh; + struct ieee80211_node *ni; + struct mbuf *m, *mnew; + struct urtw_data *data = priv; + struct urtw_softc *sc = data->sc; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + uint8_t *desc, quality, rate; + usbd_status error; + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) + return; + + if (status == USBD_STALLED) + usbd_clear_endpoint_stall_async(sc->sc_rxpipe); + ifp->if_ierrors++; + goto skip; + } + + usbd_get_xfer_status(xfer, NULL, NULL, &actlen, NULL); + if (actlen < URTW_MIN_RXBUFSZ) { + ifp->if_ierrors++; + goto skip; + } + + /* 4 dword and 4 byte CRC */ + len = actlen - (4 * 4); + desc = data->buf + len; + flen = ((desc[1] & 0x0f) << 8) + (desc[0] & 0xff); + if (flen > actlen) { + ifp->if_ierrors++; + goto skip; + } + + rate = (desc[2] & 0xf0) >> 4; + quality = desc[4] & 0xff; + /* XXX correct? */ + rssi = (desc[6] & 0xfe) >> 1; + if (!urtw_isbmode(rate)) { + rssi = (rssi > 90) ? 90 : ((rssi < 25) ? 25 : rssi); + rssi = ((90 - rssi) * 100) / 65; + } else { + rssi = (rssi > 90) ? 95 : ((rssi < 30) ? 30 : rssi); + rssi = ((95 - rssi) * 100) / 65; + } + + mnew = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + if (mnew == NULL) { + ifp->if_ierrors++; + goto skip; + } + + m = data->m; + data->m = mnew; + data->buf = mtod(mnew, uint8_t *); + + /* finalize mbuf */ + m->m_pkthdr.rcvif = ifp; + m->m_pkthdr.len = m->m_len = flen - 4; + + if (bpf_peers_present(ifp->if_bpf)) { + struct urtw_rx_radiotap_header *tap = &sc->sc_rxtap; + + /* XXX Are variables correct? */ + tap->wr_chan_freq = htole16(ic->ic_curchan->ic_freq); + tap->wr_chan_flags = htole16(ic->ic_curchan->ic_flags); + tap->wr_dbm_antsignal = (int8_t)rssi; + + bpf_mtap2(ifp->if_bpf, tap, sc->sc_rxtap_len, m); + } + + wh = mtod(m, struct ieee80211_frame *); + if ((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) == IEEE80211_FC0_TYPE_DATA) + sc->sc_currate = (rate > 0) ? rate : sc->sc_currate; + ni = ieee80211_find_rxnode(ic, (struct ieee80211_frame_min *)wh); + /* XXX correct? */ + nf = (quality > 64) ? 0 : ((64 - quality) * 100) / 64; + /* send the frame to the 802.11 layer */ + if (ni != NULL) { + (void) ieee80211_input(ni, m, rssi, -nf, 0); + /* node is no longer needed */ + ieee80211_free_node(ni); + } else + (void) ieee80211_input_all(ic, m, rssi, -nf, 0); + +skip: /* setup a new transfer */ + usbd_setup_xfer(xfer, sc->sc_rxpipe, data, data->buf, MCLBYTES, + USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT, urtw_rxeof); + error = usbd_transfer(xfer); + if (error != USBD_IN_PROGRESS && error != 0) + device_printf(sc->sc_dev, "could not queue Rx transfer\n"); +} + +static int +urtw_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) +{ + struct urtw_vap *rvp = URTW_VAP(vap); + struct ieee80211com *ic = vap->iv_ic; + struct urtw_softc *sc = ic->ic_ifp->if_softc; + + DPRINTF(sc, URTW_DEBUG_STATE, "%s: %s -> %s\n", __func__, + ieee80211_state_name[vap->iv_state], + ieee80211_state_name[nstate]); + + /* do it in a process context */ + sc->sc_state = nstate; + sc->sc_arg = arg; + + if (nstate == IEEE80211_S_INIT) { + rvp->newstate(vap, nstate, arg); + return (0); + } else { + usb_add_task(sc->sc_udev, &sc->sc_task, USB_TASKQ_DRIVER); + return (EINPROGRESS); + } +} + +static usbd_status +urtw_8225v2_setgain(struct urtw_softc *sc, int16_t gain) +{ + uint8_t *gainp; + usbd_status error; + + /* XXX for A? */ + gainp = urtw_8225v2_gain_bg; + urtw_8187_write_phy_ofdm(sc, 0x0d, gainp[gain * 3]); + usbd_delay_ms(sc->sc_udev, 1); + urtw_8187_write_phy_ofdm(sc, 0x1b, gainp[gain * 3 + 1]); + usbd_delay_ms(sc->sc_udev, 1); + urtw_8187_write_phy_ofdm(sc, 0x1d, gainp[gain * 3 + 2]); + usbd_delay_ms(sc->sc_udev, 1); + urtw_8187_write_phy_ofdm(sc, 0x21, 0x17); + usbd_delay_ms(sc->sc_udev, 1); +fail: + return (error); +} + +static usbd_status +urtw_8225v2_set_txpwrlvl(struct urtw_softc *sc, int chan) +{ + int i; + uint8_t *cck_pwrtable; + uint8_t cck_pwrlvl_max = 15, ofdm_pwrlvl_max = 25, ofdm_pwrlvl_min = 10; + uint8_t cck_pwrlvl = sc->sc_txpwr_cck[chan] & 0xff; + uint8_t ofdm_pwrlvl = sc->sc_txpwr_ofdm[chan] & 0xff; + usbd_status error; + + /* CCK power setting */ + cck_pwrlvl = (cck_pwrlvl > cck_pwrlvl_max) ? cck_pwrlvl_max : cck_pwrlvl; + cck_pwrlvl += sc->sc_txpwr_cck_base; + cck_pwrlvl = (cck_pwrlvl > 35) ? 35 : cck_pwrlvl; + cck_pwrtable = (chan == 14) ? urtw_8225v2_txpwr_cck_ch14 : + urtw_8225v2_txpwr_cck; + + for (i = 0; i < 8; i++) + urtw_8187_write_phy_cck(sc, 0x44 + i, cck_pwrtable[i]); + + urtw_write8_m(sc, URTW_TX_GAIN_CCK, + urtw_8225v2_tx_gain_cck_ofdm[cck_pwrlvl]); + usbd_delay_ms(sc->sc_udev, 1); + + /* OFDM power setting */ + ofdm_pwrlvl = (ofdm_pwrlvl > (ofdm_pwrlvl_max - ofdm_pwrlvl_min)) ? + ofdm_pwrlvl_max : ofdm_pwrlvl + ofdm_pwrlvl_min; + ofdm_pwrlvl += sc->sc_txpwr_ofdm_base; + ofdm_pwrlvl = (ofdm_pwrlvl > 35) ? 35 : ofdm_pwrlvl; + + error = urtw_8185_set_anaparam2(sc, URTW_8225_ANAPARAM2_ON); + if (error) + goto fail; + + urtw_8187_write_phy_ofdm(sc, 2, 0x42); + urtw_8187_write_phy_ofdm(sc, 5, 0x0); + urtw_8187_write_phy_ofdm(sc, 6, 0x40); + urtw_8187_write_phy_ofdm(sc, 7, 0x0); + urtw_8187_write_phy_ofdm(sc, 8, 0x40); + + urtw_write8_m(sc, URTW_TX_GAIN_OFDM, + urtw_8225v2_tx_gain_cck_ofdm[ofdm_pwrlvl]); + usbd_delay_ms(sc->sc_udev, 1); +fail: + return (error); +} + +static usbd_status +urtw_8225v2_rf_init(struct urtw_softc *sc) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + int i; + uint16_t data; + uint32_t data32; + usbd_status error; + + error = urtw_8180_set_anaparam(sc, URTW_8225_ANAPARAM_ON); + if (error) + goto fail; + + error = urtw_8225_usb_init(sc); + if (error) + goto fail; + + urtw_write32_m(sc, URTW_RF_TIMING, 0x000a8008); + urtw_read16_m(sc, URTW_BRSR, &data); /* XXX ??? */ + urtw_write16_m(sc, URTW_BRSR, 0xffff); + urtw_write32_m(sc, URTW_RF_PARA, 0x100044); + + error = urtw_set_mode(sc, URTW_EPROM_CMD_CONFIG); + if (error) + goto fail; + urtw_write8_m(sc, URTW_CONFIG3, 0x44); + error = urtw_set_mode(sc, URTW_EPROM_CMD_NORMAL); + if (error) + goto fail; + + error = urtw_8185_rf_pins_enable(sc); + if (error) + goto fail; + + usbd_delay_ms(sc->sc_udev, 500); + + for (i = 0; i < N(urtw_8225v2_rf_part1); i++) { + urtw_8225_write(sc, urtw_8225v2_rf_part1[i].reg, + urtw_8225v2_rf_part1[i].val); + } + usbd_delay_ms(sc->sc_udev, 50); + + urtw_8225_write(sc, + URTW_8225_ADDR_0_MAGIC, URTW_8225_ADDR_0_DATA_MAGIC1); + + for (i = 0; i < 95; i++) { + urtw_8225_write(sc, URTW_8225_ADDR_1_MAGIC, (uint8_t)(i + 1)); + urtw_8225_write(sc, URTW_8225_ADDR_2_MAGIC, + urtw_8225v2_rxgain[i]); + } + + urtw_8225_write(sc, + URTW_8225_ADDR_3_MAGIC, URTW_8225_ADDR_3_DATA_MAGIC1); + urtw_8225_write(sc, + URTW_8225_ADDR_5_MAGIC, URTW_8225_ADDR_5_DATA_MAGIC1); + urtw_8225_write(sc, + URTW_8225_ADDR_0_MAGIC, URTW_8225_ADDR_0_DATA_MAGIC2); + urtw_8225_write(sc, + URTW_8225_ADDR_2_MAGIC, URTW_8225_ADDR_2_DATA_MAGIC1); + usbd_delay_ms(sc->sc_udev, 100); + urtw_8225_write(sc, + URTW_8225_ADDR_2_MAGIC, URTW_8225_ADDR_2_DATA_MAGIC2); + usbd_delay_ms(sc->sc_udev, 100); + + error = urtw_8225_read(sc, URTW_8225_ADDR_6_MAGIC, &data32); + if (error != 0) + goto fail; + if (data32 != URTW_8225_ADDR_6_DATA_MAGIC1) + device_printf(sc->sc_dev, "expect 0xe6!! (0x%x)\n", data32); + if (!(data32 & URTW_8225_ADDR_6_DATA_MAGIC2)) { + urtw_8225_write(sc, + URTW_8225_ADDR_2_MAGIC, URTW_8225_ADDR_2_DATA_MAGIC1); + usbd_delay_ms(sc->sc_udev, 100); + urtw_8225_write(sc, + URTW_8225_ADDR_2_MAGIC, URTW_8225_ADDR_2_DATA_MAGIC2); + usbd_delay_ms(sc->sc_udev, 50); + error = urtw_8225_read(sc, URTW_8225_ADDR_6_MAGIC, &data32); + if (error != 0) + goto fail; + if (!(data32 & URTW_8225_ADDR_6_DATA_MAGIC2)) + device_printf(sc->sc_dev, "RF calibration failed\n"); + } + usbd_delay_ms(sc->sc_udev, 100); + + urtw_8225_write(sc, + URTW_8225_ADDR_0_MAGIC, URTW_8225_ADDR_0_DATA_MAGIC6); + for (i = 0; i < 128; i++) { + urtw_8187_write_phy_ofdm(sc, 0xb, urtw_8225_agc[i]); + urtw_8187_write_phy_ofdm(sc, 0xa, (uint8_t)i + 0x80); + } + + for (i = 0; i < N(urtw_8225v2_rf_part2); i++) { + urtw_8187_write_phy_ofdm(sc, urtw_8225v2_rf_part2[i].reg, + urtw_8225v2_rf_part2[i].val); + } + + error = urtw_8225v2_setgain(sc, 4); + if (error) + goto fail; + + for (i = 0; i < N(urtw_8225v2_rf_part3); i++) { + urtw_8187_write_phy_cck(sc, urtw_8225v2_rf_part3[i].reg, + urtw_8225v2_rf_part3[i].val); + } + + urtw_write8_m(sc, URTW_ADDR_MAGIC4, 0x0d); + + error = urtw_8225v2_set_txpwrlvl(sc, 1); + if (error) + goto fail; + + urtw_8187_write_phy_cck(sc, 0x10, 0x9b); + urtw_8187_write_phy_ofdm(sc, 0x26, 0x90); + + /* TX ant A, 0x0 for B */ + error = urtw_8185_tx_antenna(sc, 0x3); + if (error) + goto fail; + urtw_write32_m(sc, URTW_ADDR_MAGIC5, 0x3dc00002); + + error = urtw_8225_rf_set_chan(sc, 1); +fail: + return (error); +#undef N +} + +static usbd_status +urtw_8225v2_rf_set_chan(struct urtw_softc *sc, int chan) +{ + struct ieee80211com *ic = sc->sc_ifp->if_l2com; + struct ieee80211_channel *c = ic->ic_curchan; + usbd_status error; + + error = urtw_8225v2_set_txpwrlvl(sc, chan); + if (error) + goto fail; + + urtw_8225_write(sc, URTW_8225_ADDR_7_MAGIC, urtw_8225_channel[chan]); + usbd_delay_ms(sc->sc_udev, 10); + + urtw_write8_m(sc, URTW_SIFS, 0x22); + + if(sc->sc_state == IEEE80211_S_ASSOC && + ic->ic_flags & IEEE80211_F_SHSLOT) + urtw_write8_m(sc, URTW_SLOT, 0x9); + else + urtw_write8_m(sc, URTW_SLOT, 0x14); + + if (IEEE80211_IS_CHAN_G(c)) { + /* for G */ + urtw_write8_m(sc, URTW_DIFS, 0x14); + urtw_write8_m(sc, URTW_EIFS, 0x5b - 0x14); + urtw_write8_m(sc, URTW_CW_VAL, 0x73); + } else { + /* for B */ + urtw_write8_m(sc, URTW_DIFS, 0x24); + urtw_write8_m(sc, URTW_EIFS, 0x5b - 0x24); + urtw_write8_m(sc, URTW_CW_VAL, 0xa5); + } + +fail: + return (error); +} + +static void +urtw_ctxtask(void *arg) +{ + struct urtw_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + uint32_t data; + usbd_status error; + + switch (sc->sc_ctxarg) { + case URTW_SET_CHANNEL: + /* + * during changing th channel we need to temporarily be disable + * TX. + */ + urtw_read32_m(sc, URTW_TX_CONF, &data); + data &= ~URTW_TX_LOOPBACK_MASK; + urtw_write32_m(sc, URTW_TX_CONF, data | URTW_TX_LOOPBACK_MAC); + error = sc->sc_rf_set_chan(sc, + ieee80211_chan2ieee(ic, ic->ic_curchan)); + if (error != 0) + goto fail; + usbd_delay_ms(sc->sc_udev, 10); + urtw_write32_m(sc, URTW_TX_CONF, data | URTW_TX_LOOPBACK_NONE); + break; + default: + panic("unknown argument.\n"); + } + +fail: + if (error != 0) + device_printf(sc->sc_dev, "could not change the channel\n"); + return; +} + +static void +urtw_task(void *arg) +{ + struct urtw_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); + struct ieee80211_node *ni = vap->iv_bss; + struct urtw_vap *uvp = URTW_VAP(vap); + usbd_status error = 0; + + switch (sc->sc_state) { + case IEEE80211_S_RUN: + /* setting bssid. */ + urtw_write32_m(sc, URTW_BSSID, ((uint32_t *)ni->ni_bssid)[0]); + urtw_write16_m(sc, URTW_BSSID + 4, + ((uint16_t *)ni->ni_bssid)[2]); + urtw_update_msr(sc); + /* XXX maybe the below would be incorrect. */ + urtw_write16_m(sc, URTW_ATIM_WND, 2); + urtw_write16_m(sc, URTW_ATIM_TR_ITV, 100); + urtw_write16_m(sc, URTW_BEACON_INTERVAL, 0x64); + urtw_write16_m(sc, URTW_BEACON_INTERVAL_TIME, 100); + error = urtw_led_ctl(sc, URTW_LED_CTL_LINK); + if (error != 0) + device_printf(sc->sc_dev, + "could not control LED (%d)\n", error); + break; + default: + break; + } + +fail: + if (error != 0) + printf("error duing processing RUN state."); + + IEEE80211_LOCK(ic); + uvp->newstate(vap, sc->sc_state, sc->sc_arg); + if (vap->iv_newstate_cb != NULL) + vap->iv_newstate_cb(vap, sc->sc_state, sc->sc_arg); + IEEE80211_UNLOCK(ic); +} + +static void +urtw_watchdog(void *arg) +{ + struct urtw_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + + if (sc->sc_txtimer > 0) { + if (--sc->sc_txtimer == 0) { + device_printf(sc->sc_dev, "device timeout\n"); + ifp->if_oerrors++; + return; + } + callout_reset(&sc->sc_watchdog_ch, hz, urtw_watchdog, sc); + } +} + +static device_method_t urtw_methods[] = { + DEVMETHOD(device_probe, urtw_match), + DEVMETHOD(device_attach, urtw_attach), + DEVMETHOD(device_detach, urtw_detach), + { 0, 0 } +}; +static driver_t urtw_driver = { + "urtw", + urtw_methods, + sizeof(struct urtw_softc) +}; +static devclass_t urtw_devclass; + +DRIVER_MODULE(urtw, uhub, urtw_driver, urtw_devclass, usbd_driver_load, 0); +MODULE_DEPEND(urtw, wlan, 1, 1, 1); +MODULE_DEPEND(urtw, usb, 1, 1, 1); diff --git a/sys/legacy/dev/usb/if_urtwreg.h b/sys/legacy/dev/usb/if_urtwreg.h new file mode 100644 index 0000000..7a9baa3 --- /dev/null +++ b/sys/legacy/dev/usb/if_urtwreg.h @@ -0,0 +1,256 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2008 Weongyo Jeong <weongyo@FreeBSD.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define URTW_CONFIG_NO 1 +#define URTW_IFACE_INDEX 0 + +/* for 8187 */ +#define URTW_MAC0 0x0000 /* 1 byte */ +#define URTW_MAC1 0x0001 /* 1 byte */ +#define URTW_MAC2 0x0002 /* 1 byte */ +#define URTW_MAC3 0x0003 /* 1 byte */ +#define URTW_MAC4 0x0004 /* 1 byte */ +#define URTW_MAC5 0x0005 /* 1 byte */ +#define URTW_BRSR 0x002c /* 2 byte */ +#define URTW_BRSR_MBR_8185 (0x0fff) +#define URTW_BSSID 0x002e /* 6 byte */ +#define URTW_RESP_RATE 0x0034 /* 1 byte */ +#define URTW_RESP_MAX_RATE_SHIFT (4) +#define URTW_RESP_MIN_RATE_SHIFT (0) +#define URTW_EIFS 0x0035 /* 1 byte */ +#define URTW_INTR_MASK 0x003c /* 2 byte */ +#define URTW_CMD 0x0037 /* 1 byte */ +#define URTW_CMD_TX_ENABLE (0x4) +#define URTW_CMD_RX_ENABLE (0x8) +#define URTW_CMD_RST (0x10) +#define URTW_TX_CONF 0x0040 /* 4 byte */ +#define URTW_TX_LOOPBACK_SHIFT (17) +#define URTW_TX_LOOPBACK_NONE (0 << URTW_TX_LOOPBACK_SHIFT) +#define URTW_TX_LOOPBACK_MAC (1 << URTW_TX_LOOPBACK_SHIFT) +#define URTW_TX_LOOPBACK_BASEBAND (2 << URTW_TX_LOOPBACK_SHIFT) +#define URTW_TX_LOOPBACK_CONTINUE (3 << URTW_TX_LOOPBACK_SHIFT) +#define URTW_TX_LOOPBACK_MASK (0x60000) +#define URTW_TX_DPRETRY_MASK (0xff00) +#define URTW_TX_RTSRETRY_MASK (0xff) +#define URTW_TX_DPRETRY_SHIFT (0) +#define URTW_TX_RTSRETRY_SHIFT (8) +#define URTW_TX_NOCRC (0x10000) +#define URTW_TX_MXDMA_MASK (0xe00000) +#define URTW_TX_MXDMA_1024 (6 << URTW_TX_MXDMA_SHIFT) +#define URTW_TX_MXDMA_2048 (7 << URTW_TX_MXDMA_SHIFT) +#define URTW_TX_MXDMA_SHIFT (21) +#define URTW_TX_CWMIN (1 << 31) +#define URTW_TX_DISCW (1 << 20) +#define URTW_TX_SWPLCPLEN (1 << 24) +#define URTW_TX_NOICV (0x80000) +#define URTW_RX 0x0044 /* 4 byte */ +#define URTW_RX_9356SEL (1 << 6) +#define URTW_RX_FILTER_MASK \ + (URTW_RX_FILTER_ALLMAC | URTW_RX_FILTER_NICMAC | URTW_RX_FILTER_MCAST | \ + URTW_RX_FILTER_BCAST | URTW_RX_FILTER_CRCERR | URTW_RX_FILTER_ICVERR | \ + URTW_RX_FILTER_DATA | URTW_RX_FILTER_CTL | URTW_RX_FILTER_MNG | \ + (1 << 21) | \ + URTW_RX_FILTER_PWR | URTW_RX_CHECK_BSSID) +#define URTW_RX_FILTER_ALLMAC (0x00000001) +#define URTW_RX_FILTER_NICMAC (0x00000002) +#define URTW_RX_FILTER_MCAST (0x00000004) +#define URTW_RX_FILTER_BCAST (0x00000008) +#define URTW_RX_FILTER_CRCERR (0x00000020) +#define URTW_RX_FILTER_ICVERR (0x00001000) +#define URTW_RX_FILTER_DATA (0x00040000) +#define URTW_RX_FILTER_CTL (0x00080000) +#define URTW_RX_FILTER_MNG (0x00100000) +#define URTW_RX_FILTER_PWR (0x00400000) +#define URTW_RX_CHECK_BSSID (0x00800000) +#define URTW_RX_FIFO_THRESHOLD_MASK ((1 << 13) | (1 << 14) | (1 << 15)) +#define URTW_RX_FIFO_THRESHOLD_SHIFT (13) +#define URTW_RX_FIFO_THRESHOLD_128 (3) +#define URTW_RX_FIFO_THRESHOLD_256 (4) +#define URTW_RX_FIFO_THRESHOLD_512 (5) +#define URTW_RX_FIFO_THRESHOLD_1024 (6) +#define URTW_RX_FIFO_THRESHOLD_NONE (7 << URTW_RX_FIFO_THRESHOLD_SHIFT) +#define URTW_RX_AUTORESETPHY (1 << URTW_RX_AUTORESETPHY_SHIFT) +#define URTW_RX_AUTORESETPHY_SHIFT (28) +#define URTW_MAX_RX_DMA_MASK ((1<<8) | (1<<9) | (1<<10)) +#define URTW_MAX_RX_DMA_2048 (7 << URTW_MAX_RX_DMA_SHIFT) +#define URTW_MAX_RX_DMA_1024 (6) +#define URTW_MAX_RX_DMA_SHIFT (10) +#define URTW_RCR_ONLYERLPKT (1 << 31) +#define URTW_INT_TIMEOUT 0x0048 /* 4 byte */ +#define URTW_EPROM_CMD 0x0050 /* 1 byte */ +#define URTW_EPROM_CMD_NORMAL (0x0) +#define URTW_EPROM_CMD_NORMAL_MODE \ + (URTW_EPROM_CMD_NORMAL << URTW_EPROM_CMD_SHIFT) +#define URTW_EPROM_CMD_LOAD (0x1) +#define URTW_EPROM_CMD_PROGRAM (0x2) +#define URTW_EPROM_CMD_PROGRAM_MODE \ + (URTW_EPROM_CMD_PROGRAM << URTW_EPROM_CMD_SHIFT) +#define URTW_EPROM_CMD_CONFIG (0x3) +#define URTW_EPROM_CMD_SHIFT (6) +#define URTW_EPROM_CMD_MASK ((1 << 7) | (1 << 6)) +#define URTW_EPROM_READBIT (0x1) +#define URTW_EPROM_WRITEBIT (0x2) +#define URTW_EPROM_CK (0x4) +#define URTW_EPROM_CS (0x8) +#define URTW_CONFIG2 0x0053 +#define URTW_ANAPARAM 0x0054 /* 4 byte */ +#define URTW_8225_ANAPARAM_ON (0xa0000a59) +#define URTW_MSR 0x0058 /* 1 byte */ +#define URTW_MSR_LINK_MASK ((1 << 2) | (1 << 3)) +#define URTW_MSR_LINK_SHIFT (2) +#define URTW_MSR_LINK_NONE (0 << URTW_MSR_LINK_SHIFT) +#define URTW_MSR_LINK_ADHOC (1 << URTW_MSR_LINK_SHIFT) +#define URTW_MSR_LINK_STA (2 << URTW_MSR_LINK_SHIFT) +#define URTW_MSR_LINK_HOSTAP (3 << URTW_MSR_LINK_SHIFT) +#define URTW_CONFIG3 0x0059 /* 1 byte */ +#define URTW_CONFIG3_ANAPARAM_WRITE (0x40) +#define URTW_CONFIG3_ANAPARAM_W_SHIFT (6) +#define URTW_ADDR_MAGIC4 0x005b /* 1 byte */ +#define URTW_PSR 0x005e /* 1 byte */ +#define URTW_ANAPARAM2 0x0060 /* 4 byte */ +#define URTW_8225_ANAPARAM2_ON (0x860c7312) +#define URTW_BEACON_INTERVAL 0x0070 /* 2 byte */ +#define URTW_ATIM_WND 0x0072 /* 2 byte */ +#define URTW_BEACON_INTERVAL_TIME 0x0074 /* 2 byte */ +#define URTW_ATIM_TR_ITV 0x0076 /* 2 byte */ +#define URTW_PHY_MAGIC1 0x007c /* 1 byte */ +#define URTW_PHY_MAGIC2 0x007d /* 1 byte */ +#define URTW_PHY_MAGIC3 0x007e /* 1 byte */ +#define URTW_PHY_MAGIC4 0x007f /* 1 byte */ +#define URTW_RF_PINS_OUTPUT 0x0080 /* 2 byte */ +#define URTW_RF_PINS_OUTPUT_MAGIC1 (0x3a0) +#define URTW_BB_HOST_BANG_CLK (1 << 1) +#define URTW_BB_HOST_BANG_EN (1 << 2) +#define URTW_BB_HOST_BANG_RW (1 << 3) +#define URTW_RF_PINS_ENABLE 0x0082 /* 2 byte */ +#define URTW_RF_PINS_SELECT 0x0084 /* 2 byte */ +#define URTW_ADDR_MAGIC1 0x0085 /* broken? */ +#define URTW_RF_PINS_INPUT 0x0086 /* 2 byte */ +#define URTW_RF_PINS_MAGIC1 (0xfff3) +#define URTW_RF_PINS_MAGIC2 (0xfff0) +#define URTW_RF_PINS_MAGIC3 (0x0007) +#define URTW_RF_PINS_MAGIC4 (0xf) +#define URTW_RF_PINS_MAGIC5 (0x0080) +#define URTW_RF_PARA 0x0088 /* 4 byte */ +#define URTW_RF_TIMING 0x008c /* 4 byte */ +#define URTW_GP_ENABLE 0x0090 /* 1 byte */ +#define URTW_GP_ENABLE_DATA_MAGIC1 (0x1) +#define URTW_GPIO 0x0091 /* 1 byte */ +#define URTW_GPIO_DATA_MAGIC1 (0x1) +#define URTW_ADDR_MAGIC5 0x0094 /* 4 byte */ +#define URTW_TX_AGC_CTL 0x009c /* 1 byte */ +#define URTW_TX_AGC_CTL_PERPACKET_GAIN (0x1) +#define URTW_TX_AGC_CTL_PERPACKET_ANTSEL (0x2) +#define URTW_TX_AGC_CTL_FEEDBACK_ANT (0x4) +#define URTW_TX_GAIN_CCK 0x009d /* 1 byte */ +#define URTW_TX_GAIN_OFDM 0x009e /* 1 byte */ +#define URTW_TX_ANTENNA 0x009f /* 1 byte */ +#define URTW_WPA_CONFIG 0x00b0 /* 1 byte */ +#define URTW_SIFS 0x00b4 /* 1 byte */ +#define URTW_DIFS 0x00b5 /* 1 byte */ +#define URTW_SLOT 0x00b6 /* 1 byte */ +#define URTW_CW_CONF 0x00bc /* 1 byte */ +#define URTW_CW_CONF_PERPACKET_RETRY (0x2) +#define URTW_CW_CONF_PERPACKET_CW (0x1) +#define URTW_CW_VAL 0x00bd /* 1 byte */ +#define URTW_RATE_FALLBACK 0x00be /* 1 byte */ +#define URTW_TALLY_SEL 0x00fc /* 1 byte */ +#define URTW_ADDR_MAGIC2 0x00fe /* 2 byte */ +#define URTW_ADDR_MAGIC3 0x00ff /* 1 byte */ + +/* for 8225 */ +#define URTW_8225_ADDR_0_MAGIC 0x0 +#define URTW_8225_ADDR_0_DATA_MAGIC1 (0x1b7) +#define URTW_8225_ADDR_0_DATA_MAGIC2 (0x0b7) +#define URTW_8225_ADDR_0_DATA_MAGIC3 (0x127) +#define URTW_8225_ADDR_0_DATA_MAGIC4 (0x027) +#define URTW_8225_ADDR_0_DATA_MAGIC5 (0x22f) +#define URTW_8225_ADDR_0_DATA_MAGIC6 (0x2bf) +#define URTW_8225_ADDR_1_MAGIC 0x1 +#define URTW_8225_ADDR_2_MAGIC 0x2 +#define URTW_8225_ADDR_2_DATA_MAGIC1 (0xc4d) +#define URTW_8225_ADDR_2_DATA_MAGIC2 (0x44d) +#define URTW_8225_ADDR_3_MAGIC 0x3 +#define URTW_8225_ADDR_3_DATA_MAGIC1 (0x2) +#define URTW_8225_ADDR_5_MAGIC 0x5 +#define URTW_8225_ADDR_5_DATA_MAGIC1 (0x4) +#define URTW_8225_ADDR_6_MAGIC 0x6 +#define URTW_8225_ADDR_6_DATA_MAGIC1 (0xe6) +#define URTW_8225_ADDR_6_DATA_MAGIC2 (0x80) +#define URTW_8225_ADDR_7_MAGIC 0x7 +#define URTW_8225_ADDR_8_MAGIC 0x8 +#define URTW_8225_ADDR_8_DATA_MAGIC1 (0x588) +#define URTW_8225_ADDR_9_MAGIC 0x9 +#define URTW_8225_ADDR_9_DATA_MAGIC1 (0x700) +#define URTW_8225_ADDR_C_MAGIC 0xc +#define URTW_8225_ADDR_C_DATA_MAGIC1 (0x850) +#define URTW_8225_ADDR_C_DATA_MAGIC2 (0x050) + +/* for EEPROM */ +#define URTW_EPROM_TXPW_BASE 0x05 +#define URTW_EPROM_RFCHIPID 0x06 +#define URTW_EPROM_RFCHIPID_RTL8225U (5) +#define URTW_EPROM_MACADDR 0x07 +#define URTW_EPROM_TXPW0 0x16 +#define URTW_EPROM_TXPW2 0x1b +#define URTW_EPROM_TXPW1 0x3d +#define URTW_EPROM_SWREV 0x3f +#define URTW_EPROM_CID_MASK (0xff) +#define URTW_EPROM_CID_RSVD0 (0x00) +#define URTW_EPROM_CID_RSVD1 (0xff) +#define URTW_EPROM_CID_ALPHA0 (0x01) +#define URTW_EPROM_CID_SERCOMM_PS (0x02) +#define URTW_EPROM_CID_HW_LED (0x03) + +/* LED */ +#define URTW_CID_DEFAULT 0 +#define URTW_CID_8187_ALPHA0 1 +#define URTW_CID_8187_SERCOMM_PS 2 +#define URTW_CID_8187_HW_LED 3 +#define URTW_SW_LED_MODE0 0 +#define URTW_SW_LED_MODE1 1 +#define URTW_SW_LED_MODE2 2 +#define URTW_SW_LED_MODE3 3 +#define URTW_HW_LED 4 +#define URTW_LED_CTL_POWER_ON 0 +#define URTW_LED_CTL_LINK 2 +#define URTW_LED_CTL_TX 4 +#define URTW_LED_PIN_GPIO0 0 +#define URTW_LED_PIN_LED0 1 +#define URTW_LED_PIN_LED1 2 +#define URTW_LED_UNKNOWN 0 +#define URTW_LED_ON 1 +#define URTW_LED_OFF 2 +#define URTW_LED_BLINK_NORMAL 3 +#define URTW_LED_BLINK_SLOWLY 4 +#define URTW_LED_POWER_ON_BLINK 5 +#define URTW_LED_SCAN_BLINK 6 +#define URTW_LED_NO_LINK_BLINK 7 +#define URTW_LED_BLINK_CM3 8 + +/* for extra area */ +#define URTW_EPROM_DISABLE 0 +#define URTW_EPROM_ENABLE 1 +#define URTW_EPROM_DELAY 10 +#define URTW_8187_GETREGS_REQ 5 +#define URTW_8187_SETREGS_REQ 5 +#define URTW_8225_RF_MAX_SENS 6 +#define URTW_8225_RF_DEF_SENS 4 +#define URTW_DEFAULT_RTS_RETRY 7 +#define URTW_DEFAULT_TX_RETRY 7 +#define URTW_DEFAULT_RTS_THRESHOLD 2342U diff --git a/sys/legacy/dev/usb/if_urtwvar.h b/sys/legacy/dev/usb/if_urtwvar.h new file mode 100644 index 0000000..77c09ef --- /dev/null +++ b/sys/legacy/dev/usb/if_urtwvar.h @@ -0,0 +1,151 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2008 Weongyo Jeong <weongyo@FreeBSD.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* XXX no definition at net80211? */ +#define URTW_MAX_CHANNELS 15 + +struct urtw_data { + struct urtw_softc *sc; + usbd_xfer_handle xfer; + uint8_t *buf; + struct mbuf *m; + struct ieee80211_node *ni; /* NB: tx only */ +}; + +/* XXX not correct.. */ +#define URTW_MIN_RXBUFSZ \ + (sizeof(struct ieee80211_frame_min)) + +#define URTW_RX_DATA_LIST_COUNT 1 +#define URTW_TX_DATA_LIST_COUNT 16 +#define URTW_RX_MAXSIZE 0x9c4 +#define URTW_TX_MAXSIZE 0x9c4 + +struct urtw_rx_radiotap_header { + struct ieee80211_radiotap_header wr_ihdr; + uint8_t wr_flags; + uint16_t wr_chan_freq; + uint16_t wr_chan_flags; + int8_t wr_dbm_antsignal; +} __packed; + +#define URTW_RX_RADIOTAP_PRESENT \ + ((1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL) | \ + (1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL)) + +struct urtw_tx_radiotap_header { + struct ieee80211_radiotap_header wt_ihdr; + uint8_t wt_flags; + uint16_t wt_chan_freq; + uint16_t wt_chan_flags; +} __packed; + +#define URTW_TX_RADIOTAP_PRESENT \ + ((1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL)) + +struct urtw_vap { + struct ieee80211vap vap; + int (*newstate)(struct ieee80211vap *, + enum ieee80211_state, int); +}; +#define URTW_VAP(vap) ((struct urtw_vap *)(vap)) + +struct urtw_softc { + struct ifnet *sc_ifp; + device_t sc_dev; + usbd_device_handle sc_udev; + usbd_interface_handle sc_iface; + struct mtx sc_mtx; + + int sc_debug; + int sc_if_flags; + int sc_flags; +#define URTW_INIT_ONCE (1 << 1) + struct usb_task sc_task; + struct usb_task sc_ctxtask; + int sc_ctxarg; +#define URTW_SET_CHANNEL 1 + enum ieee80211_state sc_state; + int sc_arg; + int (*sc_newstate)(struct ieee80211com *, + enum ieee80211_state, int); + + int sc_epromtype; +#define URTW_EEPROM_93C46 0 +#define URTW_EEPROM_93C56 1 + uint8_t sc_crcmon; + uint8_t sc_bssid[IEEE80211_ADDR_LEN]; + + /* for RF */ + usbd_status (*sc_rf_init)(struct urtw_softc *); + usbd_status (*sc_rf_set_chan)(struct urtw_softc *, + int); + usbd_status (*sc_rf_set_sens)(struct urtw_softc *, + int); + uint8_t sc_rfchip; + uint32_t sc_max_sens; + uint32_t sc_sens; + /* for LED */ + struct callout sc_led_ch; + struct usb_task sc_ledtask; + uint8_t sc_psr; + uint8_t sc_strategy; +#define URTW_LED_GPIO 1 + uint8_t sc_gpio_ledon; + uint8_t sc_gpio_ledinprogress; + uint8_t sc_gpio_ledstate; + uint8_t sc_gpio_ledpin; + uint8_t sc_gpio_blinktime; + uint8_t sc_gpio_blinkstate; + /* RX/TX */ + usbd_pipe_handle sc_rxpipe; + usbd_pipe_handle sc_txpipe_low; + usbd_pipe_handle sc_txpipe_normal; +#define URTW_PRIORITY_LOW 0 +#define URTW_PRIORITY_NORMAL 1 +#define URTW_DATA_TIMEOUT 10000 /* 10 sec */ + struct urtw_data sc_rxdata[URTW_RX_DATA_LIST_COUNT]; + struct urtw_data sc_txdata[URTW_TX_DATA_LIST_COUNT]; + uint32_t sc_tx_low_queued; + uint32_t sc_tx_normal_queued; + uint32_t sc_txidx; + uint8_t sc_rts_retry; + uint8_t sc_tx_retry; + uint8_t sc_preamble_mode; +#define URTW_PREAMBLE_MODE_SHORT 1 +#define URTW_PREAMBLE_MODE_LONG 2 + struct callout sc_watchdog_ch; + int sc_txtimer; + int sc_currate; + /* TX power */ + uint8_t sc_txpwr_cck[URTW_MAX_CHANNELS]; + uint8_t sc_txpwr_cck_base; + uint8_t sc_txpwr_ofdm[URTW_MAX_CHANNELS]; + uint8_t sc_txpwr_ofdm_base; + + struct urtw_rx_radiotap_header sc_rxtap; + int sc_rxtap_len; + struct urtw_tx_radiotap_header sc_txtap; + int sc_txtap_len; +}; + +#define URTW_LOCK(sc) mtx_lock(&(sc)->sc_mtx) +#define URTW_UNLOCK(sc) mtx_unlock(&(sc)->sc_mtx) +#define URTW_ASSERT_LOCKED(sc) mtx_assert(&(sc)->sc_mtx, MA_OWNED) diff --git a/sys/legacy/dev/usb/if_zyd.c b/sys/legacy/dev/usb/if_zyd.c new file mode 100644 index 0000000..3a4abce --- /dev/null +++ b/sys/legacy/dev/usb/if_zyd.c @@ -0,0 +1,3126 @@ +/* $OpenBSD: if_zyd.c,v 1.52 2007/02/11 00:08:04 jsg Exp $ */ +/* $NetBSD: if_zyd.c,v 1.7 2007/06/21 04:04:29 kiyohara Exp $ */ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2006 by Damien Bergamini <damien.bergamini@free.fr> + * Copyright (c) 2006 by Florian Stoehr <ich@florian-stoehr.de> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * ZyDAS ZD1211/ZD1211B USB WLAN driver. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/sockio.h> +#include <sys/mbuf.h> +#include <sys/malloc.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/socket.h> +#include <sys/sysctl.h> +#include <sys/endian.h> +#include <sys/linker.h> + +#include <net/if.h> +#include <net/if_arp.h> +#include <net/ethernet.h> +#include <net/if_dl.h> +#include <net/if_media.h> +#include <net/if_types.h> + +#include <sys/bus.h> +#include <machine/bus.h> + +#include <net80211/ieee80211_var.h> +#include <net80211/ieee80211_amrr.h> +#include <net80211/ieee80211_phy.h> +#include <net80211/ieee80211_radiotap.h> +#include <net80211/ieee80211_regdomain.h> + +#include <net/bpf.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usbdivar.h> +#include "usbdevs.h" +#include <dev/usb/usb_ethersubr.h> + +#include <dev/mii/mii.h> +#include <dev/mii/miivar.h> + +#include <dev/usb/if_zydreg.h> +#include <dev/usb/if_zydfw.h> + +#ifdef ZYD_DEBUG +SYSCTL_NODE(_hw_usb, OID_AUTO, zyd, CTLFLAG_RW, 0, "ZyDAS zd1211/zd1211b"); +int zyd_debug = 0; +SYSCTL_INT(_hw_usb_zyd, OID_AUTO, debug, CTLFLAG_RW, &zyd_debug, 0, + "control debugging printfs"); +TUNABLE_INT("hw.usb.zyd.debug", &zyd_debug); +enum { + ZYD_DEBUG_XMIT = 0x00000001, /* basic xmit operation */ + ZYD_DEBUG_RECV = 0x00000002, /* basic recv operation */ + ZYD_DEBUG_RESET = 0x00000004, /* reset processing */ + ZYD_DEBUG_INIT = 0x00000008, /* device init */ + ZYD_DEBUG_TX_PROC = 0x00000010, /* tx ISR proc */ + ZYD_DEBUG_RX_PROC = 0x00000020, /* rx ISR proc */ + ZYD_DEBUG_STATE = 0x00000040, /* 802.11 state transitions */ + ZYD_DEBUG_STAT = 0x00000080, /* statistic */ + ZYD_DEBUG_FW = 0x00000100, /* firmware */ + ZYD_DEBUG_ANY = 0xffffffff +}; +#define DPRINTF(sc, m, fmt, ...) do { \ + if (sc->sc_debug & (m)) \ + printf(fmt, __VA_ARGS__); \ +} while (0) +#else +#define DPRINTF(sc, m, fmt, ...) do { \ + (void) sc; \ +} while (0) +#endif + +static const struct zyd_phy_pair zyd_def_phy[] = ZYD_DEF_PHY; +static const struct zyd_phy_pair zyd_def_phyB[] = ZYD_DEF_PHYB; + +/* various supported device vendors/products */ +#define ZYD_ZD1211_DEV(v, p) \ + { { USB_VENDOR_##v, USB_PRODUCT_##v##_##p }, ZYD_ZD1211 } +#define ZYD_ZD1211B_DEV(v, p) \ + { { USB_VENDOR_##v, USB_PRODUCT_##v##_##p }, ZYD_ZD1211B } +static const struct zyd_type { + struct usb_devno dev; + uint8_t rev; +#define ZYD_ZD1211 0 +#define ZYD_ZD1211B 1 +} zyd_devs[] = { + ZYD_ZD1211_DEV(3COM2, 3CRUSB10075), + ZYD_ZD1211_DEV(ABOCOM, WL54), + ZYD_ZD1211_DEV(ASUS, WL159G), + ZYD_ZD1211_DEV(CYBERTAN, TG54USB), + ZYD_ZD1211_DEV(DRAYTEK, VIGOR550), + ZYD_ZD1211_DEV(PLANEX2, GWUS54GD), + ZYD_ZD1211_DEV(PLANEX2, GWUS54GZL), + ZYD_ZD1211_DEV(PLANEX3, GWUS54GZ), + ZYD_ZD1211_DEV(PLANEX3, GWUS54MINI), + ZYD_ZD1211_DEV(SAGEM, XG760A), + ZYD_ZD1211_DEV(SENAO, NUB8301), + ZYD_ZD1211_DEV(SITECOMEU, WL113), + ZYD_ZD1211_DEV(SWEEX, ZD1211), + ZYD_ZD1211_DEV(TEKRAM, QUICKWLAN), + ZYD_ZD1211_DEV(TEKRAM, ZD1211_1), + ZYD_ZD1211_DEV(TEKRAM, ZD1211_2), + ZYD_ZD1211_DEV(TWINMOS, G240), + ZYD_ZD1211_DEV(UMEDIA, ALL0298V2), + ZYD_ZD1211_DEV(UMEDIA, TEW429UB_A), + ZYD_ZD1211_DEV(UMEDIA, TEW429UB), + ZYD_ZD1211_DEV(WISTRONNEWEB, UR055G), + ZYD_ZD1211_DEV(ZCOM, ZD1211), + ZYD_ZD1211_DEV(ZYDAS, ZD1211), + ZYD_ZD1211_DEV(ZYXEL, AG225H), + ZYD_ZD1211_DEV(ZYXEL, ZYAIRG220), + ZYD_ZD1211_DEV(ZYXEL, G200V2), + ZYD_ZD1211_DEV(ZYXEL, G202), + + ZYD_ZD1211B_DEV(ACCTON, SMCWUSBG), + ZYD_ZD1211B_DEV(ACCTON, ZD1211B), + ZYD_ZD1211B_DEV(ASUS, A9T_WIFI), + ZYD_ZD1211B_DEV(BELKIN, F5D7050_V4000), + ZYD_ZD1211B_DEV(BELKIN, ZD1211B), + ZYD_ZD1211B_DEV(CISCOLINKSYS, WUSBF54G), + ZYD_ZD1211B_DEV(FIBERLINE, WL430U), + ZYD_ZD1211B_DEV(MELCO, KG54L), + ZYD_ZD1211B_DEV(PHILIPS, SNU5600), + ZYD_ZD1211B_DEV(PLANEX2, GW_US54GXS), + ZYD_ZD1211B_DEV(SAGEM, XG76NA), + ZYD_ZD1211B_DEV(SITECOMEU, ZD1211B), + ZYD_ZD1211B_DEV(UMEDIA, TEW429UBC1), +#if 0 /* Shall we needs? */ + ZYD_ZD1211B_DEV(UNKNOWN1, ZD1211B_1), + ZYD_ZD1211B_DEV(UNKNOWN1, ZD1211B_2), + ZYD_ZD1211B_DEV(UNKNOWN2, ZD1211B), + ZYD_ZD1211B_DEV(UNKNOWN3, ZD1211B), +#endif + ZYD_ZD1211B_DEV(USR, USR5423), + ZYD_ZD1211B_DEV(VTECH, ZD1211B), + ZYD_ZD1211B_DEV(ZCOM, ZD1211B), + ZYD_ZD1211B_DEV(ZYDAS, ZD1211B), + ZYD_ZD1211B_DEV(ZYXEL, M202), + ZYD_ZD1211B_DEV(ZYXEL, G220V2), +}; +#define zyd_lookup(v, p) \ + ((const struct zyd_type *)usb_lookup(zyd_devs, v, p)) +#define zyd_read16_m(sc, val, data) do { \ + error = zyd_read16(sc, val, data); \ + if (error != 0) \ + goto fail; \ +} while (0) +#define zyd_write16_m(sc, val, data) do { \ + error = zyd_write16(sc, val, data); \ + if (error != 0) \ + goto fail; \ +} while (0) +#define zyd_read32_m(sc, val, data) do { \ + error = zyd_read32(sc, val, data); \ + if (error != 0) \ + goto fail; \ +} while (0) +#define zyd_write32_m(sc, val, data) do { \ + error = zyd_write32(sc, val, data); \ + if (error != 0) \ + goto fail; \ +} while (0) + +static device_probe_t zyd_match; +static device_attach_t zyd_attach; +static device_detach_t zyd_detach; + +static struct ieee80211vap *zyd_vap_create(struct ieee80211com *, + const char name[IFNAMSIZ], int unit, int opmode, + int flags, const uint8_t bssid[IEEE80211_ADDR_LEN], + const uint8_t mac[IEEE80211_ADDR_LEN]); +static void zyd_vap_delete(struct ieee80211vap *); +static int zyd_open_pipes(struct zyd_softc *); +static void zyd_close_pipes(struct zyd_softc *); +static int zyd_alloc_tx_list(struct zyd_softc *); +static void zyd_free_tx_list(struct zyd_softc *); +static int zyd_alloc_rx_list(struct zyd_softc *); +static void zyd_free_rx_list(struct zyd_softc *); +static struct ieee80211_node *zyd_node_alloc(struct ieee80211vap *, + const uint8_t mac[IEEE80211_ADDR_LEN]); +static void zyd_task(void *); +static int zyd_newstate(struct ieee80211vap *, enum ieee80211_state, int); +static int zyd_cmd(struct zyd_softc *, uint16_t, const void *, int, + void *, int, u_int); +static int zyd_read16(struct zyd_softc *, uint16_t, uint16_t *); +static int zyd_read32(struct zyd_softc *, uint16_t, uint32_t *); +static int zyd_write16(struct zyd_softc *, uint16_t, uint16_t); +static int zyd_write32(struct zyd_softc *, uint16_t, uint32_t); +static int zyd_rfwrite(struct zyd_softc *, uint32_t); +static int zyd_lock_phy(struct zyd_softc *); +static int zyd_unlock_phy(struct zyd_softc *); +static int zyd_rf_attach(struct zyd_softc *, uint8_t); +static const char *zyd_rf_name(uint8_t); +static int zyd_hw_init(struct zyd_softc *); +static int zyd_read_pod(struct zyd_softc *); +static int zyd_read_eeprom(struct zyd_softc *); +static int zyd_get_macaddr(struct zyd_softc *); +static int zyd_set_macaddr(struct zyd_softc *, const uint8_t *); +static int zyd_set_bssid(struct zyd_softc *, const uint8_t *); +static int zyd_switch_radio(struct zyd_softc *, int); +static int zyd_set_led(struct zyd_softc *, int, int); +static void zyd_set_multi(void *); +static void zyd_update_mcast(struct ifnet *); +static int zyd_set_rxfilter(struct zyd_softc *); +static void zyd_set_chan(struct zyd_softc *, struct ieee80211_channel *); +static int zyd_set_beacon_interval(struct zyd_softc *, int); +static void zyd_intr(usbd_xfer_handle, usbd_private_handle, usbd_status); +static void zyd_rx_data(struct zyd_softc *, const uint8_t *, uint16_t); +static void zyd_rxeof(usbd_xfer_handle, usbd_private_handle, usbd_status); +static void zyd_txeof(usbd_xfer_handle, usbd_private_handle, usbd_status); +static int zyd_tx_mgt(struct zyd_softc *, struct mbuf *, + struct ieee80211_node *); +static int zyd_tx_data(struct zyd_softc *, struct mbuf *, + struct ieee80211_node *); +static void zyd_start(struct ifnet *); +static int zyd_raw_xmit(struct ieee80211_node *, struct mbuf *, + const struct ieee80211_bpf_params *); +static void zyd_watchdog(void *); +static int zyd_ioctl(struct ifnet *, u_long, caddr_t); +static void zyd_init_locked(struct zyd_softc *); +static void zyd_init(void *); +static void zyd_stop(struct zyd_softc *, int); +static int zyd_loadfirmware(struct zyd_softc *); +static void zyd_newassoc(struct ieee80211_node *, int); +static void zyd_scantask(void *); +static void zyd_scan_start(struct ieee80211com *); +static void zyd_scan_end(struct ieee80211com *); +static void zyd_set_channel(struct ieee80211com *); +static void zyd_wakeup(struct zyd_softc *); +static int zyd_rfmd_init(struct zyd_rf *); +static int zyd_rfmd_switch_radio(struct zyd_rf *, int); +static int zyd_rfmd_set_channel(struct zyd_rf *, uint8_t); +static int zyd_al2230_init(struct zyd_rf *); +static int zyd_al2230_switch_radio(struct zyd_rf *, int); +static int zyd_al2230_set_channel(struct zyd_rf *, uint8_t); +static int zyd_al2230_set_channel_b(struct zyd_rf *, uint8_t); +static int zyd_al2230_init_b(struct zyd_rf *); +static int zyd_al7230B_init(struct zyd_rf *); +static int zyd_al7230B_switch_radio(struct zyd_rf *, int); +static int zyd_al7230B_set_channel(struct zyd_rf *, uint8_t); +static int zyd_al2210_init(struct zyd_rf *); +static int zyd_al2210_switch_radio(struct zyd_rf *, int); +static int zyd_al2210_set_channel(struct zyd_rf *, uint8_t); +static int zyd_gct_init(struct zyd_rf *); +static int zyd_gct_switch_radio(struct zyd_rf *, int); +static int zyd_gct_set_channel(struct zyd_rf *, uint8_t); +static int zyd_maxim_init(struct zyd_rf *); +static int zyd_maxim_switch_radio(struct zyd_rf *, int); +static int zyd_maxim_set_channel(struct zyd_rf *, uint8_t); +static int zyd_maxim2_init(struct zyd_rf *); +static int zyd_maxim2_switch_radio(struct zyd_rf *, int); +static int zyd_maxim2_set_channel(struct zyd_rf *, uint8_t); + +static int +zyd_match(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + + if (!uaa->iface) + return (UMATCH_NONE); + + return (zyd_lookup(uaa->vendor, uaa->product) != NULL) ? + (UMATCH_VENDOR_PRODUCT) : (UMATCH_NONE); +} + +static int +zyd_attach(device_t dev) +{ + int error = ENXIO; + struct ieee80211com *ic; + struct ifnet *ifp; + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct zyd_softc *sc = device_get_softc(dev); + usb_device_descriptor_t* ddesc; + uint8_t bands; + + sc->sc_dev = dev; + sc->sc_udev = uaa->device; + sc->sc_macrev = zyd_lookup(uaa->vendor, uaa->product)->rev; +#ifdef ZYD_DEBUG + sc->sc_debug = zyd_debug; +#endif + + ddesc = usbd_get_device_descriptor(sc->sc_udev); + if (UGETW(ddesc->bcdDevice) < 0x4330) { + device_printf(dev, "device version mismatch: 0x%x " + "(only >= 43.30 supported)\n", + UGETW(ddesc->bcdDevice)); + return (ENXIO); + } + + if ((error = zyd_get_macaddr(sc)) != 0) { + device_printf(sc->sc_dev, "could not read EEPROM\n"); + return (ENXIO); + } + + mtx_init(&sc->sc_txmtx, device_get_nameunit(sc->sc_dev), + MTX_NETWORK_LOCK, MTX_DEF); + usb_init_task(&sc->sc_mcasttask, zyd_set_multi, sc); + usb_init_task(&sc->sc_scantask, zyd_scantask, sc); + usb_init_task(&sc->sc_task, zyd_task, sc); + callout_init(&sc->sc_watchdog_ch, 0); + STAILQ_INIT(&sc->sc_rqh); + + ifp = sc->sc_ifp = if_alloc(IFT_IEEE80211); + if (ifp == NULL) { + device_printf(dev, "can not if_alloc()\n"); + error = ENXIO; + goto fail0; + } + ifp->if_softc = sc; + if_initname(ifp, "zyd", device_get_unit(sc->sc_dev)); + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST | + IFF_NEEDSGIANT; /* USB stack is still under Giant lock */ + ifp->if_init = zyd_init; + ifp->if_ioctl = zyd_ioctl; + ifp->if_start = zyd_start; + IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN); + IFQ_SET_READY(&ifp->if_snd); + + ic = ifp->if_l2com; + ic->ic_ifp = ifp; + ic->ic_phytype = IEEE80211_T_OFDM; /* not only, but not used */ + ic->ic_opmode = IEEE80211_M_STA; + IEEE80211_ADDR_COPY(ic->ic_myaddr, sc->sc_bssid); + + /* set device capabilities */ + ic->ic_caps = + IEEE80211_C_STA /* station mode */ + | IEEE80211_C_MONITOR /* monitor mode */ + | IEEE80211_C_SHPREAMBLE /* short preamble supported */ + | IEEE80211_C_SHSLOT /* short slot time supported */ + | IEEE80211_C_BGSCAN /* capable of bg scanning */ + | IEEE80211_C_WPA /* 802.11i */ + ; + + bands = 0; + setbit(&bands, IEEE80211_MODE_11B); + setbit(&bands, IEEE80211_MODE_11G); + ieee80211_init_channels(ic, NULL, &bands); + + ieee80211_ifattach(ic); + ic->ic_newassoc = zyd_newassoc; + ic->ic_raw_xmit = zyd_raw_xmit; + ic->ic_node_alloc = zyd_node_alloc; + ic->ic_scan_start = zyd_scan_start; + ic->ic_scan_end = zyd_scan_end; + ic->ic_set_channel = zyd_set_channel; + + ic->ic_vap_create = zyd_vap_create; + ic->ic_vap_delete = zyd_vap_delete; + ic->ic_update_mcast = zyd_update_mcast; + + bpfattach(ifp, DLT_IEEE802_11_RADIO, + sizeof(struct ieee80211_frame) + sizeof(sc->sc_txtap)); + sc->sc_rxtap_len = sizeof(sc->sc_rxtap); + sc->sc_rxtap.wr_ihdr.it_len = htole16(sc->sc_rxtap_len); + sc->sc_rxtap.wr_ihdr.it_present = htole32(ZYD_RX_RADIOTAP_PRESENT); + sc->sc_txtap_len = sizeof(sc->sc_txtap); + sc->sc_txtap.wt_ihdr.it_len = htole16(sc->sc_txtap_len); + sc->sc_txtap.wt_ihdr.it_present = htole32(ZYD_TX_RADIOTAP_PRESENT); + + if (bootverbose) + ieee80211_announce(ic); + + usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, sc->sc_udev, sc->sc_dev); + + return (0); + +fail0: mtx_destroy(&sc->sc_txmtx); + return (error); +} + +static int +zyd_detach(device_t dev) +{ + struct zyd_softc *sc = device_get_softc(dev); + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + if (!device_is_attached(dev)) + return (0); + + /* set a flag to indicate we're detaching. */ + sc->sc_flags |= ZYD_FLAG_DETACHING; + + zyd_stop(sc, 1); + bpfdetach(ifp); + ieee80211_ifdetach(ic); + + zyd_wakeup(sc); + zyd_close_pipes(sc); + + if_free(ifp); + mtx_destroy(&sc->sc_txmtx); + + usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_udev, sc->sc_dev); + + return (0); +} + +static struct ieee80211vap * +zyd_vap_create(struct ieee80211com *ic, + const char name[IFNAMSIZ], int unit, int opmode, int flags, + const uint8_t bssid[IEEE80211_ADDR_LEN], + const uint8_t mac[IEEE80211_ADDR_LEN]) +{ + struct zyd_vap *zvp; + struct ieee80211vap *vap; + + if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */ + return (NULL); + zvp = (struct zyd_vap *) malloc(sizeof(struct zyd_vap), + M_80211_VAP, M_NOWAIT | M_ZERO); + if (zvp == NULL) + return (NULL); + vap = &zvp->vap; + /* enable s/w bmiss handling for sta mode */ + ieee80211_vap_setup(ic, vap, name, unit, opmode, + flags | IEEE80211_CLONE_NOBEACONS, bssid, mac); + + /* override state transition machine */ + zvp->newstate = vap->iv_newstate; + vap->iv_newstate = zyd_newstate; + + ieee80211_amrr_init(&zvp->amrr, vap, + IEEE80211_AMRR_MIN_SUCCESS_THRESHOLD, + IEEE80211_AMRR_MAX_SUCCESS_THRESHOLD, + 1000 /* 1 sec */); + + /* complete setup */ + ieee80211_vap_attach(vap, ieee80211_media_change, + ieee80211_media_status); + ic->ic_opmode = opmode; + return (vap); +} + +static void +zyd_vap_delete(struct ieee80211vap *vap) +{ + struct zyd_vap *zvp = ZYD_VAP(vap); + + ieee80211_amrr_cleanup(&zvp->amrr); + ieee80211_vap_detach(vap); + free(zvp, M_80211_VAP); +} + +static int +zyd_open_pipes(struct zyd_softc *sc) +{ + usb_endpoint_descriptor_t *edesc; + int isize; + usbd_status error; + + /* interrupt in */ + edesc = usbd_get_endpoint_descriptor(sc->sc_iface, 0x83); + if (edesc == NULL) + return (EINVAL); + + isize = UGETW(edesc->wMaxPacketSize); + if (isize == 0) /* should not happen */ + return (EINVAL); + + sc->sc_ibuf = malloc(isize, M_USBDEV, M_NOWAIT); + if (sc->sc_ibuf == NULL) + return (ENOMEM); + + error = usbd_open_pipe_intr(sc->sc_iface, 0x83, USBD_SHORT_XFER_OK, + &sc->sc_ep[ZYD_ENDPT_IIN], sc, sc->sc_ibuf, isize, zyd_intr, + USBD_DEFAULT_INTERVAL); + if (error != 0) { + device_printf(sc->sc_dev, "open rx intr pipe failed: %s\n", + usbd_errstr(error)); + goto fail; + } + + /* interrupt out (not necessarily an interrupt pipe) */ + error = usbd_open_pipe(sc->sc_iface, 0x04, USBD_EXCLUSIVE_USE, + &sc->sc_ep[ZYD_ENDPT_IOUT]); + if (error != 0) { + device_printf(sc->sc_dev, "open tx intr pipe failed: %s\n", + usbd_errstr(error)); + goto fail; + } + + /* bulk in */ + error = usbd_open_pipe(sc->sc_iface, 0x82, USBD_EXCLUSIVE_USE, + &sc->sc_ep[ZYD_ENDPT_BIN]); + if (error != 0) { + device_printf(sc->sc_dev, "open rx pipe failed: %s\n", + usbd_errstr(error)); + goto fail; + } + + /* bulk out */ + error = usbd_open_pipe(sc->sc_iface, 0x01, USBD_EXCLUSIVE_USE, + &sc->sc_ep[ZYD_ENDPT_BOUT]); + if (error != 0) { + device_printf(sc->sc_dev, "open tx pipe failed: %s\n", + usbd_errstr(error)); + goto fail; + } + + return (0); + +fail: zyd_close_pipes(sc); + return (ENXIO); +} + +static void +zyd_close_pipes(struct zyd_softc *sc) +{ + int i; + + for (i = 0; i < ZYD_ENDPT_CNT; i++) { + if (sc->sc_ep[i] != NULL) { + usbd_abort_pipe(sc->sc_ep[i]); + usbd_close_pipe(sc->sc_ep[i]); + sc->sc_ep[i] = NULL; + } + } + if (sc->sc_ibuf != NULL) { + free(sc->sc_ibuf, M_USBDEV); + sc->sc_ibuf = NULL; + } +} + +static int +zyd_alloc_tx_list(struct zyd_softc *sc) +{ + int i, error; + + sc->sc_txqueued = 0; + + for (i = 0; i < ZYD_TX_LIST_CNT; i++) { + struct zyd_tx_data *data = &sc->sc_txdata[i]; + + data->sc = sc; /* backpointer for callbacks */ + + data->xfer = usbd_alloc_xfer(sc->sc_udev); + if (data->xfer == NULL) { + device_printf(sc->sc_dev, + "could not allocate tx xfer\n"); + error = ENOMEM; + goto fail; + } + data->buf = usbd_alloc_buffer(data->xfer, ZYD_MAX_TXBUFSZ); + if (data->buf == NULL) { + device_printf(sc->sc_dev, + "could not allocate tx buffer\n"); + error = ENOMEM; + goto fail; + } + + /* clear Tx descriptor */ + bzero(data->buf, sizeof(struct zyd_tx_desc)); + } + return (0); + +fail: zyd_free_tx_list(sc); + return (error); +} + +static void +zyd_free_tx_list(struct zyd_softc *sc) +{ + int i; + + for (i = 0; i < ZYD_TX_LIST_CNT; i++) { + struct zyd_tx_data *data = &sc->sc_txdata[i]; + + if (data->xfer != NULL) { + usbd_free_xfer(data->xfer); + data->xfer = NULL; + } + if (data->ni != NULL) { + ieee80211_free_node(data->ni); + data->ni = NULL; + } + } +} + +static int +zyd_alloc_rx_list(struct zyd_softc *sc) +{ + int i, error; + + for (i = 0; i < ZYD_RX_LIST_CNT; i++) { + struct zyd_rx_data *data = &sc->sc_rxdata[i]; + + data->sc = sc; /* backpointer for callbacks */ + + data->xfer = usbd_alloc_xfer(sc->sc_udev); + if (data->xfer == NULL) { + device_printf(sc->sc_dev, + "could not allocate rx xfer\n"); + error = ENOMEM; + goto fail; + } + data->buf = usbd_alloc_buffer(data->xfer, ZYX_MAX_RXBUFSZ); + if (data->buf == NULL) { + device_printf(sc->sc_dev, + "could not allocate rx buffer\n"); + error = ENOMEM; + goto fail; + } + } + return (0); + +fail: zyd_free_rx_list(sc); + return (error); +} + +static void +zyd_free_rx_list(struct zyd_softc *sc) +{ + int i; + + for (i = 0; i < ZYD_RX_LIST_CNT; i++) { + struct zyd_rx_data *data = &sc->sc_rxdata[i]; + + if (data->xfer != NULL) { + usbd_free_xfer(data->xfer); + data->xfer = NULL; + } + } +} + +/* ARGUSED */ +static struct ieee80211_node * +zyd_node_alloc(struct ieee80211vap *vap __unused, + const uint8_t mac[IEEE80211_ADDR_LEN] __unused) +{ + struct zyd_node *zn; + + zn = malloc(sizeof(struct zyd_node), M_80211_NODE, M_NOWAIT | M_ZERO); + return (zn != NULL) ? (&zn->ni) : (NULL); +} + +static void +zyd_task(void *arg) +{ + int error; + struct zyd_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); + struct ieee80211_node *ni = vap->iv_bss; + struct zyd_vap *zvp = ZYD_VAP(vap); + + switch (sc->sc_state) { + case IEEE80211_S_AUTH: + zyd_set_chan(sc, ic->ic_curchan); + break; + case IEEE80211_S_RUN: + if (vap->iv_opmode == IEEE80211_M_MONITOR) + break; + + /* turn link LED on */ + error = zyd_set_led(sc, ZYD_LED1, 1); + if (error != 0) + goto fail; + + /* make data LED blink upon Tx */ + zyd_write32_m(sc, sc->sc_fwbase + ZYD_FW_LINK_STATUS, 1); + + IEEE80211_ADDR_COPY(sc->sc_bssid, ni->ni_bssid); + zyd_set_bssid(sc, sc->sc_bssid); + break; + default: + break; + } + +fail: + IEEE80211_LOCK(ic); + zvp->newstate(vap, sc->sc_state, sc->sc_arg); + if (vap->iv_newstate_cb != NULL) + vap->iv_newstate_cb(vap, sc->sc_state, sc->sc_arg); + IEEE80211_UNLOCK(ic); +} + +static int +zyd_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) +{ + struct zyd_vap *zvp = ZYD_VAP(vap); + struct ieee80211com *ic = vap->iv_ic; + struct zyd_softc *sc = ic->ic_ifp->if_softc; + + DPRINTF(sc, ZYD_DEBUG_STATE, "%s: %s -> %s\n", __func__, + ieee80211_state_name[vap->iv_state], + ieee80211_state_name[nstate]); + + usb_rem_task(sc->sc_udev, &sc->sc_scantask); + usb_rem_task(sc->sc_udev, &sc->sc_task); + callout_stop(&sc->sc_watchdog_ch); + + /* do it in a process context */ + sc->sc_state = nstate; + sc->sc_arg = arg; + + if (nstate == IEEE80211_S_INIT) { + zvp->newstate(vap, nstate, arg); + return (0); + } else { + usb_add_task(sc->sc_udev, &sc->sc_task, USB_TASKQ_DRIVER); + return (EINPROGRESS); + } +} + +static int +zyd_cmd(struct zyd_softc *sc, uint16_t code, const void *idata, int ilen, + void *odata, int olen, u_int flags) +{ + usbd_xfer_handle xfer; + struct zyd_cmd cmd; + struct zyd_rq rq; + uint16_t xferflags; + usbd_status error; + + if (sc->sc_flags & ZYD_FLAG_DETACHING) + return (ENXIO); + + if ((xfer = usbd_alloc_xfer(sc->sc_udev)) == NULL) + return (ENOMEM); + + cmd.code = htole16(code); + bcopy(idata, cmd.data, ilen); + + xferflags = USBD_FORCE_SHORT_XFER; + if (!(flags & ZYD_CMD_FLAG_READ)) + xferflags |= USBD_SYNCHRONOUS; + else { + rq.idata = idata; + rq.odata = odata; + rq.len = olen / sizeof(struct zyd_pair); + STAILQ_INSERT_TAIL(&sc->sc_rqh, &rq, rq); + } + + usbd_setup_xfer(xfer, sc->sc_ep[ZYD_ENDPT_IOUT], 0, &cmd, + sizeof(uint16_t) + ilen, xferflags, ZYD_INTR_TIMEOUT, NULL); + error = usbd_transfer(xfer); + if (error != USBD_IN_PROGRESS && error != 0) { + device_printf(sc->sc_dev, "could not send command (error=%s)\n", + usbd_errstr(error)); + (void)usbd_free_xfer(xfer); + return (EIO); + } + if (!(flags & ZYD_CMD_FLAG_READ)) { + (void)usbd_free_xfer(xfer); + return (0); /* write: don't wait for reply */ + } + /* wait at most one second for command reply */ + error = tsleep(odata, PCATCH, "zydcmd", hz); + if (error == EWOULDBLOCK) + device_printf(sc->sc_dev, "zyd_read sleep timeout\n"); + STAILQ_REMOVE(&sc->sc_rqh, &rq, zyd_rq, rq); + + (void)usbd_free_xfer(xfer); + return (error); +} + +static int +zyd_read16(struct zyd_softc *sc, uint16_t reg, uint16_t *val) +{ + struct zyd_pair tmp; + int error; + + reg = htole16(reg); + error = zyd_cmd(sc, ZYD_CMD_IORD, ®, sizeof(reg), &tmp, sizeof(tmp), + ZYD_CMD_FLAG_READ); + if (error == 0) + *val = le16toh(tmp.val); + return (error); +} + +static int +zyd_read32(struct zyd_softc *sc, uint16_t reg, uint32_t *val) +{ + struct zyd_pair tmp[2]; + uint16_t regs[2]; + int error; + + regs[0] = htole16(ZYD_REG32_HI(reg)); + regs[1] = htole16(ZYD_REG32_LO(reg)); + error = zyd_cmd(sc, ZYD_CMD_IORD, regs, sizeof(regs), tmp, sizeof(tmp), + ZYD_CMD_FLAG_READ); + if (error == 0) + *val = le16toh(tmp[0].val) << 16 | le16toh(tmp[1].val); + return (error); +} + +static int +zyd_write16(struct zyd_softc *sc, uint16_t reg, uint16_t val) +{ + struct zyd_pair pair; + + pair.reg = htole16(reg); + pair.val = htole16(val); + + return zyd_cmd(sc, ZYD_CMD_IOWR, &pair, sizeof(pair), NULL, 0, 0); +} + +static int +zyd_write32(struct zyd_softc *sc, uint16_t reg, uint32_t val) +{ + struct zyd_pair pair[2]; + + pair[0].reg = htole16(ZYD_REG32_HI(reg)); + pair[0].val = htole16(val >> 16); + pair[1].reg = htole16(ZYD_REG32_LO(reg)); + pair[1].val = htole16(val & 0xffff); + + return zyd_cmd(sc, ZYD_CMD_IOWR, pair, sizeof(pair), NULL, 0, 0); +} + +static int +zyd_rfwrite(struct zyd_softc *sc, uint32_t val) +{ + struct zyd_rf *rf = &sc->sc_rf; + struct zyd_rfwrite_cmd req; + uint16_t cr203; + int error, i; + + zyd_read16_m(sc, ZYD_CR203, &cr203); + cr203 &= ~(ZYD_RF_IF_LE | ZYD_RF_CLK | ZYD_RF_DATA); + + req.code = htole16(2); + req.width = htole16(rf->width); + for (i = 0; i < rf->width; i++) { + req.bit[i] = htole16(cr203); + if (val & (1 << (rf->width - 1 - i))) + req.bit[i] |= htole16(ZYD_RF_DATA); + } + error = zyd_cmd(sc, ZYD_CMD_RFCFG, &req, 4 + 2 * rf->width, NULL, 0, 0); +fail: + return (error); +} + +static int +zyd_rfwrite_cr(struct zyd_softc *sc, uint32_t val) +{ + int error; + + zyd_write16_m(sc, ZYD_CR244, (val >> 16) & 0xff); + zyd_write16_m(sc, ZYD_CR243, (val >> 8) & 0xff); + zyd_write16_m(sc, ZYD_CR242, (val >> 0) & 0xff); +fail: + return (error); +} + +static int +zyd_lock_phy(struct zyd_softc *sc) +{ + int error; + uint32_t tmp; + + zyd_read32_m(sc, ZYD_MAC_MISC, &tmp); + tmp &= ~ZYD_UNLOCK_PHY_REGS; + zyd_write32_m(sc, ZYD_MAC_MISC, tmp); +fail: + return (error); +} + +static int +zyd_unlock_phy(struct zyd_softc *sc) +{ + int error; + uint32_t tmp; + + zyd_read32_m(sc, ZYD_MAC_MISC, &tmp); + tmp |= ZYD_UNLOCK_PHY_REGS; + zyd_write32_m(sc, ZYD_MAC_MISC, tmp); +fail: + return (error); +} + +/* + * RFMD RF methods. + */ +static int +zyd_rfmd_init(struct zyd_rf *rf) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + struct zyd_softc *sc = rf->rf_sc; + static const struct zyd_phy_pair phyini[] = ZYD_RFMD_PHY; + static const uint32_t rfini[] = ZYD_RFMD_RF; + int i, error; + + /* init RF-dependent PHY registers */ + for (i = 0; i < N(phyini); i++) { + zyd_write16_m(sc, phyini[i].reg, phyini[i].val); + } + + /* init RFMD radio */ + for (i = 0; i < N(rfini); i++) { + if ((error = zyd_rfwrite(sc, rfini[i])) != 0) + return (error); + } +fail: + return (error); +#undef N +} + +static int +zyd_rfmd_switch_radio(struct zyd_rf *rf, int on) +{ + int error; + struct zyd_softc *sc = rf->rf_sc; + + zyd_write16_m(sc, ZYD_CR10, on ? 0x89 : 0x15); + zyd_write16_m(sc, ZYD_CR11, on ? 0x00 : 0x81); +fail: + return (error); +} + +static int +zyd_rfmd_set_channel(struct zyd_rf *rf, uint8_t chan) +{ + int error; + struct zyd_softc *sc = rf->rf_sc; + static const struct { + uint32_t r1, r2; + } rfprog[] = ZYD_RFMD_CHANTABLE; + + error = zyd_rfwrite(sc, rfprog[chan - 1].r1); + if (error != 0) + goto fail; + error = zyd_rfwrite(sc, rfprog[chan - 1].r2); + if (error != 0) + goto fail; + +fail: + return (error); +} + +/* + * AL2230 RF methods. + */ +static int +zyd_al2230_init(struct zyd_rf *rf) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + struct zyd_softc *sc = rf->rf_sc; + static const struct zyd_phy_pair phyini[] = ZYD_AL2230_PHY; + static const struct zyd_phy_pair phy2230s[] = ZYD_AL2230S_PHY_INIT; + static const struct zyd_phy_pair phypll[] = { + { ZYD_CR251, 0x2f }, { ZYD_CR251, 0x3f }, + { ZYD_CR138, 0x28 }, { ZYD_CR203, 0x06 } + }; + static const uint32_t rfini1[] = ZYD_AL2230_RF_PART1; + static const uint32_t rfini2[] = ZYD_AL2230_RF_PART2; + static const uint32_t rfini3[] = ZYD_AL2230_RF_PART3; + int i, error; + + /* init RF-dependent PHY registers */ + for (i = 0; i < N(phyini); i++) + zyd_write16_m(sc, phyini[i].reg, phyini[i].val); + + if (sc->sc_rfrev == ZYD_RF_AL2230S || sc->sc_al2230s != 0) { + for (i = 0; i < N(phy2230s); i++) + zyd_write16_m(sc, phy2230s[i].reg, phy2230s[i].val); + } + + /* init AL2230 radio */ + for (i = 0; i < N(rfini1); i++) { + error = zyd_rfwrite(sc, rfini1[i]); + if (error != 0) + goto fail; + } + + if (sc->sc_rfrev == ZYD_RF_AL2230S || sc->sc_al2230s != 0) + error = zyd_rfwrite(sc, 0x000824); + else + error = zyd_rfwrite(sc, 0x0005a4); + if (error != 0) + goto fail; + + for (i = 0; i < N(rfini2); i++) { + error = zyd_rfwrite(sc, rfini2[i]); + if (error != 0) + goto fail; + } + + for (i = 0; i < N(phypll); i++) + zyd_write16_m(sc, phypll[i].reg, phypll[i].val); + + for (i = 0; i < N(rfini3); i++) { + error = zyd_rfwrite(sc, rfini3[i]); + if (error != 0) + goto fail; + } +fail: + return (error); +#undef N +} + +static int +zyd_al2230_fini(struct zyd_rf *rf) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + int error, i; + struct zyd_softc *sc = rf->rf_sc; + static const struct zyd_phy_pair phy[] = ZYD_AL2230_PHY_FINI_PART1; + + for (i = 0; i < N(phy); i++) + zyd_write16_m(sc, phy[i].reg, phy[i].val); + + if (sc->sc_newphy != 0) + zyd_write16_m(sc, ZYD_CR9, 0xe1); + + zyd_write16_m(sc, ZYD_CR203, 0x6); +fail: + return (error); +#undef N +} + +static int +zyd_al2230_init_b(struct zyd_rf *rf) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + struct zyd_softc *sc = rf->rf_sc; + static const struct zyd_phy_pair phy1[] = ZYD_AL2230_PHY_PART1; + static const struct zyd_phy_pair phy2[] = ZYD_AL2230_PHY_PART2; + static const struct zyd_phy_pair phy3[] = ZYD_AL2230_PHY_PART3; + static const struct zyd_phy_pair phy2230s[] = ZYD_AL2230S_PHY_INIT; + static const struct zyd_phy_pair phyini[] = ZYD_AL2230_PHY_B; + static const uint32_t rfini_part1[] = ZYD_AL2230_RF_B_PART1; + static const uint32_t rfini_part2[] = ZYD_AL2230_RF_B_PART2; + static const uint32_t rfini_part3[] = ZYD_AL2230_RF_B_PART3; + static const uint32_t zyd_al2230_chtable[][3] = ZYD_AL2230_CHANTABLE; + int i, error; + + for (i = 0; i < N(phy1); i++) + zyd_write16_m(sc, phy1[i].reg, phy1[i].val); + + /* init RF-dependent PHY registers */ + for (i = 0; i < N(phyini); i++) + zyd_write16_m(sc, phyini[i].reg, phyini[i].val); + + if (sc->sc_rfrev == ZYD_RF_AL2230S || sc->sc_al2230s != 0) { + for (i = 0; i < N(phy2230s); i++) + zyd_write16_m(sc, phy2230s[i].reg, phy2230s[i].val); + } + + for (i = 0; i < 3; i++) { + error = zyd_rfwrite_cr(sc, zyd_al2230_chtable[0][i]); + if (error != 0) + return (error); + } + + for (i = 0; i < N(rfini_part1); i++) { + error = zyd_rfwrite_cr(sc, rfini_part1[i]); + if (error != 0) + return (error); + } + + if (sc->sc_rfrev == ZYD_RF_AL2230S || sc->sc_al2230s != 0) + error = zyd_rfwrite(sc, 0x241000); + else + error = zyd_rfwrite(sc, 0x25a000); + if (error != 0) + goto fail; + + for (i = 0; i < N(rfini_part2); i++) { + error = zyd_rfwrite_cr(sc, rfini_part2[i]); + if (error != 0) + return (error); + } + + for (i = 0; i < N(phy2); i++) + zyd_write16_m(sc, phy2[i].reg, phy2[i].val); + + for (i = 0; i < N(rfini_part3); i++) { + error = zyd_rfwrite_cr(sc, rfini_part3[i]); + if (error != 0) + return (error); + } + + for (i = 0; i < N(phy3); i++) + zyd_write16_m(sc, phy3[i].reg, phy3[i].val); + + error = zyd_al2230_fini(rf); +fail: + return (error); +#undef N +} + +static int +zyd_al2230_switch_radio(struct zyd_rf *rf, int on) +{ + struct zyd_softc *sc = rf->rf_sc; + int error, on251 = (sc->sc_macrev == ZYD_ZD1211) ? 0x3f : 0x7f; + + zyd_write16_m(sc, ZYD_CR11, on ? 0x00 : 0x04); + zyd_write16_m(sc, ZYD_CR251, on ? on251 : 0x2f); +fail: + return (error); +} + +static int +zyd_al2230_set_channel(struct zyd_rf *rf, uint8_t chan) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + int error, i; + struct zyd_softc *sc = rf->rf_sc; + static const struct zyd_phy_pair phy1[] = { + { ZYD_CR138, 0x28 }, { ZYD_CR203, 0x06 }, + }; + static const struct { + uint32_t r1, r2, r3; + } rfprog[] = ZYD_AL2230_CHANTABLE; + + error = zyd_rfwrite(sc, rfprog[chan - 1].r1); + if (error != 0) + goto fail; + error = zyd_rfwrite(sc, rfprog[chan - 1].r2); + if (error != 0) + goto fail; + error = zyd_rfwrite(sc, rfprog[chan - 1].r3); + if (error != 0) + goto fail; + + for (i = 0; i < N(phy1); i++) + zyd_write16_m(sc, phy1[i].reg, phy1[i].val); +fail: + return (error); +#undef N +} + +static int +zyd_al2230_set_channel_b(struct zyd_rf *rf, uint8_t chan) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + int error, i; + struct zyd_softc *sc = rf->rf_sc; + static const struct zyd_phy_pair phy1[] = ZYD_AL2230_PHY_PART1; + static const struct { + uint32_t r1, r2, r3; + } rfprog[] = ZYD_AL2230_CHANTABLE_B; + + for (i = 0; i < N(phy1); i++) + zyd_write16_m(sc, phy1[i].reg, phy1[i].val); + + error = zyd_rfwrite_cr(sc, rfprog[chan - 1].r1); + if (error != 0) + goto fail; + error = zyd_rfwrite_cr(sc, rfprog[chan - 1].r2); + if (error != 0) + goto fail; + error = zyd_rfwrite_cr(sc, rfprog[chan - 1].r3); + if (error != 0) + goto fail; + error = zyd_al2230_fini(rf); +fail: + return (error); +#undef N +} + +#define ZYD_AL2230_PHY_BANDEDGE6 \ +{ \ + { ZYD_CR128, 0x14 }, { ZYD_CR129, 0x12 }, { ZYD_CR130, 0x10 }, \ + { ZYD_CR47, 0x1e } \ +} + +static int +zyd_al2230_bandedge6(struct zyd_rf *rf, struct ieee80211_channel *c) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + int error = 0, i; + struct zyd_softc *sc = rf->rf_sc; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct zyd_phy_pair r[] = ZYD_AL2230_PHY_BANDEDGE6; + u_int chan = ieee80211_chan2ieee(ic, c); + + if (chan == 1 || chan == 11) + r[0].val = 0x12; + + for (i = 0; i < N(r); i++) + zyd_write16_m(sc, r[i].reg, r[i].val); +fail: + return (error); +#undef N +} + +/* + * AL7230B RF methods. + */ +static int +zyd_al7230B_init(struct zyd_rf *rf) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + struct zyd_softc *sc = rf->rf_sc; + static const struct zyd_phy_pair phyini_1[] = ZYD_AL7230B_PHY_1; + static const struct zyd_phy_pair phyini_2[] = ZYD_AL7230B_PHY_2; + static const struct zyd_phy_pair phyini_3[] = ZYD_AL7230B_PHY_3; + static const uint32_t rfini_1[] = ZYD_AL7230B_RF_1; + static const uint32_t rfini_2[] = ZYD_AL7230B_RF_2; + int i, error; + + /* for AL7230B, PHY and RF need to be initialized in "phases" */ + + /* init RF-dependent PHY registers, part one */ + for (i = 0; i < N(phyini_1); i++) + zyd_write16_m(sc, phyini_1[i].reg, phyini_1[i].val); + + /* init AL7230B radio, part one */ + for (i = 0; i < N(rfini_1); i++) { + if ((error = zyd_rfwrite(sc, rfini_1[i])) != 0) + return (error); + } + /* init RF-dependent PHY registers, part two */ + for (i = 0; i < N(phyini_2); i++) + zyd_write16_m(sc, phyini_2[i].reg, phyini_2[i].val); + + /* init AL7230B radio, part two */ + for (i = 0; i < N(rfini_2); i++) { + if ((error = zyd_rfwrite(sc, rfini_2[i])) != 0) + return (error); + } + /* init RF-dependent PHY registers, part three */ + for (i = 0; i < N(phyini_3); i++) + zyd_write16_m(sc, phyini_3[i].reg, phyini_3[i].val); +fail: + return (error); +#undef N +} + +static int +zyd_al7230B_switch_radio(struct zyd_rf *rf, int on) +{ + int error; + struct zyd_softc *sc = rf->rf_sc; + + zyd_write16_m(sc, ZYD_CR11, on ? 0x00 : 0x04); + zyd_write16_m(sc, ZYD_CR251, on ? 0x3f : 0x2f); +fail: + return (error); +} + +static int +zyd_al7230B_set_channel(struct zyd_rf *rf, uint8_t chan) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + struct zyd_softc *sc = rf->rf_sc; + static const struct { + uint32_t r1, r2; + } rfprog[] = ZYD_AL7230B_CHANTABLE; + static const uint32_t rfsc[] = ZYD_AL7230B_RF_SETCHANNEL; + int i, error; + + zyd_write16_m(sc, ZYD_CR240, 0x57); + zyd_write16_m(sc, ZYD_CR251, 0x2f); + + for (i = 0; i < N(rfsc); i++) { + if ((error = zyd_rfwrite(sc, rfsc[i])) != 0) + return (error); + } + + zyd_write16_m(sc, ZYD_CR128, 0x14); + zyd_write16_m(sc, ZYD_CR129, 0x12); + zyd_write16_m(sc, ZYD_CR130, 0x10); + zyd_write16_m(sc, ZYD_CR38, 0x38); + zyd_write16_m(sc, ZYD_CR136, 0xdf); + + error = zyd_rfwrite(sc, rfprog[chan - 1].r1); + if (error != 0) + goto fail; + error = zyd_rfwrite(sc, rfprog[chan - 1].r2); + if (error != 0) + goto fail; + error = zyd_rfwrite(sc, 0x3c9000); + if (error != 0) + goto fail; + + zyd_write16_m(sc, ZYD_CR251, 0x3f); + zyd_write16_m(sc, ZYD_CR203, 0x06); + zyd_write16_m(sc, ZYD_CR240, 0x08); +fail: + return (error); +#undef N +} + +/* + * AL2210 RF methods. + */ +static int +zyd_al2210_init(struct zyd_rf *rf) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + struct zyd_softc *sc = rf->rf_sc; + static const struct zyd_phy_pair phyini[] = ZYD_AL2210_PHY; + static const uint32_t rfini[] = ZYD_AL2210_RF; + uint32_t tmp; + int i, error; + + zyd_write32_m(sc, ZYD_CR18, 2); + + /* init RF-dependent PHY registers */ + for (i = 0; i < N(phyini); i++) + zyd_write16_m(sc, phyini[i].reg, phyini[i].val); + + /* init AL2210 radio */ + for (i = 0; i < N(rfini); i++) { + if ((error = zyd_rfwrite(sc, rfini[i])) != 0) + return (error); + } + zyd_write16_m(sc, ZYD_CR47, 0x1e); + zyd_read32_m(sc, ZYD_CR_RADIO_PD, &tmp); + zyd_write32_m(sc, ZYD_CR_RADIO_PD, tmp & ~1); + zyd_write32_m(sc, ZYD_CR_RADIO_PD, tmp | 1); + zyd_write32_m(sc, ZYD_CR_RFCFG, 0x05); + zyd_write32_m(sc, ZYD_CR_RFCFG, 0x00); + zyd_write16_m(sc, ZYD_CR47, 0x1e); + zyd_write32_m(sc, ZYD_CR18, 3); +fail: + return (error); +#undef N +} + +static int +zyd_al2210_switch_radio(struct zyd_rf *rf, int on) +{ + /* vendor driver does nothing for this RF chip */ + + return (0); +} + +static int +zyd_al2210_set_channel(struct zyd_rf *rf, uint8_t chan) +{ + int error; + struct zyd_softc *sc = rf->rf_sc; + static const uint32_t rfprog[] = ZYD_AL2210_CHANTABLE; + uint32_t tmp; + + zyd_write32_m(sc, ZYD_CR18, 2); + zyd_write16_m(sc, ZYD_CR47, 0x1e); + zyd_read32_m(sc, ZYD_CR_RADIO_PD, &tmp); + zyd_write32_m(sc, ZYD_CR_RADIO_PD, tmp & ~1); + zyd_write32_m(sc, ZYD_CR_RADIO_PD, tmp | 1); + zyd_write32_m(sc, ZYD_CR_RFCFG, 0x05); + zyd_write32_m(sc, ZYD_CR_RFCFG, 0x00); + zyd_write16_m(sc, ZYD_CR47, 0x1e); + + /* actually set the channel */ + error = zyd_rfwrite(sc, rfprog[chan - 1]); + if (error != 0) + goto fail; + + zyd_write32_m(sc, ZYD_CR18, 3); +fail: + return (error); +} + +/* + * GCT RF methods. + */ +static int +zyd_gct_init(struct zyd_rf *rf) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + struct zyd_softc *sc = rf->rf_sc; + static const struct zyd_phy_pair phyini[] = ZYD_GCT_PHY; + static const uint32_t rfini[] = ZYD_GCT_RF; + int i, error; + + /* init RF-dependent PHY registers */ + for (i = 0; i < N(phyini); i++) + zyd_write16_m(sc, phyini[i].reg, phyini[i].val); + + /* init cgt radio */ + for (i = 0; i < N(rfini); i++) { + if ((error = zyd_rfwrite(sc, rfini[i])) != 0) + return (error); + } +fail: + return (error); +#undef N +} + +static int +zyd_gct_switch_radio(struct zyd_rf *rf, int on) +{ + /* vendor driver does nothing for this RF chip */ + + return (0); +} + +static int +zyd_gct_set_channel(struct zyd_rf *rf, uint8_t chan) +{ + int error; + struct zyd_softc *sc = rf->rf_sc; + static const uint32_t rfprog[] = ZYD_GCT_CHANTABLE; + + error = zyd_rfwrite(sc, 0x1c0000); + if (error != 0) + goto fail; + error = zyd_rfwrite(sc, rfprog[chan - 1]); + if (error != 0) + goto fail; + error = zyd_rfwrite(sc, 0x1c0008); +fail: + return (error); +} + +/* + * Maxim RF methods. + */ +static int +zyd_maxim_init(struct zyd_rf *rf) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + struct zyd_softc *sc = rf->rf_sc; + static const struct zyd_phy_pair phyini[] = ZYD_MAXIM_PHY; + static const uint32_t rfini[] = ZYD_MAXIM_RF; + uint16_t tmp; + int i, error; + + /* init RF-dependent PHY registers */ + for (i = 0; i < N(phyini); i++) + zyd_write16_m(sc, phyini[i].reg, phyini[i].val); + + zyd_read16_m(sc, ZYD_CR203, &tmp); + zyd_write16_m(sc, ZYD_CR203, tmp & ~(1 << 4)); + + /* init maxim radio */ + for (i = 0; i < N(rfini); i++) { + if ((error = zyd_rfwrite(sc, rfini[i])) != 0) + return (error); + } + zyd_read16_m(sc, ZYD_CR203, &tmp); + zyd_write16_m(sc, ZYD_CR203, tmp | (1 << 4)); +fail: + return (error); +#undef N +} + +static int +zyd_maxim_switch_radio(struct zyd_rf *rf, int on) +{ + + /* vendor driver does nothing for this RF chip */ + return (0); +} + +static int +zyd_maxim_set_channel(struct zyd_rf *rf, uint8_t chan) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + struct zyd_softc *sc = rf->rf_sc; + static const struct zyd_phy_pair phyini[] = ZYD_MAXIM_PHY; + static const uint32_t rfini[] = ZYD_MAXIM_RF; + static const struct { + uint32_t r1, r2; + } rfprog[] = ZYD_MAXIM_CHANTABLE; + uint16_t tmp; + int i, error; + + /* + * Do the same as we do when initializing it, except for the channel + * values coming from the two channel tables. + */ + + /* init RF-dependent PHY registers */ + for (i = 0; i < N(phyini); i++) + zyd_write16_m(sc, phyini[i].reg, phyini[i].val); + + zyd_read16_m(sc, ZYD_CR203, &tmp); + zyd_write16_m(sc, ZYD_CR203, tmp & ~(1 << 4)); + + /* first two values taken from the chantables */ + error = zyd_rfwrite(sc, rfprog[chan - 1].r1); + if (error != 0) + goto fail; + error = zyd_rfwrite(sc, rfprog[chan - 1].r2); + if (error != 0) + goto fail; + + /* init maxim radio - skipping the two first values */ + for (i = 2; i < N(rfini); i++) { + if ((error = zyd_rfwrite(sc, rfini[i])) != 0) + return (error); + } + zyd_read16_m(sc, ZYD_CR203, &tmp); + zyd_write16_m(sc, ZYD_CR203, tmp | (1 << 4)); +fail: + return (error); +#undef N +} + +/* + * Maxim2 RF methods. + */ +static int +zyd_maxim2_init(struct zyd_rf *rf) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + struct zyd_softc *sc = rf->rf_sc; + static const struct zyd_phy_pair phyini[] = ZYD_MAXIM2_PHY; + static const uint32_t rfini[] = ZYD_MAXIM2_RF; + uint16_t tmp; + int i, error; + + /* init RF-dependent PHY registers */ + for (i = 0; i < N(phyini); i++) + zyd_write16_m(sc, phyini[i].reg, phyini[i].val); + + zyd_read16_m(sc, ZYD_CR203, &tmp); + zyd_write16_m(sc, ZYD_CR203, tmp & ~(1 << 4)); + + /* init maxim2 radio */ + for (i = 0; i < N(rfini); i++) { + if ((error = zyd_rfwrite(sc, rfini[i])) != 0) + return (error); + } + zyd_read16_m(sc, ZYD_CR203, &tmp); + zyd_write16_m(sc, ZYD_CR203, tmp | (1 << 4)); +fail: + return (error); +#undef N +} + +static int +zyd_maxim2_switch_radio(struct zyd_rf *rf, int on) +{ + + /* vendor driver does nothing for this RF chip */ + return (0); +} + +static int +zyd_maxim2_set_channel(struct zyd_rf *rf, uint8_t chan) +{ +#define N(a) (sizeof(a) / sizeof((a)[0])) + struct zyd_softc *sc = rf->rf_sc; + static const struct zyd_phy_pair phyini[] = ZYD_MAXIM2_PHY; + static const uint32_t rfini[] = ZYD_MAXIM2_RF; + static const struct { + uint32_t r1, r2; + } rfprog[] = ZYD_MAXIM2_CHANTABLE; + uint16_t tmp; + int i, error; + + /* + * Do the same as we do when initializing it, except for the channel + * values coming from the two channel tables. + */ + + /* init RF-dependent PHY registers */ + for (i = 0; i < N(phyini); i++) + zyd_write16_m(sc, phyini[i].reg, phyini[i].val); + + zyd_read16_m(sc, ZYD_CR203, &tmp); + zyd_write16_m(sc, ZYD_CR203, tmp & ~(1 << 4)); + + /* first two values taken from the chantables */ + error = zyd_rfwrite(sc, rfprog[chan - 1].r1); + if (error != 0) + goto fail; + error = zyd_rfwrite(sc, rfprog[chan - 1].r2); + if (error != 0) + goto fail; + + /* init maxim2 radio - skipping the two first values */ + for (i = 2; i < N(rfini); i++) { + if ((error = zyd_rfwrite(sc, rfini[i])) != 0) + return (error); + } + zyd_read16_m(sc, ZYD_CR203, &tmp); + zyd_write16_m(sc, ZYD_CR203, tmp | (1 << 4)); +fail: + return (error); +#undef N +} + +static int +zyd_rf_attach(struct zyd_softc *sc, uint8_t type) +{ + struct zyd_rf *rf = &sc->sc_rf; + + rf->rf_sc = sc; + + switch (type) { + case ZYD_RF_RFMD: + rf->init = zyd_rfmd_init; + rf->switch_radio = zyd_rfmd_switch_radio; + rf->set_channel = zyd_rfmd_set_channel; + rf->width = 24; /* 24-bit RF values */ + break; + case ZYD_RF_AL2230: + case ZYD_RF_AL2230S: + if (sc->sc_macrev == ZYD_ZD1211B) { + rf->init = zyd_al2230_init_b; + rf->set_channel = zyd_al2230_set_channel_b; + } else { + rf->init = zyd_al2230_init; + rf->set_channel = zyd_al2230_set_channel; + } + rf->switch_radio = zyd_al2230_switch_radio; + rf->bandedge6 = zyd_al2230_bandedge6; + rf->width = 24; /* 24-bit RF values */ + break; + case ZYD_RF_AL7230B: + rf->init = zyd_al7230B_init; + rf->switch_radio = zyd_al7230B_switch_radio; + rf->set_channel = zyd_al7230B_set_channel; + rf->width = 24; /* 24-bit RF values */ + break; + case ZYD_RF_AL2210: + rf->init = zyd_al2210_init; + rf->switch_radio = zyd_al2210_switch_radio; + rf->set_channel = zyd_al2210_set_channel; + rf->width = 24; /* 24-bit RF values */ + break; + case ZYD_RF_GCT: + rf->init = zyd_gct_init; + rf->switch_radio = zyd_gct_switch_radio; + rf->set_channel = zyd_gct_set_channel; + rf->width = 21; /* 21-bit RF values */ + break; + case ZYD_RF_MAXIM_NEW: + rf->init = zyd_maxim_init; + rf->switch_radio = zyd_maxim_switch_radio; + rf->set_channel = zyd_maxim_set_channel; + rf->width = 18; /* 18-bit RF values */ + break; + case ZYD_RF_MAXIM_NEW2: + rf->init = zyd_maxim2_init; + rf->switch_radio = zyd_maxim2_switch_radio; + rf->set_channel = zyd_maxim2_set_channel; + rf->width = 18; /* 18-bit RF values */ + break; + default: + device_printf(sc->sc_dev, + "sorry, radio \"%s\" is not supported yet\n", + zyd_rf_name(type)); + return (EINVAL); + } + return (0); +} + +static const char * +zyd_rf_name(uint8_t type) +{ + static const char * const zyd_rfs[] = { + "unknown", "unknown", "UW2451", "UCHIP", "AL2230", + "AL7230B", "THETA", "AL2210", "MAXIM_NEW", "GCT", + "AL2230S", "RALINK", "INTERSIL", "RFMD", "MAXIM_NEW2", + "PHILIPS" + }; + + return zyd_rfs[(type > 15) ? 0 : type]; +} + +static int +zyd_hw_init(struct zyd_softc *sc) +{ + int error; + const struct zyd_phy_pair *phyp; + struct zyd_rf *rf = &sc->sc_rf; + uint16_t val; + + /* specify that the plug and play is finished */ + zyd_write32_m(sc, ZYD_MAC_AFTER_PNP, 1); + zyd_read16_m(sc, ZYD_FIRMWARE_BASE_ADDR, &sc->sc_fwbase); + DPRINTF(sc, ZYD_DEBUG_FW, "firmware base address=0x%04x\n", + sc->sc_fwbase); + + /* retrieve firmware revision number */ + zyd_read16_m(sc, sc->sc_fwbase + ZYD_FW_FIRMWARE_REV, &sc->sc_fwrev); + zyd_write32_m(sc, ZYD_CR_GPI_EN, 0); + zyd_write32_m(sc, ZYD_MAC_CONT_WIN_LIMIT, 0x7f043f); + /* set mandatory rates - XXX assumes 802.11b/g */ + zyd_write32_m(sc, ZYD_MAC_MAN_RATE, 0x150f); + + /* disable interrupts */ + zyd_write32_m(sc, ZYD_CR_INTERRUPT, 0); + + if ((error = zyd_read_pod(sc)) != 0) { + device_printf(sc->sc_dev, "could not read EEPROM\n"); + goto fail; + } + + /* PHY init (resetting) */ + error = zyd_lock_phy(sc); + if (error != 0) + goto fail; + phyp = (sc->sc_macrev == ZYD_ZD1211B) ? zyd_def_phyB : zyd_def_phy; + for (; phyp->reg != 0; phyp++) + zyd_write16_m(sc, phyp->reg, phyp->val); + if (sc->sc_macrev == ZYD_ZD1211 && sc->sc_fix_cr157 != 0) { + zyd_read16_m(sc, ZYD_EEPROM_PHY_REG, &val); + zyd_write32_m(sc, ZYD_CR157, val >> 8); + } + error = zyd_unlock_phy(sc); + if (error != 0) + goto fail; + + /* HMAC init */ + zyd_write32_m(sc, ZYD_MAC_ACK_EXT, 0x00000020); + zyd_write32_m(sc, ZYD_CR_ADDA_MBIAS_WT, 0x30000808); + zyd_write32_m(sc, ZYD_MAC_SNIFFER, 0x00000000); + zyd_write32_m(sc, ZYD_MAC_RXFILTER, 0x00000000); + zyd_write32_m(sc, ZYD_MAC_GHTBL, 0x00000000); + zyd_write32_m(sc, ZYD_MAC_GHTBH, 0x80000000); + zyd_write32_m(sc, ZYD_MAC_MISC, 0x000000a4); + zyd_write32_m(sc, ZYD_CR_ADDA_PWR_DWN, 0x0000007f); + zyd_write32_m(sc, ZYD_MAC_BCNCFG, 0x00f00401); + zyd_write32_m(sc, ZYD_MAC_PHY_DELAY2, 0x00000000); + zyd_write32_m(sc, ZYD_MAC_ACK_EXT, 0x00000080); + zyd_write32_m(sc, ZYD_CR_ADDA_PWR_DWN, 0x00000000); + zyd_write32_m(sc, ZYD_MAC_SIFS_ACK_TIME, 0x00000100); + zyd_write32_m(sc, ZYD_CR_RX_PE_DELAY, 0x00000070); + zyd_write32_m(sc, ZYD_CR_PS_CTRL, 0x10000000); + zyd_write32_m(sc, ZYD_MAC_RTSCTSRATE, 0x02030203); + zyd_write32_m(sc, ZYD_MAC_AFTER_PNP, 1); + zyd_write32_m(sc, ZYD_MAC_BACKOFF_PROTECT, 0x00000114); + zyd_write32_m(sc, ZYD_MAC_DIFS_EIFS_SIFS, 0x0a47c032); + zyd_write32_m(sc, ZYD_MAC_CAM_MODE, 0x3); + + if (sc->sc_macrev == ZYD_ZD1211) { + zyd_write32_m(sc, ZYD_MAC_RETRY, 0x00000002); + zyd_write32_m(sc, ZYD_MAC_RX_THRESHOLD, 0x000c0640); + } else { + zyd_write32_m(sc, ZYD_MACB_MAX_RETRY, 0x02020202); + zyd_write32_m(sc, ZYD_MACB_TXPWR_CTL4, 0x007f003f); + zyd_write32_m(sc, ZYD_MACB_TXPWR_CTL3, 0x007f003f); + zyd_write32_m(sc, ZYD_MACB_TXPWR_CTL2, 0x003f001f); + zyd_write32_m(sc, ZYD_MACB_TXPWR_CTL1, 0x001f000f); + zyd_write32_m(sc, ZYD_MACB_AIFS_CTL1, 0x00280028); + zyd_write32_m(sc, ZYD_MACB_AIFS_CTL2, 0x008C003C); + zyd_write32_m(sc, ZYD_MACB_TXOP, 0x01800824); + zyd_write32_m(sc, ZYD_MAC_RX_THRESHOLD, 0x000c0eff); + } + + /* init beacon interval to 100ms */ + if ((error = zyd_set_beacon_interval(sc, 100)) != 0) + goto fail; + + if ((error = zyd_rf_attach(sc, sc->sc_rfrev)) != 0) { + device_printf(sc->sc_dev, "could not attach RF, rev 0x%x\n", + sc->sc_rfrev); + goto fail; + } + + /* RF chip init */ + error = zyd_lock_phy(sc); + if (error != 0) + goto fail; + error = (*rf->init)(rf); + if (error != 0) { + device_printf(sc->sc_dev, + "radio initialization failed, error %d\n", error); + goto fail; + } + error = zyd_unlock_phy(sc); + if (error != 0) + goto fail; + + if ((error = zyd_read_eeprom(sc)) != 0) { + device_printf(sc->sc_dev, "could not read EEPROM\n"); + goto fail; + } + +fail: return (error); +} + +static int +zyd_read_pod(struct zyd_softc *sc) +{ + int error; + uint32_t tmp; + + zyd_read32_m(sc, ZYD_EEPROM_POD, &tmp); + sc->sc_rfrev = tmp & 0x0f; + sc->sc_ledtype = (tmp >> 4) & 0x01; + sc->sc_al2230s = (tmp >> 7) & 0x01; + sc->sc_cckgain = (tmp >> 8) & 0x01; + sc->sc_fix_cr157 = (tmp >> 13) & 0x01; + sc->sc_parev = (tmp >> 16) & 0x0f; + sc->sc_bandedge6 = (tmp >> 21) & 0x01; + sc->sc_newphy = (tmp >> 31) & 0x01; + sc->sc_txled = ((tmp & (1 << 24)) && (tmp & (1 << 29))) ? 0 : 1; +fail: + return (error); +} + +static int +zyd_read_eeprom(struct zyd_softc *sc) +{ + uint16_t val; + int error, i; + + /* read Tx power calibration tables */ + for (i = 0; i < 7; i++) { + zyd_read16_m(sc, ZYD_EEPROM_PWR_CAL + i, &val); + sc->sc_pwrcal[i * 2] = val >> 8; + sc->sc_pwrcal[i * 2 + 1] = val & 0xff; + zyd_read16_m(sc, ZYD_EEPROM_PWR_INT + i, &val); + sc->sc_pwrint[i * 2] = val >> 8; + sc->sc_pwrint[i * 2 + 1] = val & 0xff; + zyd_read16_m(sc, ZYD_EEPROM_36M_CAL + i, &val); + sc->sc_ofdm36_cal[i * 2] = val >> 8; + sc->sc_ofdm36_cal[i * 2 + 1] = val & 0xff; + zyd_read16_m(sc, ZYD_EEPROM_48M_CAL + i, &val); + sc->sc_ofdm48_cal[i * 2] = val >> 8; + sc->sc_ofdm48_cal[i * 2 + 1] = val & 0xff; + zyd_read16_m(sc, ZYD_EEPROM_54M_CAL + i, &val); + sc->sc_ofdm54_cal[i * 2] = val >> 8; + sc->sc_ofdm54_cal[i * 2 + 1] = val & 0xff; + } +fail: + return (error); +} + +static int +zyd_get_macaddr(struct zyd_softc *sc) +{ + usb_device_request_t req; + usbd_status error; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = ZYD_READFWDATAREQ; + USETW(req.wValue, ZYD_EEPROM_MAC_ADDR_P1); + USETW(req.wIndex, 0); + USETW(req.wLength, IEEE80211_ADDR_LEN); + + error = usbd_do_request(sc->sc_udev, &req, sc->sc_bssid); + if (error != 0) { + device_printf(sc->sc_dev, "could not read EEPROM: %s\n", + usbd_errstr(error)); + } + + return (error); +} + +static int +zyd_set_macaddr(struct zyd_softc *sc, const uint8_t *addr) +{ + int error; + uint32_t tmp; + + tmp = addr[3] << 24 | addr[2] << 16 | addr[1] << 8 | addr[0]; + zyd_write32_m(sc, ZYD_MAC_MACADRL, tmp); + tmp = addr[5] << 8 | addr[4]; + zyd_write32_m(sc, ZYD_MAC_MACADRH, tmp); +fail: + return (error); +} + +static int +zyd_set_bssid(struct zyd_softc *sc, const uint8_t *addr) +{ + int error; + uint32_t tmp; + + tmp = addr[3] << 24 | addr[2] << 16 | addr[1] << 8 | addr[0]; + zyd_write32_m(sc, ZYD_MAC_BSSADRL, tmp); + tmp = addr[5] << 8 | addr[4]; + zyd_write32_m(sc, ZYD_MAC_BSSADRH, tmp); +fail: + return (error); +} + +static int +zyd_switch_radio(struct zyd_softc *sc, int on) +{ + struct zyd_rf *rf = &sc->sc_rf; + int error; + + error = zyd_lock_phy(sc); + if (error != 0) + goto fail; + error = (*rf->switch_radio)(rf, on); + if (error != 0) + goto fail; + error = zyd_unlock_phy(sc); +fail: + return (error); +} + +static int +zyd_set_led(struct zyd_softc *sc, int which, int on) +{ + int error; + uint32_t tmp; + + zyd_read32_m(sc, ZYD_MAC_TX_PE_CONTROL, &tmp); + tmp &= ~which; + if (on) + tmp |= which; + zyd_write32_m(sc, ZYD_MAC_TX_PE_CONTROL, tmp); +fail: + return (error); +} + +static void +zyd_set_multi(void *arg) +{ + int error; + struct zyd_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ifmultiaddr *ifma; + uint32_t low, high; + uint8_t v; + + if (!(ifp->if_flags & IFF_UP)) + return; + + low = 0x00000000; + high = 0x80000000; + + if (ic->ic_opmode == IEEE80211_M_MONITOR || + (ifp->if_flags & (IFF_ALLMULTI | IFF_PROMISC))) { + low = 0xffffffff; + high = 0xffffffff; + } else { + IF_ADDR_LOCK(ifp); + TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { + if (ifma->ifma_addr->sa_family != AF_LINK) + continue; + v = ((uint8_t *)LLADDR((struct sockaddr_dl *) + ifma->ifma_addr))[5] >> 2; + if (v < 32) + low |= 1 << v; + else + high |= 1 << (v - 32); + } + IF_ADDR_UNLOCK(ifp); + } + + /* reprogram multicast global hash table */ + zyd_write32_m(sc, ZYD_MAC_GHTBL, low); + zyd_write32_m(sc, ZYD_MAC_GHTBH, high); +fail: + if (error != 0) + device_printf(sc->sc_dev, + "could not set multicast hash table\n"); +} + +static void +zyd_update_mcast(struct ifnet *ifp) +{ + struct zyd_softc *sc = ifp->if_softc; + + if (!(sc->sc_flags & ZYD_FLAG_INITDONE)) + return; + + usb_add_task(sc->sc_udev, &sc->sc_mcasttask, USB_TASKQ_DRIVER); +} + +static int +zyd_set_rxfilter(struct zyd_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + uint32_t rxfilter; + + switch (ic->ic_opmode) { + case IEEE80211_M_STA: + rxfilter = ZYD_FILTER_BSS; + break; + case IEEE80211_M_IBSS: + case IEEE80211_M_HOSTAP: + rxfilter = ZYD_FILTER_HOSTAP; + break; + case IEEE80211_M_MONITOR: + rxfilter = ZYD_FILTER_MONITOR; + break; + default: + /* should not get there */ + return (EINVAL); + } + return zyd_write32(sc, ZYD_MAC_RXFILTER, rxfilter); +} + +static void +zyd_set_chan(struct zyd_softc *sc, struct ieee80211_channel *c) +{ + int error; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct zyd_rf *rf = &sc->sc_rf; + uint32_t tmp; + u_int chan; + + chan = ieee80211_chan2ieee(ic, c); + if (chan == 0 || chan == IEEE80211_CHAN_ANY) { + /* XXX should NEVER happen */ + device_printf(sc->sc_dev, + "%s: invalid channel %x\n", __func__, chan); + return; + } + + error = zyd_lock_phy(sc); + if (error != 0) + goto fail; + + error = (*rf->set_channel)(rf, chan); + if (error != 0) + goto fail; + + /* update Tx power */ + zyd_write16_m(sc, ZYD_CR31, sc->sc_pwrint[chan - 1]); + + if (sc->sc_macrev == ZYD_ZD1211B) { + zyd_write16_m(sc, ZYD_CR67, sc->sc_ofdm36_cal[chan - 1]); + zyd_write16_m(sc, ZYD_CR66, sc->sc_ofdm48_cal[chan - 1]); + zyd_write16_m(sc, ZYD_CR65, sc->sc_ofdm54_cal[chan - 1]); + zyd_write16_m(sc, ZYD_CR68, sc->sc_pwrcal[chan - 1]); + zyd_write16_m(sc, ZYD_CR69, 0x28); + zyd_write16_m(sc, ZYD_CR69, 0x2a); + } + if (sc->sc_cckgain) { + /* set CCK baseband gain from EEPROM */ + if (zyd_read32(sc, ZYD_EEPROM_PHY_REG, &tmp) == 0) + zyd_write16_m(sc, ZYD_CR47, tmp & 0xff); + } + if (sc->sc_bandedge6 && rf->bandedge6 != NULL) { + error = (*rf->bandedge6)(rf, c); + if (error != 0) + goto fail; + } + zyd_write32_m(sc, ZYD_CR_CONFIG_PHILIPS, 0); + + error = zyd_unlock_phy(sc); + if (error != 0) + goto fail; + + sc->sc_rxtap.wr_chan_freq = sc->sc_txtap.wt_chan_freq = + htole16(c->ic_freq); + sc->sc_rxtap.wr_chan_flags = sc->sc_txtap.wt_chan_flags = + htole16(c->ic_flags); +fail: + return; +} + +static int +zyd_set_beacon_interval(struct zyd_softc *sc, int bintval) +{ + int error; + uint32_t val; + + zyd_read32_m(sc, ZYD_CR_ATIM_WND_PERIOD, &val); + sc->sc_atim_wnd = val; + zyd_read32_m(sc, ZYD_CR_PRE_TBTT, &val); + sc->sc_pre_tbtt = val; + sc->sc_bcn_int = bintval; + + if (sc->sc_bcn_int <= 5) + sc->sc_bcn_int = 5; + if (sc->sc_pre_tbtt < 4 || sc->sc_pre_tbtt >= sc->sc_bcn_int) + sc->sc_pre_tbtt = sc->sc_bcn_int - 1; + if (sc->sc_atim_wnd >= sc->sc_pre_tbtt) + sc->sc_atim_wnd = sc->sc_pre_tbtt - 1; + + zyd_write32_m(sc, ZYD_CR_ATIM_WND_PERIOD, sc->sc_atim_wnd); + zyd_write32_m(sc, ZYD_CR_PRE_TBTT, sc->sc_pre_tbtt); + zyd_write32_m(sc, ZYD_CR_BCN_INTERVAL, sc->sc_bcn_int); +fail: + return (error); +} + +static void +zyd_intr(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + struct zyd_softc *sc = (struct zyd_softc *)priv; + struct zyd_cmd *cmd; + uint32_t datalen; + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) + return; + + if (status == USBD_STALLED) { + usbd_clear_endpoint_stall_async( + sc->sc_ep[ZYD_ENDPT_IIN]); + } + return; + } + + cmd = (struct zyd_cmd *)sc->sc_ibuf; + + if (le16toh(cmd->code) == ZYD_NOTIF_RETRYSTATUS) { + struct zyd_notif_retry *retry = + (struct zyd_notif_retry *)cmd->data; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps); + struct ieee80211_node *ni; + + DPRINTF(sc, ZYD_DEBUG_TX_PROC, + "retry intr: rate=0x%x addr=%s count=%d (0x%x)\n", + le16toh(retry->rate), ether_sprintf(retry->macaddr), + le16toh(retry->count) & 0xff, le16toh(retry->count)); + + /* + * Find the node to which the packet was sent and update its + * retry statistics. In BSS mode, this node is the AP we're + * associated to so no lookup is actually needed. + */ + ni = ieee80211_find_txnode(vap, retry->macaddr); + if (ni != NULL) { + ieee80211_amrr_tx_complete(&ZYD_NODE(ni)->amn, + IEEE80211_AMRR_FAILURE, 1); + ieee80211_free_node(ni); + } + if (le16toh(retry->count) & 0x100) + ifp->if_oerrors++; /* too many retries */ + } else if (le16toh(cmd->code) == ZYD_NOTIF_IORD) { + struct zyd_rq *rqp; + + if (le16toh(*(uint16_t *)cmd->data) == ZYD_CR_INTERRUPT) + return; /* HMAC interrupt */ + + usbd_get_xfer_status(xfer, NULL, NULL, &datalen, NULL); + datalen -= sizeof(cmd->code); + datalen -= 2; /* XXX: padding? */ + + STAILQ_FOREACH(rqp, &sc->sc_rqh, rq) { + int i; + + if (sizeof(struct zyd_pair) * rqp->len != datalen) + continue; + for (i = 0; i < rqp->len; i++) { + if (*(((const uint16_t *)rqp->idata) + i) != + (((struct zyd_pair *)cmd->data) + i)->reg) + break; + } + if (i != rqp->len) + continue; + + /* copy answer into caller-supplied buffer */ + bcopy(cmd->data, rqp->odata, + sizeof(struct zyd_pair) * rqp->len); + wakeup(rqp->odata); /* wakeup caller */ + + return; + } + return; /* unexpected IORD notification */ + } else { + device_printf(sc->sc_dev, "unknown notification %x\n", + le16toh(cmd->code)); + } +} + +static void +zyd_rx_data(struct zyd_softc *sc, const uint8_t *buf, uint16_t len) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211_node *ni; + const struct zyd_plcphdr *plcp; + const struct zyd_rx_stat *stat; + struct mbuf *m; + int rlen, rssi, nf; + + if (len < ZYD_MIN_FRAGSZ) { + DPRINTF(sc, ZYD_DEBUG_RECV, "%s: frame too short (length=%d)\n", + device_get_nameunit(sc->sc_dev), len); + ifp->if_ierrors++; + return; + } + + plcp = (const struct zyd_plcphdr *)buf; + stat = (const struct zyd_rx_stat *) + (buf + len - sizeof(struct zyd_rx_stat)); + + if (stat->flags & ZYD_RX_ERROR) { + DPRINTF(sc, ZYD_DEBUG_RECV, + "%s: RX status indicated error (%x)\n", + device_get_nameunit(sc->sc_dev), stat->flags); + ifp->if_ierrors++; + return; + } + + /* compute actual frame length */ + rlen = len - sizeof(struct zyd_plcphdr) - + sizeof(struct zyd_rx_stat) - IEEE80211_CRC_LEN; + + /* allocate a mbuf to store the frame */ + if (rlen > MHLEN) + m = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + else + m = m_gethdr(M_DONTWAIT, MT_DATA); + if (m == NULL) { + DPRINTF(sc, ZYD_DEBUG_RECV, "%s: could not allocate rx mbuf\n", + device_get_nameunit(sc->sc_dev)); + ifp->if_ierrors++; + return; + } + m->m_pkthdr.rcvif = ifp; + m->m_pkthdr.len = m->m_len = rlen; + bcopy((const uint8_t *)(plcp + 1), mtod(m, uint8_t *), rlen); + + if (bpf_peers_present(ifp->if_bpf)) { + struct zyd_rx_radiotap_header *tap = &sc->sc_rxtap; + + tap->wr_flags = 0; + if (stat->flags & (ZYD_RX_BADCRC16 | ZYD_RX_BADCRC32)) + tap->wr_flags |= IEEE80211_RADIOTAP_F_BADFCS; + /* XXX toss, no way to express errors */ + if (stat->flags & ZYD_RX_DECRYPTERR) + tap->wr_flags |= IEEE80211_RADIOTAP_F_BADFCS; + tap->wr_rate = ieee80211_plcp2rate(plcp->signal, + (stat->flags & ZYD_RX_OFDM) ? + IEEE80211_T_OFDM : IEEE80211_T_CCK); + tap->wr_antsignal = stat->rssi + -95; + tap->wr_antnoise = -95; /* XXX */ + + bpf_mtap2(ifp->if_bpf, tap, sc->sc_rxtap_len, m); + } + + rssi = stat->rssi > 63 ? 127 : 2 * stat->rssi; + nf = -95; /* XXX */ + + ni = ieee80211_find_rxnode(ic, mtod(m, struct ieee80211_frame_min *)); + if (ni != NULL) { + (void)ieee80211_input(ni, m, rssi, nf, 0); + ieee80211_free_node(ni); + } else + (void)ieee80211_input_all(ic, m, rssi, nf, 0); +} + +static void +zyd_rxeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + struct zyd_rx_data *data = priv; + struct zyd_softc *sc = data->sc; + struct ifnet *ifp = sc->sc_ifp; + const struct zyd_rx_desc *desc; + int len; + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) + return; + + if (status == USBD_STALLED) + usbd_clear_endpoint_stall(sc->sc_ep[ZYD_ENDPT_BIN]); + + goto skip; + } + usbd_get_xfer_status(xfer, NULL, NULL, &len, NULL); + + if (len < ZYD_MIN_RXBUFSZ) { + DPRINTF(sc, ZYD_DEBUG_RECV, "%s: xfer too short (length=%d)\n", + device_get_nameunit(sc->sc_dev), len); + ifp->if_ierrors++; /* XXX not really errors */ + goto skip; + } + + desc = (const struct zyd_rx_desc *) + (data->buf + len - sizeof(struct zyd_rx_desc)); + + if (UGETW(desc->tag) == ZYD_TAG_MULTIFRAME) { + const uint8_t *p = data->buf, *end = p + len; + int i; + + DPRINTF(sc, ZYD_DEBUG_RECV, + "%s: received multi-frame transfer\n", __func__); + + for (i = 0; i < ZYD_MAX_RXFRAMECNT; i++) { + const uint16_t len16 = UGETW(desc->len[i]); + + if (len16 == 0 || p + len16 > end) + break; + + zyd_rx_data(sc, p, len16); + /* next frame is aligned on a 32-bit boundary */ + p += (len16 + 3) & ~3; + } + } else { + DPRINTF(sc, ZYD_DEBUG_RECV, + "%s: received single-frame transfer\n", __func__); + + zyd_rx_data(sc, data->buf, len); + } + +skip: /* setup a new transfer */ + usbd_setup_xfer(xfer, sc->sc_ep[ZYD_ENDPT_BIN], data, NULL, + ZYX_MAX_RXBUFSZ, USBD_NO_COPY | USBD_SHORT_XFER_OK, + USBD_NO_TIMEOUT, zyd_rxeof); + (void)usbd_transfer(xfer); +} + +static uint8_t +zyd_plcp_signal(int rate) +{ + switch (rate) { + /* OFDM rates (cf IEEE Std 802.11a-1999, pp. 14 Table 80) */ + case 12: + return (0xb); + case 18: + return (0xf); + case 24: + return (0xa); + case 36: + return (0xe); + case 48: + return (0x9); + case 72: + return (0xd); + case 96: + return (0x8); + case 108: + return (0xc); + /* CCK rates (NB: not IEEE std, device-specific) */ + case 2: + return (0x0); + case 4: + return (0x1); + case 11: + return (0x2); + case 22: + return (0x3); + } + return (0xff); /* XXX unsupported/unknown rate */ +} + +static int +zyd_tx_mgt(struct zyd_softc *sc, struct mbuf *m0, struct ieee80211_node *ni) +{ + struct ieee80211vap *vap = ni->ni_vap; + struct ieee80211com *ic = ni->ni_ic; + struct ifnet *ifp = sc->sc_ifp; + struct zyd_tx_desc *desc; + struct zyd_tx_data *data; + struct ieee80211_frame *wh; + struct ieee80211_key *k; + int data_idx, rate, totlen, xferlen; + uint16_t pktlen; + usbd_status error; + + data_idx = sc->sc_txidx; + sc->sc_txidx = (sc->sc_txidx + 1) % ZYD_TX_LIST_CNT; + + data = &sc->sc_txdata[data_idx]; + desc = (struct zyd_tx_desc *)data->buf; + + rate = IEEE80211_IS_CHAN_5GHZ(ic->ic_curchan) ? 12 : 2; + + wh = mtod(m0, struct ieee80211_frame *); + + if (wh->i_fc[1] & IEEE80211_FC1_WEP) { + k = ieee80211_crypto_encap(ni, m0); + if (k == NULL) { + m_freem(m0); + return (ENOBUFS); + } + } + + data->ni = ni; + data->m = m0; + + wh = mtod(m0, struct ieee80211_frame *); + + xferlen = sizeof(struct zyd_tx_desc) + m0->m_pkthdr.len; + totlen = m0->m_pkthdr.len + IEEE80211_CRC_LEN; + + /* fill Tx descriptor */ + desc->len = htole16(totlen); + + desc->flags = ZYD_TX_FLAG_BACKOFF; + if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { + /* multicast frames are not sent at OFDM rates in 802.11b/g */ + if (totlen > vap->iv_rtsthreshold) { + desc->flags |= ZYD_TX_FLAG_RTS; + } else if (ZYD_RATE_IS_OFDM(rate) && + (ic->ic_flags & IEEE80211_F_USEPROT)) { + if (ic->ic_protmode == IEEE80211_PROT_CTSONLY) + desc->flags |= ZYD_TX_FLAG_CTS_TO_SELF; + else if (ic->ic_protmode == IEEE80211_PROT_RTSCTS) + desc->flags |= ZYD_TX_FLAG_RTS; + } + } else + desc->flags |= ZYD_TX_FLAG_MULTICAST; + + if ((wh->i_fc[0] & + (IEEE80211_FC0_TYPE_MASK | IEEE80211_FC0_SUBTYPE_MASK)) == + (IEEE80211_FC0_TYPE_CTL | IEEE80211_FC0_SUBTYPE_PS_POLL)) + desc->flags |= ZYD_TX_FLAG_TYPE(ZYD_TX_TYPE_PS_POLL); + + desc->phy = zyd_plcp_signal(rate); + if (ZYD_RATE_IS_OFDM(rate)) { + desc->phy |= ZYD_TX_PHY_OFDM; + if (IEEE80211_IS_CHAN_5GHZ(ic->ic_curchan)) + desc->phy |= ZYD_TX_PHY_5GHZ; + } else if (rate != 2 && (ic->ic_flags & IEEE80211_F_SHPREAMBLE)) + desc->phy |= ZYD_TX_PHY_SHPREAMBLE; + + /* actual transmit length (XXX why +10?) */ + pktlen = sizeof(struct zyd_tx_desc) + 10; + if (sc->sc_macrev == ZYD_ZD1211) + pktlen += totlen; + desc->pktlen = htole16(pktlen); + + desc->plcp_length = (16 * totlen + rate - 1) / rate; + desc->plcp_service = 0; + if (rate == 22) { + const int remainder = (16 * totlen) % 22; + if (remainder != 0 && remainder < 7) + desc->plcp_service |= ZYD_PLCP_LENGEXT; + } + + if (bpf_peers_present(ifp->if_bpf)) { + struct zyd_tx_radiotap_header *tap = &sc->sc_txtap; + + tap->wt_flags = 0; + tap->wt_rate = rate; + + bpf_mtap2(ifp->if_bpf, tap, sc->sc_txtap_len, m0); + } + + m_copydata(m0, 0, m0->m_pkthdr.len, + data->buf + sizeof(struct zyd_tx_desc)); + + DPRINTF(sc, ZYD_DEBUG_XMIT, + "%s: sending mgt frame len=%zu rate=%u xferlen=%u\n", + device_get_nameunit(sc->sc_dev), (size_t)m0->m_pkthdr.len, + rate, xferlen); + + usbd_setup_xfer(data->xfer, sc->sc_ep[ZYD_ENDPT_BOUT], data, + data->buf, xferlen, USBD_FORCE_SHORT_XFER | USBD_NO_COPY, + ZYD_TX_TIMEOUT, zyd_txeof); + error = usbd_transfer(data->xfer); + if (error != USBD_IN_PROGRESS && error != 0) { + ifp->if_oerrors++; + return (EIO); + } + sc->sc_txqueued++; + + return (0); +} + +static void +zyd_txeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + struct zyd_tx_data *data = priv; + struct zyd_softc *sc = data->sc; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211_node *ni; + struct mbuf *m; + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) + return; + + device_printf(sc->sc_dev, "could not transmit buffer: %s\n", + usbd_errstr(status)); + + if (status == USBD_STALLED) { + usbd_clear_endpoint_stall_async( + sc->sc_ep[ZYD_ENDPT_BOUT]); + } + ifp->if_oerrors++; + return; + } + + ni = data->ni; + /* update rate control statistics */ + ieee80211_amrr_tx_complete(&ZYD_NODE(ni)->amn, + IEEE80211_AMRR_SUCCESS, 0); + + /* + * Do any tx complete callback. Note this must + * be done before releasing the node reference. + */ + m = data->m; + if (m != NULL && m->m_flags & M_TXCB) { + ieee80211_process_callback(ni, m, 0); /* XXX status? */ + m_freem(m); + data->m = NULL; + } + + ieee80211_free_node(ni); + data->ni = NULL; + + ZYD_TX_LOCK(sc); + sc->sc_txqueued--; + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + ZYD_TX_UNLOCK(sc); + + ifp->if_opackets++; + sc->sc_txtimer = 0; + zyd_start(ifp); +} + +static int +zyd_tx_data(struct zyd_softc *sc, struct mbuf *m0, struct ieee80211_node *ni) +{ + struct ieee80211vap *vap = ni->ni_vap; + struct ieee80211com *ic = ni->ni_ic; + struct ifnet *ifp = sc->sc_ifp; + struct zyd_tx_desc *desc; + struct zyd_tx_data *data; + struct ieee80211_frame *wh; + const struct ieee80211_txparam *tp; + struct ieee80211_key *k; + int data_idx, rate, totlen, xferlen; + uint16_t pktlen; + usbd_status error; + + data_idx = sc->sc_txidx; + sc->sc_txidx = (sc->sc_txidx + 1) % ZYD_TX_LIST_CNT; + + wh = mtod(m0, struct ieee80211_frame *); + data = &sc->sc_txdata[data_idx]; + desc = (struct zyd_tx_desc *)data->buf; + + desc->flags = ZYD_TX_FLAG_BACKOFF; + tp = &vap->iv_txparms[ieee80211_chan2mode(ni->ni_chan)]; + if (IEEE80211_IS_MULTICAST(wh->i_addr1)) { + rate = tp->mcastrate; + desc->flags |= ZYD_TX_FLAG_MULTICAST; + } else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) { + rate = tp->ucastrate; + } else { + (void) ieee80211_amrr_choose(ni, &ZYD_NODE(ni)->amn); + rate = ni->ni_txrate; + } + + if (wh->i_fc[1] & IEEE80211_FC1_WEP) { + k = ieee80211_crypto_encap(ni, m0); + if (k == NULL) { + m_freem(m0); + return (ENOBUFS); + } + /* packet header may have moved, reset our local pointer */ + wh = mtod(m0, struct ieee80211_frame *); + } + + data->ni = ni; + data->m = NULL; + + xferlen = sizeof(struct zyd_tx_desc) + m0->m_pkthdr.len; + totlen = m0->m_pkthdr.len + IEEE80211_CRC_LEN; + + /* fill Tx descriptor */ + desc->len = htole16(totlen); + + if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) { + /* multicast frames are not sent at OFDM rates in 802.11b/g */ + if (totlen > vap->iv_rtsthreshold) { + desc->flags |= ZYD_TX_FLAG_RTS; + } else if (ZYD_RATE_IS_OFDM(rate) && + (ic->ic_flags & IEEE80211_F_USEPROT)) { + if (ic->ic_protmode == IEEE80211_PROT_CTSONLY) + desc->flags |= ZYD_TX_FLAG_CTS_TO_SELF; + else if (ic->ic_protmode == IEEE80211_PROT_RTSCTS) + desc->flags |= ZYD_TX_FLAG_RTS; + } + } + + if ((wh->i_fc[0] & + (IEEE80211_FC0_TYPE_MASK | IEEE80211_FC0_SUBTYPE_MASK)) == + (IEEE80211_FC0_TYPE_CTL | IEEE80211_FC0_SUBTYPE_PS_POLL)) + desc->flags |= ZYD_TX_FLAG_TYPE(ZYD_TX_TYPE_PS_POLL); + + desc->phy = zyd_plcp_signal(rate); + if (ZYD_RATE_IS_OFDM(rate)) { + desc->phy |= ZYD_TX_PHY_OFDM; + if (IEEE80211_IS_CHAN_5GHZ(ic->ic_curchan)) + desc->phy |= ZYD_TX_PHY_5GHZ; + } else if (rate != 2 && (ic->ic_flags & IEEE80211_F_SHPREAMBLE)) + desc->phy |= ZYD_TX_PHY_SHPREAMBLE; + + /* actual transmit length (XXX why +10?) */ + pktlen = sizeof(struct zyd_tx_desc) + 10; + if (sc->sc_macrev == ZYD_ZD1211) + pktlen += totlen; + desc->pktlen = htole16(pktlen); + + desc->plcp_length = (16 * totlen + rate - 1) / rate; + desc->plcp_service = 0; + if (rate == 22) { + const int remainder = (16 * totlen) % 22; + if (remainder != 0 && remainder < 7) + desc->plcp_service |= ZYD_PLCP_LENGEXT; + } + + if (bpf_peers_present(ifp->if_bpf)) { + struct zyd_tx_radiotap_header *tap = &sc->sc_txtap; + + tap->wt_flags = 0; + tap->wt_rate = rate; + tap->wt_chan_freq = htole16(ic->ic_curchan->ic_freq); + tap->wt_chan_flags = htole16(ic->ic_curchan->ic_flags); + + bpf_mtap2(ifp->if_bpf, tap, sc->sc_txtap_len, m0); + } + + m_copydata(m0, 0, m0->m_pkthdr.len, + data->buf + sizeof(struct zyd_tx_desc)); + + DPRINTF(sc, ZYD_DEBUG_XMIT, + "%s: sending data frame len=%zu rate=%u xferlen=%u\n", + device_get_nameunit(sc->sc_dev), (size_t)m0->m_pkthdr.len, + rate, xferlen); + + m_freem(m0); /* mbuf no longer needed */ + + usbd_setup_xfer(data->xfer, sc->sc_ep[ZYD_ENDPT_BOUT], data, + data->buf, xferlen, USBD_FORCE_SHORT_XFER | USBD_NO_COPY, + ZYD_TX_TIMEOUT, zyd_txeof); + error = usbd_transfer(data->xfer); + if (error != USBD_IN_PROGRESS && error != 0) { + ifp->if_oerrors++; + return (EIO); + } + sc->sc_txqueued++; + + return (0); +} + +static void +zyd_start(struct ifnet *ifp) +{ + struct zyd_softc *sc = ifp->if_softc; + struct ieee80211_node *ni; + struct mbuf *m; + + ZYD_TX_LOCK(sc); + for (;;) { + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + if (m == NULL) + break; + if (sc->sc_txqueued >= ZYD_TX_LIST_CNT) { + IFQ_DRV_PREPEND(&ifp->if_snd, m); + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + break; + } + ni = (struct ieee80211_node *)m->m_pkthdr.rcvif; + m = ieee80211_encap(ni, m); + if (m == NULL) { + ieee80211_free_node(ni); + ifp->if_oerrors++; + continue; + } + if (zyd_tx_data(sc, m, ni) != 0) { + ieee80211_free_node(ni); + ifp->if_oerrors++; + break; + } + + sc->sc_txtimer = 5; + } + ZYD_TX_UNLOCK(sc); +} + +static int +zyd_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, + const struct ieee80211_bpf_params *params) +{ + struct ieee80211com *ic = ni->ni_ic; + struct ifnet *ifp = ic->ic_ifp; + struct zyd_softc *sc = ifp->if_softc; + + /* prevent management frames from being sent if we're not ready */ + if (!(ifp->if_drv_flags & IFF_DRV_RUNNING)) { + m_freem(m); + ieee80211_free_node(ni); + return (ENETDOWN); + } + ZYD_TX_LOCK(sc); + if (sc->sc_txqueued >= ZYD_TX_LIST_CNT) { + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + m_freem(m); + ieee80211_free_node(ni); + return (ENOBUFS); /* XXX */ + } + + /* + * Legacy path; interpret frame contents to decide + * precisely how to send the frame. + * XXX raw path + */ + if (zyd_tx_mgt(sc, m, ni) != 0) { + ZYD_TX_UNLOCK(sc); + ifp->if_oerrors++; + ieee80211_free_node(ni); + return (EIO); + } + + ZYD_TX_UNLOCK(sc); + ifp->if_opackets++; + sc->sc_txtimer = 5; + return (0); +} + +static void +zyd_watchdog(void *arg) +{ + struct zyd_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + + if (sc->sc_txtimer > 0) { + if (--sc->sc_txtimer == 0) { + device_printf(sc->sc_dev, "device timeout\n"); + /* zyd_init(ifp); XXX needs a process context ? */ + ifp->if_oerrors++; + return; + } + callout_reset(&sc->sc_watchdog_ch, hz, zyd_watchdog, sc); + } +} + +static int +zyd_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) +{ + struct zyd_softc *sc = ifp->if_softc; + struct ieee80211com *ic = ifp->if_l2com; + struct ifreq *ifr = (struct ifreq *) data; + int error = 0, startall = 0; + + switch (cmd) { + case SIOCSIFFLAGS: + ZYD_LOCK(sc); + if (ifp->if_flags & IFF_UP) { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + if ((ifp->if_flags ^ sc->sc_if_flags) & + (IFF_ALLMULTI | IFF_PROMISC)) + zyd_set_multi(sc); + } else { + zyd_init_locked(sc); + startall = 1; + } + } else { + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + zyd_stop(sc, 1); + } + sc->sc_if_flags = ifp->if_flags; + ZYD_UNLOCK(sc); + if (startall) + ieee80211_start_all(ic); + break; + case SIOCGIFMEDIA: + error = ifmedia_ioctl(ifp, ifr, &ic->ic_media, cmd); + break; + case SIOCGIFADDR: + error = ether_ioctl(ifp, cmd, data); + break; + default: + error = EINVAL; + break; + } + return (error); +} + +static void +zyd_init_locked(struct zyd_softc *sc) +{ + int error, i; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + uint32_t val; + + if (!(sc->sc_flags & ZYD_FLAG_INITONCE)) { + error = zyd_loadfirmware(sc); + if (error != 0) { + device_printf(sc->sc_dev, + "could not load firmware (error=%d)\n", error); + goto fail; + } + + error = usbd_set_config_no(sc->sc_udev, ZYD_CONFIG_NO, 1); + if (error != 0) { + device_printf(sc->sc_dev, "setting config no failed\n"); + goto fail; + } + error = usbd_device2interface_handle(sc->sc_udev, + ZYD_IFACE_INDEX, &sc->sc_iface); + if (error != 0) { + device_printf(sc->sc_dev, + "getting interface handle failed\n"); + goto fail; + } + + if ((error = zyd_open_pipes(sc)) != 0) { + device_printf(sc->sc_dev, "could not open pipes\n"); + goto fail; + } + if ((error = zyd_hw_init(sc)) != 0) { + device_printf(sc->sc_dev, + "hardware initialization failed\n"); + goto fail; + } + + device_printf(sc->sc_dev, + "HMAC ZD1211%s, FW %02x.%02x, RF %s S%x, PA%x LED %x " + "BE%x NP%x Gain%x F%x\n", + (sc->sc_macrev == ZYD_ZD1211) ? "": "B", + sc->sc_fwrev >> 8, sc->sc_fwrev & 0xff, + zyd_rf_name(sc->sc_rfrev), sc->sc_al2230s, sc->sc_parev, + sc->sc_ledtype, sc->sc_bandedge6, sc->sc_newphy, + sc->sc_cckgain, sc->sc_fix_cr157); + + /* read regulatory domain (currently unused) */ + zyd_read32_m(sc, ZYD_EEPROM_SUBID, &val); + sc->sc_regdomain = val >> 16; + DPRINTF(sc, ZYD_DEBUG_INIT, "regulatory domain %x\n", + sc->sc_regdomain); + + /* we'll do software WEP decryption for now */ + DPRINTF(sc, ZYD_DEBUG_INIT, "%s: setting encryption type\n", + __func__); + zyd_write32_m(sc, ZYD_MAC_ENCRYPTION_TYPE, ZYD_ENC_SNIFFER); + + sc->sc_flags |= ZYD_FLAG_INITONCE; + } + + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + zyd_stop(sc, 0); + + /* reset softc variables. */ + sc->sc_txidx = 0; + + IEEE80211_ADDR_COPY(ic->ic_myaddr, IF_LLADDR(ifp)); + DPRINTF(sc, ZYD_DEBUG_INIT, "setting MAC address to %s\n", + ether_sprintf(ic->ic_myaddr)); + error = zyd_set_macaddr(sc, ic->ic_myaddr); + if (error != 0) + return; + + /* set basic rates */ + if (ic->ic_curmode == IEEE80211_MODE_11B) + zyd_write32_m(sc, ZYD_MAC_BAS_RATE, 0x0003); + else if (ic->ic_curmode == IEEE80211_MODE_11A) + zyd_write32_m(sc, ZYD_MAC_BAS_RATE, 0x1500); + else /* assumes 802.11b/g */ + zyd_write32_m(sc, ZYD_MAC_BAS_RATE, 0xff0f); + + /* promiscuous mode */ + zyd_write32_m(sc, ZYD_MAC_SNIFFER, 0); + /* multicast setup */ + zyd_set_multi(sc); + /* set RX filter */ + error = zyd_set_rxfilter(sc); + if (error != 0) + goto fail; + + /* switch radio transmitter ON */ + error = zyd_switch_radio(sc, 1); + if (error != 0) + goto fail; + /* set default BSS channel */ + zyd_set_chan(sc, ic->ic_curchan); + + /* + * Allocate Tx and Rx xfer queues. + */ + if ((error = zyd_alloc_tx_list(sc)) != 0) { + device_printf(sc->sc_dev, "could not allocate Tx list\n"); + goto fail; + } + if ((error = zyd_alloc_rx_list(sc)) != 0) { + device_printf(sc->sc_dev, "could not allocate Rx list\n"); + goto fail; + } + + /* + * Start up the receive pipe. + */ + for (i = 0; i < ZYD_RX_LIST_CNT; i++) { + struct zyd_rx_data *data = &sc->sc_rxdata[i]; + + usbd_setup_xfer(data->xfer, sc->sc_ep[ZYD_ENDPT_BIN], data, + NULL, ZYX_MAX_RXBUFSZ, USBD_NO_COPY | USBD_SHORT_XFER_OK, + USBD_NO_TIMEOUT, zyd_rxeof); + error = usbd_transfer(data->xfer); + if (error != USBD_IN_PROGRESS && error != 0) { + device_printf(sc->sc_dev, + "could not queue Rx transfer\n"); + goto fail; + } + } + + /* enable interrupts */ + zyd_write32_m(sc, ZYD_CR_INTERRUPT, ZYD_HWINT_MASK); + + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + ifp->if_drv_flags |= IFF_DRV_RUNNING; + sc->sc_flags |= ZYD_FLAG_INITDONE; + + callout_reset(&sc->sc_watchdog_ch, hz, zyd_watchdog, sc); + return; + +fail: zyd_stop(sc, 1); + return; +} + +static void +zyd_init(void *priv) +{ + struct zyd_softc *sc = priv; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + ZYD_LOCK(sc); + zyd_init_locked(sc); + ZYD_UNLOCK(sc); + + if (ifp->if_drv_flags & IFF_DRV_RUNNING) + ieee80211_start_all(ic); /* start all vap's */ +} + +static void +zyd_stop(struct zyd_softc *sc, int disable) +{ + int error; + struct ifnet *ifp = sc->sc_ifp; + + sc->sc_txtimer = 0; + ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE); + + /* switch radio transmitter OFF */ + error = zyd_switch_radio(sc, 0); + if (error != 0) + goto fail; + /* disable Rx */ + zyd_write32_m(sc, ZYD_MAC_RXFILTER, 0); + /* disable interrupts */ + zyd_write32_m(sc, ZYD_CR_INTERRUPT, 0); + + usb_rem_task(sc->sc_udev, &sc->sc_scantask); + usb_rem_task(sc->sc_udev, &sc->sc_task); + callout_stop(&sc->sc_watchdog_ch); + + usbd_abort_pipe(sc->sc_ep[ZYD_ENDPT_BIN]); + usbd_abort_pipe(sc->sc_ep[ZYD_ENDPT_BOUT]); + + zyd_free_rx_list(sc); + zyd_free_tx_list(sc); +fail: + return; +} + +static int +zyd_loadfirmware(struct zyd_softc *sc) +{ + usb_device_request_t req; + size_t size; + u_char *fw; + uint8_t stat; + uint16_t addr; + + if (sc->sc_flags & ZYD_FLAG_FWLOADED) + return (0); + + if (sc->sc_macrev == ZYD_ZD1211) { + fw = (u_char *)zd1211_firmware; + size = sizeof(zd1211_firmware); + } else { + fw = (u_char *)zd1211b_firmware; + size = sizeof(zd1211b_firmware); + } + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = ZYD_DOWNLOADREQ; + USETW(req.wIndex, 0); + + addr = ZYD_FIRMWARE_START_ADDR; + while (size > 0) { + /* + * When the transfer size is 4096 bytes, it is not + * likely to be able to transfer it. + * The cause is port or machine or chip? + */ + const int mlen = min(size, 64); + + DPRINTF(sc, ZYD_DEBUG_FW, + "loading firmware block: len=%d, addr=0x%x\n", mlen, addr); + + USETW(req.wValue, addr); + USETW(req.wLength, mlen); + if (usbd_do_request(sc->sc_udev, &req, fw) != 0) + return (EIO); + + addr += mlen / 2; + fw += mlen; + size -= mlen; + } + + /* check whether the upload succeeded */ + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = ZYD_DOWNLOADSTS; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, sizeof(stat)); + if (usbd_do_request(sc->sc_udev, &req, &stat) != 0) + return (EIO); + + sc->sc_flags |= ZYD_FLAG_FWLOADED; + + return (stat & 0x80) ? (EIO) : (0); +} + +static void +zyd_newassoc(struct ieee80211_node *ni, int isnew) +{ + struct ieee80211vap *vap = ni->ni_vap; + + ieee80211_amrr_node_init(&ZYD_VAP(vap)->amrr, &ZYD_NODE(ni)->amn, ni); +} + +static void +zyd_scan_start(struct ieee80211com *ic) +{ + struct zyd_softc *sc = ic->ic_ifp->if_softc; + + usb_rem_task(sc->sc_udev, &sc->sc_scantask); + + /* do it in a process context */ + sc->sc_scan_action = ZYD_SCAN_START; + usb_add_task(sc->sc_udev, &sc->sc_scantask, USB_TASKQ_DRIVER); +} + +static void +zyd_scan_end(struct ieee80211com *ic) +{ + struct zyd_softc *sc = ic->ic_ifp->if_softc; + + usb_rem_task(sc->sc_udev, &sc->sc_scantask); + + /* do it in a process context */ + sc->sc_scan_action = ZYD_SCAN_END; + usb_add_task(sc->sc_udev, &sc->sc_scantask, USB_TASKQ_DRIVER); +} + +static void +zyd_set_channel(struct ieee80211com *ic) +{ + struct zyd_softc *sc = ic->ic_ifp->if_softc; + + usb_rem_task(sc->sc_udev, &sc->sc_scantask); + /* do it in a process context */ + sc->sc_scan_action = ZYD_SET_CHANNEL; + usb_add_task(sc->sc_udev, &sc->sc_scantask, USB_TASKQ_DRIVER); +} + +static void +zyd_scantask(void *arg) +{ + struct zyd_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + ZYD_LOCK(sc); + + switch (sc->sc_scan_action) { + case ZYD_SCAN_START: + /* want broadcast address while scanning */ + zyd_set_bssid(sc, ifp->if_broadcastaddr); + break; + case ZYD_SCAN_END: + /* restore previous bssid */ + zyd_set_bssid(sc, sc->sc_bssid); + break; + case ZYD_SET_CHANNEL: + zyd_set_chan(sc, ic->ic_curchan); + break; + default: + device_printf(sc->sc_dev, "unknown scan action %d\n", + sc->sc_scan_action); + break; + } + + ZYD_UNLOCK(sc); +} + +static void +zyd_wakeup(struct zyd_softc *sc) +{ + struct zyd_rq *rqp; + + STAILQ_FOREACH(rqp, &sc->sc_rqh, rq) + wakeup(rqp->odata); /* wakeup sleeping caller */ +} + +static device_method_t zyd_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, zyd_match), + DEVMETHOD(device_attach, zyd_attach), + DEVMETHOD(device_detach, zyd_detach), + + { 0, 0 } +}; + +static driver_t zyd_driver = { + "zyd", + zyd_methods, + sizeof(struct zyd_softc) +}; + +static devclass_t zyd_devclass; + +DRIVER_MODULE(zyd, uhub, zyd_driver, zyd_devclass, usbd_driver_load, 0); +MODULE_DEPEND(zyd, wlan, 1, 1, 1); +MODULE_DEPEND(zyd, wlan_amrr, 1, 1, 1); +MODULE_DEPEND(zyd, usb, 1, 1, 1); diff --git a/sys/legacy/dev/usb/if_zydfw.h b/sys/legacy/dev/usb/if_zydfw.h new file mode 100644 index 0000000..46f5c2a --- /dev/null +++ b/sys/legacy/dev/usb/if_zydfw.h @@ -0,0 +1,1144 @@ +/* + * Copyright (C) 2001, 2002, 2003,2004 ZyDAS Technology Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted provided + * that the following conditions are met: + * 1. Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistribution 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. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission + * + * 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$ */ + +uint8_t zd1211_firmware[] = { + 0x08, 0x91, 0xFF, 0xED, 0x09, 0x93, 0x1E, 0xEE, + 0xD1, 0x94, 0x11, 0xEE, 0x88, 0xD4, 0xD1, 0x96, + 0xD1, 0x98, 0x5C, 0x99, 0x5C, 0x99, 0x4C, 0x99, + 0x04, 0x9D, 0xD1, 0x98, 0xD1, 0x9A, 0x03, 0xEE, + 0xF4, 0x94, 0xD3, 0xD4, 0x41, 0x2A, 0x40, 0x4A, + 0x45, 0xBE, 0x88, 0x92, 0x41, 0x24, 0x40, 0x44, + 0x53, 0xBE, 0x40, 0xF0, 0x93, 0xEE, 0x41, 0xEE, + 0x98, 0x9A, 0xD4, 0xF7, 0x02, 0x00, 0x1F, 0xEC, + 0x00, 0x00, 0xB2, 0xF8, 0x4D, 0x00, 0xA1, 0xEC, + 0x00, 0x00, 0xA6, 0xF7, 0x21, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA0, 0xD8, + 0xA0, 0x90, 0x98, 0x9A, 0x98, 0x9A, 0xA0, 0xD8, + 0x40, 0xF0, 0xB4, 0xF0, 0xA0, 0x90, 0x98, 0x9A, + 0xA0, 0xD8, 0x40, 0xF0, 0x64, 0xEF, 0xA0, 0x90, + 0x98, 0x9A, 0xA0, 0xD8, 0x40, 0xF0, 0xF6, 0xF0, + 0xA0, 0x90, 0x98, 0x9A, 0xA0, 0xD8, 0x40, 0xF0, + 0xF7, 0xF6, 0xA0, 0x90, 0x98, 0x9A, 0xA0, 0xD8, + 0x40, 0xF0, 0xF8, 0xF5, 0xA0, 0x90, 0x98, 0x9A, + 0xA0, 0xD8, 0x40, 0xF0, 0xF1, 0xF0, 0xA0, 0x90, + 0x98, 0x9A, 0x98, 0x9A, 0xA0, 0xD8, 0x40, 0xF0, + 0x97, 0xF7, 0xA0, 0x90, 0x98, 0x9A, 0x88, 0xDA, + 0x08, 0x0B, 0x01, 0x00, 0x0D, 0x03, 0x03, 0x00, + 0x09, 0x05, 0x01, 0x00, 0xC2, 0x94, 0x42, 0x02, + 0xC1, 0x92, 0x03, 0x96, 0x1B, 0xD7, 0x2A, 0x86, + 0x1A, 0xD5, 0x2B, 0x86, 0x09, 0xA3, 0x00, 0x80, + 0x19, 0xD3, 0x2C, 0x86, 0x00, 0xEE, 0x0A, 0x65, + 0xC0, 0x7A, 0x02, 0x97, 0xC3, 0x92, 0x09, 0xB3, + 0xFE, 0xFF, 0xC2, 0xD2, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x42, 0x20, 0x08, 0x0B, 0x01, 0x00, + 0x0D, 0x03, 0x05, 0x00, 0x05, 0x94, 0xC5, 0xD4, + 0x09, 0x05, 0x01, 0x00, 0xC2, 0x94, 0x01, 0xD4, + 0x42, 0x02, 0xC1, 0x96, 0x0A, 0x65, 0xC0, 0x7A, + 0x02, 0x99, 0xC4, 0x92, 0x41, 0xA2, 0xC4, 0xD2, + 0xC5, 0x98, 0x1C, 0xD9, 0x2A, 0x86, 0x01, 0x98, + 0x1C, 0xD9, 0x2B, 0x86, 0x1B, 0xD7, 0x2C, 0x86, + 0x00, 0xEE, 0x09, 0xB3, 0xFE, 0xFF, 0xC2, 0xD2, + 0x42, 0x00, 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, + 0x41, 0x20, 0x08, 0x0B, 0x01, 0x00, 0x40, 0xF0, + 0xE5, 0xEE, 0x11, 0x93, 0xD8, 0xF7, 0x41, 0x42, + 0x02, 0x5E, 0x0F, 0x9F, 0xAE, 0xEE, 0x40, 0xF1, + 0x40, 0x92, 0x19, 0xD3, 0xD8, 0xF7, 0xC5, 0x92, + 0x41, 0x92, 0x19, 0xD3, 0x00, 0x83, 0x40, 0x92, + 0x19, 0xD3, 0x00, 0x83, 0x0F, 0x9F, 0x95, 0xF8, + 0x0F, 0x9F, 0x99, 0xEE, 0x42, 0x42, 0x02, 0x5E, + 0x0F, 0x9F, 0x99, 0xEE, 0x40, 0x92, 0x19, 0xD3, + 0xD8, 0xF7, 0x09, 0x93, 0xC7, 0xF7, 0x19, 0xD3, + 0x91, 0xEC, 0x40, 0xF0, 0x5F, 0xF2, 0x09, 0x63, + 0x00, 0x80, 0x19, 0xD3, 0xF2, 0xBD, 0x0F, 0x9F, + 0x99, 0xEE, 0x41, 0x00, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, 0x40, 0x92, + 0x19, 0xD3, 0x12, 0x95, 0x19, 0xD3, 0x10, 0x95, + 0x19, 0xD3, 0x02, 0x80, 0x19, 0xD3, 0x03, 0x82, + 0x09, 0x93, 0xC7, 0xF7, 0x19, 0xD3, 0x91, 0xEC, + 0x40, 0xF0, 0x5F, 0xF2, 0x40, 0xF0, 0xDE, 0xF3, + 0x11, 0x93, 0x04, 0xEC, 0x42, 0x42, 0x02, 0x5E, + 0x0F, 0x9F, 0xE3, 0xEE, 0x40, 0x92, 0x19, 0xD3, + 0x04, 0xEC, 0x40, 0xF0, 0x38, 0xF2, 0x88, 0x98, + 0x90, 0x9A, 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, + 0x11, 0x93, 0x44, 0x96, 0x09, 0xB3, 0xFF, 0xFD, + 0x19, 0xD3, 0x44, 0x96, 0x40, 0xF0, 0x90, 0xF7, + 0x6E, 0x92, 0x19, 0xD3, 0x05, 0x84, 0x40, 0xF0, + 0xC4, 0xEE, 0x4B, 0x62, 0x0A, 0x95, 0x2E, 0xEE, + 0xD1, 0xD4, 0x0B, 0x97, 0x2B, 0xEE, 0xD1, 0xD6, + 0x0A, 0x95, 0x00, 0xEE, 0xD1, 0xD4, 0x0B, 0x97, + 0x2F, 0xEE, 0xD1, 0xD6, 0x0A, 0x95, 0x34, 0xEE, + 0xD1, 0xD4, 0x0B, 0x97, 0x39, 0xEE, 0xD1, 0xD6, + 0x0A, 0x95, 0x3E, 0xEE, 0xD1, 0xD4, 0x0B, 0x97, + 0x43, 0xEE, 0xD1, 0xD6, 0x0A, 0x95, 0x48, 0xEE, + 0xD1, 0xD4, 0x0B, 0x97, 0x4D, 0xEE, 0xD1, 0xD6, + 0x0A, 0x95, 0x4E, 0xEE, 0xC1, 0xD4, 0x0A, 0x65, + 0x00, 0x44, 0x02, 0x97, 0xC3, 0x92, 0x44, 0xA2, + 0xC2, 0xD2, 0x43, 0xF1, 0x09, 0x93, 0x01, 0x3F, + 0x19, 0xD3, 0xC0, 0x85, 0x11, 0x93, 0x44, 0x96, + 0x09, 0xB3, 0xFF, 0xFC, 0x19, 0xD3, 0x44, 0x96, + 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, 0x08, 0x0B, + 0x01, 0x00, 0x0D, 0x03, 0x03, 0x00, 0x03, 0x96, + 0x41, 0x02, 0x03, 0x99, 0xC4, 0x94, 0x42, 0x04, + 0xC1, 0x04, 0xC2, 0x94, 0xC3, 0xD4, 0x88, 0x98, + 0x90, 0x9A, 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, + 0x40, 0x92, 0x19, 0xD3, 0x94, 0xEC, 0x13, 0x97, + 0x95, 0xEC, 0x1B, 0xD7, 0x02, 0x80, 0x11, 0x93, + 0x99, 0xEC, 0x19, 0xD3, 0x7C, 0x96, 0x0B, 0x97, + 0xA0, 0x00, 0x1B, 0xD7, 0x6E, 0xEC, 0x0A, 0x65, + 0x0E, 0x42, 0x02, 0x97, 0xC3, 0x92, 0x09, 0xB3, + 0xFF, 0xBF, 0x11, 0xA3, 0x9A, 0xEC, 0xC2, 0xD2, + 0x0A, 0x65, 0xEB, 0x43, 0x02, 0x97, 0xC3, 0x92, + 0x09, 0xA3, 0xC0, 0x00, 0xC2, 0xD2, 0x0A, 0x65, + 0xE9, 0x43, 0x02, 0x97, 0xC3, 0x92, 0x09, 0xB3, + 0xBF, 0xFF, 0xC2, 0xD2, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x47, 0x20, 0x08, 0x0B, 0x01, 0x00, + 0x14, 0x99, 0x03, 0x80, 0x0C, 0xB3, 0x00, 0x10, + 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, 0x97, 0xF0, + 0x11, 0x93, 0x9F, 0xEC, 0x41, 0x02, 0x19, 0xD3, + 0x9F, 0xEC, 0x11, 0x93, 0xD6, 0xF7, 0x40, 0x42, + 0x02, 0x4E, 0x0F, 0x9F, 0x84, 0xEF, 0x0A, 0x65, + 0xFE, 0x7F, 0x02, 0x97, 0xC3, 0x92, 0x09, 0xA3, + 0x00, 0x04, 0xC2, 0xD2, 0x0F, 0x9F, 0xB1, 0xF0, + 0x11, 0x93, 0x94, 0xEC, 0x02, 0xD2, 0x40, 0x42, + 0x02, 0x5E, 0x0F, 0x9F, 0xD0, 0xEF, 0x41, 0x92, + 0x19, 0xD3, 0x94, 0xEC, 0x19, 0xD3, 0x9F, 0xEC, + 0x12, 0x95, 0x02, 0x80, 0x1A, 0xD5, 0x95, 0xEC, + 0x13, 0x97, 0x7C, 0x96, 0x1B, 0xD7, 0x99, 0xEC, + 0x0A, 0x65, 0x0E, 0x42, 0x02, 0x97, 0xC3, 0x92, + 0x09, 0xB3, 0x00, 0x40, 0x19, 0xD3, 0x9A, 0xEC, + 0x09, 0x63, 0x00, 0x40, 0xC2, 0xD2, 0x02, 0x94, + 0x1A, 0xD5, 0x7C, 0x96, 0x0C, 0xB3, 0x00, 0x08, + 0x40, 0x42, 0x02, 0x5E, 0x0F, 0x9F, 0xB0, 0xEF, + 0x0C, 0xB3, 0xFF, 0x07, 0x0F, 0x9F, 0xB4, 0xEF, + 0x11, 0x93, 0x06, 0x80, 0x09, 0xB3, 0xFF, 0x07, + 0x09, 0x03, 0x00, 0xA0, 0x19, 0xD3, 0x97, 0xEC, + 0x40, 0x98, 0x0B, 0x97, 0x9C, 0xEC, 0x04, 0x95, + 0x03, 0x05, 0x14, 0x03, 0x97, 0xEC, 0x46, 0x02, + 0xC1, 0x92, 0xC2, 0xD2, 0x41, 0x08, 0x42, 0x48, + 0x02, 0x9E, 0x0F, 0x9F, 0xBB, 0xEF, 0x11, 0x93, + 0x97, 0xEC, 0xC1, 0x92, 0xC5, 0xD2, 0x5F, 0xB2, + 0x19, 0xD3, 0x9B, 0xEC, 0x0F, 0x9F, 0xD3, 0xEF, + 0x13, 0x97, 0x98, 0xEC, 0xC5, 0xD6, 0x11, 0x93, + 0x03, 0x80, 0x09, 0xB3, 0x00, 0x08, 0x40, 0x42, + 0x02, 0x4E, 0x0F, 0x9F, 0xE9, 0xEF, 0x11, 0x93, + 0xDC, 0xF7, 0x41, 0x02, 0x19, 0xD3, 0xDC, 0xF7, + 0x11, 0x93, 0xDB, 0xF7, 0x09, 0xA3, 0x00, 0x10, + 0x19, 0xD3, 0xDB, 0xF7, 0x40, 0x98, 0x1C, 0xD9, + 0x9B, 0xEC, 0x12, 0x95, 0x9B, 0xEC, 0x40, 0x44, + 0x02, 0x4E, 0x0F, 0x9F, 0x86, 0xF0, 0x0A, 0xB3, + 0x08, 0x00, 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, + 0x07, 0xF0, 0x0A, 0xB3, 0x07, 0x00, 0x09, 0x05, + 0xA9, 0xEC, 0xC2, 0x94, 0x01, 0xD4, 0x09, 0x03, + 0xA1, 0xEC, 0xC1, 0x92, 0x19, 0xD3, 0x9B, 0xEC, + 0xC5, 0x94, 0x0A, 0xB5, 0x00, 0xFF, 0x01, 0xA5, + 0xC5, 0xD4, 0x0F, 0x9F, 0x13, 0xF0, 0x0A, 0x05, + 0xFF, 0xFF, 0x0A, 0x03, 0xB1, 0xEC, 0xC1, 0x92, + 0x01, 0xD2, 0x1A, 0xD5, 0x9B, 0xEC, 0xC5, 0x96, + 0x0B, 0x07, 0xFF, 0xFF, 0xC5, 0xD6, 0x11, 0x93, + 0x97, 0xEC, 0xC5, 0x98, 0xC1, 0xD8, 0x11, 0x93, + 0x97, 0xEC, 0x09, 0x05, 0x0B, 0x00, 0x03, 0xD4, + 0xC2, 0x96, 0x06, 0xD6, 0x7B, 0x95, 0x7A, 0x95, + 0x4C, 0x02, 0xC1, 0x92, 0x59, 0x93, 0x59, 0x93, + 0x01, 0xA5, 0x01, 0x98, 0x0C, 0xF5, 0x7B, 0x93, + 0x09, 0x09, 0x01, 0x00, 0x06, 0x92, 0x09, 0xB3, + 0xFF, 0x00, 0x04, 0xD2, 0x5C, 0x93, 0x59, 0x93, + 0x04, 0x94, 0x01, 0xA5, 0x03, 0x96, 0xC3, 0xD4, + 0x11, 0x93, 0x97, 0xEC, 0x4C, 0x02, 0x05, 0xD2, + 0xC1, 0x92, 0x09, 0xB3, 0x00, 0xFF, 0x7C, 0x95, + 0x7A, 0x95, 0x02, 0xA3, 0x05, 0x98, 0xC4, 0xD2, + 0x12, 0x95, 0x97, 0xEC, 0x45, 0x04, 0x02, 0x97, + 0xC3, 0x92, 0x09, 0xA3, 0x00, 0x01, 0xC2, 0xD2, + 0x12, 0x95, 0x9B, 0xEC, 0x0A, 0xB3, 0x08, 0x00, + 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, 0x5B, 0xF0, + 0x12, 0x95, 0x97, 0xEC, 0x4A, 0x04, 0x02, 0x99, + 0xC4, 0x92, 0x01, 0x98, 0x0C, 0xF3, 0x7B, 0x93, + 0x41, 0x02, 0x0F, 0x9F, 0x7C, 0xF0, 0x43, 0x44, + 0x02, 0x8E, 0x0F, 0x9F, 0x7D, 0xF0, 0x11, 0x93, + 0x97, 0xEC, 0x42, 0x02, 0x0A, 0x05, 0xFF, 0xFF, + 0xC1, 0xD4, 0x11, 0x93, 0x97, 0xEC, 0x4A, 0x02, + 0x12, 0x95, 0x60, 0x96, 0xC1, 0xD4, 0x12, 0x95, + 0x97, 0xEC, 0x4B, 0x04, 0x02, 0x97, 0xC3, 0x92, + 0x09, 0xB3, 0x1F, 0xFF, 0xC2, 0xD2, 0x12, 0x95, + 0x97, 0xEC, 0x4B, 0x04, 0x11, 0x93, 0x62, 0x96, + 0x41, 0x93, 0x59, 0x93, 0x02, 0x99, 0xC4, 0xA2, + 0xC2, 0xD2, 0xC5, 0x92, 0x19, 0xD3, 0x98, 0xEC, + 0x0A, 0x95, 0x0C, 0x02, 0x1A, 0xD5, 0x02, 0x80, + 0x0F, 0x9F, 0xB1, 0xF0, 0x09, 0x63, 0xFE, 0x7F, + 0x01, 0x97, 0xC3, 0x94, 0x0A, 0xA5, 0x00, 0x04, + 0xC1, 0xD4, 0x11, 0x93, 0x9F, 0xEC, 0x09, 0xA3, + 0x00, 0x01, 0x19, 0xD3, 0x9F, 0xEC, 0x40, 0xF0, + 0x39, 0xEF, 0x0F, 0x9F, 0xB1, 0xF0, 0x11, 0x93, + 0x94, 0xEC, 0x41, 0x42, 0x02, 0x5E, 0x0F, 0x9F, + 0xA6, 0xF0, 0x40, 0xF0, 0x39, 0xEF, 0x11, 0x93, + 0x95, 0xEC, 0x44, 0xB2, 0x40, 0x42, 0x02, 0x4E, + 0x0F, 0x9F, 0xB1, 0xF0, 0x48, 0x98, 0x1C, 0xD9, + 0x02, 0x80, 0x11, 0x93, 0x91, 0xEC, 0x41, 0x22, + 0x0A, 0x95, 0xB1, 0xF0, 0x88, 0xD4, 0x88, 0xDC, + 0x91, 0x9A, 0x47, 0x00, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, 0x11, 0x93, + 0x04, 0x82, 0x48, 0xB2, 0x40, 0x42, 0x02, 0x4E, + 0x0F, 0x9F, 0xC8, 0xF0, 0x0A, 0x65, 0xFD, 0x7D, + 0x02, 0x97, 0xC3, 0x92, 0x09, 0xB3, 0xFF, 0xFE, + 0xC2, 0xD2, 0x41, 0x92, 0x19, 0xD3, 0xBF, 0xEC, + 0x11, 0x93, 0x04, 0x82, 0x43, 0xB2, 0x12, 0x95, + 0x03, 0x82, 0x02, 0xB3, 0x40, 0x42, 0x02, 0x4E, + 0x0F, 0x9F, 0xEF, 0xF0, 0x0A, 0xB3, 0x00, 0xFF, + 0x48, 0xA2, 0x19, 0xD3, 0x03, 0x82, 0x40, 0xF0, + 0xEB, 0xF3, 0x11, 0x93, 0xBF, 0xEC, 0x41, 0x42, + 0x02, 0x5E, 0x0F, 0x9F, 0xEF, 0xF0, 0x11, 0x93, + 0x07, 0x82, 0x11, 0x43, 0x03, 0xEC, 0x02, 0x0E, + 0x0F, 0x9F, 0xEF, 0xF0, 0x11, 0x93, 0x03, 0x82, + 0x09, 0xA3, 0x00, 0x01, 0x19, 0xD3, 0x03, 0x82, + 0x40, 0x96, 0x1B, 0xD7, 0xBF, 0xEC, 0x88, 0x98, + 0x90, 0x9A, 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, + 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, 0x08, 0x0B, + 0x01, 0x00, 0x11, 0x93, 0x20, 0xBC, 0xC8, 0xD2, + 0x40, 0xF0, 0x48, 0xF1, 0x41, 0x00, 0x88, 0x98, + 0x90, 0x9A, 0x88, 0xDA, 0x42, 0x20, 0x08, 0x0B, + 0x01, 0x00, 0x0D, 0x03, 0x05, 0x00, 0x05, 0x94, + 0x41, 0x02, 0xC1, 0x92, 0x01, 0x97, 0xC3, 0x96, + 0xC2, 0xD6, 0x0A, 0x45, 0x00, 0x95, 0x02, 0x5E, + 0x0F, 0x9F, 0x45, 0xF1, 0xC1, 0x92, 0x41, 0xB2, + 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, 0x45, 0xF1, + 0x11, 0x93, 0xC0, 0xEC, 0x40, 0x42, 0x02, 0x5E, + 0x0F, 0x9F, 0x45, 0xF1, 0x41, 0x98, 0x1C, 0xD9, + 0xC0, 0xEC, 0x12, 0x95, 0x02, 0x80, 0x01, 0xD4, + 0x40, 0xF0, 0x56, 0xF2, 0x0B, 0x67, 0xFD, 0x7D, + 0x03, 0x99, 0xC4, 0x92, 0x0C, 0x99, 0x96, 0x03, + 0x1C, 0xD9, 0x06, 0x82, 0x41, 0x98, 0x1C, 0xD9, + 0x02, 0x82, 0x42, 0x98, 0x1C, 0xD9, 0x05, 0x82, + 0x0C, 0x69, 0x80, 0x7F, 0x1C, 0xD9, 0x00, 0xB0, + 0x09, 0xA3, 0x00, 0x01, 0xC3, 0xD2, 0x01, 0x94, + 0x0A, 0xB3, 0x04, 0x00, 0x40, 0x42, 0x02, 0x4E, + 0x0F, 0x9F, 0x43, 0xF1, 0x42, 0xA4, 0x1A, 0xD5, + 0x02, 0x80, 0x42, 0x00, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x42, 0x20, 0x08, 0x0B, 0x01, 0x00, + 0x05, 0x92, 0xC5, 0xD2, 0x60, 0xB2, 0x40, 0x42, + 0x02, 0x4E, 0x0F, 0x9F, 0x55, 0xF1, 0x40, 0xF0, + 0x35, 0xF7, 0xC5, 0x94, 0x0A, 0xB3, 0x10, 0x00, + 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, 0x5E, 0xF1, + 0x40, 0xF0, 0x23, 0xF6, 0xC5, 0x96, 0x0B, 0xB3, + 0x40, 0x00, 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, + 0x67, 0xF1, 0x40, 0xF0, 0x5D, 0xF5, 0xC5, 0x94, + 0x0A, 0xB3, 0x01, 0x00, 0x40, 0x42, 0x02, 0x4E, + 0x0F, 0x9F, 0xC8, 0xF1, 0x13, 0x97, 0x21, 0xBC, + 0x01, 0xD6, 0x0B, 0xB3, 0x02, 0x00, 0x40, 0x42, + 0x02, 0x4E, 0x0F, 0x9F, 0x79, 0xF1, 0x40, 0xF0, + 0x62, 0xFB, 0x01, 0x94, 0x0A, 0xB3, 0x04, 0x00, + 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, 0x82, 0xF1, + 0x40, 0xF0, 0x6C, 0xFB, 0x01, 0x96, 0x0B, 0xB3, + 0x01, 0x00, 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, + 0xA2, 0xF1, 0x40, 0xF0, 0xB0, 0xFA, 0x41, 0x92, + 0x19, 0xD3, 0xD5, 0xF7, 0x11, 0x93, 0x03, 0xEC, + 0x09, 0x43, 0x40, 0x00, 0x02, 0x5E, 0x0F, 0x9F, + 0x98, 0xF1, 0x40, 0x94, 0x1A, 0xD5, 0xD5, 0xF7, + 0x11, 0x93, 0x00, 0xEC, 0x40, 0x42, 0x02, 0x4E, + 0x0F, 0x9F, 0xAB, 0xF1, 0x40, 0xF0, 0x38, 0xF2, + 0x0F, 0x9F, 0xAB, 0xF1, 0x01, 0x96, 0x0B, 0xB3, + 0x08, 0x00, 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, + 0xAB, 0xF1, 0x40, 0xF0, 0x7C, 0xFB, 0x01, 0x94, + 0x0A, 0xB3, 0x10, 0x00, 0x40, 0x42, 0x02, 0x4E, + 0x0F, 0x9F, 0xB4, 0xF1, 0x40, 0xF0, 0x87, 0xFB, + 0x11, 0x93, 0x10, 0xEC, 0x42, 0x42, 0x02, 0x5E, + 0x0F, 0x9F, 0xBF, 0xF1, 0x44, 0x96, 0x1B, 0xD7, + 0x0B, 0xBC, 0x0F, 0x9F, 0xC5, 0xF1, 0x41, 0x42, + 0x02, 0x5E, 0x0F, 0x9F, 0xC5, 0xF1, 0x19, 0xD3, + 0x0B, 0xBC, 0x40, 0x92, 0x19, 0xD3, 0x10, 0xEC, + 0xC5, 0x94, 0x0A, 0xB3, 0x80, 0x00, 0x40, 0x42, + 0x02, 0x4E, 0x0F, 0x9F, 0x12, 0xF2, 0x13, 0x97, + 0x28, 0xBC, 0x01, 0xD6, 0x0B, 0xB3, 0x40, 0x00, + 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, 0xDA, 0xF1, + 0x40, 0xF0, 0x18, 0xF7, 0x01, 0x94, 0x0A, 0xB3, + 0x02, 0x00, 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, + 0xED, 0xF1, 0x40, 0xF0, 0xC4, 0xEE, 0x40, 0xF0, + 0x8F, 0xFB, 0x40, 0xF0, 0x1B, 0xF2, 0x40, 0x96, + 0x1B, 0xD7, 0x00, 0xEC, 0x41, 0x92, 0x19, 0xD3, + 0xD8, 0xF7, 0x01, 0x94, 0x0A, 0xB3, 0x04, 0x00, + 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, 0x09, 0xF2, + 0x40, 0xF0, 0x9E, 0xFB, 0x09, 0x63, 0x00, 0x44, + 0x01, 0x97, 0xC3, 0x94, 0x48, 0xA4, 0xC1, 0xD4, + 0x00, 0xEE, 0x40, 0x92, 0x19, 0xD3, 0x12, 0x95, + 0x19, 0xD3, 0x10, 0x95, 0x19, 0xD3, 0x02, 0x80, + 0x19, 0xD3, 0x03, 0x82, 0x41, 0x92, 0x19, 0xD3, + 0xD8, 0xF7, 0x01, 0x94, 0x0A, 0xB3, 0x08, 0x00, + 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, 0x12, 0xF2, + 0x40, 0xF0, 0xAE, 0xFB, 0x0A, 0x65, 0x00, 0x44, + 0x02, 0x97, 0xC3, 0x92, 0x44, 0xA2, 0xC2, 0xD2, + 0x42, 0x00, 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, + 0x08, 0x0B, 0x01, 0x00, 0x09, 0x63, 0x00, 0x40, + 0x19, 0xD3, 0xF2, 0xBD, 0x0A, 0x65, 0xEA, 0x43, + 0x02, 0x97, 0xC3, 0x92, 0x44, 0xA2, 0xC2, 0xD2, + 0x0A, 0x65, 0xE9, 0x43, 0x02, 0x97, 0xC3, 0x92, + 0x09, 0xA3, 0x40, 0x00, 0xC2, 0xD2, 0x0A, 0x65, + 0xEB, 0x43, 0x02, 0x97, 0xC3, 0x92, 0x09, 0xA3, + 0xC0, 0x00, 0xC2, 0xD2, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, 0x09, 0x63, + 0x00, 0x80, 0x19, 0xD3, 0xF2, 0xBD, 0x0A, 0x65, + 0xE8, 0x43, 0x02, 0x97, 0xC3, 0x92, 0x09, 0xA3, + 0xC0, 0x00, 0xC2, 0xD2, 0x0A, 0x65, 0xEB, 0x43, + 0x02, 0x97, 0xC3, 0x92, 0x09, 0xB3, 0xBF, 0xFF, + 0xC2, 0xD2, 0x0A, 0x65, 0xEA, 0x43, 0x02, 0x97, + 0xC3, 0x92, 0x09, 0xB3, 0xFB, 0xFF, 0xC2, 0xD2, + 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, 0x08, 0x0B, + 0x01, 0x00, 0x09, 0x93, 0x00, 0x01, 0x19, 0xD3, + 0x02, 0x80, 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, + 0x08, 0x0B, 0x01, 0x00, 0x09, 0x93, 0x00, 0x09, + 0x19, 0xD3, 0x02, 0x80, 0x40, 0xF0, 0x56, 0xF2, + 0x40, 0x92, 0x19, 0xD3, 0x94, 0xEC, 0xC8, 0xD2, + 0x09, 0x93, 0x91, 0xEC, 0xC8, 0xD2, 0x40, 0xF0, + 0x2A, 0xEF, 0x42, 0x00, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, 0x40, 0xF0, + 0x3B, 0xF5, 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, + 0x85, 0xF2, 0x0A, 0x65, 0xFE, 0x7F, 0x02, 0x97, + 0xC3, 0x92, 0x44, 0xA2, 0xC2, 0xD2, 0x0F, 0x9F, + 0x92, 0xF2, 0x40, 0xF0, 0x94, 0xF2, 0x40, 0x42, + 0x02, 0x5E, 0x0F, 0x9F, 0x92, 0xF2, 0xC8, 0xD2, + 0x09, 0x93, 0x91, 0xEC, 0xC8, 0xD2, 0x40, 0xF0, + 0x2A, 0xEF, 0x42, 0x00, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, 0x11, 0x93, + 0xF1, 0xBD, 0x19, 0xD3, 0xB6, 0xEC, 0x11, 0x93, + 0xB4, 0xEC, 0x40, 0x42, 0x02, 0x5E, 0x0F, 0x9F, + 0xAC, 0xF2, 0x09, 0x63, 0x00, 0x80, 0x01, 0x97, + 0xC3, 0x94, 0x0A, 0x07, 0x07, 0x00, 0xC1, 0xD6, + 0x0A, 0x05, 0x00, 0xA0, 0x1A, 0xD5, 0x96, 0xEC, + 0x11, 0x93, 0xB6, 0xEC, 0x19, 0xD3, 0x01, 0x80, + 0x0A, 0x65, 0xFE, 0x7F, 0x02, 0x97, 0xC3, 0x92, + 0x41, 0xA2, 0xC2, 0xD2, 0x40, 0x92, 0x88, 0x98, + 0x90, 0x9A, 0x88, 0xDA, 0x41, 0x20, 0x08, 0x0B, + 0x01, 0x00, 0x13, 0x97, 0xB4, 0xEC, 0x40, 0x46, + 0x02, 0x5E, 0x0F, 0x9F, 0x2C, 0xF3, 0x12, 0x95, + 0x96, 0xEC, 0x0A, 0x03, 0x07, 0x00, 0xC1, 0x92, + 0xC2, 0xD2, 0x11, 0x93, 0x96, 0xEC, 0x09, 0x05, + 0x01, 0x00, 0x48, 0x02, 0xC1, 0x92, 0xC2, 0xD2, + 0x11, 0x93, 0x96, 0xEC, 0x4E, 0x02, 0xC1, 0x94, + 0xC5, 0xD6, 0xC5, 0x92, 0x11, 0x07, 0x96, 0xEC, + 0x0B, 0x03, 0x0F, 0x00, 0xC1, 0x98, 0x46, 0x06, + 0x7A, 0x93, 0x79, 0x93, 0x5C, 0x95, 0x5A, 0x95, + 0x02, 0xA3, 0xC3, 0xD2, 0x04, 0x95, 0xC5, 0x96, + 0x41, 0x06, 0xC5, 0xD6, 0x42, 0x46, 0x02, 0x9E, + 0x0F, 0x9F, 0xD5, 0xF2, 0x11, 0x93, 0x96, 0xEC, + 0x09, 0x05, 0x05, 0x00, 0x41, 0x02, 0xC1, 0x92, + 0xC2, 0xD2, 0x11, 0x93, 0x96, 0xEC, 0xC1, 0x92, + 0x09, 0xB5, 0x1F, 0x00, 0x43, 0x44, 0x02, 0x8E, + 0x0F, 0x9F, 0x02, 0xF3, 0x40, 0x44, 0x02, 0x4E, + 0x0F, 0x9F, 0x03, 0xF3, 0x0A, 0x05, 0xFF, 0xFF, + 0x0F, 0x9F, 0x03, 0xF3, 0x43, 0x94, 0x11, 0x93, + 0x96, 0xEC, 0x42, 0x02, 0xC1, 0xD4, 0x11, 0x93, + 0x96, 0xEC, 0x49, 0x02, 0xC1, 0x92, 0x19, 0xD3, + 0xB4, 0xEC, 0x09, 0x05, 0xF2, 0xFF, 0x1A, 0xD5, + 0x92, 0xEC, 0x09, 0x43, 0xD0, 0x07, 0x02, 0x9E, + 0x0F, 0x9F, 0x2C, 0xF3, 0x11, 0x93, 0xDC, 0xF7, + 0x41, 0x02, 0x19, 0xD3, 0xDC, 0xF7, 0x11, 0x93, + 0xDB, 0xF7, 0x09, 0xA3, 0x40, 0x00, 0x19, 0xD3, + 0xDB, 0xF7, 0x09, 0x63, 0x00, 0x80, 0x01, 0x95, + 0xC2, 0x94, 0x1A, 0xD5, 0xB5, 0xEC, 0x40, 0x96, + 0x1B, 0xD7, 0xB4, 0xEC, 0x0F, 0x9F, 0x92, 0xF3, + 0x11, 0x93, 0x92, 0xEC, 0x12, 0x95, 0xB6, 0xEC, + 0x02, 0x43, 0x02, 0x8E, 0x0F, 0x9F, 0x7A, 0xF3, + 0x02, 0x0E, 0x0F, 0x9F, 0x4D, 0xF3, 0x11, 0x93, + 0xDC, 0xF7, 0x41, 0x02, 0x19, 0xD3, 0xDC, 0xF7, + 0x11, 0x93, 0xDB, 0xF7, 0x09, 0xA3, 0x80, 0x00, + 0x19, 0xD3, 0xDB, 0xF7, 0x09, 0x63, 0x00, 0x80, + 0x01, 0x95, 0xC2, 0x94, 0x1A, 0xD5, 0xB5, 0xEC, + 0x40, 0x96, 0x1B, 0xD7, 0xB4, 0xEC, 0x0F, 0x9F, + 0x92, 0xF3, 0x11, 0x93, 0x03, 0x80, 0x09, 0xB3, + 0x00, 0x40, 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, + 0x5F, 0xF3, 0x11, 0x93, 0xC0, 0xEC, 0x40, 0x42, + 0x02, 0x5E, 0x0F, 0x9F, 0x5F, 0xF3, 0x40, 0xF0, + 0xA6, 0xF3, 0x0F, 0x9F, 0x94, 0xF3, 0x41, 0x92, + 0xC8, 0xD2, 0x0A, 0x95, 0x91, 0xEC, 0xC8, 0xD4, + 0x40, 0xF0, 0x2A, 0xEF, 0x42, 0x00, 0x11, 0x93, + 0xC0, 0xEC, 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, + 0x72, 0xF3, 0x42, 0x96, 0x1B, 0xD7, 0xC0, 0xEC, + 0x0F, 0x9F, 0x94, 0xF3, 0x0A, 0x65, 0xFE, 0x7F, + 0x02, 0x97, 0xC3, 0x92, 0x42, 0xA2, 0xC2, 0xD2, + 0x0F, 0x9F, 0x94, 0xF3, 0x12, 0x45, 0x03, 0xEC, + 0x02, 0x4E, 0x0F, 0x9F, 0x8C, 0xF3, 0x11, 0x93, + 0xDC, 0xF7, 0x41, 0x02, 0x19, 0xD3, 0xDC, 0xF7, + 0x11, 0x93, 0xDB, 0xF7, 0x09, 0xA3, 0x00, 0x08, + 0x19, 0xD3, 0xDB, 0xF7, 0x1A, 0xD5, 0x92, 0xEC, + 0x11, 0x93, 0x92, 0xEC, 0x19, 0x25, 0x92, 0xEC, + 0x09, 0x63, 0x00, 0x80, 0x19, 0xD3, 0xF2, 0xBD, + 0x41, 0x00, 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, + 0x08, 0x0B, 0x01, 0x00, 0x40, 0xF0, 0xA6, 0xF3, + 0x40, 0x92, 0xC8, 0xD2, 0x09, 0x93, 0x91, 0xEC, + 0xC8, 0xD2, 0x40, 0xF0, 0x2A, 0xEF, 0x42, 0x00, + 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, 0x08, 0x0B, + 0x01, 0x00, 0x11, 0x93, 0xD7, 0xF7, 0x40, 0x42, + 0x02, 0x4E, 0x0F, 0x9F, 0xB6, 0xF3, 0x0A, 0x65, + 0xBC, 0x69, 0x02, 0x97, 0xC3, 0x92, 0x09, 0x83, + 0x00, 0x02, 0xC2, 0xD2, 0x11, 0x93, 0x03, 0x80, + 0x09, 0xB3, 0x00, 0x40, 0x40, 0x42, 0x02, 0x5E, + 0x0F, 0x9F, 0xC9, 0xF3, 0x11, 0x93, 0xDC, 0xF7, + 0x41, 0x02, 0x19, 0xD3, 0xDC, 0xF7, 0x11, 0x93, + 0xDB, 0xF7, 0x09, 0xA3, 0x00, 0x20, 0x19, 0xD3, + 0xDB, 0xF7, 0x11, 0x93, 0xB5, 0xEC, 0x19, 0xD3, + 0x04, 0x80, 0x12, 0x95, 0xB4, 0xEC, 0x1A, 0xD5, + 0x05, 0x80, 0x09, 0x63, 0x00, 0x80, 0x01, 0x97, + 0xC3, 0x96, 0x1B, 0xD7, 0xB5, 0xEC, 0x40, 0x94, + 0x1A, 0xD5, 0xB4, 0xEC, 0x19, 0xD3, 0xF2, 0xBD, + 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, 0x08, 0x0B, + 0x01, 0x00, 0x09, 0x93, 0x96, 0x03, 0x19, 0xD3, + 0x06, 0x82, 0x09, 0x93, 0x00, 0x01, 0x19, 0xD3, + 0x03, 0x82, 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, + 0x47, 0x20, 0x08, 0x0B, 0x01, 0x00, 0x11, 0x93, + 0x01, 0x82, 0xC5, 0xD2, 0x40, 0x94, 0x01, 0xD4, + 0x13, 0x97, 0xB8, 0xEC, 0x02, 0xD6, 0x03, 0x95, + 0x0C, 0x99, 0xBB, 0xEC, 0x04, 0x05, 0x13, 0x97, + 0x03, 0xEC, 0x01, 0x27, 0x02, 0x99, 0xC4, 0x92, + 0x03, 0x03, 0xC2, 0xD2, 0x14, 0x99, 0xBA, 0xEC, + 0x03, 0x09, 0x1C, 0xD9, 0xBA, 0xEC, 0x12, 0x95, + 0x04, 0x82, 0x0A, 0xB3, 0x02, 0x00, 0x40, 0x42, + 0x02, 0x4E, 0x0F, 0x9F, 0x29, 0xF5, 0x01, 0x92, + 0x03, 0xD2, 0x0A, 0xA3, 0x02, 0x00, 0x19, 0xD3, + 0x04, 0x82, 0x02, 0x96, 0x0B, 0x05, 0x01, 0x00, + 0x1A, 0xD5, 0xB8, 0xEC, 0xC5, 0x92, 0x43, 0x42, + 0x02, 0x9E, 0x0F, 0x9F, 0x37, 0xF4, 0x42, 0x44, + 0x02, 0x8E, 0x0F, 0x9F, 0x37, 0xF4, 0x11, 0x93, + 0xBF, 0xEC, 0x40, 0x42, 0x02, 0x5E, 0x0F, 0x9F, + 0x37, 0xF4, 0x0C, 0x49, 0xD3, 0x08, 0x02, 0x8E, + 0x0F, 0x9F, 0x37, 0xF4, 0x11, 0x63, 0x07, 0x82, + 0x11, 0xA3, 0x07, 0x82, 0x71, 0x93, 0x79, 0x93, + 0x79, 0x93, 0x79, 0x93, 0x03, 0xD2, 0xC5, 0x94, + 0x0A, 0xB5, 0xFC, 0xFF, 0x04, 0xD4, 0x03, 0x96, + 0x40, 0x46, 0x02, 0x5E, 0x0F, 0x9F, 0x46, 0xF4, + 0x11, 0x93, 0xB8, 0xEC, 0x41, 0x42, 0x02, 0x8E, + 0x0F, 0x9F, 0x4D, 0xF4, 0xC5, 0x98, 0x0C, 0x03, + 0xFF, 0xFF, 0x42, 0x42, 0x02, 0x8E, 0x0F, 0x9F, + 0x74, 0xF4, 0x0A, 0x95, 0xBB, 0xEC, 0x42, 0x92, + 0x19, 0xD3, 0xB9, 0xEC, 0xC5, 0x96, 0x43, 0x46, + 0x02, 0x9E, 0x0F, 0x9F, 0x66, 0xF4, 0x0B, 0x07, + 0xFC, 0xFF, 0xC5, 0xD6, 0xD2, 0x98, 0x1C, 0xD9, + 0xC8, 0xBC, 0xD2, 0x96, 0x1B, 0xD7, 0xCA, 0xBC, + 0x09, 0x03, 0xFF, 0xFF, 0x40, 0x42, 0x02, 0x5E, + 0x0F, 0x9F, 0x52, 0xF4, 0x19, 0xD3, 0xB9, 0xEC, + 0x40, 0x42, 0x02, 0x5E, 0x0F, 0x9F, 0x72, 0xF4, + 0x0A, 0x05, 0xFE, 0xFF, 0xCA, 0xD2, 0xC2, 0xD2, + 0x0F, 0x9F, 0x74, 0xF4, 0x1A, 0xD5, 0x93, 0xEC, + 0x03, 0x98, 0x40, 0x48, 0x02, 0x5E, 0x0F, 0x9F, + 0xA1, 0xF4, 0x11, 0x93, 0xB8, 0xEC, 0x41, 0x42, + 0x02, 0x9E, 0x0F, 0x9F, 0x84, 0xF4, 0x04, 0x94, + 0x48, 0x44, 0x02, 0x4E, 0x0F, 0x9F, 0x8F, 0xF4, + 0x41, 0x42, 0x02, 0x5E, 0x0F, 0x9F, 0xA1, 0xF4, + 0x11, 0x93, 0x04, 0x82, 0x41, 0xB2, 0x40, 0x42, + 0x02, 0x4E, 0x0F, 0x9F, 0xA1, 0xF4, 0x41, 0x96, + 0x01, 0xD6, 0x0A, 0x65, 0xBD, 0x43, 0x02, 0x99, + 0xC4, 0x92, 0x09, 0xA3, 0x80, 0x00, 0xC2, 0xD2, + 0x0A, 0x65, 0xE8, 0x43, 0x02, 0x97, 0xC3, 0x92, + 0x09, 0xB3, 0xBF, 0xFF, 0xC2, 0xD2, 0x0F, 0x9F, + 0xFA, 0xF4, 0xC5, 0x98, 0x43, 0x48, 0x02, 0x9E, + 0x0F, 0x9F, 0xFA, 0xF4, 0x4F, 0x96, 0x0C, 0xB3, + 0x01, 0x00, 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, + 0xAE, 0xF4, 0x47, 0x96, 0x11, 0x93, 0xB7, 0xEC, + 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, 0xD6, 0xF4, + 0x11, 0x93, 0xB8, 0xEC, 0x41, 0x42, 0x02, 0x5E, + 0x0F, 0x9F, 0xD6, 0xF4, 0x12, 0x95, 0x00, 0x82, + 0x0A, 0x05, 0xFF, 0xAF, 0x05, 0xD4, 0xC8, 0xD6, + 0xC8, 0xD2, 0x40, 0xF0, 0x7B, 0xF7, 0x42, 0x00, + 0x05, 0x96, 0xC3, 0x94, 0x01, 0xB5, 0x40, 0x44, + 0x02, 0x4E, 0x0F, 0x9F, 0xD6, 0xF4, 0x06, 0x98, + 0x50, 0x98, 0x1C, 0xD9, 0xA2, 0xBC, 0x40, 0x98, + 0x1C, 0xD9, 0xA2, 0xBC, 0x40, 0x92, 0x03, 0xD2, + 0x0F, 0x9F, 0xFF, 0xF4, 0x03, 0x94, 0x40, 0x44, + 0x02, 0x5E, 0x0F, 0x9F, 0xE3, 0xF4, 0x0A, 0x65, + 0x5E, 0x43, 0x02, 0x97, 0xC3, 0x92, 0x48, 0xA2, + 0xC2, 0xD2, 0x0F, 0x9F, 0xFF, 0xF4, 0x11, 0x93, + 0xB8, 0xEC, 0x0C, 0x99, 0xBB, 0xEC, 0x04, 0x03, + 0x04, 0x96, 0x13, 0x25, 0x03, 0xEC, 0xC1, 0xD4, + 0x11, 0x93, 0xBA, 0xEC, 0x19, 0x05, 0xBA, 0xEC, + 0x1B, 0xD7, 0x01, 0x82, 0x0A, 0x65, 0xFD, 0x7D, + 0x02, 0x99, 0xC4, 0x92, 0x43, 0xA2, 0xC2, 0xD2, + 0x41, 0x92, 0x01, 0xD2, 0x03, 0x94, 0x40, 0x44, + 0x02, 0x5E, 0x0F, 0x9F, 0x13, 0xF5, 0x11, 0x93, + 0xB9, 0xEC, 0x40, 0x42, 0x02, 0x5E, 0x0F, 0x9F, + 0x0B, 0xF5, 0x19, 0xD3, 0xB8, 0xEC, 0x19, 0xD3, + 0xBA, 0xEC, 0x19, 0xD3, 0xBB, 0xEC, 0x03, 0x96, + 0x40, 0x46, 0x02, 0x5E, 0x0F, 0x9F, 0x13, 0xF5, + 0x41, 0x98, 0x1C, 0xD9, 0xB7, 0xEC, 0x11, 0x93, + 0xBF, 0xEC, 0x41, 0x42, 0x02, 0x5E, 0x0F, 0x9F, + 0x24, 0xF5, 0x11, 0x93, 0x00, 0x82, 0x19, 0xD3, + 0x02, 0x82, 0x0A, 0x65, 0xFD, 0x7D, 0x02, 0x97, + 0xC3, 0x92, 0x09, 0xA3, 0x00, 0x01, 0xC2, 0xD2, + 0x40, 0x98, 0x1C, 0xD9, 0xBF, 0xEC, 0x0F, 0x9F, + 0x2C, 0xF5, 0x01, 0x92, 0x19, 0xD3, 0xB7, 0xEC, + 0x01, 0x94, 0x40, 0x44, 0x02, 0x5E, 0x0F, 0x9F, + 0x38, 0xF5, 0x0A, 0x65, 0xEA, 0x43, 0x02, 0x97, + 0xC3, 0x92, 0x09, 0xB3, 0xFB, 0xFF, 0xC2, 0xD2, + 0x47, 0x00, 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, + 0x08, 0x0B, 0x01, 0x00, 0x12, 0x95, 0x03, 0x80, + 0x0A, 0xB3, 0x00, 0x40, 0x40, 0x42, 0x02, 0x4E, + 0x0F, 0x9F, 0x57, 0xF5, 0x0A, 0xB7, 0x00, 0x08, + 0x40, 0x46, 0x02, 0x5E, 0x0F, 0x9F, 0x5A, 0xF5, + 0x11, 0x93, 0x03, 0xEC, 0x41, 0x02, 0x09, 0xB3, + 0xFE, 0xFF, 0x12, 0x95, 0x07, 0x80, 0x01, 0x45, + 0x02, 0x8E, 0x0F, 0x9F, 0x5A, 0xF5, 0x41, 0x92, + 0x0F, 0x9F, 0x5B, 0xF5, 0x40, 0x92, 0x88, 0x98, + 0x90, 0x9A, 0x88, 0xDA, 0x41, 0x20, 0x08, 0x0B, + 0x01, 0x00, 0x0A, 0x65, 0xE9, 0x43, 0x02, 0x97, + 0xC3, 0x92, 0x09, 0xA3, 0x40, 0x00, 0xC2, 0xD2, + 0x13, 0x97, 0x6E, 0xEC, 0x0B, 0x47, 0xA0, 0x00, + 0x02, 0x5E, 0x0F, 0x9F, 0x86, 0xF5, 0x09, 0x63, + 0x08, 0x43, 0x0A, 0x65, 0xFF, 0x5F, 0x01, 0x99, + 0xC4, 0xD4, 0x0A, 0x95, 0x9B, 0xEC, 0xD2, 0x96, + 0x1B, 0xD7, 0xFA, 0xBC, 0xD2, 0x96, 0xC4, 0xD6, + 0xD2, 0x98, 0x1C, 0xD9, 0xFA, 0xBC, 0xD2, 0x96, + 0xC1, 0xD6, 0xC2, 0x94, 0x1A, 0xD5, 0xFA, 0xBC, + 0x0F, 0x9F, 0xC4, 0xF5, 0x0C, 0x69, 0xFF, 0x6F, + 0x1C, 0xD9, 0xF8, 0xBC, 0x0B, 0x47, 0x10, 0x95, + 0x02, 0x5E, 0x0F, 0x9F, 0x9E, 0xF5, 0x0A, 0x95, + 0x6F, 0xEC, 0x09, 0x63, 0x06, 0x43, 0x01, 0x99, + 0xC4, 0xD6, 0xD2, 0x96, 0x1B, 0xD7, 0xF8, 0xBC, + 0x0C, 0x69, 0xEE, 0x6A, 0xC1, 0xD8, 0xC2, 0x94, + 0x1A, 0xD5, 0xF8, 0xBC, 0x40, 0x92, 0xC5, 0xD2, + 0x11, 0x43, 0xC1, 0xEC, 0x02, 0x0E, 0x0F, 0x9F, + 0xC1, 0xF5, 0xC5, 0x94, 0x0A, 0x03, 0x71, 0xEC, + 0xC1, 0x94, 0x1A, 0xD5, 0xFA, 0xBC, 0x11, 0x93, + 0xC0, 0xEC, 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, + 0xB3, 0xF5, 0x0A, 0x95, 0x6F, 0xEC, 0xC8, 0xD4, + 0x40, 0xF0, 0x9C, 0xF7, 0x19, 0xD3, 0xF8, 0xBC, + 0x41, 0x00, 0xC5, 0x96, 0x41, 0x06, 0xC5, 0xD6, + 0x13, 0x47, 0xC1, 0xEC, 0x02, 0x1E, 0x0F, 0x9F, + 0xA5, 0xF5, 0x40, 0x98, 0x1C, 0xD9, 0xFA, 0xBC, + 0x40, 0x92, 0x19, 0xD3, 0x6E, 0xEC, 0x19, 0xD3, + 0xC1, 0xEC, 0x0A, 0x65, 0x52, 0x43, 0x02, 0x97, + 0xC3, 0x92, 0x48, 0xA2, 0xC2, 0xD2, 0x0A, 0x65, + 0xEB, 0x43, 0x02, 0x99, 0xC4, 0x92, 0x09, 0xB3, + 0xBF, 0xFF, 0xC2, 0xD2, 0x41, 0x00, 0x88, 0x98, + 0x90, 0x9A, 0x88, 0xDA, 0x43, 0x20, 0x08, 0x0B, + 0x01, 0x00, 0x06, 0x92, 0x01, 0xD2, 0x0A, 0x65, + 0xF0, 0x6A, 0x0B, 0x97, 0x6F, 0xEC, 0x02, 0x99, + 0xC4, 0x98, 0xD3, 0xD8, 0x02, 0xD6, 0x0A, 0x03, + 0x02, 0x00, 0x01, 0x97, 0xC3, 0x98, 0x02, 0x96, + 0xC3, 0xD8, 0x01, 0x96, 0xC1, 0xD6, 0x1A, 0xD5, + 0x6E, 0xEC, 0xC5, 0x98, 0x14, 0x99, 0x6F, 0xEC, + 0xC2, 0xD8, 0x43, 0x00, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, 0x40, 0x92, + 0xC8, 0xD2, 0x40, 0xF0, 0xD9, 0xF5, 0x41, 0x00, + 0x11, 0x93, 0xC0, 0xEC, 0x40, 0x42, 0x02, 0x4E, + 0x0F, 0x9F, 0x13, 0xF6, 0x42, 0x42, 0x02, 0x5E, + 0x0F, 0x9F, 0x10, 0xF6, 0x0A, 0x65, 0xFE, 0x7F, + 0x02, 0x97, 0xC3, 0x92, 0x42, 0xA2, 0xC2, 0xD2, + 0x40, 0x92, 0x19, 0xD3, 0xC0, 0xEC, 0x0A, 0x65, + 0xEB, 0x43, 0x02, 0x97, 0xC3, 0x92, 0x09, 0xA3, + 0xC0, 0x00, 0xC2, 0xD2, 0x0A, 0x65, 0xE9, 0x43, + 0x02, 0x97, 0xC3, 0x92, 0x09, 0xB3, 0xBF, 0xFF, + 0xC2, 0xD2, 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, + 0x63, 0x20, 0x08, 0x0B, 0x01, 0x00, 0x11, 0x93, + 0xAF, 0xBC, 0x47, 0xB2, 0x59, 0x95, 0x5A, 0x95, + 0x12, 0xA5, 0xBF, 0xBC, 0x0A, 0xB3, 0x01, 0x00, + 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, 0x35, 0xF6, + 0x41, 0x04, 0x05, 0x93, 0x40, 0x96, 0x20, 0xD6, + 0x62, 0x97, 0x0F, 0x9F, 0x44, 0xF6, 0x14, 0x99, + 0xFC, 0xBC, 0xD1, 0xD8, 0x14, 0x99, 0xFE, 0xBC, + 0xD1, 0xD8, 0x20, 0x98, 0x42, 0x08, 0x20, 0xD8, + 0x20, 0x98, 0x03, 0x49, 0x02, 0x1E, 0x0F, 0x9F, + 0x3B, 0xF6, 0xC5, 0x92, 0x62, 0x42, 0x02, 0x4E, + 0x0F, 0x9F, 0x5D, 0xF6, 0x02, 0x8E, 0x0F, 0x9F, + 0x57, 0xF6, 0x61, 0x42, 0x02, 0x4E, 0x0F, 0x9F, + 0x81, 0xF6, 0x0F, 0x9F, 0xAE, 0xF6, 0x63, 0x42, + 0x02, 0x4E, 0x0F, 0x9F, 0xA4, 0xF6, 0x0F, 0x9F, + 0xAE, 0xF6, 0x0D, 0x03, 0x01, 0x00, 0x0C, 0x99, + 0x71, 0xEC, 0x0B, 0x05, 0xFF, 0xFF, 0x40, 0x96, + 0x0F, 0x9F, 0x6A, 0xF6, 0xD1, 0x96, 0xD4, 0xD6, + 0x20, 0x96, 0x41, 0x06, 0x20, 0xD6, 0x02, 0x47, + 0x02, 0x1E, 0x0F, 0x9F, 0x66, 0xF6, 0x1A, 0xD5, + 0xC1, 0xEC, 0x0A, 0x65, 0xEB, 0x43, 0x02, 0x99, + 0xC4, 0x92, 0x09, 0xA3, 0xC0, 0x00, 0xC2, 0xD2, + 0x0A, 0x65, 0xE9, 0x43, 0x02, 0x97, 0xC3, 0x92, + 0x09, 0xB3, 0xBF, 0xFF, 0xC2, 0xD2, 0x0F, 0x9F, + 0xAE, 0xF6, 0x0A, 0x03, 0xFE, 0xFF, 0x61, 0x95, + 0x40, 0x98, 0x20, 0xD8, 0x02, 0x49, 0x02, 0x0E, + 0x0F, 0x9F, 0xAE, 0xF6, 0x0D, 0x03, 0x01, 0x00, + 0x21, 0xD2, 0x20, 0x92, 0x05, 0x03, 0x42, 0x02, + 0xC8, 0xD2, 0x21, 0x96, 0xC3, 0x92, 0x42, 0x06, + 0x21, 0xD6, 0xC8, 0xD2, 0x22, 0xD4, 0x40, 0xF0, + 0x01, 0xF1, 0x42, 0x00, 0x20, 0x98, 0x42, 0x08, + 0x20, 0xD8, 0x22, 0x94, 0x02, 0x49, 0x02, 0x1E, + 0x0F, 0x9F, 0x8D, 0xF6, 0x0F, 0x9F, 0xAE, 0xF6, + 0x0D, 0x03, 0x03, 0x00, 0xC8, 0xD2, 0x02, 0x92, + 0xC8, 0xD2, 0x01, 0x96, 0xC8, 0xD6, 0x40, 0xF0, + 0xB1, 0xF6, 0x43, 0x00, 0x63, 0x00, 0x88, 0x98, + 0x90, 0x9A, 0x88, 0xDA, 0x45, 0x20, 0x08, 0x0B, + 0x01, 0x00, 0x0D, 0x03, 0x08, 0x00, 0x08, 0x94, + 0xC5, 0xD4, 0x09, 0x05, 0x01, 0x00, 0xC2, 0x94, + 0x03, 0xD4, 0x42, 0x02, 0xC1, 0x92, 0x01, 0xD2, + 0x02, 0x97, 0xC5, 0x94, 0x0A, 0x83, 0xFF, 0xFF, + 0x11, 0xB3, 0x2C, 0x93, 0x09, 0xB3, 0xFB, 0xFF, + 0x19, 0xD3, 0x2C, 0x93, 0x03, 0x92, 0x40, 0x42, + 0x02, 0x4E, 0x0F, 0x9F, 0xE4, 0xF6, 0x01, 0x94, + 0xD2, 0x92, 0x19, 0xD3, 0x2C, 0x93, 0x01, 0xD4, + 0x02, 0x94, 0x12, 0x95, 0x2C, 0x93, 0x44, 0xA4, + 0x1A, 0xD5, 0x2C, 0x93, 0x0A, 0xB5, 0xFB, 0xFF, + 0x1A, 0xD5, 0x2C, 0x93, 0x0B, 0x07, 0xFF, 0xFF, + 0x40, 0x46, 0x02, 0x5E, 0x0F, 0x9F, 0xCF, 0xF6, + 0x09, 0x63, 0xD4, 0x6C, 0x01, 0x95, 0xC2, 0x96, + 0xC5, 0x94, 0x02, 0xA7, 0xC1, 0xD6, 0x03, 0x92, + 0x54, 0x42, 0x02, 0x5E, 0x0F, 0x9F, 0xF4, 0xF6, + 0x0A, 0x83, 0xFF, 0xFF, 0x1B, 0xB3, 0x2C, 0x93, + 0x45, 0x00, 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, + 0x08, 0x0B, 0x01, 0x00, 0x09, 0x63, 0x00, 0x40, + 0x19, 0xD3, 0xF2, 0xBD, 0x40, 0xF0, 0x3B, 0xF5, + 0x40, 0x42, 0x02, 0x5E, 0x0F, 0x9F, 0x08, 0xF7, + 0x40, 0xF0, 0x94, 0xF2, 0x0F, 0x9F, 0x16, 0xF7, + 0x40, 0x96, 0xC8, 0xD6, 0x09, 0x93, 0x91, 0xEC, + 0xC8, 0xD2, 0x40, 0xF0, 0x2A, 0xEF, 0x0A, 0x65, + 0xFE, 0x7F, 0x02, 0x97, 0xC3, 0x92, 0x44, 0xA2, + 0xC2, 0xD2, 0x42, 0x00, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, 0x0A, 0x65, + 0xE8, 0x43, 0x02, 0x97, 0xC3, 0x92, 0x09, 0xA3, + 0x40, 0x00, 0xC2, 0xD2, 0x0A, 0x65, 0xEA, 0x43, + 0x02, 0x97, 0xC3, 0x92, 0x09, 0xB3, 0xFB, 0xFF, + 0xC2, 0xD2, 0x40, 0x92, 0x19, 0xD3, 0x2D, 0xBC, + 0x0A, 0x65, 0xD8, 0x43, 0x02, 0x97, 0xC3, 0x92, + 0x09, 0xB3, 0xBF, 0xFF, 0xC2, 0xD2, 0x88, 0x98, + 0x90, 0x9A, 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, + 0x09, 0x63, 0xEA, 0x43, 0x01, 0x97, 0xC3, 0x94, + 0x44, 0xA4, 0xC1, 0xD4, 0x11, 0x93, 0xB9, 0xEC, + 0x40, 0x42, 0x02, 0x4E, 0x0F, 0x9F, 0x6F, 0xF7, + 0x12, 0x95, 0x93, 0xEC, 0x0B, 0x67, 0x36, 0x43, + 0xD2, 0x98, 0x1C, 0xD9, 0xC8, 0xBC, 0xD2, 0x98, + 0x03, 0x93, 0xC1, 0xD8, 0x11, 0x93, 0xB9, 0xEC, + 0x09, 0x03, 0xFF, 0xFF, 0x19, 0xD3, 0xB9, 0xEC, + 0x40, 0x42, 0x02, 0x5E, 0x0F, 0x9F, 0x48, 0xF7, + 0x19, 0xD3, 0xB8, 0xEC, 0x19, 0xD3, 0xBA, 0xEC, + 0x0A, 0x05, 0xFE, 0xFF, 0xCA, 0xD2, 0xCA, 0xD2, + 0xC2, 0xD2, 0x0A, 0x65, 0x5E, 0x43, 0x02, 0x97, + 0xC3, 0x92, 0x48, 0xA2, 0xC2, 0xD2, 0x0A, 0x65, + 0xEA, 0x43, 0x02, 0x99, 0xC4, 0x92, 0x09, 0xB3, + 0xFB, 0xFF, 0x0F, 0x9F, 0x78, 0xF7, 0x11, 0x93, + 0x03, 0xEC, 0x19, 0xD3, 0x01, 0x82, 0x0A, 0x65, + 0xFD, 0x7D, 0x02, 0x97, 0xC3, 0x92, 0x43, 0xA2, + 0xC2, 0xD2, 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, + 0x08, 0x0B, 0x01, 0x00, 0x03, 0x92, 0x04, 0x96, + 0x0D, 0x5E, 0x50, 0x46, 0x02, 0x0E, 0x40, 0x92, + 0x09, 0xEE, 0x44, 0x46, 0x04, 0x0E, 0x59, 0x93, + 0x44, 0x26, 0x04, 0x5E, 0x46, 0xEE, 0x41, 0x93, + 0x41, 0x26, 0x43, 0x4E, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, 0x40, 0xF0, + 0xB1, 0xFE, 0x88, 0x98, 0x90, 0x9A, 0x88, 0xDA, + 0x08, 0x0B, 0x01, 0x00, 0x88, 0x98, 0x90, 0x9A, + 0x88, 0xDA, 0x08, 0x0B, 0x01, 0x00, 0x03, 0x94, + 0x1A, 0xD5, 0xA3, 0xF7, 0x11, 0x93, 0x00, 0x90, + 0x88, 0x98, 0x90, 0x9A, 0x1D, 0x00, 0x1A, 0x00, + 0x03, 0x00, 0x03, 0x00, 0x18, 0x00, 0x19, 0x00, + 0x1A, 0x00, 0x1B, 0x00, 0x16, 0x00, 0x21, 0x00, + 0x12, 0x00, 0x09, 0x00, 0x13, 0x00, 0x19, 0x00, + 0x19, 0x00, 0x19, 0x00, 0x21, 0x00, 0x2D, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x69, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x5F, 0xF2, 0xCD, 0xF7, 0x00, 0x00, 0x74, 0xF2, + 0xCD, 0xF7, 0x00, 0x00, 0xB9, 0xF2, 0xCA, 0xF7, + 0xD1, 0xF7, 0x00, 0x00, 0x97, 0xF3, 0xCD, 0xF7, + 0x05, 0x46, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +/* + * current zd1211b firmware version. + */ +#define ZD1211B_FIRMWARE_VER 4705 + +uint8_t zd1211b_firmware[] = { + 0x08, 0x91, 0xff, 0xed, 0x09, 0x93, 0x1e, 0xee, 0xd1, 0x94, 0x11, + 0xee, 0x88, 0xd4, 0xd1, 0x96, 0xd1, 0x98, 0x5c, 0x99, 0x5c, 0x99, + 0x4c, 0x99, 0x04, 0x9d, 0xd1, 0x98, 0xd1, 0x9a, 0x03, 0xee, 0xf4, + 0x94, 0xd3, 0xd4, 0x41, 0x2a, 0x40, 0x4a, 0x45, 0xbe, 0x88, 0x92, + 0x41, 0x24, 0x40, 0x44, 0x53, 0xbe, 0x40, 0xf0, 0x4e, 0xee, 0x41, + 0xee, 0x98, 0x9a, 0x72, 0xf7, 0x02, 0x00, 0x1f, 0xec, 0x00, 0x00, + 0xb2, 0xf8, 0x4d, 0x00, 0xa1, 0xec, 0x00, 0x00, 0x43, 0xf7, 0x22, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0xd8, + 0xa0, 0x90, 0x98, 0x9a, 0x98, 0x9a, 0xa0, 0xd8, 0x40, 0xf0, 0x5a, + 0xf0, 0xa0, 0x90, 0x98, 0x9a, 0xa0, 0xd8, 0x40, 0xf0, 0x0a, 0xef, + 0xa0, 0x90, 0x98, 0x9a, 0xa0, 0xd8, 0x40, 0xf0, 0x97, 0xf0, 0xa0, + 0x90, 0x98, 0x9a, 0xa0, 0xd8, 0x40, 0xf0, 0x94, 0xf6, 0xa0, 0x90, + 0x98, 0x9a, 0xa0, 0xd8, 0x40, 0xf0, 0x95, 0xf5, 0xa0, 0x90, 0x98, + 0x9a, 0x98, 0x9a, 0xa0, 0xd8, 0x40, 0xf0, 0x34, 0xf7, 0xa0, 0x90, + 0x98, 0x9a, 0x88, 0xda, 0x41, 0x20, 0x08, 0x0b, 0x01, 0x00, 0x40, + 0xf0, 0x8e, 0xee, 0x40, 0x96, 0x0a, 0x65, 0x00, 0x7d, 0x11, 0x93, + 0x76, 0xf7, 0x41, 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0x57, 0xee, 0x40, + 0xf1, 0x1b, 0xd7, 0x76, 0xf7, 0xc5, 0x92, 0x02, 0x99, 0x41, 0x92, + 0xc4, 0xd2, 0x40, 0x92, 0xc4, 0xd2, 0x0f, 0x9f, 0x95, 0xf8, 0x0f, + 0x9f, 0x57, 0xee, 0x41, 0x00, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, + 0x08, 0x0b, 0x01, 0x00, 0x40, 0x92, 0x19, 0xd3, 0x12, 0x95, 0x19, + 0xd3, 0x10, 0x95, 0x19, 0xd3, 0x02, 0x80, 0x19, 0xd3, 0x03, 0x82, + 0x09, 0x93, 0x65, 0xf7, 0x19, 0xd3, 0x91, 0xec, 0x40, 0xf0, 0x07, + 0xf2, 0x40, 0xf0, 0x75, 0xf3, 0x11, 0x93, 0x04, 0xec, 0x42, 0x42, + 0x02, 0x5e, 0x0f, 0x9f, 0x8c, 0xee, 0x40, 0x92, 0x19, 0xd3, 0x04, + 0xec, 0x40, 0xf0, 0xe0, 0xf1, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, + 0x08, 0x0b, 0x01, 0x00, 0x11, 0x93, 0x44, 0x96, 0x09, 0xb3, 0xff, + 0xfd, 0x19, 0xd3, 0x44, 0x96, 0x40, 0xf0, 0x2d, 0xf7, 0x40, 0xf0, + 0x6d, 0xee, 0x4b, 0x62, 0x0a, 0x95, 0x2e, 0xee, 0xd1, 0xd4, 0x0b, + 0x97, 0x2b, 0xee, 0xd1, 0xd6, 0x0a, 0x95, 0x00, 0xee, 0xd1, 0xd4, + 0x0b, 0x97, 0x2f, 0xee, 0xd1, 0xd6, 0x0a, 0x95, 0x34, 0xee, 0xd1, + 0xd4, 0x0b, 0x97, 0x39, 0xee, 0xd1, 0xd6, 0x0a, 0x95, 0x3e, 0xee, + 0xd1, 0xd4, 0x0b, 0x97, 0x43, 0xee, 0xd1, 0xd6, 0x0a, 0x95, 0x2e, + 0xee, 0xd1, 0xd4, 0x0b, 0x97, 0x48, 0xee, 0xd1, 0xd6, 0x0a, 0x95, + 0x49, 0xee, 0xc1, 0xd4, 0x0a, 0x65, 0x00, 0x44, 0x02, 0x97, 0xc3, + 0x92, 0x44, 0xa2, 0xc2, 0xd2, 0x43, 0xf1, 0x09, 0x93, 0x01, 0x3f, + 0x19, 0xd3, 0xc0, 0x85, 0x11, 0x93, 0x44, 0x96, 0x09, 0xb3, 0xff, + 0xfc, 0x19, 0xd3, 0x44, 0x96, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, + 0x08, 0x0b, 0x01, 0x00, 0x0d, 0x03, 0x03, 0x00, 0x03, 0x96, 0x41, + 0x02, 0x03, 0x99, 0xc4, 0x94, 0x42, 0x04, 0xc1, 0x04, 0xc2, 0x94, + 0xc3, 0xd4, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, + 0x00, 0x40, 0x92, 0x19, 0xd3, 0x94, 0xec, 0x13, 0x97, 0x95, 0xec, + 0x1b, 0xd7, 0x02, 0x80, 0x11, 0x93, 0x99, 0xec, 0x19, 0xd3, 0x7c, + 0x96, 0x0b, 0x97, 0xa0, 0x00, 0x1b, 0xd7, 0x6e, 0xec, 0x0a, 0x65, + 0x0e, 0x42, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xb3, 0xff, 0xbf, 0x11, + 0xa3, 0x9a, 0xec, 0xc2, 0xd2, 0x0a, 0x65, 0xeb, 0x43, 0x02, 0x97, + 0xc3, 0x92, 0x09, 0xa3, 0xc0, 0x00, 0xc2, 0xd2, 0x0a, 0x65, 0xe9, + 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xb3, 0xbf, 0xff, 0xc2, 0xd2, + 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x47, 0x20, 0x08, 0x0b, 0x01, + 0x00, 0x14, 0x99, 0x03, 0x80, 0x0c, 0xb3, 0x00, 0x10, 0x40, 0x42, + 0x02, 0x4e, 0x0f, 0x9f, 0x3d, 0xf0, 0x11, 0x93, 0x9f, 0xec, 0x41, + 0x02, 0x19, 0xd3, 0x9f, 0xec, 0x11, 0x93, 0x74, 0xf7, 0x40, 0x42, + 0x02, 0x4e, 0x0f, 0x9f, 0x2a, 0xef, 0x0a, 0x65, 0xfe, 0x7f, 0x02, + 0x97, 0xc3, 0x92, 0x09, 0xa3, 0x00, 0x04, 0xc2, 0xd2, 0x0f, 0x9f, + 0x57, 0xf0, 0x11, 0x93, 0x94, 0xec, 0x02, 0xd2, 0x40, 0x42, 0x02, + 0x5e, 0x0f, 0x9f, 0x76, 0xef, 0x41, 0x92, 0x19, 0xd3, 0x94, 0xec, + 0x19, 0xd3, 0x9f, 0xec, 0x12, 0x95, 0x02, 0x80, 0x1a, 0xd5, 0x95, + 0xec, 0x13, 0x97, 0x7c, 0x96, 0x1b, 0xd7, 0x99, 0xec, 0x0a, 0x65, + 0x0e, 0x42, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xb3, 0x00, 0x40, 0x19, + 0xd3, 0x9a, 0xec, 0x09, 0x63, 0x00, 0x40, 0xc2, 0xd2, 0x02, 0x94, + 0x1a, 0xd5, 0x7c, 0x96, 0x0c, 0xb3, 0x00, 0x08, 0x40, 0x42, 0x02, + 0x5e, 0x0f, 0x9f, 0x56, 0xef, 0x0c, 0xb3, 0xff, 0x07, 0x0f, 0x9f, + 0x5a, 0xef, 0x11, 0x93, 0x06, 0x80, 0x09, 0xb3, 0xff, 0x07, 0x09, + 0x03, 0x00, 0xa0, 0x19, 0xd3, 0x97, 0xec, 0x40, 0x98, 0x0b, 0x97, + 0x9c, 0xec, 0x04, 0x95, 0x03, 0x05, 0x14, 0x03, 0x97, 0xec, 0x46, + 0x02, 0xc1, 0x92, 0xc2, 0xd2, 0x41, 0x08, 0x42, 0x48, 0x02, 0x9e, + 0x0f, 0x9f, 0x61, 0xef, 0x11, 0x93, 0x97, 0xec, 0xc1, 0x92, 0xc5, + 0xd2, 0x5f, 0xb2, 0x19, 0xd3, 0x9b, 0xec, 0x0f, 0x9f, 0x79, 0xef, + 0x13, 0x97, 0x98, 0xec, 0xc5, 0xd6, 0x11, 0x93, 0x03, 0x80, 0x09, + 0xb3, 0x00, 0x08, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x8f, 0xef, + 0x11, 0x93, 0x7a, 0xf7, 0x41, 0x02, 0x19, 0xd3, 0x7a, 0xf7, 0x11, + 0x93, 0x79, 0xf7, 0x09, 0xa3, 0x00, 0x10, 0x19, 0xd3, 0x79, 0xf7, + 0x40, 0x98, 0x1c, 0xd9, 0x9b, 0xec, 0x12, 0x95, 0x9b, 0xec, 0x40, + 0x44, 0x02, 0x4e, 0x0f, 0x9f, 0x2c, 0xf0, 0x0a, 0xb3, 0x08, 0x00, + 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0xad, 0xef, 0x0a, 0xb3, 0x07, + 0x00, 0x09, 0x05, 0xa9, 0xec, 0xc2, 0x94, 0x01, 0xd4, 0x09, 0x03, + 0xa1, 0xec, 0xc1, 0x92, 0x19, 0xd3, 0x9b, 0xec, 0xc5, 0x94, 0x0a, + 0xb5, 0x00, 0xff, 0x01, 0xa5, 0xc5, 0xd4, 0x0f, 0x9f, 0xb9, 0xef, + 0x0a, 0x05, 0xff, 0xff, 0x0a, 0x03, 0xb1, 0xec, 0xc1, 0x92, 0x01, + 0xd2, 0x1a, 0xd5, 0x9b, 0xec, 0xc5, 0x96, 0x0b, 0x07, 0xff, 0xff, + 0xc5, 0xd6, 0x11, 0x93, 0x97, 0xec, 0xc5, 0x98, 0xc1, 0xd8, 0x11, + 0x93, 0x97, 0xec, 0x09, 0x05, 0x0b, 0x00, 0x03, 0xd4, 0xc2, 0x96, + 0x06, 0xd6, 0x7b, 0x95, 0x7a, 0x95, 0x4c, 0x02, 0xc1, 0x92, 0x59, + 0x93, 0x59, 0x93, 0x01, 0xa5, 0x01, 0x98, 0x0c, 0xf5, 0x7b, 0x93, + 0x09, 0x09, 0x01, 0x00, 0x06, 0x92, 0x09, 0xb3, 0xff, 0x00, 0x04, + 0xd2, 0x5c, 0x93, 0x59, 0x93, 0x04, 0x94, 0x01, 0xa5, 0x03, 0x96, + 0xc3, 0xd4, 0x11, 0x93, 0x97, 0xec, 0x4c, 0x02, 0x05, 0xd2, 0xc1, + 0x92, 0x09, 0xb3, 0x00, 0xff, 0x7c, 0x95, 0x7a, 0x95, 0x02, 0xa3, + 0x05, 0x98, 0xc4, 0xd2, 0x12, 0x95, 0x97, 0xec, 0x45, 0x04, 0x02, + 0x97, 0xc3, 0x92, 0x09, 0xa3, 0x00, 0x01, 0xc2, 0xd2, 0x12, 0x95, + 0x9b, 0xec, 0x0a, 0xb3, 0x08, 0x00, 0x40, 0x42, 0x02, 0x4e, 0x0f, + 0x9f, 0x01, 0xf0, 0x12, 0x95, 0x97, 0xec, 0x4a, 0x04, 0x02, 0x99, + 0xc4, 0x92, 0x01, 0x98, 0x0c, 0xf3, 0x7b, 0x93, 0x41, 0x02, 0x0f, + 0x9f, 0x22, 0xf0, 0x43, 0x44, 0x02, 0x8e, 0x0f, 0x9f, 0x23, 0xf0, + 0x11, 0x93, 0x97, 0xec, 0x42, 0x02, 0x0a, 0x05, 0xff, 0xff, 0xc1, + 0xd4, 0x11, 0x93, 0x97, 0xec, 0x4a, 0x02, 0x12, 0x95, 0x60, 0x96, + 0xc1, 0xd4, 0x12, 0x95, 0x97, 0xec, 0x4b, 0x04, 0x02, 0x97, 0xc3, + 0x92, 0x09, 0xb3, 0x1f, 0xff, 0xc2, 0xd2, 0x12, 0x95, 0x97, 0xec, + 0x4b, 0x04, 0x11, 0x93, 0x62, 0x96, 0x41, 0x93, 0x59, 0x93, 0x02, + 0x99, 0xc4, 0xa2, 0xc2, 0xd2, 0xc5, 0x92, 0x19, 0xd3, 0x98, 0xec, + 0x0a, 0x95, 0x0c, 0x02, 0x1a, 0xd5, 0x02, 0x80, 0x0f, 0x9f, 0x57, + 0xf0, 0x09, 0x63, 0xfe, 0x7f, 0x01, 0x97, 0xc3, 0x94, 0x0a, 0xa5, + 0x00, 0x04, 0xc1, 0xd4, 0x11, 0x93, 0x9f, 0xec, 0x09, 0xa3, 0x00, + 0x01, 0x19, 0xd3, 0x9f, 0xec, 0x40, 0xf0, 0xdf, 0xee, 0x0f, 0x9f, + 0x57, 0xf0, 0x11, 0x93, 0x94, 0xec, 0x41, 0x42, 0x02, 0x5e, 0x0f, + 0x9f, 0x4c, 0xf0, 0x40, 0xf0, 0xdf, 0xee, 0x11, 0x93, 0x95, 0xec, + 0x44, 0xb2, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x57, 0xf0, 0x48, + 0x98, 0x1c, 0xd9, 0x02, 0x80, 0x11, 0x93, 0x91, 0xec, 0x41, 0x22, + 0x0a, 0x95, 0x57, 0xf0, 0x88, 0xd4, 0x88, 0xdc, 0x91, 0x9a, 0x47, + 0x00, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, + 0x11, 0x93, 0x04, 0x82, 0x48, 0xb2, 0x40, 0x42, 0x02, 0x4e, 0x0f, + 0x9f, 0x6e, 0xf0, 0x0a, 0x65, 0xfd, 0x7d, 0x02, 0x97, 0xc3, 0x92, + 0x09, 0xb3, 0xff, 0xfe, 0xc2, 0xd2, 0x41, 0x92, 0x19, 0xd3, 0xbf, + 0xec, 0x11, 0x93, 0x04, 0x82, 0x43, 0xb2, 0x12, 0x95, 0x03, 0x82, + 0x02, 0xb3, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x95, 0xf0, 0x0a, + 0xb3, 0x00, 0xff, 0x48, 0xa2, 0x19, 0xd3, 0x03, 0x82, 0x40, 0xf0, + 0x82, 0xf3, 0x11, 0x93, 0xbf, 0xec, 0x41, 0x42, 0x02, 0x5e, 0x0f, + 0x9f, 0x95, 0xf0, 0x11, 0x93, 0x07, 0x82, 0x11, 0x43, 0x03, 0xec, + 0x02, 0x0e, 0x0f, 0x9f, 0x95, 0xf0, 0x11, 0x93, 0x03, 0x82, 0x09, + 0xa3, 0x00, 0x01, 0x19, 0xd3, 0x03, 0x82, 0x40, 0x96, 0x1b, 0xd7, + 0xbf, 0xec, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, + 0x00, 0x11, 0x93, 0x20, 0xbc, 0xc8, 0xd2, 0x40, 0xf0, 0xe9, 0xf0, + 0x41, 0x00, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x42, 0x20, 0x08, + 0x0b, 0x01, 0x00, 0x0d, 0x03, 0x05, 0x00, 0x05, 0x94, 0x41, 0x02, + 0xc1, 0x92, 0x01, 0x97, 0xc3, 0x96, 0xc2, 0xd6, 0x0a, 0x45, 0x00, + 0x95, 0x02, 0x5e, 0x0f, 0x9f, 0xe6, 0xf0, 0xc1, 0x92, 0x41, 0xb2, + 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0xe6, 0xf0, 0x11, 0x93, 0xc0, + 0xec, 0x40, 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0xe6, 0xf0, 0x41, 0x98, + 0x1c, 0xd9, 0xc0, 0xec, 0x12, 0x95, 0x02, 0x80, 0x01, 0xd4, 0x40, + 0xf0, 0xfe, 0xf1, 0x0b, 0x67, 0xfd, 0x7d, 0x03, 0x99, 0xc4, 0x92, + 0x0c, 0x99, 0x96, 0x03, 0x1c, 0xd9, 0x06, 0x82, 0x41, 0x98, 0x1c, + 0xd9, 0x02, 0x82, 0x42, 0x98, 0x1c, 0xd9, 0x05, 0x82, 0x0c, 0x69, + 0x80, 0x7f, 0x1c, 0xd9, 0x00, 0xb0, 0x09, 0xa3, 0x00, 0x01, 0xc3, + 0xd2, 0x01, 0x94, 0x0a, 0xb3, 0x04, 0x00, 0x40, 0x42, 0x02, 0x4e, + 0x0f, 0x9f, 0xe4, 0xf0, 0x42, 0xa4, 0x1a, 0xd5, 0x02, 0x80, 0x42, + 0x00, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x42, 0x20, 0x08, 0x0b, + 0x01, 0x00, 0x05, 0x92, 0xc5, 0xd2, 0x60, 0xb2, 0x40, 0x42, 0x02, + 0x4e, 0x0f, 0x9f, 0xf6, 0xf0, 0x40, 0xf0, 0xd2, 0xf6, 0xc5, 0x94, + 0x0a, 0xb3, 0x10, 0x00, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0xff, + 0xf0, 0x40, 0xf0, 0xc0, 0xf5, 0xc5, 0x96, 0x0b, 0xb3, 0x40, 0x00, + 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x08, 0xf1, 0x40, 0xf0, 0xfa, + 0xf4, 0xc5, 0x94, 0x0a, 0xb3, 0x01, 0x00, 0x40, 0x42, 0x02, 0x4e, + 0x0f, 0x9f, 0x70, 0xf1, 0x13, 0x97, 0x21, 0xbc, 0x01, 0xd6, 0x0b, + 0xb3, 0x02, 0x00, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x1a, 0xf1, + 0x40, 0xf0, 0x62, 0xfb, 0x01, 0x94, 0x0a, 0xb3, 0x04, 0x00, 0x40, + 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x23, 0xf1, 0x40, 0xf0, 0x6c, 0xfb, + 0x01, 0x96, 0x0b, 0xb3, 0x01, 0x00, 0x40, 0x42, 0x02, 0x4e, 0x0f, + 0x9f, 0x4c, 0xf1, 0x40, 0xf0, 0xb0, 0xfa, 0x41, 0x92, 0x19, 0xd3, + 0x73, 0xf7, 0x11, 0x93, 0x03, 0xec, 0x09, 0x43, 0x40, 0x00, 0x02, + 0x5e, 0x0f, 0x9f, 0x39, 0xf1, 0x40, 0x94, 0x1a, 0xd5, 0x73, 0xf7, + 0x11, 0x93, 0x00, 0xec, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x55, + 0xf1, 0x11, 0x93, 0xc1, 0xec, 0x40, 0x42, 0x02, 0x5e, 0x0f, 0x9f, + 0x55, 0xf1, 0x40, 0xf0, 0xe0, 0xf1, 0x41, 0x96, 0x1b, 0xd7, 0xc1, + 0xec, 0x0f, 0x9f, 0x55, 0xf1, 0x01, 0x94, 0x0a, 0xb3, 0x08, 0x00, + 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x55, 0xf1, 0x40, 0xf0, 0x7c, + 0xfb, 0x01, 0x96, 0x0b, 0xb3, 0x10, 0x00, 0x40, 0x42, 0x02, 0x4e, + 0x0f, 0x9f, 0x5e, 0xf1, 0x40, 0xf0, 0x87, 0xfb, 0x11, 0x93, 0x10, + 0xec, 0x42, 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0x67, 0xf1, 0x44, 0x92, + 0x0f, 0x9f, 0x6b, 0xf1, 0x41, 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0x6d, + 0xf1, 0x19, 0xd3, 0x0b, 0xbc, 0x40, 0x94, 0x1a, 0xd5, 0x10, 0xec, + 0xc5, 0x96, 0x0b, 0xb3, 0x80, 0x00, 0x40, 0x42, 0x02, 0x4e, 0x0f, + 0x9f, 0xba, 0xf1, 0x11, 0x93, 0x28, 0xbc, 0x01, 0xd2, 0x09, 0xb3, + 0x40, 0x00, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x82, 0xf1, 0x40, + 0xf0, 0xb5, 0xf6, 0x01, 0x94, 0x0a, 0xb3, 0x02, 0x00, 0x40, 0x42, + 0x02, 0x4e, 0x0f, 0x9f, 0x95, 0xf1, 0x40, 0xf0, 0x6d, 0xee, 0x40, + 0xf0, 0x8f, 0xfb, 0x40, 0xf0, 0xc3, 0xf1, 0x40, 0x96, 0x1b, 0xd7, + 0x00, 0xec, 0x41, 0x92, 0x19, 0xd3, 0x76, 0xf7, 0x01, 0x94, 0x0a, + 0xb3, 0x04, 0x00, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0xb1, 0xf1, + 0x40, 0xf0, 0x9e, 0xfb, 0x09, 0x63, 0x00, 0x44, 0x01, 0x97, 0xc3, + 0x94, 0x48, 0xa4, 0xc1, 0xd4, 0x00, 0xee, 0x40, 0x92, 0x19, 0xd3, + 0x12, 0x95, 0x19, 0xd3, 0x10, 0x95, 0x19, 0xd3, 0x02, 0x80, 0x19, + 0xd3, 0x03, 0x82, 0x41, 0x92, 0x19, 0xd3, 0x76, 0xf7, 0x01, 0x94, + 0x0a, 0xb3, 0x08, 0x00, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0xba, + 0xf1, 0x40, 0xf0, 0xae, 0xfb, 0x0a, 0x65, 0x00, 0x44, 0x02, 0x97, + 0xc3, 0x92, 0x44, 0xa2, 0xc2, 0xd2, 0x42, 0x00, 0x88, 0x98, 0x90, + 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x09, 0x63, 0x00, 0x40, + 0x19, 0xd3, 0xf2, 0xbd, 0x0a, 0x65, 0xea, 0x43, 0x02, 0x97, 0xc3, + 0x92, 0x44, 0xa2, 0xc2, 0xd2, 0x0a, 0x65, 0xe9, 0x43, 0x02, 0x97, + 0xc3, 0x92, 0x09, 0xa3, 0x40, 0x00, 0xc2, 0xd2, 0x0a, 0x65, 0xeb, + 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xa3, 0xc0, 0x00, 0xc2, 0xd2, + 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x09, + 0x63, 0x00, 0x80, 0x19, 0xd3, 0xf2, 0xbd, 0x0a, 0x65, 0xe8, 0x43, + 0x02, 0x97, 0xc3, 0x92, 0x09, 0xa3, 0xc0, 0x00, 0xc2, 0xd2, 0x0a, + 0x65, 0xeb, 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xb3, 0xbf, 0xff, + 0xc2, 0xd2, 0x0a, 0x65, 0xea, 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, + 0xb3, 0xfb, 0xff, 0xc2, 0xd2, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, + 0x08, 0x0b, 0x01, 0x00, 0x09, 0x93, 0x00, 0x01, 0x19, 0xd3, 0x02, + 0x80, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, + 0x09, 0x93, 0x00, 0x09, 0x19, 0xd3, 0x02, 0x80, 0x40, 0xf0, 0xfe, + 0xf1, 0x40, 0x92, 0x19, 0xd3, 0x94, 0xec, 0xc8, 0xd2, 0x09, 0x93, + 0x91, 0xec, 0xc8, 0xd2, 0x40, 0xf0, 0xd0, 0xee, 0x42, 0x00, 0x88, + 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x40, 0xf0, + 0xd8, 0xf4, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x2d, 0xf2, 0x0a, + 0x65, 0xfe, 0x7f, 0x02, 0x97, 0xc3, 0x92, 0x44, 0xa2, 0xc2, 0xd2, + 0x0f, 0x9f, 0x3a, 0xf2, 0x40, 0xf0, 0x3c, 0xf2, 0x40, 0x42, 0x02, + 0x5e, 0x0f, 0x9f, 0x3a, 0xf2, 0xc8, 0xd2, 0x09, 0x93, 0x91, 0xec, + 0xc8, 0xd2, 0x40, 0xf0, 0xd0, 0xee, 0x42, 0x00, 0x88, 0x98, 0x90, + 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x11, 0x93, 0xf1, 0xbd, + 0x19, 0xd3, 0xb6, 0xec, 0x11, 0x93, 0xb4, 0xec, 0x40, 0x42, 0x02, + 0x5e, 0x0f, 0x9f, 0x54, 0xf2, 0x09, 0x63, 0x00, 0x80, 0x01, 0x97, + 0xc3, 0x94, 0x0a, 0x07, 0x07, 0x00, 0xc1, 0xd6, 0x0a, 0x05, 0x00, + 0xa0, 0x1a, 0xd5, 0x96, 0xec, 0x11, 0x93, 0xb6, 0xec, 0x19, 0xd3, + 0x01, 0x80, 0x0a, 0x65, 0xfe, 0x7f, 0x02, 0x97, 0xc3, 0x92, 0x41, + 0xa2, 0xc2, 0xd2, 0x40, 0x92, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, + 0x41, 0x20, 0x08, 0x0b, 0x01, 0x00, 0x13, 0x97, 0xb4, 0xec, 0x40, + 0x46, 0x02, 0x5e, 0x0f, 0x9f, 0xc3, 0xf2, 0x12, 0x95, 0x96, 0xec, + 0x0a, 0x03, 0x07, 0x00, 0xc1, 0x92, 0xc2, 0xd2, 0x11, 0x93, 0x96, + 0xec, 0x09, 0x05, 0x01, 0x00, 0x48, 0x02, 0xc1, 0x92, 0xc2, 0xd2, + 0x11, 0x93, 0x96, 0xec, 0x4e, 0x02, 0xc1, 0x94, 0xc5, 0xd6, 0xc5, + 0x92, 0x11, 0x07, 0x96, 0xec, 0x0b, 0x03, 0x0f, 0x00, 0xc1, 0x98, + 0x46, 0x06, 0x7a, 0x93, 0x79, 0x93, 0x5c, 0x95, 0x5a, 0x95, 0x02, + 0xa3, 0xc3, 0xd2, 0x04, 0x95, 0xc5, 0x96, 0x41, 0x06, 0xc5, 0xd6, + 0x42, 0x46, 0x02, 0x9e, 0x0f, 0x9f, 0x7d, 0xf2, 0x11, 0x93, 0x96, + 0xec, 0x09, 0x05, 0x05, 0x00, 0x41, 0x02, 0xc1, 0x92, 0xc2, 0xd2, + 0x11, 0x93, 0x96, 0xec, 0xc1, 0x92, 0x09, 0xb5, 0x1f, 0x00, 0x43, + 0x44, 0x02, 0x8e, 0x0f, 0x9f, 0xaa, 0xf2, 0x40, 0x44, 0x02, 0x4e, + 0x0f, 0x9f, 0xab, 0xf2, 0x0a, 0x05, 0xff, 0xff, 0x0f, 0x9f, 0xab, + 0xf2, 0x43, 0x94, 0x11, 0x93, 0x96, 0xec, 0x42, 0x02, 0xc1, 0xd4, + 0x13, 0x97, 0x96, 0xec, 0x03, 0x93, 0xd1, 0x94, 0x7a, 0x95, 0x7a, + 0x95, 0xc1, 0x92, 0x59, 0x93, 0x59, 0x93, 0x01, 0x05, 0x49, 0x06, + 0xc3, 0x92, 0x7f, 0xb2, 0x01, 0x05, 0x1a, 0xd5, 0xb4, 0xec, 0x0a, + 0x05, 0xf2, 0xff, 0x1a, 0xd5, 0x92, 0xec, 0x11, 0x93, 0x92, 0xec, + 0x12, 0x95, 0xb6, 0xec, 0x02, 0x43, 0x02, 0x8e, 0x0f, 0x9f, 0x11, + 0xf3, 0x02, 0x0e, 0x0f, 0x9f, 0xe4, 0xf2, 0x11, 0x93, 0x7a, 0xf7, + 0x41, 0x02, 0x19, 0xd3, 0x7a, 0xf7, 0x11, 0x93, 0x79, 0xf7, 0x09, + 0xa3, 0x80, 0x00, 0x19, 0xd3, 0x79, 0xf7, 0x09, 0x63, 0x00, 0x80, + 0x01, 0x95, 0xc2, 0x94, 0x1a, 0xd5, 0xb5, 0xec, 0x40, 0x96, 0x1b, + 0xd7, 0xb4, 0xec, 0x0f, 0x9f, 0x29, 0xf3, 0x11, 0x93, 0x03, 0x80, + 0x09, 0xb3, 0x00, 0x40, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0xf6, + 0xf2, 0x11, 0x93, 0xc0, 0xec, 0x40, 0x42, 0x02, 0x5e, 0x0f, 0x9f, + 0xf6, 0xf2, 0x40, 0xf0, 0x3d, 0xf3, 0x0f, 0x9f, 0x2b, 0xf3, 0x41, + 0x92, 0xc8, 0xd2, 0x0a, 0x95, 0x91, 0xec, 0xc8, 0xd4, 0x40, 0xf0, + 0xd0, 0xee, 0x42, 0x00, 0x11, 0x93, 0xc0, 0xec, 0x40, 0x42, 0x02, + 0x4e, 0x0f, 0x9f, 0x09, 0xf3, 0x42, 0x96, 0x1b, 0xd7, 0xc0, 0xec, + 0x0f, 0x9f, 0x2b, 0xf3, 0x0a, 0x65, 0xfe, 0x7f, 0x02, 0x97, 0xc3, + 0x92, 0x42, 0xa2, 0xc2, 0xd2, 0x0f, 0x9f, 0x2b, 0xf3, 0x12, 0x45, + 0x03, 0xec, 0x02, 0x4e, 0x0f, 0x9f, 0x23, 0xf3, 0x11, 0x93, 0x7a, + 0xf7, 0x41, 0x02, 0x19, 0xd3, 0x7a, 0xf7, 0x11, 0x93, 0x79, 0xf7, + 0x09, 0xa3, 0x00, 0x08, 0x19, 0xd3, 0x79, 0xf7, 0x1a, 0xd5, 0x92, + 0xec, 0x11, 0x93, 0x92, 0xec, 0x19, 0x25, 0x92, 0xec, 0x09, 0x63, + 0x00, 0x80, 0x19, 0xd3, 0xf2, 0xbd, 0x41, 0x00, 0x88, 0x98, 0x90, + 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x40, 0xf0, 0x3d, 0xf3, + 0x40, 0x92, 0xc8, 0xd2, 0x09, 0x93, 0x91, 0xec, 0xc8, 0xd2, 0x40, + 0xf0, 0xd0, 0xee, 0x42, 0x00, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, + 0x08, 0x0b, 0x01, 0x00, 0x11, 0x93, 0x75, 0xf7, 0x40, 0x42, 0x02, + 0x4e, 0x0f, 0x9f, 0x4d, 0xf3, 0x0a, 0x65, 0xbc, 0x69, 0x02, 0x97, + 0xc3, 0x92, 0x09, 0x83, 0x00, 0x02, 0xc2, 0xd2, 0x11, 0x93, 0x03, + 0x80, 0x09, 0xb3, 0x00, 0x40, 0x40, 0x42, 0x02, 0x5e, 0x0f, 0x9f, + 0x60, 0xf3, 0x11, 0x93, 0x7a, 0xf7, 0x41, 0x02, 0x19, 0xd3, 0x7a, + 0xf7, 0x11, 0x93, 0x79, 0xf7, 0x09, 0xa3, 0x00, 0x20, 0x19, 0xd3, + 0x79, 0xf7, 0x11, 0x93, 0xb5, 0xec, 0x19, 0xd3, 0x04, 0x80, 0x12, + 0x95, 0xb4, 0xec, 0x1a, 0xd5, 0x05, 0x80, 0x09, 0x63, 0x00, 0x80, + 0x01, 0x97, 0xc3, 0x96, 0x1b, 0xd7, 0xb5, 0xec, 0x40, 0x94, 0x1a, + 0xd5, 0xb4, 0xec, 0x19, 0xd3, 0xf2, 0xbd, 0x88, 0x98, 0x90, 0x9a, + 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x09, 0x93, 0x96, 0x03, 0x19, + 0xd3, 0x06, 0x82, 0x09, 0x93, 0x00, 0x01, 0x19, 0xd3, 0x03, 0x82, + 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x47, 0x20, 0x08, 0x0b, 0x01, + 0x00, 0x11, 0x93, 0x01, 0x82, 0xc5, 0xd2, 0x40, 0x94, 0x01, 0xd4, + 0x13, 0x97, 0xb8, 0xec, 0x02, 0xd6, 0x03, 0x95, 0x0c, 0x99, 0xbb, + 0xec, 0x04, 0x05, 0x13, 0x97, 0x03, 0xec, 0x01, 0x27, 0x02, 0x99, + 0xc4, 0x92, 0x03, 0x03, 0xc2, 0xd2, 0x14, 0x99, 0xba, 0xec, 0x03, + 0x09, 0x1c, 0xd9, 0xba, 0xec, 0x12, 0x95, 0x04, 0x82, 0x0a, 0xb3, + 0x02, 0x00, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0xc6, 0xf4, 0x01, + 0x92, 0x03, 0xd2, 0x0a, 0xa3, 0x02, 0x00, 0x19, 0xd3, 0x04, 0x82, + 0x02, 0x96, 0x0b, 0x05, 0x01, 0x00, 0x1a, 0xd5, 0xb8, 0xec, 0xc5, + 0x92, 0x43, 0x42, 0x02, 0x9e, 0x0f, 0x9f, 0xce, 0xf3, 0x42, 0x44, + 0x02, 0x8e, 0x0f, 0x9f, 0xce, 0xf3, 0x11, 0x93, 0xbf, 0xec, 0x40, + 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0xce, 0xf3, 0x0c, 0x49, 0xd3, 0x08, + 0x02, 0x8e, 0x0f, 0x9f, 0xce, 0xf3, 0x11, 0x63, 0x07, 0x82, 0x11, + 0xa3, 0x07, 0x82, 0x71, 0x93, 0x79, 0x93, 0x79, 0x93, 0x79, 0x93, + 0x03, 0xd2, 0xc5, 0x94, 0x0a, 0xb5, 0xfc, 0xff, 0x04, 0xd4, 0x03, + 0x96, 0x40, 0x46, 0x02, 0x5e, 0x0f, 0x9f, 0xdd, 0xf3, 0x11, 0x93, + 0xb8, 0xec, 0x41, 0x42, 0x02, 0x8e, 0x0f, 0x9f, 0xe4, 0xf3, 0xc5, + 0x98, 0x0c, 0x03, 0xff, 0xff, 0x42, 0x42, 0x02, 0x8e, 0x0f, 0x9f, + 0x0b, 0xf4, 0x0a, 0x95, 0xbb, 0xec, 0x42, 0x92, 0x19, 0xd3, 0xb9, + 0xec, 0xc5, 0x96, 0x43, 0x46, 0x02, 0x9e, 0x0f, 0x9f, 0xfd, 0xf3, + 0x0b, 0x07, 0xfc, 0xff, 0xc5, 0xd6, 0xd2, 0x98, 0x1c, 0xd9, 0xc8, + 0xbc, 0xd2, 0x96, 0x1b, 0xd7, 0xca, 0xbc, 0x09, 0x03, 0xff, 0xff, + 0x40, 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0xe9, 0xf3, 0x19, 0xd3, 0xb9, + 0xec, 0x40, 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0x09, 0xf4, 0x0a, 0x05, + 0xfe, 0xff, 0xca, 0xd2, 0xc2, 0xd2, 0x0f, 0x9f, 0x0b, 0xf4, 0x1a, + 0xd5, 0x93, 0xec, 0x03, 0x98, 0x40, 0x48, 0x02, 0x5e, 0x0f, 0x9f, + 0x38, 0xf4, 0x11, 0x93, 0xb8, 0xec, 0x41, 0x42, 0x02, 0x9e, 0x0f, + 0x9f, 0x1b, 0xf4, 0x04, 0x94, 0x48, 0x44, 0x02, 0x4e, 0x0f, 0x9f, + 0x26, 0xf4, 0x41, 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0x38, 0xf4, 0x11, + 0x93, 0x04, 0x82, 0x41, 0xb2, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, + 0x38, 0xf4, 0x41, 0x96, 0x01, 0xd6, 0x0a, 0x65, 0xbd, 0x43, 0x02, + 0x99, 0xc4, 0x92, 0x09, 0xa3, 0x80, 0x00, 0xc2, 0xd2, 0x0a, 0x65, + 0xe8, 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xb3, 0xbf, 0xff, 0xc2, + 0xd2, 0x0f, 0x9f, 0x97, 0xf4, 0xc5, 0x98, 0x43, 0x48, 0x02, 0x9e, + 0x0f, 0x9f, 0x97, 0xf4, 0x4f, 0x96, 0x0c, 0xb3, 0x01, 0x00, 0x40, + 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x45, 0xf4, 0x47, 0x96, 0x11, 0x93, + 0xb7, 0xec, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x73, 0xf4, 0x11, + 0x93, 0xb8, 0xec, 0x41, 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0x73, 0xf4, + 0x12, 0x95, 0x00, 0x82, 0x0a, 0x05, 0xff, 0xaf, 0x05, 0xd4, 0xc8, + 0xd6, 0xc8, 0xd2, 0x40, 0xf0, 0x18, 0xf7, 0x42, 0x00, 0x05, 0x96, + 0xc3, 0x94, 0x01, 0xb5, 0x40, 0x44, 0x02, 0x5e, 0x0f, 0x9f, 0x68, + 0xf4, 0x11, 0x93, 0xba, 0xec, 0x4d, 0x42, 0x02, 0x8e, 0x0f, 0x9f, + 0x73, 0xf4, 0x06, 0x98, 0x50, 0x98, 0x1c, 0xd9, 0xa2, 0xbc, 0x40, + 0x98, 0x1c, 0xd9, 0xa2, 0xbc, 0x40, 0x92, 0x03, 0xd2, 0x0f, 0x9f, + 0x9c, 0xf4, 0x03, 0x94, 0x40, 0x44, 0x02, 0x5e, 0x0f, 0x9f, 0x80, + 0xf4, 0x0a, 0x65, 0x5e, 0x43, 0x02, 0x97, 0xc3, 0x92, 0x48, 0xa2, + 0xc2, 0xd2, 0x0f, 0x9f, 0x9c, 0xf4, 0x11, 0x93, 0xb8, 0xec, 0x0c, + 0x99, 0xbb, 0xec, 0x04, 0x03, 0x04, 0x96, 0x13, 0x25, 0x03, 0xec, + 0xc1, 0xd4, 0x11, 0x93, 0xba, 0xec, 0x19, 0x05, 0xba, 0xec, 0x1b, + 0xd7, 0x01, 0x82, 0x0a, 0x65, 0xfd, 0x7d, 0x02, 0x99, 0xc4, 0x92, + 0x43, 0xa2, 0xc2, 0xd2, 0x41, 0x92, 0x01, 0xd2, 0x03, 0x94, 0x40, + 0x44, 0x02, 0x5e, 0x0f, 0x9f, 0xb0, 0xf4, 0x11, 0x93, 0xb9, 0xec, + 0x40, 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0xa8, 0xf4, 0x19, 0xd3, 0xb8, + 0xec, 0x19, 0xd3, 0xba, 0xec, 0x19, 0xd3, 0xbb, 0xec, 0x03, 0x96, + 0x40, 0x46, 0x02, 0x5e, 0x0f, 0x9f, 0xb0, 0xf4, 0x41, 0x98, 0x1c, + 0xd9, 0xb7, 0xec, 0x11, 0x93, 0xbf, 0xec, 0x41, 0x42, 0x02, 0x5e, + 0x0f, 0x9f, 0xc1, 0xf4, 0x11, 0x93, 0x00, 0x82, 0x19, 0xd3, 0x02, + 0x82, 0x0a, 0x65, 0xfd, 0x7d, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xa3, + 0x00, 0x01, 0xc2, 0xd2, 0x40, 0x98, 0x1c, 0xd9, 0xbf, 0xec, 0x0f, + 0x9f, 0xc9, 0xf4, 0x01, 0x92, 0x19, 0xd3, 0xb7, 0xec, 0x01, 0x94, + 0x40, 0x44, 0x02, 0x5e, 0x0f, 0x9f, 0xd5, 0xf4, 0x0a, 0x65, 0xea, + 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xb3, 0xfb, 0xff, 0xc2, 0xd2, + 0x47, 0x00, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, + 0x00, 0x12, 0x95, 0x03, 0x80, 0x0a, 0xb3, 0x00, 0x40, 0x40, 0x42, + 0x02, 0x4e, 0x0f, 0x9f, 0xf4, 0xf4, 0x0a, 0xb7, 0x00, 0x08, 0x40, + 0x46, 0x02, 0x5e, 0x0f, 0x9f, 0xf7, 0xf4, 0x11, 0x93, 0x03, 0xec, + 0x41, 0x02, 0x09, 0xb3, 0xfe, 0xff, 0x12, 0x95, 0x07, 0x80, 0x01, + 0x45, 0x02, 0x8e, 0x0f, 0x9f, 0xf7, 0xf4, 0x41, 0x92, 0x0f, 0x9f, + 0xf8, 0xf4, 0x40, 0x92, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x41, + 0x20, 0x08, 0x0b, 0x01, 0x00, 0x0a, 0x65, 0xe9, 0x43, 0x02, 0x97, + 0xc3, 0x92, 0x09, 0xa3, 0x40, 0x00, 0xc2, 0xd2, 0x13, 0x97, 0x6e, + 0xec, 0x0b, 0x47, 0xa0, 0x00, 0x02, 0x5e, 0x0f, 0x9f, 0x23, 0xf5, + 0x09, 0x63, 0x08, 0x43, 0x0a, 0x65, 0xff, 0x5f, 0x01, 0x99, 0xc4, + 0xd4, 0x0a, 0x95, 0x9b, 0xec, 0xd2, 0x96, 0x1b, 0xd7, 0xfa, 0xbc, + 0xd2, 0x96, 0xc4, 0xd6, 0xd2, 0x98, 0x1c, 0xd9, 0xfa, 0xbc, 0xd2, + 0x96, 0xc1, 0xd6, 0xc2, 0x94, 0x1a, 0xd5, 0xfa, 0xbc, 0x0f, 0x9f, + 0x61, 0xf5, 0x0c, 0x69, 0xff, 0x6f, 0x1c, 0xd9, 0xf8, 0xbc, 0x0b, + 0x47, 0x10, 0x95, 0x02, 0x5e, 0x0f, 0x9f, 0x3b, 0xf5, 0x0a, 0x95, + 0x6f, 0xec, 0x09, 0x63, 0x06, 0x43, 0x01, 0x99, 0xc4, 0xd6, 0xd2, + 0x96, 0x1b, 0xd7, 0xf8, 0xbc, 0x0c, 0x69, 0xee, 0x6a, 0xc1, 0xd8, + 0xc2, 0x94, 0x1a, 0xd5, 0xf8, 0xbc, 0x40, 0x92, 0xc5, 0xd2, 0x11, + 0x43, 0xc2, 0xec, 0x02, 0x0e, 0x0f, 0x9f, 0x5e, 0xf5, 0xc5, 0x94, + 0x0a, 0x03, 0x71, 0xec, 0xc1, 0x94, 0x1a, 0xd5, 0xfa, 0xbc, 0x11, + 0x93, 0xc0, 0xec, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x50, 0xf5, + 0x0a, 0x95, 0x6f, 0xec, 0xc8, 0xd4, 0x40, 0xf0, 0x39, 0xf7, 0x19, + 0xd3, 0xf8, 0xbc, 0x41, 0x00, 0xc5, 0x96, 0x41, 0x06, 0xc5, 0xd6, + 0x13, 0x47, 0xc2, 0xec, 0x02, 0x1e, 0x0f, 0x9f, 0x42, 0xf5, 0x40, + 0x98, 0x1c, 0xd9, 0xfa, 0xbc, 0x40, 0x92, 0x19, 0xd3, 0x6e, 0xec, + 0x19, 0xd3, 0xc2, 0xec, 0x0a, 0x65, 0x52, 0x43, 0x02, 0x97, 0xc3, + 0x92, 0x48, 0xa2, 0xc2, 0xd2, 0x0a, 0x65, 0xeb, 0x43, 0x02, 0x99, + 0xc4, 0x92, 0x09, 0xb3, 0xbf, 0xff, 0xc2, 0xd2, 0x41, 0x00, 0x88, + 0x98, 0x90, 0x9a, 0x88, 0xda, 0x43, 0x20, 0x08, 0x0b, 0x01, 0x00, + 0x06, 0x92, 0x01, 0xd2, 0x0a, 0x65, 0xf0, 0x6a, 0x0b, 0x97, 0x6f, + 0xec, 0x02, 0x99, 0xc4, 0x98, 0xd3, 0xd8, 0x02, 0xd6, 0x0a, 0x03, + 0x02, 0x00, 0x01, 0x97, 0xc3, 0x98, 0x02, 0x96, 0xc3, 0xd8, 0x01, + 0x96, 0xc1, 0xd6, 0x1a, 0xd5, 0x6e, 0xec, 0xc5, 0x98, 0x14, 0x99, + 0x6f, 0xec, 0xc2, 0xd8, 0x43, 0x00, 0x88, 0x98, 0x90, 0x9a, 0x88, + 0xda, 0x08, 0x0b, 0x01, 0x00, 0x40, 0x92, 0xc8, 0xd2, 0x40, 0xf0, + 0x76, 0xf5, 0x41, 0x00, 0x11, 0x93, 0xc0, 0xec, 0x40, 0x42, 0x02, + 0x4e, 0x0f, 0x9f, 0xb0, 0xf5, 0x42, 0x42, 0x02, 0x5e, 0x0f, 0x9f, + 0xad, 0xf5, 0x0a, 0x65, 0xfe, 0x7f, 0x02, 0x97, 0xc3, 0x92, 0x42, + 0xa2, 0xc2, 0xd2, 0x40, 0x92, 0x19, 0xd3, 0xc0, 0xec, 0x0a, 0x65, + 0xeb, 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xa3, 0xc0, 0x00, 0xc2, + 0xd2, 0x0a, 0x65, 0xe9, 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xb3, + 0xbf, 0xff, 0xc2, 0xd2, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x63, + 0x20, 0x08, 0x0b, 0x01, 0x00, 0x11, 0x93, 0xaf, 0xbc, 0x47, 0xb2, + 0x59, 0x95, 0x5a, 0x95, 0x12, 0xa5, 0xbf, 0xbc, 0x0a, 0xb3, 0x01, + 0x00, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0xd2, 0xf5, 0x41, 0x04, + 0x05, 0x93, 0x40, 0x96, 0x20, 0xd6, 0x62, 0x97, 0x0f, 0x9f, 0xe1, + 0xf5, 0x14, 0x99, 0xfc, 0xbc, 0xd1, 0xd8, 0x14, 0x99, 0xfe, 0xbc, + 0xd1, 0xd8, 0x20, 0x98, 0x42, 0x08, 0x20, 0xd8, 0x20, 0x98, 0x03, + 0x49, 0x02, 0x1e, 0x0f, 0x9f, 0xd8, 0xf5, 0xc5, 0x92, 0x62, 0x42, + 0x02, 0x4e, 0x0f, 0x9f, 0xfa, 0xf5, 0x02, 0x8e, 0x0f, 0x9f, 0xf4, + 0xf5, 0x61, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x1e, 0xf6, 0x0f, 0x9f, + 0x4b, 0xf6, 0x63, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x41, 0xf6, 0x0f, + 0x9f, 0x4b, 0xf6, 0x0d, 0x03, 0x01, 0x00, 0x0c, 0x99, 0x71, 0xec, + 0x0b, 0x05, 0xff, 0xff, 0x40, 0x96, 0x0f, 0x9f, 0x07, 0xf6, 0xd1, + 0x96, 0xd4, 0xd6, 0x20, 0x96, 0x41, 0x06, 0x20, 0xd6, 0x02, 0x47, + 0x02, 0x1e, 0x0f, 0x9f, 0x03, 0xf6, 0x1a, 0xd5, 0xc2, 0xec, 0x0a, + 0x65, 0xeb, 0x43, 0x02, 0x99, 0xc4, 0x92, 0x09, 0xa3, 0xc0, 0x00, + 0xc2, 0xd2, 0x0a, 0x65, 0xe9, 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, + 0xb3, 0xbf, 0xff, 0xc2, 0xd2, 0x0f, 0x9f, 0x4b, 0xf6, 0x0a, 0x03, + 0xfe, 0xff, 0x61, 0x95, 0x40, 0x98, 0x20, 0xd8, 0x02, 0x49, 0x02, + 0x0e, 0x0f, 0x9f, 0x4b, 0xf6, 0x0d, 0x03, 0x01, 0x00, 0x21, 0xd2, + 0x20, 0x92, 0x05, 0x03, 0x42, 0x02, 0xc8, 0xd2, 0x21, 0x96, 0xc3, + 0x92, 0x42, 0x06, 0x21, 0xd6, 0xc8, 0xd2, 0x22, 0xd4, 0x40, 0xf0, + 0xa2, 0xf0, 0x42, 0x00, 0x20, 0x98, 0x42, 0x08, 0x20, 0xd8, 0x22, + 0x94, 0x02, 0x49, 0x02, 0x1e, 0x0f, 0x9f, 0x2a, 0xf6, 0x0f, 0x9f, + 0x4b, 0xf6, 0x0d, 0x03, 0x03, 0x00, 0xc8, 0xd2, 0x02, 0x92, 0xc8, + 0xd2, 0x01, 0x96, 0xc8, 0xd6, 0x40, 0xf0, 0x4e, 0xf6, 0x43, 0x00, + 0x63, 0x00, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x45, 0x20, 0x08, + 0x0b, 0x01, 0x00, 0x0d, 0x03, 0x08, 0x00, 0x08, 0x94, 0xc5, 0xd4, + 0x09, 0x05, 0x01, 0x00, 0xc2, 0x94, 0x03, 0xd4, 0x42, 0x02, 0xc1, + 0x92, 0x01, 0xd2, 0x02, 0x97, 0xc5, 0x94, 0x0a, 0x83, 0xff, 0xff, + 0x11, 0xb3, 0x2c, 0x93, 0x09, 0xb3, 0xfb, 0xff, 0x19, 0xd3, 0x2c, + 0x93, 0x03, 0x92, 0x40, 0x42, 0x02, 0x4e, 0x0f, 0x9f, 0x81, 0xf6, + 0x01, 0x94, 0xd2, 0x92, 0x19, 0xd3, 0x2c, 0x93, 0x01, 0xd4, 0x02, + 0x94, 0x12, 0x95, 0x2c, 0x93, 0x44, 0xa4, 0x1a, 0xd5, 0x2c, 0x93, + 0x0a, 0xb5, 0xfb, 0xff, 0x1a, 0xd5, 0x2c, 0x93, 0x0b, 0x07, 0xff, + 0xff, 0x40, 0x46, 0x02, 0x5e, 0x0f, 0x9f, 0x6c, 0xf6, 0x09, 0x63, + 0xd4, 0x6c, 0x01, 0x95, 0xc2, 0x96, 0xc5, 0x94, 0x02, 0xa7, 0xc1, + 0xd6, 0x03, 0x92, 0x54, 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0x91, 0xf6, + 0x0a, 0x83, 0xff, 0xff, 0x1b, 0xb3, 0x2c, 0x93, 0x45, 0x00, 0x88, + 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x09, 0x63, + 0x00, 0x40, 0x19, 0xd3, 0xf2, 0xbd, 0x40, 0xf0, 0xd8, 0xf4, 0x40, + 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0xa5, 0xf6, 0x40, 0xf0, 0x3c, 0xf2, + 0x0f, 0x9f, 0xb3, 0xf6, 0x40, 0x96, 0xc8, 0xd6, 0x09, 0x93, 0x91, + 0xec, 0xc8, 0xd2, 0x40, 0xf0, 0xd0, 0xee, 0x0a, 0x65, 0xfe, 0x7f, + 0x02, 0x97, 0xc3, 0x92, 0x44, 0xa2, 0xc2, 0xd2, 0x42, 0x00, 0x88, + 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x0a, 0x65, + 0xe8, 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xa3, 0x40, 0x00, 0xc2, + 0xd2, 0x0a, 0x65, 0xea, 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xb3, + 0xfb, 0xff, 0xc2, 0xd2, 0x40, 0x92, 0x19, 0xd3, 0x2d, 0xbc, 0x0a, + 0x65, 0xd8, 0x43, 0x02, 0x97, 0xc3, 0x92, 0x09, 0xb3, 0xbf, 0xff, + 0xc2, 0xd2, 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, + 0x00, 0x09, 0x63, 0xea, 0x43, 0x01, 0x97, 0xc3, 0x94, 0x44, 0xa4, + 0xc1, 0xd4, 0x11, 0x93, 0xb9, 0xec, 0x40, 0x42, 0x02, 0x4e, 0x0f, + 0x9f, 0x0c, 0xf7, 0x12, 0x95, 0x93, 0xec, 0x0b, 0x67, 0x36, 0x43, + 0xd2, 0x98, 0x1c, 0xd9, 0xc8, 0xbc, 0xd2, 0x98, 0x03, 0x93, 0xc1, + 0xd8, 0x11, 0x93, 0xb9, 0xec, 0x09, 0x03, 0xff, 0xff, 0x19, 0xd3, + 0xb9, 0xec, 0x40, 0x42, 0x02, 0x5e, 0x0f, 0x9f, 0xe5, 0xf6, 0x19, + 0xd3, 0xb8, 0xec, 0x19, 0xd3, 0xba, 0xec, 0x0a, 0x05, 0xfe, 0xff, + 0xca, 0xd2, 0xca, 0xd2, 0xc2, 0xd2, 0x0a, 0x65, 0x5e, 0x43, 0x02, + 0x97, 0xc3, 0x92, 0x48, 0xa2, 0xc2, 0xd2, 0x0a, 0x65, 0xea, 0x43, + 0x02, 0x99, 0xc4, 0x92, 0x09, 0xb3, 0xfb, 0xff, 0x0f, 0x9f, 0x15, + 0xf7, 0x11, 0x93, 0x03, 0xec, 0x19, 0xd3, 0x01, 0x82, 0x0a, 0x65, + 0xfd, 0x7d, 0x02, 0x97, 0xc3, 0x92, 0x43, 0xa2, 0xc2, 0xd2, 0x88, + 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x03, 0x92, + 0x04, 0x96, 0x0d, 0x5e, 0x50, 0x46, 0x02, 0x0e, 0x40, 0x92, 0x09, + 0xee, 0x44, 0x46, 0x04, 0x0e, 0x59, 0x93, 0x44, 0x26, 0x04, 0x5e, + 0x46, 0xee, 0x41, 0x93, 0x41, 0x26, 0x43, 0x4e, 0x88, 0x98, 0x90, + 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x40, 0xf0, 0xb1, 0xfe, + 0x88, 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x88, + 0x98, 0x90, 0x9a, 0x88, 0xda, 0x08, 0x0b, 0x01, 0x00, 0x03, 0x94, + 0x1a, 0xd5, 0x40, 0xf7, 0x11, 0x93, 0x00, 0x90, 0x88, 0x98, 0x90, + 0x9a, 0x1d, 0x00, 0x1a, 0x00, 0x03, 0x00, 0x03, 0x00, 0x18, 0x00, + 0x19, 0x00, 0x1a, 0x00, 0x1b, 0x00, 0x16, 0x00, 0x21, 0x00, 0x12, + 0x00, 0x09, 0x00, 0x13, 0x00, 0x19, 0x00, 0x19, 0x00, 0x19, 0x00, + 0x21, 0x00, 0x2d, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x7e, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xf2, 0x6b, 0xf7, 0x00, 0x00, + 0x1c, 0xf2, 0x6b, 0xf7, 0x00, 0x00, 0x61, 0xf2, 0x68, 0xf7, 0x6f, + 0xf7, 0x00, 0x00, 0x2e, 0xf3, 0x6b, 0xf7, 0x25, 0x47, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00 +}; diff --git a/sys/legacy/dev/usb/if_zydreg.h b/sys/legacy/dev/usb/if_zydreg.h new file mode 100644 index 0000000..feae22f --- /dev/null +++ b/sys/legacy/dev/usb/if_zydreg.h @@ -0,0 +1,1322 @@ +/* $OpenBSD: if_zydreg.h,v 1.19 2006/11/30 19:28:07 damien Exp $ */ +/* $NetBSD: if_zydreg.h,v 1.2 2007/06/16 11:18:45 kiyohara Exp $ */ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2006 by Damien Bergamini <damien.bergamini@free.fr> + * Copyright (c) 2006 by Florian Stoehr <ich@florian-stoehr.de> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * ZyDAS ZD1211/ZD1211B USB WLAN driver. + */ + +#define ZYD_CR_GPI_EN 0x9418 +#define ZYD_CR_RADIO_PD 0x942c +#define ZYD_CR_RF2948_PD 0x942c +#define ZYD_CR_EN_PS_MANUAL_AGC 0x943c +#define ZYD_CR_CONFIG_PHILIPS 0x9440 +#define ZYD_CR_I2C_WRITE 0x9444 +#define ZYD_CR_SA2400_SER_RP 0x9448 +#define ZYD_CR_RADIO_PE 0x9458 +#define ZYD_CR_RST_BUS_MASTER 0x945c +#define ZYD_CR_RFCFG 0x9464 +#define ZYD_CR_HSTSCHG 0x946c +#define ZYD_CR_PHY_ON 0x9474 +#define ZYD_CR_RX_DELAY 0x9478 +#define ZYD_CR_RX_PE_DELAY 0x947c +#define ZYD_CR_GPIO_1 0x9490 +#define ZYD_CR_GPIO_2 0x9494 +#define ZYD_CR_EnZYD_CRyBufMux 0x94a8 +#define ZYD_CR_PS_CTRL 0x9500 +#define ZYD_CR_ADDA_PWR_DWN 0x9504 +#define ZYD_CR_ADDA_MBIAS_WT 0x9508 +#define ZYD_CR_INTERRUPT 0x9510 +#define ZYD_CR_MAC_PS_STATE 0x950c +#define ZYD_CR_ATIM_WND_PERIOD 0x951c +#define ZYD_CR_BCN_INTERVAL 0x9520 +#define ZYD_CR_PRE_TBTT 0x9524 + +/* + * MAC registers. + */ +#define ZYD_MAC_MACADRL 0x9610 /* MAC address (low) */ +#define ZYD_MAC_MACADRH 0x9614 /* MAC address (high) */ +#define ZYD_MAC_BSSADRL 0x9618 /* BSS address (low) */ +#define ZYD_MAC_BSSADRH 0x961c /* BSS address (high) */ +#define ZYD_MAC_BCNCFG 0x9620 /* BCN configuration */ +#define ZYD_MAC_GHTBL 0x9624 /* Group hash table (low) */ +#define ZYD_MAC_GHTBH 0x9628 /* Group hash table (high) */ +#define ZYD_MAC_RX_TIMEOUT 0x962c /* Rx timeout value */ +#define ZYD_MAC_BAS_RATE 0x9630 /* Basic rate setting */ +#define ZYD_MAC_MAN_RATE 0x9634 /* Mandatory rate setting */ +#define ZYD_MAC_RTSCTSRATE 0x9638 /* RTS CTS rate */ +#define ZYD_MAC_BACKOFF_PROTECT 0x963c /* Backoff protection */ +#define ZYD_MAC_RX_THRESHOLD 0x9640 /* Rx threshold */ +#define ZYD_MAC_TX_PE_CONTROL 0x9644 /* Tx_PE control */ +#define ZYD_MAC_AFTER_PNP 0x9648 /* After PnP */ +#define ZYD_MAC_RX_PE_DELAY 0x964c /* Rx_pe delay */ +#define ZYD_MAC_RX_ADDR2_L 0x9650 /* RX address2 (low) */ +#define ZYD_MAC_RX_ADDR2_H 0x9654 /* RX address2 (high) */ +#define ZYD_MAC_SIFS_ACK_TIME 0x9658 /* Dynamic SIFS ack time */ +#define ZYD_MAC_PHY_DELAY 0x9660 /* PHY delay */ +#define ZYD_MAC_PHY_DELAY2 0x966c /* PHY delay */ +#define ZYD_MAC_BCNFIFO 0x9670 /* Beacon FIFO I/O port */ +#define ZYD_MAC_SNIFFER 0x9674 /* Sniffer on/off */ +#define ZYD_MAC_ENCRYPTION_TYPE 0x9678 /* Encryption type */ +#define ZYD_MAC_RETRY 0x967c /* Retry time */ +#define ZYD_MAC_MISC 0x9680 /* Misc */ +#define ZYD_MAC_STMACHINESTAT 0x9684 /* State machine status */ +#define ZYD_MAC_TX_UNDERRUN_CNT 0x9688 /* TX underrun counter */ +#define ZYD_MAC_RXFILTER 0x968c /* Send to host settings */ +#define ZYD_MAC_ACK_EXT 0x9690 /* Acknowledge extension */ +#define ZYD_MAC_BCNFIFOST 0x9694 /* BCN FIFO set and status */ +#define ZYD_MAC_DIFS_EIFS_SIFS 0x9698 /* DIFS, EIFS & SIFS settings */ +#define ZYD_MAC_RX_TIMEOUT_CNT 0x969c /* RX timeout count */ +#define ZYD_MAC_RX_TOTAL_FRAME 0x96a0 /* RX total frame count */ +#define ZYD_MAC_RX_CRC32_CNT 0x96a4 /* RX CRC32 frame count */ +#define ZYD_MAC_RX_CRC16_CNT 0x96a8 /* RX CRC16 frame count */ +#define ZYD_MAC_RX_UDEC 0x96ac /* RX unicast decr. error count */ +#define ZYD_MAC_RX_OVERRUN_CNT 0x96b0 /* RX FIFO overrun count */ +#define ZYD_MAC_RX_MDEC 0x96bc /* RX multicast decr. err. cnt. */ +#define ZYD_MAC_NAV_TCR 0x96c4 /* NAV timer count read */ +#define ZYD_MAC_BACKOFF_ST_RD 0x96c8 /* Backoff status read */ +#define ZYD_MAC_DM_RETRY_CNT_RD 0x96cc /* DM retry count read */ +#define ZYD_MAC_RX_ACR 0x96d0 /* RX arbitration count read */ +#define ZYD_MAC_TX_CCR 0x96d4 /* Tx complete count read */ +#define ZYD_MAC_TCB_ADDR 0x96e8 /* Current PCI process TCP addr */ +#define ZYD_MAC_RCB_ADDR 0x96ec /* Next RCB address */ +#define ZYD_MAC_CONT_WIN_LIMIT 0x96f0 /* Contention window limit */ +#define ZYD_MAC_TX_PKT 0x96f4 /* Tx total packet count read */ +#define ZYD_MAC_DL_CTRL 0x96f8 /* Download control */ +#define ZYD_MAC_CAM_MODE 0x9700 /* CAM: Continuous Access Mode */ +#define ZYD_MACB_TXPWR_CTL1 0x9b00 +#define ZYD_MACB_TXPWR_CTL2 0x9b04 +#define ZYD_MACB_TXPWR_CTL3 0x9b08 +#define ZYD_MACB_TXPWR_CTL4 0x9b0c +#define ZYD_MACB_AIFS_CTL1 0x9b10 +#define ZYD_MACB_AIFS_CTL2 0x9b14 +#define ZYD_MACB_TXOP 0x9b20 +#define ZYD_MACB_MAX_RETRY 0x9b28 + +/* + * Miscellanous registers. + */ +#define ZYD_FIRMWARE_START_ADDR 0xee00 +#define ZYD_FIRMWARE_BASE_ADDR 0xee1d /* Firmware base address */ + +/* + * EEPROM registers. + */ +#define ZYD_EEPROM_START_HEAD 0xf800 /* EEPROM start */ +#define ZYD_EEPROM_SUBID 0xf817 +#define ZYD_EEPROM_POD 0xf819 +#define ZYD_EEPROM_MAC_ADDR_P1 0xf81b /* Part 1 of the MAC address */ +#define ZYD_EEPROM_MAC_ADDR_P2 0xf81d /* Part 2 of the MAC address */ +#define ZYD_EEPROM_PWR_CAL 0xf81f /* Calibration */ +#define ZYD_EEPROM_PWR_INT 0xf827 /* Calibration */ +#define ZYD_EEPROM_ALLOWEDCHAN 0xf82f /* Allowed CH mask, 1 bit each */ +#define ZYD_EEPROM_DEVICE_VER 0xf837 /* Device version */ +#define ZYD_EEPROM_PHY_REG 0xf83c /* PHY registers */ +#define ZYD_EEPROM_36M_CAL 0xf83f /* Calibration */ +#define ZYD_EEPROM_11A_INT 0xf847 /* Interpolation */ +#define ZYD_EEPROM_48M_CAL 0xf84f /* Calibration */ +#define ZYD_EEPROM_48M_INT 0xf857 /* Interpolation */ +#define ZYD_EEPROM_54M_CAL 0xf85f /* Calibration */ +#define ZYD_EEPROM_54M_INT 0xf867 /* Interpolation */ + +/* + * Firmware registers offsets (relative to fwbase). + */ +#define ZYD_FW_FIRMWARE_REV 0x0000 /* Firmware version */ +#define ZYD_FW_USB_SPEED 0x0001 /* USB speed (!=0 if highspeed) */ +#define ZYD_FW_FIX_TX_RATE 0x0002 /* Fixed TX rate */ +#define ZYD_FW_LINK_STATUS 0x0003 +#define ZYD_FW_SOFT_RESET 0x0004 +#define ZYD_FW_FLASH_CHK 0x0005 + +/* possible flags for register ZYD_FW_LINK_STATUS */ +#define ZYD_LED1 (1 << 8) +#define ZYD_LED2 (1 << 9) + +/* + * RF IDs. + */ +#define ZYD_RF_UW2451 0x2 /* not supported yet */ +#define ZYD_RF_UCHIP 0x3 /* not supported yet */ +#define ZYD_RF_AL2230 0x4 +#define ZYD_RF_AL7230B 0x5 +#define ZYD_RF_THETA 0x6 /* not supported yet */ +#define ZYD_RF_AL2210 0x7 +#define ZYD_RF_MAXIM_NEW 0x8 +#define ZYD_RF_GCT 0x9 +#define ZYD_RF_AL2230S 0xa /* not supported yet */ +#define ZYD_RF_RALINK 0xb /* not supported yet */ +#define ZYD_RF_INTERSIL 0xc /* not supported yet */ +#define ZYD_RF_RFMD 0xd +#define ZYD_RF_MAXIM_NEW2 0xe +#define ZYD_RF_PHILIPS 0xf /* not supported yet */ + +/* + * PHY registers (8 bits, not documented). + */ +#define ZYD_CR0 0x9000 +#define ZYD_CR1 0x9004 +#define ZYD_CR2 0x9008 +#define ZYD_CR3 0x900c +#define ZYD_CR5 0x9010 +#define ZYD_CR6 0x9014 +#define ZYD_CR7 0x9018 +#define ZYD_CR8 0x901c +#define ZYD_CR4 0x9020 +#define ZYD_CR9 0x9024 +#define ZYD_CR10 0x9028 +#define ZYD_CR11 0x902c +#define ZYD_CR12 0x9030 +#define ZYD_CR13 0x9034 +#define ZYD_CR14 0x9038 +#define ZYD_CR15 0x903c +#define ZYD_CR16 0x9040 +#define ZYD_CR17 0x9044 +#define ZYD_CR18 0x9048 +#define ZYD_CR19 0x904c +#define ZYD_CR20 0x9050 +#define ZYD_CR21 0x9054 +#define ZYD_CR22 0x9058 +#define ZYD_CR23 0x905c +#define ZYD_CR24 0x9060 +#define ZYD_CR25 0x9064 +#define ZYD_CR26 0x9068 +#define ZYD_CR27 0x906c +#define ZYD_CR28 0x9070 +#define ZYD_CR29 0x9074 +#define ZYD_CR30 0x9078 +#define ZYD_CR31 0x907c +#define ZYD_CR32 0x9080 +#define ZYD_CR33 0x9084 +#define ZYD_CR34 0x9088 +#define ZYD_CR35 0x908c +#define ZYD_CR36 0x9090 +#define ZYD_CR37 0x9094 +#define ZYD_CR38 0x9098 +#define ZYD_CR39 0x909c +#define ZYD_CR40 0x90a0 +#define ZYD_CR41 0x90a4 +#define ZYD_CR42 0x90a8 +#define ZYD_CR43 0x90ac +#define ZYD_CR44 0x90b0 +#define ZYD_CR45 0x90b4 +#define ZYD_CR46 0x90b8 +#define ZYD_CR47 0x90bc +#define ZYD_CR48 0x90c0 +#define ZYD_CR49 0x90c4 +#define ZYD_CR50 0x90c8 +#define ZYD_CR51 0x90cc +#define ZYD_CR52 0x90d0 +#define ZYD_CR53 0x90d4 +#define ZYD_CR54 0x90d8 +#define ZYD_CR55 0x90dc +#define ZYD_CR56 0x90e0 +#define ZYD_CR57 0x90e4 +#define ZYD_CR58 0x90e8 +#define ZYD_CR59 0x90ec +#define ZYD_CR60 0x90f0 +#define ZYD_CR61 0x90f4 +#define ZYD_CR62 0x90f8 +#define ZYD_CR63 0x90fc +#define ZYD_CR64 0x9100 +#define ZYD_CR65 0x9104 +#define ZYD_CR66 0x9108 +#define ZYD_CR67 0x910c +#define ZYD_CR68 0x9110 +#define ZYD_CR69 0x9114 +#define ZYD_CR70 0x9118 +#define ZYD_CR71 0x911c +#define ZYD_CR72 0x9120 +#define ZYD_CR73 0x9124 +#define ZYD_CR74 0x9128 +#define ZYD_CR75 0x912c +#define ZYD_CR76 0x9130 +#define ZYD_CR77 0x9134 +#define ZYD_CR78 0x9138 +#define ZYD_CR79 0x913c +#define ZYD_CR80 0x9140 +#define ZYD_CR81 0x9144 +#define ZYD_CR82 0x9148 +#define ZYD_CR83 0x914c +#define ZYD_CR84 0x9150 +#define ZYD_CR85 0x9154 +#define ZYD_CR86 0x9158 +#define ZYD_CR87 0x915c +#define ZYD_CR88 0x9160 +#define ZYD_CR89 0x9164 +#define ZYD_CR90 0x9168 +#define ZYD_CR91 0x916c +#define ZYD_CR92 0x9170 +#define ZYD_CR93 0x9174 +#define ZYD_CR94 0x9178 +#define ZYD_CR95 0x917c +#define ZYD_CR96 0x9180 +#define ZYD_CR97 0x9184 +#define ZYD_CR98 0x9188 +#define ZYD_CR99 0x918c +#define ZYD_CR100 0x9190 +#define ZYD_CR101 0x9194 +#define ZYD_CR102 0x9198 +#define ZYD_CR103 0x919c +#define ZYD_CR104 0x91a0 +#define ZYD_CR105 0x91a4 +#define ZYD_CR106 0x91a8 +#define ZYD_CR107 0x91ac +#define ZYD_CR108 0x91b0 +#define ZYD_CR109 0x91b4 +#define ZYD_CR110 0x91b8 +#define ZYD_CR111 0x91bc +#define ZYD_CR112 0x91c0 +#define ZYD_CR113 0x91c4 +#define ZYD_CR114 0x91c8 +#define ZYD_CR115 0x91cc +#define ZYD_CR116 0x91d0 +#define ZYD_CR117 0x91d4 +#define ZYD_CR118 0x91d8 +#define ZYD_CR119 0x91dc +#define ZYD_CR120 0x91e0 +#define ZYD_CR121 0x91e4 +#define ZYD_CR122 0x91e8 +#define ZYD_CR123 0x91ec +#define ZYD_CR124 0x91f0 +#define ZYD_CR125 0x91f4 +#define ZYD_CR126 0x91f8 +#define ZYD_CR127 0x91fc +#define ZYD_CR128 0x9200 +#define ZYD_CR129 0x9204 +#define ZYD_CR130 0x9208 +#define ZYD_CR131 0x920c +#define ZYD_CR132 0x9210 +#define ZYD_CR133 0x9214 +#define ZYD_CR134 0x9218 +#define ZYD_CR135 0x921c +#define ZYD_CR136 0x9220 +#define ZYD_CR137 0x9224 +#define ZYD_CR138 0x9228 +#define ZYD_CR139 0x922c +#define ZYD_CR140 0x9230 +#define ZYD_CR141 0x9234 +#define ZYD_CR142 0x9238 +#define ZYD_CR143 0x923c +#define ZYD_CR144 0x9240 +#define ZYD_CR145 0x9244 +#define ZYD_CR146 0x9248 +#define ZYD_CR147 0x924c +#define ZYD_CR148 0x9250 +#define ZYD_CR149 0x9254 +#define ZYD_CR150 0x9258 +#define ZYD_CR151 0x925c +#define ZYD_CR152 0x9260 +#define ZYD_CR153 0x9264 +#define ZYD_CR154 0x9268 +#define ZYD_CR155 0x926c +#define ZYD_CR156 0x9270 +#define ZYD_CR157 0x9274 +#define ZYD_CR158 0x9278 +#define ZYD_CR159 0x927c +#define ZYD_CR160 0x9280 +#define ZYD_CR161 0x9284 +#define ZYD_CR162 0x9288 +#define ZYD_CR163 0x928c +#define ZYD_CR164 0x9290 +#define ZYD_CR165 0x9294 +#define ZYD_CR166 0x9298 +#define ZYD_CR167 0x929c +#define ZYD_CR168 0x92a0 +#define ZYD_CR169 0x92a4 +#define ZYD_CR170 0x92a8 +#define ZYD_CR171 0x92ac +#define ZYD_CR172 0x92b0 +#define ZYD_CR173 0x92b4 +#define ZYD_CR174 0x92b8 +#define ZYD_CR175 0x92bc +#define ZYD_CR176 0x92c0 +#define ZYD_CR177 0x92c4 +#define ZYD_CR178 0x92c8 +#define ZYD_CR179 0x92cc +#define ZYD_CR180 0x92d0 +#define ZYD_CR181 0x92d4 +#define ZYD_CR182 0x92d8 +#define ZYD_CR183 0x92dc +#define ZYD_CR184 0x92e0 +#define ZYD_CR185 0x92e4 +#define ZYD_CR186 0x92e8 +#define ZYD_CR187 0x92ec +#define ZYD_CR188 0x92f0 +#define ZYD_CR189 0x92f4 +#define ZYD_CR190 0x92f8 +#define ZYD_CR191 0x92fc +#define ZYD_CR192 0x9300 +#define ZYD_CR193 0x9304 +#define ZYD_CR194 0x9308 +#define ZYD_CR195 0x930c +#define ZYD_CR196 0x9310 +#define ZYD_CR197 0x9314 +#define ZYD_CR198 0x9318 +#define ZYD_CR199 0x931c +#define ZYD_CR200 0x9320 +#define ZYD_CR201 0x9324 +#define ZYD_CR202 0x9328 +#define ZYD_CR203 0x932c +#define ZYD_CR204 0x9330 +#define ZYD_CR205 0x9334 +#define ZYD_CR206 0x9338 +#define ZYD_CR207 0x933c +#define ZYD_CR208 0x9340 +#define ZYD_CR209 0x9344 +#define ZYD_CR210 0x9348 +#define ZYD_CR211 0x934c +#define ZYD_CR212 0x9350 +#define ZYD_CR213 0x9354 +#define ZYD_CR214 0x9358 +#define ZYD_CR215 0x935c +#define ZYD_CR216 0x9360 +#define ZYD_CR217 0x9364 +#define ZYD_CR218 0x9368 +#define ZYD_CR219 0x936c +#define ZYD_CR220 0x9370 +#define ZYD_CR221 0x9374 +#define ZYD_CR222 0x9378 +#define ZYD_CR223 0x937c +#define ZYD_CR224 0x9380 +#define ZYD_CR225 0x9384 +#define ZYD_CR226 0x9388 +#define ZYD_CR227 0x938c +#define ZYD_CR228 0x9390 +#define ZYD_CR229 0x9394 +#define ZYD_CR230 0x9398 +#define ZYD_CR231 0x939c +#define ZYD_CR232 0x93a0 +#define ZYD_CR233 0x93a4 +#define ZYD_CR234 0x93a8 +#define ZYD_CR235 0x93ac +#define ZYD_CR236 0x93b0 +#define ZYD_CR240 0x93c0 +#define ZYD_CR241 0x93c4 +#define ZYD_CR242 0x93c8 +#define ZYD_CR243 0x93cc +#define ZYD_CR244 0x93d0 +#define ZYD_CR245 0x93d4 +#define ZYD_CR251 0x93ec +#define ZYD_CR252 0x93f0 +#define ZYD_CR253 0x93f4 +#define ZYD_CR254 0x93f8 +#define ZYD_CR255 0x93fc + +/* copied nearly verbatim from the Linux driver rewrite */ +#define ZYD_DEF_PHY \ +{ \ + { ZYD_CR0, 0x0a }, { ZYD_CR1, 0x06 }, { ZYD_CR2, 0x26 }, \ + { ZYD_CR3, 0x38 }, { ZYD_CR4, 0x80 }, { ZYD_CR9, 0xa0 }, \ + { ZYD_CR10, 0x81 }, { ZYD_CR11, 0x00 }, { ZYD_CR12, 0x7f }, \ + { ZYD_CR13, 0x8c }, { ZYD_CR14, 0x80 }, { ZYD_CR15, 0x3d }, \ + { ZYD_CR16, 0x20 }, { ZYD_CR17, 0x1e }, { ZYD_CR18, 0x0a }, \ + { ZYD_CR19, 0x48 }, { ZYD_CR20, 0x0c }, { ZYD_CR21, 0x0c }, \ + { ZYD_CR22, 0x23 }, { ZYD_CR23, 0x90 }, { ZYD_CR24, 0x14 }, \ + { ZYD_CR25, 0x40 }, { ZYD_CR26, 0x10 }, { ZYD_CR27, 0x19 }, \ + { ZYD_CR28, 0x7f }, { ZYD_CR29, 0x80 }, { ZYD_CR30, 0x4b }, \ + { ZYD_CR31, 0x60 }, { ZYD_CR32, 0x43 }, { ZYD_CR33, 0x08 }, \ + { ZYD_CR34, 0x06 }, { ZYD_CR35, 0x0a }, { ZYD_CR36, 0x00 }, \ + { ZYD_CR37, 0x00 }, { ZYD_CR38, 0x38 }, { ZYD_CR39, 0x0c }, \ + { ZYD_CR40, 0x84 }, { ZYD_CR41, 0x2a }, { ZYD_CR42, 0x80 }, \ + { ZYD_CR43, 0x10 }, { ZYD_CR44, 0x12 }, { ZYD_CR46, 0xff }, \ + { ZYD_CR47, 0x1e }, { ZYD_CR48, 0x26 }, { ZYD_CR49, 0x5b }, \ + { ZYD_CR64, 0xd0 }, { ZYD_CR65, 0x04 }, { ZYD_CR66, 0x58 }, \ + { ZYD_CR67, 0xc9 }, { ZYD_CR68, 0x88 }, { ZYD_CR69, 0x41 }, \ + { ZYD_CR70, 0x23 }, { ZYD_CR71, 0x10 }, { ZYD_CR72, 0xff }, \ + { ZYD_CR73, 0x32 }, { ZYD_CR74, 0x30 }, { ZYD_CR75, 0x65 }, \ + { ZYD_CR76, 0x41 }, { ZYD_CR77, 0x1b }, { ZYD_CR78, 0x30 }, \ + { ZYD_CR79, 0x68 }, { ZYD_CR80, 0x64 }, { ZYD_CR81, 0x64 }, \ + { ZYD_CR82, 0x00 }, { ZYD_CR83, 0x00 }, { ZYD_CR84, 0x00 }, \ + { ZYD_CR85, 0x02 }, { ZYD_CR86, 0x00 }, { ZYD_CR87, 0x00 }, \ + { ZYD_CR88, 0xff }, { ZYD_CR89, 0xfc }, { ZYD_CR90, 0x00 }, \ + { ZYD_CR91, 0x00 }, { ZYD_CR92, 0x00 }, { ZYD_CR93, 0x08 }, \ + { ZYD_CR94, 0x00 }, { ZYD_CR95, 0x00 }, { ZYD_CR96, 0xff }, \ + { ZYD_CR97, 0xe7 }, { ZYD_CR98, 0x00 }, { ZYD_CR99, 0x00 }, \ + { ZYD_CR100, 0x00 }, { ZYD_CR101, 0xae }, { ZYD_CR102, 0x02 }, \ + { ZYD_CR103, 0x00 }, { ZYD_CR104, 0x03 }, { ZYD_CR105, 0x65 }, \ + { ZYD_CR106, 0x04 }, { ZYD_CR107, 0x00 }, { ZYD_CR108, 0x0a }, \ + { ZYD_CR109, 0xaa }, { ZYD_CR110, 0xaa }, { ZYD_CR111, 0x25 }, \ + { ZYD_CR112, 0x25 }, { ZYD_CR113, 0x00 }, { ZYD_CR119, 0x1e }, \ + { ZYD_CR125, 0x90 }, { ZYD_CR126, 0x00 }, { ZYD_CR127, 0x00 }, \ + { ZYD_CR5, 0x00 }, { ZYD_CR6, 0x00 }, { ZYD_CR7, 0x00 }, \ + { ZYD_CR8, 0x00 }, { ZYD_CR9, 0x20 }, { ZYD_CR12, 0xf0 }, \ + { ZYD_CR20, 0x0e }, { ZYD_CR21, 0x0e }, { ZYD_CR27, 0x10 }, \ + { ZYD_CR44, 0x33 }, { ZYD_CR47, 0x1E }, { ZYD_CR83, 0x24 }, \ + { ZYD_CR84, 0x04 }, { ZYD_CR85, 0x00 }, { ZYD_CR86, 0x0C }, \ + { ZYD_CR87, 0x12 }, { ZYD_CR88, 0x0C }, { ZYD_CR89, 0x00 }, \ + { ZYD_CR90, 0x10 }, { ZYD_CR91, 0x08 }, { ZYD_CR93, 0x00 }, \ + { ZYD_CR94, 0x01 }, { ZYD_CR95, 0x00 }, { ZYD_CR96, 0x50 }, \ + { ZYD_CR97, 0x37 }, { ZYD_CR98, 0x35 }, { ZYD_CR101, 0x13 }, \ + { ZYD_CR102, 0x27 }, { ZYD_CR103, 0x27 }, { ZYD_CR104, 0x18 }, \ + { ZYD_CR105, 0x12 }, { ZYD_CR109, 0x27 }, { ZYD_CR110, 0x27 }, \ + { ZYD_CR111, 0x27 }, { ZYD_CR112, 0x27 }, { ZYD_CR113, 0x27 }, \ + { ZYD_CR114, 0x27 }, { ZYD_CR115, 0x26 }, { ZYD_CR116, 0x24 }, \ + { ZYD_CR117, 0xfc }, { ZYD_CR118, 0xfa }, { ZYD_CR120, 0x4f }, \ + { ZYD_CR125, 0xaa }, { ZYD_CR127, 0x03 }, { ZYD_CR128, 0x14 }, \ + { ZYD_CR129, 0x12 }, { ZYD_CR130, 0x10 }, { ZYD_CR131, 0x0C }, \ + { ZYD_CR136, 0xdf }, { ZYD_CR137, 0x40 }, { ZYD_CR138, 0xa0 }, \ + { ZYD_CR139, 0xb0 }, { ZYD_CR140, 0x99 }, { ZYD_CR141, 0x82 }, \ + { ZYD_CR142, 0x54 }, { ZYD_CR143, 0x1c }, { ZYD_CR144, 0x6c }, \ + { ZYD_CR147, 0x07 }, { ZYD_CR148, 0x4c }, { ZYD_CR149, 0x50 }, \ + { ZYD_CR150, 0x0e }, { ZYD_CR151, 0x18 }, { ZYD_CR160, 0xfe }, \ + { ZYD_CR161, 0xee }, { ZYD_CR162, 0xaa }, { ZYD_CR163, 0xfa }, \ + { ZYD_CR164, 0xfa }, { ZYD_CR165, 0xea }, { ZYD_CR166, 0xbe }, \ + { ZYD_CR167, 0xbe }, { ZYD_CR168, 0x6a }, { ZYD_CR169, 0xba }, \ + { ZYD_CR170, 0xba }, { ZYD_CR171, 0xba }, { ZYD_CR204, 0x7d }, \ + { ZYD_CR203, 0x30 }, { 0, 0} \ +} + +#define ZYD_DEF_PHYB \ +{ \ + { ZYD_CR0, 0x14 }, { ZYD_CR1, 0x06 }, { ZYD_CR2, 0x26 }, \ + { ZYD_CR3, 0x38 }, { ZYD_CR4, 0x80 }, { ZYD_CR9, 0xe0 }, \ + { ZYD_CR10, 0x81 }, { ZYD_CR11, 0x00 }, { ZYD_CR12, 0xf0 }, \ + { ZYD_CR13, 0x8c }, { ZYD_CR14, 0x80 }, { ZYD_CR15, 0x3d }, \ + { ZYD_CR16, 0x20 }, { ZYD_CR17, 0x1e }, { ZYD_CR18, 0x0a }, \ + { ZYD_CR19, 0x48 }, { ZYD_CR20, 0x10 }, { ZYD_CR21, 0x0e }, \ + { ZYD_CR22, 0x23 }, { ZYD_CR23, 0x90 }, { ZYD_CR24, 0x14 }, \ + { ZYD_CR25, 0x40 }, { ZYD_CR26, 0x10 }, { ZYD_CR27, 0x10 }, \ + { ZYD_CR28, 0x7f }, { ZYD_CR29, 0x80 }, { ZYD_CR30, 0x4b }, \ + { ZYD_CR31, 0x60 }, { ZYD_CR32, 0x43 }, { ZYD_CR33, 0x08 }, \ + { ZYD_CR34, 0x06 }, { ZYD_CR35, 0x0a }, { ZYD_CR36, 0x00 }, \ + { ZYD_CR37, 0x00 }, { ZYD_CR38, 0x38 }, { ZYD_CR39, 0x0c }, \ + { ZYD_CR40, 0x84 }, { ZYD_CR41, 0x2a }, { ZYD_CR42, 0x80 }, \ + { ZYD_CR43, 0x10 }, { ZYD_CR44, 0x33 }, { ZYD_CR46, 0xff }, \ + { ZYD_CR47, 0x1E }, { ZYD_CR48, 0x26 }, { ZYD_CR49, 0x5b }, \ + { ZYD_CR64, 0xd0 }, { ZYD_CR65, 0x04 }, { ZYD_CR66, 0x58 }, \ + { ZYD_CR67, 0xc9 }, { ZYD_CR68, 0x88 }, { ZYD_CR69, 0x41 }, \ + { ZYD_CR70, 0x23 }, { ZYD_CR71, 0x10 }, { ZYD_CR72, 0xff }, \ + { ZYD_CR73, 0x32 }, { ZYD_CR74, 0x30 }, { ZYD_CR75, 0x65 }, \ + { ZYD_CR76, 0x41 }, { ZYD_CR77, 0x1b }, { ZYD_CR78, 0x30 }, \ + { ZYD_CR79, 0xf0 }, { ZYD_CR80, 0x64 }, { ZYD_CR81, 0x64 }, \ + { ZYD_CR82, 0x00 }, { ZYD_CR83, 0x24 }, { ZYD_CR84, 0x04 }, \ + { ZYD_CR85, 0x00 }, { ZYD_CR86, 0x0c }, { ZYD_CR87, 0x12 }, \ + { ZYD_CR88, 0x0c }, { ZYD_CR89, 0x00 }, { ZYD_CR90, 0x58 }, \ + { ZYD_CR91, 0x04 }, { ZYD_CR92, 0x00 }, { ZYD_CR93, 0x00 }, \ + { ZYD_CR94, 0x01 }, { ZYD_CR95, 0x20 }, { ZYD_CR96, 0x50 }, \ + { ZYD_CR97, 0x37 }, { ZYD_CR98, 0x35 }, { ZYD_CR99, 0x00 }, \ + { ZYD_CR100, 0x01 }, { ZYD_CR101, 0x13 }, { ZYD_CR102, 0x27 }, \ + { ZYD_CR103, 0x27 }, { ZYD_CR104, 0x18 }, { ZYD_CR105, 0x12 }, \ + { ZYD_CR106, 0x04 }, { ZYD_CR107, 0x00 }, { ZYD_CR108, 0x0a }, \ + { ZYD_CR109, 0x27 }, { ZYD_CR110, 0x27 }, { ZYD_CR111, 0x27 }, \ + { ZYD_CR112, 0x27 }, { ZYD_CR113, 0x27 }, { ZYD_CR114, 0x27 }, \ + { ZYD_CR115, 0x26 }, { ZYD_CR116, 0x24 }, { ZYD_CR117, 0xfc }, \ + { ZYD_CR118, 0xfa }, { ZYD_CR119, 0x1e }, { ZYD_CR125, 0x90 }, \ + { ZYD_CR126, 0x00 }, { ZYD_CR127, 0x00 }, { ZYD_CR128, 0x14 }, \ + { ZYD_CR129, 0x12 }, { ZYD_CR130, 0x10 }, { ZYD_CR131, 0x0c }, \ + { ZYD_CR136, 0xdf }, { ZYD_CR137, 0xa0 }, { ZYD_CR138, 0xa8 }, \ + { ZYD_CR139, 0xb4 }, { ZYD_CR140, 0x98 }, { ZYD_CR141, 0x82 }, \ + { ZYD_CR142, 0x53 }, { ZYD_CR143, 0x1c }, { ZYD_CR144, 0x6c }, \ + { ZYD_CR147, 0x07 }, { ZYD_CR148, 0x40 }, { ZYD_CR149, 0x40 }, \ + { ZYD_CR150, 0x14 }, { ZYD_CR151, 0x18 }, { ZYD_CR159, 0x70 }, \ + { ZYD_CR160, 0xfe }, { ZYD_CR161, 0xee }, { ZYD_CR162, 0xaa }, \ + { ZYD_CR163, 0xfa }, { ZYD_CR164, 0xfa }, { ZYD_CR165, 0xea }, \ + { ZYD_CR166, 0xbe }, { ZYD_CR167, 0xbe }, { ZYD_CR168, 0x6a }, \ + { ZYD_CR169, 0xba }, { ZYD_CR170, 0xba }, { ZYD_CR171, 0xba }, \ + { ZYD_CR204, 0x7d }, { ZYD_CR203, 0x30 }, \ + { 0, 0 } \ +} + +#define ZYD_RFMD_PHY \ +{ \ + { ZYD_CR2, 0x1e }, { ZYD_CR9, 0x20 }, { ZYD_CR10, 0x89 }, \ + { ZYD_CR11, 0x00 }, { ZYD_CR15, 0xd0 }, { ZYD_CR17, 0x68 }, \ + { ZYD_CR19, 0x4a }, { ZYD_CR20, 0x0c }, { ZYD_CR21, 0x0e }, \ + { ZYD_CR23, 0x48 }, { ZYD_CR24, 0x14 }, { ZYD_CR26, 0x90 }, \ + { ZYD_CR27, 0x30 }, { ZYD_CR29, 0x20 }, { ZYD_CR31, 0xb2 }, \ + { ZYD_CR32, 0x43 }, { ZYD_CR33, 0x28 }, { ZYD_CR38, 0x30 }, \ + { ZYD_CR34, 0x0f }, { ZYD_CR35, 0xf0 }, { ZYD_CR41, 0x2a }, \ + { ZYD_CR46, 0x7f }, { ZYD_CR47, 0x1e }, { ZYD_CR51, 0xc5 }, \ + { ZYD_CR52, 0xc5 }, { ZYD_CR53, 0xc5 }, { ZYD_CR79, 0x58 }, \ + { ZYD_CR80, 0x30 }, { ZYD_CR81, 0x30 }, { ZYD_CR82, 0x00 }, \ + { ZYD_CR83, 0x24 }, { ZYD_CR84, 0x04 }, { ZYD_CR85, 0x00 }, \ + { ZYD_CR86, 0x10 }, { ZYD_CR87, 0x2a }, { ZYD_CR88, 0x10 }, \ + { ZYD_CR89, 0x24 }, { ZYD_CR90, 0x18 }, { ZYD_CR91, 0x00 }, \ + { ZYD_CR92, 0x0a }, { ZYD_CR93, 0x00 }, { ZYD_CR94, 0x01 }, \ + { ZYD_CR95, 0x00 }, { ZYD_CR96, 0x40 }, { ZYD_CR97, 0x37 }, \ + { ZYD_CR98, 0x05 }, { ZYD_CR99, 0x28 }, { ZYD_CR100, 0x00 }, \ + { ZYD_CR101, 0x13 }, { ZYD_CR102, 0x27 }, { ZYD_CR103, 0x27 }, \ + { ZYD_CR104, 0x18 }, { ZYD_CR105, 0x12 }, { ZYD_CR106, 0x1a }, \ + { ZYD_CR107, 0x24 }, { ZYD_CR108, 0x0a }, { ZYD_CR109, 0x13 }, \ + { ZYD_CR110, 0x2f }, { ZYD_CR111, 0x27 }, { ZYD_CR112, 0x27 }, \ + { ZYD_CR113, 0x27 }, { ZYD_CR114, 0x27 }, { ZYD_CR115, 0x40 }, \ + { ZYD_CR116, 0x40 }, { ZYD_CR117, 0xf0 }, { ZYD_CR118, 0xf0 }, \ + { ZYD_CR119, 0x16 }, { ZYD_CR122, 0x00 }, { ZYD_CR127, 0x03 }, \ + { ZYD_CR131, 0x08 }, { ZYD_CR138, 0x28 }, { ZYD_CR148, 0x44 }, \ + { ZYD_CR150, 0x10 }, { ZYD_CR169, 0xbb }, { ZYD_CR170, 0xbb } \ +} + +#define ZYD_RFMD_RF \ +{ \ + 0x000007, 0x07dd43, 0x080959, 0x0e6666, 0x116a57, 0x17dd43, \ + 0x1819f9, 0x1e6666, 0x214554, 0x25e7fa, 0x27fffa, 0x294128, \ + 0x2c0000, 0x300000, 0x340000, 0x381e0f, 0x6c180f \ +} + +#define ZYD_RFMD_CHANTABLE \ +{ \ + { 0x181979, 0x1e6666 }, \ + { 0x181989, 0x1e6666 }, \ + { 0x181999, 0x1e6666 }, \ + { 0x1819a9, 0x1e6666 }, \ + { 0x1819b9, 0x1e6666 }, \ + { 0x1819c9, 0x1e6666 }, \ + { 0x1819d9, 0x1e6666 }, \ + { 0x1819e9, 0x1e6666 }, \ + { 0x1819f9, 0x1e6666 }, \ + { 0x181a09, 0x1e6666 }, \ + { 0x181a19, 0x1e6666 }, \ + { 0x181a29, 0x1e6666 }, \ + { 0x181a39, 0x1e6666 }, \ + { 0x181a60, 0x1c0000 } \ +} + +#define ZYD_AL2230_PHY \ +{ \ + { ZYD_CR15, 0x20 }, { ZYD_CR23, 0x40 }, { ZYD_CR24, 0x20 }, \ + { ZYD_CR26, 0x11 }, { ZYD_CR28, 0x3e }, { ZYD_CR29, 0x00 }, \ + { ZYD_CR44, 0x33 }, { ZYD_CR106, 0x2a }, { ZYD_CR107, 0x1a }, \ + { ZYD_CR109, 0x09 }, { ZYD_CR110, 0x27 }, { ZYD_CR111, 0x2b }, \ + { ZYD_CR112, 0x2b }, { ZYD_CR119, 0x0a }, { ZYD_CR10, 0x89 }, \ + { ZYD_CR17, 0x28 }, { ZYD_CR26, 0x93 }, { ZYD_CR34, 0x30 }, \ + { ZYD_CR35, 0x3e }, { ZYD_CR41, 0x24 }, { ZYD_CR44, 0x32 }, \ + { ZYD_CR46, 0x96 }, { ZYD_CR47, 0x1e }, { ZYD_CR79, 0x58 }, \ + { ZYD_CR80, 0x30 }, { ZYD_CR81, 0x30 }, { ZYD_CR87, 0x0a }, \ + { ZYD_CR89, 0x04 }, { ZYD_CR92, 0x0a }, { ZYD_CR99, 0x28 }, \ + { ZYD_CR100, 0x00 }, { ZYD_CR101, 0x13 }, { ZYD_CR102, 0x27 }, \ + { ZYD_CR106, 0x24 }, { ZYD_CR107, 0x2a }, { ZYD_CR109, 0x09 }, \ + { ZYD_CR110, 0x13 }, { ZYD_CR111, 0x1f }, { ZYD_CR112, 0x1f }, \ + { ZYD_CR113, 0x27 }, { ZYD_CR114, 0x27 }, { ZYD_CR115, 0x24 }, \ + { ZYD_CR116, 0x24 }, { ZYD_CR117, 0xf4 }, { ZYD_CR118, 0xfc }, \ + { ZYD_CR119, 0x10 }, { ZYD_CR120, 0x4f }, { ZYD_CR121, 0x77 }, \ + { ZYD_CR122, 0xe0 }, { ZYD_CR137, 0x88 }, { ZYD_CR252, 0xff }, \ + { ZYD_CR253, 0xff }, { ZYD_CR251, 0x2f }, { ZYD_CR251, 0x3f }, \ + { ZYD_CR138, 0x28 }, { ZYD_CR203, 0x06 } \ +} + +#define ZYD_AL2230_PHY_B \ +{ \ + { ZYD_CR10, 0x89 }, { ZYD_CR15, 0x20 }, { ZYD_CR17, 0x2B }, \ + { ZYD_CR23, 0x40 }, { ZYD_CR24, 0x20 }, { ZYD_CR26, 0x93 }, \ + { ZYD_CR28, 0x3e }, { ZYD_CR29, 0x00 }, { ZYD_CR33, 0x28 }, \ + { ZYD_CR34, 0x30 }, { ZYD_CR35, 0x3e }, { ZYD_CR41, 0x24 }, \ + { ZYD_CR44, 0x32 }, { ZYD_CR46, 0x99 }, { ZYD_CR47, 0x1e }, \ + { ZYD_CR48, 0x06 }, { ZYD_CR49, 0xf9 }, { ZYD_CR51, 0x01 }, \ + { ZYD_CR52, 0x80 }, { ZYD_CR53, 0x7e }, { ZYD_CR65, 0x00 }, \ + { ZYD_CR66, 0x00 }, { ZYD_CR67, 0x00 }, { ZYD_CR68, 0x00 }, \ + { ZYD_CR69, 0x28 }, { ZYD_CR79, 0x58 }, { ZYD_CR80, 0x30 }, \ + { ZYD_CR81, 0x30 }, { ZYD_CR87, 0x0a }, { ZYD_CR89, 0x04 }, \ + { ZYD_CR91, 0x00 }, { ZYD_CR92, 0x0a }, { ZYD_CR98, 0x8d }, \ + { ZYD_CR99, 0x00 }, { ZYD_CR101, 0x13 }, { ZYD_CR102, 0x27 }, \ + { ZYD_CR106, 0x24 }, { ZYD_CR107, 0x2a }, { ZYD_CR109, 0x13 }, \ + { ZYD_CR110, 0x1f }, { ZYD_CR111, 0x1f }, { ZYD_CR112, 0x1f }, \ + { ZYD_CR113, 0x27 }, { ZYD_CR114, 0x27 }, { ZYD_CR115, 0x26 }, \ + { ZYD_CR116, 0x24 }, { ZYD_CR117, 0xfa }, { ZYD_CR118, 0xfa }, \ + { ZYD_CR119, 0x10 }, { ZYD_CR120, 0x4f }, { ZYD_CR121, 0x6c }, \ + { ZYD_CR122, 0xfc }, { ZYD_CR123, 0x57 }, { ZYD_CR125, 0xad }, \ + { ZYD_CR126, 0x6c }, { ZYD_CR127, 0x03 }, { ZYD_CR137, 0x50 }, \ + { ZYD_CR138, 0xa8 }, { ZYD_CR144, 0xac }, { ZYD_CR150, 0x0d }, \ + { ZYD_CR252, 0x34 }, { ZYD_CR253, 0x34 } \ +} + +#define ZYD_AL2230_PHY_PART1 \ +{ \ + { ZYD_CR240, 0x57 }, { ZYD_CR9, 0xe0 } \ +} + +#define ZYD_AL2230_PHY_PART2 \ +{ \ + { ZYD_CR251, 0x2f }, { ZYD_CR251, 0x7f }, \ +} + +#define ZYD_AL2230_PHY_PART3 \ +{ \ + { ZYD_CR128, 0x14 }, { ZYD_CR129, 0x12 }, { ZYD_CR130, 0x10 }, \ +} + +#define ZYD_AL2230S_PHY_INIT \ +{ \ + { ZYD_CR47, 0x1e }, { ZYD_CR106, 0x22 }, { ZYD_CR107, 0x2a }, \ + { ZYD_CR109, 0x13 }, { ZYD_CR118, 0xf8 }, { ZYD_CR119, 0x12 }, \ + { ZYD_CR122, 0xe0 }, { ZYD_CR128, 0x10 }, { ZYD_CR129, 0x0e }, \ + { ZYD_CR130, 0x10 } \ +} + +#define ZYD_AL2230_PHY_FINI_PART1 \ +{ \ + { ZYD_CR80, 0x30 }, { ZYD_CR81, 0x30 }, { ZYD_CR79, 0x58 }, \ + { ZYD_CR12, 0xf0 }, { ZYD_CR77, 0x1b }, { ZYD_CR78, 0x58 }, \ + { ZYD_CR203, 0x06 }, { ZYD_CR240, 0x80 }, \ +} + +#define ZYD_AL2230_RF_PART1 \ +{ \ + 0x03f790, 0x033331, 0x00000d, 0x0b3331, 0x03b812, 0x00fff3 \ +} + +#define ZYD_AL2230_RF_PART2 \ +{ \ + 0x000da4, 0x0f4dc5, 0x0805b6, 0x011687, 0x000688, 0x0403b9, \ + 0x00dbba, 0x00099b, 0x0bdffc, 0x00000d, 0x00500f \ +} + +#define ZYD_AL2230_RF_PART3 \ +{ \ + 0x00d00f, 0x004c0f, 0x00540f, 0x00700f, 0x00500f \ +} + +#define ZYD_AL2230_RF_B \ +{ \ + 0x03f790, 0x033331, 0x00000d, 0x0b3331, 0x03b812, 0x00fff3, \ + 0x0005a4, 0x0f4dc5, 0x0805b6, 0x0146c7, 0x000688, 0x0403b9, \ + 0x00dbba, 0x00099b, 0x0bdffc, 0x00000d, 0x00580f \ +} + +#define ZYD_AL2230_RF_B_PART1 \ +{ \ + 0x8cccd0, 0x481dc0, 0xcfff00, 0x25a000 \ +} + +#define ZYD_AL2230_RF_B_PART2 \ +{ \ + 0x25a000, 0xa3b2f0, 0x6da010, 0xe36280, 0x116000, 0x9dc020, \ + 0x5ddb00, 0xd99000, 0x3ffbd0, 0xb00000, 0xf01a00 \ +} + +#define ZYD_AL2230_RF_B_PART3 \ +{ \ + 0xf01b00, 0xf01e00, 0xf01a00 \ +} + +#define ZYD_AL2230_CHANTABLE \ +{ \ + { 0x03f790, 0x033331, 0x00000d }, \ + { 0x03f790, 0x0b3331, 0x00000d }, \ + { 0x03e790, 0x033331, 0x00000d }, \ + { 0x03e790, 0x0b3331, 0x00000d }, \ + { 0x03f7a0, 0x033331, 0x00000d }, \ + { 0x03f7a0, 0x0b3331, 0x00000d }, \ + { 0x03e7a0, 0x033331, 0x00000d }, \ + { 0x03e7a0, 0x0b3331, 0x00000d }, \ + { 0x03f7b0, 0x033331, 0x00000d }, \ + { 0x03f7b0, 0x0b3331, 0x00000d }, \ + { 0x03e7b0, 0x033331, 0x00000d }, \ + { 0x03e7b0, 0x0b3331, 0x00000d }, \ + { 0x03f7c0, 0x033331, 0x00000d }, \ + { 0x03e7c0, 0x066661, 0x00000d } \ +} + +#define ZYD_AL2230_CHANTABLE_B \ +{ \ + { 0x09efc0, 0x8cccc0, 0xb00000 }, \ + { 0x09efc0, 0x8cccd0, 0xb00000 }, \ + { 0x09e7c0, 0x8cccc0, 0xb00000 }, \ + { 0x09e7c0, 0x8cccd0, 0xb00000 }, \ + { 0x05efc0, 0x8cccc0, 0xb00000 }, \ + { 0x05efc0, 0x8cccd0, 0xb00000 }, \ + { 0x05e7c0, 0x8cccc0, 0xb00000 }, \ + { 0x05e7c0, 0x8cccd0, 0xb00000 }, \ + { 0x0defc0, 0x8cccc0, 0xb00000 }, \ + { 0x0defc0, 0x8cccd0, 0xb00000 }, \ + { 0x0de7c0, 0x8cccc0, 0xb00000 }, \ + { 0x0de7c0, 0x8cccd0, 0xb00000 }, \ + { 0x03efc0, 0x8cccc0, 0xb00000 }, \ + { 0x03e7c0, 0x866660, 0xb00000 } \ +} + +#define ZYD_AL7230B_PHY_1 \ +{ \ + { ZYD_CR240, 0x57 }, { ZYD_CR15, 0x20 }, { ZYD_CR23, 0x40 }, \ + { ZYD_CR24, 0x20 }, { ZYD_CR26, 0x11 }, { ZYD_CR28, 0x3e }, \ + { ZYD_CR29, 0x00 }, { ZYD_CR44, 0x33 }, { ZYD_CR106, 0x22 }, \ + { ZYD_CR107, 0x1a }, { ZYD_CR109, 0x09 }, { ZYD_CR110, 0x27 }, \ + { ZYD_CR111, 0x2b }, { ZYD_CR112, 0x2b }, { ZYD_CR119, 0x0a }, \ + { ZYD_CR122, 0xfc }, { ZYD_CR10, 0x89 }, { ZYD_CR17, 0x28 }, \ + { ZYD_CR26, 0x93 }, { ZYD_CR34, 0x30 }, { ZYD_CR35, 0x3e }, \ + { ZYD_CR41, 0x24 }, { ZYD_CR44, 0x32 }, { ZYD_CR46, 0x96 }, \ + { ZYD_CR47, 0x1e }, { ZYD_CR79, 0x58 }, { ZYD_CR80, 0x30 }, \ + { ZYD_CR81, 0x30 }, { ZYD_CR87, 0x0a }, { ZYD_CR89, 0x04 }, \ + { ZYD_CR92, 0x0a }, { ZYD_CR99, 0x28 }, { ZYD_CR100, 0x02 }, \ + { ZYD_CR101, 0x13 }, { ZYD_CR102, 0x27 }, { ZYD_CR106, 0x22 }, \ + { ZYD_CR107, 0x3f }, { ZYD_CR109, 0x09 }, { ZYD_CR110, 0x1f }, \ + { ZYD_CR111, 0x1f }, { ZYD_CR112, 0x1f }, { ZYD_CR113, 0x27 }, \ + { ZYD_CR114, 0x27 }, { ZYD_CR115, 0x24 }, { ZYD_CR116, 0x3f }, \ + { ZYD_CR117, 0xfa }, { ZYD_CR118, 0xfc }, { ZYD_CR119, 0x10 }, \ + { ZYD_CR120, 0x4f }, { ZYD_CR121, 0x77 }, { ZYD_CR137, 0x88 }, \ + { ZYD_CR138, 0xa8 }, { ZYD_CR252, 0x34 }, { ZYD_CR253, 0x34 }, \ + { ZYD_CR251, 0x2f } \ +} + +#define ZYD_AL7230B_PHY_2 \ +{ \ + { ZYD_CR251, 0x3f }, { ZYD_CR128, 0x14 }, { ZYD_CR129, 0x12 }, \ + { ZYD_CR130, 0x10 }, { ZYD_CR38, 0x38 }, { ZYD_CR136, 0xdf } \ +} + +#define ZYD_AL7230B_PHY_3 \ +{ \ + { ZYD_CR203, 0x06 }, { ZYD_CR240, 0x80 } \ +} + +#define ZYD_AL7230B_RF_1 \ +{ \ + 0x09ec04, 0x8cccc8, 0x4ff821, 0xc5fbfc, 0x21ebfe, 0xafd401, \ + 0x6cf56a, 0xe04073, 0x193d76, 0x9dd844, 0x500007, 0xd8c010, \ + 0x3c9000, 0xbfffff, 0x700000, 0xf15d58 \ +} + +#define ZYD_AL7230B_RF_2 \ +{ \ + 0xf15d59, 0xf15d5c, 0xf15d58 \ +} + +#define ZYD_AL7230B_RF_SETCHANNEL \ +{ \ + 0x4ff821, 0xc5fbfc, 0x21ebfe, 0xafd401, 0x6cf56a, 0xe04073, \ + 0x193d76, 0x9dd844, 0x500007, 0xd8c010, 0x3c9000, 0xf15d58 \ +} + +#define ZYD_AL7230B_CHANTABLE \ +{ \ + { 0x09ec00, 0x8cccc8 }, \ + { 0x09ec00, 0x8cccd8 }, \ + { 0x09ec00, 0x8cccc0 }, \ + { 0x09ec00, 0x8cccd0 }, \ + { 0x05ec00, 0x8cccc8 }, \ + { 0x05ec00, 0x8cccd8 }, \ + { 0x05ec00, 0x8cccc0 }, \ + { 0x05ec00, 0x8cccd0 }, \ + { 0x0dec00, 0x8cccc8 }, \ + { 0x0dec00, 0x8cccd8 }, \ + { 0x0dec00, 0x8cccc0 }, \ + { 0x0dec00, 0x8cccd0 }, \ + { 0x03ec00, 0x8cccc8 }, \ + { 0x03ec00, 0x866660 } \ +} + +#define ZYD_AL2210_PHY \ +{ \ + { ZYD_CR9, 0xe0 }, { ZYD_CR10, 0x91 }, { ZYD_CR12, 0x90 }, \ + { ZYD_CR15, 0xd0 }, { ZYD_CR16, 0x40 }, { ZYD_CR17, 0x58 }, \ + { ZYD_CR18, 0x04 }, { ZYD_CR23, 0x66 }, { ZYD_CR24, 0x14 }, \ + { ZYD_CR26, 0x90 }, { ZYD_CR31, 0x80 }, { ZYD_CR34, 0x06 }, \ + { ZYD_CR35, 0x3e }, { ZYD_CR38, 0x38 }, { ZYD_CR46, 0x90 }, \ + { ZYD_CR47, 0x1e }, { ZYD_CR64, 0x64 }, { ZYD_CR79, 0xb5 }, \ + { ZYD_CR80, 0x38 }, { ZYD_CR81, 0x30 }, { ZYD_CR113, 0xc0 }, \ + { ZYD_CR127, 0x03 } \ +} + +#define ZYD_AL2210_RF \ +{ \ + 0x2396c0, 0x00fcb1, 0x358132, 0x0108b3, 0xc77804, 0x456415, \ + 0xff2226, 0x806667, 0x7860f8, 0xbb01c9, 0x00000a, 0x00000b \ +} + +#define ZYD_AL2210_CHANTABLE \ +{ \ + 0x0196c0, 0x019710, 0x019760, 0x0197b0, 0x019800, 0x019850, \ + 0x0198a0, 0x0198f0, 0x019940, 0x019990, 0x0199e0, 0x019a30, \ + 0x019a80, 0x019b40 \ +} + +#define ZYD_GCT_PHY \ +{ \ + { ZYD_CR47, 0x1e }, { ZYD_CR15, 0xdc }, { ZYD_CR113, 0xc0 }, \ + { ZYD_CR20, 0x0c }, { ZYD_CR17, 0x65 }, { ZYD_CR34, 0x04 }, \ + { ZYD_CR35, 0x35 }, { ZYD_CR24, 0x20 }, { ZYD_CR9, 0xe0 }, \ + { ZYD_CR127, 0x02 }, { ZYD_CR10, 0x91 }, { ZYD_CR23, 0x7f }, \ + { ZYD_CR27, 0x10 }, { ZYD_CR28, 0x7a }, { ZYD_CR79, 0xb5 }, \ + { ZYD_CR64, 0x80 }, { ZYD_CR33, 0x28 }, { ZYD_CR38, 0x30 } \ +} + +#define ZYD_GCT_RF \ +{ \ + 0x1f0000, 0x1f0000, 0x1f0200, 0x1f0600, 0x1f8600, 0x1f8600, \ + 0x002050, 0x1f8000, 0x1f8200, 0x1f8600, 0x1c0000, 0x10c458, \ + 0x088e92, 0x187b82, 0x0401b4, 0x140816, 0x0c7000, 0x1c0000, \ + 0x02ccae, 0x128023, 0x0a0000, 0x1a0000, 0x06e380, 0x16cb94, \ + 0x0e1740, 0x014980, 0x116240, 0x090000, 0x192304, 0x05112f, \ + 0x0d54a8, 0x0f8000, 0x1c0008, 0x1c0000, 0x1a0000, 0x1c0008, \ + 0x150000, 0x0c7000, 0x150800, 0x150000 \ +} + +#define ZYD_GCT_CHANTABLE \ +{ \ + 0x1a0000, 0x1a8000, 0x1a4000, 0x1ac000, 0x1a2000, 0x1aa000, \ + 0x1a6000, 0x1ae000, 0x1a1000, 0x1a9000, 0x1a5000, 0x1ad000, \ + 0x1a3000, 0x1ab000 \ +} + +#define ZYD_MAXIM_PHY \ +{ \ + { ZYD_CR23, 0x40 }, { ZYD_CR15, 0x20 }, { ZYD_CR28, 0x3e }, \ + { ZYD_CR29, 0x00 }, { ZYD_CR26, 0x11 }, { ZYD_CR44, 0x33 }, \ + { ZYD_CR106, 0x2a }, { ZYD_CR107, 0x1a }, { ZYD_CR109, 0x2b }, \ + { ZYD_CR110, 0x2b }, { ZYD_CR111, 0x2b }, { ZYD_CR112, 0x2b }, \ + { ZYD_CR10, 0x89 }, { ZYD_CR17, 0x20 }, { ZYD_CR26, 0x93 }, \ + { ZYD_CR34, 0x30 }, { ZYD_CR35, 0x40 }, { ZYD_CR41, 0x24 }, \ + { ZYD_CR44, 0x32 }, { ZYD_CR46, 0x90 }, { ZYD_CR89, 0x18 }, \ + { ZYD_CR92, 0x0a }, { ZYD_CR101, 0x13 }, { ZYD_CR102, 0x27 }, \ + { ZYD_CR106, 0x20 }, { ZYD_CR107, 0x24 }, { ZYD_CR109, 0x09 }, \ + { ZYD_CR110, 0x13 }, { ZYD_CR111, 0x13 }, { ZYD_CR112, 0x13 }, \ + { ZYD_CR113, 0x27 }, { ZYD_CR114, 0x27 }, { ZYD_CR115, 0x24 }, \ + { ZYD_CR116, 0x24 }, { ZYD_CR117, 0xf4 }, { ZYD_CR118, 0xfa }, \ + { ZYD_CR120, 0x4f }, { ZYD_CR121, 0x77 }, { ZYD_CR122, 0xfe }, \ + { ZYD_CR10, 0x89 }, { ZYD_CR17, 0x20 }, { ZYD_CR26, 0x93 }, \ + { ZYD_CR34, 0x30 }, { ZYD_CR35, 0x40 }, { ZYD_CR41, 0x24 }, \ + { ZYD_CR44, 0x32 }, { ZYD_CR46, 0x90 }, { ZYD_CR89, 0x18 }, \ + { ZYD_CR92, 0x0a }, { ZYD_CR101, 0x13 }, { ZYD_CR102, 0x27 }, \ + { ZYD_CR106, 0x20 }, { ZYD_CR107, 0x24 }, { ZYD_CR109, 0x13 }, \ + { ZYD_CR110, 0x27 }, { ZYD_CR111, 0x27 }, { ZYD_CR112, 0x13 }, \ + { ZYD_CR113, 0x27 }, { ZYD_CR114, 0x27 }, { ZYD_CR115, 0x24 }, \ + { ZYD_CR116, 0x24 }, { ZYD_CR117, 0xf4 }, { ZYD_CR118, 0x00 }, \ + { ZYD_CR120, 0x4f }, { ZYD_CR121, 0x06 }, { ZYD_CR122, 0xfe }, \ + { ZYD_CR150, 0x0d } \ +} + +#define ZYD_MAXIM_RF \ +{ \ + 0x00ccd4, 0x030a03, 0x000400, 0x000ca1, 0x010072, 0x018645, \ + 0x004006, 0x0000a7, 0x008258, 0x003fc9, 0x00040a, 0x00000b, \ + 0x00026c \ +} + +#define ZYD_MAXIM_CHANTABLE \ +{ \ + { 0x0ccd4, 0x30a03 }, \ + { 0x22224, 0x00a13 }, \ + { 0x37774, 0x10a13 }, \ + { 0x0ccd4, 0x30a13 }, \ + { 0x22224, 0x00a23 }, \ + { 0x37774, 0x10a23 }, \ + { 0x0ccd4, 0x30a23 }, \ + { 0x22224, 0x00a33 }, \ + { 0x37774, 0x10a33 }, \ + { 0x0ccd4, 0x30a33 }, \ + { 0x22224, 0x00a43 }, \ + { 0x37774, 0x10a43 }, \ + { 0x0ccd4, 0x30a43 }, \ + { 0x199a4, 0x20a53 } \ +} + +#define ZYD_MAXIM2_PHY \ +{ \ + { ZYD_CR23, 0x40 }, { ZYD_CR15, 0x20 }, { ZYD_CR28, 0x3e }, \ + { ZYD_CR29, 0x00 }, { ZYD_CR26, 0x11 }, { ZYD_CR44, 0x33 }, \ + { ZYD_CR106, 0x2a }, { ZYD_CR107, 0x1a }, { ZYD_CR109, 0x2b }, \ + { ZYD_CR110, 0x2b }, { ZYD_CR111, 0x2b }, { ZYD_CR112, 0x2b }, \ + { ZYD_CR10, 0x89 }, { ZYD_CR17, 0x20 }, { ZYD_CR26, 0x93 }, \ + { ZYD_CR34, 0x30 }, { ZYD_CR35, 0x40 }, { ZYD_CR41, 0x24 }, \ + { ZYD_CR44, 0x32 }, { ZYD_CR46, 0x90 }, { ZYD_CR89, 0x18 }, \ + { ZYD_CR92, 0x0a }, { ZYD_CR101, 0x13 }, { ZYD_CR102, 0x27 }, \ + { ZYD_CR106, 0x20 }, { ZYD_CR107, 0x24 }, { ZYD_CR109, 0x09 }, \ + { ZYD_CR110, 0x13 }, { ZYD_CR111, 0x13 }, { ZYD_CR112, 0x13 }, \ + { ZYD_CR113, 0x27 }, { ZYD_CR114, 0x27 }, { ZYD_CR115, 0x24 }, \ + { ZYD_CR116, 0x24 }, { ZYD_CR117, 0xf4 }, { ZYD_CR118, 0xfa }, \ + { ZYD_CR120, 0x4f }, { ZYD_CR121, 0x77 }, { ZYD_CR122, 0xfe }, \ + { ZYD_CR10, 0x89 }, { ZYD_CR17, 0x20 }, { ZYD_CR26, 0x93 }, \ + { ZYD_CR34, 0x30 }, { ZYD_CR35, 0x40 }, { ZYD_CR41, 0x24 }, \ + { ZYD_CR44, 0x32 }, { ZYD_CR46, 0x90 }, { ZYD_CR79, 0x58 }, \ + { ZYD_CR80, 0x30 }, { ZYD_CR81, 0x30 }, { ZYD_CR89, 0x18 }, \ + { ZYD_CR92, 0x0a }, { ZYD_CR101, 0x13 }, { ZYD_CR102, 0x27 }, \ + { ZYD_CR106, 0x20 }, { ZYD_CR107, 0x24 }, { ZYD_CR109, 0x09 }, \ + { ZYD_CR110, 0x13 }, { ZYD_CR111, 0x13 }, { ZYD_CR112, 0x13 }, \ + { ZYD_CR113, 0x27 }, { ZYD_CR114, 0x27 }, { ZYD_CR115, 0x24 }, \ + { ZYD_CR116, 0x24 }, { ZYD_CR117, 0xf4 }, { ZYD_CR118, 0x00 }, \ + { ZYD_CR120, 0x4f }, { ZYD_CR121, 0x06 }, { ZYD_CR122, 0xfe } \ +} + +#define ZYD_MAXIM2_RF \ +{ \ + 0x33334, 0x10a03, 0x00400, 0x00ca1, 0x10072, 0x18645, 0x04006, \ + 0x000a7, 0x08258, 0x03fc9, 0x0040a, 0x0000b, 0x0026c \ +} + +#define ZYD_MAXIM2_CHANTABLE_F \ +{ \ + 0x33334, 0x08884, 0x1ddd4, 0x33334, 0x08884, 0x1ddd4, 0x33334, \ + 0x08884, 0x1ddd4, 0x33334, 0x08884, 0x1ddd4, 0x33334, 0x26664 \ +} + +#define ZYD_MAXIM2_CHANTABLE \ +{ \ + { 0x33334, 0x10a03 }, \ + { 0x08884, 0x20a13 }, \ + { 0x1ddd4, 0x30a13 }, \ + { 0x33334, 0x10a13 }, \ + { 0x08884, 0x20a23 }, \ + { 0x1ddd4, 0x30a23 }, \ + { 0x33334, 0x10a23 }, \ + { 0x08884, 0x20a33 }, \ + { 0x1ddd4, 0x30a33 }, \ + { 0x33334, 0x10a33 }, \ + { 0x08884, 0x20a43 }, \ + { 0x1ddd4, 0x30a43 }, \ + { 0x33334, 0x10a43 }, \ + { 0x26664, 0x20a53 } \ +} + +/* + * Control pipe requests. + */ +#define ZYD_DOWNLOADREQ 0x30 +#define ZYD_DOWNLOADSTS 0x31 +#define ZYD_READFWDATAREQ 0x32 + +/* possible values for register ZYD_CR_INTERRUPT */ +#define ZYD_HWINT_MASK 0x004f0000 + +/* possible values for register ZYD_MAC_MISC */ +#define ZYD_UNLOCK_PHY_REGS 0x80 + +/* possible values for register ZYD_MAC_ENCRYPTION_TYPE */ +#define ZYD_ENC_SNIFFER 8 + +/* flags for register ZYD_MAC_RXFILTER */ +#define ZYD_FILTER_ASS_REQ (1 << 0) +#define ZYD_FILTER_ASS_RSP (1 << 1) +#define ZYD_FILTER_REASS_REQ (1 << 2) +#define ZYD_FILTER_REASS_RSP (1 << 3) +#define ZYD_FILTER_PRB_REQ (1 << 4) +#define ZYD_FILTER_PRB_RSP (1 << 5) +#define ZYD_FILTER_BCN (1 << 8) +#define ZYD_FILTER_ATIM (1 << 9) +#define ZYD_FILTER_DEASS (1 << 10) +#define ZYD_FILTER_AUTH (1 << 11) +#define ZYD_FILTER_DEAUTH (1 << 12) +#define ZYD_FILTER_PS_POLL (1 << 26) +#define ZYD_FILTER_RTS (1 << 27) +#define ZYD_FILTER_CTS (1 << 28) +#define ZYD_FILTER_ACK (1 << 29) +#define ZYD_FILTER_CFE (1 << 30) +#define ZYD_FILTER_CFE_A (1 << 31) + +/* helpers for register ZYD_MAC_RXFILTER */ +#define ZYD_FILTER_MONITOR 0xffffffff +#define ZYD_FILTER_BSS \ + (ZYD_FILTER_ASS_REQ | ZYD_FILTER_ASS_RSP | \ + ZYD_FILTER_REASS_REQ | ZYD_FILTER_REASS_RSP | \ + ZYD_FILTER_PRB_REQ | ZYD_FILTER_PRB_RSP | \ + (0x3 << 6) | \ + ZYD_FILTER_BCN | ZYD_FILTER_ATIM | ZYD_FILTER_DEASS | \ + ZYD_FILTER_AUTH | ZYD_FILTER_DEAUTH | \ + (0x7 << 13) | \ + ZYD_FILTER_PS_POLL | ZYD_FILTER_ACK) +#define ZYD_FILTER_HOSTAP \ + (ZYD_FILTER_ASS_REQ | ZYD_FILTER_REASS_REQ | \ + ZYD_FILTER_PRB_REQ | ZYD_FILTER_DEASS | ZYD_FILTER_AUTH | \ + ZYD_FILTER_DEAUTH | ZYD_FILTER_PS_POLL) + +struct zyd_tx_desc { + uint8_t phy; +#define ZYD_TX_PHY_SIGNAL(x) ((x) & 0xf) +#define ZYD_TX_PHY_OFDM (1 << 4) +#define ZYD_TX_PHY_SHPREAMBLE (1 << 5) /* CCK */ +#define ZYD_TX_PHY_5GHZ (1 << 5) /* OFDM */ + uint16_t len; + uint8_t flags; +#define ZYD_TX_FLAG_BACKOFF (1 << 0) +#define ZYD_TX_FLAG_MULTICAST (1 << 1) +#define ZYD_TX_FLAG_TYPE(x) (((x) & 0x3) << 2) +#define ZYD_TX_TYPE_DATA 0 +#define ZYD_TX_TYPE_PS_POLL 1 +#define ZYD_TX_TYPE_MGMT 2 +#define ZYD_TX_TYPE_CTL 3 +#define ZYD_TX_FLAG_WAKEUP (1 << 4) +#define ZYD_TX_FLAG_RTS (1 << 5) +#define ZYD_TX_FLAG_ENCRYPT (1 << 6) +#define ZYD_TX_FLAG_CTS_TO_SELF (1 << 7) + uint16_t pktlen; + uint16_t plcp_length; + uint8_t plcp_service; +#define ZYD_PLCP_LENGEXT 0x80 + uint16_t nextlen; +} __packed; + +struct zyd_plcphdr { + uint8_t signal; + uint8_t reserved[2]; + uint16_t service; /* unaligned! */ +} __packed; + +struct zyd_rx_stat { + uint8_t signal_cck; + uint8_t rssi; + uint8_t signal_ofdm; + uint8_t cipher; +#define ZYD_RX_CIPHER_WEP64 1 +#define ZYD_RX_CIPHER_TKIP 2 +#define ZYD_RX_CIPHER_AES 4 +#define ZYD_RX_CIPHER_WEP128 5 +#define ZYD_RX_CIPHER_WEP256 6 +#define ZYD_RX_CIPHER_WEP \ + (ZYD_RX_CIPHER_WEP64 | ZYD_RX_CIPHER_WEP128 | ZYD_RX_CIPHER_WEP256) + uint8_t flags; +#define ZYD_RX_OFDM (1 << 0) +#define ZYD_RX_TIMEOUT (1 << 1) +#define ZYD_RX_OVERRUN (1 << 2) +#define ZYD_RX_DECRYPTERR (1 << 3) +#define ZYD_RX_BADCRC32 (1 << 4) +#define ZYD_RX_NOT2ME (1 << 5) +#define ZYD_RX_BADCRC16 (1 << 6) +#define ZYD_RX_ERROR (1 << 7) +} __packed; + +/* this structure may be unaligned */ +struct zyd_rx_desc { +#define ZYD_MAX_RXFRAMECNT 3 + uWord len[ZYD_MAX_RXFRAMECNT]; + uWord tag; +#define ZYD_TAG_MULTIFRAME 0x697e +} __packed; + +/* I2C bus alike */ +struct zyd_rfwrite_cmd { + uint16_t code; + uint16_t width; + uint16_t bit[32]; +#define ZYD_RF_IF_LE (1 << 1) +#define ZYD_RF_CLK (1 << 2) +#define ZYD_RF_DATA (1 << 3) +} __packed; + +struct zyd_cmd { + uint16_t code; +#define ZYD_CMD_IOWR 0x0021 /* write HMAC or PHY register */ +#define ZYD_CMD_IORD 0x0022 /* read HMAC or PHY register */ +#define ZYD_CMD_RFCFG 0x0023 /* write RF register */ +#define ZYD_NOTIF_IORD 0x9001 /* response for ZYD_CMD_IORD */ +#define ZYD_NOTIF_MACINTR 0x9001 /* interrupt notification */ +#define ZYD_NOTIF_RETRYSTATUS 0xa001 /* Tx retry notification */ + uint8_t data[64]; +} __packed; + +/* structure for command ZYD_CMD_IOWR */ +struct zyd_pair { + uint16_t reg; +/* helpers macros to read/write 32-bit registers */ +#define ZYD_REG32_LO(reg) (reg) +#define ZYD_REG32_HI(reg) \ + ((reg) + ((((reg) & 0xf000) == 0x9000) ? 2 : 1)) + uint16_t val; +} __packed; + +/* structure for notification ZYD_NOTIF_RETRYSTATUS */ +struct zyd_notif_retry { + uint16_t rate; + uint8_t macaddr[IEEE80211_ADDR_LEN]; + uint16_t count; +} __packed; + +#define ZYD_CONFIG_NO 1 +#define ZYD_IFACE_INDEX 0 + +#define ZYD_INTR_TIMEOUT 1000 +#define ZYD_TX_TIMEOUT 10000 + +#define ZYD_MAX_TXBUFSZ \ + (sizeof(struct zyd_tx_desc) + MCLBYTES) +#define ZYD_MIN_FRAGSZ \ + (sizeof(struct zyd_plcphdr) + IEEE80211_MIN_LEN + \ + sizeof(struct zyd_rx_stat)) +#define ZYD_MIN_RXBUFSZ ZYD_MIN_FRAGSZ +#define ZYX_MAX_RXBUFSZ \ + ((sizeof (struct zyd_plcphdr) + IEEE80211_MAX_LEN + \ + sizeof (struct zyd_rx_stat)) * ZYD_MAX_RXFRAMECNT + \ + sizeof (struct zyd_rx_desc)) + +#define ZYD_RX_LIST_CNT 1 +#define ZYD_TX_LIST_CNT 5 +#define ZYD_CMD_FLAG_READ (1 << 0) + +/* quickly determine if a given rate is CCK or OFDM */ +#define ZYD_RATE_IS_OFDM(rate) ((rate) >= 12 && (rate) != 22) + +struct zyd_phy_pair { + uint16_t reg; + uint8_t val; +}; + +struct zyd_mac_pair { + uint16_t reg; + uint32_t val; +}; + +struct zyd_tx_data { + struct zyd_softc *sc; + usbd_xfer_handle xfer; + uint8_t *buf; + struct ieee80211_node *ni; + struct mbuf *m; +}; + +struct zyd_rx_data { + struct zyd_softc *sc; + usbd_xfer_handle xfer; + const uint8_t *buf; +}; + +struct zyd_node { + struct ieee80211_node ni; /* must be the first */ + struct ieee80211_amrr_node amn; +}; +#define ZYD_NODE(ni) ((struct zyd_node *)(ni)) + +struct zyd_rx_radiotap_header { + struct ieee80211_radiotap_header wr_ihdr; + uint8_t wr_flags; + uint8_t wr_rate; + uint16_t wr_chan_freq; + uint16_t wr_chan_flags; + int8_t wr_antsignal; + int8_t wr_antnoise; +} __packed; + +#define ZYD_RX_RADIOTAP_PRESENT \ + ((1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_RATE) | \ + (1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL) | \ + (1 << IEEE80211_RADIOTAP_DBM_ANTNOISE) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL)) + +struct zyd_tx_radiotap_header { + struct ieee80211_radiotap_header wt_ihdr; + uint8_t wt_flags; + uint8_t wt_rate; + uint16_t wt_chan_freq; + uint16_t wt_chan_flags; +} __packed; + +#define ZYD_TX_RADIOTAP_PRESENT \ + ((1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_RATE) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL)) + +struct zyd_softc; /* forward declaration */ + +struct zyd_rf { + /* RF methods */ + int (*init)(struct zyd_rf *); + int (*switch_radio)(struct zyd_rf *, int); + int (*set_channel)(struct zyd_rf *, uint8_t); + int (*bandedge6)(struct zyd_rf *, + struct ieee80211_channel *); + /* RF attributes */ + struct zyd_softc *rf_sc; /* back-pointer */ + int width; +}; + +struct zyd_rq { + const uint16_t *idata; + struct zyd_pair *odata; + int len; + STAILQ_ENTRY(zyd_rq) rq; +}; + +struct zyd_vap { + struct ieee80211vap vap; + int (*newstate)(struct ieee80211vap *, + enum ieee80211_state, int); + struct callout amrr_ch; + struct ieee80211_amrr amrr; +}; +#define ZYD_VAP(vap) ((struct zyd_vap *)(vap)) + +struct zyd_softc { + device_t sc_dev; + usbd_device_handle sc_udev; + usbd_interface_handle sc_iface; + struct ifnet *sc_ifp; + + enum ieee80211_state sc_state; + int sc_arg; + int sc_flags; +#define ZYD_FLAG_FWLOADED (1 << 0) +#define ZYD_FLAG_DETACHING (1 << 1) +#define ZYD_FLAG_INITONCE (1 << 2) +#define ZYD_FLAG_INITDONE (1 << 3) + int sc_if_flags; + uint32_t sc_debug; + + struct usb_task sc_mcasttask; + struct usb_task sc_scantask; + int sc_scan_action; +#define ZYD_SCAN_START 0 +#define ZYD_SCAN_END 1 +#define ZYD_SET_CHANNEL 2 + struct usb_task sc_task; + struct callout sc_watchdog_ch; + + struct zyd_rf sc_rf; + + STAILQ_HEAD(, zyd_rq) sc_rqh; + + uint8_t sc_bssid[IEEE80211_ADDR_LEN]; + uint16_t sc_fwbase; + uint8_t sc_regdomain; + uint8_t sc_macrev; + uint16_t sc_fwrev; + uint8_t sc_rfrev; + uint8_t sc_parev; + uint8_t sc_al2230s; + uint8_t sc_bandedge6; + uint8_t sc_newphy; + uint8_t sc_cckgain; + uint8_t sc_fix_cr157; + uint8_t sc_ledtype; + uint8_t sc_txled; + + uint32_t sc_atim_wnd; + uint32_t sc_pre_tbtt; + uint32_t sc_bcn_int; + + uint8_t sc_pwrcal[14]; + uint8_t sc_pwrint[14]; + uint8_t sc_ofdm36_cal[14]; + uint8_t sc_ofdm48_cal[14]; + uint8_t sc_ofdm54_cal[14]; +#define ZYD_ENDPT_BOUT 0 +#define ZYD_ENDPT_BIN 1 +#define ZYD_ENDPT_IIN 2 +#define ZYD_ENDPT_IOUT 3 +#define ZYD_ENDPT_CNT 4 + usbd_pipe_handle sc_ep[ZYD_ENDPT_CNT]; + uint8_t *sc_ibuf; + + struct mtx sc_txmtx; + struct zyd_rx_data sc_rxdata[ZYD_RX_LIST_CNT]; + struct zyd_tx_data sc_txdata[ZYD_TX_LIST_CNT]; + int sc_txidx; + int sc_txqueued; + int sc_txtimer; + + struct zyd_rx_radiotap_header sc_rxtap; + int sc_rxtap_len; + struct zyd_tx_radiotap_header sc_txtap; + int sc_txtap_len; +}; + +#define ZYD_LOCK(sc) do { ((sc) = (sc)); mtx_lock(&Giant); } while (0) +#define ZYD_UNLOCK(sc) mtx_unlock(&Giant) +#define ZYD_TX_LOCK(sc) mtx_lock(&(sc)->sc_txmtx) +#define ZYD_TX_UNLOCK(sc) mtx_unlock(&(sc)->sc_txmtx) + diff --git a/sys/legacy/dev/usb/kue_fw.h b/sys/legacy/dev/usb/kue_fw.h new file mode 100644 index 0000000..8934465 --- /dev/null +++ b/sys/legacy/dev/usb/kue_fw.h @@ -0,0 +1,685 @@ +/*- + * Copyright (c) 1997, 1998, 1999, 2000 + * Bill Paul <wpaul@ee.columbia.edu>. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * This file contains the firmware needed to make the KLSI chip work, + * along with a few constants related to the QT Engine microcontroller + * embedded in the KLSI part. + * + * Firmware is loaded using the vendor-specific 'send scan data' + * command (0xFF). The basic operation is that we must load the + * firmware, then issue some trigger commands to fix it up and start + * it running. There are three transfers: load the binary code, + * load the 'fixup' (data segment?), then issue a command to + * start the code firmware running. The data itself is prefixed by + * a 16-bit signature word, a 16-bit length value, a type byte + * and an interrupt (command) byte. The code segment is of type + * 0x02 (replacement interrupt vector data) and the fixup segment + * is of type 0x03 (replacement interrupt fixup data). The interrupt + * code is 0x64 (load new code). The length word is the total length + * of the segment minus 7. I precomputed the values and stuck them + * into the appropriate locations within the segments to save some + * work in the driver. + */ + +/* QT controller data block types. */ +/* Write data into specific memory location. */ +#define KUE_QTBTYPE_WRITE_DATA 0x00 +/* Write data into interrupt vector location */ +#define KUE_QTBTYPE_WRITE_INTVEC 0x01 +/* Replace interrupt vector with this data */ +#define KUE_QTBTYPE_REPL_INTVEC 0x02 +/* Fixup interrupt vector code with this data */ +#define KUE_QTBTYPE_FIXUP_INTVEC 0x03 +/* Force jump to location */ +#define KUE_QTBTYPE_JUMP 0x04 +/* Force call to location */ +#define KUE_QTBTYPE_CALL 0x05 +/* Force interrupt call */ +#define KUE_QTBTYPE_CALLINTR 0x06 +/* + * Cause data to be written using the specified QT engine + * interrupt, from starting location in memory for a specified + * number of bytes. + */ +#define KUE_QTBTYPE_WRITE_WITH_INTR 0x07 +/* Cause data from stream to be written using specified QT interrupt. */ +#define KUE_QTBTYPE_WRITE_STR_WITH_INTR 0x08 +/* Cause data to be written to config locations. */ +/* Addresses assume 0xc000 offset. */ +#define KUE_QTBTYPE_WRITE_CONFIG 0x09 + +#define KUE_QTINTR_LOAD_CODE 0x64 +#define KUE_QTINTR_TRIGGER_CODE 0x3B +#define KUE_QTINTR_LOAD_CODE_HIGH 0x9C + +/* Firmware code segment */ +static unsigned char kue_code_seg[] = +{ + /******************************************/ + /* NOTE: B6/C3 is data header signature */ + /* 0xAA/0xBB is data length = total */ + /* bytes - 7, 0xCC is type, 0xDD is */ + /* interrupt to use. */ + /******************************************/ + 0xB6, 0xC3, 0xf7, 0x0e, 0x02, 0x64, + 0x9f, 0xcf, 0xbc, 0x08, 0xe7, 0x57, 0x00, 0x00, + 0x9a, 0x08, 0x97, 0xc1, 0xe7, 0x67, 0xff, 0x1f, + 0x28, 0xc0, 0xe7, 0x87, 0x00, 0x04, 0x24, 0xc0, + 0xe7, 0x67, 0xff, 0xf9, 0x22, 0xc0, 0x97, 0xcf, + 0xe7, 0x09, 0xa2, 0xc0, 0x94, 0x08, 0xd7, 0x09, + 0x00, 0xc0, 0xe7, 0x59, 0xba, 0x08, 0x94, 0x08, + 0x03, 0xc1, 0xe7, 0x67, 0xff, 0xf7, 0x24, 0xc0, + 0xe7, 0x05, 0x00, 0xc0, 0xa7, 0xcf, 0x92, 0x08, + 0xe7, 0x57, 0x00, 0x00, 0x8e, 0x08, 0xa7, 0xa1, + 0x8e, 0x08, 0x97, 0xcf, 0xe7, 0x57, 0x00, 0x00, + 0xf2, 0x09, 0x0a, 0xc0, 0xe7, 0x57, 0x00, 0x00, + 0xa4, 0xc0, 0xa7, 0xc0, 0x56, 0x08, 0x9f, 0xaf, + 0x70, 0x09, 0xe7, 0x07, 0x00, 0x00, 0xf2, 0x09, + 0xe7, 0x57, 0xff, 0xff, 0x90, 0x08, 0x9f, 0xa0, + 0x40, 0x00, 0xe7, 0x59, 0x90, 0x08, 0x94, 0x08, + 0x9f, 0xa0, 0x40, 0x00, 0xc8, 0x09, 0xa2, 0x08, + 0x08, 0x62, 0x9f, 0xa1, 0x14, 0x0a, 0xe7, 0x57, + 0x00, 0x00, 0x52, 0x08, 0xa7, 0xc0, 0x56, 0x08, + 0x9f, 0xaf, 0x04, 0x00, 0xe7, 0x57, 0x00, 0x00, + 0x8e, 0x08, 0xa7, 0xc1, 0x56, 0x08, 0xc0, 0x09, + 0xa8, 0x08, 0x00, 0x60, 0x05, 0xc4, 0xc0, 0x59, + 0x94, 0x08, 0x02, 0xc0, 0x9f, 0xaf, 0xee, 0x00, + 0xe7, 0x59, 0xae, 0x08, 0x94, 0x08, 0x02, 0xc1, + 0x9f, 0xaf, 0xf6, 0x00, 0x9f, 0xaf, 0x9e, 0x03, + 0xef, 0x57, 0x00, 0x00, 0xf0, 0x09, 0x9f, 0xa1, + 0xde, 0x01, 0xe7, 0x57, 0x00, 0x00, 0x78, 0x08, + 0x9f, 0xa0, 0xe4, 0x03, 0x9f, 0xaf, 0x2c, 0x04, + 0xa7, 0xcf, 0x56, 0x08, 0x48, 0x02, 0xe7, 0x09, + 0x94, 0x08, 0xa8, 0x08, 0xc8, 0x37, 0x04, 0x00, + 0x9f, 0xaf, 0x68, 0x04, 0x97, 0xcf, 0xe7, 0x57, + 0x00, 0x00, 0xa6, 0x08, 0x97, 0xc0, 0xd7, 0x09, + 0x00, 0xc0, 0xc1, 0xdf, 0xc8, 0x09, 0x9c, 0x08, + 0x08, 0x62, 0x1d, 0xc0, 0x27, 0x04, 0x9c, 0x08, + 0x10, 0x94, 0xf0, 0x07, 0xee, 0x09, 0x02, 0x00, + 0xc1, 0x07, 0x01, 0x00, 0x70, 0x00, 0x04, 0x00, + 0xf0, 0x07, 0x44, 0x01, 0x06, 0x00, 0x50, 0xaf, + 0xe7, 0x09, 0x94, 0x08, 0xae, 0x08, 0xe7, 0x17, + 0x14, 0x00, 0xae, 0x08, 0xe7, 0x67, 0xff, 0x07, + 0xae, 0x08, 0xe7, 0x07, 0xff, 0xff, 0xa8, 0x08, + 0xe7, 0x07, 0x00, 0x00, 0xa6, 0x08, 0xe7, 0x05, + 0x00, 0xc0, 0x97, 0xcf, 0xd7, 0x09, 0x00, 0xc0, + 0xc1, 0xdf, 0x48, 0x02, 0xd0, 0x09, 0x9c, 0x08, + 0x27, 0x02, 0x9c, 0x08, 0xe7, 0x09, 0x20, 0xc0, + 0xee, 0x09, 0xe7, 0xd0, 0xee, 0x09, 0xe7, 0x05, + 0x00, 0xc0, 0x97, 0xcf, 0x48, 0x02, 0xc8, 0x37, + 0x04, 0x00, 0x00, 0x0c, 0x0c, 0x00, 0x00, 0x60, + 0x21, 0xc0, 0xc0, 0x37, 0x3e, 0x00, 0x23, 0xc9, + 0xc0, 0x57, 0xb4, 0x05, 0x1b, 0xc8, 0xc0, 0x17, + 0x3f, 0x00, 0xc0, 0x67, 0xc0, 0xff, 0x30, 0x00, + 0x08, 0x00, 0xf0, 0x07, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x02, 0xc0, 0x17, 0x4c, 0x00, 0x30, 0x00, + 0x06, 0x00, 0xf0, 0x07, 0xbe, 0x01, 0x0a, 0x00, + 0x48, 0x02, 0xc1, 0x07, 0x02, 0x00, 0xd7, 0x09, + 0x00, 0xc0, 0xc1, 0xdf, 0x51, 0xaf, 0xe7, 0x05, + 0x00, 0xc0, 0x97, 0xcf, 0x9f, 0xaf, 0x68, 0x04, + 0x9f, 0xaf, 0xe4, 0x03, 0x97, 0xcf, 0x9f, 0xaf, + 0xe4, 0x03, 0xc9, 0x37, 0x04, 0x00, 0xc1, 0xdf, + 0xc8, 0x09, 0x70, 0x08, 0x50, 0x02, 0x67, 0x02, + 0x70, 0x08, 0xd1, 0x07, 0x00, 0x00, 0xc0, 0xdf, + 0x9f, 0xaf, 0xde, 0x01, 0x97, 0xcf, 0xe7, 0x57, + 0x00, 0x00, 0xaa, 0x08, 0x97, 0xc1, 0xe7, 0x57, + 0x01, 0x00, 0x7a, 0x08, 0x97, 0xc0, 0xc8, 0x09, + 0x6e, 0x08, 0x08, 0x62, 0x97, 0xc0, 0x00, 0x02, + 0xc0, 0x17, 0x0e, 0x00, 0x27, 0x00, 0x34, 0x01, + 0x27, 0x0c, 0x0c, 0x00, 0x36, 0x01, 0xef, 0x57, + 0x00, 0x00, 0xf0, 0x09, 0x9f, 0xc0, 0xbe, 0x02, + 0xe7, 0x57, 0x00, 0x00, 0xb0, 0x08, 0x97, 0xc1, + 0xe7, 0x07, 0x09, 0x00, 0x12, 0xc0, 0xe7, 0x77, + 0x00, 0x08, 0x20, 0xc0, 0x9f, 0xc1, 0xb6, 0x02, + 0xe7, 0x57, 0x09, 0x00, 0x12, 0xc0, 0x77, 0xc9, + 0xd7, 0x09, 0x00, 0xc0, 0xc1, 0xdf, 0xe7, 0x77, + 0x00, 0x08, 0x20, 0xc0, 0x2f, 0xc1, 0xe7, 0x07, + 0x00, 0x00, 0x42, 0xc0, 0xe7, 0x07, 0x05, 0x00, + 0x90, 0xc0, 0xc8, 0x07, 0x0a, 0x00, 0xe7, 0x77, + 0x04, 0x00, 0x20, 0xc0, 0x09, 0xc1, 0x08, 0xda, + 0x7a, 0xc1, 0xe7, 0x07, 0x00, 0x01, 0x42, 0xc0, + 0xe7, 0x07, 0x04, 0x00, 0x90, 0xc0, 0x1a, 0xcf, + 0xe7, 0x07, 0x01, 0x00, 0x7a, 0x08, 0x00, 0xd8, + 0x27, 0x50, 0x34, 0x01, 0x17, 0xc1, 0xe7, 0x77, + 0x02, 0x00, 0x20, 0xc0, 0x79, 0xc1, 0x27, 0x50, + 0x34, 0x01, 0x10, 0xc1, 0xe7, 0x77, 0x02, 0x00, + 0x20, 0xc0, 0x79, 0xc0, 0x9f, 0xaf, 0xd8, 0x02, + 0xe7, 0x05, 0x00, 0xc0, 0x00, 0x60, 0x9f, 0xc0, + 0xde, 0x01, 0x97, 0xcf, 0xe7, 0x07, 0x01, 0x00, + 0xb8, 0x08, 0x06, 0xcf, 0xe7, 0x07, 0x30, 0x0e, + 0x02, 0x00, 0xe7, 0x07, 0x50, 0xc3, 0x12, 0xc0, + 0xe7, 0x05, 0x00, 0xc0, 0x97, 0xcf, 0xe7, 0x07, + 0x01, 0x00, 0xb8, 0x08, 0x97, 0xcf, 0xe7, 0x07, + 0x50, 0xc3, 0x12, 0xc0, 0xe7, 0x07, 0x30, 0x0e, + 0x02, 0x00, 0xe7, 0x07, 0x01, 0x00, 0x7a, 0x08, + 0xe7, 0x07, 0x05, 0x00, 0x90, 0xc0, 0x97, 0xcf, + 0xe7, 0x07, 0x00, 0x01, 0x42, 0xc0, 0xe7, 0x07, + 0x04, 0x00, 0x90, 0xc0, 0xe7, 0x07, 0x00, 0x00, + 0x7a, 0x08, 0xe7, 0x57, 0x0f, 0x00, 0xb2, 0x08, + 0x13, 0xc1, 0x9f, 0xaf, 0x2e, 0x08, 0xca, 0x09, + 0xac, 0x08, 0xf2, 0x17, 0x01, 0x00, 0x5c, 0x00, + 0xf2, 0x27, 0x00, 0x00, 0x5e, 0x00, 0xe7, 0x07, + 0x00, 0x00, 0xb2, 0x08, 0xe7, 0x07, 0x01, 0x00, + 0xb4, 0x08, 0xc0, 0x07, 0xff, 0xff, 0x97, 0xcf, + 0x9f, 0xaf, 0x4c, 0x03, 0xc0, 0x69, 0xb4, 0x08, + 0x57, 0x00, 0x9f, 0xde, 0x33, 0x00, 0xc1, 0x05, + 0x27, 0xd8, 0xb2, 0x08, 0x27, 0xd2, 0xb4, 0x08, + 0xe7, 0x87, 0x01, 0x00, 0xb4, 0x08, 0xe7, 0x67, + 0xff, 0x03, 0xb4, 0x08, 0x00, 0x60, 0x97, 0xc0, + 0xe7, 0x07, 0x01, 0x00, 0xb0, 0x08, 0x27, 0x00, + 0x12, 0xc0, 0x97, 0xcf, 0xc0, 0x09, 0xb6, 0x08, + 0x00, 0xd2, 0x02, 0xc3, 0xc0, 0x97, 0x05, 0x80, + 0x27, 0x00, 0xb6, 0x08, 0xc0, 0x99, 0x82, 0x08, + 0xc0, 0x99, 0xa2, 0xc0, 0x97, 0xcf, 0xe7, 0x07, + 0x00, 0x00, 0xb0, 0x08, 0xc0, 0xdf, 0x97, 0xcf, + 0xc8, 0x09, 0x72, 0x08, 0x08, 0x62, 0x02, 0xc0, + 0x10, 0x64, 0x07, 0xc1, 0xe7, 0x07, 0x00, 0x00, + 0x64, 0x08, 0xe7, 0x07, 0xc8, 0x05, 0x24, 0x00, + 0x97, 0xcf, 0x27, 0x04, 0x72, 0x08, 0xc8, 0x17, + 0x0e, 0x00, 0x27, 0x02, 0x64, 0x08, 0xe7, 0x07, + 0xd6, 0x05, 0x24, 0x00, 0x97, 0xcf, 0xd7, 0x09, + 0x00, 0xc0, 0xc1, 0xdf, 0xe7, 0x57, 0x00, 0x00, + 0x62, 0x08, 0x13, 0xc1, 0x9f, 0xaf, 0x70, 0x03, + 0xe7, 0x57, 0x00, 0x00, 0x64, 0x08, 0x13, 0xc0, + 0xe7, 0x09, 0x64, 0x08, 0x30, 0x01, 0xe7, 0x07, + 0xf2, 0x05, 0x32, 0x01, 0xe7, 0x07, 0x10, 0x00, + 0x96, 0xc0, 0xe7, 0x09, 0x64, 0x08, 0x62, 0x08, + 0x04, 0xcf, 0xe7, 0x57, 0x00, 0x00, 0x64, 0x08, + 0x02, 0xc1, 0x9f, 0xaf, 0x70, 0x03, 0xe7, 0x05, + 0x00, 0xc0, 0x97, 0xcf, 0xd7, 0x09, 0x00, 0xc0, + 0xc1, 0xdf, 0xc8, 0x09, 0x72, 0x08, 0x27, 0x02, + 0x78, 0x08, 0x08, 0x62, 0x03, 0xc1, 0xe7, 0x05, + 0x00, 0xc0, 0x97, 0xcf, 0x27, 0x04, 0x72, 0x08, + 0xe7, 0x05, 0x00, 0xc0, 0xf0, 0x07, 0x40, 0x00, + 0x08, 0x00, 0xf0, 0x07, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x02, 0xc0, 0x17, 0x0c, 0x00, 0x30, 0x00, + 0x06, 0x00, 0xf0, 0x07, 0x64, 0x01, 0x0a, 0x00, + 0xc8, 0x17, 0x04, 0x00, 0xc1, 0x07, 0x02, 0x00, + 0x51, 0xaf, 0x97, 0xcf, 0xe7, 0x57, 0x00, 0x00, + 0x6a, 0x08, 0x97, 0xc0, 0xc1, 0xdf, 0xc8, 0x09, + 0x6a, 0x08, 0x27, 0x04, 0x6a, 0x08, 0x27, 0x52, + 0x6c, 0x08, 0x03, 0xc1, 0xe7, 0x07, 0x6a, 0x08, + 0x6c, 0x08, 0xc0, 0xdf, 0x17, 0x02, 0xc8, 0x17, + 0x0e, 0x00, 0x9f, 0xaf, 0x16, 0x05, 0xc8, 0x05, + 0x00, 0x60, 0x03, 0xc0, 0x9f, 0xaf, 0x80, 0x04, + 0x97, 0xcf, 0x9f, 0xaf, 0x68, 0x04, 0x97, 0xcf, + 0xd7, 0x09, 0x00, 0xc0, 0xc1, 0xdf, 0x08, 0x62, + 0x1c, 0xc0, 0xd0, 0x09, 0x72, 0x08, 0x27, 0x02, + 0x72, 0x08, 0xe7, 0x05, 0x00, 0xc0, 0x97, 0xcf, + 0x97, 0x02, 0xca, 0x09, 0xac, 0x08, 0xf2, 0x17, + 0x01, 0x00, 0x04, 0x00, 0xf2, 0x27, 0x00, 0x00, + 0x06, 0x00, 0xca, 0x17, 0x2c, 0x00, 0xf8, 0x77, + 0x01, 0x00, 0x0e, 0x00, 0x06, 0xc0, 0xca, 0xd9, + 0xf8, 0x57, 0xff, 0x00, 0x0e, 0x00, 0x01, 0xc1, + 0xca, 0xd9, 0x22, 0x1c, 0x0c, 0x00, 0xe2, 0x27, + 0x00, 0x00, 0xe2, 0x17, 0x01, 0x00, 0xe2, 0x27, + 0x00, 0x00, 0xca, 0x05, 0x00, 0x0c, 0x0c, 0x00, + 0xc0, 0x17, 0x41, 0x00, 0xc0, 0x67, 0xc0, 0xff, + 0x30, 0x00, 0x08, 0x00, 0x00, 0x02, 0xc0, 0x17, + 0x0c, 0x00, 0x30, 0x00, 0x06, 0x00, 0xf0, 0x07, + 0xdc, 0x00, 0x0a, 0x00, 0xf0, 0x07, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x0c, 0x08, 0x00, 0x40, 0xd1, + 0x01, 0x00, 0xc0, 0x19, 0xa6, 0x08, 0xc0, 0x59, + 0x98, 0x08, 0x04, 0xc9, 0x49, 0xaf, 0x9f, 0xaf, + 0xee, 0x00, 0x4a, 0xaf, 0x67, 0x10, 0xa6, 0x08, + 0xc8, 0x17, 0x04, 0x00, 0xc1, 0x07, 0x01, 0x00, + 0xd7, 0x09, 0x00, 0xc0, 0xc1, 0xdf, 0x50, 0xaf, + 0xe7, 0x05, 0x00, 0xc0, 0x97, 0xcf, 0xc0, 0x07, + 0x01, 0x00, 0xc1, 0x09, 0x7c, 0x08, 0xc1, 0x77, + 0x01, 0x00, 0x97, 0xc1, 0xd8, 0x77, 0x01, 0x00, + 0x12, 0xc0, 0xc9, 0x07, 0x4c, 0x08, 0x9f, 0xaf, + 0x64, 0x05, 0x04, 0xc1, 0xc1, 0x77, 0x08, 0x00, + 0x13, 0xc0, 0x97, 0xcf, 0xc1, 0x77, 0x02, 0x00, + 0x97, 0xc1, 0xc1, 0x77, 0x10, 0x00, 0x0c, 0xc0, + 0x9f, 0xaf, 0x86, 0x05, 0x97, 0xcf, 0xc1, 0x77, + 0x04, 0x00, 0x06, 0xc0, 0xc9, 0x07, 0x7e, 0x08, + 0x9f, 0xaf, 0x64, 0x05, 0x97, 0xc0, 0x00, 0xcf, + 0x00, 0x90, 0x97, 0xcf, 0x50, 0x54, 0x97, 0xc1, + 0x70, 0x5c, 0x02, 0x00, 0x02, 0x00, 0x97, 0xc1, + 0x70, 0x5c, 0x04, 0x00, 0x04, 0x00, 0x97, 0xcf, + 0xc0, 0x00, 0x60, 0x00, 0x30, 0x00, 0x18, 0x00, + 0x0c, 0x00, 0x06, 0x00, 0x00, 0x00, 0xcb, 0x09, + 0x88, 0x08, 0xcc, 0x09, 0x8a, 0x08, 0x0b, 0x53, + 0x11, 0xc0, 0xc9, 0x02, 0xca, 0x07, 0x78, 0x05, + 0x9f, 0xaf, 0x64, 0x05, 0x97, 0xc0, 0x0a, 0xc8, + 0x82, 0x08, 0x0a, 0xcf, 0x82, 0x08, 0x9f, 0xaf, + 0x64, 0x05, 0x97, 0xc0, 0x05, 0xc2, 0x89, 0x30, + 0x82, 0x60, 0x78, 0xc1, 0x00, 0x90, 0x97, 0xcf, + 0x89, 0x10, 0x09, 0x53, 0x79, 0xc2, 0x89, 0x30, + 0x82, 0x08, 0x7a, 0xcf, 0xc0, 0xdf, 0x97, 0xcf, + 0xe7, 0x09, 0x96, 0xc0, 0x66, 0x08, 0xe7, 0x09, + 0x98, 0xc0, 0x68, 0x08, 0x0f, 0xcf, 0xe7, 0x09, + 0x96, 0xc0, 0x66, 0x08, 0xe7, 0x09, 0x98, 0xc0, + 0x68, 0x08, 0xe7, 0x09, 0x64, 0x08, 0x30, 0x01, + 0xe7, 0x07, 0xf2, 0x05, 0x32, 0x01, 0xe7, 0x07, + 0x10, 0x00, 0x96, 0xc0, 0xd7, 0x09, 0x00, 0xc0, + 0x17, 0x02, 0xc8, 0x09, 0x62, 0x08, 0xc8, 0x37, + 0x0e, 0x00, 0xe7, 0x57, 0x04, 0x00, 0x68, 0x08, + 0x3d, 0xc0, 0xe7, 0x87, 0x00, 0x08, 0x24, 0xc0, + 0xe7, 0x09, 0x94, 0x08, 0xba, 0x08, 0xe7, 0x17, + 0x64, 0x00, 0xba, 0x08, 0xe7, 0x67, 0xff, 0x07, + 0xba, 0x08, 0xe7, 0x77, 0x2a, 0x00, 0x66, 0x08, + 0x30, 0xc0, 0x97, 0x02, 0xca, 0x09, 0xac, 0x08, + 0xe7, 0x77, 0x20, 0x00, 0x66, 0x08, 0x0e, 0xc0, + 0xf2, 0x17, 0x01, 0x00, 0x10, 0x00, 0xf2, 0x27, + 0x00, 0x00, 0x12, 0x00, 0xe7, 0x77, 0x0a, 0x00, + 0x66, 0x08, 0xca, 0x05, 0x1e, 0xc0, 0x97, 0x02, + 0xca, 0x09, 0xac, 0x08, 0xf2, 0x17, 0x01, 0x00, + 0x0c, 0x00, 0xf2, 0x27, 0x00, 0x00, 0x0e, 0x00, + 0xe7, 0x77, 0x02, 0x00, 0x66, 0x08, 0x07, 0xc0, + 0xf2, 0x17, 0x01, 0x00, 0x44, 0x00, 0xf2, 0x27, + 0x00, 0x00, 0x46, 0x00, 0x06, 0xcf, 0xf2, 0x17, + 0x01, 0x00, 0x60, 0x00, 0xf2, 0x27, 0x00, 0x00, + 0x62, 0x00, 0xca, 0x05, 0x9f, 0xaf, 0x68, 0x04, + 0x0f, 0xcf, 0x57, 0x02, 0x09, 0x02, 0xf1, 0x09, + 0x68, 0x08, 0x0c, 0x00, 0xf1, 0xda, 0x0c, 0x00, + 0xc8, 0x09, 0x6c, 0x08, 0x50, 0x02, 0x67, 0x02, + 0x6c, 0x08, 0xd1, 0x07, 0x00, 0x00, 0xc9, 0x05, + 0xe7, 0x09, 0x64, 0x08, 0x62, 0x08, 0xe7, 0x57, + 0x00, 0x00, 0x62, 0x08, 0x02, 0xc0, 0x9f, 0xaf, + 0x70, 0x03, 0xc8, 0x05, 0xe7, 0x05, 0x00, 0xc0, + 0xc0, 0xdf, 0x97, 0xcf, 0xd7, 0x09, 0x00, 0xc0, + 0x17, 0x00, 0x17, 0x02, 0x97, 0x02, 0xc0, 0x09, + 0x92, 0xc0, 0xe7, 0x87, 0x00, 0x08, 0x24, 0xc0, + 0xe7, 0x09, 0x94, 0x08, 0xba, 0x08, 0xe7, 0x17, + 0x64, 0x00, 0xba, 0x08, 0xe7, 0x67, 0xff, 0x07, + 0xba, 0x08, 0xe7, 0x07, 0x04, 0x00, 0x90, 0xc0, + 0xca, 0x09, 0xac, 0x08, 0xe7, 0x07, 0x00, 0x00, + 0x7a, 0x08, 0xe7, 0x07, 0x66, 0x03, 0x02, 0x00, + 0xc0, 0x77, 0x02, 0x00, 0x10, 0xc0, 0xef, 0x57, + 0x00, 0x00, 0xf0, 0x09, 0x04, 0xc0, 0x9f, 0xaf, + 0xd8, 0x02, 0x9f, 0xcf, 0x12, 0x08, 0xf2, 0x17, + 0x01, 0x00, 0x50, 0x00, 0xf2, 0x27, 0x00, 0x00, + 0x52, 0x00, 0x9f, 0xcf, 0x12, 0x08, 0xef, 0x57, + 0x00, 0x00, 0xf0, 0x09, 0x08, 0xc0, 0xe7, 0x57, + 0x00, 0x00, 0xb8, 0x08, 0xe7, 0x07, 0x00, 0x00, + 0xb8, 0x08, 0x0a, 0xc0, 0x03, 0xcf, 0xc0, 0x77, + 0x10, 0x00, 0x06, 0xc0, 0xf2, 0x17, 0x01, 0x00, + 0x58, 0x00, 0xf2, 0x27, 0x00, 0x00, 0x5a, 0x00, + 0xc0, 0x77, 0x80, 0x00, 0x06, 0xc0, 0xf2, 0x17, + 0x01, 0x00, 0x70, 0x00, 0xf2, 0x27, 0x00, 0x00, + 0x72, 0x00, 0xc0, 0x77, 0x08, 0x00, 0x1d, 0xc1, + 0xf2, 0x17, 0x01, 0x00, 0x08, 0x00, 0xf2, 0x27, + 0x00, 0x00, 0x0a, 0x00, 0xc0, 0x77, 0x00, 0x02, + 0x06, 0xc0, 0xf2, 0x17, 0x01, 0x00, 0x64, 0x00, + 0xf2, 0x27, 0x00, 0x00, 0x66, 0x00, 0xc0, 0x77, + 0x40, 0x00, 0x06, 0xc0, 0xf2, 0x17, 0x01, 0x00, + 0x5c, 0x00, 0xf2, 0x27, 0x00, 0x00, 0x5e, 0x00, + 0xc0, 0x77, 0x01, 0x00, 0x01, 0xc0, 0x37, 0xcf, + 0x36, 0xcf, 0xf2, 0x17, 0x01, 0x00, 0x00, 0x00, + 0xf2, 0x27, 0x00, 0x00, 0x02, 0x00, 0xef, 0x57, + 0x00, 0x00, 0xf0, 0x09, 0x18, 0xc0, 0xe7, 0x57, + 0x01, 0x00, 0xb2, 0x08, 0x0e, 0xc2, 0x07, 0xc8, + 0xf2, 0x17, 0x01, 0x00, 0x50, 0x00, 0xf2, 0x27, + 0x00, 0x00, 0x52, 0x00, 0x06, 0xcf, 0xf2, 0x17, + 0x01, 0x00, 0x54, 0x00, 0xf2, 0x27, 0x00, 0x00, + 0x56, 0x00, 0xe7, 0x07, 0x00, 0x00, 0xb2, 0x08, + 0xe7, 0x07, 0x01, 0x00, 0xb4, 0x08, 0xc8, 0x09, + 0x34, 0x01, 0xca, 0x17, 0x14, 0x00, 0xd8, 0x77, + 0x01, 0x00, 0x05, 0xc0, 0xca, 0xd9, 0xd8, 0x57, + 0xff, 0x00, 0x01, 0xc0, 0xca, 0xd9, 0xe2, 0x19, + 0x94, 0xc0, 0xe2, 0x27, 0x00, 0x00, 0xe2, 0x17, + 0x01, 0x00, 0xe2, 0x27, 0x00, 0x00, 0x9f, 0xaf, + 0x2e, 0x08, 0x9f, 0xaf, 0xde, 0x01, 0xe7, 0x57, + 0x00, 0x00, 0xaa, 0x08, 0x9f, 0xa1, 0xf0, 0x0b, + 0xca, 0x05, 0xc8, 0x05, 0xc0, 0x05, 0xe7, 0x05, + 0x00, 0xc0, 0xc0, 0xdf, 0x97, 0xcf, 0xc8, 0x09, + 0x6e, 0x08, 0x08, 0x62, 0x97, 0xc0, 0x27, 0x04, + 0x6e, 0x08, 0x27, 0x52, 0x70, 0x08, 0x03, 0xc1, + 0xe7, 0x07, 0x6e, 0x08, 0x70, 0x08, 0x9f, 0xaf, + 0x68, 0x04, 0x97, 0xcf, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x33, 0xcc, + 0x00, 0x00, 0x00, 0x00, 0xe7, 0x57, 0x00, 0x80, + 0xb2, 0x00, 0x06, 0xc2, 0xe7, 0x07, 0x52, 0x0e, + 0x12, 0x00, 0xe7, 0x07, 0x98, 0x0e, 0xb2, 0x00, + 0xe7, 0x07, 0xa4, 0x09, 0xf2, 0x02, 0xc8, 0x09, + 0xb4, 0x00, 0xf8, 0x07, 0x02, 0x00, 0x0d, 0x00, + 0xd7, 0x09, 0x0e, 0xc0, 0xe7, 0x07, 0x00, 0x00, + 0x0e, 0xc0, 0xc8, 0x09, 0xdc, 0x00, 0xf0, 0x07, + 0xff, 0xff, 0x09, 0x00, 0xf0, 0x07, 0xfb, 0x13, + 0x0b, 0x00, 0xe7, 0x09, 0xc0, 0x00, 0x58, 0x08, + 0xe7, 0x09, 0xbe, 0x00, 0x54, 0x08, 0xe7, 0x09, + 0x10, 0x00, 0x92, 0x08, 0xc8, 0x07, 0xb4, 0x09, + 0x9f, 0xaf, 0x8c, 0x09, 0x9f, 0xaf, 0xe2, 0x0b, + 0xc0, 0x07, 0x80, 0x01, 0x44, 0xaf, 0x27, 0x00, + 0x88, 0x08, 0x27, 0x00, 0x8a, 0x08, 0x27, 0x00, + 0x8c, 0x08, 0xc0, 0x07, 0x74, 0x00, 0x44, 0xaf, + 0x27, 0x00, 0xac, 0x08, 0x08, 0x00, 0x00, 0x90, + 0xc1, 0x07, 0x1d, 0x00, 0x20, 0x00, 0x20, 0x00, + 0x01, 0xda, 0x7c, 0xc1, 0x9f, 0xaf, 0x8a, 0x0b, + 0xc0, 0x07, 0x4c, 0x00, 0x48, 0xaf, 0x27, 0x00, + 0x56, 0x08, 0x9f, 0xaf, 0x72, 0x0c, 0xe7, 0x07, + 0x00, 0x80, 0x96, 0x08, 0xef, 0x57, 0x00, 0x00, + 0xf0, 0x09, 0x03, 0xc0, 0xe7, 0x07, 0x01, 0x00, + 0x1c, 0xc0, 0xe7, 0x05, 0x0e, 0xc0, 0x97, 0xcf, + 0x49, 0xaf, 0xe7, 0x87, 0x43, 0x00, 0x0e, 0xc0, + 0xe7, 0x07, 0xff, 0xff, 0x94, 0x08, 0x9f, 0xaf, + 0x8a, 0x0c, 0xc0, 0x07, 0x01, 0x00, 0x60, 0xaf, + 0x4a, 0xaf, 0x97, 0xcf, 0x00, 0x08, 0x09, 0x08, + 0x11, 0x08, 0x00, 0xda, 0x7c, 0xc1, 0x97, 0xcf, + 0x67, 0x04, 0xcc, 0x02, 0xc0, 0xdf, 0x51, 0x94, + 0xb1, 0xaf, 0x06, 0x00, 0xc1, 0xdf, 0xc9, 0x09, + 0xcc, 0x02, 0x49, 0x62, 0x75, 0xc1, 0xc0, 0xdf, + 0xa7, 0xcf, 0xd6, 0x02, 0x0e, 0x00, 0x24, 0x00, + 0xd6, 0x05, 0x22, 0x00, 0xc4, 0x06, 0xd0, 0x00, + 0xf0, 0x0b, 0xaa, 0x00, 0x0e, 0x0a, 0xbe, 0x00, + 0x2c, 0x0c, 0x10, 0x00, 0x20, 0x00, 0x04, 0x00, + 0xc4, 0x05, 0x02, 0x00, 0x66, 0x03, 0x06, 0x00, + 0x00, 0x00, 0x24, 0xc0, 0x04, 0x04, 0x28, 0xc0, + 0xfe, 0xfb, 0x1e, 0xc0, 0x00, 0x04, 0x22, 0xc0, + 0xff, 0xf0, 0xc0, 0x00, 0x60, 0x0b, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x34, 0x0a, 0x3e, 0x0a, + 0x9e, 0x0a, 0xa8, 0x0a, 0xce, 0x0a, 0xd2, 0x0a, + 0xd6, 0x0a, 0x00, 0x0b, 0x10, 0x0b, 0x1e, 0x0b, + 0x20, 0x0b, 0x28, 0x0b, 0x28, 0x0b, 0x27, 0x02, + 0xa2, 0x08, 0x97, 0xcf, 0xe7, 0x07, 0x00, 0x00, + 0xa2, 0x08, 0x0a, 0x0e, 0x01, 0x00, 0xca, 0x57, + 0x0e, 0x00, 0x9f, 0xc3, 0x2a, 0x0b, 0xca, 0x37, + 0x00, 0x00, 0x9f, 0xc2, 0x2a, 0x0b, 0x0a, 0xd2, + 0xb2, 0xcf, 0xf4, 0x09, 0xc8, 0x09, 0xde, 0x00, + 0x07, 0x06, 0x9f, 0xcf, 0x3c, 0x0b, 0xf0, 0x57, + 0x80, 0x01, 0x06, 0x00, 0x9f, 0xc8, 0x2a, 0x0b, + 0x27, 0x0c, 0x02, 0x00, 0x86, 0x08, 0xc0, 0x09, + 0x88, 0x08, 0x27, 0x00, 0x8a, 0x08, 0xe7, 0x07, + 0x00, 0x00, 0x84, 0x08, 0x27, 0x00, 0x5c, 0x08, + 0x00, 0x1c, 0x06, 0x00, 0x27, 0x00, 0x8c, 0x08, + 0x41, 0x90, 0x67, 0x50, 0x86, 0x08, 0x0d, 0xc0, + 0x67, 0x00, 0x5a, 0x08, 0x27, 0x0c, 0x06, 0x00, + 0x5e, 0x08, 0xe7, 0x07, 0x8a, 0x0a, 0x60, 0x08, + 0xc8, 0x07, 0x5a, 0x08, 0x41, 0x90, 0x51, 0xaf, + 0x97, 0xcf, 0x9f, 0xaf, 0xac, 0x0e, 0xe7, 0x09, + 0x8c, 0x08, 0x8a, 0x08, 0xe7, 0x09, 0x86, 0x08, + 0x84, 0x08, 0x59, 0xaf, 0x97, 0xcf, 0x27, 0x0c, + 0x02, 0x00, 0x7c, 0x08, 0x59, 0xaf, 0x97, 0xcf, + 0x09, 0x0c, 0x02, 0x00, 0x09, 0xda, 0x49, 0xd2, + 0xc9, 0x19, 0xac, 0x08, 0xc8, 0x07, 0x5a, 0x08, + 0xe0, 0x07, 0x00, 0x00, 0x60, 0x02, 0xe0, 0x07, + 0x04, 0x00, 0xd0, 0x07, 0x9a, 0x0a, 0x48, 0xdb, + 0x41, 0x90, 0x50, 0xaf, 0x97, 0xcf, 0x59, 0xaf, + 0x97, 0xcf, 0x59, 0xaf, 0x97, 0xcf, 0xf0, 0x57, + 0x06, 0x00, 0x06, 0x00, 0x26, 0xc1, 0xe7, 0x07, + 0x7e, 0x08, 0x5c, 0x08, 0x41, 0x90, 0x67, 0x00, + 0x5a, 0x08, 0x27, 0x0c, 0x06, 0x00, 0x5e, 0x08, + 0xe7, 0x07, 0x5c, 0x0b, 0x60, 0x08, 0xc8, 0x07, + 0x5a, 0x08, 0x41, 0x90, 0x51, 0xaf, 0x97, 0xcf, + 0x07, 0x0c, 0x06, 0x00, 0xc7, 0x57, 0x06, 0x00, + 0x10, 0xc1, 0xc8, 0x07, 0x7e, 0x08, 0x16, 0xcf, + 0x00, 0x0c, 0x02, 0x00, 0x00, 0xda, 0x40, 0xd1, + 0x27, 0x00, 0x98, 0x08, 0x1f, 0xcf, 0x1e, 0xcf, + 0x27, 0x0c, 0x02, 0x00, 0xa4, 0x08, 0x1a, 0xcf, + 0x00, 0xcf, 0x27, 0x02, 0x20, 0x01, 0xe7, 0x07, + 0x08, 0x00, 0x22, 0x01, 0xe7, 0x07, 0x13, 0x00, + 0xb0, 0xc0, 0x97, 0xcf, 0x41, 0x90, 0x67, 0x00, + 0x5a, 0x08, 0xe7, 0x01, 0x5e, 0x08, 0x27, 0x02, + 0x5c, 0x08, 0xe7, 0x07, 0x5c, 0x0b, 0x60, 0x08, + 0xc8, 0x07, 0x5a, 0x08, 0xc1, 0x07, 0x00, 0x80, + 0x50, 0xaf, 0x97, 0xcf, 0x59, 0xaf, 0x97, 0xcf, + 0x00, 0x60, 0x05, 0xc0, 0xe7, 0x07, 0x00, 0x00, + 0x9a, 0x08, 0xa7, 0xcf, 0x58, 0x08, 0x9f, 0xaf, + 0xe2, 0x0b, 0xe7, 0x07, 0x01, 0x00, 0x9a, 0x08, + 0x49, 0xaf, 0xd7, 0x09, 0x00, 0xc0, 0x07, 0xaf, + 0xe7, 0x05, 0x00, 0xc0, 0x4a, 0xaf, 0xa7, 0xcf, + 0x58, 0x08, 0xc0, 0x07, 0x40, 0x00, 0x44, 0xaf, + 0x27, 0x00, 0xa0, 0x08, 0x08, 0x00, 0xc0, 0x07, + 0x20, 0x00, 0x20, 0x94, 0x00, 0xda, 0x7d, 0xc1, + 0xc0, 0x07, 0xfe, 0x7f, 0x44, 0xaf, 0x40, 0x00, + 0x41, 0x90, 0xc0, 0x37, 0x08, 0x00, 0xdf, 0xde, + 0x50, 0x06, 0xc0, 0x57, 0x10, 0x00, 0x02, 0xc2, + 0xc0, 0x07, 0x10, 0x00, 0x27, 0x00, 0x76, 0x08, + 0x41, 0x90, 0x9f, 0xde, 0x40, 0x06, 0x44, 0xaf, + 0x27, 0x00, 0x74, 0x08, 0xc0, 0x09, 0x76, 0x08, + 0x41, 0x90, 0x00, 0xd2, 0x00, 0xd8, 0x9f, 0xde, + 0x08, 0x00, 0x44, 0xaf, 0x27, 0x00, 0x9e, 0x08, + 0x97, 0xcf, 0xe7, 0x87, 0x00, 0x84, 0x28, 0xc0, + 0xe7, 0x67, 0xff, 0xf3, 0x24, 0xc0, 0x97, 0xcf, + 0xe7, 0x87, 0x01, 0x00, 0xaa, 0x08, 0xe7, 0x57, + 0x00, 0x00, 0x7a, 0x08, 0x97, 0xc1, 0x9f, 0xaf, + 0xe2, 0x0b, 0xe7, 0x87, 0x00, 0x06, 0x22, 0xc0, + 0xe7, 0x07, 0x00, 0x00, 0x90, 0xc0, 0xe7, 0x67, + 0xfe, 0xff, 0x3e, 0xc0, 0xe7, 0x07, 0x2e, 0x00, + 0x0a, 0xc0, 0xe7, 0x87, 0x01, 0x00, 0x3e, 0xc0, + 0xe7, 0x07, 0xff, 0xff, 0x94, 0x08, 0x9f, 0xaf, + 0xf0, 0x0c, 0x97, 0xcf, 0x17, 0x00, 0xa7, 0xaf, + 0x54, 0x08, 0xc0, 0x05, 0x27, 0x00, 0x52, 0x08, + 0xe7, 0x87, 0x01, 0x00, 0xaa, 0x08, 0x9f, 0xaf, + 0xe2, 0x0b, 0xe7, 0x07, 0x0c, 0x00, 0x40, 0xc0, + 0x9f, 0xaf, 0xf0, 0x0c, 0xe7, 0x07, 0x00, 0x00, + 0x78, 0x08, 0x00, 0x90, 0xe7, 0x09, 0x88, 0x08, + 0x8a, 0x08, 0x27, 0x00, 0x84, 0x08, 0x27, 0x00, + 0x7c, 0x08, 0x9f, 0xaf, 0x8a, 0x0c, 0xe7, 0x07, + 0x00, 0x00, 0xb2, 0x02, 0xe7, 0x07, 0x00, 0x00, + 0xb4, 0x02, 0xc0, 0x07, 0x06, 0x00, 0xc8, 0x09, + 0xde, 0x00, 0xc8, 0x17, 0x03, 0x00, 0xc9, 0x07, + 0x7e, 0x08, 0x29, 0x0a, 0x00, 0xda, 0x7d, 0xc1, + 0x97, 0xcf, 0xd7, 0x09, 0x00, 0xc0, 0xc1, 0xdf, + 0x00, 0x90, 0x27, 0x00, 0x6a, 0x08, 0xe7, 0x07, + 0x6a, 0x08, 0x6c, 0x08, 0x27, 0x00, 0x6e, 0x08, + 0xe7, 0x07, 0x6e, 0x08, 0x70, 0x08, 0x27, 0x00, + 0x78, 0x08, 0x27, 0x00, 0x62, 0x08, 0x27, 0x00, + 0x64, 0x08, 0xc8, 0x09, 0x74, 0x08, 0xc1, 0x09, + 0x76, 0x08, 0xc9, 0x07, 0x72, 0x08, 0x11, 0x02, + 0x09, 0x02, 0xc8, 0x17, 0x40, 0x06, 0x01, 0xda, + 0x7a, 0xc1, 0x51, 0x94, 0xc8, 0x09, 0x9e, 0x08, + 0xc9, 0x07, 0x9c, 0x08, 0xc1, 0x09, 0x76, 0x08, + 0x01, 0xd2, 0x01, 0xd8, 0x11, 0x02, 0x09, 0x02, + 0xc8, 0x17, 0x08, 0x00, 0x01, 0xda, 0x7a, 0xc1, + 0x51, 0x94, 0xe7, 0x05, 0x00, 0xc0, 0x97, 0xcf, + 0xe7, 0x57, 0x00, 0x00, 0x52, 0x08, 0x97, 0xc0, + 0x9f, 0xaf, 0x04, 0x00, 0xe7, 0x09, 0x94, 0x08, + 0x90, 0x08, 0xe7, 0x57, 0xff, 0xff, 0x90, 0x08, + 0x04, 0xc1, 0xe7, 0x07, 0xf0, 0x0c, 0x8e, 0x08, + 0x97, 0xcf, 0xe7, 0x17, 0x32, 0x00, 0x90, 0x08, + 0xe7, 0x67, 0xff, 0x07, 0x90, 0x08, 0xe7, 0x07, + 0x26, 0x0d, 0x8e, 0x08, 0x97, 0xcf, 0xd7, 0x09, + 0x00, 0xc0, 0xc1, 0xdf, 0xe7, 0x57, 0x00, 0x00, + 0x96, 0x08, 0x23, 0xc0, 0xe7, 0x07, 0x00, 0x80, + 0x80, 0xc0, 0xe7, 0x07, 0x04, 0x00, 0x90, 0xc0, + 0xe7, 0x07, 0x00, 0x00, 0x80, 0xc0, 0xe7, 0x07, + 0x00, 0x80, 0x80, 0xc0, 0xc0, 0x07, 0x00, 0x00, + 0xc0, 0x07, 0x00, 0x00, 0xc0, 0x07, 0x00, 0x00, + 0xe7, 0x07, 0x00, 0x00, 0x80, 0xc0, 0xe7, 0x07, + 0x00, 0x80, 0x80, 0xc0, 0xe7, 0x07, 0x00, 0x80, + 0x40, 0xc0, 0xc0, 0x07, 0x00, 0x00, 0xe7, 0x07, + 0x00, 0x00, 0x40, 0xc0, 0xe7, 0x07, 0x00, 0x00, + 0x80, 0xc0, 0xef, 0x57, 0x00, 0x00, 0xf1, 0x09, + 0x9f, 0xa0, 0xc0, 0x0d, 0xe7, 0x07, 0x04, 0x00, + 0x90, 0xc0, 0xe7, 0x07, 0x00, 0x02, 0x40, 0xc0, + 0xe7, 0x07, 0x0c, 0x02, 0x40, 0xc0, 0xe7, 0x07, + 0x00, 0x00, 0x96, 0x08, 0xe7, 0x07, 0x00, 0x00, + 0x8e, 0x08, 0xe7, 0x07, 0x00, 0x00, 0xaa, 0x08, + 0xd7, 0x09, 0x00, 0xc0, 0xc1, 0xdf, 0x9f, 0xaf, + 0x9e, 0x03, 0xe7, 0x05, 0x00, 0xc0, 0x9f, 0xaf, + 0xde, 0x01, 0xe7, 0x05, 0x00, 0xc0, 0x97, 0xcf, + 0x9f, 0xaf, 0xde, 0x0d, 0xef, 0x77, 0x00, 0x00, + 0xf1, 0x09, 0x97, 0xc1, 0x9f, 0xaf, 0xde, 0x0d, + 0xef, 0x77, 0x00, 0x00, 0xf1, 0x09, 0x97, 0xc1, + 0xef, 0x07, 0x01, 0x00, 0xf1, 0x09, 0xe7, 0x87, + 0x00, 0x08, 0x1e, 0xc0, 0xe7, 0x87, 0x00, 0x08, + 0x22, 0xc0, 0xe7, 0x67, 0xff, 0xf7, 0x22, 0xc0, + 0xe7, 0x77, 0x00, 0x08, 0x20, 0xc0, 0x11, 0xc0, + 0xe7, 0x67, 0xff, 0xf7, 0x1e, 0xc0, 0xe7, 0x87, + 0x00, 0x08, 0x22, 0xc0, 0xe7, 0x67, 0xff, 0xf7, + 0x22, 0xc0, 0xe7, 0x77, 0x00, 0x08, 0x20, 0xc0, + 0x04, 0xc1, 0xe7, 0x87, 0x00, 0x08, 0x22, 0xc0, + 0x97, 0xcf, 0xe7, 0x07, 0x01, 0x01, 0xf0, 0x09, + 0xef, 0x57, 0x18, 0x00, 0xfe, 0xff, 0x97, 0xc2, + 0xef, 0x07, 0x00, 0x00, 0xf0, 0x09, 0x97, 0xcf, + 0xd7, 0x09, 0x00, 0xc0, 0x17, 0x00, 0x17, 0x02, + 0x97, 0x02, 0xe7, 0x57, 0x00, 0x00, 0x7a, 0x08, + 0x06, 0xc0, 0xc0, 0x09, 0x92, 0xc0, 0xc0, 0x77, + 0x09, 0x02, 0x9f, 0xc1, 0xea, 0x06, 0x9f, 0xcf, + 0x20, 0x08, 0xd7, 0x09, 0x0e, 0xc0, 0xe7, 0x07, + 0x00, 0x00, 0x0e, 0xc0, 0x9f, 0xaf, 0x66, 0x0e, + 0xe7, 0x05, 0x0e, 0xc0, 0x97, 0xcf, 0xd7, 0x09, + 0x00, 0xc0, 0x17, 0x02, 0xc8, 0x09, 0xb0, 0xc0, + 0xe7, 0x67, 0xfe, 0x7f, 0xb0, 0xc0, 0xc8, 0x77, + 0x00, 0x20, 0x9f, 0xc1, 0x64, 0xeb, 0xe7, 0x57, + 0x00, 0x00, 0xc8, 0x02, 0x9f, 0xc1, 0x80, 0xeb, + 0xc8, 0x99, 0xca, 0x02, 0xc8, 0x67, 0x04, 0x00, + 0x9f, 0xc1, 0x96, 0xeb, 0x9f, 0xcf, 0x4c, 0xeb, + 0xe7, 0x07, 0x00, 0x00, 0xa6, 0xc0, 0xe7, 0x09, + 0xb0, 0xc0, 0xc8, 0x02, 0xe7, 0x07, 0x03, 0x00, + 0xb0, 0xc0, 0x97, 0xcf, 0xc0, 0x09, 0x86, 0x08, + 0xc0, 0x37, 0x01, 0x00, 0x97, 0xc9, 0xc9, 0x09, + 0x88, 0x08, 0x02, 0x00, 0x41, 0x90, 0x48, 0x02, + 0xc9, 0x17, 0x06, 0x00, 0x9f, 0xaf, 0x64, 0x05, + 0x9f, 0xa2, 0xd6, 0x0e, 0x02, 0xda, 0x77, 0xc1, + 0x41, 0x60, 0x71, 0xc1, 0x97, 0xcf, 0x17, 0x02, + 0x57, 0x02, 0x43, 0x04, 0x21, 0x04, 0xe0, 0x00, + 0x43, 0x04, 0x21, 0x04, 0xe0, 0x00, 0x43, 0x04, + 0x21, 0x04, 0xe0, 0x00, 0xc1, 0x07, 0x01, 0x00, + 0xc9, 0x05, 0xc8, 0x05, 0x97, 0xcf, + 0, 0 +}; + +/* Firmware fixup (data?) segment */ +static unsigned char kue_fix_seg[] = +{ + /******************************************/ + /* NOTE: B6/C3 is data header signature */ + /* 0xAA/0xBB is data length = total */ + /* bytes - 7, 0xCC is type, 0xDD is */ + /* interrupt to use. */ + /******************************************/ + 0xB6, 0xC3, 0xc9, 0x02, 0x03, 0x64, + 0x02, 0x00, 0x08, 0x00, 0x24, 0x00, 0x2e, 0x00, + 0x2c, 0x00, 0x3e, 0x00, 0x44, 0x00, 0x48, 0x00, + 0x50, 0x00, 0x5c, 0x00, 0x60, 0x00, 0x66, 0x00, + 0x6c, 0x00, 0x70, 0x00, 0x76, 0x00, 0x74, 0x00, + 0x7a, 0x00, 0x7e, 0x00, 0x84, 0x00, 0x8a, 0x00, + 0x8e, 0x00, 0x92, 0x00, 0x98, 0x00, 0x9c, 0x00, + 0xa0, 0x00, 0xa8, 0x00, 0xae, 0x00, 0xb4, 0x00, + 0xb2, 0x00, 0xba, 0x00, 0xbe, 0x00, 0xc4, 0x00, + 0xc8, 0x00, 0xce, 0x00, 0xd2, 0x00, 0xd6, 0x00, + 0xda, 0x00, 0xe2, 0x00, 0xe0, 0x00, 0xea, 0x00, + 0xf2, 0x00, 0xfe, 0x00, 0x06, 0x01, 0x0c, 0x01, + 0x1a, 0x01, 0x24, 0x01, 0x22, 0x01, 0x2a, 0x01, + 0x30, 0x01, 0x36, 0x01, 0x3c, 0x01, 0x4e, 0x01, + 0x52, 0x01, 0x58, 0x01, 0x5c, 0x01, 0x9c, 0x01, + 0xb6, 0x01, 0xba, 0x01, 0xc0, 0x01, 0xca, 0x01, + 0xd0, 0x01, 0xda, 0x01, 0xe2, 0x01, 0xea, 0x01, + 0xf0, 0x01, 0x0a, 0x02, 0x0e, 0x02, 0x14, 0x02, + 0x26, 0x02, 0x6c, 0x02, 0x8e, 0x02, 0x98, 0x02, + 0xa0, 0x02, 0xa6, 0x02, 0xba, 0x02, 0xc6, 0x02, + 0xce, 0x02, 0xe8, 0x02, 0xee, 0x02, 0xf4, 0x02, + 0xf8, 0x02, 0x0a, 0x03, 0x10, 0x03, 0x1a, 0x03, + 0x1e, 0x03, 0x2a, 0x03, 0x2e, 0x03, 0x34, 0x03, + 0x3a, 0x03, 0x44, 0x03, 0x4e, 0x03, 0x5a, 0x03, + 0x5e, 0x03, 0x6a, 0x03, 0x72, 0x03, 0x80, 0x03, + 0x84, 0x03, 0x8c, 0x03, 0x94, 0x03, 0x98, 0x03, + 0xa8, 0x03, 0xae, 0x03, 0xb4, 0x03, 0xba, 0x03, + 0xce, 0x03, 0xcc, 0x03, 0xd6, 0x03, 0xdc, 0x03, + 0xec, 0x03, 0xf0, 0x03, 0xfe, 0x03, 0x1c, 0x04, + 0x30, 0x04, 0x38, 0x04, 0x3c, 0x04, 0x40, 0x04, + 0x48, 0x04, 0x46, 0x04, 0x54, 0x04, 0x5e, 0x04, + 0x64, 0x04, 0x74, 0x04, 0x78, 0x04, 0x84, 0x04, + 0xd8, 0x04, 0xec, 0x04, 0xf0, 0x04, 0xf8, 0x04, + 0xfe, 0x04, 0x1c, 0x05, 0x2c, 0x05, 0x30, 0x05, + 0x4a, 0x05, 0x56, 0x05, 0x5a, 0x05, 0x88, 0x05, + 0x8c, 0x05, 0x96, 0x05, 0x9a, 0x05, 0xa8, 0x05, + 0xcc, 0x05, 0xd2, 0x05, 0xda, 0x05, 0xe0, 0x05, + 0xe4, 0x05, 0xfc, 0x05, 0x06, 0x06, 0x14, 0x06, + 0x12, 0x06, 0x1a, 0x06, 0x20, 0x06, 0x26, 0x06, + 0x2e, 0x06, 0x34, 0x06, 0x48, 0x06, 0x52, 0x06, + 0x64, 0x06, 0x86, 0x06, 0x90, 0x06, 0x9a, 0x06, + 0xa0, 0x06, 0xac, 0x06, 0xaa, 0x06, 0xb2, 0x06, + 0xb8, 0x06, 0xdc, 0x06, 0xda, 0x06, 0xe2, 0x06, + 0xe8, 0x06, 0xf2, 0x06, 0xf8, 0x06, 0xfc, 0x06, + 0x0a, 0x07, 0x10, 0x07, 0x14, 0x07, 0x24, 0x07, + 0x2a, 0x07, 0x32, 0x07, 0x38, 0x07, 0xb2, 0x07, + 0xba, 0x07, 0xde, 0x07, 0xe4, 0x07, 0x10, 0x08, + 0x14, 0x08, 0x1a, 0x08, 0x1e, 0x08, 0x30, 0x08, + 0x38, 0x08, 0x3c, 0x08, 0x44, 0x08, 0x42, 0x08, + 0x48, 0x08, 0xc6, 0x08, 0xcc, 0x08, 0xd2, 0x08, + 0xfe, 0x08, 0x04, 0x09, 0x0a, 0x09, 0x0e, 0x09, + 0x12, 0x09, 0x16, 0x09, 0x20, 0x09, 0x24, 0x09, + 0x28, 0x09, 0x32, 0x09, 0x46, 0x09, 0x4a, 0x09, + 0x50, 0x09, 0x54, 0x09, 0x5a, 0x09, 0x60, 0x09, + 0x7c, 0x09, 0x80, 0x09, 0xb8, 0x09, 0xbc, 0x09, + 0xc0, 0x09, 0xc4, 0x09, 0xc8, 0x09, 0xcc, 0x09, + 0xd0, 0x09, 0xd4, 0x09, 0xec, 0x09, 0xf4, 0x09, + 0xf6, 0x09, 0xf8, 0x09, 0xfa, 0x09, 0xfc, 0x09, + 0xfe, 0x09, 0x00, 0x0a, 0x02, 0x0a, 0x04, 0x0a, + 0x06, 0x0a, 0x08, 0x0a, 0x0a, 0x0a, 0x0c, 0x0a, + 0x10, 0x0a, 0x18, 0x0a, 0x24, 0x0a, 0x2c, 0x0a, + 0x32, 0x0a, 0x3c, 0x0a, 0x46, 0x0a, 0x4c, 0x0a, + 0x50, 0x0a, 0x54, 0x0a, 0x5a, 0x0a, 0x5e, 0x0a, + 0x66, 0x0a, 0x6c, 0x0a, 0x72, 0x0a, 0x78, 0x0a, + 0x7e, 0x0a, 0x7c, 0x0a, 0x82, 0x0a, 0x8c, 0x0a, + 0x92, 0x0a, 0x90, 0x0a, 0x98, 0x0a, 0x96, 0x0a, + 0xa2, 0x0a, 0xb2, 0x0a, 0xb6, 0x0a, 0xc4, 0x0a, + 0xe2, 0x0a, 0xe0, 0x0a, 0xe8, 0x0a, 0xee, 0x0a, + 0xf4, 0x0a, 0xf2, 0x0a, 0xf8, 0x0a, 0x0c, 0x0b, + 0x1a, 0x0b, 0x24, 0x0b, 0x40, 0x0b, 0x44, 0x0b, + 0x48, 0x0b, 0x4e, 0x0b, 0x4c, 0x0b, 0x52, 0x0b, + 0x68, 0x0b, 0x6c, 0x0b, 0x70, 0x0b, 0x76, 0x0b, + 0x88, 0x0b, 0x92, 0x0b, 0xbe, 0x0b, 0xca, 0x0b, + 0xce, 0x0b, 0xde, 0x0b, 0xf4, 0x0b, 0xfa, 0x0b, + 0x00, 0x0c, 0x24, 0x0c, 0x28, 0x0c, 0x30, 0x0c, + 0x36, 0x0c, 0x3c, 0x0c, 0x40, 0x0c, 0x4a, 0x0c, + 0x50, 0x0c, 0x58, 0x0c, 0x56, 0x0c, 0x5c, 0x0c, + 0x60, 0x0c, 0x64, 0x0c, 0x80, 0x0c, 0x94, 0x0c, + 0x9a, 0x0c, 0x98, 0x0c, 0x9e, 0x0c, 0xa4, 0x0c, + 0xa2, 0x0c, 0xa8, 0x0c, 0xac, 0x0c, 0xb0, 0x0c, + 0xb4, 0x0c, 0xb8, 0x0c, 0xbc, 0x0c, 0xce, 0x0c, + 0xd2, 0x0c, 0xd6, 0x0c, 0xf4, 0x0c, 0xfa, 0x0c, + 0x00, 0x0d, 0xfe, 0x0c, 0x06, 0x0d, 0x0e, 0x0d, + 0x0c, 0x0d, 0x16, 0x0d, 0x1c, 0x0d, 0x22, 0x0d, + 0x20, 0x0d, 0x30, 0x0d, 0x7e, 0x0d, 0x82, 0x0d, + 0x9a, 0x0d, 0xa0, 0x0d, 0xa6, 0x0d, 0xb0, 0x0d, + 0xb8, 0x0d, 0xc2, 0x0d, 0xc8, 0x0d, 0xce, 0x0d, + 0xd4, 0x0d, 0xdc, 0x0d, 0x1e, 0x0e, 0x2c, 0x0e, + 0x3e, 0x0e, 0x4c, 0x0e, 0x50, 0x0e, 0x5e, 0x0e, + 0xae, 0x0e, 0xb8, 0x0e, 0xc6, 0x0e, 0xca, 0x0e, + 0, 0 +}; + +/* Fixup command. */ +#define KUE_TRIGCMD_OFFSET 5 +static unsigned char kue_trig_seg[] = { +0xb6, 0xc3, 0x01, 0x00, 0x06, 0x64, 0x00, 0x00 +}; diff --git a/sys/legacy/dev/usb/ohci.c b/sys/legacy/dev/usb/ohci.c new file mode 100644 index 0000000..efa6e7e --- /dev/null +++ b/sys/legacy/dev/usb/ohci.c @@ -0,0 +1,3638 @@ +/* $NetBSD: ohci.c,v 1.138 2003/02/08 03:32:50 ichiro Exp $ */ + +/* Also, already ported: + * $NetBSD: ohci.c,v 1.140 2003/05/13 04:42:00 gson Exp $ + * $NetBSD: ohci.c,v 1.141 2003/09/10 20:08:29 mycroft Exp $ + * $NetBSD: ohci.c,v 1.142 2003/10/11 03:04:26 toshii Exp $ + * $NetBSD: ohci.c,v 1.143 2003/10/18 04:50:35 simonb Exp $ + * $NetBSD: ohci.c,v 1.144 2003/11/23 19:18:06 augustss Exp $ + * $NetBSD: ohci.c,v 1.145 2003/11/23 19:20:25 augustss Exp $ + * $NetBSD: ohci.c,v 1.146 2003/12/29 08:17:10 toshii Exp $ + * $NetBSD: ohci.c,v 1.147 2004/06/22 07:20:35 mycroft Exp $ + * $NetBSD: ohci.c,v 1.148 2004/06/22 18:27:46 mycroft Exp $ + */ + +#include <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. + */ + +/* + * 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 <sys/param.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/kernel.h> +#include <sys/endian.h> +#include <sys/module.h> +#include <sys/bus.h> +#if defined(DIAGNOSTIC) && defined(__i386__) && defined(__FreeBSD__) +#include <machine/cpu.h> +#endif +#include <sys/proc.h> +#include <sys/queue.h> +#include <sys/sysctl.h> + +#include <machine/bus.h> +#include <machine/endian.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdivar.h> +#include <dev/usb/usb_mem.h> +#include <dev/usb/usb_quirks.h> + +#include <dev/usb/ohcireg.h> +#include <dev/usb/ohcivar.h> + +#define delay(d) DELAY(d) + +#ifdef USB_DEBUG +#define DPRINTF(x) if (ohcidebug) printf x +#define DPRINTFN(n,x) if (ohcidebug>(n)) printf x +int ohcidebug = 0; +SYSCTL_NODE(_hw_usb, OID_AUTO, ohci, CTLFLAG_RW, 0, "USB ohci"); +SYSCTL_INT(_hw_usb_ohci, OID_AUTO, debug, CTLFLAG_RW, + &ohcidebug, 0, "ohci debug level"); +#define bitmask_snprintf(q,f,b,l) snprintf((b), (l), "%b", (q), (f)) +#else +#define DPRINTF(x) +#define DPRINTFN(n,x) +#endif + +struct ohci_pipe; + +static ohci_soft_ed_t *ohci_alloc_sed(ohci_softc_t *); +static void ohci_free_sed(ohci_softc_t *, ohci_soft_ed_t *); + +static ohci_soft_td_t *ohci_alloc_std(ohci_softc_t *); +static void ohci_free_std(ohci_softc_t *, ohci_soft_td_t *); + +static ohci_soft_itd_t *ohci_alloc_sitd(ohci_softc_t *); +static void ohci_free_sitd(ohci_softc_t *,ohci_soft_itd_t *); + +#if 0 +static void ohci_free_std_chain(ohci_softc_t *, ohci_soft_td_t *, + ohci_soft_td_t *); +#endif +static usbd_status ohci_alloc_std_chain(struct ohci_pipe *, + ohci_softc_t *, int, int, usbd_xfer_handle, + ohci_soft_td_t *, ohci_soft_td_t **); + +#if defined(__NetBSD__) || defined(__OpenBSD__) +static void ohci_shutdown(void *v); +static void ohci_power(int, void *); +#endif +static usbd_status ohci_open(usbd_pipe_handle); +static void ohci_poll(struct usbd_bus *); +static void ohci_softintr(void *); +static void ohci_waitintr(ohci_softc_t *, usbd_xfer_handle); +static void ohci_add_done(ohci_softc_t *, ohci_physaddr_t); +static void ohci_rhsc(ohci_softc_t *, usbd_xfer_handle); + +static usbd_status ohci_device_request(usbd_xfer_handle xfer); +static void ohci_add_ed(ohci_soft_ed_t *, ohci_soft_ed_t *); +static void ohci_rem_ed(ohci_soft_ed_t *, ohci_soft_ed_t *); +static void ohci_hash_add_td(ohci_softc_t *, ohci_soft_td_t *); +static void ohci_hash_rem_td(ohci_softc_t *, ohci_soft_td_t *); +static ohci_soft_td_t *ohci_hash_find_td(ohci_softc_t *, ohci_physaddr_t); +static void ohci_hash_add_itd(ohci_softc_t *, ohci_soft_itd_t *); +static void ohci_hash_rem_itd(ohci_softc_t *, ohci_soft_itd_t *); +static ohci_soft_itd_t *ohci_hash_find_itd(ohci_softc_t *, ohci_physaddr_t); + +static usbd_status ohci_setup_isoc(usbd_pipe_handle pipe); +static void ohci_device_isoc_enter(usbd_xfer_handle); + +static usbd_status ohci_allocm(struct usbd_bus *, usb_dma_t *, u_int32_t); +static void ohci_freem(struct usbd_bus *, usb_dma_t *); + +static usbd_xfer_handle ohci_allocx(struct usbd_bus *); +static void ohci_freex(struct usbd_bus *, usbd_xfer_handle); + +static usbd_status ohci_root_ctrl_transfer(usbd_xfer_handle); +static usbd_status ohci_root_ctrl_start(usbd_xfer_handle); +static void ohci_root_ctrl_abort(usbd_xfer_handle); +static void ohci_root_ctrl_close(usbd_pipe_handle); +static void ohci_root_ctrl_done(usbd_xfer_handle); + +static usbd_status ohci_root_intr_transfer(usbd_xfer_handle); +static usbd_status ohci_root_intr_start(usbd_xfer_handle); +static void ohci_root_intr_abort(usbd_xfer_handle); +static void ohci_root_intr_close(usbd_pipe_handle); +static void ohci_root_intr_done(usbd_xfer_handle); + +static usbd_status ohci_device_ctrl_transfer(usbd_xfer_handle); +static usbd_status ohci_device_ctrl_start(usbd_xfer_handle); +static void ohci_device_ctrl_abort(usbd_xfer_handle); +static void ohci_device_ctrl_close(usbd_pipe_handle); +static void ohci_device_ctrl_done(usbd_xfer_handle); + +static usbd_status ohci_device_bulk_transfer(usbd_xfer_handle); +static usbd_status ohci_device_bulk_start(usbd_xfer_handle); +static void ohci_device_bulk_abort(usbd_xfer_handle); +static void ohci_device_bulk_close(usbd_pipe_handle); +static void ohci_device_bulk_done(usbd_xfer_handle); + +static usbd_status ohci_device_intr_transfer(usbd_xfer_handle); +static usbd_status ohci_device_intr_start(usbd_xfer_handle); +static void ohci_device_intr_abort(usbd_xfer_handle); +static void ohci_device_intr_close(usbd_pipe_handle); +static void ohci_device_intr_done(usbd_xfer_handle); + +static usbd_status ohci_device_isoc_transfer(usbd_xfer_handle); +static usbd_status ohci_device_isoc_start(usbd_xfer_handle); +static void ohci_device_isoc_abort(usbd_xfer_handle); +static void ohci_device_isoc_close(usbd_pipe_handle); +static void ohci_device_isoc_done(usbd_xfer_handle); + +static usbd_status ohci_device_setintr(ohci_softc_t *sc, + struct ohci_pipe *pipe, int ival); +static usbd_status ohci_device_intr_insert(ohci_softc_t *sc, + usbd_xfer_handle xfer); + +static int ohci_str(usb_string_descriptor_t *, int, const char *); + +static void ohci_timeout(void *); +static void ohci_timeout_task(void *); +static void ohci_rhsc_able(ohci_softc_t *, int); +static void ohci_rhsc_enable(void *); + +static void ohci_close_pipe(usbd_pipe_handle, ohci_soft_ed_t *); +static void ohci_abort_xfer(usbd_xfer_handle, usbd_status); + +static void ohci_device_clear_toggle(usbd_pipe_handle pipe); +static void ohci_noop(usbd_pipe_handle pipe); + +static usbd_status ohci_controller_init(ohci_softc_t *sc); + +#ifdef USB_DEBUG +static void ohci_dumpregs(ohci_softc_t *); +static void ohci_dump_tds(ohci_soft_td_t *); +static void ohci_dump_td(ohci_soft_td_t *); +static void ohci_dump_ed(ohci_soft_ed_t *); +static void ohci_dump_itd(ohci_soft_itd_t *); +static void ohci_dump_itds(ohci_soft_itd_t *); +#endif + +#define OBARR(sc) bus_space_barrier((sc)->iot, (sc)->ioh, 0, (sc)->sc_size, \ + BUS_SPACE_BARRIER_READ|BUS_SPACE_BARRIER_WRITE) +#define OWRITE1(sc, r, x) \ + do { OBARR(sc); bus_space_write_1((sc)->iot, (sc)->ioh, (r), (x)); } while (0) +#define OWRITE2(sc, r, x) \ + do { OBARR(sc); bus_space_write_2((sc)->iot, (sc)->ioh, (r), (x)); } while (0) +#define OWRITE4(sc, r, x) \ + do { OBARR(sc); bus_space_write_4((sc)->iot, (sc)->ioh, (r), (x)); } while (0) +#define OREAD1(sc, r) (OBARR(sc), bus_space_read_1((sc)->iot, (sc)->ioh, (r))) +#define OREAD2(sc, r) (OBARR(sc), bus_space_read_2((sc)->iot, (sc)->ioh, (r))) +#define OREAD4(sc, r) (OBARR(sc), bus_space_read_4((sc)->iot, (sc)->ioh, (r))) + +/* Reverse the bits in a value 0 .. 31 */ +static u_int8_t revbits[OHCI_NO_INTRS] = + { 0x00, 0x10, 0x08, 0x18, 0x04, 0x14, 0x0c, 0x1c, + 0x02, 0x12, 0x0a, 0x1a, 0x06, 0x16, 0x0e, 0x1e, + 0x01, 0x11, 0x09, 0x19, 0x05, 0x15, 0x0d, 0x1d, + 0x03, 0x13, 0x0b, 0x1b, 0x07, 0x17, 0x0f, 0x1f }; + +struct ohci_pipe { + struct usbd_pipe pipe; + ohci_soft_ed_t *sed; + u_int32_t aborting; + union { + ohci_soft_td_t *td; + ohci_soft_itd_t *itd; + } tail; + /* Info needed for different pipe kinds. */ + union { + /* Control pipe */ + struct { + usb_dma_t reqdma; + u_int length; + ohci_soft_td_t *setup, *data, *stat; + } ctl; + /* Interrupt pipe */ + struct { + int nslots; + int pos; + } intr; + /* Bulk pipe */ + struct { + u_int length; + int isread; + } bulk; + /* Iso pipe */ + struct iso { + int next, inuse; + } iso; + } u; +}; + +#define OHCI_INTR_ENDPT 1 + +static struct usbd_bus_methods ohci_bus_methods = { + ohci_open, + ohci_softintr, + ohci_poll, + ohci_allocm, + ohci_freem, + ohci_allocx, + ohci_freex, +}; + +static struct usbd_pipe_methods ohci_root_ctrl_methods = { + ohci_root_ctrl_transfer, + ohci_root_ctrl_start, + ohci_root_ctrl_abort, + ohci_root_ctrl_close, + ohci_noop, + ohci_root_ctrl_done, +}; + +static struct usbd_pipe_methods ohci_root_intr_methods = { + ohci_root_intr_transfer, + ohci_root_intr_start, + ohci_root_intr_abort, + ohci_root_intr_close, + ohci_noop, + ohci_root_intr_done, +}; + +static struct usbd_pipe_methods ohci_device_ctrl_methods = { + ohci_device_ctrl_transfer, + ohci_device_ctrl_start, + ohci_device_ctrl_abort, + ohci_device_ctrl_close, + ohci_noop, + ohci_device_ctrl_done, +}; + +static struct usbd_pipe_methods ohci_device_intr_methods = { + ohci_device_intr_transfer, + ohci_device_intr_start, + ohci_device_intr_abort, + ohci_device_intr_close, + ohci_device_clear_toggle, + ohci_device_intr_done, +}; + +static struct usbd_pipe_methods ohci_device_bulk_methods = { + ohci_device_bulk_transfer, + ohci_device_bulk_start, + ohci_device_bulk_abort, + ohci_device_bulk_close, + ohci_device_clear_toggle, + ohci_device_bulk_done, +}; + +static struct usbd_pipe_methods ohci_device_isoc_methods = { + ohci_device_isoc_transfer, + ohci_device_isoc_start, + ohci_device_isoc_abort, + ohci_device_isoc_close, + ohci_noop, + ohci_device_isoc_done, +}; + +int +ohci_detach(struct ohci_softc *sc, int flags) +{ + int i, rv = 0; + + sc->sc_dying = 1; + callout_stop(&sc->sc_tmo_rhsc); + +#if defined(__NetBSD__) || defined(__OpenBSD__) + powerhook_disestablish(sc->sc_powerhook); + shutdownhook_disestablish(sc->sc_shutdownhook); +#endif + + OWRITE4(sc, OHCI_INTERRUPT_DISABLE, OHCI_ALL_INTRS); + OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET); + + usb_delay_ms(&sc->sc_bus, 300); /* XXX let stray task complete */ + + for (i = 0; i < OHCI_NO_EDS; i++) + ohci_free_sed(sc, sc->sc_eds[i]); + ohci_free_sed(sc, sc->sc_isoc_head); + ohci_free_sed(sc, sc->sc_bulk_head); + ohci_free_sed(sc, sc->sc_ctrl_head); + usb_freemem(&sc->sc_bus, &sc->sc_hccadma); + + return (rv); +} + +ohci_soft_ed_t * +ohci_alloc_sed(ohci_softc_t *sc) +{ + ohci_soft_ed_t *sed; + usbd_status err; + int i, offs; + usb_dma_t dma; + + if (sc->sc_freeeds == NULL) { + DPRINTFN(2, ("ohci_alloc_sed: allocating chunk\n")); + err = usb_allocmem(&sc->sc_bus, OHCI_SED_SIZE * OHCI_SED_CHUNK, + OHCI_ED_ALIGN, &dma); + if (err) + return (NULL); + for(i = 0; i < OHCI_SED_CHUNK; i++) { + offs = i * OHCI_SED_SIZE; + sed = KERNADDR(&dma, offs); + sed->physaddr = DMAADDR(&dma, offs); + sed->next = sc->sc_freeeds; + sc->sc_freeeds = sed; + } + } + sed = sc->sc_freeeds; + sc->sc_freeeds = sed->next; + memset(&sed->ed, 0, sizeof(ohci_ed_t)); + sed->next = 0; + return (sed); +} + +void +ohci_free_sed(ohci_softc_t *sc, ohci_soft_ed_t *sed) +{ + sed->next = sc->sc_freeeds; + sc->sc_freeeds = sed; +} + +ohci_soft_td_t * +ohci_alloc_std(ohci_softc_t *sc) +{ + ohci_soft_td_t *std; + usbd_status err; + int i, offs; + usb_dma_t dma; + int s; + + if (sc->sc_freetds == NULL) { + DPRINTFN(2, ("ohci_alloc_std: allocating chunk\n")); + err = usb_allocmem(&sc->sc_bus, OHCI_STD_SIZE * OHCI_STD_CHUNK, + OHCI_TD_ALIGN, &dma); + if (err) + return (NULL); + s = splusb(); + for(i = 0; i < OHCI_STD_CHUNK; i++) { + offs = i * OHCI_STD_SIZE; + std = KERNADDR(&dma, offs); + std->physaddr = DMAADDR(&dma, offs); + std->nexttd = sc->sc_freetds; + sc->sc_freetds = std; + } + splx(s); + } + + s = splusb(); + std = sc->sc_freetds; + sc->sc_freetds = std->nexttd; + memset(&std->td, 0, sizeof(ohci_td_t)); + std->nexttd = NULL; + std->xfer = NULL; + ohci_hash_add_td(sc, std); + splx(s); + + return (std); +} + +void +ohci_free_std(ohci_softc_t *sc, ohci_soft_td_t *std) +{ + int s; + + s = splusb(); + ohci_hash_rem_td(sc, std); + std->nexttd = sc->sc_freetds; + sc->sc_freetds = std; + splx(s); +} + +usbd_status +ohci_alloc_std_chain(struct ohci_pipe *opipe, ohci_softc_t *sc, + int alen, int rd, usbd_xfer_handle xfer, + ohci_soft_td_t *sp, ohci_soft_td_t **ep) +{ + ohci_soft_td_t *next, *cur, *end; + ohci_physaddr_t dataphys, physend; + u_int32_t tdflags; + int offset = 0; + int len, maxp, curlen, curlen2, seg, segoff; + struct usb_dma_mapping *dma = &xfer->dmamap; + u_int16_t flags = xfer->flags; + + DPRINTFN(alen < 4096,("ohci_alloc_std_chain: start len=%d\n", alen)); + + len = alen; + cur = sp; + end = NULL; + + maxp = UGETW(opipe->pipe.endpoint->edesc->wMaxPacketSize); + tdflags = htole32( + (rd ? OHCI_TD_IN : OHCI_TD_OUT) | + (flags & USBD_SHORT_XFER_OK ? OHCI_TD_R : 0) | + OHCI_TD_NOCC | OHCI_TD_TOGGLE_CARRY | OHCI_TD_SET_DI(6)); + + seg = 0; + segoff = 0; + while (len > 0) { + next = ohci_alloc_std(sc); + if (next == NULL) + goto nomem; + + /* + * The OHCI hardware can handle at most one 4k crossing. + * The OHCI spec says: If during the data transfer the buffer + * address contained in the HC's working copy of + * CurrentBufferPointer crosses a 4K boundary, the upper 20 + * bits of Buffer End are copied to the working value of + * CurrentBufferPointer causing the next buffer address to + * be the 0th byte in the same 4K page that contains the + * last byte of the buffer (the 4K boundary crossing may + * occur within a data packet transfer.) + */ + KASSERT(seg < dma->nsegs, ("ohci_alloc_std_chain: overrun")); + dataphys = dma->segs[seg].ds_addr + segoff; + curlen = dma->segs[seg].ds_len - segoff; + if (curlen > len) + curlen = len; + physend = dataphys + curlen - 1; + if (OHCI_PAGE(dataphys) != OHCI_PAGE(physend)) { + /* Truncate to two OHCI pages if there are more. */ + if (curlen > 2 * OHCI_PAGE_SIZE - + OHCI_PAGE_OFFSET(dataphys)) + curlen = 2 * OHCI_PAGE_SIZE - + OHCI_PAGE_OFFSET(dataphys); + if (curlen < len) + curlen -= curlen % maxp; + physend = dataphys + curlen - 1; + } else if (OHCI_PAGE_OFFSET(physend + 1) == 0 && curlen < len && + curlen + segoff == dma->segs[seg].ds_len) { + /* We can possibly include another segment. */ + KASSERT(seg + 1 < dma->nsegs, + ("ohci_alloc_std_chain: overrun2")); + seg++; + + /* Determine how much of the second segment to use. */ + curlen2 = dma->segs[seg].ds_len; + if (curlen + curlen2 > len) + curlen2 = len - curlen; + if (OHCI_PAGE(dma->segs[seg].ds_addr) != + OHCI_PAGE(dma->segs[seg].ds_addr + curlen2 - 1)) + curlen2 = OHCI_PAGE_SIZE - + OHCI_PAGE_OFFSET(dma->segs[seg].ds_addr); + if (curlen + curlen2 < len) + curlen2 -= (curlen + curlen2) % maxp; + + if (curlen2 > 0) { + /* We can include a second segment */ + segoff = curlen2; + physend = dma->segs[seg].ds_addr + curlen2 - 1; + curlen += curlen2; + } else { + /* Second segment not usable now. */ + seg--; + segoff += curlen; + } + } else { + /* Simple case where there is just one OHCI page. */ + segoff += curlen; + } + if (curlen == 0 && len != 0) { + /* + * A maxp length packet would need to be split. + * This shouldn't be possible if PAGE_SIZE >= 4k + * and the buffer is contiguous in virtual memory. + */ + panic("ohci_alloc_std_chain: XXX need to copy"); + } + if (segoff >= dma->segs[seg].ds_len) { + KASSERT(segoff == dma->segs[seg].ds_len, + ("ohci_alloc_std_chain: overlap")); + seg++; + segoff = 0; + } + DPRINTFN(4,("ohci_alloc_std_chain: dataphys=0x%08x " + "len=%d curlen=%d\n", + dataphys, len, curlen)); + len -= curlen; + + cur->td.td_flags = tdflags; + cur->td.td_cbp = htole32(dataphys); + cur->nexttd = next; + cur->td.td_nexttd = htole32(next->physaddr); + cur->td.td_be = htole32(physend); + cur->len = curlen; + cur->flags = OHCI_ADD_LEN; + cur->xfer = xfer; + DPRINTFN(10,("ohci_alloc_std_chain: cbp=0x%08x be=0x%08x\n", + dataphys, dataphys + curlen - 1)); + if (len < 0) + panic("Length went negative: %d curlen %d dma %p offset %08x", len, curlen, dma, (int)0); + + DPRINTFN(10,("ohci_alloc_std_chain: extend chain\n")); + offset += curlen; + end = cur; + cur = next; + } + if (((flags & USBD_FORCE_SHORT_XFER) || alen == 0) && + alen % UGETW(opipe->pipe.endpoint->edesc->wMaxPacketSize) == 0) { + /* Force a 0 length transfer at the end. */ + next = ohci_alloc_std(sc); + if (next == NULL) + goto nomem; + + cur->td.td_flags = tdflags; + cur->td.td_cbp = 0; /* indicate 0 length packet */ + cur->nexttd = next; + cur->td.td_nexttd = htole32(next->physaddr); + cur->td.td_be = ~0; + cur->len = 0; + cur->flags = 0; + cur->xfer = xfer; + DPRINTFN(2,("ohci_alloc_std_chain: add 0 xfer\n")); + end = cur; + } + *ep = end; + + return (USBD_NORMAL_COMPLETION); + + nomem: + /* XXX free chain */ + return (USBD_NOMEM); +} + +#if 0 +static void +ohci_free_std_chain(ohci_softc_t *sc, ohci_soft_td_t *std, + ohci_soft_td_t *stdend) +{ + ohci_soft_td_t *p; + + for (; std != stdend; std = p) { + p = std->nexttd; + ohci_free_std(sc, std); + } +} +#endif + +ohci_soft_itd_t * +ohci_alloc_sitd(ohci_softc_t *sc) +{ + ohci_soft_itd_t *sitd; + usbd_status err; + int i, s, offs; + usb_dma_t dma; + + if (sc->sc_freeitds == NULL) { + DPRINTFN(2, ("ohci_alloc_sitd: allocating chunk\n")); + err = usb_allocmem(&sc->sc_bus, OHCI_SITD_SIZE * OHCI_SITD_CHUNK, + OHCI_ITD_ALIGN, &dma); + if (err) + return (NULL); + s = splusb(); + for(i = 0; i < OHCI_SITD_CHUNK; i++) { + offs = i * OHCI_SITD_SIZE; + sitd = KERNADDR(&dma, offs); + sitd->physaddr = DMAADDR(&dma, offs); + sitd->nextitd = sc->sc_freeitds; + sc->sc_freeitds = sitd; + } + splx(s); + } + + s = splusb(); + sitd = sc->sc_freeitds; + sc->sc_freeitds = sitd->nextitd; + memset(&sitd->itd, 0, sizeof(ohci_itd_t)); + sitd->nextitd = NULL; + sitd->xfer = NULL; + ohci_hash_add_itd(sc, sitd); + splx(s); + +#ifdef DIAGNOSTIC + sitd->isdone = 0; +#endif + + return (sitd); +} + +void +ohci_free_sitd(ohci_softc_t *sc, ohci_soft_itd_t *sitd) +{ + int s; + + DPRINTFN(10,("ohci_free_sitd: sitd=%p\n", sitd)); + +#ifdef DIAGNOSTIC + if (!sitd->isdone) { + panic("ohci_free_sitd: sitd=%p not done", sitd); + return; + } + /* Warn double free */ + sitd->isdone = 0; +#endif + + s = splusb(); + ohci_hash_rem_itd(sc, sitd); + sitd->nextitd = sc->sc_freeitds; + sc->sc_freeitds = sitd; + splx(s); +} + +usbd_status +ohci_init(ohci_softc_t *sc) +{ + ohci_soft_ed_t *sed, *psed; + usbd_status err; + int i; + u_int32_t rev; + + DPRINTF(("ohci_init: start\n")); + printf("%s:", device_get_nameunit(sc->sc_bus.bdev)); + rev = OREAD4(sc, OHCI_REVISION); + printf(" OHCI version %d.%d%s\n", OHCI_REV_HI(rev), OHCI_REV_LO(rev), + OHCI_REV_LEGACY(rev) ? ", legacy support" : ""); + + if (OHCI_REV_HI(rev) != 1 || OHCI_REV_LO(rev) != 0) { + printf("%s: unsupported OHCI revision\n", + device_get_nameunit(sc->sc_bus.bdev)); + sc->sc_bus.usbrev = USBREV_UNKNOWN; + return (USBD_INVAL); + } + sc->sc_bus.usbrev = USBREV_1_0; + + for (i = 0; i < OHCI_HASH_SIZE; i++) + LIST_INIT(&sc->sc_hash_tds[i]); + for (i = 0; i < OHCI_HASH_SIZE; i++) + LIST_INIT(&sc->sc_hash_itds[i]); + + STAILQ_INIT(&sc->sc_free_xfers); + + /* XXX determine alignment by R/W */ + /* Allocate the HCCA area. */ + err = usb_allocmem(&sc->sc_bus, OHCI_HCCA_SIZE, + OHCI_HCCA_ALIGN, &sc->sc_hccadma); + if (err) + return (err); + sc->sc_hcca = KERNADDR(&sc->sc_hccadma, 0); + memset(sc->sc_hcca, 0, OHCI_HCCA_SIZE); + + sc->sc_eintrs = OHCI_NORMAL_INTRS; + + /* Allocate dummy ED that starts the control list. */ + sc->sc_ctrl_head = ohci_alloc_sed(sc); + if (sc->sc_ctrl_head == NULL) { + err = USBD_NOMEM; + goto bad1; + } + sc->sc_ctrl_head->ed.ed_flags |= htole32(OHCI_ED_SKIP); + + /* Allocate dummy ED that starts the bulk list. */ + sc->sc_bulk_head = ohci_alloc_sed(sc); + if (sc->sc_bulk_head == NULL) { + err = USBD_NOMEM; + goto bad2; + } + sc->sc_bulk_head->ed.ed_flags |= htole32(OHCI_ED_SKIP); + + /* Allocate dummy ED that starts the isochronous list. */ + sc->sc_isoc_head = ohci_alloc_sed(sc); + if (sc->sc_isoc_head == NULL) { + err = USBD_NOMEM; + goto bad3; + } + sc->sc_isoc_head->ed.ed_flags |= htole32(OHCI_ED_SKIP); + + /* Allocate all the dummy EDs that make up the interrupt tree. */ + for (i = 0; i < OHCI_NO_EDS; i++) { + sed = ohci_alloc_sed(sc); + if (sed == NULL) { + while (--i >= 0) + ohci_free_sed(sc, sc->sc_eds[i]); + err = USBD_NOMEM; + goto bad4; + } + /* All ED fields are set to 0. */ + sc->sc_eds[i] = sed; + sed->ed.ed_flags |= htole32(OHCI_ED_SKIP); + if (i != 0) + psed = sc->sc_eds[(i-1) / 2]; + else + psed= sc->sc_isoc_head; + sed->next = psed; + sed->ed.ed_nexted = htole32(psed->physaddr); + } + /* + * Fill HCCA interrupt table. The bit reversal is to get + * the tree set up properly to spread the interrupts. + */ + for (i = 0; i < OHCI_NO_INTRS; i++) + sc->sc_hcca->hcca_interrupt_table[revbits[i]] = + htole32(sc->sc_eds[OHCI_NO_EDS-OHCI_NO_INTRS+i]->physaddr); + +#ifdef USB_DEBUG + if (ohcidebug > 15) { + for (i = 0; i < OHCI_NO_EDS; i++) { + printf("ed#%d ", i); + ohci_dump_ed(sc->sc_eds[i]); + } + printf("iso "); + ohci_dump_ed(sc->sc_isoc_head); + } +#endif + + err = ohci_controller_init(sc); + if (err != USBD_NORMAL_COMPLETION) + goto bad5; + + /* Set up the bus struct. */ + sc->sc_bus.methods = &ohci_bus_methods; + sc->sc_bus.pipe_size = sizeof(struct ohci_pipe); + +#if defined(__NetBSD__) || defined(__OpenBSD__) + sc->sc_powerhook = powerhook_establish(ohci_power, sc); + sc->sc_shutdownhook = shutdownhook_establish(ohci_shutdown, sc); +#endif + + callout_init(&sc->sc_tmo_rhsc, 0); + + return (USBD_NORMAL_COMPLETION); + + bad5: + for (i = 0; i < OHCI_NO_EDS; i++) + ohci_free_sed(sc, sc->sc_eds[i]); + bad4: + ohci_free_sed(sc, sc->sc_isoc_head); + bad3: + ohci_free_sed(sc, sc->sc_bulk_head); + bad2: + ohci_free_sed(sc, sc->sc_ctrl_head); + bad1: + usb_freemem(&sc->sc_bus, &sc->sc_hccadma); + return (err); +} + +static usbd_status +ohci_controller_init(ohci_softc_t *sc) +{ + int i; + u_int32_t ctl, ival, hcr, fm, per, desca; + + /* Determine in what context we are running. */ + ctl = OREAD4(sc, OHCI_CONTROL); + if (ctl & OHCI_IR) { + /* SMM active, request change */ + DPRINTF(("ohci_init: SMM active, request owner change\n")); + OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_OCR); + for (i = 0; i < 100 && (ctl & OHCI_IR); i++) { + usb_delay_ms(&sc->sc_bus, 1); + ctl = OREAD4(sc, OHCI_CONTROL); + } + if (ctl & OHCI_IR) { + printf("%s: SMM does not respond, resetting\n", + device_get_nameunit(sc->sc_bus.bdev)); + OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET); + goto reset; + } +#if 0 +/* Don't bother trying to reuse the BIOS init, we'll reset it anyway. */ + } else if ((ctl & OHCI_HCFS_MASK) != OHCI_HCFS_RESET) { + /* BIOS started controller. */ + DPRINTF(("ohci_init: BIOS active\n")); + if ((ctl & OHCI_HCFS_MASK) != OHCI_HCFS_OPERATIONAL) { + OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_OPERATIONAL); + usb_delay_ms(&sc->sc_bus, USB_RESUME_DELAY); + } +#endif + } else { + DPRINTF(("ohci_init: cold started\n")); + reset: + /* Controller was cold started. */ + usb_delay_ms(&sc->sc_bus, USB_BUS_RESET_DELAY); + } + + /* + * This reset should not be necessary according to the OHCI spec, but + * without it some controllers do not start. + */ + DPRINTF(("%s: resetting\n", device_get_nameunit(sc->sc_bus.bdev))); + OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET); + usb_delay_ms(&sc->sc_bus, USB_BUS_RESET_DELAY); + + /* We now own the host controller and the bus has been reset. */ + ival = OHCI_GET_IVAL(OREAD4(sc, OHCI_FM_INTERVAL)); + + OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_HCR); /* Reset HC */ + /* Nominal time for a reset is 10 us. */ + for (i = 0; i < 10; i++) { + delay(10); + hcr = OREAD4(sc, OHCI_COMMAND_STATUS) & OHCI_HCR; + if (!hcr) + break; + } + if (hcr) { + printf("%s: reset timeout\n", device_get_nameunit(sc->sc_bus.bdev)); + return (USBD_IOERROR); + } +#ifdef USB_DEBUG + if (ohcidebug > 15) + ohci_dumpregs(sc); +#endif + + /* The controller is now in SUSPEND state, we have 2ms to finish. */ + + /* Set up HC registers. */ + OWRITE4(sc, OHCI_HCCA, DMAADDR(&sc->sc_hccadma, 0)); + OWRITE4(sc, OHCI_CONTROL_HEAD_ED, sc->sc_ctrl_head->physaddr); + OWRITE4(sc, OHCI_BULK_HEAD_ED, sc->sc_bulk_head->physaddr); + /* disable all interrupts and then switch on all desired interrupts */ + OWRITE4(sc, OHCI_INTERRUPT_DISABLE, OHCI_ALL_INTRS); + OWRITE4(sc, OHCI_INTERRUPT_ENABLE, sc->sc_eintrs | OHCI_MIE); + /* switch on desired functional features */ + ctl = OREAD4(sc, OHCI_CONTROL); + ctl &= ~(OHCI_CBSR_MASK | OHCI_LES | OHCI_HCFS_MASK | OHCI_IR); + ctl |= OHCI_PLE | OHCI_IE | OHCI_CLE | OHCI_BLE | + OHCI_RATIO_1_4 | OHCI_HCFS_OPERATIONAL; + /* And finally start it! */ + OWRITE4(sc, OHCI_CONTROL, ctl); + + /* + * The controller is now OPERATIONAL. Set a some final + * registers that should be set earlier, but that the + * controller ignores when in the SUSPEND state. + */ + fm = (OREAD4(sc, OHCI_FM_INTERVAL) & OHCI_FIT) ^ OHCI_FIT; + fm |= OHCI_FSMPS(ival) | ival; + OWRITE4(sc, OHCI_FM_INTERVAL, fm); + per = OHCI_PERIODIC(ival); /* 90% periodic */ + OWRITE4(sc, OHCI_PERIODIC_START, per); + + /* Fiddle the No OverCurrent Protection bit to avoid chip bug. */ + desca = OREAD4(sc, OHCI_RH_DESCRIPTOR_A); + OWRITE4(sc, OHCI_RH_DESCRIPTOR_A, desca | OHCI_NOCP); + OWRITE4(sc, OHCI_RH_STATUS, OHCI_LPSC); /* Enable port power */ + usb_delay_ms(&sc->sc_bus, OHCI_ENABLE_POWER_DELAY); + OWRITE4(sc, OHCI_RH_DESCRIPTOR_A, desca); + + /* + * The AMD756 requires a delay before re-reading the register, + * otherwise it will occasionally report 0 ports. + */ + sc->sc_noport = 0; + for (i = 0; i < 10 && sc->sc_noport == 0; i++) { + usb_delay_ms(&sc->sc_bus, OHCI_READ_DESC_DELAY); + sc->sc_noport = OHCI_GET_NDP(OREAD4(sc, OHCI_RH_DESCRIPTOR_A)); + } + +#ifdef USB_DEBUG + if (ohcidebug > 5) + ohci_dumpregs(sc); +#endif + return (USBD_NORMAL_COMPLETION); +} + +usbd_status +ohci_allocm(struct usbd_bus *bus, usb_dma_t *dma, u_int32_t size) +{ + return (usb_allocmem(bus, size, 0, dma)); +} + +void +ohci_freem(struct usbd_bus *bus, usb_dma_t *dma) +{ + usb_freemem(bus, dma); +} + +usbd_xfer_handle +ohci_allocx(struct usbd_bus *bus) +{ + struct ohci_softc *sc = (struct ohci_softc *)bus; + usbd_xfer_handle xfer; + + xfer = STAILQ_FIRST(&sc->sc_free_xfers); + if (xfer != NULL) { + STAILQ_REMOVE_HEAD(&sc->sc_free_xfers, next); +#ifdef DIAGNOSTIC + if (xfer->busy_free != XFER_FREE) { + printf("ohci_allocx: xfer=%p not free, 0x%08x\n", xfer, + xfer->busy_free); + } +#endif + } else { + xfer = malloc(sizeof(struct ohci_xfer), M_USB, M_NOWAIT); + } + if (xfer != NULL) { + memset(xfer, 0, sizeof (struct ohci_xfer)); + usb_init_task(&OXFER(xfer)->abort_task, ohci_timeout_task, + xfer); + OXFER(xfer)->ohci_xfer_flags = 0; +#ifdef DIAGNOSTIC + xfer->busy_free = XFER_BUSY; +#endif + } + return (xfer); +} + +void +ohci_freex(struct usbd_bus *bus, usbd_xfer_handle xfer) +{ + struct ohci_softc *sc = (struct ohci_softc *)bus; + +#ifdef DIAGNOSTIC + if (xfer->busy_free != XFER_BUSY) { + printf("ohci_freex: xfer=%p not busy, 0x%08x\n", xfer, + xfer->busy_free); + return; + } + xfer->busy_free = XFER_FREE; +#endif + STAILQ_INSERT_HEAD(&sc->sc_free_xfers, xfer, next); +} + +/* + * Shut down the controller when the system is going down. + */ +void +ohci_shutdown(void *v) +{ + ohci_softc_t *sc = v; + + DPRINTF(("ohci_shutdown: stopping the HC\n")); + OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET); +} + +/* + * Handle suspend/resume. + * + * We need to switch to polling mode here, because this routine is + * called from an intterupt context. This is all right since we + * are almost suspended anyway. + */ +void +ohci_power(int why, void *v) +{ + ohci_softc_t *sc = v; + u_int32_t ctl; + int s; + +#ifdef USB_DEBUG + DPRINTF(("ohci_power: sc=%p, why=%d\n", sc, why)); + ohci_dumpregs(sc); +#endif + + s = splhardusb(); + if (why != PWR_RESUME) { + sc->sc_bus.use_polling++; + ctl = OREAD4(sc, OHCI_CONTROL) & ~OHCI_HCFS_MASK; + if (sc->sc_control == 0) { + /* + * Preserve register values, in case that APM BIOS + * does not recover them. + */ + sc->sc_control = ctl; + sc->sc_intre = OREAD4(sc, OHCI_INTERRUPT_ENABLE); + } + ctl |= OHCI_HCFS_SUSPEND; + OWRITE4(sc, OHCI_CONTROL, ctl); + usb_delay_ms(&sc->sc_bus, USB_RESUME_WAIT); + sc->sc_bus.use_polling--; + } else { + sc->sc_bus.use_polling++; + + /* Some broken BIOSes never initialize Controller chip */ + ohci_controller_init(sc); + + if (sc->sc_intre) + OWRITE4(sc, OHCI_INTERRUPT_ENABLE, + sc->sc_intre & (OHCI_ALL_INTRS | OHCI_MIE)); + if (sc->sc_control) + ctl = sc->sc_control; + else + ctl = OREAD4(sc, OHCI_CONTROL); + ctl |= OHCI_HCFS_RESUME; + OWRITE4(sc, OHCI_CONTROL, ctl); + usb_delay_ms(&sc->sc_bus, USB_RESUME_DELAY); + ctl = (ctl & ~OHCI_HCFS_MASK) | OHCI_HCFS_OPERATIONAL; + OWRITE4(sc, OHCI_CONTROL, ctl); + usb_delay_ms(&sc->sc_bus, USB_RESUME_RECOVERY); + sc->sc_control = sc->sc_intre = 0; + sc->sc_bus.use_polling--; + } + splx(s); +} + +#ifdef USB_DEBUG +void +ohci_dumpregs(ohci_softc_t *sc) +{ + DPRINTF(("ohci_dumpregs: rev=0x%08x control=0x%08x command=0x%08x\n", + OREAD4(sc, OHCI_REVISION), + OREAD4(sc, OHCI_CONTROL), + OREAD4(sc, OHCI_COMMAND_STATUS))); + DPRINTF((" intrstat=0x%08x intre=0x%08x intrd=0x%08x\n", + OREAD4(sc, OHCI_INTERRUPT_STATUS), + OREAD4(sc, OHCI_INTERRUPT_ENABLE), + OREAD4(sc, OHCI_INTERRUPT_DISABLE))); + DPRINTF((" hcca=0x%08x percur=0x%08x ctrlhd=0x%08x\n", + OREAD4(sc, OHCI_HCCA), + OREAD4(sc, OHCI_PERIOD_CURRENT_ED), + OREAD4(sc, OHCI_CONTROL_HEAD_ED))); + DPRINTF((" ctrlcur=0x%08x bulkhd=0x%08x bulkcur=0x%08x\n", + OREAD4(sc, OHCI_CONTROL_CURRENT_ED), + OREAD4(sc, OHCI_BULK_HEAD_ED), + OREAD4(sc, OHCI_BULK_CURRENT_ED))); + DPRINTF((" done=0x%08x fmival=0x%08x fmrem=0x%08x\n", + OREAD4(sc, OHCI_DONE_HEAD), + OREAD4(sc, OHCI_FM_INTERVAL), + OREAD4(sc, OHCI_FM_REMAINING))); + DPRINTF((" fmnum=0x%08x perst=0x%08x lsthrs=0x%08x\n", + OREAD4(sc, OHCI_FM_NUMBER), + OREAD4(sc, OHCI_PERIODIC_START), + OREAD4(sc, OHCI_LS_THRESHOLD))); + DPRINTF((" desca=0x%08x descb=0x%08x stat=0x%08x\n", + OREAD4(sc, OHCI_RH_DESCRIPTOR_A), + OREAD4(sc, OHCI_RH_DESCRIPTOR_B), + OREAD4(sc, OHCI_RH_STATUS))); + DPRINTF((" port1=0x%08x port2=0x%08x\n", + OREAD4(sc, OHCI_RH_PORT_STATUS(1)), + OREAD4(sc, OHCI_RH_PORT_STATUS(2)))); + DPRINTF((" HCCA: frame_number=0x%04x done_head=0x%08x\n", + le32toh(sc->sc_hcca->hcca_frame_number), + le32toh(sc->sc_hcca->hcca_done_head))); +} +#endif + +static int ohci_intr1(ohci_softc_t *); + +void +ohci_intr(void *p) +{ + ohci_softc_t *sc = p; + + if (sc == NULL || sc->sc_dying) + return; + + /* If we get an interrupt while polling, then just ignore it. */ + if (sc->sc_bus.use_polling) { +#ifdef DIAGNOSTIC + printf("ohci_intr: ignored interrupt while polling\n"); +#endif + return; + } + + ohci_intr1(sc); +} + +static int +ohci_intr1(ohci_softc_t *sc) +{ + u_int32_t intrs, eintrs; + ohci_physaddr_t done; + + DPRINTFN(14,("ohci_intr1: enter\n")); + + /* In case the interrupt occurs before initialization has completed. */ + if (sc == NULL || sc->sc_hcca == NULL) { +#ifdef DIAGNOSTIC + printf("ohci_intr: sc->sc_hcca == NULL\n"); +#endif + return (0); + } + + intrs = 0; + done = le32toh(sc->sc_hcca->hcca_done_head); + + /* The LSb of done is used to inform the HC Driver that an interrupt + * condition exists for both the Done list and for another event + * recorded in HcInterruptStatus. On an interrupt from the HC, the HC + * Driver checks the HccaDoneHead Value. If this value is 0, then the + * interrupt was caused by other than the HccaDoneHead update and the + * HcInterruptStatus register needs to be accessed to determine that + * exact interrupt cause. If HccaDoneHead is nonzero, then a Done list + * update interrupt is indicated and if the LSb of done is nonzero, + * then an additional interrupt event is indicated and + * HcInterruptStatus should be checked to determine its cause. + */ + if (done != 0) { + if (done & ~OHCI_DONE_INTRS) + intrs = OHCI_WDH; + if (done & OHCI_DONE_INTRS) { + intrs |= OREAD4(sc, OHCI_INTERRUPT_STATUS); + done &= ~OHCI_DONE_INTRS; + } + sc->sc_hcca->hcca_done_head = 0; + } else + intrs = OREAD4(sc, OHCI_INTERRUPT_STATUS) & ~OHCI_WDH; + + if (intrs == 0) /* nothing to be done (PCI shared interrupt) */ + return (0); + + intrs &= ~OHCI_MIE; + OWRITE4(sc, OHCI_INTERRUPT_STATUS, intrs); /* Acknowledge */ + eintrs = intrs & sc->sc_eintrs; + if (!eintrs) + return (0); + + sc->sc_bus.intr_context++; + sc->sc_bus.no_intrs++; + DPRINTFN(7, ("ohci_intr: sc=%p intrs=0x%x(0x%x) eintrs=0x%x\n", + sc, (u_int)intrs, OREAD4(sc, OHCI_INTERRUPT_STATUS), + (u_int)eintrs)); + + if (eintrs & OHCI_SO) { + sc->sc_overrun_cnt++; + if (usbd_ratecheck(&sc->sc_overrun_ntc)) { + printf("%s: %u scheduling overruns\n", + device_get_nameunit(sc->sc_bus.bdev), sc->sc_overrun_cnt); + sc->sc_overrun_cnt = 0; + } + /* XXX do what */ + eintrs &= ~OHCI_SO; + } + if (eintrs & OHCI_WDH) { + ohci_add_done(sc, done &~ OHCI_DONE_INTRS); + usb_schedsoftintr(&sc->sc_bus); + eintrs &= ~OHCI_WDH; + } + if (eintrs & OHCI_RD) { + printf("%s: resume detect\n", device_get_nameunit(sc->sc_bus.bdev)); + /* XXX process resume detect */ + } + if (eintrs & OHCI_UE) { + printf("%s: unrecoverable error, controller halted\n", + device_get_nameunit(sc->sc_bus.bdev)); + OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET); + /* XXX what else */ + } + if (eintrs & OHCI_RHSC) { + ohci_rhsc(sc, sc->sc_intrxfer); + /* + * Disable RHSC interrupt for now, because it will be + * on until the port has been reset. + */ + ohci_rhsc_able(sc, 0); + /* Do not allow RHSC interrupts > 1 per second */ + callout_reset(&sc->sc_tmo_rhsc, hz, ohci_rhsc_enable, sc); + eintrs &= ~OHCI_RHSC; + } + + sc->sc_bus.intr_context--; + + if (eintrs != 0) { + /* Block unprocessed interrupts. XXX */ + OWRITE4(sc, OHCI_INTERRUPT_DISABLE, eintrs); + sc->sc_eintrs &= ~eintrs; + printf("%s: blocking intrs 0x%x\n", + device_get_nameunit(sc->sc_bus.bdev), eintrs); + } + + return (1); +} + +void +ohci_rhsc_able(ohci_softc_t *sc, int on) +{ + DPRINTFN(4, ("ohci_rhsc_able: on=%d\n", on)); + if (on) { + sc->sc_eintrs |= OHCI_RHSC; + OWRITE4(sc, OHCI_INTERRUPT_ENABLE, OHCI_RHSC); + } else { + sc->sc_eintrs &= ~OHCI_RHSC; + OWRITE4(sc, OHCI_INTERRUPT_DISABLE, OHCI_RHSC); + } +} + +void +ohci_rhsc_enable(void *v_sc) +{ + ohci_softc_t *sc = v_sc; + int s; + + s = splhardusb(); + ohci_rhsc_able(sc, 1); + splx(s); +} + +#ifdef USB_DEBUG +char *ohci_cc_strs[] = { + "NO_ERROR", + "CRC", + "BIT_STUFFING", + "DATA_TOGGLE_MISMATCH", + "STALL", + "DEVICE_NOT_RESPONDING", + "PID_CHECK_FAILURE", + "UNEXPECTED_PID", + "DATA_OVERRUN", + "DATA_UNDERRUN", + "BUFFER_OVERRUN", + "BUFFER_UNDERRUN", + "reserved", + "reserved", + "NOT_ACCESSED", + "NOT_ACCESSED" +}; +#endif + +void +ohci_add_done(ohci_softc_t *sc, ohci_physaddr_t done) +{ + ohci_soft_itd_t *sitd, *sidone, **ip; + ohci_soft_td_t *std, *sdone, **p; + + /* Reverse the done list. */ + for (sdone = NULL, sidone = NULL; done != 0; ) { + std = ohci_hash_find_td(sc, done); + if (std != NULL) { + std->dnext = sdone; + done = le32toh(std->td.td_nexttd); + sdone = std; + DPRINTFN(10,("add TD %p\n", std)); + continue; + } + sitd = ohci_hash_find_itd(sc, done); + if (sitd != NULL) { + sitd->dnext = sidone; + done = le32toh(sitd->itd.itd_nextitd); + sidone = sitd; + DPRINTFN(5,("add ITD %p\n", sitd)); + continue; + } + panic("ohci_add_done: addr 0x%08lx not found", (u_long)done); + } + + /* sdone & sidone now hold the done lists. */ + /* Put them on the already processed lists. */ + for (p = &sc->sc_sdone; *p != NULL; p = &(*p)->dnext) + ; + *p = sdone; + for (ip = &sc->sc_sidone; *ip != NULL; ip = &(*ip)->dnext) + ; + *ip = sidone; +} + +void +ohci_softintr(void *v) +{ + ohci_softc_t *sc = v; + ohci_soft_itd_t *sitd, *sidone, *sitdnext; + ohci_soft_td_t *std, *sdone, *stdnext, *p, *n; + usbd_xfer_handle xfer; + struct ohci_pipe *opipe; + int len, cc, s; + int i, j, iframes; + + DPRINTFN(10,("ohci_softintr: enter\n")); + + sc->sc_bus.intr_context++; + + s = splhardusb(); + sdone = sc->sc_sdone; + sc->sc_sdone = NULL; + sidone = sc->sc_sidone; + sc->sc_sidone = NULL; + splx(s); + + DPRINTFN(10,("ohci_softintr: sdone=%p sidone=%p\n", sdone, sidone)); + +#ifdef USB_DEBUG + if (ohcidebug > 10) { + DPRINTF(("ohci_process_done: TD done:\n")); + ohci_dump_tds(sdone); + } +#endif + + for (std = sdone; std; std = stdnext) { + xfer = std->xfer; + stdnext = std->dnext; + DPRINTFN(10, ("ohci_process_done: std=%p xfer=%p hcpriv=%p\n", + std, xfer, (xfer ? xfer->hcpriv : NULL))); + if (xfer == NULL) { + /* + * xfer == NULL: There seems to be no xfer associated + * with this TD. It is tailp that happened to end up on + * the done queue. + */ + continue; + } + if (xfer->status == USBD_CANCELLED || + xfer->status == USBD_TIMEOUT) { + DPRINTF(("ohci_process_done: cancel/timeout %p\n", + xfer)); + /* Handled by abort routine. */ + continue; + } + + len = std->len; + if (std->td.td_cbp != 0) + len -= le32toh(std->td.td_be) - + le32toh(std->td.td_cbp) + 1; + DPRINTFN(10, ("ohci_process_done: len=%d, flags=0x%x\n", len, + std->flags)); + if (std->flags & OHCI_ADD_LEN) + xfer->actlen += len; + + cc = OHCI_TD_GET_CC(le32toh(std->td.td_flags)); + if (cc != OHCI_CC_NO_ERROR) { + /* + * Endpoint is halted. First unlink all the TDs + * belonging to the failed transfer, and then restart + * the endpoint. + */ + opipe = (struct ohci_pipe *)xfer->pipe; + + DPRINTFN(15,("ohci_process_done: error cc=%d (%s)\n", + OHCI_TD_GET_CC(le32toh(std->td.td_flags)), + ohci_cc_strs[OHCI_TD_GET_CC(le32toh(std->td.td_flags))])); + callout_stop(&xfer->timeout_handle); + usb_rem_task(OXFER(xfer)->xfer.pipe->device, + &OXFER(xfer)->abort_task); + + /* Remove all this xfer's TDs from the done queue. */ + for (p = std; p->dnext != NULL; p = p->dnext) { + if (p->dnext->xfer != xfer) + continue; + p->dnext = p->dnext->dnext; + } + /* The next TD may have been removed. */ + stdnext = std->dnext; + + /* Remove all TDs belonging to this xfer. */ + for (p = xfer->hcpriv; p->xfer == xfer; p = n) { + n = p->nexttd; + ohci_free_std(sc, p); + } + + /* clear halt */ + opipe->sed->ed.ed_headp = htole32(p->physaddr); + OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_CLF); + + if (cc == OHCI_CC_STALL) + xfer->status = USBD_STALLED; + else + xfer->status = USBD_IOERROR; + s = splusb(); + usb_transfer_complete(xfer); + splx(s); + continue; + } + /* + * Skip intermediate TDs. They remain linked from + * xfer->hcpriv and we free them when the transfer completes. + */ + if ((std->flags & OHCI_CALL_DONE) == 0) + continue; + + /* Normal transfer completion */ + callout_stop(&xfer->timeout_handle); + usb_rem_task(OXFER(xfer)->xfer.pipe->device, + &OXFER(xfer)->abort_task); + for (p = xfer->hcpriv; p->xfer == xfer; p = n) { + n = p->nexttd; + ohci_free_std(sc, p); + } + xfer->status = USBD_NORMAL_COMPLETION; + s = splusb(); + usb_transfer_complete(xfer); + splx(s); + } + +#ifdef USB_DEBUG + if (ohcidebug > 10) { + DPRINTF(("ohci_softintr: ITD done:\n")); + ohci_dump_itds(sidone); + } +#endif + + for (sitd = sidone; sitd != NULL; sitd = sitdnext) { + xfer = sitd->xfer; + sitdnext = sitd->dnext; + sitd->flags |= OHCI_ITD_INTFIN; + DPRINTFN(1, ("ohci_process_done: sitd=%p xfer=%p hcpriv=%p\n", + sitd, xfer, xfer ? xfer->hcpriv : 0)); + if (xfer == NULL) + continue; + if (xfer->status == USBD_CANCELLED || + xfer->status == USBD_TIMEOUT) { + DPRINTF(("ohci_process_done: cancel/timeout %p\n", + xfer)); + /* Handled by abort routine. */ + continue; + } + if (xfer->pipe) + if (xfer->pipe->aborting) + continue; /*Ignore.*/ +#ifdef DIAGNOSTIC + if (sitd->isdone) + printf("ohci_softintr: sitd=%p is done\n", sitd); + sitd->isdone = 1; +#endif + opipe = (struct ohci_pipe *)xfer->pipe; + if (opipe->aborting) + continue; + + if (sitd->flags & OHCI_CALL_DONE) { + ohci_soft_itd_t *next; + + opipe->u.iso.inuse -= xfer->nframes; + xfer->status = USBD_NORMAL_COMPLETION; + for (i = 0, sitd = xfer->hcpriv;;sitd = next) { + next = sitd->nextitd; + if (OHCI_ITD_GET_CC(sitd->itd.itd_flags) != OHCI_CC_NO_ERROR) + xfer->status = USBD_IOERROR; + + if (xfer->status == USBD_NORMAL_COMPLETION) { + iframes = OHCI_ITD_GET_FC(sitd->itd.itd_flags); + for (j = 0; j < iframes; i++, j++) { + len = le16toh(sitd->itd.itd_offset[j]); + len = + (OHCI_ITD_PSW_GET_CC(len) == + OHCI_CC_NOT_ACCESSED) ? 0 : + OHCI_ITD_PSW_LENGTH(len); + xfer->frlengths[i] = len; + } + } + if (sitd->flags & OHCI_CALL_DONE) + break; + } + for (sitd = xfer->hcpriv; sitd->xfer == xfer; + sitd = next) { + next = sitd->nextitd; + ohci_free_sitd(sc, sitd); + } + + s = splusb(); + usb_transfer_complete(xfer); + splx(s); + } + } + +#ifdef USB_USE_SOFTINTR + if (sc->sc_softwake) { + sc->sc_softwake = 0; + wakeup(&sc->sc_softwake); + } +#endif /* USB_USE_SOFTINTR */ + + sc->sc_bus.intr_context--; + DPRINTFN(10,("ohci_softintr: done:\n")); +} + +void +ohci_device_ctrl_done(usbd_xfer_handle xfer) +{ + DPRINTFN(10,("ohci_device_ctrl_done: xfer=%p\n", xfer)); + +#ifdef DIAGNOSTIC + if (!(xfer->rqflags & URQ_REQUEST)) { + panic("ohci_device_ctrl_done: not a request"); + } +#endif + xfer->hcpriv = NULL; +} + +void +ohci_device_intr_done(usbd_xfer_handle xfer) +{ + struct ohci_pipe *opipe = (struct ohci_pipe *)xfer->pipe; + ohci_softc_t *sc = (ohci_softc_t *)opipe->pipe.device->bus; + usbd_status err; + + DPRINTFN(10,("ohci_device_intr_done: xfer=%p, actlen=%d\n", + xfer, xfer->actlen)); + + xfer->hcpriv = NULL; + if (xfer->pipe->repeat) { + err = ohci_device_intr_insert(sc, xfer); + if (err) { + xfer->status = err; + return; + } + } +} + +void +ohci_device_bulk_done(usbd_xfer_handle xfer) +{ + DPRINTFN(10,("ohci_device_bulk_done: xfer=%p, actlen=%d\n", + xfer, xfer->actlen)); + + xfer->hcpriv = NULL; +} + +/* + * XXX write back xfer data for architectures with a write-back + * data cache; this is a hack because usb is mis-architected + * in blindly mixing bus_dma w/ PIO. + */ +static __inline void +hacksync(usbd_xfer_handle xfer) +{ + bus_dma_tag_t tag; + struct usb_dma_mapping *dmap; + + if (xfer->length == 0) + return; + tag = xfer->pipe->device->bus->buffer_dmatag; + dmap = &xfer->dmamap; + bus_dmamap_sync(tag, dmap->map, BUS_DMASYNC_PREWRITE); +} + +void +ohci_rhsc(ohci_softc_t *sc, usbd_xfer_handle xfer) +{ + usbd_pipe_handle pipe; + u_char *p; + int i, m; + int hstatus; + + hstatus = OREAD4(sc, OHCI_RH_STATUS); + DPRINTF(("ohci_rhsc: sc=%p xfer=%p hstatus=0x%08x\n", + sc, xfer, hstatus)); + + if (xfer == NULL) { + /* Just ignore the change. */ + return; + } + + pipe = xfer->pipe; + + p = xfer->buffer; + m = min(sc->sc_noport, xfer->length * 8 - 1); + memset(p, 0, xfer->length); + for (i = 1; i <= m; i++) { + /* Pick out CHANGE bits from the status reg. */ + if (OREAD4(sc, OHCI_RH_PORT_STATUS(i)) >> 16) + p[i/8] |= 1 << (i%8); + } + DPRINTF(("ohci_rhsc: change=0x%02x\n", *p)); + xfer->actlen = xfer->length; + xfer->status = USBD_NORMAL_COMPLETION; + + hacksync(xfer); /* XXX to compensate for usb_transfer_complete */ + usb_transfer_complete(xfer); +} + +void +ohci_root_intr_done(usbd_xfer_handle xfer) +{ + xfer->hcpriv = NULL; +} + +void +ohci_root_ctrl_done(usbd_xfer_handle xfer) +{ + xfer->hcpriv = NULL; +} + +/* + * Wait here until controller claims to have an interrupt. + * Then call ohci_intr and return. Use timeout to avoid waiting + * too long. + */ +void +ohci_waitintr(ohci_softc_t *sc, usbd_xfer_handle xfer) +{ + int timo = xfer->timeout; + int usecs; + u_int32_t intrs; + + xfer->status = USBD_IN_PROGRESS; + for (usecs = timo * 1000000 / hz; usecs > 0; usecs -= 1000) { + usb_delay_ms(&sc->sc_bus, 1); + if (sc->sc_dying) + break; + intrs = OREAD4(sc, OHCI_INTERRUPT_STATUS) & sc->sc_eintrs; + DPRINTFN(15,("ohci_waitintr: 0x%04x\n", intrs)); +#ifdef USB_DEBUG + if (ohcidebug > 15) + ohci_dumpregs(sc); +#endif + if (intrs) { + ohci_intr1(sc); + if (xfer->status != USBD_IN_PROGRESS) + return; + } + } + + /* Timeout */ + DPRINTF(("ohci_waitintr: timeout\n")); + xfer->status = USBD_TIMEOUT; + usb_transfer_complete(xfer); + /* XXX should free TD */ +} + +void +ohci_poll(struct usbd_bus *bus) +{ + ohci_softc_t *sc = (ohci_softc_t *)bus; +#ifdef USB_DEBUG + static int last; + int new; + new = OREAD4(sc, OHCI_INTERRUPT_STATUS); + if (new != last) { + DPRINTFN(10,("ohci_poll: intrs=0x%04x\n", new)); + last = new; + } +#endif + + if (OREAD4(sc, OHCI_INTERRUPT_STATUS) & sc->sc_eintrs) + ohci_intr1(sc); +} + +usbd_status +ohci_device_request(usbd_xfer_handle xfer) +{ + struct ohci_pipe *opipe = (struct ohci_pipe *)xfer->pipe; + usb_device_request_t *req = &xfer->request; + usbd_device_handle dev = opipe->pipe.device; + ohci_softc_t *sc = (ohci_softc_t *)dev->bus; + ohci_soft_td_t *setup, *stat, *next, *tail; + ohci_soft_ed_t *sed; + int isread; + int len; + usbd_status err; + int s; + + isread = req->bmRequestType & UT_READ; + len = UGETW(req->wLength); + + DPRINTFN(3,("ohci_device_control type=0x%02x, request=0x%02x, " + "wValue=0x%04x, wIndex=0x%04x len=%d, addr=%d, endpt=%d\n", + req->bmRequestType, req->bRequest, UGETW(req->wValue), + UGETW(req->wIndex), len, dev->address, + opipe->pipe.endpoint->edesc->bEndpointAddress)); + + setup = opipe->tail.td; + stat = ohci_alloc_std(sc); + if (stat == NULL) { + err = USBD_NOMEM; + goto bad1; + } + tail = ohci_alloc_std(sc); + if (tail == NULL) { + err = USBD_NOMEM; + goto bad2; + } + tail->xfer = NULL; + + sed = opipe->sed; + opipe->u.ctl.length = len; + next = stat; + + /* Set up data transaction */ + if (len != 0) { + ohci_soft_td_t *std = stat; + + err = ohci_alloc_std_chain(opipe, sc, len, isread, xfer, + std, &stat); + stat = stat->nexttd; /* point at free TD */ + if (err) + goto bad3; + /* Start toggle at 1 and then use the carried toggle. */ + std->td.td_flags &= htole32(~OHCI_TD_TOGGLE_MASK); + std->td.td_flags |= htole32(OHCI_TD_TOGGLE_1); + } + + memcpy(KERNADDR(&opipe->u.ctl.reqdma, 0), req, sizeof *req); + + setup->td.td_flags = htole32(OHCI_TD_SETUP | OHCI_TD_NOCC | + OHCI_TD_TOGGLE_0 | OHCI_TD_SET_DI(6)); + setup->td.td_cbp = htole32(DMAADDR(&opipe->u.ctl.reqdma, 0)); + setup->nexttd = next; + setup->td.td_nexttd = htole32(next->physaddr); + setup->td.td_be = htole32(le32toh(setup->td.td_cbp) + sizeof *req - 1); + setup->len = 0; + setup->xfer = xfer; + setup->flags = 0; + xfer->hcpriv = setup; + + stat->td.td_flags = htole32( + (isread ? OHCI_TD_OUT : OHCI_TD_IN) | + OHCI_TD_NOCC | OHCI_TD_TOGGLE_1 | OHCI_TD_SET_DI(1)); + stat->td.td_cbp = 0; + stat->nexttd = tail; + stat->td.td_nexttd = htole32(tail->physaddr); + stat->td.td_be = 0; + stat->flags = OHCI_CALL_DONE; + stat->len = 0; + stat->xfer = xfer; + +#ifdef USB_DEBUG + if (ohcidebug > 5) { + DPRINTF(("ohci_device_request:\n")); + ohci_dump_ed(sed); + ohci_dump_tds(setup); + } +#endif + + /* Insert ED in schedule */ + s = splusb(); + sed->ed.ed_tailp = htole32(tail->physaddr); + opipe->tail.td = tail; + OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_CLF); + if (xfer->timeout && !sc->sc_bus.use_polling) { + callout_reset(&xfer->timeout_handle, MS_TO_TICKS(xfer->timeout), + ohci_timeout, xfer); + } + splx(s); + +#ifdef USB_DEBUG + if (ohcidebug > 20) { + delay(10000); + DPRINTF(("ohci_device_request: status=%x\n", + OREAD4(sc, OHCI_COMMAND_STATUS))); + ohci_dumpregs(sc); + printf("ctrl head:\n"); + ohci_dump_ed(sc->sc_ctrl_head); + printf("sed:\n"); + ohci_dump_ed(sed); + ohci_dump_tds(setup); + } +#endif + + return (USBD_NORMAL_COMPLETION); + + bad3: + ohci_free_std(sc, tail); + bad2: + ohci_free_std(sc, stat); + bad1: + return (err); +} + +/* + * Add an ED to the schedule. Called at splusb(). + */ +void +ohci_add_ed(ohci_soft_ed_t *sed, ohci_soft_ed_t *head) +{ + DPRINTFN(8,("ohci_add_ed: sed=%p head=%p\n", sed, head)); + + SPLUSBCHECK; + sed->next = head->next; + sed->ed.ed_nexted = head->ed.ed_nexted; + head->next = sed; + head->ed.ed_nexted = htole32(sed->physaddr); +} + +/* + * Remove an ED from the schedule. Called at splusb(). + */ +void +ohci_rem_ed(ohci_soft_ed_t *sed, ohci_soft_ed_t *head) +{ + ohci_soft_ed_t *p; + + SPLUSBCHECK; + + /* XXX */ + for (p = head; p != NULL && p->next != sed; p = p->next) + ; + if (p == NULL) + panic("ohci_rem_ed: ED not found"); + p->next = sed->next; + p->ed.ed_nexted = sed->ed.ed_nexted; +} + +/* + * When a transfer is completed the TD is added to the done queue by + * the host controller. This queue is the processed by software. + * Unfortunately the queue contains the physical address of the TD + * and we have no simple way to translate this back to a kernel address. + * To make the translation possible (and fast) we use a hash table of + * TDs currently in the schedule. The physical address is used as the + * hash value. + */ + +#define HASH(a) (((a) >> 4) % OHCI_HASH_SIZE) +/* Called at splusb() */ +void +ohci_hash_add_td(ohci_softc_t *sc, ohci_soft_td_t *std) +{ + int h = HASH(std->physaddr); + + SPLUSBCHECK; + + LIST_INSERT_HEAD(&sc->sc_hash_tds[h], std, hnext); +} + +/* Called at splusb() */ +void +ohci_hash_rem_td(ohci_softc_t *sc, ohci_soft_td_t *std) +{ + SPLUSBCHECK; + + LIST_REMOVE(std, hnext); +} + +ohci_soft_td_t * +ohci_hash_find_td(ohci_softc_t *sc, ohci_physaddr_t a) +{ + int h = HASH(a); + ohci_soft_td_t *std; + + /* if these are present they should be masked out at an earlier + * stage. + */ + KASSERT((a&~OHCI_HEADMASK) == 0, ("%s: 0x%b has lower bits set\n", + device_get_nameunit(sc->sc_bus.bdev), + (int) a, "\20\1HALT\2TOGGLE")); + + for (std = LIST_FIRST(&sc->sc_hash_tds[h]); + std != NULL; + std = LIST_NEXT(std, hnext)) + if (std->physaddr == a) + return (std); + + DPRINTF(("%s: ohci_hash_find_td: addr 0x%08lx not found\n", + device_get_nameunit(sc->sc_bus.bdev), (u_long) a)); + return (NULL); +} + +/* Called at splusb() */ +void +ohci_hash_add_itd(ohci_softc_t *sc, ohci_soft_itd_t *sitd) +{ + int h = HASH(sitd->physaddr); + + SPLUSBCHECK; + + DPRINTFN(10,("ohci_hash_add_itd: sitd=%p physaddr=0x%08lx\n", + sitd, (u_long)sitd->physaddr)); + + LIST_INSERT_HEAD(&sc->sc_hash_itds[h], sitd, hnext); +} + +/* Called at splusb() */ +void +ohci_hash_rem_itd(ohci_softc_t *sc, ohci_soft_itd_t *sitd) +{ + SPLUSBCHECK; + + DPRINTFN(10,("ohci_hash_rem_itd: sitd=%p physaddr=0x%08lx\n", + sitd, (u_long)sitd->physaddr)); + + LIST_REMOVE(sitd, hnext); +} + +ohci_soft_itd_t * +ohci_hash_find_itd(ohci_softc_t *sc, ohci_physaddr_t a) +{ + int h = HASH(a); + ohci_soft_itd_t *sitd; + + for (sitd = LIST_FIRST(&sc->sc_hash_itds[h]); + sitd != NULL; + sitd = LIST_NEXT(sitd, hnext)) + if (sitd->physaddr == a) + return (sitd); + return (NULL); +} + +void +ohci_timeout(void *addr) +{ + struct ohci_xfer *oxfer = addr; + struct ohci_pipe *opipe = (struct ohci_pipe *)oxfer->xfer.pipe; + ohci_softc_t *sc = (ohci_softc_t *)opipe->pipe.device->bus; + + DPRINTF(("ohci_timeout: oxfer=%p\n", oxfer)); + + if (sc->sc_dying) { + ohci_abort_xfer(&oxfer->xfer, USBD_TIMEOUT); + return; + } + + /* Execute the abort in a process context. */ + usb_add_task(oxfer->xfer.pipe->device, &oxfer->abort_task, + USB_TASKQ_HC); +} + +void +ohci_timeout_task(void *addr) +{ + usbd_xfer_handle xfer = addr; + int s; + + DPRINTF(("ohci_timeout_task: xfer=%p\n", xfer)); + + s = splusb(); + ohci_abort_xfer(xfer, USBD_TIMEOUT); + splx(s); +} + +#ifdef USB_DEBUG +void +ohci_dump_tds(ohci_soft_td_t *std) +{ + for (; std; std = std->nexttd) + ohci_dump_td(std); +} + +void +ohci_dump_td(ohci_soft_td_t *std) +{ + char sbuf[128]; + + bitmask_snprintf((u_int32_t)le32toh(std->td.td_flags), + "\20\23R\24OUT\25IN\31TOG1\32SETTOGGLE", + sbuf, sizeof(sbuf)); + + printf("TD(%p) at %08lx: %s delay=%d ec=%d cc=%d\ncbp=0x%08lx " + "nexttd=0x%08lx be=0x%08lx\n", + std, (u_long)std->physaddr, sbuf, + OHCI_TD_GET_DI(le32toh(std->td.td_flags)), + OHCI_TD_GET_EC(le32toh(std->td.td_flags)), + OHCI_TD_GET_CC(le32toh(std->td.td_flags)), + (u_long)le32toh(std->td.td_cbp), + (u_long)le32toh(std->td.td_nexttd), + (u_long)le32toh(std->td.td_be)); +} + +void +ohci_dump_itd(ohci_soft_itd_t *sitd) +{ + int i; + + printf("ITD(%p) at %08lx: sf=%d di=%d fc=%d cc=%d\n" + "bp0=0x%08lx next=0x%08lx be=0x%08lx\n", + sitd, (u_long)sitd->physaddr, + OHCI_ITD_GET_SF(le32toh(sitd->itd.itd_flags)), + OHCI_ITD_GET_DI(le32toh(sitd->itd.itd_flags)), + OHCI_ITD_GET_FC(le32toh(sitd->itd.itd_flags)), + OHCI_ITD_GET_CC(le32toh(sitd->itd.itd_flags)), + (u_long)le32toh(sitd->itd.itd_bp0), + (u_long)le32toh(sitd->itd.itd_nextitd), + (u_long)le32toh(sitd->itd.itd_be)); + for (i = 0; i < OHCI_ITD_NOFFSET; i++) + printf("offs[%d]=0x%04x ", i, + (u_int)le16toh(sitd->itd.itd_offset[i])); + printf("\n"); +} + +void +ohci_dump_itds(ohci_soft_itd_t *sitd) +{ + for (; sitd; sitd = sitd->nextitd) + ohci_dump_itd(sitd); +} + +void +ohci_dump_ed(ohci_soft_ed_t *sed) +{ + char sbuf[128], sbuf2[128]; + + bitmask_snprintf((u_int32_t)le32toh(sed->ed.ed_flags), + "\20\14OUT\15IN\16LOWSPEED\17SKIP\20ISO", + sbuf, sizeof(sbuf)); + bitmask_snprintf((u_int32_t)le32toh(sed->ed.ed_headp), + "\20\1HALT\2CARRY", sbuf2, sizeof(sbuf2)); + + printf("ED(%p) at 0x%08lx: addr=%d endpt=%d maxp=%d flags=%s\ntailp=0x%08lx " + "headflags=%s headp=0x%08lx nexted=0x%08lx\n", + sed, (u_long)sed->physaddr, + OHCI_ED_GET_FA(le32toh(sed->ed.ed_flags)), + OHCI_ED_GET_EN(le32toh(sed->ed.ed_flags)), + OHCI_ED_GET_MAXP(le32toh(sed->ed.ed_flags)), sbuf, + (u_long)le32toh(sed->ed.ed_tailp), sbuf2, + (u_long)le32toh(sed->ed.ed_headp), + (u_long)le32toh(sed->ed.ed_nexted)); +} +#endif + +usbd_status +ohci_open(usbd_pipe_handle pipe) +{ + usbd_device_handle dev = pipe->device; + ohci_softc_t *sc = (ohci_softc_t *)dev->bus; + usb_endpoint_descriptor_t *ed = pipe->endpoint->edesc; + struct ohci_pipe *opipe = (struct ohci_pipe *)pipe; + u_int8_t addr = dev->address; + u_int8_t xfertype = ed->bmAttributes & UE_XFERTYPE; + ohci_soft_ed_t *sed; + ohci_soft_td_t *std; + ohci_soft_itd_t *sitd; + ohci_physaddr_t tdphys; + u_int32_t fmt; + usbd_status err; + int s; + int ival; + + DPRINTFN(1, ("ohci_open: pipe=%p, addr=%d, endpt=%d (%d)\n", + pipe, addr, ed->bEndpointAddress, sc->sc_addr)); + + if (sc->sc_dying) + return (USBD_IOERROR); + + std = NULL; + sed = NULL; + + if (addr == sc->sc_addr) { + switch (ed->bEndpointAddress) { + case USB_CONTROL_ENDPOINT: + pipe->methods = &ohci_root_ctrl_methods; + break; + case UE_DIR_IN | OHCI_INTR_ENDPT: + pipe->methods = &ohci_root_intr_methods; + break; + default: + return (USBD_INVAL); + } + } else { + sed = ohci_alloc_sed(sc); + if (sed == NULL) + goto bad0; + opipe->sed = sed; + if (xfertype == UE_ISOCHRONOUS) { + sitd = ohci_alloc_sitd(sc); + if (sitd == NULL) + goto bad1; + opipe->tail.itd = sitd; + opipe->aborting = 0; + tdphys = sitd->physaddr; + fmt = OHCI_ED_FORMAT_ISO; + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN) + fmt |= OHCI_ED_DIR_IN; + else + fmt |= OHCI_ED_DIR_OUT; + } else { + std = ohci_alloc_std(sc); + if (std == NULL) + goto bad1; + opipe->tail.td = std; + tdphys = std->physaddr; + fmt = OHCI_ED_FORMAT_GEN | OHCI_ED_DIR_TD; + } + sed->ed.ed_flags = htole32( + OHCI_ED_SET_FA(addr) | + OHCI_ED_SET_EN(UE_GET_ADDR(ed->bEndpointAddress)) | + (dev->speed == USB_SPEED_LOW ? OHCI_ED_SPEED : 0) | + fmt | + OHCI_ED_SET_MAXP(UGETW(ed->wMaxPacketSize))); + sed->ed.ed_headp = htole32(tdphys | + (pipe->endpoint->savedtoggle ? OHCI_TOGGLECARRY : 0)); + sed->ed.ed_tailp = htole32(tdphys); + + switch (xfertype) { + case UE_CONTROL: + pipe->methods = &ohci_device_ctrl_methods; + err = usb_allocmem(&sc->sc_bus, + sizeof(usb_device_request_t), + 0, &opipe->u.ctl.reqdma); + if (err) + goto bad; + s = splusb(); + ohci_add_ed(sed, sc->sc_ctrl_head); + splx(s); + break; + case UE_INTERRUPT: + pipe->methods = &ohci_device_intr_methods; + ival = pipe->interval; + if (ival == USBD_DEFAULT_INTERVAL) + ival = ed->bInterval; + return (ohci_device_setintr(sc, opipe, ival)); + case UE_ISOCHRONOUS: + pipe->methods = &ohci_device_isoc_methods; + return (ohci_setup_isoc(pipe)); + case UE_BULK: + pipe->methods = &ohci_device_bulk_methods; + s = splusb(); + ohci_add_ed(sed, sc->sc_bulk_head); + splx(s); + break; + } + } + return (USBD_NORMAL_COMPLETION); + + bad: + if (std != NULL) + ohci_free_std(sc, std); + bad1: + if (sed != NULL) + ohci_free_sed(sc, sed); + bad0: + return (USBD_NOMEM); + +} + +/* + * Close a reqular pipe. + * Assumes that there are no pending transactions. + */ +void +ohci_close_pipe(usbd_pipe_handle pipe, ohci_soft_ed_t *head) +{ + struct ohci_pipe *opipe = (struct ohci_pipe *)pipe; + ohci_softc_t *sc = (ohci_softc_t *)pipe->device->bus; + ohci_soft_ed_t *sed = opipe->sed; + int s; + + s = splusb(); +#ifdef DIAGNOSTIC + sed->ed.ed_flags |= htole32(OHCI_ED_SKIP); + if ((le32toh(sed->ed.ed_tailp) & OHCI_HEADMASK) != + (le32toh(sed->ed.ed_headp) & OHCI_HEADMASK)) { + ohci_soft_td_t *std; + std = ohci_hash_find_td(sc, le32toh(sed->ed.ed_headp)); + printf("ohci_close_pipe: pipe not empty sed=%p hd=0x%x " + "tl=0x%x pipe=%p, std=%p\n", sed, + (int)le32toh(sed->ed.ed_headp), + (int)le32toh(sed->ed.ed_tailp), + pipe, std); +#ifdef USB_DEBUG + usbd_dump_pipe(&opipe->pipe); +#endif +#ifdef USB_DEBUG + ohci_dump_ed(sed); + if (std) + ohci_dump_td(std); +#endif + usb_delay_ms(&sc->sc_bus, 2); + if ((le32toh(sed->ed.ed_tailp) & OHCI_HEADMASK) != + (le32toh(sed->ed.ed_headp) & OHCI_HEADMASK)) + printf("ohci_close_pipe: pipe still not empty\n"); + } +#endif + ohci_rem_ed(sed, head); + /* Make sure the host controller is not touching this ED */ + usb_delay_ms(&sc->sc_bus, 1); + splx(s); + pipe->endpoint->savedtoggle = + (le32toh(sed->ed.ed_headp) & OHCI_TOGGLECARRY) ? 1 : 0; + ohci_free_sed(sc, opipe->sed); +} + +/* + * Abort a device request. + * If this routine is called at splusb() it guarantees that the request + * will be removed from the hardware scheduling and that the callback + * for it will be called with USBD_CANCELLED status. + * It's impossible to guarantee that the requested transfer will not + * have happened since the hardware runs concurrently. + * If the transaction has already happened we rely on the ordinary + * interrupt processing to process it. + */ +void +ohci_abort_xfer(usbd_xfer_handle xfer, usbd_status status) +{ + struct ohci_xfer *oxfer = OXFER(xfer); + struct ohci_pipe *opipe = (struct ohci_pipe *)xfer->pipe; + ohci_softc_t *sc = (ohci_softc_t *)opipe->pipe.device->bus; + ohci_soft_ed_t *sed = opipe->sed; + ohci_soft_td_t *p, *n; + ohci_physaddr_t headp; + int s, hit; + + DPRINTF(("ohci_abort_xfer: xfer=%p pipe=%p sed=%p\n", xfer, opipe,sed)); + + if (sc->sc_dying) { + /* If we're dying, just do the software part. */ + s = splusb(); + xfer->status = status; /* make software ignore it */ + callout_stop(&xfer->timeout_handle); + usb_rem_task(xfer->pipe->device, &OXFER(xfer)->abort_task); + usb_transfer_complete(xfer); + splx(s); + return; + } + + if (xfer->device->bus->intr_context || !curproc) + panic("ohci_abort_xfer: not in process context"); + + /* + * If an abort is already in progress then just wait for it to + * complete and return. + */ + if (oxfer->ohci_xfer_flags & OHCI_XFER_ABORTING) { + DPRINTFN(2, ("ohci_abort_xfer: already aborting\n")); + /* No need to wait if we're aborting from a timeout. */ + if (status == USBD_TIMEOUT) + return; + /* Override the status which might be USBD_TIMEOUT. */ + xfer->status = status; + DPRINTFN(2, ("ohci_abort_xfer: waiting for abort to finish\n")); + oxfer->ohci_xfer_flags |= OHCI_XFER_ABORTWAIT; + while (oxfer->ohci_xfer_flags & OHCI_XFER_ABORTING) + tsleep(&oxfer->ohci_xfer_flags, PZERO, "ohciaw", 0); + return; + } + + /* + * Step 1: Make interrupt routine and hardware ignore xfer. + */ + s = splusb(); + oxfer->ohci_xfer_flags |= OHCI_XFER_ABORTING; + xfer->status = status; /* make software ignore it */ + callout_stop(&xfer->timeout_handle); + usb_rem_task(xfer->pipe->device, &OXFER(xfer)->abort_task); + splx(s); + DPRINTFN(1,("ohci_abort_xfer: stop ed=%p\n", sed)); + sed->ed.ed_flags |= htole32(OHCI_ED_SKIP); /* force hardware skip */ + + /* + * Step 2: Wait until we know hardware has finished any possible + * use of the xfer. Also make sure the soft interrupt routine + * has run. + */ + usb_delay_ms(opipe->pipe.device->bus, 20); /* Hardware finishes in 1ms */ + s = splusb(); +#ifdef USB_USE_SOFTINTR + sc->sc_softwake = 1; +#endif /* USB_USE_SOFTINTR */ + usb_schedsoftintr(&sc->sc_bus); +#ifdef USB_USE_SOFTINTR + tsleep(&sc->sc_softwake, PZERO, "ohciab", 0); +#endif /* USB_USE_SOFTINTR */ + splx(s); + + /* + * Step 3: Remove any vestiges of the xfer from the hardware. + * The complication here is that the hardware may have executed + * beyond the xfer we're trying to abort. So as we're scanning + * the TDs of this xfer we check if the hardware points to + * any of them. + */ + s = splusb(); /* XXX why? */ + p = xfer->hcpriv; +#ifdef DIAGNOSTIC + if (p == NULL) { + oxfer->ohci_xfer_flags &= ~OHCI_XFER_ABORTING; /* XXX */ + splx(s); + printf("ohci_abort_xfer: hcpriv is NULL\n"); + return; + } +#endif +#ifdef USB_DEBUG + if (ohcidebug > 1) { + DPRINTF(("ohci_abort_xfer: sed=\n")); + ohci_dump_ed(sed); + ohci_dump_tds(p); + } +#endif + headp = le32toh(sed->ed.ed_headp) & OHCI_HEADMASK; + hit = 0; + for (; p->xfer == xfer; p = n) { + hit |= headp == p->physaddr; + n = p->nexttd; + ohci_free_std(sc, p); + } + /* Zap headp register if hardware pointed inside the xfer. */ + if (hit) { + DPRINTFN(1,("ohci_abort_xfer: set hd=0x08%x, tl=0x%08x\n", + (int)p->physaddr, (int)le32toh(sed->ed.ed_tailp))); + sed->ed.ed_headp = htole32(p->physaddr); /* unlink TDs */ + } else { + DPRINTFN(1,("ohci_abort_xfer: no hit\n")); + } + + /* + * Step 4: Turn on hardware again. + */ + sed->ed.ed_flags &= htole32(~OHCI_ED_SKIP); /* remove hardware skip */ + + /* + * Step 5: Execute callback. + */ + /* Do the wakeup first to avoid touching the xfer after the callback. */ + oxfer->ohci_xfer_flags &= ~OHCI_XFER_ABORTING; + if (oxfer->ohci_xfer_flags & OHCI_XFER_ABORTWAIT) { + oxfer->ohci_xfer_flags &= ~OHCI_XFER_ABORTWAIT; + wakeup(&oxfer->ohci_xfer_flags); + } + usb_transfer_complete(xfer); + + splx(s); +} + +/* + * Data structures and routines to emulate the root hub. + */ +static usb_device_descriptor_t ohci_devd = { + USB_DEVICE_DESCRIPTOR_SIZE, + UDESC_DEVICE, /* type */ + {0x00, 0x01}, /* USB version */ + UDCLASS_HUB, /* class */ + UDSUBCLASS_HUB, /* subclass */ + UDPROTO_FSHUB, /* protocol */ + 64, /* max packet */ + {0},{0},{0x00,0x01}, /* device id */ + 1,2,0, /* string indicies */ + 1 /* # of configurations */ +}; + +static usb_config_descriptor_t ohci_confd = { + USB_CONFIG_DESCRIPTOR_SIZE, + UDESC_CONFIG, + {USB_CONFIG_DESCRIPTOR_SIZE + + USB_INTERFACE_DESCRIPTOR_SIZE + + USB_ENDPOINT_DESCRIPTOR_SIZE}, + 1, + 1, + 0, + UC_SELF_POWERED, + 0 /* max power */ +}; + +static usb_interface_descriptor_t ohci_ifcd = { + USB_INTERFACE_DESCRIPTOR_SIZE, + UDESC_INTERFACE, + 0, + 0, + 1, + UICLASS_HUB, + UISUBCLASS_HUB, + UIPROTO_FSHUB, + 0 +}; + +static usb_endpoint_descriptor_t ohci_endpd = { + USB_ENDPOINT_DESCRIPTOR_SIZE, + UDESC_ENDPOINT, + UE_DIR_IN | OHCI_INTR_ENDPT, + UE_INTERRUPT, + {8, 0}, /* max packet */ + 255 +}; + +static usb_hub_descriptor_t ohci_hubd = { + USB_HUB_DESCRIPTOR_SIZE, + UDESC_HUB, + 0, + {0,0}, + 0, + 0, + {0}, +}; + +static int +ohci_str(usb_string_descriptor_t *p, int l, const char *s) +{ + int i; + + if (l == 0) + return (0); + p->bLength = 2 * strlen(s) + 2; + if (l == 1) + return (1); + p->bDescriptorType = UDESC_STRING; + l -= 2; + for (i = 0; s[i] && l > 1; i++, l -= 2) + USETW2(p->bString[i], 0, s[i]); + return (2*i+2); +} + +/* + * Simulate a hardware hub by handling all the necessary requests. + */ +static usbd_status +ohci_root_ctrl_transfer(usbd_xfer_handle xfer) +{ + usbd_status err; + + /* Insert last in queue. */ + err = usb_insert_transfer(xfer); + if (err) + return (err); + + /* Pipe isn't running, start first */ + return (ohci_root_ctrl_start(STAILQ_FIRST(&xfer->pipe->queue))); +} + +static usbd_status +ohci_root_ctrl_start(usbd_xfer_handle xfer) +{ + ohci_softc_t *sc = (ohci_softc_t *)xfer->pipe->device->bus; + usb_device_request_t *req; + void *buf = NULL; + int port, i; + int s, len, value, index, l, totlen = 0; + usb_port_status_t ps; + usb_hub_descriptor_t hubd; + usbd_status err; + u_int32_t v; + + if (sc->sc_dying) + return (USBD_IOERROR); + +#ifdef DIAGNOSTIC + if (!(xfer->rqflags & URQ_REQUEST)) + /* XXX panic */ + return (USBD_INVAL); +#endif + req = &xfer->request; + + DPRINTFN(4,("ohci_root_ctrl_control type=0x%02x request=%02x\n", + req->bmRequestType, req->bRequest)); + + len = UGETW(req->wLength); + value = UGETW(req->wValue); + index = UGETW(req->wIndex); + + if (len != 0) + buf = xfer->buffer; + +#define C(x,y) ((x) | ((y) << 8)) + switch(C(req->bRequest, req->bmRequestType)) { + case C(UR_CLEAR_FEATURE, UT_WRITE_DEVICE): + case C(UR_CLEAR_FEATURE, UT_WRITE_INTERFACE): + case C(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT): + /* + * DEVICE_REMOTE_WAKEUP and ENDPOINT_HALT are no-ops + * for the integrated root hub. + */ + break; + case C(UR_GET_CONFIG, UT_READ_DEVICE): + if (len > 0) { + *(u_int8_t *)buf = sc->sc_conf; + totlen = 1; + } + break; + case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE): + DPRINTFN(8,("ohci_root_ctrl_control wValue=0x%04x\n", value)); + switch(value >> 8) { + case UDESC_DEVICE: + if ((value & 0xff) != 0) { + err = USBD_IOERROR; + goto ret; + } + totlen = l = min(len, USB_DEVICE_DESCRIPTOR_SIZE); + USETW(ohci_devd.idVendor, sc->sc_id_vendor); + memcpy(buf, &ohci_devd, l); + break; + case UDESC_CONFIG: + if ((value & 0xff) != 0) { + err = USBD_IOERROR; + goto ret; + } + totlen = l = min(len, USB_CONFIG_DESCRIPTOR_SIZE); + memcpy(buf, &ohci_confd, l); + buf = (char *)buf + l; + len -= l; + l = min(len, USB_INTERFACE_DESCRIPTOR_SIZE); + totlen += l; + memcpy(buf, &ohci_ifcd, l); + buf = (char *)buf + l; + len -= l; + l = min(len, USB_ENDPOINT_DESCRIPTOR_SIZE); + totlen += l; + memcpy(buf, &ohci_endpd, l); + break; + case UDESC_STRING: + if (len == 0) + break; + *(u_int8_t *)buf = 0; + totlen = 1; + switch (value & 0xff) { + case 1: /* Vendor */ + totlen = ohci_str(buf, len, sc->sc_vendor); + break; + case 2: /* Product */ + totlen = ohci_str(buf, len, "OHCI root hub"); + break; + } + break; + default: + err = USBD_IOERROR; + goto ret; + } + break; + case C(UR_GET_INTERFACE, UT_READ_INTERFACE): + if (len > 0) { + *(u_int8_t *)buf = 0; + totlen = 1; + } + break; + case C(UR_GET_STATUS, UT_READ_DEVICE): + if (len > 1) { + USETW(((usb_status_t *)buf)->wStatus,UDS_SELF_POWERED); + totlen = 2; + } + break; + case C(UR_GET_STATUS, UT_READ_INTERFACE): + case C(UR_GET_STATUS, UT_READ_ENDPOINT): + if (len > 1) { + USETW(((usb_status_t *)buf)->wStatus, 0); + totlen = 2; + } + break; + case C(UR_SET_ADDRESS, UT_WRITE_DEVICE): + if (value >= USB_MAX_DEVICES) { + err = USBD_IOERROR; + goto ret; + } + sc->sc_addr = value; + break; + case C(UR_SET_CONFIG, UT_WRITE_DEVICE): + if (value != 0 && value != 1) { + err = USBD_IOERROR; + goto ret; + } + sc->sc_conf = value; + break; + case C(UR_SET_DESCRIPTOR, UT_WRITE_DEVICE): + break; + case C(UR_SET_FEATURE, UT_WRITE_DEVICE): + case C(UR_SET_FEATURE, UT_WRITE_INTERFACE): + case C(UR_SET_FEATURE, UT_WRITE_ENDPOINT): + err = USBD_IOERROR; + goto ret; + case C(UR_SET_INTERFACE, UT_WRITE_INTERFACE): + break; + case C(UR_SYNCH_FRAME, UT_WRITE_ENDPOINT): + break; + /* Hub requests */ + case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_DEVICE): + break; + case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_OTHER): + DPRINTFN(8, ("ohci_root_ctrl_control: UR_CLEAR_PORT_FEATURE " + "port=%d feature=%d\n", + index, value)); + if (index < 1 || index > sc->sc_noport) { + err = USBD_IOERROR; + goto ret; + } + port = OHCI_RH_PORT_STATUS(index); + switch(value) { + case UHF_PORT_ENABLE: + OWRITE4(sc, port, UPS_CURRENT_CONNECT_STATUS); + break; + case UHF_PORT_SUSPEND: + OWRITE4(sc, port, UPS_OVERCURRENT_INDICATOR); + break; + case UHF_PORT_POWER: + /* Yes, writing to the LOW_SPEED bit clears power. */ + OWRITE4(sc, port, UPS_LOW_SPEED); + break; + case UHF_C_PORT_CONNECTION: + OWRITE4(sc, port, UPS_C_CONNECT_STATUS << 16); + break; + case UHF_C_PORT_ENABLE: + OWRITE4(sc, port, UPS_C_PORT_ENABLED << 16); + break; + case UHF_C_PORT_SUSPEND: + OWRITE4(sc, port, UPS_C_SUSPEND << 16); + break; + case UHF_C_PORT_OVER_CURRENT: + OWRITE4(sc, port, UPS_C_OVERCURRENT_INDICATOR << 16); + break; + case UHF_C_PORT_RESET: + OWRITE4(sc, port, UPS_C_PORT_RESET << 16); + break; + default: + err = USBD_IOERROR; + goto ret; + } + switch(value) { + case UHF_C_PORT_CONNECTION: + case UHF_C_PORT_ENABLE: + case UHF_C_PORT_SUSPEND: + case UHF_C_PORT_OVER_CURRENT: + case UHF_C_PORT_RESET: + /* Enable RHSC interrupt if condition is cleared. */ + if ((OREAD4(sc, port) >> 16) == 0) + ohci_rhsc_able(sc, 1); + break; + default: + break; + } + break; + case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE): + if ((value & 0xff) != 0) { + err = USBD_IOERROR; + goto ret; + } + v = OREAD4(sc, OHCI_RH_DESCRIPTOR_A); + hubd = ohci_hubd; + hubd.bNbrPorts = sc->sc_noport; + USETW(hubd.wHubCharacteristics, + (v & OHCI_NPS ? UHD_PWR_NO_SWITCH : + v & OHCI_PSM ? UHD_PWR_GANGED : UHD_PWR_INDIVIDUAL) + /* XXX overcurrent */ + ); + hubd.bPwrOn2PwrGood = OHCI_GET_POTPGT(v); + v = OREAD4(sc, OHCI_RH_DESCRIPTOR_B); + for (i = 0, l = sc->sc_noport; l > 0; i++, l -= 8, v >>= 8) + hubd.DeviceRemovable[i++] = (u_int8_t)v; + hubd.bDescLength = USB_HUB_DESCRIPTOR_SIZE + i; + l = min(len, hubd.bDescLength); + totlen = l; + memcpy(buf, &hubd, l); + break; + case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE): + if (len != 4) { + err = USBD_IOERROR; + goto ret; + } + memset(buf, 0, len); /* ? XXX */ + totlen = len; + break; + case C(UR_GET_STATUS, UT_READ_CLASS_OTHER): + DPRINTFN(8,("ohci_root_ctrl_transfer: get port status i=%d\n", + index)); + if (index < 1 || index > sc->sc_noport) { + err = USBD_IOERROR; + goto ret; + } + if (len != 4) { + err = USBD_IOERROR; + goto ret; + } + v = OREAD4(sc, OHCI_RH_PORT_STATUS(index)); + DPRINTFN(8,("ohci_root_ctrl_transfer: port status=0x%04x\n", + v)); + USETW(ps.wPortStatus, v); + USETW(ps.wPortChange, v >> 16); + l = min(len, sizeof ps); + memcpy(buf, &ps, l); + totlen = l; + break; + case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE): + err = USBD_IOERROR; + goto ret; + case C(UR_SET_FEATURE, UT_WRITE_CLASS_DEVICE): + break; + case C(UR_SET_FEATURE, UT_WRITE_CLASS_OTHER): + if (index < 1 || index > sc->sc_noport) { + err = USBD_IOERROR; + goto ret; + } + port = OHCI_RH_PORT_STATUS(index); + switch(value) { + case UHF_PORT_ENABLE: + OWRITE4(sc, port, UPS_PORT_ENABLED); + break; + case UHF_PORT_SUSPEND: + OWRITE4(sc, port, UPS_SUSPEND); + break; + case UHF_PORT_RESET: + DPRINTFN(5,("ohci_root_ctrl_transfer: reset port %d\n", + index)); + OWRITE4(sc, port, UPS_RESET); + for (i = 0; i < 5; i++) { + usb_delay_ms(&sc->sc_bus, + USB_PORT_ROOT_RESET_DELAY); + if (sc->sc_dying) { + err = USBD_IOERROR; + goto ret; + } + if ((OREAD4(sc, port) & UPS_RESET) == 0) + break; + } + DPRINTFN(8,("ohci port %d reset, status = 0x%04x\n", + index, OREAD4(sc, port))); + break; + case UHF_PORT_POWER: + DPRINTFN(2,("ohci_root_ctrl_transfer: set port power " + "%d\n", index)); + OWRITE4(sc, port, UPS_PORT_POWER); + break; + default: + err = USBD_IOERROR; + goto ret; + } + break; + default: + err = USBD_IOERROR; + goto ret; + } + xfer->actlen = totlen; + err = USBD_NORMAL_COMPLETION; + ret: + xfer->status = err; + s = splusb(); + hacksync(xfer); /* XXX to compensate for usb_transfer_complete */ + usb_transfer_complete(xfer); + splx(s); + return (USBD_IN_PROGRESS); +} + +/* Abort a root control request. */ +static void +ohci_root_ctrl_abort(usbd_xfer_handle xfer) +{ + /* Nothing to do, all transfers are synchronous. */ +} + +/* Close the root pipe. */ +static void +ohci_root_ctrl_close(usbd_pipe_handle pipe) +{ + DPRINTF(("ohci_root_ctrl_close\n")); + /* Nothing to do. */ +} + +static usbd_status +ohci_root_intr_transfer(usbd_xfer_handle xfer) +{ + usbd_status err; + + /* Insert last in queue. */ + err = usb_insert_transfer(xfer); + if (err) + return (err); + + /* Pipe isn't running, start first */ + return (ohci_root_intr_start(STAILQ_FIRST(&xfer->pipe->queue))); +} + +static usbd_status +ohci_root_intr_start(usbd_xfer_handle xfer) +{ + usbd_pipe_handle pipe = xfer->pipe; + ohci_softc_t *sc = (ohci_softc_t *)pipe->device->bus; + + if (sc->sc_dying) + return (USBD_IOERROR); + + sc->sc_intrxfer = xfer; + + return (USBD_IN_PROGRESS); +} + +/* Abort a root interrupt request. */ +static void +ohci_root_intr_abort(usbd_xfer_handle xfer) +{ + int s; + + if (xfer->pipe->intrxfer == xfer) { + DPRINTF(("ohci_root_intr_abort: remove\n")); + xfer->pipe->intrxfer = NULL; + } + xfer->status = USBD_CANCELLED; + s = splusb(); + usb_transfer_complete(xfer); + splx(s); +} + +/* Close the root pipe. */ +static void +ohci_root_intr_close(usbd_pipe_handle pipe) +{ + ohci_softc_t *sc = (ohci_softc_t *)pipe->device->bus; + + DPRINTF(("ohci_root_intr_close\n")); + + sc->sc_intrxfer = NULL; +} + +/************************/ + +static usbd_status +ohci_device_ctrl_transfer(usbd_xfer_handle xfer) +{ + usbd_status err; + + /* Insert last in queue. */ + err = usb_insert_transfer(xfer); + if (err) + return (err); + + /* Pipe isn't running, start first */ + return (ohci_device_ctrl_start(STAILQ_FIRST(&xfer->pipe->queue))); +} + +static usbd_status +ohci_device_ctrl_start(usbd_xfer_handle xfer) +{ + ohci_softc_t *sc = (ohci_softc_t *)xfer->pipe->device->bus; + usbd_status err; + + if (sc->sc_dying) + return (USBD_IOERROR); + +#ifdef DIAGNOSTIC + if (!(xfer->rqflags & URQ_REQUEST)) { + /* XXX panic */ + printf("ohci_device_ctrl_transfer: not a request\n"); + return (USBD_INVAL); + } +#endif + + err = ohci_device_request(xfer); + if (err) + return (err); + + if (sc->sc_bus.use_polling) + ohci_waitintr(sc, xfer); + return (USBD_IN_PROGRESS); +} + +/* Abort a device control request. */ +static void +ohci_device_ctrl_abort(usbd_xfer_handle xfer) +{ + DPRINTF(("ohci_device_ctrl_abort: xfer=%p\n", xfer)); + ohci_abort_xfer(xfer, USBD_CANCELLED); +} + +/* Close a device control pipe. */ +static void +ohci_device_ctrl_close(usbd_pipe_handle pipe) +{ + struct ohci_pipe *opipe = (struct ohci_pipe *)pipe; + ohci_softc_t *sc = (ohci_softc_t *)pipe->device->bus; + + DPRINTF(("ohci_device_ctrl_close: pipe=%p\n", pipe)); + ohci_close_pipe(pipe, sc->sc_ctrl_head); + ohci_free_std(sc, opipe->tail.td); +} + +/************************/ + +static void +ohci_device_clear_toggle(usbd_pipe_handle pipe) +{ + struct ohci_pipe *opipe = (struct ohci_pipe *)pipe; + + opipe->sed->ed.ed_headp &= htole32(~OHCI_TOGGLECARRY); +} + +static void +ohci_noop(usbd_pipe_handle pipe) +{ +} + +static usbd_status +ohci_device_bulk_transfer(usbd_xfer_handle xfer) +{ + usbd_status err; + + /* Insert last in queue. */ + err = usb_insert_transfer(xfer); + if (err) + return (err); + + /* Pipe isn't running, start first */ + return (ohci_device_bulk_start(STAILQ_FIRST(&xfer->pipe->queue))); +} + +static usbd_status +ohci_device_bulk_start(usbd_xfer_handle xfer) +{ + struct ohci_pipe *opipe = (struct ohci_pipe *)xfer->pipe; + usbd_device_handle dev = opipe->pipe.device; + ohci_softc_t *sc = (ohci_softc_t *)dev->bus; + int addr = dev->address; + ohci_soft_td_t *data, *tail, *tdp; + ohci_soft_ed_t *sed; + int s, len, isread, endpt; + usbd_status err; + + if (sc->sc_dying) + return (USBD_IOERROR); + +#ifdef DIAGNOSTIC + if (xfer->rqflags & URQ_REQUEST) { + /* XXX panic */ + printf("ohci_device_bulk_start: a request\n"); + return (USBD_INVAL); + } +#endif + + len = xfer->length; + endpt = xfer->pipe->endpoint->edesc->bEndpointAddress; + isread = UE_GET_DIR(endpt) == UE_DIR_IN; + sed = opipe->sed; + + DPRINTFN(4,("ohci_device_bulk_start: xfer=%p len=%d isread=%d " + "flags=%d endpt=%d\n", xfer, len, isread, xfer->flags, + endpt)); + + opipe->u.bulk.isread = isread; + opipe->u.bulk.length = len; + + /* Update device address */ + sed->ed.ed_flags = htole32( + (le32toh(sed->ed.ed_flags) & ~OHCI_ED_ADDRMASK) | + OHCI_ED_SET_FA(addr)); + + /* Allocate a chain of new TDs (including a new tail). */ + data = opipe->tail.td; + err = ohci_alloc_std_chain(opipe, sc, len, isread, xfer, + data, &tail); + /* We want interrupt at the end of the transfer. */ + tail->td.td_flags &= htole32(~OHCI_TD_INTR_MASK); + tail->td.td_flags |= htole32(OHCI_TD_SET_DI(1)); + tail->flags |= OHCI_CALL_DONE; + tail = tail->nexttd; /* point at sentinel */ + if (err) + return (err); + + tail->xfer = NULL; + xfer->hcpriv = data; + + DPRINTFN(4,("ohci_device_bulk_start: ed_flags=0x%08x td_flags=0x%08x " + "td_cbp=0x%08x td_be=0x%08x\n", + (int)le32toh(sed->ed.ed_flags), + (int)le32toh(data->td.td_flags), + (int)le32toh(data->td.td_cbp), + (int)le32toh(data->td.td_be))); + +#ifdef USB_DEBUG + if (ohcidebug > 5) { + ohci_dump_ed(sed); + ohci_dump_tds(data); + } +#endif + + /* Insert ED in schedule */ + s = splusb(); + for (tdp = data; tdp != tail; tdp = tdp->nexttd) { + tdp->xfer = xfer; + } + sed->ed.ed_tailp = htole32(tail->physaddr); + opipe->tail.td = tail; + sed->ed.ed_flags &= htole32(~OHCI_ED_SKIP); + OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_BLF); + if (xfer->timeout && !sc->sc_bus.use_polling) { + callout_reset(&xfer->timeout_handle, MS_TO_TICKS(xfer->timeout), + ohci_timeout, xfer); + } + +#if 0 +/* This goes wrong if we are too slow. */ + if (ohcidebug > 10) { + delay(10000); + DPRINTF(("ohci_device_intr_transfer: status=%x\n", + OREAD4(sc, OHCI_COMMAND_STATUS))); + ohci_dump_ed(sed); + ohci_dump_tds(data); + } +#endif + + splx(s); + + if (sc->sc_bus.use_polling) + ohci_waitintr(sc, xfer); + + return (USBD_IN_PROGRESS); +} + +static void +ohci_device_bulk_abort(usbd_xfer_handle xfer) +{ + DPRINTF(("ohci_device_bulk_abort: xfer=%p\n", xfer)); + ohci_abort_xfer(xfer, USBD_CANCELLED); +} + +/* + * Close a device bulk pipe. + */ +static void +ohci_device_bulk_close(usbd_pipe_handle pipe) +{ + struct ohci_pipe *opipe = (struct ohci_pipe *)pipe; + ohci_softc_t *sc = (ohci_softc_t *)pipe->device->bus; + + DPRINTF(("ohci_device_bulk_close: pipe=%p\n", pipe)); + ohci_close_pipe(pipe, sc->sc_bulk_head); + ohci_free_std(sc, opipe->tail.td); +} + +/************************/ + +static usbd_status +ohci_device_intr_transfer(usbd_xfer_handle xfer) +{ + usbd_status err; + + /* Insert last in queue. */ + err = usb_insert_transfer(xfer); + if (err) + return (err); + + /* Pipe isn't running, start first */ + return (ohci_device_intr_start(STAILQ_FIRST(&xfer->pipe->queue))); +} + +static usbd_status +ohci_device_intr_start(usbd_xfer_handle xfer) +{ + struct ohci_pipe *opipe = (struct ohci_pipe *)xfer->pipe; + ohci_softc_t *sc = (ohci_softc_t *)opipe->pipe.device->bus; + ohci_soft_ed_t *sed = opipe->sed; + usbd_status err; + + if (sc->sc_dying) + return (USBD_IOERROR); + + DPRINTFN(3, ("ohci_device_intr_start: xfer=%p len=%d " + "flags=%d priv=%p\n", + xfer, xfer->length, xfer->flags, xfer->priv)); + +#ifdef DIAGNOSTIC + if (xfer->rqflags & URQ_REQUEST) + panic("ohci_device_intr_start: a request"); +#endif + + err = ohci_device_intr_insert(sc, xfer); + if (err) + return (err); + + sed->ed.ed_flags &= htole32(~OHCI_ED_SKIP); + + return (USBD_IN_PROGRESS); +} + +/* + * Insert an interrupt transfer into an endpoint descriptor list + */ +static usbd_status +ohci_device_intr_insert(ohci_softc_t *sc, usbd_xfer_handle xfer) +{ + struct ohci_pipe *opipe = (struct ohci_pipe *)xfer->pipe; + ohci_soft_ed_t *sed = opipe->sed; + ohci_soft_td_t *data, *tail; + ohci_physaddr_t dataphys, physend; + int s; + + DPRINTFN(4, ("ohci_device_intr_insert: xfer=%p", xfer)); + + data = opipe->tail.td; + tail = ohci_alloc_std(sc); + if (tail == NULL) + return (USBD_NOMEM); + tail->xfer = NULL; + + data->td.td_flags = htole32( + OHCI_TD_IN | OHCI_TD_NOCC | + OHCI_TD_SET_DI(1) | OHCI_TD_TOGGLE_CARRY); + if (xfer->flags & USBD_SHORT_XFER_OK) + data->td.td_flags |= htole32(OHCI_TD_R); + /* + * Assume a short mapping with no complications, which + * should always be true for <= 4k buffers in contiguous + * virtual memory. The data can take the following forms: + * 1 segment in 1 OHCI page + * 1 segment in 2 OHCI pages + * 2 segments in 2 OHCI pages + * (see comment in ohci_alloc_std_chain() for details) + */ + KASSERT(xfer->length > 0 && xfer->length <= OHCI_PAGE_SIZE, + ("ohci_device_intr_insert: bad length %d", xfer->length)); + dataphys = xfer->dmamap.segs[0].ds_addr; + physend = dataphys + xfer->length - 1; + if (xfer->dmamap.nsegs == 2) { + KASSERT(OHCI_PAGE_OFFSET(dataphys + + xfer->dmamap.segs[0].ds_len) == 0, + ("ohci_device_intr_insert: bad seg 0 termination")); + physend = xfer->dmamap.segs[1].ds_addr + xfer->length - + xfer->dmamap.segs[0].ds_len - 1; + } else { + KASSERT(xfer->dmamap.nsegs == 1, + ("ohci_device_intr_insert: bad seg count %d", + (u_int)xfer->dmamap.nsegs)); + } + data->td.td_cbp = htole32(dataphys); + data->nexttd = tail; + data->td.td_nexttd = htole32(tail->physaddr); + data->td.td_be = htole32(physend); + data->len = xfer->length; + data->xfer = xfer; + data->flags = OHCI_CALL_DONE | OHCI_ADD_LEN; + xfer->hcpriv = data; + xfer->actlen = 0; + +#ifdef USB_DEBUG + if (ohcidebug > 5) { + DPRINTF(("ohci_device_intr_insert:\n")); + ohci_dump_ed(sed); + ohci_dump_tds(data); + } +#endif + + /* Insert ED in schedule */ + s = splusb(); + sed->ed.ed_tailp = htole32(tail->physaddr); + opipe->tail.td = tail; + splx(s); + + return (USBD_NORMAL_COMPLETION); +} + +/* Abort a device control request. */ +static void +ohci_device_intr_abort(usbd_xfer_handle xfer) +{ + if (xfer->pipe->intrxfer == xfer) { + DPRINTF(("ohci_device_intr_abort: remove\n")); + xfer->pipe->intrxfer = NULL; + } + ohci_abort_xfer(xfer, USBD_CANCELLED); +} + +/* Close a device interrupt pipe. */ +static void +ohci_device_intr_close(usbd_pipe_handle pipe) +{ + struct ohci_pipe *opipe = (struct ohci_pipe *)pipe; + ohci_softc_t *sc = (ohci_softc_t *)pipe->device->bus; + int nslots = opipe->u.intr.nslots; + int pos = opipe->u.intr.pos; + int j; + ohci_soft_ed_t *p, *sed = opipe->sed; + int s; + + DPRINTFN(1,("ohci_device_intr_close: pipe=%p nslots=%d pos=%d\n", + pipe, nslots, pos)); + s = splusb(); + sed->ed.ed_flags |= htole32(OHCI_ED_SKIP); + if ((le32toh(sed->ed.ed_tailp) & OHCI_HEADMASK) != + (le32toh(sed->ed.ed_headp) & OHCI_HEADMASK)) + usb_delay_ms(&sc->sc_bus, 2); +#ifdef DIAGNOSTIC + if ((le32toh(sed->ed.ed_tailp) & OHCI_HEADMASK) != + (le32toh(sed->ed.ed_headp) & OHCI_HEADMASK)) + panic("%s: Intr pipe %p still has TDs queued", + device_get_nameunit(sc->sc_bus.bdev), pipe); +#endif + + for (p = sc->sc_eds[pos]; p && p->next != sed; p = p->next) + ; +#ifdef DIAGNOSTIC + if (p == NULL) + panic("ohci_device_intr_close: ED not found"); +#endif + p->next = sed->next; + p->ed.ed_nexted = sed->ed.ed_nexted; + splx(s); + + for (j = 0; j < nslots; j++) + --sc->sc_bws[(pos * nslots + j) % OHCI_NO_INTRS]; + + ohci_free_std(sc, opipe->tail.td); + ohci_free_sed(sc, opipe->sed); +} + +static usbd_status +ohci_device_setintr(ohci_softc_t *sc, struct ohci_pipe *opipe, int ival) +{ + int i, j, s, best; + u_int npoll, slow, shigh, nslots; + u_int bestbw, bw; + ohci_soft_ed_t *hsed, *sed = opipe->sed; + + DPRINTFN(2, ("ohci_setintr: pipe=%p\n", opipe)); + if (ival == 0) { + printf("ohci_setintr: 0 interval\n"); + return (USBD_INVAL); + } + + npoll = OHCI_NO_INTRS; + while (npoll > ival) + npoll /= 2; + DPRINTFN(2, ("ohci_setintr: ival=%d npoll=%d\n", ival, npoll)); + + /* + * We now know which level in the tree the ED must go into. + * Figure out which slot has most bandwidth left over. + * Slots to examine: + * npoll + * 1 0 + * 2 1 2 + * 4 3 4 5 6 + * 8 7 8 9 10 11 12 13 14 + * N (N-1) .. (N-1+N-1) + */ + slow = npoll-1; + shigh = slow + npoll; + nslots = OHCI_NO_INTRS / npoll; + for (best = i = slow, bestbw = ~0; i < shigh; i++) { + bw = 0; + for (j = 0; j < nslots; j++) + bw += sc->sc_bws[(i * nslots + j) % OHCI_NO_INTRS]; + if (bw < bestbw) { + best = i; + bestbw = bw; + } + } + DPRINTFN(2, ("ohci_setintr: best=%d(%d..%d) bestbw=%d\n", + best, slow, shigh, bestbw)); + + s = splusb(); + hsed = sc->sc_eds[best]; + sed->next = hsed->next; + sed->ed.ed_nexted = hsed->ed.ed_nexted; + hsed->next = sed; + hsed->ed.ed_nexted = htole32(sed->physaddr); + splx(s); + + for (j = 0; j < nslots; j++) + ++sc->sc_bws[(best * nslots + j) % OHCI_NO_INTRS]; + opipe->u.intr.nslots = nslots; + opipe->u.intr.pos = best; + + DPRINTFN(5, ("ohci_setintr: returns %p\n", opipe)); + return (USBD_NORMAL_COMPLETION); +} + +/***********************/ + +usbd_status +ohci_device_isoc_transfer(usbd_xfer_handle xfer) +{ + usbd_status err; + + DPRINTFN(5,("ohci_device_isoc_transfer: xfer=%p\n", xfer)); + + /* Put it on our queue, */ + err = usb_insert_transfer(xfer); + + /* bail out on error, */ + if (err && err != USBD_IN_PROGRESS) + return (err); + + /* XXX should check inuse here */ + + /* insert into schedule, */ + ohci_device_isoc_enter(xfer); + + /* and start if the pipe wasn't running */ + if (!err) + ohci_device_isoc_start(STAILQ_FIRST(&xfer->pipe->queue)); + + return (err); +} + +void +ohci_device_isoc_enter(usbd_xfer_handle xfer) +{ + struct ohci_pipe *opipe = (struct ohci_pipe *)xfer->pipe; + usbd_device_handle dev = opipe->pipe.device; + ohci_softc_t *sc = (ohci_softc_t *)dev->bus; + ohci_soft_ed_t *sed = opipe->sed; + struct iso *iso = &opipe->u.iso; + struct usb_dma_mapping *dma = &xfer->dmamap; + ohci_soft_itd_t *sitd, *nsitd; + ohci_physaddr_t dataphys, bp0, physend, prevpage; + int curlen, i, len, ncur, nframes, npages, seg, segoff; + int s; + + DPRINTFN(1,("ohci_device_isoc_enter: used=%d next=%d xfer=%p " + "nframes=%d\n", + iso->inuse, iso->next, xfer, xfer->nframes)); + + if (sc->sc_dying) + return; + + if (iso->next == -1) { + /* Not in use yet, schedule it a few frames ahead. */ + iso->next = le32toh(sc->sc_hcca->hcca_frame_number) + 5; + DPRINTFN(2,("ohci_device_isoc_enter: start next=%d\n", + iso->next)); + } + + sitd = opipe->tail.itd; + nframes = xfer->nframes; + xfer->hcpriv = sitd; + seg = 0; + segoff = 0; + i = 0; + while (i < nframes) { + /* + * Fill in as many ITD frames as possible. + */ + KASSERT(seg < dma->nsegs, ("ohci_device_isoc_enter: overrun")); + bp0 = dma->segs[seg].ds_addr + segoff; + sitd->itd.itd_bp0 = htole32(bp0); + prevpage = OHCI_PAGE(bp0); + npages = 1; + + ncur = 0; + while (ncur < OHCI_ITD_NOFFSET && i < nframes) { + /* Find the frame start and end physical addresses. */ + len = xfer->frlengths[i]; + dataphys = dma->segs[seg].ds_addr + segoff; + curlen = dma->segs[seg].ds_len - segoff; + if (len > curlen) { + KASSERT(seg + 1 < dma->nsegs, + ("ohci_device_isoc_enter: overrun2")); + seg++; + segoff = len - curlen; + } else { + segoff += len; + } + KASSERT(segoff <= dma->segs[seg].ds_len, + ("ohci_device_isoc_enter: overrun3")); + physend = dma->segs[seg].ds_addr + segoff - 1; + + /* Check if there would be more than 2 pages . */ + if (OHCI_PAGE(dataphys) != prevpage) { + prevpage = OHCI_PAGE(dataphys); + npages++; + } + if (OHCI_PAGE(physend) != prevpage) { + prevpage = OHCI_PAGE(physend); + npages++; + } + if (npages > 2) { + /* We cannot fit this frame now. */ + segoff -= len; + if (segoff < 0) { + seg--; + segoff += dma->segs[seg].ds_len; + } + break; + } + + sitd->itd.itd_be = htole32(physend); + sitd->itd.itd_offset[ncur] = + htole16(OHCI_ITD_MK_OFFS(OHCI_PAGE(dataphys) == + OHCI_PAGE(bp0) ? 0 : 1, dataphys)); + i++; + ncur++; + } + if (segoff >= dma->segs[seg].ds_len) { + KASSERT(segoff == dma->segs[seg].ds_len, + ("ohci_device_isoc_enter: overlap")); + seg++; + segoff = 0; + } + + /* Allocate next ITD */ + nsitd = ohci_alloc_sitd(sc); + if (nsitd == NULL) { + /* XXX what now? */ + printf("%s: isoc TD alloc failed\n", + device_get_nameunit(sc->sc_bus.bdev)); + return; + } + + /* Fill out remaining fields of current ITD */ + sitd->nextitd = nsitd; + sitd->itd.itd_nextitd = htole32(nsitd->physaddr); + sitd->xfer = xfer; + if (i < nframes) { + sitd->itd.itd_flags = htole32( + OHCI_ITD_NOCC | + OHCI_ITD_SET_SF(iso->next) | + OHCI_ITD_SET_DI(6) | /* delay intr a little */ + OHCI_ITD_SET_FC(ncur)); + sitd->flags = OHCI_ITD_ACTIVE; + } else { + sitd->itd.itd_flags = htole32( + OHCI_ITD_NOCC | + OHCI_ITD_SET_SF(iso->next) | + OHCI_ITD_SET_DI(0) | + OHCI_ITD_SET_FC(ncur)); + sitd->flags = OHCI_CALL_DONE | OHCI_ITD_ACTIVE; + } + iso->next += ncur; + + sitd = nsitd; + } + + iso->inuse += nframes; + + /* XXX pretend we did it all */ + xfer->actlen = 0; + for (i = 0; i < nframes; i++) + xfer->actlen += xfer->frlengths[i]; + + xfer->status = USBD_IN_PROGRESS; + +#ifdef USB_DEBUG + if (ohcidebug > 5) { + DPRINTF(("ohci_device_isoc_enter: frame=%d\n", + le32toh(sc->sc_hcca->hcca_frame_number))); + ohci_dump_itds(xfer->hcpriv); + ohci_dump_ed(sed); + } +#endif + + s = splusb(); + opipe->tail.itd = sitd; + sed->ed.ed_flags &= htole32(~OHCI_ED_SKIP); + sed->ed.ed_tailp = htole32(sitd->physaddr); + splx(s); + +#ifdef USB_DEBUG + if (ohcidebug > 5) { + delay(150000); + DPRINTF(("ohci_device_isoc_enter: after frame=%d\n", + le32toh(sc->sc_hcca->hcca_frame_number))); + ohci_dump_itds(xfer->hcpriv); + ohci_dump_ed(sed); + } +#endif +} + +usbd_status +ohci_device_isoc_start(usbd_xfer_handle xfer) +{ + struct ohci_pipe *opipe = (struct ohci_pipe *)xfer->pipe; + ohci_softc_t *sc = (ohci_softc_t *)opipe->pipe.device->bus; + ohci_soft_ed_t *sed; + int s; + + DPRINTFN(5,("ohci_device_isoc_start: xfer=%p\n", xfer)); + + if (sc->sc_dying) + return (USBD_IOERROR); + +#ifdef DIAGNOSTIC + if (xfer->status != USBD_IN_PROGRESS) + printf("ohci_device_isoc_start: not in progress %p\n", xfer); +#endif + + /* XXX anything to do? */ + + s = splusb(); + sed = opipe->sed; /* Turn off ED skip-bit to start processing */ + sed->ed.ed_flags &= htole32(~OHCI_ED_SKIP); /* ED's ITD list.*/ + splx(s); + + return (USBD_IN_PROGRESS); +} + +void +ohci_device_isoc_abort(usbd_xfer_handle xfer) +{ + struct ohci_pipe *opipe = (struct ohci_pipe *)xfer->pipe; + ohci_softc_t *sc = (ohci_softc_t *)opipe->pipe.device->bus; + ohci_soft_ed_t *sed; + ohci_soft_itd_t *sitd, *sitdnext, *tmp_sitd; + int s,undone,num_sitds; + + s = splusb(); + opipe->aborting = 1; + + DPRINTFN(1,("ohci_device_isoc_abort: xfer=%p\n", xfer)); + + /* Transfer is already done. */ + if (xfer->status != USBD_NOT_STARTED && + xfer->status != USBD_IN_PROGRESS) { + splx(s); + printf("ohci_device_isoc_abort: early return\n"); + return; + } + + /* Give xfer the requested abort code. */ + xfer->status = USBD_CANCELLED; + + sed = opipe->sed; + sed->ed.ed_flags |= htole32(OHCI_ED_SKIP); /* force hardware skip */ + + num_sitds = 0; + sitd = xfer->hcpriv; +#ifdef DIAGNOSTIC + if (sitd == NULL) { + splx(s); + printf("ohci_device_isoc_abort: hcpriv==0\n"); + return; + } +#endif + for (; sitd != NULL && sitd->xfer == xfer; sitd = sitd->nextitd) { + num_sitds++; +#ifdef DIAGNOSTIC + DPRINTFN(1,("abort sets done sitd=%p\n", sitd)); + sitd->isdone = 1; +#endif + } + + splx(s); + + /* + * Each sitd has up to OHCI_ITD_NOFFSET transfers, each can + * take a usb 1ms cycle. Conservatively wait for it to drain. + * Even with DMA done, it can take awhile for the "batch" + * delivery of completion interrupts to occur thru the controller. + */ + + do { + usb_delay_ms(&sc->sc_bus, 2*(num_sitds*OHCI_ITD_NOFFSET)); + + undone = 0; + tmp_sitd = xfer->hcpriv; + for (; tmp_sitd != NULL && tmp_sitd->xfer == xfer; + tmp_sitd = tmp_sitd->nextitd) { + if (OHCI_CC_NO_ERROR == + OHCI_ITD_GET_CC(le32toh(tmp_sitd->itd.itd_flags)) && + tmp_sitd->flags & OHCI_ITD_ACTIVE && + (tmp_sitd->flags & OHCI_ITD_INTFIN) == 0) + undone++; + } + } while( undone != 0 ); + + /* Free the sitds */ + for (sitd = xfer->hcpriv; sitd->xfer == xfer; + sitd = sitdnext) { + sitdnext = sitd->nextitd; + ohci_free_sitd(sc, sitd); + } + + s = splusb(); + + /* Run callback. */ + usb_transfer_complete(xfer); + + /* There is always a `next' sitd so link it up. */ + sed->ed.ed_headp = htole32(sitd->physaddr); + + sed->ed.ed_flags &= htole32(~OHCI_ED_SKIP); /* remove hardware skip */ + + splx(s); +} + +void +ohci_device_isoc_done(usbd_xfer_handle xfer) +{ + /* This null routine corresponds to non-isoc "done()" routines + * that free the stds associated with an xfer after a completed + * xfer interrupt. However, in the case of isoc transfers, the + * sitds associated with the transfer have already been processed + * and reallocated for the next iteration by + * "ohci_device_isoc_transfer()". + * + * Routine "usb_transfer_complete()" is called at the end of every + * relevant usb interrupt. "usb_transfer_complete()" indirectly + * calls 1) "ohci_device_isoc_transfer()" (which keeps pumping the + * pipeline by setting up the next transfer iteration) and 2) then + * calls "ohci_device_isoc_done()". Isoc transfers have not been + * working for the ohci usb because this routine was trashing the + * xfer set up for the next iteration (thus, only the first + * UGEN_NISOREQS xfers outstanding on an open would work). Perhaps + * this could all be re-factored, but that's another pass... + */ +} + +usbd_status +ohci_setup_isoc(usbd_pipe_handle pipe) +{ + struct ohci_pipe *opipe = (struct ohci_pipe *)pipe; + ohci_softc_t *sc = (ohci_softc_t *)pipe->device->bus; + struct iso *iso = &opipe->u.iso; + int s; + + iso->next = -1; + iso->inuse = 0; + + s = splusb(); + ohci_add_ed(opipe->sed, sc->sc_isoc_head); + splx(s); + + return (USBD_NORMAL_COMPLETION); +} + +void +ohci_device_isoc_close(usbd_pipe_handle pipe) +{ + struct ohci_pipe *opipe = (struct ohci_pipe *)pipe; + ohci_softc_t *sc = (ohci_softc_t *)pipe->device->bus; + ohci_soft_ed_t *sed; + + DPRINTF(("ohci_device_isoc_close: pipe=%p\n", pipe)); + + sed = opipe->sed; + sed->ed.ed_flags |= htole32(OHCI_ED_SKIP); /* Stop device. */ + + ohci_close_pipe(pipe, sc->sc_isoc_head); /* Stop isoc list, free ED.*/ + + /* up to NISOREQs xfers still outstanding. */ + +#ifdef DIAGNOSTIC + opipe->tail.itd->isdone = 1; +#endif + ohci_free_sitd(sc, opipe->tail.itd); /* Next `avail free' sitd.*/ +} diff --git a/sys/legacy/dev/usb/ohci_pci.c b/sys/legacy/dev/usb/ohci_pci.c new file mode 100644 index 0000000..fadffb6 --- /dev/null +++ b/sys/legacy/dev/usb/ohci_pci.c @@ -0,0 +1,411 @@ +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (augustss@carlstedt.se) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * USB Open Host Controller driver. + * + * OHCI spec: http://www.intel.com/design/usb/ohci11d.pdf + */ + +/* The low level controller code for OHCI has been split into + * PCI probes and OHCI specific code. This was done to facilitate the + * sharing of code between *BSD's + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/bus.h> +#include <sys/queue.h> +#include <machine/bus.h> +#include <sys/rman.h> +#include <machine/resource.h> + +#include <dev/pci/pcivar.h> +#include <dev/pci/pcireg.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdivar.h> +#include <dev/usb/usb_mem.h> + +#include <dev/usb/ohcireg.h> +#include <dev/usb/ohcivar.h> + +#define PCI_OHCI_VENDORID_ACERLABS 0x10b9 +#define PCI_OHCI_VENDORID_AMD 0x1022 +#define PCI_OHCI_VENDORID_APPLE 0x106b +#define PCI_OHCI_VENDORID_ATI 0x1002 +#define PCI_OHCI_VENDORID_CMDTECH 0x1095 +#define PCI_OHCI_VENDORID_NEC 0x1033 +#define PCI_OHCI_VENDORID_NVIDIA 0x12D2 +#define PCI_OHCI_VENDORID_NVIDIA2 0x10DE +#define PCI_OHCI_VENDORID_OPTI 0x1045 +#define PCI_OHCI_VENDORID_SIS 0x1039 +#define PCI_OHCI_VENDORID_SUN 0x108e + +#define PCI_OHCI_DEVICEID_ALADDIN_V 0x523710b9 +static const char *ohci_device_aladdin_v = "AcerLabs M5237 (Aladdin-V) USB controller"; + +#define PCI_OHCI_DEVICEID_AMD756 0x740c1022 +static const char *ohci_device_amd756 = "AMD-756 USB Controller"; + +#define PCI_OHCI_DEVICEID_AMD766 0x74141022 +static const char *ohci_device_amd766 = "AMD-766 USB Controller"; + +#define PCI_OHCI_DEVICEID_SB400_1 0x43741002 +#define PCI_OHCI_DEVICEID_SB400_2 0x43751002 +static const char *ohci_device_sb400 = "ATI SB400 USB Controller"; + +#define PCI_OHCI_DEVICEID_FIRELINK 0xc8611045 +static const char *ohci_device_firelink = "OPTi 82C861 (FireLink) USB controller"; + +#define PCI_OHCI_DEVICEID_NEC 0x00351033 +static const char *ohci_device_nec = "NEC uPD 9210 USB controller"; + +#define PCI_OHCI_DEVICEID_NFORCE3 0x00d710de +static const char *ohci_device_nforce3 = "nVidia nForce3 USB Controller"; + +#define PCI_OHCI_DEVICEID_USB0670 0x06701095 +static const char *ohci_device_usb0670 = "CMD Tech 670 (USB0670) USB controller"; + +#define PCI_OHCI_DEVICEID_USB0673 0x06731095 +static const char *ohci_device_usb0673 = "CMD Tech 673 (USB0673) USB controller"; + +#define PCI_OHCI_DEVICEID_SIS5571 0x70011039 +static const char *ohci_device_sis5571 = "SiS 5571 USB controller"; + +#define PCI_OHCI_DEVICEID_KEYLARGO 0x0019106b +static const char *ohci_device_keylargo = "Apple KeyLargo USB controller"; + +#define PCI_OHCI_DEVICEID_PCIO2USB 0x1103108e +static const char *ohci_device_pcio2usb = "Sun PCIO-2 USB controller"; + +static const char *ohci_device_generic = "OHCI (generic) USB controller"; + +#define PCI_OHCI_BASE_REG 0x10 + + +static device_attach_t ohci_pci_attach; +static device_detach_t ohci_pci_detach; +static device_suspend_t ohci_pci_suspend; +static device_resume_t ohci_pci_resume; + +static int +ohci_pci_suspend(device_t self) +{ + ohci_softc_t *sc = device_get_softc(self); + int err; + + err = bus_generic_suspend(self); + if (err) + return err; + ohci_power(PWR_SUSPEND, sc); + + return 0; +} + +static int +ohci_pci_resume(device_t self) +{ + ohci_softc_t *sc = device_get_softc(self); + + ohci_power(PWR_RESUME, sc); + bus_generic_resume(self); + + return 0; +} + +static const char * +ohci_pci_match(device_t self) +{ + u_int32_t device_id = pci_get_devid(self); + + switch (device_id) { + case PCI_OHCI_DEVICEID_ALADDIN_V: + return (ohci_device_aladdin_v); + case PCI_OHCI_DEVICEID_AMD756: + return (ohci_device_amd756); + case PCI_OHCI_DEVICEID_AMD766: + return (ohci_device_amd766); + case PCI_OHCI_DEVICEID_SB400_1: + case PCI_OHCI_DEVICEID_SB400_2: + return (ohci_device_sb400); + case PCI_OHCI_DEVICEID_USB0670: + return (ohci_device_usb0670); + case PCI_OHCI_DEVICEID_USB0673: + return (ohci_device_usb0673); + case PCI_OHCI_DEVICEID_FIRELINK: + return (ohci_device_firelink); + case PCI_OHCI_DEVICEID_NEC: + return (ohci_device_nec); + case PCI_OHCI_DEVICEID_NFORCE3: + return (ohci_device_nforce3); + case PCI_OHCI_DEVICEID_SIS5571: + return (ohci_device_sis5571); + case PCI_OHCI_DEVICEID_KEYLARGO: + return (ohci_device_keylargo); + case PCI_OHCI_DEVICEID_PCIO2USB: + return (ohci_device_pcio2usb); + default: + if (pci_get_class(self) == PCIC_SERIALBUS + && pci_get_subclass(self) == PCIS_SERIALBUS_USB + && pci_get_progif(self) == PCI_INTERFACE_OHCI) { + return (ohci_device_generic); + } + } + + return NULL; /* dunno */ +} + +static int +ohci_pci_probe(device_t self) +{ + const char *desc = ohci_pci_match(self); + + if (desc) { + device_set_desc(self, desc); + return BUS_PROBE_DEFAULT; + } else { + return ENXIO; + } +} + +static int +ohci_pci_attach(device_t self) +{ + ohci_softc_t *sc = device_get_softc(self); + int err; + int rid; + + /* XXX where does it say so in the spec? */ + sc->sc_bus.usbrev = USBREV_1_0; + + pci_enable_busmaster(self); + + /* + * Some Sun PCIO-2 USB controllers have their intpin register + * bogusly set to 0, although it should be 4. Correct that. + */ + if (pci_get_devid(self) == PCI_OHCI_DEVICEID_PCIO2USB && + pci_get_intpin(self) == 0) + pci_set_intpin(self, 4); + + rid = PCI_CBMEM; + sc->io_res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid, + RF_ACTIVE); + if (!sc->io_res) { + device_printf(self, "Could not map memory\n"); + return ENXIO; + } + sc->iot = rman_get_bustag(sc->io_res); + sc->ioh = rman_get_bushandle(sc->io_res); + + rid = 0; + sc->irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid, + RF_SHAREABLE | RF_ACTIVE); + if (sc->irq_res == NULL) { + device_printf(self, "Could not allocate irq\n"); + ohci_pci_detach(self); + return ENXIO; + } + sc->sc_bus.bdev = device_add_child(self, "usb", -1); + if (!sc->sc_bus.bdev) { + device_printf(self, "Could not add USB device\n"); + ohci_pci_detach(self); + return ENOMEM; + } + device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); + + /* ohci_pci_match will never return NULL if ohci_pci_probe succeeded */ + device_set_desc(sc->sc_bus.bdev, ohci_pci_match(self)); + switch (pci_get_vendor(self)) { + case PCI_OHCI_VENDORID_ACERLABS: + sprintf(sc->sc_vendor, "AcerLabs"); + break; + case PCI_OHCI_VENDORID_AMD: + sprintf(sc->sc_vendor, "AMD"); + break; + case PCI_OHCI_VENDORID_APPLE: + sprintf(sc->sc_vendor, "Apple"); + break; + case PCI_OHCI_VENDORID_ATI: + sprintf(sc->sc_vendor, "ATI"); + break; + case PCI_OHCI_VENDORID_CMDTECH: + sprintf(sc->sc_vendor, "CMDTECH"); + break; + case PCI_OHCI_VENDORID_NEC: + sprintf(sc->sc_vendor, "NEC"); + break; + case PCI_OHCI_VENDORID_NVIDIA: + case PCI_OHCI_VENDORID_NVIDIA2: + sprintf(sc->sc_vendor, "nVidia"); + break; + case PCI_OHCI_VENDORID_OPTI: + sprintf(sc->sc_vendor, "OPTi"); + break; + case PCI_OHCI_VENDORID_SIS: + sprintf(sc->sc_vendor, "SiS"); + break; + default: + if (bootverbose) + device_printf(self, "(New OHCI DeviceId=0x%08x)\n", + pci_get_devid(self)); + sprintf(sc->sc_vendor, "(0x%04x)", pci_get_vendor(self)); + } + + err = bus_setup_intr(self, sc->irq_res, INTR_TYPE_BIO, NULL, ohci_intr, + sc, &sc->ih); + if (err) { + device_printf(self, "Could not setup irq, %d\n", err); + sc->ih = NULL; + ohci_pci_detach(self); + return ENXIO; + } + + /* Allocate a parent dma tag for DMA maps */ + err = bus_dma_tag_create(bus_get_dma_tag(self), 1, 0, + BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, + BUS_SPACE_MAXSIZE_32BIT, USB_DMA_NSEG, BUS_SPACE_MAXSIZE_32BIT, 0, + NULL, NULL, &sc->sc_bus.parent_dmatag); + if (err) { + device_printf(self, "Could not allocate parent DMA tag (%d)\n", + err); + ohci_pci_detach(self); + return ENXIO; + } + /* Allocate a dma tag for transfer buffers */ + err = bus_dma_tag_create(sc->sc_bus.parent_dmatag, 1, 0, + BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, + BUS_SPACE_MAXSIZE_32BIT, USB_DMA_NSEG, BUS_SPACE_MAXSIZE_32BIT, 0, + busdma_lock_mutex, &Giant, &sc->sc_bus.buffer_dmatag); + if (err) { + device_printf(self, "Could not allocate transfer tag (%d)\n", + err); + ohci_pci_detach(self); + return ENXIO; + } + + err = ohci_init(sc); + if (!err) { + sc->sc_flags |= OHCI_SCFLG_DONEINIT; + err = device_probe_and_attach(sc->sc_bus.bdev); + } + + if (err) { + device_printf(self, "USB init failed\n"); + ohci_pci_detach(self); + return EIO; + } + return 0; +} + +static int +ohci_pci_detach(device_t self) +{ + ohci_softc_t *sc = device_get_softc(self); + + if (sc->sc_flags & OHCI_SCFLG_DONEINIT) { + ohci_detach(sc, 0); + sc->sc_flags &= ~OHCI_SCFLG_DONEINIT; + } + + if (sc->sc_bus.parent_dmatag != NULL) + bus_dma_tag_destroy(sc->sc_bus.parent_dmatag); + if (sc->sc_bus.buffer_dmatag != NULL) + bus_dma_tag_destroy(sc->sc_bus.buffer_dmatag); + + if (sc->irq_res && sc->ih) { + int err = bus_teardown_intr(self, sc->irq_res, sc->ih); + + if (err) + /* XXX or should we panic? */ + device_printf(self, "Could not tear down irq, %d\n", + err); + sc->ih = NULL; + } + if (sc->sc_bus.bdev) { + device_delete_child(self, sc->sc_bus.bdev); + sc->sc_bus.bdev = NULL; + } + if (sc->irq_res) { + bus_release_resource(self, SYS_RES_IRQ, 0, sc->irq_res); + sc->irq_res = NULL; + } + if (sc->io_res) { + bus_release_resource(self, SYS_RES_MEMORY, PCI_CBMEM, + sc->io_res); + sc->io_res = NULL; + sc->iot = 0; + sc->ioh = 0; + } + return 0; +} + +static device_method_t ohci_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ohci_pci_probe), + DEVMETHOD(device_attach, ohci_pci_attach), + DEVMETHOD(device_detach, ohci_pci_detach), + DEVMETHOD(device_suspend, ohci_pci_suspend), + DEVMETHOD(device_resume, ohci_pci_resume), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + + /* Bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + + {0, 0} +}; + +static driver_t ohci_driver = { + "ohci", + ohci_methods, + sizeof(ohci_softc_t), +}; + +static devclass_t ohci_devclass; + +DRIVER_MODULE(ohci, pci, ohci_driver, ohci_devclass, 0, 0); +DRIVER_MODULE(ohci, cardbus, ohci_driver, ohci_devclass, 0, 0); +MODULE_DEPEND(ohci, usb, 1, 1, 1); diff --git a/sys/legacy/dev/usb/ohcireg.h b/sys/legacy/dev/usb/ohcireg.h new file mode 100644 index 0000000..429beb8 --- /dev/null +++ b/sys/legacy/dev/usb/ohcireg.h @@ -0,0 +1,250 @@ +/* $NetBSD: ohcireg.h,v 1.17 2000/04/01 09:27:35 augustss Exp $ */ +/* $FreeBSD$ */ + + +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _DEV_PCI_OHCIREG_H_ +#define _DEV_PCI_OHCIREG_H_ + +/*** PCI config registers ***/ + +#define PCI_CBMEM 0x10 /* configuration base memory */ + +#define PCI_INTERFACE_OHCI 0x10 + +/*** OHCI registers */ + +#define OHCI_REVISION 0x00 /* OHCI revision # */ +#define OHCI_REV_LO(rev) ((rev)&0xf) +#define OHCI_REV_HI(rev) (((rev)>>4)&0xf) +#define OHCI_REV_LEGACY(rev) ((rev) & 0x100) + +#define OHCI_CONTROL 0x04 +#define OHCI_CBSR_MASK 0x00000003 /* Control/Bulk Service Ratio */ +#define OHCI_RATIO_1_1 0x00000000 +#define OHCI_RATIO_1_2 0x00000001 +#define OHCI_RATIO_1_3 0x00000002 +#define OHCI_RATIO_1_4 0x00000003 +#define OHCI_PLE 0x00000004 /* Periodic List Enable */ +#define OHCI_IE 0x00000008 /* Isochronous Enable */ +#define OHCI_CLE 0x00000010 /* Control List Enable */ +#define OHCI_BLE 0x00000020 /* Bulk List Enable */ +#define OHCI_HCFS_MASK 0x000000c0 /* HostControllerFunctionalState */ +#define OHCI_HCFS_RESET 0x00000000 +#define OHCI_HCFS_RESUME 0x00000040 +#define OHCI_HCFS_OPERATIONAL 0x00000080 +#define OHCI_HCFS_SUSPEND 0x000000c0 +#define OHCI_IR 0x00000100 /* Interrupt Routing */ +#define OHCI_RWC 0x00000200 /* Remote Wakeup Connected */ +#define OHCI_RWE 0x00000400 /* Remote Wakeup Enabled */ +#define OHCI_COMMAND_STATUS 0x08 +#define OHCI_HCR 0x00000001 /* Host Controller Reset */ +#define OHCI_CLF 0x00000002 /* Control List Filled */ +#define OHCI_BLF 0x00000004 /* Bulk List Filled */ +#define OHCI_OCR 0x00000008 /* Ownership Change Request */ +#define OHCI_SOC_MASK 0x00030000 /* Scheduling Overrun Count */ +#define OHCI_INTERRUPT_STATUS 0x0c +#define OHCI_SO 0x00000001 /* Scheduling Overrun */ +#define OHCI_WDH 0x00000002 /* Writeback Done Head */ +#define OHCI_SF 0x00000004 /* Start of Frame */ +#define OHCI_RD 0x00000008 /* Resume Detected */ +#define OHCI_UE 0x00000010 /* Unrecoverable Error */ +#define OHCI_FNO 0x00000020 /* Frame Number Overflow */ +#define OHCI_RHSC 0x00000040 /* Root Hub Status Change */ +#define OHCI_OC 0x40000000 /* Ownership Change */ +#define OHCI_MIE 0x80000000 /* Master Interrupt Enable */ +#define OHCI_INTERRUPT_ENABLE 0x10 +#define OHCI_INTERRUPT_DISABLE 0x14 +#define OHCI_HCCA 0x18 +#define OHCI_PERIOD_CURRENT_ED 0x1c +#define OHCI_CONTROL_HEAD_ED 0x20 +#define OHCI_CONTROL_CURRENT_ED 0x24 +#define OHCI_BULK_HEAD_ED 0x28 +#define OHCI_BULK_CURRENT_ED 0x2c +#define OHCI_DONE_HEAD 0x30 +#define OHCI_FM_INTERVAL 0x34 +#define OHCI_GET_IVAL(s) ((s) & 0x3fff) +#define OHCI_GET_FSMPS(s) (((s) >> 16) & 0x7fff) +#define OHCI_FIT 0x80000000 +#define OHCI_FM_REMAINING 0x38 +#define OHCI_FM_NUMBER 0x3c +#define OHCI_PERIODIC_START 0x40 +#define OHCI_LS_THRESHOLD 0x44 +#define OHCI_RH_DESCRIPTOR_A 0x48 +#define OHCI_GET_NDP(s) ((s) & 0xff) +#define OHCI_PSM 0x0100 /* Power Switching Mode */ +#define OHCI_NPS 0x0200 /* No Power Switching */ +#define OHCI_DT 0x0400 /* Device Type */ +#define OHCI_OCPM 0x0800 /* Overcurrent Protection Mode */ +#define OHCI_NOCP 0x1000 /* No Overcurrent Protection */ +#define OHCI_GET_POTPGT(s) ((s) >> 24) +#define OHCI_RH_DESCRIPTOR_B 0x4c +#define OHCI_RH_STATUS 0x50 +#define OHCI_LPS 0x00000001 /* Local Power Status */ +#define OHCI_OCI 0x00000002 /* OverCurrent Indicator */ +#define OHCI_DRWE 0x00008000 /* Device Remote Wakeup Enable */ +#define OHCI_LPSC 0x00010000 /* Local Power Status Change */ +#define OHCI_CCIC 0x00020000 /* OverCurrent Indicator Change */ +#define OHCI_CRWE 0x80000000 /* Clear Remote Wakeup Enable */ +#define OHCI_RH_PORT_STATUS(n) (0x50 + (n)*4) /* 1 based indexing */ + +#define OHCI_LES (OHCI_PLE | OHCI_IE | OHCI_CLE | OHCI_BLE) +#define OHCI_ALL_INTRS (OHCI_SO | OHCI_WDH | OHCI_SF | OHCI_RD | OHCI_UE | \ + OHCI_FNO | OHCI_RHSC | OHCI_OC) +#define OHCI_NORMAL_INTRS (OHCI_WDH | OHCI_RD | OHCI_UE | OHCI_RHSC) + +#define OHCI_FSMPS(i) (((i-210)*6/7) << 16) +#define OHCI_PERIODIC(i) ((i)*9/10) + +typedef u_int32_t ohci_physaddr_t; + +#define OHCI_NO_INTRS 32 +struct ohci_hcca { + ohci_physaddr_t hcca_interrupt_table[OHCI_NO_INTRS]; + u_int32_t hcca_frame_number; + ohci_physaddr_t hcca_done_head; +#define OHCI_DONE_INTRS 1 +}; +#define OHCI_HCCA_SIZE 256 +#define OHCI_HCCA_ALIGN 256 + +#define OHCI_PAGE_SIZE 0x1000 +#define OHCI_PAGE(x) ((x) &~ 0xfff) +#define OHCI_PAGE_OFFSET(x) ((x) & 0xfff) +#define OHCI_PAGE_MASK(x) ((x) & 0xfff) + +typedef struct { + u_int32_t ed_flags; +#define OHCI_ED_GET_FA(s) ((s) & 0x7f) +#define OHCI_ED_ADDRMASK 0x0000007f +#define OHCI_ED_SET_FA(s) (s) +#define OHCI_ED_GET_EN(s) (((s) >> 7) & 0xf) +#define OHCI_ED_SET_EN(s) ((s) << 7) +#define OHCI_ED_DIR_MASK 0x00001800 +#define OHCI_ED_DIR_TD 0x00000000 +#define OHCI_ED_DIR_OUT 0x00000800 +#define OHCI_ED_DIR_IN 0x00001000 +#define OHCI_ED_SPEED 0x00002000 +#define OHCI_ED_SKIP 0x00004000 +#define OHCI_ED_FORMAT_GEN 0x00000000 +#define OHCI_ED_FORMAT_ISO 0x00008000 +#define OHCI_ED_GET_MAXP(s) (((s) >> 16) & 0x07ff) +#define OHCI_ED_SET_MAXP(s) ((s) << 16) +#define OHCI_ED_MAXPMASK (0x7ff << 16) + ohci_physaddr_t ed_tailp; + ohci_physaddr_t ed_headp; +#define OHCI_HALTED 0x00000001 +#define OHCI_TOGGLECARRY 0x00000002 +#define OHCI_HEADMASK 0xfffffffc + ohci_physaddr_t ed_nexted; +} ohci_ed_t; +/* #define OHCI_ED_SIZE 16 */ +#define OHCI_ED_ALIGN 16 + +typedef struct { + u_int32_t td_flags; +#define OHCI_TD_R 0x00040000 /* Buffer Rounding */ +#define OHCI_TD_DP_MASK 0x00180000 /* Direction / PID */ +#define OHCI_TD_SETUP 0x00000000 +#define OHCI_TD_OUT 0x00080000 +#define OHCI_TD_IN 0x00100000 +#define OHCI_TD_GET_DI(x) (((x) >> 21) & 7) /* Delay Interrupt */ +#define OHCI_TD_SET_DI(x) ((x) << 21) +#define OHCI_TD_NOINTR 0x00e00000 +#define OHCI_TD_INTR_MASK 0x00e00000 +#define OHCI_TD_TOGGLE_CARRY 0x00000000 +#define OHCI_TD_TOGGLE_0 0x02000000 +#define OHCI_TD_TOGGLE_1 0x03000000 +#define OHCI_TD_TOGGLE_MASK 0x03000000 +#define OHCI_TD_GET_EC(x) (((x) >> 26) & 3) /* Error Count */ +#define OHCI_TD_GET_CC(x) ((x) >> 28) /* Condition Code */ +#define OHCI_TD_NOCC 0xf0000000 + ohci_physaddr_t td_cbp; /* Current Buffer Pointer */ + ohci_physaddr_t td_nexttd; /* Next TD */ + ohci_physaddr_t td_be; /* Buffer End */ +} ohci_td_t; +/* #define OHCI_TD_SIZE 16 */ +#define OHCI_TD_ALIGN 16 + +#define OHCI_ITD_NOFFSET 8 +typedef struct { + u_int32_t itd_flags; +#define OHCI_ITD_GET_SF(x) ((x) & 0x0000ffff) +#define OHCI_ITD_SET_SF(x) ((x) & 0xffff) +#define OHCI_ITD_GET_DI(x) (((x) >> 21) & 7) /* Delay Interrupt */ +#define OHCI_ITD_SET_DI(x) ((x) << 21) +#define OHCI_ITD_NOINTR 0x00e00000 +#define OHCI_ITD_GET_FC(x) ((((x) >> 24) & 7)+1) /* Frame Count */ +#define OHCI_ITD_SET_FC(x) (((x)-1) << 24) +#define OHCI_ITD_GET_CC(x) ((x) >> 28) /* Condition Code */ +#define OHCI_ITD_NOCC 0xf0000000 + ohci_physaddr_t itd_bp0; /* Buffer Page 0 */ + ohci_physaddr_t itd_nextitd; /* Next ITD */ + ohci_physaddr_t itd_be; /* Buffer End */ + u_int16_t itd_offset[OHCI_ITD_NOFFSET]; /* Buffer offsets */ +#define itd_pswn itd_offset /* Packet Status Word*/ +#define OHCI_ITD_PAGE_SELECT 0x00001000 +#define OHCI_ITD_MK_OFFS(page, off) \ + (0xe000 | ((page) ? OHCI_ITD_PAGE_SELECT : 0) | ((off) & 0xfff)) +#define OHCI_ITD_PSW_LENGTH(x) ((x) & 0xfff) /* Transfer length */ +#define OHCI_ITD_PSW_GET_CC(x) ((x) >> 12) /* Condition Code */ +} ohci_itd_t; +/* #define OHCI_ITD_SIZE 32 */ +#define OHCI_ITD_ALIGN 32 + + +#define OHCI_CC_NO_ERROR 0 +#define OHCI_CC_CRC 1 +#define OHCI_CC_BIT_STUFFING 2 +#define OHCI_CC_DATA_TOGGLE_MISMATCH 3 +#define OHCI_CC_STALL 4 +#define OHCI_CC_DEVICE_NOT_RESPONDING 5 +#define OHCI_CC_PID_CHECK_FAILURE 6 +#define OHCI_CC_UNEXPECTED_PID 7 +#define OHCI_CC_DATA_OVERRUN 8 +#define OHCI_CC_DATA_UNDERRUN 9 +#define OHCI_CC_BUFFER_OVERRUN 12 +#define OHCI_CC_BUFFER_UNDERRUN 13 +#define OHCI_CC_NOT_ACCESSED 15 + +/* Some delay needed when changing certain registers. */ +#define OHCI_ENABLE_POWER_DELAY 5 +#define OHCI_READ_DESC_DELAY 5 + +#endif /* _DEV_PCI_OHCIREG_H_ */ diff --git a/sys/legacy/dev/usb/ohcivar.h b/sys/legacy/dev/usb/ohcivar.h new file mode 100644 index 0000000..48f99e7 --- /dev/null +++ b/sys/legacy/dev/usb/ohcivar.h @@ -0,0 +1,164 @@ +/* $NetBSD: ohcivar.h,v 1.30 2001/12/31 12:20:35 augustss Exp $ */ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +typedef struct ohci_soft_ed { + ohci_ed_t ed; + struct ohci_soft_ed *next; + ohci_physaddr_t physaddr; +} ohci_soft_ed_t; +#define OHCI_SED_SIZE ((sizeof (struct ohci_soft_ed) + OHCI_ED_ALIGN - 1) / OHCI_ED_ALIGN * OHCI_ED_ALIGN) +#define OHCI_SED_CHUNK (PAGE_SIZE / OHCI_SED_SIZE) + +typedef struct ohci_soft_td { + ohci_td_t td; + struct ohci_soft_td *nexttd; /* mirrors nexttd in TD */ + struct ohci_soft_td *dnext; /* next in done list */ + ohci_physaddr_t physaddr; + LIST_ENTRY(ohci_soft_td) hnext; + usbd_xfer_handle xfer; + u_int16_t len; + u_int16_t flags; +#define OHCI_CALL_DONE 0x0001 +#define OHCI_ADD_LEN 0x0002 +} ohci_soft_td_t; +#define OHCI_STD_SIZE ((sizeof (struct ohci_soft_td) + OHCI_TD_ALIGN - 1) / OHCI_TD_ALIGN * OHCI_TD_ALIGN) +#define OHCI_STD_CHUNK (PAGE_SIZE / OHCI_STD_SIZE) + +typedef struct ohci_soft_itd { + ohci_itd_t itd; + struct ohci_soft_itd *nextitd; /* mirrors nexttd in ITD */ + struct ohci_soft_itd *dnext; /* next in done list */ + ohci_physaddr_t physaddr; + LIST_ENTRY(ohci_soft_itd) hnext; + usbd_xfer_handle xfer; + u_int16_t flags; +#define OHCI_ITD_ACTIVE 0x0010 /* Hardware op in progress */ +#define OHCI_ITD_INTFIN 0x0020 /* Hw completion interrupt seen.*/ +#ifdef DIAGNOSTIC + char isdone; +#endif +} ohci_soft_itd_t; +#define OHCI_SITD_SIZE ((sizeof (struct ohci_soft_itd) + OHCI_ITD_ALIGN - 1) / OHCI_ITD_ALIGN * OHCI_ITD_ALIGN) +#define OHCI_SITD_CHUNK (PAGE_SIZE / OHCI_SITD_SIZE) + +#define OHCI_NO_EDS (2*OHCI_NO_INTRS-1) + +#define OHCI_HASH_SIZE 128 + +#define OHCI_SCFLG_DONEINIT 0x0001 /* ohci_init() done. */ + +typedef struct ohci_softc { + struct usbd_bus sc_bus; /* base device */ + int sc_flags; + bus_space_tag_t iot; + bus_space_handle_t ioh; + bus_size_t sc_size; + + void *ih; + + struct resource *io_res; + struct resource *irq_res; + + usb_dma_t sc_hccadma; + struct ohci_hcca *sc_hcca; + ohci_soft_ed_t *sc_eds[OHCI_NO_EDS]; + u_int sc_bws[OHCI_NO_INTRS]; + + u_int32_t sc_eintrs; /* enabled interrupts */ + + ohci_soft_ed_t *sc_isoc_head; + ohci_soft_ed_t *sc_ctrl_head; + ohci_soft_ed_t *sc_bulk_head; + + LIST_HEAD(, ohci_soft_td) sc_hash_tds[OHCI_HASH_SIZE]; + LIST_HEAD(, ohci_soft_itd) sc_hash_itds[OHCI_HASH_SIZE]; + + int sc_noport; + u_int8_t sc_addr; /* device address */ + u_int8_t sc_conf; /* device configuration */ + +#ifdef USB_USE_SOFTINTR + char sc_softwake; +#endif /* USB_USE_SOFTINTR */ + + ohci_soft_ed_t *sc_freeeds; + ohci_soft_td_t *sc_freetds; + ohci_soft_itd_t *sc_freeitds; + + STAILQ_HEAD(, usbd_xfer) sc_free_xfers; /* free xfers */ + + usbd_xfer_handle sc_intrxfer; + + ohci_soft_itd_t *sc_sidone; + ohci_soft_td_t *sc_sdone; + + char sc_vendor[16]; + int sc_id_vendor; + +#if defined(__NetBSD__) || defined(__OpenBSD__) + void *sc_powerhook; /* cookie from power hook */ + void *sc_shutdownhook; /* cookie from shutdown hook */ +#endif + u_int32_t sc_control; /* Preserved during suspend/standby */ + u_int32_t sc_intre; + + u_int sc_overrun_cnt; + struct timeval sc_overrun_ntc; + + struct callout sc_tmo_rhsc; + char sc_dying; +} ohci_softc_t; + +struct ohci_xfer { + struct usbd_xfer xfer; + struct usb_task abort_task; + u_int32_t ohci_xfer_flags; +}; +#define OHCI_XFER_ABORTING 0x01 /* xfer is aborting. */ +#define OHCI_XFER_ABORTWAIT 0x02 /* abort completion is being awaited. */ + +#define OXFER(xfer) ((struct ohci_xfer *)(xfer)) +#define MS_TO_TICKS(ms) ((ms) * hz / 1000) + +usbd_status ohci_init(ohci_softc_t *); +void ohci_intr(void *); +int ohci_detach(ohci_softc_t *, int); +void ohci_shutdown(void *v); +void ohci_power(int state, void *priv); diff --git a/sys/legacy/dev/usb/rio500_usb.h b/sys/legacy/dev/usb/rio500_usb.h new file mode 100644 index 0000000..5b53e2c --- /dev/null +++ b/sys/legacy/dev/usb/rio500_usb.h @@ -0,0 +1,48 @@ +/*- + ---------------------------------------------------------------------- + + Copyright (C) 2000 Cesar Miquel (miquel@df.uba.ar) + + Redistribution and use in source and binary forms, with or without + modification, are permitted under any licence of your choise which + meets the open source licence definiton + http://www.opensource.org/opd.html such as the GNU licence or the + BSD licence. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License or the BSD license for more details. + + ---------------------------------------------------------------------- + + Modified for FreeBSD by Iwasa Kazmi <kzmi@ca2.so-net.ne.jp> + + ---------------------------------------------------------------------- */ + +/* $FreeBSD$ */ + +#include <sys/ioccom.h> +#ifndef USB_VENDOR_DIAMOND +#define USB_VENDOR_DIAMOND 0x841 +#endif +#ifndef USB_PRODUCT_DIAMOND_RIO500USB +#define USB_PRODUCT_DIAMOND_RIO500USB 0x1 +#endif + +struct RioCommand +{ + uint16_t length; + int request; + int requesttype; + int value; + int index; + void *buffer; + int timeout; +}; + +#define RIO_SEND_COMMAND _IOWR('U', 200, struct RioCommand) +#define RIO_RECV_COMMAND _IOWR('U', 201, struct RioCommand) + +#define RIO_DIR_OUT 0x0 +#define RIO_DIR_IN 0x1 diff --git a/sys/legacy/dev/usb/rt2573_ucode.h b/sys/legacy/dev/usb/rt2573_ucode.h new file mode 100644 index 0000000..f2040f3 --- /dev/null +++ b/sys/legacy/dev/usb/rt2573_ucode.h @@ -0,0 +1,213 @@ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2005-2006, Ralink Technology, Corp. + * Paul Lin <paul_lin@ralinktech.com.tw> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This file contains the loadable 8051 microcode for the Ralink RT2573 + * chipset. + */ + +static const uint8_t rt2573_ucode[] = { + 0x02, 0x13, 0x25, 0x12, 0x10, 0xd9, 0x02, 0x12, 0x58, 0x02, 0x13, + 0x58, 0x02, 0x13, 0x5a, 0xc0, 0xd0, 0x75, 0xd0, 0x18, 0x12, 0x13, + 0x5c, 0xd0, 0xd0, 0x22, 0x02, 0x14, 0x5c, 0x02, 0x14, 0xe7, 0xed, + 0x4c, 0x70, 0x44, 0x90, 0x01, 0xa8, 0x74, 0x80, 0xf0, 0xef, 0x30, + 0xe5, 0x07, 0xe4, 0x90, 0x00, 0x0f, 0xf0, 0x80, 0x2c, 0xe5, 0x40, + 0x24, 0xc0, 0x60, 0x13, 0x24, 0xc0, 0x60, 0x16, 0x24, 0xc0, 0x60, + 0x19, 0x24, 0xc0, 0x70, 0x1a, 0xe4, 0x90, 0x00, 0x0b, 0xf0, 0x80, + 0x13, 0xe4, 0x90, 0x00, 0x13, 0xf0, 0x80, 0x0c, 0xe4, 0x90, 0x00, + 0x1b, 0xf0, 0x80, 0x05, 0xe4, 0x90, 0x00, 0x23, 0xf0, 0xe4, 0x90, + 0x01, 0xa8, 0xf0, 0xd3, 0x22, 0x90, 0x02, 0x02, 0xed, 0xf0, 0x90, + 0x02, 0x01, 0xef, 0xf0, 0xd3, 0x22, 0xef, 0x24, 0xc0, 0x60, 0x1f, + 0x24, 0xc0, 0x60, 0x2e, 0x24, 0xc0, 0x60, 0x3d, 0x24, 0xc0, 0x70, + 0x53, 0x90, 0x00, 0x0b, 0xe0, 0x30, 0xe1, 0x02, 0xc3, 0x22, 0x90, + 0x00, 0x09, 0xe0, 0xfe, 0x90, 0x00, 0x08, 0x80, 0x37, 0x90, 0x00, + 0x13, 0xe0, 0x30, 0xe1, 0x02, 0xc3, 0x22, 0x90, 0x00, 0x11, 0xe0, + 0xfe, 0x90, 0x00, 0x10, 0x80, 0x24, 0x90, 0x00, 0x1b, 0xe0, 0x30, + 0xe1, 0x02, 0xc3, 0x22, 0x90, 0x00, 0x19, 0xe0, 0xfe, 0x90, 0x00, + 0x18, 0x80, 0x11, 0x90, 0x00, 0x23, 0xe0, 0x30, 0xe1, 0x02, 0xc3, + 0x22, 0x90, 0x00, 0x21, 0xe0, 0xfe, 0x90, 0x00, 0x20, 0xe0, 0xfd, + 0xee, 0xf5, 0x37, 0xed, 0xf5, 0x38, 0xd3, 0x22, 0x30, 0x09, 0x20, + 0x20, 0x04, 0x0b, 0x90, 0x02, 0x08, 0xe0, 0x54, 0x0f, 0x70, 0x03, + 0x02, 0x12, 0x57, 0xc2, 0x09, 0x90, 0x02, 0x00, 0xe0, 0x44, 0x04, + 0xf0, 0x74, 0x04, 0x12, 0x0c, 0x3a, 0xc2, 0x04, 0xc2, 0x07, 0x90, + 0x02, 0x01, 0xe0, 0x30, 0xe0, 0x03, 0x00, 0x80, 0xf6, 0x90, 0x03, + 0x26, 0xe0, 0x20, 0xe2, 0x03, 0x02, 0x12, 0x57, 0x90, 0x02, 0x08, + 0xe0, 0x70, 0x1b, 0x20, 0x07, 0x03, 0x02, 0x12, 0x57, 0x90, 0x03, + 0x12, 0xe0, 0x64, 0x22, 0x60, 0x03, 0x02, 0x12, 0x57, 0xd2, 0x09, + 0xc2, 0x07, 0x74, 0x02, 0x12, 0x0c, 0x3a, 0x22, 0x90, 0x02, 0x03, + 0xe0, 0x30, 0xe4, 0x47, 0x20, 0x06, 0x44, 0xe5, 0x3c, 0x60, 0x34, + 0xe5, 0x40, 0x24, 0xc0, 0x60, 0x14, 0x24, 0xc0, 0x60, 0x18, 0x24, + 0xc0, 0x60, 0x1c, 0x24, 0xc0, 0x70, 0x22, 0x90, 0x00, 0x0b, 0xe0, + 0x30, 0xe1, 0x1b, 0x22, 0x90, 0x00, 0x13, 0xe0, 0x30, 0xe1, 0x13, + 0x22, 0x90, 0x00, 0x1b, 0xe0, 0x30, 0xe1, 0x0b, 0x22, 0x90, 0x00, + 0x23, 0xe0, 0x30, 0xe1, 0x03, 0x02, 0x12, 0x57, 0x90, 0x02, 0x03, + 0x74, 0x01, 0xf0, 0x00, 0xe0, 0x54, 0xc0, 0xf5, 0x40, 0xe5, 0x40, + 0x24, 0xc0, 0x60, 0x20, 0x24, 0xc0, 0x60, 0x30, 0x24, 0xc0, 0x60, + 0x40, 0x24, 0xc0, 0x70, 0x56, 0x90, 0x00, 0x0b, 0xe0, 0x30, 0xe1, + 0x03, 0x02, 0x12, 0x57, 0x90, 0x00, 0x09, 0xe0, 0xfe, 0x90, 0x00, + 0x08, 0x80, 0x3a, 0x90, 0x00, 0x13, 0xe0, 0x30, 0xe1, 0x03, 0x02, + 0x12, 0x57, 0x90, 0x00, 0x11, 0xe0, 0xfe, 0x90, 0x00, 0x10, 0x80, + 0x26, 0x90, 0x00, 0x1b, 0xe0, 0x30, 0xe1, 0x03, 0x02, 0x12, 0x57, + 0x90, 0x00, 0x19, 0xe0, 0xfe, 0x90, 0x00, 0x18, 0x80, 0x12, 0x90, + 0x00, 0x23, 0xe0, 0x30, 0xe1, 0x03, 0x02, 0x12, 0x57, 0x90, 0x00, + 0x21, 0xe0, 0xfe, 0x90, 0x00, 0x20, 0xe0, 0xfd, 0xee, 0xf5, 0x37, + 0xed, 0xf5, 0x38, 0x90, 0x03, 0x27, 0x74, 0x82, 0xf0, 0x90, 0x02, + 0x01, 0xe5, 0x40, 0xf0, 0x90, 0x02, 0x06, 0xe0, 0xf5, 0x3c, 0xc3, + 0xe5, 0x38, 0x95, 0x3a, 0xe5, 0x37, 0x95, 0x39, 0x50, 0x21, 0xe5, + 0x40, 0x44, 0x05, 0xff, 0xe5, 0x37, 0xa2, 0xe7, 0x13, 0xfc, 0xe5, + 0x38, 0x13, 0xfd, 0x12, 0x10, 0x20, 0xe5, 0x3c, 0x30, 0xe2, 0x04, + 0xd2, 0x06, 0x80, 0x02, 0xc2, 0x06, 0x53, 0x3c, 0x01, 0x22, 0x30, + 0x0b, 0x07, 0xe4, 0x90, 0x02, 0x02, 0xf0, 0x80, 0x06, 0x90, 0x02, + 0x02, 0x74, 0x20, 0xf0, 0xe5, 0x40, 0x44, 0x01, 0x90, 0x02, 0x01, + 0xf0, 0x90, 0x02, 0x01, 0xe0, 0x30, 0xe0, 0x03, 0x00, 0x80, 0xf6, + 0x90, 0x03, 0x27, 0x74, 0x02, 0xf0, 0xaf, 0x40, 0x12, 0x10, 0x74, + 0x40, 0xa5, 0x00, 0x80, 0xf6, 0x22, 0x90, 0x7f, 0xf8, 0xe0, 0xb4, + 0x02, 0x03, 0x12, 0x16, 0x38, 0x90, 0x02, 0x01, 0xe0, 0x30, 0xe0, + 0x03, 0x00, 0x80, 0xf6, 0x90, 0x03, 0x26, 0xe0, 0x20, 0xe1, 0x07, + 0xe5, 0x3b, 0x70, 0x03, 0x02, 0x13, 0x24, 0xe5, 0x3b, 0x70, 0x15, + 0x90, 0x03, 0x24, 0xe0, 0x75, 0xf0, 0x40, 0xa4, 0xf5, 0x36, 0x85, + 0xf0, 0x35, 0x75, 0x24, 0x83, 0x75, 0x3b, 0x01, 0x80, 0x03, 0x75, + 0x24, 0x03, 0xd3, 0xe5, 0x36, 0x95, 0x3a, 0xe5, 0x35, 0x95, 0x39, + 0x40, 0x36, 0x90, 0x02, 0x01, 0xe0, 0x30, 0xe0, 0x03, 0x00, 0x80, + 0xf6, 0x90, 0x03, 0x27, 0xe5, 0x24, 0xf0, 0x90, 0x00, 0x0f, 0xe0, + 0x30, 0xe1, 0x04, 0x30, 0x0e, 0xf6, 0x22, 0x30, 0x0b, 0x07, 0xe4, + 0x90, 0x02, 0x02, 0xf0, 0x80, 0x06, 0x90, 0x02, 0x02, 0x74, 0x20, + 0xf0, 0x90, 0x02, 0x01, 0x74, 0x21, 0xf0, 0x75, 0x24, 0x03, 0x80, + 0x3d, 0xe5, 0x35, 0xa2, 0xe7, 0x13, 0xfe, 0xe5, 0x36, 0x13, 0xfd, + 0xac, 0x06, 0x90, 0x02, 0x01, 0xe0, 0x30, 0xe0, 0x03, 0x00, 0x80, + 0xf6, 0x90, 0x03, 0x27, 0xe5, 0x24, 0xf0, 0x90, 0x00, 0x0f, 0xe0, + 0x30, 0xe1, 0x04, 0x30, 0x0e, 0xf6, 0x22, 0x7f, 0x25, 0x12, 0x10, + 0x20, 0xe5, 0x36, 0xb5, 0x3a, 0x08, 0xe5, 0x35, 0xb5, 0x39, 0x03, + 0x00, 0x80, 0x04, 0xe4, 0xf5, 0x3b, 0x22, 0xc3, 0xe5, 0x36, 0x95, + 0x3a, 0xf5, 0x36, 0xe5, 0x35, 0x95, 0x39, 0xf5, 0x35, 0x02, 0x12, + 0x96, 0x22, 0x75, 0xa8, 0x0f, 0x90, 0x03, 0x06, 0x74, 0x01, 0xf0, + 0x90, 0x03, 0x07, 0xf0, 0x90, 0x03, 0x08, 0x04, 0xf0, 0x90, 0x03, + 0x09, 0x74, 0x6c, 0xf0, 0x90, 0x03, 0x0a, 0x74, 0xff, 0xf0, 0x90, + 0x03, 0x02, 0x74, 0x1f, 0xf0, 0x90, 0x03, 0x00, 0x74, 0x04, 0xf0, + 0x90, 0x03, 0x25, 0x74, 0x31, 0xf0, 0xd2, 0xaf, 0x22, 0x00, 0x22, + 0x00, 0x22, 0x90, 0x03, 0x05, 0xe0, 0x30, 0xe0, 0x0b, 0xe0, 0x44, + 0x01, 0xf0, 0x30, 0x09, 0x02, 0xd2, 0x04, 0xc2, 0x07, 0x22, 0x8d, + 0x24, 0xa9, 0x07, 0x90, 0x7f, 0xfc, 0xe0, 0x75, 0x25, 0x00, 0xf5, + 0x26, 0xa3, 0xe0, 0x75, 0x27, 0x00, 0xf5, 0x28, 0xa3, 0xe0, 0xff, + 0xa3, 0xe0, 0xfd, 0xe9, 0x30, 0xe5, 0x14, 0x54, 0xc0, 0x60, 0x05, + 0x43, 0x05, 0x03, 0x80, 0x03, 0x53, 0x05, 0xfc, 0xef, 0x54, 0x3f, + 0x44, 0x40, 0xff, 0x80, 0x06, 0x53, 0x07, 0x3f, 0x53, 0x05, 0xf0, + 0xe5, 0x24, 0x30, 0xe0, 0x05, 0x43, 0x05, 0x10, 0x80, 0x03, 0x53, + 0x05, 0xef, 0x90, 0x7f, 0xfc, 0xe5, 0x26, 0xf0, 0xa3, 0xe5, 0x28, + 0xf0, 0xa3, 0xef, 0xf0, 0xa3, 0xed, 0xf0, 0x22, 0x8f, 0x24, 0xa9, + 0x05, 0x90, 0x7f, 0xfc, 0xe0, 0x75, 0x25, 0x00, 0xf5, 0x26, 0xa3, + 0xe0, 0x75, 0x27, 0x00, 0xf5, 0x28, 0xa3, 0xe0, 0xff, 0xa3, 0xe0, + 0xfd, 0xe5, 0x24, 0x30, 0xe5, 0x0b, 0x43, 0x05, 0x0f, 0xef, 0x54, + 0x3f, 0x44, 0x40, 0xff, 0x80, 0x06, 0x53, 0x05, 0xf0, 0x53, 0x07, + 0x3f, 0xe9, 0x30, 0xe0, 0x05, 0x43, 0x05, 0x10, 0x80, 0x03, 0x53, + 0x05, 0xef, 0x90, 0x7f, 0xfc, 0xe5, 0x26, 0xf0, 0xa3, 0xe5, 0x28, + 0xf0, 0xa3, 0xef, 0xf0, 0xa3, 0xed, 0xf0, 0x22, 0x90, 0x7f, 0xfc, + 0xe0, 0xf9, 0xa3, 0xe0, 0xfe, 0xa3, 0xe0, 0xfc, 0xa3, 0xe0, 0xfb, + 0xef, 0x30, 0xe5, 0x0b, 0x43, 0x03, 0x0f, 0xec, 0x54, 0x3f, 0x44, + 0x40, 0xfc, 0x80, 0x06, 0x53, 0x03, 0xf0, 0x53, 0x04, 0x3f, 0xed, + 0x30, 0xe0, 0x07, 0xef, 0x54, 0xc0, 0x60, 0x07, 0x80, 0x0a, 0xef, + 0x54, 0xc0, 0x60, 0x05, 0x43, 0x03, 0x10, 0x80, 0x03, 0x53, 0x03, + 0xef, 0x90, 0x7f, 0xfc, 0xe9, 0xf0, 0xa3, 0xee, 0xf0, 0xa3, 0xec, + 0xf0, 0xa3, 0xeb, 0xf0, 0x22, 0xe5, 0x4b, 0xfd, 0x54, 0x1f, 0x90, + 0x7f, 0xf8, 0xf0, 0xe5, 0x4a, 0xf5, 0x09, 0x90, 0x30, 0x38, 0xe0, + 0x90, 0x7f, 0xfc, 0xf0, 0x90, 0x30, 0x39, 0xe0, 0x90, 0x7f, 0xfd, + 0xf0, 0x90, 0x30, 0x3a, 0xe0, 0x90, 0x7f, 0xfe, 0xf0, 0x90, 0x30, + 0x3b, 0xe0, 0x90, 0x7f, 0xff, 0xf0, 0xed, 0x30, 0xe5, 0x0c, 0x54, + 0xc0, 0x60, 0x0d, 0x90, 0x7f, 0xf0, 0xe5, 0x47, 0xf0, 0x80, 0x05, + 0xe4, 0x90, 0x7f, 0xf0, 0xf0, 0x90, 0x7f, 0xf8, 0xe0, 0x14, 0x60, + 0x08, 0x24, 0xfe, 0x60, 0x0d, 0x24, 0x03, 0x80, 0x12, 0xaf, 0x05, + 0xad, 0x09, 0x12, 0x13, 0xc5, 0x80, 0x10, 0xaf, 0x05, 0xad, 0x09, + 0x12, 0x14, 0x12, 0x80, 0x07, 0xaf, 0x05, 0xad, 0x09, 0x12, 0x13, + 0x6f, 0x90, 0x7f, 0xfc, 0xe0, 0x90, 0x30, 0x38, 0xf0, 0x90, 0x7f, + 0xfd, 0xe0, 0x90, 0x30, 0x39, 0xf0, 0x90, 0x7f, 0xfe, 0xe0, 0x90, + 0x30, 0x3a, 0xf0, 0x90, 0x7f, 0xff, 0xe0, 0x90, 0x30, 0x3b, 0xf0, + 0x22, 0xe5, 0x4b, 0x64, 0x01, 0x60, 0x03, 0x02, 0x15, 0x71, 0xf5, + 0x4b, 0xe5, 0x44, 0x45, 0x43, 0x70, 0x03, 0x02, 0x15, 0xa0, 0x12, + 0x0c, 0x14, 0x12, 0x0b, 0x86, 0x50, 0xfb, 0x90, 0x00, 0x00, 0xe0, + 0xf5, 0x25, 0x12, 0x15, 0xb4, 0xc2, 0x92, 0xe4, 0xf5, 0x24, 0xe5, + 0x24, 0xc3, 0x95, 0x25, 0x50, 0x49, 0x7e, 0x00, 0x7f, 0x4c, 0x74, + 0x40, 0x25, 0x24, 0xf5, 0x82, 0xe4, 0x34, 0x01, 0xad, 0x82, 0xfc, + 0x75, 0x2b, 0x02, 0x7b, 0x10, 0x12, 0x07, 0x1e, 0xc2, 0x93, 0x12, + 0x15, 0xa1, 0x7d, 0xa0, 0x12, 0x15, 0xd0, 0xe5, 0x24, 0x54, 0x0f, + 0x24, 0x4c, 0xf8, 0xe6, 0xfd, 0xaf, 0x4b, 0xae, 0x4a, 0x12, 0x15, + 0xd8, 0x05, 0x4b, 0xe5, 0x4b, 0x70, 0x02, 0x05, 0x4a, 0x12, 0x0a, + 0x5f, 0x05, 0x24, 0xe5, 0x24, 0x54, 0x0f, 0x70, 0xd5, 0xd2, 0x93, + 0x80, 0xb0, 0xc3, 0xe5, 0x44, 0x95, 0x25, 0xf5, 0x44, 0xe5, 0x43, + 0x94, 0x00, 0xf5, 0x43, 0x02, 0x14, 0xf2, 0x12, 0x15, 0xb4, 0xc2, + 0x93, 0xc2, 0x92, 0x12, 0x15, 0xa1, 0x7d, 0x80, 0x12, 0x15, 0xd0, + 0x7d, 0xaa, 0x74, 0x55, 0xff, 0xfe, 0x12, 0x15, 0xd8, 0x7d, 0x55, + 0x7f, 0xaa, 0x7e, 0x2a, 0x12, 0x15, 0xd8, 0x7d, 0x30, 0xaf, 0x4b, + 0xae, 0x4a, 0x12, 0x15, 0xd8, 0x12, 0x0a, 0x5f, 0xd2, 0x93, 0x22, + 0x7d, 0xaa, 0x74, 0x55, 0xff, 0xfe, 0x12, 0x15, 0xd8, 0x7d, 0x55, + 0x7f, 0xaa, 0x7e, 0x2a, 0x12, 0x15, 0xd8, 0x22, 0xad, 0x47, 0x7f, + 0x34, 0x7e, 0x30, 0x12, 0x15, 0xd8, 0x7d, 0xff, 0x7f, 0x35, 0x7e, + 0x30, 0x12, 0x15, 0xd8, 0xe4, 0xfd, 0x7f, 0x37, 0x7e, 0x30, 0x12, + 0x15, 0xd8, 0x22, 0x74, 0x55, 0xff, 0xfe, 0x12, 0x15, 0xd8, 0x22, + 0x8f, 0x82, 0x8e, 0x83, 0xed, 0xf0, 0x22, 0xe4, 0xfc, 0x90, 0x7f, + 0xf0, 0xe0, 0xaf, 0x09, 0x14, 0x60, 0x14, 0x14, 0x60, 0x15, 0x14, + 0x60, 0x16, 0x14, 0x60, 0x17, 0x14, 0x60, 0x18, 0x24, 0x05, 0x70, + 0x16, 0xe4, 0xfc, 0x80, 0x12, 0x7c, 0x01, 0x80, 0x0e, 0x7c, 0x03, + 0x80, 0x0a, 0x7c, 0x07, 0x80, 0x06, 0x7c, 0x0f, 0x80, 0x02, 0x7c, + 0x1f, 0xec, 0x6f, 0xf4, 0x54, 0x1f, 0xfc, 0x90, 0x30, 0x34, 0xe0, + 0x54, 0xe0, 0x4c, 0xfd, 0xa3, 0xe0, 0xfc, 0x43, 0x04, 0x1f, 0x7f, + 0x34, 0x7e, 0x30, 0x12, 0x15, 0xd8, 0xad, 0x04, 0x0f, 0x12, 0x15, + 0xd8, 0xe4, 0xfd, 0x7f, 0x37, 0x02, 0x15, 0xd8, 0x02, 0x15, 0xdf, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x07, + 0x29, 0xe9 +}; diff --git a/sys/legacy/dev/usb/sl811hs.c b/sys/legacy/dev/usb/sl811hs.c new file mode 100644 index 0000000..3620d5f --- /dev/null +++ b/sys/legacy/dev/usb/sl811hs.c @@ -0,0 +1,1654 @@ +/* $NetBSD: sl811hs.c,v 1.5 2005/02/27 00:27:02 perry Exp $ */ + +/* + * Copyright (c) 2001 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Tetsuya Isaki. + * + * 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. + */ + + +/* + * ScanLogic SL811HS/T USB Host Controller + */ +/* + * !! HIGHLY EXPERIMENTAL CODE !! + */ + +#include <sys/cdefs.h> +//_RCSID(0, "$NetBSD: sl811hs.c,v 1.5 2005/02/27 00:27:02 perry Exp $"); + +#include "opt_slhci.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/proc.h> +#include <sys/bus.h> +#include <sys/malloc.h> + +#include <machine/bus.h> +#include <machine/cpu.h> +#include <sys/module.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdivar.h> +#include <dev/usb/usb_mem.h> +#include <dev/usb/usb_port.h> +#include "usbdevs.h" + +#include <dev/usb/sl811hsreg.h> +#include <dev/usb/sl811hsvar.h> + +__FBSDID("$FreeBSD$"); + +static inline u_int8_t sl11read(struct slhci_softc *, int); +static inline void sl11write(struct slhci_softc *, int, u_int8_t); +static inline void sl11read_region(struct slhci_softc *, u_char *, int, int); +static inline void sl11write_region(struct slhci_softc *, int, u_char *, int); + +static void sl11_reset(struct slhci_softc *); +static void sl11_speed(struct slhci_softc *); + +static usbd_status slhci_open(usbd_pipe_handle); +static void slhci_softintr(void *); +static void slhci_poll(struct usbd_bus *); +static void slhci_poll_hub(void *); +static void slhci_poll_device(void *arg); +static usbd_status slhci_allocm(struct usbd_bus *, usb_dma_t *, u_int32_t); +static void slhci_freem(struct usbd_bus *, usb_dma_t *); +static usbd_xfer_handle slhci_allocx(struct usbd_bus *); +static void slhci_freex(struct usbd_bus *, usbd_xfer_handle); + +static int slhci_str(usb_string_descriptor_t *, int, const char *); + +static usbd_status slhci_root_ctrl_transfer(usbd_xfer_handle); +static usbd_status slhci_root_ctrl_start(usbd_xfer_handle); +static void slhci_root_ctrl_abort(usbd_xfer_handle); +static void slhci_root_ctrl_close(usbd_pipe_handle); +static void slhci_root_ctrl_done(usbd_xfer_handle); + +static usbd_status slhci_root_intr_transfer(usbd_xfer_handle); +static usbd_status slhci_root_intr_start(usbd_xfer_handle); +static void slhci_root_intr_abort(usbd_xfer_handle); +static void slhci_root_intr_close(usbd_pipe_handle); +static void slhci_root_intr_done(usbd_xfer_handle); + +static usbd_status slhci_device_ctrl_transfer(usbd_xfer_handle); +static usbd_status slhci_device_ctrl_start(usbd_xfer_handle); +static void slhci_device_ctrl_abort(usbd_xfer_handle); +static void slhci_device_ctrl_close(usbd_pipe_handle); +static void slhci_device_ctrl_done(usbd_xfer_handle); + +static usbd_status slhci_device_intr_transfer(usbd_xfer_handle); +static usbd_status slhci_device_intr_start(usbd_xfer_handle); +static void slhci_device_intr_abort(usbd_xfer_handle); +static void slhci_device_intr_close(usbd_pipe_handle); +static void slhci_device_intr_done(usbd_xfer_handle); + +static usbd_status slhci_device_isoc_transfer(usbd_xfer_handle); +static usbd_status slhci_device_isoc_start(usbd_xfer_handle); +static void slhci_device_isoc_abort(usbd_xfer_handle); +static void slhci_device_isoc_close(usbd_pipe_handle); +static void slhci_device_isoc_done(usbd_xfer_handle); + +static usbd_status slhci_device_bulk_transfer(usbd_xfer_handle); +static usbd_status slhci_device_bulk_start(usbd_xfer_handle); +static void slhci_device_bulk_abort(usbd_xfer_handle); +static void slhci_device_bulk_close(usbd_pipe_handle); +static void slhci_device_bulk_done(usbd_xfer_handle); + +static int slhci_transaction(struct slhci_softc *, + usbd_pipe_handle, u_int8_t, int, u_char *, u_int8_t); +static void slhci_noop(usbd_pipe_handle); +static void slhci_abort_xfer(usbd_xfer_handle, usbd_status); +static void slhci_device_clear_toggle(usbd_pipe_handle); + +extern int usbdebug; + +/* For root hub */ +#define SLHCI_INTR_ENDPT (1) + +#ifdef SLHCI_DEBUG +#define D_TRACE (0x0001) /* function trace */ +#define D_MSG (0x0002) /* debug messages */ +#define D_XFER (0x0004) /* transfer messages (noisy!) */ +#define D_MEM (0x0008) /* memory allocation */ + +int slhci_debug = D_MSG | D_XFER; +#define DPRINTF(z,x) if((slhci_debug&(z))!=0)printf x +void print_req(usb_device_request_t *); +void print_req_hub(usb_device_request_t *); +void print_dumpreg(struct slhci_softc *); +void print_xfer(usbd_xfer_handle); +#else +#define DPRINTF(z,x) +#endif + + +/* XXX: sync with argument */ +static const char *sltypestr [] = { + "SL11H/T", + "SL811HS/T", +}; + + +struct usbd_bus_methods slhci_bus_methods = { + slhci_open, + slhci_softintr, + slhci_poll, + slhci_allocm, + slhci_freem, + slhci_allocx, + slhci_freex, +}; + +struct usbd_pipe_methods slhci_root_ctrl_methods = { + slhci_root_ctrl_transfer, + slhci_root_ctrl_start, + slhci_root_ctrl_abort, + slhci_root_ctrl_close, + slhci_noop, + slhci_root_ctrl_done, +}; + +struct usbd_pipe_methods slhci_root_intr_methods = { + slhci_root_intr_transfer, + slhci_root_intr_start, + slhci_root_intr_abort, + slhci_root_intr_close, + slhci_noop, + slhci_root_intr_done, +}; + +struct usbd_pipe_methods slhci_device_ctrl_methods = { + slhci_device_ctrl_transfer, + slhci_device_ctrl_start, + slhci_device_ctrl_abort, + slhci_device_ctrl_close, + slhci_noop, + slhci_device_ctrl_done, +}; + +struct usbd_pipe_methods slhci_device_intr_methods = { + slhci_device_intr_transfer, + slhci_device_intr_start, + slhci_device_intr_abort, + slhci_device_intr_close, + slhci_device_clear_toggle, + slhci_device_intr_done, +}; + +struct usbd_pipe_methods slhci_device_isoc_methods = { + slhci_device_isoc_transfer, + slhci_device_isoc_start, + slhci_device_isoc_abort, + slhci_device_isoc_close, + slhci_noop, + slhci_device_isoc_done, +}; + +struct usbd_pipe_methods slhci_device_bulk_methods = { + slhci_device_bulk_transfer, + slhci_device_bulk_start, + slhci_device_bulk_abort, + slhci_device_bulk_close, + slhci_noop, + slhci_device_bulk_done, +}; + +struct slhci_pipe { + struct usbd_pipe pipe; +}; + + +/* + * SL811HS Register read/write routine + */ +static inline u_int8_t +sl11read(struct slhci_softc *sc, int reg) +{ +#if 1 + int b; + DELAY(80); + bus_space_write_1(sc->sc_iot, sc->sc_ioh, SL11_IDX_ADDR, reg); + b = bus_space_read_1(sc->sc_iot, sc->sc_ioh, SL11_IDX_DATA); + return b; +#else + outb(0x4000, reg&0xff); + return (inb(0x4001)&0xff); +#endif +} + +static inline void +sl11write(struct slhci_softc *sc, int reg, u_int8_t data) +{ +#if 1 + DELAY(80); + bus_space_write_1(sc->sc_iot, sc->sc_ioh, SL11_IDX_ADDR, reg); + bus_space_write_1(sc->sc_iot, sc->sc_ioh, SL11_IDX_DATA, data); +#else + outb(0x4000, reg&0xff); + outb(0x4000, data&0xff); +#endif +} + +static inline void +sl11read_region(struct slhci_softc *sc, u_char *buf, int reg, int len) +{ + int i; + bus_space_write_1(sc->sc_iot, sc->sc_ioh, SL11_IDX_ADDR, reg); + for (i = 0; i < len; i++) + buf[i] = bus_space_read_1(sc->sc_iot, sc->sc_ioh, SL11_IDX_DATA); +} + +static inline void +sl11write_region(struct slhci_softc *sc, int reg, u_char *buf, int len) +{ + int i; + bus_space_write_1(sc->sc_iot, sc->sc_ioh, SL11_IDX_ADDR, reg); + for (i = 0; i < len; i++) + bus_space_write_1(sc->sc_iot, sc->sc_ioh, SL11_IDX_DATA, buf[i]); +} + +/* + * USB bus reset. From sl811hs_appnote.pdf, p22 + */ +static void +sl11_reset(struct slhci_softc *sc) +{ + u_int8_t r; + + DPRINTF(D_TRACE, ("%s() ", __FUNCTION__)); + // r = sl11read(sc, SL11_CTRL); + r = 0; + sl11write(sc, SL11_CTRL, r | SL11_CTRL_RESETENGINE); + delay_ms(250); + sl11write(sc, SL11_CTRL, r | SL11_CTRL_JKSTATE | SL11_CTRL_RESETENGINE); delay_ms(150); + sl11write(sc, SL11_CTRL, r | SL11_CTRL_RESETENGINE); + delay_ms(10); + sl11write(sc, SL11_CTRL, r); +} + +/* + * Detect the speed of attached device. + */ +static void +sl11_speed(struct slhci_softc *sc) +{ + u_int8_t r; + + sl11write(sc, SL11_ISR, 0xff); + r = sl11read(sc, SL11_ISR); + if ((r & SL11_ISR_RESET)) { + DPRINTF(D_MSG, ("NC ")); + sl11write(sc, SL11_ISR, SL11_ISR_RESET); + sc->sc_connect = 0; + } + + if ((sl11read(sc, SL11_ISR) & SL11_ISR_RESET)) { + sl11write(sc, SL11_ISR, 0xff); + } else { + u_int8_t pol = 0, ctrl = 0; + + sc->sc_connect = 1; + if (r & SL11_ISR_DATA) { + DPRINTF(D_MSG, ("FS ")); + pol = 0; + ctrl = SL11_CTRL_EOF2; + sc->sc_fullspeed = 1; + } else { + DPRINTF(D_MSG, ("LS ")); + pol = SL811_CSOF_POLARITY; + ctrl = SL11_CTRL_LOWSPEED; + sc->sc_fullspeed = 0; + } + sl11write(sc, SL811_CSOF, pol | SL811_CSOF_MASTER | 0x2e); + sl11write(sc, SL11_DATA, 0xe0); + sl11write(sc, SL11_CTRL, ctrl | SL11_CTRL_ENABLESOF); + } + + sl11write(sc, SL11_E0PID, (SL11_PID_SOF << 4) + 0); + sl11write(sc, SL11_E0DEV, 0); + sl11write(sc, SL11_E0CTRL, SL11_EPCTRL_ARM); + delay_ms(30); +} + +/* + * If detect some known controller, return the type. + * If does not, return -1. + */ +int +sl811hs_find(struct slhci_softc *sc) +{ + int rev; + sc->sc_sltype = -1; + + rev = sl11read(sc, SL11_REV) >> 4; + if (rev >= SLTYPE_SL11H && rev <= SLTYPE_SL811HS_R14) + sc->sc_sltype = rev; + return sc->sc_sltype; +} + +/* + * Attach SL11H/SL811HS. Return 0 if success. + */ +int +slhci_attach(struct slhci_softc *sc) +{ + int rev; + /* Detect and check the controller type */ + + rev = sl811hs_find(sc); + if (rev == -1) + return -1; + + printf("%s: ScanLogic %s USB Host Controller", + device_get_nameunit(sc->sc_bus.bdev), sltypestr[(rev > 0)]); + switch (rev) { + case SLTYPE_SL11H: + break; + case SLTYPE_SL811HS_R12: + printf(" (rev 1.2)"); + break; + case SLTYPE_SL811HS_R14: + printf(" (rev 1.4)"); + break; + default: + printf(" (unknown revision)"); + break; + } + printf("\n"); + + + /* Initialize sc */ + sc->sc_bus.usbrev = USBREV_1_1; + sc->sc_bus.methods = &slhci_bus_methods; + sc->sc_bus.pipe_size = sizeof(struct slhci_pipe); + sc->sc_bus.parent_dmatag = NULL; /* XXX */ + sc->sc_bus.buffer_dmatag = NULL; /* XXX */ + STAILQ_INIT(&sc->sc_free_xfers); + + usb_callout_init(sc->sc_poll_handle); + + /* Disable interrupt, then wait 40msec */ + sl11write(sc, SL11_IER, 0x00); + delay_ms(40); + + /* Initialize controller */ + sl11write(sc, SL811_CSOF, SL811_CSOF_MASTER | 0x2e); + delay_ms(40); + + sl11write(sc, SL11_ISR, 0xff); + + /* Disable interrupt, then wait 40msec */ + sl11write(sc, SL11_IER, 0x00); + + /* Reset USB engine */ + sl11write(sc, SL11_CTRL, SL11_CTRL_JKSTATE| SL11_CTRL_RESETENGINE); + delay_ms(40); + sl11write(sc, SL11_CTRL, 0x00); + delay_ms(10); + + /* USB Bus reset for GET_PORT_STATUS */ + sl11_reset(sc); + sc->sc_flags = SLF_ATTACHED; + + /* Enable interrupt */ + sl11write(sc, SL11_IER, SL11_IER_INSERT); + /* x68k Nereid USB controller needs it */ + if (sc->sc_enable_intr) + sc->sc_enable_intr(sc->sc_arg, INTR_ON); +#ifdef USB_DEBUG + usbdebug = 0; +#endif + + return 0; +} + +int +slhci_intr(void *arg) +{ + struct slhci_softc *sc = arg; + u_int8_t r; +#ifdef SLHCI_DEBUG + char bitbuf[256]; +#endif + + + if((sc->sc_flags & SLF_ATTACHED) == 0) + return 0; + + r = sl11read(sc, SL11_ISR); + + + + sl11write(sc, SL11_ISR, SL11_ISR_DATA | SL11_ISR_SOFTIMER); + + if ((r & SL11_ISR_RESET)) { + sc->sc_flags |= SLF_RESET; + sl11write(sc, SL11_ISR, SL11_ISR_RESET); + } + if ((r & SL11_ISR_INSERT)) { + sc->sc_flags |= SLF_INSERT; + sl11write(sc, SL11_ISR, SL11_ISR_INSERT); + } + +#ifdef SLHCI_DEBUG + bitmask_snprintf(r, + (sl11read(sc, SL11_CTRL) & SL11_CTRL_SUSPEND) + ? "\20\x8""D+\7RESUME\6INSERT\5SOF\4res\3""BABBLE\2USBB\1USBA" + : "\20\x8""D+\7RESET\6INSERT\5SOF\4res\3""BABBLE\2USBB\1USBA", + bitbuf, sizeof(bitbuf)); + DPRINTF(D_XFER, ("I=%s ", bitbuf)); +#endif /* SLHCI_DEBUG */ + + return 0; +} + +usbd_status +slhci_open(usbd_pipe_handle pipe) +{ + usbd_device_handle dev = pipe->device; + struct slhci_softc *sc = (struct slhci_softc *)dev->bus; + usb_endpoint_descriptor_t *ed = pipe->endpoint->edesc; + + DPRINTF(D_TRACE, ("slhci_open(addr=%d,ep=%d,scaddr=%d)", + dev->address, ed->bEndpointAddress, sc->sc_addr)); + + if (dev->address == sc->sc_addr) { + switch (ed->bEndpointAddress) { + case USB_CONTROL_ENDPOINT: + pipe->methods = &slhci_root_ctrl_methods; + break; + case UE_DIR_IN | SLHCI_INTR_ENDPT: + pipe->methods = &slhci_root_intr_methods; + break; + default: + printf("open:endpointErr!\n"); + return USBD_INVAL; + } + } else { + switch (ed->bmAttributes & UE_XFERTYPE) { + case UE_CONTROL: + DPRINTF(D_MSG, ("control ")); + pipe->methods = &slhci_device_ctrl_methods; + break; + case UE_INTERRUPT: + DPRINTF(D_MSG, ("interrupt ")); + pipe->methods = &slhci_device_intr_methods; + break; + case UE_ISOCHRONOUS: + DPRINTF(D_MSG, ("isochronous ")); + pipe->methods = &slhci_device_isoc_methods; + break; + case UE_BULK: + DPRINTF(D_MSG, ("bluk ")); + pipe->methods = &slhci_device_bulk_methods; + break; + } + } + return USBD_NORMAL_COMPLETION; +} + +void +slhci_softintr(void *arg) +{ + DPRINTF(D_TRACE, ("%s()", __FUNCTION__)); +} + +void +slhci_poll(struct usbd_bus *bus) +{ + DPRINTF(D_TRACE, ("%s()", __FUNCTION__)); +} + +/* + * Emulation of interrupt transfer for status change endpoint + * of root hub. + */ +void +slhci_poll_hub(void *arg) +{ + usbd_xfer_handle xfer = arg; + usbd_pipe_handle pipe = xfer->pipe; + struct slhci_softc *sc = (struct slhci_softc *)pipe->device->bus; + int s; + u_char *p; + + usb_callout(sc->sc_poll_handle, sc->sc_interval, slhci_poll_hub, xfer); + + /* USB spec 11.13.3 (p.260) */ + p = xfer->buffer; + p[0] = 0; + if ((sc->sc_flags & (SLF_INSERT | SLF_RESET))) { + p[0] = 2; + DPRINTF(D_TRACE, ("!")); + } + + /* no change, return NAK */ + if (p[0] == 0) + return; + + xfer->actlen = 1; + xfer->status = USBD_NORMAL_COMPLETION; + s = splusb(); + xfer->device->bus->intr_context++; + usb_transfer_complete(xfer); + xfer->device->bus->intr_context--; + splx(s); +} + +usbd_status +slhci_allocm(struct usbd_bus *bus, usb_dma_t *dma, u_int32_t size) +{ + struct slhci_softc *sc = (struct slhci_softc *)bus; + + DPRINTF(D_MEM, ("SLallocm")); + return usb_allocmem(&sc->sc_bus, size, 0, dma); +} + +void +slhci_freem(struct usbd_bus *bus, usb_dma_t *dma) +{ + struct slhci_softc *sc = (struct slhci_softc *)bus; + + DPRINTF(D_MEM, ("SLfreem")); + usb_freemem(&sc->sc_bus, dma); +} + +usbd_xfer_handle +slhci_allocx(struct usbd_bus *bus) +{ + struct slhci_softc *sc = (struct slhci_softc *)bus; + usbd_xfer_handle xfer; + + DPRINTF(D_MEM, ("SLallocx")); + + xfer = STAILQ_FIRST(&sc->sc_free_xfers); + if (xfer) { + STAILQ_REMOVE_HEAD(&sc->sc_free_xfers, next); +#ifdef DIAGNOSTIC + if (xfer->busy_free != XFER_FREE) { + printf("slhci_allocx: xfer=%p not free, 0x%08x\n", + xfer, xfer->busy_free); + } +#endif + } else { + xfer = malloc(sizeof(*xfer), M_USB, M_NOWAIT); + } + + if (xfer) { + memset(xfer, 0, sizeof(*xfer)); +#ifdef DIAGNOSTIC + xfer->busy_free = XFER_BUSY; +#endif + } + + return xfer; +} + +void +slhci_freex(struct usbd_bus *bus, usbd_xfer_handle xfer) +{ + struct slhci_softc *sc = (struct slhci_softc *)bus; + + DPRINTF(D_MEM, ("SLfreex")); + +#ifdef DIAGNOSTIC + if (xfer->busy_free != XFER_BUSY) { + printf("slhci_freex: xfer=%p not busy, 0x%08x\n", + xfer, xfer->busy_free); + return; + } + xfer->busy_free = XFER_FREE; +#endif + STAILQ_INSERT_HEAD(&sc->sc_free_xfers, xfer, next); +} + +void +slhci_noop(usbd_pipe_handle pipe) +{ + DPRINTF(D_TRACE, ("%s()", __FUNCTION__)); +} + +/* + * Data structures and routines to emulate the root hub. + */ +usb_device_descriptor_t slhci_devd = { + USB_DEVICE_DESCRIPTOR_SIZE, + UDESC_DEVICE, /* type */ + {0x01, 0x01}, /* USB version */ + UDCLASS_HUB, /* class */ + UDSUBCLASS_HUB, /* subclass */ + 0, /* protocol */ + 64, /* max packet */ + {USB_VENDOR_SCANLOGIC & 0xff, /* vendor ID (low) */ + USB_VENDOR_SCANLOGIC >> 8 }, /* vendor ID (high) */ + {0} /* ? */, /* product ID */ + {0}, /* device */ + 1, /* index to manufacturer */ + 2, /* index to product */ + 0, /* index to serial number */ + 1 /* number of configurations */ +}; + +usb_config_descriptor_t slhci_confd = { + USB_CONFIG_DESCRIPTOR_SIZE, + UDESC_CONFIG, + {USB_CONFIG_DESCRIPTOR_SIZE + + USB_INTERFACE_DESCRIPTOR_SIZE + + USB_ENDPOINT_DESCRIPTOR_SIZE}, + 1, /* number of interfaces */ + 1, /* configuration value */ + 0, /* index to configuration */ + UC_SELF_POWERED, /* attributes */ + 15 /* max current is 30mA... */ +}; + +usb_interface_descriptor_t slhci_ifcd = { + USB_INTERFACE_DESCRIPTOR_SIZE, + UDESC_INTERFACE, + 0, /* interface number */ + 0, /* alternate setting */ + 1, /* number of endpoint */ + UICLASS_HUB, /* class */ + UISUBCLASS_HUB, /* subclass */ + 0, /* protocol */ + 0 /* index to interface */ +}; + +usb_endpoint_descriptor_t slhci_endpd = { + USB_ENDPOINT_DESCRIPTOR_SIZE, + UDESC_ENDPOINT, + UE_DIR_IN | SLHCI_INTR_ENDPT, /* endpoint address */ + UE_INTERRUPT, /* attributes */ + {8}, /* max packet size */ + 255 /* interval */ +}; + +usb_hub_descriptor_t slhci_hubd = { + USB_HUB_DESCRIPTOR_SIZE, + UDESC_HUB, + 1, /* number of ports */ + {UHD_PWR_INDIVIDUAL | UHD_OC_NONE, 0}, /* hub characteristics */ + 20 /* ? */, /* 5:power on to power good */ + 50, /* 6:maximum current */ + { 0x00 }, /* both ports are removable */ + { 0x00 } /* port power control mask */ +}; + +static int +slhci_str(usb_string_descriptor_t *p, int l, const char *s) +{ + int i; + + if (l == 0) + return 0; + p->bLength = 2 * strlen(s) + 2; + if (l == 1) + return 1; + p->bDescriptorType = UDESC_STRING; + l -= 2; + for (i = 0; s[i] && l > 1; i++, l -= 2) + USETW2(p->bString[i], 0, s[i]); + return 2 * i + 2; +} + +usbd_status +slhci_root_ctrl_transfer(usbd_xfer_handle xfer) +{ + usbd_status error; + + DPRINTF(D_TRACE, ("SLRCtrans ")); + + /* Insert last in queue */ + error = usb_insert_transfer(xfer); + if (error) { + DPRINTF(D_MSG, ("usb_insert_transfer returns err! ")); + return error; + } + + /* + * Pipe isn't running (otherwise error would be USBD_INPROG), + * so start it first. + */ + return slhci_root_ctrl_start(STAILQ_FIRST(&xfer->pipe->queue)); +} + +usbd_status +slhci_root_ctrl_start(usbd_xfer_handle xfer) +{ + struct slhci_softc *sc = (struct slhci_softc *)xfer->pipe->device->bus; + usb_device_request_t *req; + int len, value, index, l, s, status; + int totlen = 0; + void *buf = NULL; + usb_port_status_t ps; + usbd_status error; + char slbuf[50]; + u_int8_t r; + + DPRINTF(D_TRACE, ("SLRCstart ")); + + req = &xfer->request; + + len = UGETW(req->wLength); + value = UGETW(req->wValue); + index = UGETW(req->wIndex); + + if (len) + buf = xfer->buffer; + +#ifdef SLHCI_DEBUG + if ((slhci_debug & D_TRACE)) + print_req_hub(req); +#endif + +#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): + DPRINTF(D_MSG, ("UR_CLEAR_FEATURE ")); + break; + case C(UR_GET_CONFIG, UT_READ_DEVICE): + DPRINTF(D_MSG, ("UR_GET_CONFIG ")); + if (len > 0) { + *(u_int8_t *)buf = sc->sc_conf; + totlen = 1; + } + break; + case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE): + switch (value >> 8) { + case UDESC_DEVICE: + DPRINTF(D_MSG, ("UDESC_DEVICE ")); + if ((value & 0xff) != 0) { + error = USBD_IOERROR; + goto ret; + } + totlen = l = min(len, USB_DEVICE_DESCRIPTOR_SIZE); + memcpy(buf, &slhci_devd, l); + break; + case UDESC_CONFIG: + DPRINTF(D_MSG, ("UDESC_CONFIG ")); + if ((value & 0xff) != 0) { + error = USBD_IOERROR; + goto ret; + } + totlen = l = min(len, USB_CONFIG_DESCRIPTOR_SIZE); + memcpy(buf, &slhci_confd, l); + buf = (char *)buf + l; + len -= l; + + l = min(len, USB_INTERFACE_DESCRIPTOR_SIZE); + totlen += l; + memcpy(buf, &slhci_ifcd, l); + buf = (char *)buf + l; + len -= l; + + l = min(len, USB_ENDPOINT_DESCRIPTOR_SIZE); + totlen += l; + memcpy(buf, &slhci_endpd, l); + break; + case UDESC_STRING: + DPRINTF(D_MSG, ("UDESC_STR ")); + if (len == 0) + break; + *(u_int8_t *)buf = 0; + totlen = 1; + switch (value & 0xff) { + case 0: + break; + case 1: /* Vendor */ + totlen = slhci_str(buf, len, "ScanLogic"); + break; + case 2: /* Product */ + snprintf(slbuf, sizeof(slbuf), "%s root hub", + sltypestr[sc->sc_sltype>0]); + totlen = slhci_str(buf, len, slbuf); + break; + default: + printf("strerr%d ", value & 0xff); + break; + } + break; + default: + printf("unknownGetDescriptor=%x", value); + error = USBD_IOERROR; + break; + } + break; + case C(UR_GET_INTERFACE, UT_READ_INTERFACE): + /* Get Interface, 9.4.4 */ + if (len > 0) { + *(u_int8_t *)buf = 0; + totlen = 1; + } + break; + case C(UR_GET_STATUS, UT_READ_DEVICE): + /* Get Status from device, 9.4.5 */ + if (len > 1) { + USETW(((usb_status_t *)buf)->wStatus, UDS_SELF_POWERED); + totlen = 2; + } + break; + case C(UR_GET_STATUS, UT_READ_INTERFACE): + case C(UR_GET_STATUS, UT_READ_ENDPOINT): + /* Get Status from interface, endpoint, 9.4.5 */ + if (len > 1) { + USETW(((usb_status_t *)buf)->wStatus, 0); + totlen = 2; + } + break; + case C(UR_SET_ADDRESS, UT_WRITE_DEVICE): + /* Set Address, 9.4.6 */ + DPRINTF(D_MSG, ("UR_SET_ADDRESS ")); + if (value >= USB_MAX_DEVICES) { + error = USBD_IOERROR; + goto ret; + } + sc->sc_addr = value; + break; + case C(UR_SET_CONFIG, UT_WRITE_DEVICE): + /* Set Configuration, 9.4.7 */ + DPRINTF(D_MSG, ("UR_SET_CONFIG ")); + if (value != 0 && value != 1) { + error = USBD_IOERROR; + goto ret; + } + sc->sc_conf = value; + break; + case C(UR_SET_DESCRIPTOR, UT_WRITE_DEVICE): + /* Set Descriptor, 9.4.8, not supported */ + DPRINTF(D_MSG, ("UR_SET_DESCRIPTOR,WRITE_DEVICE not supported\n")); + 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): + /* Set Feature, 9.4.9, not supported */ + DPRINTF(D_MSG, ("UR_SET_FEATURE not supported\n")); + error = USBD_IOERROR; + break; + case C(UR_SET_INTERFACE, UT_WRITE_INTERFACE): + /* Set Interface, 9.4.10, not supported */ + break; + case C(UR_SYNCH_FRAME, UT_WRITE_ENDPOINT): + /* Synch Frame, 9.4.11, not supported */ + break; + + /* + * Hub specific requests + */ + case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_DEVICE): + /* Clear Hub Feature, 11.16.2.1, not supported */ + DPRINTF(D_MSG, ("ClearHubFeature not supported\n")); + break; + case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_OTHER): + /* Clear Port Feature, 11.16.2.2 */ + if (index != 1) { + error = USBD_IOERROR; + goto ret; + } + switch (value) { + case UHF_PORT_POWER: + DPRINTF(D_MSG, ("POWER_OFF ")); + sc->sc_powerstat = POWER_OFF; + /* x68k Nereid USB controller needs it */ + if (sc->sc_enable_power) + sc->sc_enable_power(sc, sc->sc_powerstat); + break; + case UHF_PORT_SUSPEND: + DPRINTF(D_MSG, ("SUSPEND ")); + sl11write(sc, SL11_CTRL, + sl11read(sc, SL11_CTRL) & ~SL11_CTRL_SUSPEND); + break; + case UHF_C_PORT_CONNECTION: + sc->sc_change &= ~UPS_C_CONNECT_STATUS; + break; + case UHF_C_PORT_RESET: + sc->sc_change &= ~UPS_C_PORT_RESET; + break; + case UHF_PORT_ENABLE: + break; + case UHF_C_PORT_SUSPEND: + case UHF_C_PORT_ENABLE: + case UHF_C_PORT_OVER_CURRENT: + default: + printf("ClrPortFeatERR:value=0x%x ", value); + error = USBD_IOERROR; + break; + } + //DPRINTF(D_XFER, ("CH=%04x ", sc->sc_change)); + break; + case C(UR_GET_BUS_STATE, UT_READ_CLASS_OTHER): + /* Get Bus State, 11.16.2.3, not supported */ + /* shall return a STALL... */ + break; + case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE): + /* Get Hub Descriptor, 11.16.2.4 */ + if (value != 0) { + error = USBD_IOERROR; + goto ret; + } + l = min(len, USB_HUB_DESCRIPTOR_SIZE); + totlen = l; + memcpy(buf, &slhci_hubd, l); + break; + case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE): + /* Get Hub Status, 11.16.2.5 */ + DPRINTF(D_MSG, ("UR_GET_STATUS RCD")); + if (len != 4) { + error = USBD_IOERROR; + goto ret; + } + memset(buf, 0, len); + totlen = len; + break; + case C(UR_GET_STATUS, UT_READ_CLASS_OTHER): + /* Get Port Status, 11.16.2.6 */ + if (index != 1 || len != 4) { + printf("index=%d,len=%d ", index, len); + error = USBD_IOERROR; + goto ret; + } + /* + * change + * o port is always enabled. + * o cannot detect over current. + */ + s = splusb(); + sc->sc_change &= ~(UPS_C_CONNECT_STATUS | UPS_C_PORT_RESET); + if ((sc->sc_flags & SLF_INSERT)) { + sc->sc_flags &= ~SLF_INSERT; + sc->sc_change |= UPS_C_CONNECT_STATUS; + } + if ((sc->sc_flags & SLF_RESET)) { + sc->sc_flags &= ~SLF_RESET; + sc->sc_change |= UPS_C_PORT_RESET; + } + splx(s); + /* + * XXX It can recognize that device is detached, + * while there is sl11_speed() here. + */ + if (sc->sc_change) + sl11_speed(sc); + /* + * status + * o port is always enabled. + * o cannot detect over current. + */ + status = 0; + if (sc->sc_connect) + status |= UPS_CURRENT_CONNECT_STATUS | UPS_PORT_ENABLED; + r = sl11read(sc, SL11_CTRL); + if (r & SL11_CTRL_SUSPEND) + status |= UPS_SUSPEND; + if (sc->sc_powerstat) + status |= UPS_PORT_POWER; + if (!sc->sc_fullspeed) + status |= UPS_LOW_SPEED; + + //DPRINTF(D_XFER, ("ST=%04x,CH=%04x ", status, sc->sc_change)); + USETW(ps.wPortStatus, status); + USETW(ps.wPortChange, sc->sc_change); + l = min(len, sizeof(ps)); + memcpy(buf, &ps, l); + totlen = l; + break; + case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE): + /* Set Hub Descriptor, 11.16.2.7, not supported */ + /* STALL ? */ + error = USBD_IOERROR; + break; + case C(UR_SET_FEATURE, UT_WRITE_CLASS_DEVICE): + /* Set Hub Feature, 11.16.2.8, not supported */ + break; + case C(UR_SET_FEATURE, UT_WRITE_CLASS_OTHER): + /* Set Port Feature, 11.16.2.9 */ + if (index != 1) { + printf("index=%d ", index); + error = USBD_IOERROR; + goto ret; + } + switch (value) { + case UHF_PORT_RESET: + DPRINTF(D_MSG, ("PORT_RESET ")); + sl11_reset(sc); + sl11_speed(sc); + sc->sc_change = 0; + break; + case UHF_PORT_POWER: + DPRINTF(D_MSG, ("PORT_POWER ")); + sc->sc_powerstat = POWER_ON; + /* x68k Nereid USB controller needs it */ + if (sc->sc_enable_power) + sc->sc_enable_power(sc, sc->sc_powerstat); + delay_ms(25); + break; + default: + printf("SetPortFeatERR=0x%x ", value); + error = USBD_IOERROR; + break; + } + break; + default: + DPRINTF(D_MSG, ("ioerr(UR=%02x,UT=%02x) ", + req->bRequest, req->bmRequestType)); + error = USBD_IOERROR; + goto ret; + } + xfer->actlen = totlen; + error = USBD_NORMAL_COMPLETION; + ret: + xfer->status = error; + s = splusb(); + usb_transfer_complete(xfer); + splx(s); + return USBD_IN_PROGRESS; +} + +void +slhci_root_ctrl_abort(usbd_xfer_handle xfer) +{ + DPRINTF(D_TRACE, ("SLRCabort ")); +} + +void +slhci_root_ctrl_close(usbd_pipe_handle pipe) +{ + DPRINTF(D_TRACE, ("SLRCclose ")); +} + +void +slhci_root_ctrl_done(usbd_xfer_handle xfer) +{ + DPRINTF(D_TRACE, ("SLRCdone\n")); +} + +static usbd_status +slhci_root_intr_transfer(usbd_xfer_handle xfer) +{ + usbd_status error; + + DPRINTF(D_TRACE, ("SLRItransfer ")); + + /* Insert last in queue */ + error = usb_insert_transfer(xfer); + if (error) + return error; + + /* + * Pipe isn't running (otherwise error would be USBD_INPROG), + * start first. + */ + return slhci_root_intr_start(STAILQ_FIRST(&xfer->pipe->queue)); +} + +static usbd_status +slhci_root_intr_start(usbd_xfer_handle xfer) +{ + usbd_pipe_handle pipe = xfer->pipe; + struct slhci_softc *sc = (struct slhci_softc *)pipe->device->bus; + + DPRINTF(D_TRACE, ("SLRIstart ")); + + sc->sc_interval = MS_TO_TICKS(xfer->pipe->endpoint->edesc->bInterval); + usb_callout(sc->sc_poll_handle, sc->sc_interval, slhci_poll_hub, xfer); + sc->sc_intr_xfer = xfer; + return USBD_IN_PROGRESS; +} + +static void +slhci_root_intr_abort(usbd_xfer_handle xfer) +{ + DPRINTF(D_TRACE, ("SLRIabort ")); +} + +static void +slhci_root_intr_close(usbd_pipe_handle pipe) +{ + struct slhci_softc *sc = (struct slhci_softc *)pipe->device->bus; + + DPRINTF(D_TRACE, ("SLRIclose ")); + + usb_uncallout(sc->sc_poll_handle, slhci_poll_hub, sc->sc_intr_xfer); + sc->sc_intr_xfer = NULL; +} + +static void +slhci_root_intr_done(usbd_xfer_handle xfer) +{ + //DPRINTF(D_XFER, ("RIdn ")); +} + +static usbd_status +slhci_device_ctrl_transfer(usbd_xfer_handle xfer) +{ + usbd_status error; + + DPRINTF(D_TRACE, ("C")); + + error = usb_insert_transfer(xfer); + if (error) + return error; + + return slhci_device_ctrl_start(STAILQ_FIRST(&xfer->pipe->queue)); +} + +static usbd_status +slhci_device_ctrl_start(usbd_xfer_handle xfer) +{ + usb_device_request_t *req = &xfer->request; + usbd_pipe_handle pipe = xfer->pipe; + struct slhci_softc *sc = (struct slhci_softc *)pipe->device->bus; + usbd_status status = USBD_NORMAL_COMPLETION; + u_char *buf; + int pid = SL11_PID_OUT; + int len, actlen, size; + int s; + u_int8_t toggle = 0; + + DPRINTF(D_TRACE, ("st ")); +#ifdef SLHCI_DEBUG + if ((slhci_debug & D_TRACE)) + print_req_hub(req); +#endif + + /* SETUP transaction */ + if (slhci_transaction(sc, pipe, SL11_PID_SETUP, + sizeof(*req), (u_char*)req, toggle) == -1) { + status = USBD_IOERROR; + goto ret; + } + toggle ^= SL11_EPCTRL_DATATOGGLE; + + /* DATA transaction */ + actlen = 0; + len = UGETW(req->wLength); + if (len) { + buf = xfer->buffer; + if (req->bmRequestType & UT_READ) + pid = SL11_PID_IN; + for (; actlen < len; ) { + size = min(len - actlen, 8/* Minimum size */); + if (slhci_transaction(sc, pipe, pid, size, buf, toggle) == -1) + break; + toggle ^= SL11_EPCTRL_DATATOGGLE; + buf += size; + actlen += size; + } + } + xfer->actlen = actlen; + + /* ACK (status) */ + if (pid == SL11_PID_IN) + pid = SL11_PID_OUT; + else + pid = SL11_PID_IN; + if (slhci_transaction(sc, pipe, pid, 0, NULL, toggle) == -1) + status = USBD_IOERROR; + + ret: + xfer->status = status; + +#ifdef SLHCI_DEBUG + if((slhci_debug & D_TRACE) && UGETW(req->wLength) > 0){ + int i; + for(i=0; i < UGETW(req->wLength); i++) + printf("%02x", ((unsigned char *)xfer->buffer)[i]); + printf(" "); + } +#endif + s = splusb(); + usb_transfer_complete(xfer); + splx(s); + return USBD_IN_PROGRESS; +} + +static void +slhci_device_ctrl_abort(usbd_xfer_handle xfer) +{ + DPRINTF(D_TRACE, ("Cab ")); + slhci_abort_xfer(xfer, USBD_CANCELLED); +} + +static void +slhci_device_ctrl_close(usbd_pipe_handle pipe) +{ + DPRINTF(D_TRACE, ("Ccl ")); +} + +static void +slhci_device_ctrl_done(usbd_xfer_handle xfer) +{ + DPRINTF(D_TRACE, ("Cdn ")); +} + +static usbd_status +slhci_device_intr_transfer(usbd_xfer_handle xfer) +{ + usbd_status error; + + DPRINTF(D_TRACE, ("INTRtrans ")); + + error = usb_insert_transfer(xfer); + if (error) + return error; + + return slhci_device_intr_start(STAILQ_FIRST(&xfer->pipe->queue)); +} + +static usbd_status +slhci_device_intr_start(usbd_xfer_handle xfer) +{ + usbd_pipe_handle pipe = xfer->pipe; + struct slhci_xfer *sx; + + DPRINTF(D_TRACE, ("INTRstart ")); + + sx = malloc(sizeof(*sx), M_USB, M_NOWAIT); + if (sx == NULL) + goto reterr; + memset(sx, 0, sizeof(*sx)); + sx->sx_xfer = xfer; + xfer->hcpriv = sx; + + /* initialize callout */ + usb_callout_init(sx->sx_callout_t); + usb_callout(sx->sx_callout_t, + MS_TO_TICKS(pipe->endpoint->edesc->bInterval), + slhci_poll_device, sx); + + /* ACK */ + return USBD_IN_PROGRESS; + + reterr: + return USBD_IOERROR; +} + +static void +slhci_poll_device(void *arg) +{ + struct slhci_xfer *sx = (struct slhci_xfer *)arg; + usbd_xfer_handle xfer = sx->sx_xfer; + usbd_pipe_handle pipe = xfer->pipe; + struct slhci_softc *sc = (struct slhci_softc *)pipe->device->bus; + void *buf; + int pid; + int r; + int s; + + DPRINTF(D_TRACE, ("pldev")); + + usb_callout(sx->sx_callout_t, + MS_TO_TICKS(pipe->endpoint->edesc->bInterval), + slhci_poll_device, sx); + + /* interrupt transfer */ + pid = (UE_GET_DIR(pipe->endpoint->edesc->bEndpointAddress) == UE_DIR_IN) + ? SL11_PID_IN : SL11_PID_OUT; + buf = xfer->buffer; + + r = slhci_transaction(sc, pipe, pid, xfer->length, buf, 0/*toggle*/); + if (r < 0) { + DPRINTF(D_MSG, ("%s error", __FUNCTION__)); + return; + } + /* no change, return NAK */ + if (r == 0) + return; + + xfer->status = USBD_NORMAL_COMPLETION; + s = splusb(); + xfer->device->bus->intr_context++; + usb_transfer_complete(xfer); + xfer->device->bus->intr_context--; + splx(s); +} + +static void +slhci_device_intr_abort(usbd_xfer_handle xfer) +{ + struct slhci_xfer *sx; + + DPRINTF(D_TRACE, ("INTRabort ")); + + sx = xfer->hcpriv; + if (sx) { + usb_uncallout(sx->sx_callout_t, slhci_poll_device, sx); + free(sx, M_USB); + xfer->hcpriv = NULL; + } else { + printf("%s: sx == NULL!\n", __FUNCTION__); + } + slhci_abort_xfer(xfer, USBD_CANCELLED); +} + +static void +slhci_device_intr_close(usbd_pipe_handle pipe) +{ + DPRINTF(D_TRACE, ("INTRclose ")); +} + +static void +slhci_device_intr_done(usbd_xfer_handle xfer) +{ + DPRINTF(D_TRACE, ("INTRdone ")); +} + +static usbd_status +slhci_device_isoc_transfer(usbd_xfer_handle xfer) +{ + DPRINTF(D_TRACE, ("S")); + return USBD_NORMAL_COMPLETION; +} + +static usbd_status +slhci_device_isoc_start(usbd_xfer_handle xfer) +{ + DPRINTF(D_TRACE, ("st ")); + return USBD_NORMAL_COMPLETION; +} + +static void +slhci_device_isoc_abort(usbd_xfer_handle xfer) +{ + DPRINTF(D_TRACE, ("Sab ")); +} + +static void +slhci_device_isoc_close(usbd_pipe_handle pipe) +{ + DPRINTF(D_TRACE, ("Scl ")); +} + +static void +slhci_device_isoc_done(usbd_xfer_handle xfer) +{ + DPRINTF(D_TRACE, ("Sdn ")); +} + +static usbd_status +slhci_device_bulk_transfer(usbd_xfer_handle xfer) +{ + DPRINTF(D_TRACE, ("B")); + return USBD_NORMAL_COMPLETION; +} + +static usbd_status +slhci_device_bulk_start(usbd_xfer_handle xfer) +{ + DPRINTF(D_TRACE, ("st ")); + return USBD_NORMAL_COMPLETION; +} + +static void +slhci_device_bulk_abort(usbd_xfer_handle xfer) +{ + DPRINTF(D_TRACE, ("Bab ")); +} + +static void +slhci_device_bulk_close(usbd_pipe_handle pipe) +{ + DPRINTF(D_TRACE, ("Bcl ")); +} + +static void +slhci_device_bulk_done(usbd_xfer_handle xfer) +{ + DPRINTF(D_TRACE, ("Bdn ")); +} + +#define DATA0_RD (0x03) +#define DATA0_WR (0x07) +#define SLHCI_TIMEOUT (5000) + +/* + * Do a transaction. + * return 1 if ACK, 0 if NAK, -1 if error. + */ +static int +slhci_transaction(struct slhci_softc *sc, usbd_pipe_handle pipe, + u_int8_t pid, int len, u_char *buf, u_int8_t toggle) +{ +#ifdef SLHCI_DEBUG + char str[64]; + int i; +#endif + int timeout; + int ls_via_hub = 0; + int pl; + u_int8_t isr; + u_int8_t result = 0; + u_int8_t devaddr = pipe->device->address; + u_int8_t endpointaddr = pipe->endpoint->edesc->bEndpointAddress; + u_int8_t endpoint; + u_int8_t cmd = DATA0_RD; + + endpoint = UE_GET_ADDR(endpointaddr); + DPRINTF(D_XFER, ("\n(%x,%d%s%d,%d) ", + pid, len, (pid == SL11_PID_IN) ? "<-" : "->", devaddr, endpoint)); + + /* Set registers */ + sl11write(sc, SL11_E0ADDR, 0x40); + sl11write(sc, SL11_E0LEN, len); + sl11write(sc, SL11_E0PID, (pid << 4) + endpoint); + sl11write(sc, SL11_E0DEV, devaddr); + + /* Set buffer unless PID_IN */ + if (pid != SL11_PID_IN) { + if (len > 0) + sl11write_region(sc, 0x40, buf, len); + cmd = DATA0_WR; + } + + /* timing ? */ + pl = (len >> 3) + 3; + + /* Low speed device via HUB */ + /* XXX does not work... */ + if ((sc->sc_fullspeed) && pipe->device->speed == USB_SPEED_LOW) { + pl = len + 16; + cmd |= SL11_EPCTRL_PREAMBLE; + + /* + * SL811HS/T rev 1.2 has a bug, when it got PID_IN + * from LowSpeed device via HUB. + */ + if (sc->sc_sltype == SLTYPE_SL811HS_R12 && pid == SL11_PID_IN) { + ls_via_hub = 1; + DPRINTF(D_MSG, ("LSvH ")); + } + } + + /* timing ? */ + if (sl11read(sc, SL811_CSOF) <= (u_int8_t)pl) + cmd |= SL11_EPCTRL_SOF; + + /* Transfer */ + sl11write(sc, SL11_ISR, 0xff); + sl11write(sc, SL11_E0CTRL, cmd | toggle); + + /* Polling */ + for (timeout = SLHCI_TIMEOUT; timeout; timeout--) { + isr = sl11read(sc, SL11_ISR); + if ((isr & SL11_ISR_USBA)) + break; + } + + /* Check result status */ + result = sl11read(sc, SL11_E0STAT); + if (!(result & SL11_EPSTAT_NAK) && ls_via_hub) { + /* Resend PID_IN within 20usec */ + sl11write(sc, SL11_ISR, 0xff); + sl11write(sc, SL11_E0CTRL, SL11_EPCTRL_ARM); + } + + sl11write(sc, SL11_ISR, 0xff); + + DPRINTF(D_XFER, ("t=%d i=%x ", SLHCI_TIMEOUT - timeout, isr)); +#ifdef SLHCI_DEBUG + bitmask_snprintf(result, + "\20\x8STALL\7NAK\6OV\5SETUP\4DATA1\3TIMEOUT\2ERR\1ACK", + str, sizeof(str)); + DPRINTF(D_XFER, ("STAT=%s ", str)); +#endif + + if ((result & SL11_EPSTAT_ERROR)) + return -1; + + if ((result & SL11_EPSTAT_NAK)) + return 0; + + /* Read buffer if PID_IN */ + if (pid == SL11_PID_IN && len > 0) { + sl11read_region(sc, buf, 0x40, len); +#ifdef SLHCI_DEBUG + for (i = 0; i < len; i++) + DPRINTF(D_XFER, ("%02X ", buf[i])); +#endif + } + + return 1; +} + +void +slhci_abort_xfer(usbd_xfer_handle xfer, usbd_status status) +{ + xfer->status = status; + usb_transfer_complete(xfer); +} + +void +slhci_device_clear_toggle(usbd_pipe_handle pipe) +{ + DPRINTF(D_TRACE, ("SLdevice_clear_toggle ")); +} + +#ifdef SLHCI_DEBUG +void +print_req(usb_device_request_t *r) +{ + char *xmes[]={ + "GETSTAT", + "CLRFEAT", + "res", + "SETFEAT", + "res", + "SETADDR", + "GETDESC", + "SETDESC", + "GETCONF", + "SETCONF", + "GETIN/F", + "SETIN/F", + "SYNC_FR" + }; + int req, type, value, index, len; + + req = r->bRequest; + type = r->bmRequestType; + value = UGETW(r->wValue); + index = UGETW(r->wIndex); + len = UGETW(r->wLength); + + printf("%x,%s,v=%d,i=%d,l=%d ", + type, xmes[req], value, index, len); +} + +void +print_req_hub(usb_device_request_t *r) +{ + struct { + int req; + int type; + char *str; + } conf[] = { + { 1, 0x20, "ClrHubFeat" }, + { 1, 0x23, "ClrPortFeat" }, + { 2, 0xa3, "GetBusState" }, + { 6, 0xa0, "GetHubDesc" }, + { 0, 0xa0, "GetHubStat" }, + { 0, 0xa3, "GetPortStat" }, + { 7, 0x20, "SetHubDesc" }, + { 3, 0x20, "SetHubFeat" }, + { 3, 0x23, "SetPortFeat" }, + {-1, 0, NULL}, + }; + int i; + int value, index, len; + + value = UGETW(r->wValue); + index = UGETW(r->wIndex); + len = UGETW(r->wLength); + for (i = 0; ; i++) { + if (conf[i].req == -1 ) + return print_req(r); + if (r->bmRequestType == conf[i].type && r->bRequest == conf[i].req) { + printf("%s", conf[i].str); + break; + } + } + printf(",v=%d,i=%d,l=%d ", value, index, len); +} + +void +print_dumpreg(struct slhci_softc *sc) +{ + printf("00=%02x,01=%02x,02=%02x,03=%02x,04=%02x," + "08=%02x,09=%02x,0A=%02x,0B=%02x,0C=%02x,", + sl11read(sc, 0), sl11read(sc, 1), + sl11read(sc, 2), sl11read(sc, 3), + sl11read(sc, 4), sl11read(sc, 8), + sl11read(sc, 9), sl11read(sc, 10), + sl11read(sc, 11), sl11read(sc, 12) + ); + printf("CR1=%02x,IER=%02x,0D=%02x,0E=%02x,0F=%02x ", + sl11read(sc, 5), sl11read(sc, 6), + sl11read(sc, 13), sl11read(sc, 14), sl11read(sc, 15) + ); +} + +void +print_xfer(usbd_xfer_handle xfer) +{ + printf("xfer: length=%d, actlen=%d, flags=%x, timeout=%d,", + xfer->length, xfer->actlen, xfer->flags, xfer->timeout); + printf("request{ "); + print_req_hub(&xfer->request); + printf("} "); +} +#endif /* SLHCI_DEBUG */ diff --git a/sys/legacy/dev/usb/sl811hsreg.h b/sys/legacy/dev/usb/sl811hsreg.h new file mode 100644 index 0000000..f9c0328 --- /dev/null +++ b/sys/legacy/dev/usb/sl811hsreg.h @@ -0,0 +1,126 @@ +/* $NetBSD$ */ +/* $FreeBSD$ */ + + +/* + * Copyright (c) 2001 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Tetsuya Isaki. + * + * 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. + */ + +/* + * ScanLogic SL811HS/T USB Host Controller + */ + +#define SL11_IDX_ADDR (0x00) +#define SL11_IDX_DATA (0x01) +#define SL11_PORTSIZE (0x02) + +#define SL11_E0BASE (0x00) /* Base of Control0 */ +#define SL11_E0CTRL (0x00) /* Host Control Register */ +#define SL11_E0ADDR (0x01) /* Host Base Address */ +#define SL11_E0LEN (0x02) /* Host Base Length */ +#define SL11_E0STAT (0x03) /* USB Status (Read) */ +#define SL11_E0PID SL11_E0STAT /* Host PID, Device Endpoint (Write) */ +#define SL11_E0CONT (0x04) /* Transfer Count (Read) */ +#define SL11_E0DEV SL11_E0CONT /* Host Device Address (Write) */ + +#define SL11_E1BASE (0x08) /* Base of Control1 */ +#define SL11_E1CTRL (SL11_E1BASE + SL11_E0CTRL) +#define SL11_E1ADDR (SL11_E1BASE + SL11_E0ADDR) +#define SL11_E1LEN (SL11_E1BASE + SL11_E0LEN) +#define SL11_E1STAT (SL11_E1BASE + SL11_E0STAT) +#define SL11_E1PID (SL11_E1BASE + SL11_E0PID) +#define SL11_E1CONT (SL11_E1BASE + SL11_E0CONT) +#define SL11_E1DEV (SL11_E1BASE + SL11_E0DEV) + +#define SL11_CTRL (0x05) /* Control Register1 */ +#define SL11_IER (0x06) /* Interrupt Enable Register */ +#define SL11_ISR (0x0d) /* Interrupt Status Register */ +#define SL11_DATA (0x0e) /* SOF Counter Low (Write) */ +#define SL11_REV SL11_DATA /* HW Revision Register (Read) */ +#define SL811_CSOF (0x0f) /* SOF Counter High(R), Control2(W) */ +#define SL11_MEM (0x10) /* Memory Buffer (0x10 - 0xff) */ + +#define SL11_EPCTRL_ARM (0x01) +#define SL11_EPCTRL_ENABLE (0x02) +#define SL11_EPCTRL_DIRECTION (0x04) +#define SL11_EPCTRL_ISO (0x10) +#define SL11_EPCTRL_SOF (0x20) +#define SL11_EPCTRL_DATATOGGLE (0x40) +#define SL11_EPCTRL_PREAMBLE (0x80) + +#define SL11_EPPID_PIDMASK (0xf0) +#define SL11_EPPID_EPMASK (0x0f) + +#define SL11_EPSTAT_ACK (0x01) +#define SL11_EPSTAT_ERROR (0x02) +#define SL11_EPSTAT_TIMEOUT (0x04) +#define SL11_EPSTAT_SEQUENCE (0x08) +#define SL11_EPSTAT_SETUP (0x10) +#define SL11_EPSTAT_OVERFLOW (0x20) +#define SL11_EPSTAT_NAK (0x40) +#define SL11_EPSTAT_STALL (0x80) + +#define SL11_CTRL_ENABLESOF (0x01) +#define SL11_CTRL_EOF2 (0x04) +#define SL11_CTRL_RESETENGINE (0x08) +#define SL11_CTRL_JKSTATE (0x10) +#define SL11_CTRL_LOWSPEED (0x20) +#define SL11_CTRL_SUSPEND (0x40) + +#define SL11_IER_USBA (0x01) /* USB-A done */ +#define SL11_IER_USBB (0x02) /* USB-B done */ +#define SL11_IER_BABBLE (0x04) /* Babble detection */ +#define SL11_IER_SOFTIMER (0x10) /* 1ms SOF timer */ +#define SL11_IER_INSERT (0x20) /* Slave Insert/Remove detection */ +#define SL11_IER_RESET (0x40) /* USB Reset/Resume */ + +#define SL11_ISR_USBA (0x01) /* USB-A done */ +#define SL11_ISR_USBB (0x02) /* USB-B done */ +#define SL11_ISR_BABBLE (0x04) /* Babble detection */ +#define SL11_ISR_SOFTIMER (0x10) /* 1ms SOF timer */ +#define SL11_ISR_INSERT (0x20) /* Slave Insert/Remove detection */ +#define SL11_ISR_RESET (0x40) /* USB Reset/Resume */ +#define SL11_ISR_DATA (0x80) /* Value of the Data+ pin */ + +#define SL11_REV_USBA (0x01) /* USB-A */ +#define SL11_REV_USBB (0x02) /* USB-B */ +#define SL11_REV_REVMASK (0xf0) /* HW Revision */ +#define SL11_REV_REVSL11H (0x00) /* HW is SL11H */ +#define SL11_REV_REVSL811HS (0x10) /* HW is SL811HS */ + +#define SL811_CSOF_SOFMASK (0x3f) /* SOF High Counter */ +#define SL811_CSOF_POLARITY (0x40) /* Change polarity */ +#define SL811_CSOF_MASTER (0x80) /* Master/Slave selection */ + diff --git a/sys/legacy/dev/usb/sl811hsvar.h b/sys/legacy/dev/usb/sl811hsvar.h new file mode 100644 index 0000000..6f5e6d6 --- /dev/null +++ b/sys/legacy/dev/usb/sl811hsvar.h @@ -0,0 +1,107 @@ +/* $NetBSD$ */ +/* $FreeBSD$ */ + +/* + * Copyright (c) 2001 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Tetsuya Isaki. + * + * 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. + */ + +/* + * ScanLogic SL811HS/T USB Host Controller + */ + +#define MS_TO_TICKS(ms) ((ms) * hz / 1000) +#define delay_ms(X) \ + pause("slhci", MS_TO_TICKS(X)) + +#define SL11_PID_OUT (0x1) +#define SL11_PID_IN (0x9) +#define SL11_PID_SOF (0x5) +#define SL11_PID_SETUP (0xd) + +struct slhci_xfer { + usbd_xfer_handle sx_xfer; + usb_callout_t sx_callout_t; +}; + +struct slhci_softc { + struct usbd_bus sc_bus; + bus_space_tag_t sc_iot; + bus_space_handle_t sc_ioh; + +#ifdef __FreeBSD__ + void *ih; + struct resource *io_res; + struct resource *irq_res; +#endif + + void (*sc_enable_power)(void *, int); + void (*sc_enable_intr)(void *, int); + void *sc_arg; + int sc_powerstat; +#define POWER_ON (1) +#define POWER_OFF (0) +#define INTR_ON (1) +#define INTR_OFF (0) + + struct device *sc_parent; /* parent device */ + int sc_sltype; /* revision */ +#define SLTYPE_SL11H (0x00) +#define SLTYPE_SL811HS (0x01) +#define SLTYPE_SL811HS_R12 SLTYPE_SL811HS +#define SLTYPE_SL811HS_R14 (0x02) + + u_int8_t sc_addr; /* device address of root hub */ + u_int8_t sc_conf; + STAILQ_HEAD(, usbd_xfer) sc_free_xfers; + + /* Information for the root hub interrupt pipe */ + int sc_interval; + usbd_xfer_handle sc_intr_xfer; + usb_callout_t sc_poll_handle; + + int sc_flags; +#define SLF_RESET (0x01) +#define SLF_INSERT (0x02) +#define SLF_ATTACHED (0x04) + + /* Root HUB specific members */ + int sc_fullspeed; + int sc_connect; /* XXX */ + int sc_change; +}; + +int sl811hs_find(struct slhci_softc *); +int slhci_attach(struct slhci_softc *); +int slhci_intr(void *); diff --git a/sys/legacy/dev/usb/slhci_pccard.c b/sys/legacy/dev/usb/slhci_pccard.c new file mode 100644 index 0000000..794b81d --- /dev/null +++ b/sys/legacy/dev/usb/slhci_pccard.c @@ -0,0 +1,206 @@ +/*- + * Copyright (C) 2005 Takanori Watanabe. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY 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 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 <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/malloc.h> +#include <sys/sema.h> +#include <sys/taskqueue.h> +#include <vm/uma.h> +#include <machine/stdarg.h> +#include <machine/resource.h> +#include <machine/bus.h> +#include <sys/rman.h> +#include <dev/pccard/pccard_cis.h> +#include <dev/pccard/pccardreg.h> +#include <dev/pccard/pccardvar.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdivar.h> +#include <dev/usb/usb_mem.h> +#include <dev/usb/usb_port.h> +#include <dev/usb/sl811hsvar.h> +#include "pccarddevs.h" + +__FBSDID("$FreeBSD$"); + +static void slhci_pccard_intr(void *arg); + +static const struct pccard_product slhci_pccard_products[] = { + PCMCIA_CARD(RATOC, REX_CFU1), + {NULL} +}; + +static int +slhci_pccard_probe(device_t dev) +{ + const struct pccard_product *pp; + u_int32_t fcn = PCCARD_FUNCTION_UNSPEC; + int error = 0; + + if ((error = pccard_get_function(dev, &fcn))) + return error; + + /* if it says its a disk we should register it */ + if (fcn == PCCARD_FUNCTION_DISK) + return 0; + + /* match other devices here, primarily cdrom/dvd rom */ + if ((pp = pccard_product_lookup(dev, slhci_pccard_products, + sizeof(slhci_pccard_products[0]), NULL))) { + if (pp->pp_name) + device_set_desc(dev, pp->pp_name); + return 0; + } + return ENXIO; +} + +static int +slhci_pccard_detach(device_t dev) +{ + struct slhci_softc *sc = device_get_softc(dev); + bus_generic_detach(dev); + + if (sc->ih) + bus_teardown_intr(dev, sc->irq_res, sc->ih); + if (sc->io_res) + bus_release_resource(dev, SYS_RES_IOPORT, 0, sc->io_res); + if (sc->irq_res) + bus_release_resource(dev, SYS_RES_IRQ, 0, sc->irq_res); + if (sc->sc_bus.bdev) + device_delete_child(dev, sc->sc_bus.bdev); + return 0; +} + +static int +slhci_pccard_attach(device_t dev) +{ + struct slhci_softc *sc = device_get_softc(dev); + int error = ENXIO; + int rid = 0; + if ((sc->io_res = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &rid, RF_ACTIVE)) == NULL) { + return ENOMEM; + } + sc->sc_iot = rman_get_bustag(sc->io_res); + sc->sc_ioh = rman_get_bushandle(sc->io_res); + + if (sl811hs_find(sc) == -1) { + error = ENXIO; + goto out; + } + + rid = 0; + if ((sc->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_SHAREABLE | RF_ACTIVE)) == NULL) { + printf("CANNOT ALLOC IRQ\n"); + error = ENOMEM; + goto out; + } + sc->sc_iot = rman_get_bustag(sc->io_res); + sc->sc_ioh = rman_get_bushandle(sc->io_res); + sc->sc_bus.bdev = device_add_child(dev, "usb", -1); + if (!sc->sc_bus.bdev) { + device_printf(dev, "Could not add USB device\n"); + } + device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); + error = bus_setup_intr(dev, sc->irq_res, INTR_TYPE_BIO, NULL, slhci_pccard_intr, sc, &sc->ih); + if (error) + goto out; +#if 1 + + if (slhci_attach(sc) == -1) { + printf("MI attach NO\n"); + goto out; + } + + error = device_probe_and_attach(sc->sc_bus.bdev); + + if (error) { + printf("Probing USB bus %x\n", error); + goto out; + } +#endif + printf("ATTACHED\n"); + return 0; +out: + slhci_pccard_detach(dev); + return error; +} + +#if 0 +static void +slhci_pccard_enable_power(void *arg, int mode) +{ +#if 0 + struct slhci_softc *sc = arg; + u_int8_t r; +#endif +} + +static void +slhci_pccard_enable_intr(void *arg, int mode) +{ +#if 0 + struct slhci_softc *sc = arg; + u_int8_t r; +#endif +} + +#endif +static void +slhci_pccard_intr(void *arg) +{ +#if 1 + struct slhci_softc *sc = arg; + sc = sc; + slhci_intr(sc); +#endif +} + +static device_method_t slhci_pccard_methods[] = { + /* device interface */ + DEVMETHOD(device_probe, slhci_pccard_probe), + DEVMETHOD(device_attach, slhci_pccard_attach), + DEVMETHOD(device_detach, slhci_pccard_detach), + + {0, 0} +}; + +static driver_t slhci_pccard_driver = { + "slhci", + slhci_pccard_methods, + sizeof(struct slhci_softc), +}; + +devclass_t slhci_devclass; + +DRIVER_MODULE(slhci, pccard, slhci_pccard_driver, slhci_devclass, 0, 0); +MODULE_DEPEND(slhci, usb, 1, 1, 1); diff --git a/sys/legacy/dev/usb/u3g.c b/sys/legacy/dev/usb/u3g.c new file mode 100644 index 0000000..8c6f67e --- /dev/null +++ b/sys/legacy/dev/usb/u3g.c @@ -0,0 +1,799 @@ +/* + * Copyright (c) 2008 AnyWi Technologies + * Author: Andrea Guzzo <aguzzo@anywi.com> + * * based on uark.c 1.1 2006/08/14 08:30:22 jsg * + * * parts from ubsa.c 183348 2008-09-25 12:00:56Z phk * + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $FreeBSD$ + */ + +/* + * Notes: + * - The detour through the tty layer is ridiculously expensive wrt buffering + * due to the high speeds. + * We should consider adding a simple r/w device which allows attaching of PPP + * in a more efficient way. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/ioccom.h> +#include <sys/fcntl.h> +#include <sys/conf.h> +#include <sys/tty.h> +#include <sys/file.h> +#include <sys/selinfo.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> + +#include <dev/usb/ucomvar.h> + +#if __FreeBSD_version >= 800000 +#include "opt_u3g.h" +#endif +#include "usbdevs.h" + +//#define U3G_DEBUG +#ifdef U3G_DEBUG +#define DPRINTF(x...) do { if (u3gdebug) device_printf(sc->sc_dev, ##x); } while (0) +int u3gdebug = 1; +#else +#define DPRINTF(x...) /* nop */ +#endif + +#define U3G_MAXPORTS 4 +#define U3G_CONFIG_INDEX 0 + +struct u3g_softc { + struct ucom_softc sc_ucom[U3G_MAXPORTS]; + device_t sc_dev; + usbd_device_handle sc_udev; + u_int8_t sc_speed; + u_int8_t sc_flags; + u_char sc_numports; +}; + +static int u3g_open(void *addr, int portno); +static void u3g_close(void *addr, int portno); + +struct ucom_callback u3g_callback = { + NULL, + NULL, + NULL, + NULL, + u3g_open, + u3g_close, + NULL, + NULL, +}; + + +struct u3g_speeds_s { + u_int32_t ispeed; + u_int32_t ospeed; +}; + +static const struct u3g_speeds_s u3g_speeds[] = { +#define U3GSP_GPRS 0 + {64000, 64000}, +#define U3GSP_EDGE 1 + {384000, 64000}, +#define U3GSP_CDMA 2 + {384000, 64000}, +#define U3GSP_UMTS 3 + {384000, 64000}, +#define U3GSP_HSDPA 4 + {1200000, 384000}, +#define U3GSP_HSUPA 5 + {1200000, 384000}, +#define U3GSP_HSPA 6 + {7200000, 384000}, +}; + +#define U3GIBUFSIZE 1024 +#define U3GOBUFSIZE 1024 + +/* + * Various supported device vendors/products. + */ +struct u3g_dev_type_s { + struct usb_devno devno; + u_int8_t speed; + u_int8_t flags; +#define U3GFL_NONE 0x00 +#define U3GFL_HUAWEI_INIT 0x01 // Requires init command (Huawei cards) +#define U3GFL_SCSI_EJECT 0x02 // Requires SCSI eject command (Novatel) +#define U3GFL_SIERRA_INIT 0x04 // Requires init command (Sierra cards) +#define U3GFL_CMOTECH_INIT 0x08 // Requires init command (CMOTECH cards) +#define U3GFL_STUB_WAIT 0x80 // Device reappears after a short delay +}; + +// Note: The entries marked with XXX should be checked for the correct speed +// indication to set the buffer sizes. +static const struct u3g_dev_type_s u3g_devs[] = { + /* OEM: Option */ + {{ USB_VENDOR_OPTION, USB_PRODUCT_OPTION_GT3G }, U3GSP_UMTS, U3GFL_NONE }, + {{ USB_VENDOR_OPTION, USB_PRODUCT_OPTION_GT3GQUAD }, U3GSP_UMTS, U3GFL_NONE }, + {{ USB_VENDOR_OPTION, USB_PRODUCT_OPTION_GT3GPLUS }, U3GSP_UMTS, U3GFL_NONE }, + {{ USB_VENDOR_OPTION, USB_PRODUCT_OPTION_GTMAX36 }, U3GSP_HSDPA, U3GFL_NONE }, + {{ USB_VENDOR_OPTION, USB_PRODUCT_OPTION_GTMAXHSUPA }, U3GSP_HSDPA, U3GFL_NONE }, + {{ USB_VENDOR_OPTION, USB_PRODUCT_OPTION_VODAFONEMC3G }, U3GSP_UMTS, U3GFL_NONE }, + /* OEM: Qualcomm, Inc. */ + {{ USB_VENDOR_QUALCOMMINC, USB_PRODUCT_QUALCOMMINC_ZTE_STOR }, U3GSP_CDMA, U3GFL_SCSI_EJECT }, + {{ USB_VENDOR_QUALCOMMINC, USB_PRODUCT_QUALCOMMINC_CDMA_MSM }, U3GSP_CDMA, U3GFL_SCSI_EJECT }, + /* OEM: Huawei */ + {{ USB_VENDOR_HUAWEI, USB_PRODUCT_HUAWEI_MOBILE }, U3GSP_HSDPA, U3GFL_HUAWEI_INIT }, + {{ USB_VENDOR_HUAWEI, USB_PRODUCT_HUAWEI_E220 }, U3GSP_HSPA, U3GFL_HUAWEI_INIT }, + /* OEM: Novatel */ + {{ USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_CDMA_MODEM }, U3GSP_CDMA, U3GFL_SCSI_EJECT }, + {{ USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_ES620 }, U3GSP_UMTS, U3GFL_SCSI_EJECT }, // XXX + {{ USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_MC950D }, U3GSP_HSUPA, U3GFL_SCSI_EJECT }, + {{ USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_U720 }, U3GSP_UMTS, U3GFL_SCSI_EJECT }, // XXX + {{ USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_U727 }, U3GSP_UMTS, U3GFL_SCSI_EJECT }, // XXX + {{ USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_U740 }, U3GSP_HSDPA, U3GFL_SCSI_EJECT }, + {{ USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_U740_2 }, U3GSP_HSDPA, U3GFL_SCSI_EJECT }, + {{ USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_U870 }, U3GSP_UMTS, U3GFL_SCSI_EJECT }, // XXX + {{ USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_V620 }, U3GSP_UMTS, U3GFL_SCSI_EJECT }, // XXX + {{ USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_V640 }, U3GSP_UMTS, U3GFL_SCSI_EJECT }, // XXX + {{ USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_V720 }, U3GSP_UMTS, U3GFL_SCSI_EJECT }, // XXX + {{ USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_V740 }, U3GSP_HSDPA, U3GFL_SCSI_EJECT }, + {{ USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_X950D }, U3GSP_HSUPA, U3GFL_SCSI_EJECT }, + {{ USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_XU870 }, U3GSP_HSDPA, U3GFL_SCSI_EJECT }, + {{ USB_VENDOR_NOVATEL, USB_PRODUCT_NOVATEL_ZEROCD }, U3GSP_HSUPA, U3GFL_SCSI_EJECT }, + {{ USB_VENDOR_DELL, USB_PRODUCT_DELL_U740 }, U3GSP_HSDPA, U3GFL_SCSI_EJECT }, + /* OEM: Merlin */ + {{ USB_VENDOR_MERLIN, USB_PRODUCT_MERLIN_V620 }, U3GSP_UMTS, U3GFL_NONE }, // XXX + /* OEM: Sierra Wireless: */ + {{ USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AIRCARD580 }, U3GSP_UMTS, U3GFL_NONE }, // XXX + {{ USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AIRCARD595 }, U3GSP_UMTS, U3GFL_NONE }, // XXX + {{ USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AC595U }, U3GSP_UMTS, U3GFL_NONE }, // XXX + {{ USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AC597E }, U3GSP_UMTS, U3GFL_NONE }, // XXX + {{ USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_C597 }, U3GSP_UMTS, U3GFL_NONE }, // XXX + {{ USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AC880 }, U3GSP_UMTS, U3GFL_NONE }, // XXX + {{ USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AC880E }, U3GSP_UMTS, U3GFL_NONE }, // XXX + {{ USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AC880U }, U3GSP_UMTS, U3GFL_NONE }, // XXX + {{ USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AC881 }, U3GSP_UMTS, U3GFL_NONE }, // XXX + {{ USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AC881E }, U3GSP_UMTS, U3GFL_NONE }, // XXX + {{ USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AC881U }, U3GSP_UMTS, U3GFL_NONE }, // XXX + {{ USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_EM5625 }, U3GSP_UMTS, U3GFL_NONE }, // XXX + {{ USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC5720 }, U3GSP_UMTS, U3GFL_NONE }, // XXX + {{ USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC5720_2 }, U3GSP_UMTS, U3GFL_NONE }, // XXX + {{ USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC5725 }, U3GSP_UMTS, U3GFL_NONE }, // XXX + {{ USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MINI5725 }, U3GSP_UMTS, U3GFL_NONE }, // XXX + {{ USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AIRCARD875 }, U3GSP_UMTS, U3GFL_NONE }, // XXX + {{ USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC8755 }, U3GSP_UMTS, U3GFL_NONE }, // XXX + {{ USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC8755_2 }, U3GSP_UMTS, U3GFL_NONE }, // XXX + {{ USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC8755_3 }, U3GSP_UMTS, U3GFL_NONE }, // XXX + {{ USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC8765 }, U3GSP_UMTS, U3GFL_NONE }, // XXX + {{ USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_AC875U }, U3GSP_UMTS, U3GFL_NONE }, // XXX + {{ USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC8775_2 }, U3GSP_HSDPA, U3GFL_NONE }, // XXX + {{ USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC8780 }, U3GSP_UMTS, U3GFL_NONE }, // XXX + {{ USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC8781 }, U3GSP_UMTS, U3GFL_NONE }, // XXX + {{ USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_TRUINSTALL }, U3GSP_UMTS, U3GFL_SIERRA_INIT }, + {{ USB_VENDOR_HP, USB_PRODUCT_HP_HS2300 }, U3GSP_HSDPA, U3GFL_NONE }, + /* OEM: CMOTECH */ + {{ USB_VENDOR_CMOTECH, USB_PRODUCT_CMOTECH_CGU628 }, U3GSP_HSDPA, U3GFL_CMOTECH_INIT }, + {{ USB_VENDOR_CMOTECH, USB_PRODUCT_CMOTECH_DISK }, U3GSP_HSDPA, U3GFL_NONE }, +}; +#define u3g_lookup(v, p) ((const struct u3g_dev_type_s *)usb_lookup(u3g_devs, v, p)) + +static int +u3g_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + const struct u3g_dev_type_s *u3g_dev_type; + + if (!uaa->iface) + return UMATCH_NONE; + + u3g_dev_type = u3g_lookup(uaa->vendor, uaa->product); + if (!u3g_dev_type) + return UMATCH_NONE; + + if (u3g_dev_type->flags&U3GFL_HUAWEI_INIT) { + /* If the interface class of the first interface is no longer + * mass storage the card has changed to modem (see u3g_attach() + * below). + */ + usb_interface_descriptor_t *id; + id = usbd_get_interface_descriptor(uaa->iface); + if (!id || id->bInterfaceClass == UICLASS_MASS) + return UMATCH_NONE; + } + + return UMATCH_VENDOR_PRODUCT_CONF_IFACE; +} + +static int +u3g_attach(device_t self) +{ + struct u3g_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + const struct u3g_dev_type_s *u3g_dev_type; + usbd_device_handle dev = uaa->device; + usb_interface_descriptor_t *id; + usb_endpoint_descriptor_t *ed; + int i, n; + usb_config_descriptor_t *cd; + char devnamefmt[32]; + +#if __FreeBSD_version < 700000 + char *devinfo = malloc(1024, M_USBDEV, M_WAITOK); + usbd_devinfo(dev, 0, devinfo); + device_printf(self, "%s\n", devinfo); + free(devinfo, M_USBDEV); +#endif + + /* get the config descriptor */ + cd = usbd_get_config_descriptor(dev); + if (cd == NULL) { + device_printf(self, "failed to get configuration descriptor\n"); + return ENXIO; + } + + sc->sc_dev = self; + sc->sc_udev = dev; + + u3g_dev_type = u3g_lookup(uaa->vendor, uaa->product); + sc->sc_flags = u3g_dev_type->flags; + sc->sc_speed = u3g_dev_type->speed; + + sprintf(devnamefmt,"U%d.%%d", device_get_unit(self)); + int portno = 0; + for (i = 0; i < uaa->nifaces && portno < U3G_MAXPORTS; i++) { + if (uaa->ifaces[i] == NULL) + continue; + + id = usbd_get_interface_descriptor(uaa->ifaces[i]); + if (id && id->bInterfaceClass == UICLASS_MASS) { + /* We attach to the interface instead of the device as + * some devices have a built-in SD card reader. + * Claim the first umass device (cdX) as it contains + * only Windows drivers anyway (CD-ROM), hiding it. + */ +#ifndef U3G_DEBUG + if (!bootverbose) + if (uaa->vendor == USB_VENDOR_HUAWEI) + if (id->bInterfaceNumber == 2) + uaa->ifaces[i] = NULL; +#endif + continue; + } + + int bulkin_no = -1, bulkout_no = -1; + int claim_iface = 0; + for (n = 0; n < id->bNumEndpoints && portno < U3G_MAXPORTS; n++) { + ed = usbd_interface2endpoint_descriptor(uaa->ifaces[i], n); + if (ed == NULL) + continue; + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN + && UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) + bulkin_no = ed->bEndpointAddress; + else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT + && UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) + bulkout_no = ed->bEndpointAddress; + + /* If we have found a pair of bulk-in/-out endpoints + * create a serial port for it. Note: We assume that + * the bulk-in and bulk-out endpoints appear in pairs. + */ + if (bulkin_no != -1 && bulkout_no != -1) { + struct ucom_softc *ucom = &sc->sc_ucom[portno]; + + ucom->sc_dev = self; + ucom->sc_udev = dev; + ucom->sc_iface = uaa->ifaces[i]; + ucom->sc_bulkin_no = bulkin_no; + ucom->sc_bulkout_no = bulkout_no; + ucom->sc_ibufsize = U3GIBUFSIZE; + ucom->sc_ibufsizepad = U3GIBUFSIZE; + ucom->sc_obufsize = U3GOBUFSIZE; + ucom->sc_opkthdrlen = 0; + + ucom->sc_callback = &u3g_callback; + ucom->sc_parent = sc; + ucom->sc_portno = portno; + + DPRINTF("port=%d iface=%d in=0x%x out=0x%x\n", + portno, i, + ucom->sc_bulkin_no, + ucom->sc_bulkout_no); +#if __FreeBSD_version < 700000 + ucom_attach_tty(ucom, MINOR_CALLOUT, devnamefmt, portno); +#elif __FreeBSD_version < 800000 + ucom_attach_tty(ucom, TS_CALLOUT, devnamefmt, portno); +#else + ucom_attach_tty(ucom, devnamefmt, portno); +#endif + + claim_iface = 1; + portno++; + bulkin_no = bulkout_no = -1; + } + } + if (claim_iface) + uaa->ifaces[i] = NULL; // claim the interface + } + sc->sc_numports = portno; + + device_printf(self, "configured %d serial ports (%s)\n", + sc->sc_numports, devnamefmt); + return 0; +} + +static int +u3g_detach(device_t self) +{ + struct u3g_softc *sc = device_get_softc(self); + int rv = 0; + int i; + + for (i = 0; i < sc->sc_numports; i++) { + sc->sc_ucom[i].sc_dying = 1; + rv = ucom_detach(&sc->sc_ucom[i]); + if (rv != 0) { + device_printf(self, "ucom_detach(U%d.%d\n", device_get_unit(self), i); + return rv; + } + } + + return 0; +} + +static int +u3g_open(void *addr, int portno) +{ +#if __FreeBSD_version < 800000 + /* Supply generous buffering for these cards to avoid disappointments + * when setting the speed incorrectly. Only do this for the first port + * assuming that the rest of the ports are used for diagnostics only + * anyway. + * Note: We abuse the fact that ucom sets the speed through + * ispeed/ospeed, not through ispeedwat/ospeedwat. + */ + if (portno == 0) { + struct u3g_softc *sc = addr; + struct ucom_softc *ucom = &sc->sc_ucom[portno]; + struct tty *tp = ucom->sc_tty; + + tp->t_ispeedwat = u3g_speeds[sc->sc_speed].ispeed; + tp->t_ospeedwat = u3g_speeds[sc->sc_speed].ospeed; + + /* Avoid excessive buffer sizes. + * XXX The values here should be checked. Lower them and see + * whether 'lost chars' messages appear. + */ + if (tp->t_ispeedwat > 384000) + tp->t_ispeedwat = 384000; + if (tp->t_ospeedwat > 384000) + tp->t_ospeedwat = 384000; + + ttsetwater(tp); + } +#endif + + return 0; +} + +static void +u3g_close(void *addr, int portno) +{ +#if __FreeBSD_version < 800000 + if (portno == 0) { /* see u3g_open() */ + /* Reduce the buffers allocated above again */ + struct u3g_softc *sc = addr; + struct ucom_softc *ucom = &sc->sc_ucom[portno]; + struct tty *tp = ucom->sc_tty; +#ifdef U3G_DEBUG + device_t self = sc->sc_dev; +#endif + + tp->t_ispeedwat = (speed_t)-1; + tp->t_ospeedwat = (speed_t)-1; + + ttsetwater(tp); + } +#endif +} + +static device_method_t u3g_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, u3g_match), + DEVMETHOD(device_attach, u3g_attach), + DEVMETHOD(device_detach, u3g_detach), + + { 0, 0 } +}; + +static driver_t u3g_driver = { + "ucom", + u3g_methods, + sizeof (struct u3g_softc) +}; + +DRIVER_MODULE(u3g, uhub, u3g_driver, ucom_devclass, usbd_driver_load, 0); +MODULE_DEPEND(u3g, usb, 1, 1, 1); +MODULE_DEPEND(u3g, ucom, UCOM_MINVER, UCOM_PREFVER, UCOM_MAXVER); +MODULE_VERSION(u3g, 1); + +/******************************************************************* + ****** Stub driver to hide devices that need to reinitialise ****** + *******************************************************************/ + +struct u3gstub_softc { + device_t sc_dev; + usbd_device_handle sc_udev; + usbd_pipe_handle sc_pipe_out, sc_pipe_in; + usbd_xfer_handle sc_xfer; +}; + +static int +u3gstub_huawei_init(struct u3gstub_softc *sc, struct usb_attach_arg *uaa) +{ + usb_device_request_t req; + + req.bmRequestType = UT_WRITE_DEVICE; + req.bRequest = UR_SET_FEATURE; + USETW(req.wValue, UF_DEVICE_REMOTE_WAKEUP); + USETW(req.wIndex, UHF_PORT_SUSPEND); + USETW(req.wLength, 0); + + (void) usbd_do_request(uaa->device, &req, 0); /* ignore any error */ + + return 1; +} + +static void +u3gstub_BBB_cb(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status err) +{ + struct u3gstub_softc *sc = (struct u3gstub_softc *) priv; + unsigned char cmd[13]; + + if (err) { + device_printf(sc->sc_dev, + "Failed to send CD eject command to " + "change to modem mode\n"); + } else { + usbd_setup_xfer(sc->sc_xfer, sc->sc_pipe_in, NULL, cmd, sizeof(cmd), + 0, USBD_DEFAULT_TIMEOUT, NULL); + int err = usbd_transfer(sc->sc_xfer) != USBD_NORMAL_COMPLETION; + if (err != USBD_NORMAL_COMPLETION && err != USBD_IN_PROGRESS) + DPRINTF("failed to start transfer (CSW)\n"); + } +} + +static int +u3gstub_scsi_eject(struct u3gstub_softc *sc, struct usb_attach_arg *uaa) +{ + /* See definition of umass_bbb_cbw_t in sys/dev/usb/umass.c and struct + * scsi_start_stop_unit in sys/cam/scsi/scsi_all.h . + */ + unsigned char cmd[31] = { + 0x55, 0x53, 0x42, 0x43, /* 0..3: Command Block Wrapper (CBW) signature */ + 0x01, 0x00, 0x00, 0x00, /* 4..7: CBW Tag, unique 32-bit number */ + 0x00, 0x00, 0x00, 0x00, /* 8..11: CBW Transfer Length, no data here */ + 0x00, /* 12: CBW Flag: output */ + 0x00, /* 13: CBW Lun */ + 0x06, /* 14: CBW Length */ + + 0x1b, /* 15+0: opcode: SCSI START/STOP */ + 0x00, /* 15+1: byte2: Not immediate */ + 0x00, 0x00, /* 15+2..3: reserved */ + 0x02, /* 15+4: Load/Eject command */ + 0x00, /* 15+5: control */ + 0x00, 0x00, 0x00, 0x00, /* 15+6..15: unused */ + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00 + }; + + usb_interface_descriptor_t *id; + usb_endpoint_descriptor_t *ed = NULL; + int i; + + + /* Find the bulk-out endpoints */ + id = usbd_get_interface_descriptor(uaa->iface); + for (i = 0 ; i < id->bNumEndpoints; i++) { + ed = usbd_interface2endpoint_descriptor(uaa->iface, i); + if (ed != NULL + && (ed->bmAttributes & UE_XFERTYPE) == UE_BULK) { + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT) { + if (usbd_open_pipe(uaa->iface, + ed->bEndpointAddress, + USBD_EXCLUSIVE_USE, + &sc->sc_pipe_out) + != USBD_NORMAL_COMPLETION) { + DPRINTF("failed to open bulk-out pipe on endpoint %d\n", + ed->bEndpointAddress); + return 0; + } + } else { + if (usbd_open_pipe(uaa->iface, + ed->bEndpointAddress, + USBD_EXCLUSIVE_USE, + &sc->sc_pipe_in) + != USBD_NORMAL_COMPLETION) { + DPRINTF("failed to open bulk-in pipe on endpoint %d\n", + ed->bEndpointAddress); + return 0; + } + } + } + if (sc->sc_pipe_out && sc->sc_pipe_in) + break; + } + + if (i == id->bNumEndpoints) { + DPRINTF("failed to find bulk-out pipe\n"); + return 0; + } + + sc->sc_xfer = usbd_alloc_xfer(uaa->device); + if (sc->sc_xfer == NULL) { + DPRINTF("failed to allocate xfer\n"); + return 0; + } + + usbd_setup_xfer(sc->sc_xfer, sc->sc_pipe_out, NULL, cmd, sizeof(cmd), + 0, USBD_DEFAULT_TIMEOUT, u3gstub_BBB_cb); + int err = usbd_transfer(sc->sc_xfer) != USBD_NORMAL_COMPLETION; + if (err != USBD_NORMAL_COMPLETION && err != USBD_IN_PROGRESS) { + DPRINTF("failed to start transfer (CBW)\n"); + return 0; + } + + return 1; +} + +static int +u3gstub_cmotech_init(struct u3gstub_softc *sc, struct usb_attach_arg *uaa) +{ + /* See definition of umass_bbb_cbw_t in sys/dev/usb/umass.c + * in sys/cam/scsi/scsi_all.h . + */ + unsigned char cmd[31] = { + 0x55, 0x53, 0x42, 0x43, /* 0..3: Command Block Wrapper (CBW) signature */ + 0x01, 0x00, 0x00, 0x00, /* 4..7: CBW Tag, unique 32-bit number */ + 0x00, 0x00, 0x00, 0x00, /* 8..11: CBW Transfer Length, no data here */ + 0x80, /* 12: CBW Flag: output, so 0 */ + 0x00, /* 13: CBW Lun */ + 0x08, /* 14: CBW Length */ + + 0xff, /* 15+0 */ + 0x52, /* 15+1 */ + 0x44, /* 15+2 */ + 0x45, /* 15+2 */ + 0x56, /* 15+4 */ + 0x43, /* 15+5 */ + 0x48, /* 15+5 */ + 0x47, /* 15+5 */ + 0x00, 0x00, 0x00, 0x00, /* 15+8..15: unused */ + 0x00, 0x00, 0x00, 0x00 + }; + + usb_interface_descriptor_t *id; + usb_endpoint_descriptor_t *ed = NULL; + int i; + + + /* Find the bulk-out endpoints */ + id = usbd_get_interface_descriptor(uaa->iface); + for (i = 0 ; i < id->bNumEndpoints ; i++) { + ed = usbd_interface2endpoint_descriptor(uaa->iface, i); + if (ed != NULL + && (ed->bmAttributes & UE_XFERTYPE) == UE_BULK) { + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT) { + if (usbd_open_pipe(uaa->iface, + ed->bEndpointAddress, + USBD_EXCLUSIVE_USE, + &sc->sc_pipe_out) + != USBD_NORMAL_COMPLETION) { + DPRINTF("failed to open bulk-out pipe on endpoint %d\n", + ed->bEndpointAddress); + return 0; + } + } else { + if (usbd_open_pipe(uaa->iface, + ed->bEndpointAddress, + USBD_EXCLUSIVE_USE, + &sc->sc_pipe_in) + != USBD_NORMAL_COMPLETION) { + DPRINTF("failed to open bulk-in pipe on endpoint %d\n", + ed->bEndpointAddress); + return 0; + } + } + } + if (sc->sc_pipe_out && sc->sc_pipe_in) + break; + } + + if (i == id->bNumEndpoints) { + DPRINTF("failed to find bulk-out pipe\n"); + return 0; + } + + sc->sc_xfer = usbd_alloc_xfer(uaa->device); + if (sc->sc_xfer == NULL) { + DPRINTF("failed to allocate xfer\n"); + return 0; + } + + usbd_setup_xfer(sc->sc_xfer, sc->sc_pipe_out, NULL, cmd, sizeof(cmd), + 0, USBD_DEFAULT_TIMEOUT, u3gstub_BBB_cb); + int err = usbd_transfer(sc->sc_xfer) != USBD_NORMAL_COMPLETION; + if (err != USBD_NORMAL_COMPLETION && err != USBD_IN_PROGRESS) { + DPRINTF("failed to start transfer (CBW)\n"); + return 0; + } + + return 1; +} + +static int +u3gstub_sierra_init(struct u3gstub_softc *sc, struct usb_attach_arg *uaa) +{ + usb_device_request_t req; + + req.bmRequestType = UT_VENDOR; + req.bRequest = UR_SET_INTERFACE; + USETW(req.wValue, UF_DEVICE_REMOTE_WAKEUP); + USETW(req.wIndex, UHF_PORT_CONNECTION); + USETW(req.wLength, 0); + + (void) usbd_do_request(uaa->device, &req, 0); /* ignore any error */ + + return 1; +} + +static int +u3gstub_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + const struct u3g_dev_type_s *u3g_dev_type; + usb_interface_descriptor_t *id; + + /* This stub handles 3G modem devices (E220, Mobile, etc.) with + * auto-install flash disks for Windows/MacOSX on the first interface. + * After some command or some delay they change appearance to a modem. + */ + + if (!uaa->iface) + return UMATCH_NONE; + + u3g_dev_type = u3g_lookup(uaa->vendor, uaa->product); + if (!u3g_dev_type) + return UMATCH_NONE; + + if (u3g_dev_type->flags&U3GFL_HUAWEI_INIT + || u3g_dev_type->flags&U3GFL_SCSI_EJECT + || u3g_dev_type->flags&U3GFL_SIERRA_INIT + || u3g_dev_type->flags&U3GFL_CMOTECH_INIT + || u3g_dev_type->flags&U3GFL_STUB_WAIT) { + /* We assume that if the first interface is still a mass + * storage device the device has not yet changed appearance. + */ + id = usbd_get_interface_descriptor(uaa->iface); + if (id && id->bInterfaceNumber == 0 + && id->bInterfaceClass == UICLASS_MASS) { +#ifndef U3G_DEBUG + if (!bootverbose) + device_quiet(self); +#endif + + return UMATCH_VENDOR_PRODUCT; + } + } + + return UMATCH_NONE; +} + +static int +u3gstub_attach(device_t self) +{ + struct u3gstub_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + const struct u3g_dev_type_s *u3g_dev_type; + int i; + +#ifndef U3G_DEBUG + if (!bootverbose) + device_quiet(self); +#endif + + sc->sc_dev = self; + sc->sc_udev = uaa->device; + + for (i = 0; i < uaa->nifaces; i++) + uaa->ifaces[i] = NULL; // claim all interfaces + + u3g_dev_type = u3g_lookup(uaa->vendor, uaa->product); + if (u3g_dev_type->flags&U3GFL_HUAWEI_INIT) { + if (bootverbose) + device_printf(sc->sc_dev, + "changing Huawei modem to modem mode\n"); + if (!u3gstub_huawei_init(sc, uaa)) + return ENXIO; + } else if (u3g_dev_type->flags&U3GFL_SCSI_EJECT) { + if (bootverbose) + device_printf(sc->sc_dev, "sending CD eject command to " + "change to modem mode\n"); + if (!u3gstub_scsi_eject(sc, uaa)) + return ENXIO; + } else if (u3g_dev_type->flags&U3GFL_SIERRA_INIT) { + if (bootverbose) + device_printf(sc->sc_dev, + "changing Sierra modem to modem mode\n"); + if (!u3gstub_sierra_init(sc, uaa)) + return ENXIO; + } else if (u3g_dev_type->flags&U3GFL_CMOTECH_INIT) { + if (bootverbose) + device_printf(sc->sc_dev, + "changing CMOTECH modem to modem mode\n"); + if (!u3gstub_cmotech_init(sc, uaa)) + return ENXIO; + } else if (u3g_dev_type->flags&U3GFL_STUB_WAIT) { + if (bootverbose) + device_printf(sc->sc_dev, "waiting for modem to change " + "to modem mode\n"); + /* nop */ + } + + return 0; +} + +static int +u3gstub_detach(device_t self) +{ + struct u3gstub_softc *sc = device_get_softc(self); + + if (sc->sc_xfer) + usbd_free_xfer(sc->sc_xfer); + + if (sc->sc_pipe_in) { + usbd_abort_pipe(sc->sc_pipe_in); + usbd_close_pipe(sc->sc_pipe_in); + } + if (sc->sc_pipe_out) { + usbd_abort_pipe(sc->sc_pipe_out); + usbd_close_pipe(sc->sc_pipe_out); + } + + return 0; +} + +static device_method_t u3gstub_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, u3gstub_match), + DEVMETHOD(device_attach, u3gstub_attach), + DEVMETHOD(device_detach, u3gstub_detach), + + { 0, 0 } +}; + +static driver_t u3gstub_driver = { + "u3g", + u3gstub_methods, + sizeof (struct u3gstub_softc) +}; + +DRIVER_MODULE(u3gstub, uhub, u3gstub_driver, ucom_devclass, usbd_driver_load, 0); +MODULE_DEPEND(u3gstub, usb, 1, 1, 1); diff --git a/sys/legacy/dev/usb/uark.c b/sys/legacy/dev/usb/uark.c new file mode 100644 index 0000000..af85819 --- /dev/null +++ b/sys/legacy/dev/usb/uark.c @@ -0,0 +1,339 @@ +/* $OpenBSD: uark.c,v 1.1 2006/08/14 08:30:22 jsg Exp $ */ + +/* + * Copyright (c) 2006 Jonathan Gray <jsg@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/ioccom.h> +#include <sys/fcntl.h> +#include <sys/conf.h> +#include <sys/tty.h> +#include <sys/file.h> +#include <sys/selinfo.h> +#include <sys/sysctl.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include "usbdevs.h" + +#include <dev/usb/ucomvar.h> + +#ifdef UARK_DEBUG +#define DPRINTFN(n, x) do { \ + if (uarkdebug > (n)) \ + printf x; \ +} while (0) +int uarkebug = 0; +#else +#define DPRINTFN(n, x) +#endif +#define DPRINTF(x) DPRINTFN(0, x) + +#define UARKBUFSZ 256 +#define UARK_CONFIG_NO 0 +#define UARK_IFACE_NO 0 + +#define UARK_SET_DATA_BITS(x) (x - 5) + +#define UARK_PARITY_NONE 0x00 +#define UARK_PARITY_ODD 0x08 +#define UARK_PARITY_EVEN 0x18 + +#define UARK_STOP_BITS_1 0x00 +#define UARK_STOP_BITS_2 0x04 + +#define UARK_BAUD_REF 3000000 + +#define UARK_WRITE 0x40 +#define UARK_READ 0xc0 + +#define UARK_REQUEST 0xfe + +#define UARK_CONFIG_INDEX 0 +#define UARK_IFACE_INDEX 0 + +struct uark_softc { + struct ucom_softc sc_ucom; + usbd_interface_handle sc_iface; + + u_char sc_msr; + u_char sc_lsr; +}; + +static void uark_get_status(void *, int portno, u_char *lsr, u_char *msr); +static void uark_set(void *, int, int, int); +static int uark_param(void *, int, struct termios *); +static void uark_break(void *, int, int); +static int uark_cmd(struct uark_softc *, uint16_t, uint16_t); + +struct ucom_callback uark_callback = { + uark_get_status, + uark_set, + uark_param, + NULL, + NULL, + NULL, + NULL, + NULL, +}; + +static const struct usb_devno uark_devs[] = { + { USB_VENDOR_ARKMICRO, USB_PRODUCT_ARKMICRO_ARK3116 } +}; + +static int +uark_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + + if (uaa->iface != NULL) + return (UMATCH_NONE); + + return (usb_lookup(uark_devs, uaa->vendor, uaa->product) != NULL) ? + UMATCH_VENDOR_PRODUCT : UMATCH_NONE; +} + +static int +uark_attach(device_t self) +{ + struct uark_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + usbd_device_handle dev = uaa->device; + usbd_interface_handle iface; + usb_interface_descriptor_t *id; + usb_endpoint_descriptor_t *ed; + usbd_status error; + int i; + struct ucom_softc *ucom = &sc->sc_ucom; + + ucom->sc_dev = self; + ucom->sc_udev = dev; + + if (uaa->iface == NULL) { + /* Move the device into the configured state. */ + error = usbd_set_config_index(dev, UARK_CONFIG_INDEX, 1); + if (error) { + device_printf(ucom->sc_dev, + "failed to set configuration, err=%s\n", + usbd_errstr(error)); + goto bad; + } + error = + usbd_device2interface_handle(dev, UARK_IFACE_INDEX, &iface); + if (error) { + device_printf(ucom->sc_dev, + "failed to get interface, err=%s\n", + usbd_errstr(error)); + goto bad; + } + } else + iface = uaa->iface; + + id = usbd_get_interface_descriptor(iface); + ucom->sc_iface = iface; + + ucom->sc_bulkin_no = ucom->sc_bulkout_no = -1; + for (i = 0; i < id->bNumEndpoints; i++) { + ed = usbd_interface2endpoint_descriptor(iface, i); + if (ed == NULL) { + device_printf(ucom->sc_dev, + "could not read endpoint descriptor\n"); + goto bad; + } + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) + ucom->sc_bulkin_no = ed->bEndpointAddress; + else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) + ucom->sc_bulkout_no = ed->bEndpointAddress; + } + if (ucom->sc_bulkin_no == -1 || ucom->sc_bulkout_no == -1) { + device_printf(ucom->sc_dev, "missing endpoint\n"); + goto bad; + } + ucom->sc_parent = sc; + ucom->sc_ibufsize = UARKBUFSZ; + ucom->sc_obufsize = UARKBUFSZ; + ucom->sc_ibufsizepad = UARKBUFSZ; + ucom->sc_opkthdrlen = 0; + + ucom->sc_callback = &uark_callback; + + DPRINTF(("uark: in=0x%x out=0x%x\n", ucom->sc_bulkin_no, ucom->sc_bulkout_no)); + ucom_attach(&sc->sc_ucom); + return 0; + +bad: + DPRINTF(("uark_attach: ATTACH ERROR\n")); + ucom->sc_dying = 1; + return ENXIO; +} + +static int +uark_detach(device_t self) +{ + struct uark_softc *sc = device_get_softc(self); + int rv = 0; + + DPRINTF(("uark_detach: sc=%p\n", sc)); + sc->sc_ucom.sc_dying = 1; + rv = ucom_detach(&sc->sc_ucom); + + return (rv); +} + +static void +uark_set(void *vsc, int portno, int reg, int onoff) +{ + struct uark_softc *sc = vsc; + + switch (reg) { + case UCOM_SET_BREAK: + uark_break(sc, portno, onoff); + return; + /* NOTREACHED */ + case UCOM_SET_DTR: + case UCOM_SET_RTS: + default: + return; + /* NOTREACHED */ + } +} + +static int +uark_param(void *vsc, int portno, struct termios *t) +{ + struct uark_softc *sc = (struct uark_softc *)vsc; + int data, divisor; + speed_t speed = t->c_ospeed; + + if (speed < 300 || speed > 115200) + return (EINVAL); + divisor = (UARK_BAUD_REF + speed / 2) / speed; + /* Check that we're within 3% of the requested rate. */ + if (speed * divisor < UARK_BAUD_REF * 100 / 103 || + speed * divisor > UARK_BAUD_REF * 100 / 97) + return (EINVAL); + uark_cmd(sc, 3, 0x83); + uark_cmd(sc, 0, divisor & 0xFF); + uark_cmd(sc, 1, divisor >> 8); + uark_cmd(sc, 3, 0x03); + + if (ISSET(t->c_cflag, CSTOPB)) + data = UARK_STOP_BITS_2; + else + data = UARK_STOP_BITS_1; + + if (ISSET(t->c_cflag, PARENB)) { + if (ISSET(t->c_cflag, PARODD)) + data |= UARK_PARITY_ODD; + else + data |= UARK_PARITY_EVEN; + } else + data |= UARK_PARITY_NONE; + + switch (ISSET(t->c_cflag, CSIZE)) { + case CS5: + data |= UARK_SET_DATA_BITS(5); + break; + case CS6: + data |= UARK_SET_DATA_BITS(6); + break; + case CS7: + data |= UARK_SET_DATA_BITS(7); + break; + case CS8: + data |= UARK_SET_DATA_BITS(8); + break; + } + uark_cmd(sc, 3, 0x00); + uark_cmd(sc, 3, data); + + return (0); +} + +void +uark_get_status(void *vsc, int portno, u_char *lsr, u_char *msr) +{ + struct uark_softc *sc = vsc; + + if (msr != NULL) + *msr = sc->sc_msr; + if (lsr != NULL) + *lsr = sc->sc_lsr; +} + +void +uark_break(void *vsc, int portno, int onoff) +{ +#ifdef UARK_DEBUG + struct uark_softc *sc = vsc; + + device_printf(sc->sc_dev, "%s: break %s!\n", onoff ? "on" : "off"); + if (onoff) + /* break on */ + uark_cmd(sc, 4, 0x01); + else + uark_cmd(sc, 4, 0x00); +#endif +} + +int +uark_cmd(struct uark_softc *sc, uint16_t index, uint16_t value) +{ + usb_device_request_t req; + usbd_status err; + struct ucom_softc *ucom = &sc->sc_ucom; + + req.bmRequestType = UARK_WRITE; + req.bRequest = UARK_REQUEST; + USETW(req.wValue, value); + USETW(req.wIndex, index); + USETW(req.wLength, 0); + err = usbd_do_request(ucom->sc_udev, &req, NULL); + + if (err) + return (EIO); + + return (0); +} + +static device_method_t uark_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, uark_match), + DEVMETHOD(device_attach, uark_attach), + DEVMETHOD(device_detach, uark_detach), + + { 0, 0 } +}; + +static driver_t uark_driver = { + "ucom", + uark_methods, + sizeof (struct uark_softc) +}; + +DRIVER_MODULE(uark, uhub, uark_driver, ucom_devclass, usbd_driver_load, 0); +MODULE_DEPEND(uark, usb, 1, 1, 1); +MODULE_DEPEND(uark, ucom, UCOM_MINVER, UCOM_PREFVER, UCOM_MAXVER); diff --git a/sys/legacy/dev/usb/ubsa.c b/sys/legacy/dev/usb/ubsa.c new file mode 100644 index 0000000..b66cce8 --- /dev/null +++ b/sys/legacy/dev/usb/ubsa.c @@ -0,0 +1,742 @@ +/*- + * Copyright (c) 2002, Alexander Kabaev <kan.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. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); +/*- + * Copyright (c) 2001 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Ichiro FUKUHARA (ichiro@ichiro.org). + * + * 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 <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/ioccom.h> +#include <sys/fcntl.h> +#include <sys/taskqueue.h> +#include <sys/conf.h> +#include <sys/tty.h> +#include <sys/file.h> +#include <sys/selinfo.h> +#include <sys/proc.h> +#include <sys/poll.h> +#include <sys/sysctl.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbcdc.h> + +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include "usbdevs.h" +#include <dev/usb/usb_quirks.h> + +#include <dev/usb/ucomvar.h> + +#ifdef USB_DEBUG +static int ubsadebug = 0; +SYSCTL_NODE(_hw_usb, OID_AUTO, ubsa, CTLFLAG_RW, 0, "USB ubsa"); +SYSCTL_INT(_hw_usb_ubsa, OID_AUTO, debug, CTLFLAG_RW, + &ubsadebug, 0, "ubsa debug level"); + +#define DPRINTFN(n, x) do { \ + if (ubsadebug > (n)) \ + printf x; \ + } while (0) +#else +#define DPRINTFN(n, x) +#endif +#define DPRINTF(x) DPRINTFN(0, x) + +#define UBSA_MODVER 1 /* module version */ + +#define UBSA_CONFIG_INDEX 1 +#define UBSA_IFACE_INDEX 0 + +#define UBSA_INTR_INTERVAL 100 /* ms */ + +#define UBSA_SET_BAUDRATE 0x00 +#define UBSA_SET_STOP_BITS 0x01 +#define UBSA_SET_DATA_BITS 0x02 +#define UBSA_SET_PARITY 0x03 +#define UBSA_SET_DTR 0x0A +#define UBSA_SET_RTS 0x0B +#define UBSA_SET_BREAK 0x0C +#define UBSA_SET_FLOW_CTRL 0x10 + +#define UBSA_PARITY_NONE 0x00 +#define UBSA_PARITY_EVEN 0x01 +#define UBSA_PARITY_ODD 0x02 +#define UBSA_PARITY_MARK 0x03 +#define UBSA_PARITY_SPACE 0x04 + +#define UBSA_FLOW_NONE 0x0000 +#define UBSA_FLOW_OCTS 0x0001 +#define UBSA_FLOW_ODSR 0x0002 +#define UBSA_FLOW_IDSR 0x0004 +#define UBSA_FLOW_IDTR 0x0008 +#define UBSA_FLOW_IRTS 0x0010 +#define UBSA_FLOW_ORTS 0x0020 +#define UBSA_FLOW_UNKNOWN 0x0040 +#define UBSA_FLOW_OXON 0x0080 +#define UBSA_FLOW_IXON 0x0100 + +/* line status register */ +#define UBSA_LSR_TSRE 0x40 /* Transmitter empty: byte sent */ +#define UBSA_LSR_TXRDY 0x20 /* Transmitter buffer empty */ +#define UBSA_LSR_BI 0x10 /* Break detected */ +#define UBSA_LSR_FE 0x08 /* Framing error: bad stop bit */ +#define UBSA_LSR_PE 0x04 /* Parity error */ +#define UBSA_LSR_OE 0x02 /* Overrun, lost incoming byte */ +#define UBSA_LSR_RXRDY 0x01 /* Byte ready in Receive Buffer */ +#define UBSA_LSR_RCV_MASK 0x1f /* Mask for incoming data or error */ + +/* modem status register */ +/* All deltas are from the last read of the MSR. */ +#define UBSA_MSR_DCD 0x80 /* Current Data Carrier Detect */ +#define UBSA_MSR_RI 0x40 /* Current Ring Indicator */ +#define UBSA_MSR_DSR 0x20 /* Current Data Set Ready */ +#define UBSA_MSR_CTS 0x10 /* Current Clear to Send */ +#define UBSA_MSR_DDCD 0x08 /* DCD has changed state */ +#define UBSA_MSR_TERI 0x04 /* RI has toggled low to high */ +#define UBSA_MSR_DDSR 0x02 /* DSR has changed state */ +#define UBSA_MSR_DCTS 0x01 /* CTS has changed state */ + +struct ubsa_softc { + struct ucom_softc sc_ucom; + + int sc_iface_number; /* interface number */ + + usbd_interface_handle sc_intr_iface; /* interrupt interface */ + int sc_intr_number; /* interrupt number */ + usbd_pipe_handle sc_intr_pipe; /* interrupt pipe */ + u_char *sc_intr_buf; /* interrupt buffer */ + int sc_isize; + + u_char sc_dtr; /* current DTR state */ + u_char sc_rts; /* current RTS state */ + + u_char sc_lsr; /* Local status register */ + u_char sc_msr; /* ubsa status register */ + struct task sc_task; +}; + +static void ubsa_intr(usbd_xfer_handle, usbd_private_handle, usbd_status); +static void ubsa_notify(void *, int count); + +static void ubsa_get_status(void *, int, u_char *, u_char *); +static void ubsa_set(void *, int, int, int); +static int ubsa_param(void *, int, struct termios *); +static int ubsa_open(void *, int); +static void ubsa_close(void *, int); + +static int ubsa_request(struct ubsa_softc *, u_int8_t, u_int16_t); +static void ubsa_dtr(struct ubsa_softc *, int); +static void ubsa_rts(struct ubsa_softc *, int); +static void ubsa_baudrate(struct ubsa_softc *, speed_t); +static void ubsa_parity(struct ubsa_softc *, tcflag_t); +static void ubsa_databits(struct ubsa_softc *, tcflag_t); +static void ubsa_stopbits(struct ubsa_softc *, tcflag_t); +static void ubsa_flow(struct ubsa_softc *, tcflag_t, tcflag_t); + +struct ucom_callback ubsa_callback = { + ubsa_get_status, + ubsa_set, + ubsa_param, + NULL, + ubsa_open, + ubsa_close, + NULL, + NULL +}; + +static const struct ubsa_product { + uint16_t vendor; + uint16_t product; +} ubsa_products [] = { + /* AnyData ADU-500A */ + { USB_VENDOR_ANYDATA, USB_PRODUCT_ANYDATA_ADU_500A }, + /* AnyData ADU-E100A/H */ + { USB_VENDOR_ANYDATA, USB_PRODUCT_ANYDATA_ADU_E100X }, + /* Axesstel MV100H */ + { USB_VENDOR_AXESSTEL, USB_PRODUCT_AXESSTEL_DATAMODEM }, + /* BELKIN F5U103 */ + { USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5U103 }, + /* BELKIN F5U120 */ + { USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5U120 }, + /* GoHubs GO-COM232 */ + { USB_VENDOR_ETEK, USB_PRODUCT_ETEK_1COM }, + /* GoHubs GO-COM232 */ + { USB_VENDOR_GOHUBS, USB_PRODUCT_GOHUBS_GOCOM232 }, + /* Peracom */ + { USB_VENDOR_PERACOM, USB_PRODUCT_PERACOM_SERIAL1 }, + { 0, 0 } +}; + +static device_probe_t ubsa_match; +static device_attach_t ubsa_attach; +static device_detach_t ubsa_detach; + +static device_method_t ubsa_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ubsa_match), + DEVMETHOD(device_attach, ubsa_attach), + DEVMETHOD(device_detach, ubsa_detach), + { 0, 0 } +}; + +static driver_t ubsa_driver = { + "ucom", + ubsa_methods, + sizeof (struct ubsa_softc) +}; + +DRIVER_MODULE(ubsa, uhub, ubsa_driver, ucom_devclass, usbd_driver_load, 0); +MODULE_DEPEND(ubsa, usb, 1, 1, 1); +MODULE_DEPEND(ubsa, ucom, UCOM_MINVER, UCOM_PREFVER, UCOM_MAXVER); +MODULE_VERSION(ubsa, UBSA_MODVER); + +static int +ubsa_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + int i; + + if (uaa->iface != NULL) + return (UMATCH_NONE); + + for (i = 0; ubsa_products[i].vendor != 0; i++) { + if (ubsa_products[i].vendor == uaa->vendor && + ubsa_products[i].product == uaa->product) { + return (UMATCH_VENDOR_PRODUCT); + } + } + return (UMATCH_NONE); +} + +static int +ubsa_attach(device_t self) +{ + struct ubsa_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + usbd_device_handle dev; + struct ucom_softc *ucom; + usb_config_descriptor_t *cdesc; + usb_interface_descriptor_t *id; + usb_endpoint_descriptor_t *ed; + usbd_status err; + int i; + + dev = uaa->device; + ucom = &sc->sc_ucom; + + /* + * initialize rts, dtr variables to something + * different from boolean 0, 1 + */ + sc->sc_dtr = -1; + sc->sc_rts = -1; + + ucom->sc_dev = self; + ucom->sc_udev = dev; + ucom->sc_iface = uaa->iface; + + DPRINTF(("ubsa attach: sc = %p\n", sc)); + + /* initialize endpoints */ + ucom->sc_bulkin_no = ucom->sc_bulkout_no = -1; + sc->sc_intr_number = -1; + sc->sc_intr_pipe = NULL; + + /* Move the device into the configured state. */ + err = usbd_set_config_index(dev, UBSA_CONFIG_INDEX, 1); + if (err) { + device_printf(ucom->sc_dev, "failed to set configuration: %s\n", + usbd_errstr(err)); + ucom->sc_dying = 1; + goto error; + } + + /* get the config descriptor */ + cdesc = usbd_get_config_descriptor(ucom->sc_udev); + + if (cdesc == NULL) { + device_printf(ucom->sc_dev, + "failed to get configuration descriptor\n"); + ucom->sc_dying = 1; + goto error; + } + + /* get the first interface */ + err = usbd_device2interface_handle(dev, UBSA_IFACE_INDEX, + &ucom->sc_iface); + if (err) { + device_printf(ucom->sc_dev, "failed to get interface: %s\n", + usbd_errstr(err)); + ucom->sc_dying = 1; + goto error; + } + + /* Find the endpoints */ + + id = usbd_get_interface_descriptor(ucom->sc_iface); + sc->sc_iface_number = id->bInterfaceNumber; + + for (i = 0; i < id->bNumEndpoints; i++) { + ed = usbd_interface2endpoint_descriptor(ucom->sc_iface, i); + if (ed == NULL) { + device_printf(ucom->sc_dev, + "no endpoint descriptor for %d\n", i); + ucom->sc_dying = 1; + goto error; + } + + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_INTERRUPT) { + sc->sc_intr_number = ed->bEndpointAddress; + sc->sc_isize = UGETW(ed->wMaxPacketSize); + } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + ucom->sc_bulkin_no = ed->bEndpointAddress; + ucom->sc_ibufsize = UGETW(ed->wMaxPacketSize); + } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + ucom->sc_bulkout_no = ed->bEndpointAddress; + ucom->sc_obufsize = UGETW(ed->wMaxPacketSize); + } + } + + if (sc->sc_intr_number == -1) { + device_printf(ucom->sc_dev, "Could not find interrupt in\n"); + ucom->sc_dying = 1; + goto error; + } + + /* keep interface for interrupt */ + sc->sc_intr_iface = ucom->sc_iface; + + if (ucom->sc_bulkin_no == -1) { + device_printf(ucom->sc_dev, "Could not find data bulk in\n"); + ucom->sc_dying = 1; + goto error; + } + + if (ucom->sc_bulkout_no == -1) { + device_printf(ucom->sc_dev, "Could not find data bulk out\n"); + ucom->sc_dying = 1; + goto error; + } + + ucom->sc_parent = sc; + ucom->sc_portno = UCOM_UNK_PORTNO; + /* bulkin, bulkout set above */ + ucom->sc_ibufsize = 1024; + ucom->sc_obufsize = 1024; + ucom->sc_ibufsizepad = ucom->sc_ibufsize; + ucom->sc_opkthdrlen = 0; + ucom->sc_callback = &ubsa_callback; + + DPRINTF(("ubsa: in = 0x%x, out = 0x%x, intr = 0x%x\n", + ucom->sc_bulkin_no, ucom->sc_bulkout_no, sc->sc_intr_number)); + + TASK_INIT(&sc->sc_task, 0, ubsa_notify, sc); + ucom_attach(ucom); + return 0; + +error: + return ENXIO; +} + +static int +ubsa_detach(device_t self) +{ + struct ubsa_softc *sc = device_get_softc(self); + int rv; + + DPRINTF(("ubsa_detach: sc = %p\n", sc)); + + if (sc->sc_intr_pipe != NULL) { + usbd_abort_pipe(sc->sc_intr_pipe); + usbd_close_pipe(sc->sc_intr_pipe); + free(sc->sc_intr_buf, M_USBDEV); + sc->sc_intr_pipe = NULL; + } + + sc->sc_ucom.sc_dying = 1; +#if 0 + taskqueue_drain(taskqueue_swi_giant); +#endif + rv = ucom_detach(&sc->sc_ucom); + + return (rv); +} + +static int +ubsa_request(struct ubsa_softc *sc, u_int8_t request, u_int16_t value) +{ + usb_device_request_t req; + usbd_status err; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = request; + USETW(req.wValue, value); + USETW(req.wIndex, sc->sc_iface_number); + USETW(req.wLength, 0); + + err = usbd_do_request(sc->sc_ucom.sc_udev, &req, 0); + if (err) + device_printf(sc->sc_ucom.sc_dev, "ubsa_request(%x, %x): %s\n", + request, value, usbd_errstr(err)); + return (err); +} + +static void +ubsa_dtr(struct ubsa_softc *sc, int onoff) +{ + + DPRINTF(("ubsa_dtr: onoff = %d\n", onoff)); + + if (sc->sc_dtr == onoff) + return; + sc->sc_dtr = onoff; + + ubsa_request(sc, UBSA_SET_DTR, onoff ? 1 : 0); +} + +static void +ubsa_rts(struct ubsa_softc *sc, int onoff) +{ + + DPRINTF(("ubsa_rts: onoff = %d\n", onoff)); + + if (sc->sc_rts == onoff) + return; + sc->sc_rts = onoff; + + ubsa_request(sc, UBSA_SET_RTS, onoff ? 1 : 0); +} + +static void +ubsa_break(struct ubsa_softc *sc, int onoff) +{ + + DPRINTF(("ubsa_rts: onoff = %d\n", onoff)); + + ubsa_request(sc, UBSA_SET_BREAK, onoff ? 1 : 0); +} + +static void +ubsa_set(void *addr, int portno, int reg, int onoff) +{ + struct ubsa_softc *sc; + + sc = addr; + switch (reg) { + case UCOM_SET_DTR: + ubsa_dtr(sc, onoff); + break; + case UCOM_SET_RTS: + ubsa_rts(sc, onoff); + break; + case UCOM_SET_BREAK: + ubsa_break(sc, onoff); + break; + default: + break; + } +} + +static void +ubsa_baudrate(struct ubsa_softc *sc, speed_t speed) +{ + u_int16_t value = 0; + + DPRINTF(("ubsa_baudrate: speed = %d\n", speed)); + + switch(speed) { + case B0: + break; + case B300: + case B600: + case B1200: + case B2400: + case B4800: + case B9600: + case B19200: + case B38400: + case B57600: + case B115200: + case B230400: + value = B230400 / speed; + break; + default: + device_printf(sc->sc_ucom.sc_dev, + "ubsa_param: unsupported baud, forcing default of 9600\n"); + value = B230400 / B9600; + break; + }; + + if (speed == B0) { + ubsa_flow(sc, 0, 0); + ubsa_dtr(sc, 0); + ubsa_rts(sc, 0); + } else + ubsa_request(sc, UBSA_SET_BAUDRATE, value); +} + +static void +ubsa_parity(struct ubsa_softc *sc, tcflag_t cflag) +{ + int value; + + DPRINTF(("ubsa_parity: cflag = 0x%x\n", cflag)); + + if (cflag & PARENB) + value = (cflag & PARODD) ? UBSA_PARITY_ODD : UBSA_PARITY_EVEN; + else + value = UBSA_PARITY_NONE; + + ubsa_request(sc, UBSA_SET_PARITY, value); +} + +static void +ubsa_databits(struct ubsa_softc *sc, tcflag_t cflag) +{ + int value; + + DPRINTF(("ubsa_databits: cflag = 0x%x\n", cflag)); + + switch (cflag & CSIZE) { + case CS5: value = 0; break; + case CS6: value = 1; break; + case CS7: value = 2; break; + case CS8: value = 3; break; + default: + device_printf(sc->sc_ucom.sc_dev, + "ubsa_param: unsupported databits requested, " + "forcing default of 8\n"); + value = 3; + } + + ubsa_request(sc, UBSA_SET_DATA_BITS, value); +} + +static void +ubsa_stopbits(struct ubsa_softc *sc, tcflag_t cflag) +{ + int value; + + DPRINTF(("ubsa_stopbits: cflag = 0x%x\n", cflag)); + + value = (cflag & CSTOPB) ? 1 : 0; + + ubsa_request(sc, UBSA_SET_STOP_BITS, value); +} + +static void +ubsa_flow(struct ubsa_softc *sc, tcflag_t cflag, tcflag_t iflag) +{ + int value; + + DPRINTF(("ubsa_flow: cflag = 0x%x, iflag = 0x%x\n", cflag, iflag)); + + value = 0; + if (cflag & CRTSCTS) + value |= UBSA_FLOW_OCTS | UBSA_FLOW_IRTS; + if (iflag & (IXON|IXOFF)) + value |= UBSA_FLOW_OXON | UBSA_FLOW_IXON; + + ubsa_request(sc, UBSA_SET_FLOW_CTRL, value); +} + +static int +ubsa_param(void *addr, int portno, struct termios *ti) +{ + struct ubsa_softc *sc; + + sc = addr; + + DPRINTF(("ubsa_param: sc = %p\n", sc)); + + ubsa_baudrate(sc, ti->c_ospeed); + ubsa_parity(sc, ti->c_cflag); + ubsa_databits(sc, ti->c_cflag); + ubsa_stopbits(sc, ti->c_cflag); + ubsa_flow(sc, ti->c_cflag, ti->c_iflag); + + return (0); +} + +static int +ubsa_open(void *addr, int portno) +{ + struct ubsa_softc *sc; + int err; + + sc = addr; + if (sc->sc_ucom.sc_dying) + return (ENXIO); + + DPRINTF(("ubsa_open: sc = %p\n", sc)); + + if (sc->sc_intr_number != -1 && sc->sc_intr_pipe == NULL) { + sc->sc_intr_buf = malloc(sc->sc_isize, M_USBDEV, M_WAITOK); + err = usbd_open_pipe_intr(sc->sc_intr_iface, + sc->sc_intr_number, + USBD_SHORT_XFER_OK, + &sc->sc_intr_pipe, + sc, + sc->sc_intr_buf, + sc->sc_isize, + ubsa_intr, + UBSA_INTR_INTERVAL); + if (err) { + device_printf(sc->sc_ucom.sc_dev, + "cannot open interrupt pipe (addr %d)\n", + sc->sc_intr_number); + return (EIO); + } + } + + return (0); +} + +static void +ubsa_close(void *addr, int portno) +{ + struct ubsa_softc *sc; + int err; + + sc = addr; + if (sc->sc_ucom.sc_dying) + return; + + DPRINTF(("ubsa_close: close\n")); + + if (sc->sc_intr_pipe != NULL) { + err = usbd_abort_pipe(sc->sc_intr_pipe); + if (err) + device_printf(sc->sc_ucom.sc_dev, + "abort interrupt pipe failed: %s\n", + usbd_errstr(err)); + err = usbd_close_pipe(sc->sc_intr_pipe); + if (err) + device_printf(sc->sc_ucom.sc_dev, + "close interrupt pipe failed: %s\n", + usbd_errstr(err)); + free(sc->sc_intr_buf, M_USBDEV); + sc->sc_intr_pipe = NULL; + } +} + +static void +ubsa_intr(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + struct ubsa_softc *sc; + u_char *buf; + + sc = priv; + buf = sc->sc_intr_buf; + if (sc->sc_ucom.sc_dying) + return; + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) + return; + + DPRINTF(("%s: ubsa_intr: abnormal status: %s\n", + device_get_nameunit(sc->sc_ucom.sc_dev), + usbd_errstr(status))); + usbd_clear_endpoint_stall_async(sc->sc_intr_pipe); + return; + } + + /* incidentally, Belkin adapter status bits match UART 16550 bits */ + sc->sc_lsr = buf[2]; + sc->sc_msr = buf[3]; + + DPRINTF(("%s: ubsa lsr = 0x%02x, msr = 0x%02x\n", + device_get_nameunit(sc->sc_ucom.sc_dev), sc->sc_lsr, sc->sc_msr)); + + taskqueue_enqueue(taskqueue_swi_giant, &sc->sc_task); +} + +/* Handle delayed events. */ +static void +ubsa_notify(void *arg, int count) +{ + struct ubsa_softc *sc; + + sc = arg; + ucom_status_change(&sc->sc_ucom); +} + +static void +ubsa_get_status(void *addr, int portno, u_char *lsr, u_char *msr) +{ + struct ubsa_softc *sc; + + DPRINTF(("ubsa_get_status\n")); + + sc = addr; + if (lsr != NULL) + *lsr = sc->sc_lsr; + if (msr != NULL) + *msr = sc->sc_msr; +} diff --git a/sys/legacy/dev/usb/ubser.c b/sys/legacy/dev/usb/ubser.c new file mode 100644 index 0000000..29994b9 --- /dev/null +++ b/sys/legacy/dev/usb/ubser.c @@ -0,0 +1,882 @@ +/*- + * Copyright (c) 2004 Bernd Walter <ticso@freebsd.org> + * + * $URL: https://devel.bwct.de/svn/projects/ubser/ubser.c $ + * $Date: 2004-02-29 01:53:10 +0100 (Sun, 29 Feb 2004) $ + * $Author: ticso $ + * $Rev: 1127 $ + */ + +/*- + * Copyright (c) 2001-2002, Shunsuke Akiyama <akiyama@jp.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. + */ + +/*- + * Copyright (c) 2000 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. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * BWCT serial adapter driver + */ + +#include <sys/cdefs.h> + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/malloc.h> +#include <sys/bus.h> +#include <sys/ioccom.h> +#include <sys/fcntl.h> +#include <sys/conf.h> +#include <sys/serial.h> +#include <sys/tty.h> +#include <sys/clist.h> +#include <sys/file.h> + +#include <sys/selinfo.h> + +#include <sys/sysctl.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbhid.h> + +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include "usbdevs.h" + +#include <dev/usb/ubser.h> + +#ifdef USB_DEBUG +static int ubserdebug = 0; +SYSCTL_NODE(_hw_usb, OID_AUTO, ubser, CTLFLAG_RW, 0, "USB ubser"); +SYSCTL_INT(_hw_usb_ubser, OID_AUTO, debug, CTLFLAG_RW, + &ubserdebug, 0, "ubser debug level"); +#define DPRINTF(x) do { \ + if (ubserdebug) \ + printf x; \ + } while (0) + +#define DPRINTFN(n, x) do { \ + if (ubserdebug > (n)) \ + printf x; \ + } while (0) +#else +#define DPRINTF(x) +#define DPRINTFN(n,x) +#endif + +#define ISSET(t, f) ((t) & (f)) +#define SET(t, f) (t) |= (f) +#define CLR(t, f) (t) &= ~((unsigned)(f)) + +struct ubser_port { + int p_port; + struct ubser_softc *p_sc; + usbd_xfer_handle p_oxfer; /* write request */ + u_char *p_obuf; /* write buffer */ + struct tty *p_tty; +}; + +struct ubser_softc { + device_t sc_dev; + usbd_device_handle sc_udev; + usbd_interface_handle sc_iface; /* data interface */ + int sc_ifaceno; + + int sc_refcnt; + u_char sc_dying; + u_char sc_opening; + int sc_state; + uint8_t sc_numser; + + int sc_bulkin_no; /* bulk in endpoint address */ + usbd_pipe_handle sc_bulkin_pipe; /* bulk in pipe */ + usbd_xfer_handle sc_ixfer; /* read request */ + u_char *sc_ibuf; /* read buffer */ + u_int sc_ibufsize; /* read buffer size */ + u_int sc_ibufsizepad; /* read buffer size padded */ + + int sc_bulkout_no; /* bulk out endpoint address */ + usbd_pipe_handle sc_bulkout_pipe;/* bulk out pipe */ + u_int sc_obufsize; /* write buffer size */ + u_int sc_opkthdrlen; /* header length of + output packet */ + + struct ubser_port *sc_port; +}; + +static int ubserparam(struct tty *, struct termios *); +static void ubserstart(struct tty *); +static void ubserstop(struct tty *, int); +static usbd_status ubserstartread(struct ubser_softc *); +static void ubserreadcb(usbd_xfer_handle, usbd_private_handle, usbd_status); +static void ubserwritecb(usbd_xfer_handle, usbd_private_handle, usbd_status); +static void ubser_cleanup(struct ubser_softc *sc); + +static t_break_t ubserbreak; +static t_open_t ubseropen; +static t_close_t ubserclose; +static t_modem_t ubsermodem; + +static device_probe_t ubser_match; +static device_attach_t ubser_attach; +static device_detach_t ubser_detach; + +static device_method_t ubser_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ubser_match), + DEVMETHOD(device_attach, ubser_attach), + DEVMETHOD(device_detach, ubser_detach), + + { 0, 0 } +}; + +static driver_t ubser_driver = { + "ubser", + ubser_methods, + sizeof(struct ubser_softc) +}; + +static devclass_t ubser_devclass; + +static int +ubser_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + usb_string_descriptor_t us; + usb_interface_descriptor_t *id; + usb_device_descriptor_t *dd; + int err, size; + + if (uaa->iface == NULL) + return (UMATCH_NONE); + + DPRINTFN(20,("ubser: vendor=0x%x, product=0x%x\n", + uaa->vendor, uaa->product)); + + dd = usbd_get_device_descriptor(uaa->device); + if (dd == NULL) { + printf("ubser: failed to get device descriptor\n"); + return (UMATCH_NONE); + } + + id = usbd_get_interface_descriptor(uaa->iface); + if (id == NULL) { + printf("ubser: failed to get interface descriptor\n"); + return (UMATCH_NONE); + } + + err = usbd_get_string_desc(uaa->device, dd->iManufacturer, 0, &us, + &size); + if (err != 0) + return (UMATCH_NONE); + + /* check if this is a BWCT vendor specific ubser interface */ + if (strcmp((char*)us.bString, "B\0W\0C\0T\0") == 0 && + id->bInterfaceClass == 0xff && id->bInterfaceSubClass == 0x00) + return (UMATCH_VENDOR_IFACESUBCLASS); + + return (UMATCH_NONE); +} + +static int +ubser_attach(device_t self) +{ + struct ubser_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + usbd_device_handle udev = uaa->device; + usb_endpoint_descriptor_t *ed; + usb_interface_descriptor_t *id; + usb_device_request_t req; + struct tty *tp; + usbd_status err; + int i; + int alen; + uint8_t epcount; + struct ubser_port *pp; + + sc->sc_dev = self; + + DPRINTFN(10,("\nubser_attach: sc=%p\n", sc)); + + sc->sc_udev = udev = uaa->device; + sc->sc_iface = uaa->iface; + sc->sc_numser = 0; + sc->sc_port = NULL; + + /* get interface index */ + id = usbd_get_interface_descriptor(uaa->iface); + if (id == NULL) { + printf("ubser: failed to get interface descriptor\n"); + return (UMATCH_NONE); + } + sc->sc_ifaceno = id->bInterfaceNumber; + + /* get number of serials */ + req.bmRequestType = UT_READ_VENDOR_INTERFACE; + req.bRequest = VENDOR_GET_NUMSER; + USETW(req.wValue, 0); + USETW(req.wIndex, sc->sc_ifaceno); + USETW(req.wLength, 1); + err = usbd_do_request_flags(udev, &req, &sc->sc_numser, + USBD_SHORT_XFER_OK, &alen, USBD_DEFAULT_TIMEOUT); + if (err) { + device_printf(self, "failed to get number of serials\n"); + goto bad; + } else if (alen != 1) { + device_printf(self, "bogus answer on get_numser\n"); + goto bad; + } + if (sc->sc_numser > MAX_SER) + sc->sc_numser = MAX_SER; + device_printf(self, "found %i serials\n", sc->sc_numser); + + sc->sc_port = malloc(sizeof(*sc->sc_port) * sc->sc_numser, + M_USBDEV, M_WAITOK); + + /* find our bulk endpoints */ + epcount = 0; + (void)usbd_endpoint_count(sc->sc_iface, &epcount); + sc->sc_bulkin_no = -1; + sc->sc_bulkout_no = -1; + for (i = 0; i < epcount; i++) { + ed = usbd_interface2endpoint_descriptor(sc->sc_iface, i); + if (ed == NULL) { + device_printf(self, "couldn't get ep %d\n", i); + return ENXIO; + } + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + sc->sc_bulkin_no = ed->bEndpointAddress; + sc->sc_ibufsizepad = UGETW(ed->wMaxPacketSize); + sc->sc_ibufsizepad = UGETW(ed->wMaxPacketSize) - 1; + } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + sc->sc_bulkout_no = ed->bEndpointAddress; + sc->sc_obufsize = UGETW(ed->wMaxPacketSize) - 1; + sc->sc_opkthdrlen = 1; + } + } + if (sc->sc_bulkin_no == -1 || sc->sc_bulkout_no == -1) { + device_printf(self, "could not find bulk in/out endpoint\n"); + sc->sc_dying = 1; + goto bad; + } + + /* Open the bulk pipes */ + /* Bulk-in pipe */ + err = usbd_open_pipe(sc->sc_iface, sc->sc_bulkin_no, 0, + &sc->sc_bulkin_pipe); + if (err) { + device_printf(self, "open bulk in error (addr %d): %s\n", + sc->sc_bulkin_no, usbd_errstr(err)); + goto fail_0; + } + /* Bulk-out pipe */ + err = usbd_open_pipe(sc->sc_iface, sc->sc_bulkout_no, + USBD_EXCLUSIVE_USE, &sc->sc_bulkout_pipe); + if (err) { + device_printf(self, "open bulk out error (addr %d): %s\n", + sc->sc_bulkout_no, usbd_errstr(err)); + goto fail_1; + } + + /* Allocate a request and an input buffer */ + sc->sc_ixfer = usbd_alloc_xfer(sc->sc_udev); + if (sc->sc_ixfer == NULL) { + goto fail_2; + } + + sc->sc_ibuf = usbd_alloc_buffer(sc->sc_ixfer, + sc->sc_ibufsizepad); + if (sc->sc_ibuf == NULL) { + goto fail_3; + } + + for (i = 0; i < sc->sc_numser; i++) { + pp = &sc->sc_port[i]; + pp->p_port = i; + pp->p_sc = sc; + tp = pp->p_tty = ttyalloc(); + tp->t_sc = pp; + DPRINTF(("ubser_attach: tty_attach tp = %p\n", tp)); + tp->t_oproc = ubserstart; + tp->t_param = ubserparam; + tp->t_stop = ubserstop; + tp->t_break = ubserbreak; + tp->t_open = ubseropen; + tp->t_close = ubserclose; + tp->t_modem = ubsermodem; + ttycreate(tp, 0, "y%r%r", device_get_unit(sc->sc_dev), i); + } + + + for (i = 0; i < sc->sc_numser; i++) { + sc->sc_port[i].p_oxfer = NULL; + sc->sc_port[i].p_obuf = NULL; + } + for (i = 0; i < sc->sc_numser; i++) { + sc->sc_port[i].p_oxfer = usbd_alloc_xfer(sc->sc_udev); + if (sc->sc_port[i].p_oxfer == NULL) { + goto fail_4; + } + + sc->sc_port[i].p_obuf = usbd_alloc_buffer(sc->sc_port[i].p_oxfer, + sc->sc_obufsize + + sc->sc_opkthdrlen); + if (sc->sc_port[i].p_obuf == NULL) { + goto fail_4; + } + } + + ubserstartread(sc); + return 0; + +fail_4: + for (i = 0; i < sc->sc_numser; i++) { + if (sc->sc_port[i].p_oxfer != NULL) { + usbd_free_xfer(sc->sc_port[i].p_oxfer); + sc->sc_port[i].p_oxfer = NULL; + } + } +fail_3: + usbd_free_xfer(sc->sc_ixfer); + sc->sc_ixfer = NULL; +fail_2: + usbd_close_pipe(sc->sc_bulkout_pipe); + sc->sc_bulkout_pipe = NULL; +fail_1: + usbd_close_pipe(sc->sc_bulkin_pipe); + sc->sc_bulkin_pipe = NULL; +fail_0: + sc->sc_opening = 0; + wakeup(&sc->sc_opening); + +bad: + ubser_cleanup(sc); + if (sc->sc_port != NULL) { + for (i = 0; i < sc->sc_numser; i++) { + pp = &sc->sc_port[i]; + if (pp->p_tty != NULL) + ttyfree(pp->p_tty); + } + free(sc->sc_port, M_USBDEV); + sc->sc_port = NULL; + } + + DPRINTF(("ubser_attach: ATTACH ERROR\n")); + return ENXIO; +} + +static int +ubser_detach(device_t self) +{ + struct ubser_softc *sc = device_get_softc(self); + int i; + struct ubser_port *pp; + + DPRINTF(("ubser_detach: sc=%p\n", sc)); + + sc->sc_dying = 1; + for (i = 0; i < sc->sc_numser; i++) { + pp = &sc->sc_port[i]; + if (pp->p_tty != NULL) + ttygone(pp->p_tty); + } + + if (sc->sc_bulkin_pipe != NULL) + usbd_abort_pipe(sc->sc_bulkin_pipe); + if (sc->sc_bulkout_pipe != NULL) + usbd_abort_pipe(sc->sc_bulkout_pipe); + + if (sc->sc_port != NULL) { + for (i = 0; i < sc->sc_numser; i++) { + pp = &sc->sc_port[i]; + if (pp->p_tty != NULL) + ttyfree(pp->p_tty); + } + free(sc->sc_port, M_USBDEV); + sc->sc_port = NULL; + } + + if (--sc->sc_refcnt >= 0) { + /* Wait for processes to go away. */ + usb_detach_wait(sc->sc_dev); + } + + return (0); +} + +static int +ubserparam(struct tty *tp, struct termios *t) +{ + struct ubser_softc *sc; + struct ubser_port *pp; + + pp = tp->t_sc; + sc = pp->p_sc; + + if (sc->sc_dying) + return (EIO); + + DPRINTF(("ubserparam: sc = %p\n", sc)); + + /* + * The firmware on our devices can only do 8n1@9600bps + * without handshake. + * We refuse to accept other configurations. + */ + + /* enshure 9600bps */ + switch (t->c_ospeed) { + case 9600: + break; + default: + return (EINVAL); + } + + /* 2 stop bits not possible */ + if (ISSET(t->c_cflag, CSTOPB)) + return (EINVAL); + + /* XXX parity handling not possible with current firmware */ + if (ISSET(t->c_cflag, PARENB)) + return (EINVAL); + + /* we can only do 8 data bits */ + switch (ISSET(t->c_cflag, CSIZE)) { + case CS8: + break; + default: + return (EINVAL); + } + + /* we can't do any kind of hardware handshaking */ + if ((t->c_cflag & + (CRTS_IFLOW | CDTR_IFLOW |CDSR_OFLOW |CCAR_OFLOW)) != 0) + return (EINVAL); + + /* + * XXX xon/xoff not supported by the firmware! + * This is handled within FreeBSD only and may overflow buffers + * because of delayed reaction due to device buffering. + */ + + ttsetwater(tp); + + return (0); +} + +static void +ubserstart(struct tty *tp) +{ + struct ubser_softc *sc; + struct ubser_port *pp; + struct cblock *cbp; + usbd_status err; + u_char *data; + int cnt; + uint8_t serial; + + pp = tp->t_sc; + sc = pp->p_sc; + serial = pp->p_port; + DPRINTF(("ubserstart: sc = %p, tp = %p\n", sc, tp)); + + if (sc->sc_dying) + return; + + if (ISSET(tp->t_state, TS_BUSY | TS_TIMEOUT | TS_TTSTOP)) { + ttwwakeup(tp); + DPRINTF(("ubserstart: stopped\n")); + return; + } + + if (tp->t_outq.c_cc <= tp->t_olowat) { + if (ISSET(tp->t_state, TS_SO_OLOWAT)) { + CLR(tp->t_state, TS_SO_OLOWAT); + wakeup(TSA_OLOWAT(tp)); + } + selwakeuppri(&tp->t_wsel, TTIPRI); + if (tp->t_outq.c_cc == 0) { + if (ISSET(tp->t_state, TS_BUSY | TS_SO_OCOMPLETE) == + TS_SO_OCOMPLETE && tp->t_outq.c_cc == 0) { + CLR(tp->t_state, TS_SO_OCOMPLETE); + wakeup(TSA_OCOMPLETE(tp)); + } + return; + } + } + + /* Grab the first contiguous region of buffer space. */ + data = tp->t_outq.c_cf; + cbp = (struct cblock *) ((intptr_t) tp->t_outq.c_cf & ~CROUND); + cnt = min((char *) (cbp+1) - tp->t_outq.c_cf, tp->t_outq.c_cc); + + if (cnt == 0) { + DPRINTF(("ubserstart: cnt == 0\n")); + return; + } + + SET(tp->t_state, TS_BUSY); + + if (cnt + sc->sc_opkthdrlen > sc->sc_obufsize) { + DPRINTF(("ubserstart: big buffer %d chars\n", cnt)); + cnt = sc->sc_obufsize; + } + sc->sc_port[serial].p_obuf[0] = serial; + memcpy(sc->sc_port[serial].p_obuf + sc->sc_opkthdrlen, data, cnt); + + + DPRINTF(("ubserstart: %d chars\n", cnt)); + usbd_setup_xfer(sc->sc_port[serial].p_oxfer, sc->sc_bulkout_pipe, + (usbd_private_handle)tp, sc->sc_port[serial].p_obuf, + cnt + sc->sc_opkthdrlen, + USBD_NO_COPY, USBD_NO_TIMEOUT, ubserwritecb); + /* What can we do on error? */ + err = usbd_transfer(sc->sc_port[serial].p_oxfer); + if (err != USBD_IN_PROGRESS) + printf("ubserstart: err=%s\n", usbd_errstr(err)); + + ttwwakeup(tp); +} + +static void +ubserstop(struct tty *tp, int flag) +{ + struct ubser_softc *sc; + + sc = tp->t_sc; + + DPRINTF(("ubserstop: %d\n", flag)); + + if (flag & FWRITE) { + DPRINTF(("ubserstop: write\n")); + if (ISSET(tp->t_state, TS_BUSY)) { + /* XXX do what? */ + if (!ISSET(tp->t_state, TS_TTSTOP)) + SET(tp->t_state, TS_FLUSH); + } + } + + DPRINTF(("ubserstop: done\n")); +} + +static void +ubserwritecb(usbd_xfer_handle xfer, usbd_private_handle p, usbd_status status) +{ + struct tty *tp; + struct ubser_softc *sc; + struct ubser_port *pp; + u_int32_t cc; + + tp = (struct tty *)p; + pp = tp->t_sc; + sc = pp->p_sc; + + DPRINTF(("ubserwritecb: status = %d\n", status)); + + if (status == USBD_CANCELLED || sc->sc_dying) + goto error; + + if (status != USBD_NORMAL_COMPLETION) { + device_printf(sc->sc_dev, "ubserwritecb: %s\n", + usbd_errstr(status)); + if (status == USBD_STALLED) + usbd_clear_endpoint_stall_async(sc->sc_bulkin_pipe); + /* XXX we should restart after some delay. */ + goto error; + } + + usbd_get_xfer_status(xfer, NULL, NULL, &cc, NULL); + DPRINTF(("ubserwritecb: cc = %d\n", cc)); + if (cc <= sc->sc_opkthdrlen) { + device_printf(sc->sc_dev, "sent size too small, cc = %d\n", + cc); + goto error; + } + + /* convert from USB bytes to tty bytes */ + cc -= sc->sc_opkthdrlen; + + CLR(tp->t_state, TS_BUSY); + if (ISSET(tp->t_state, TS_FLUSH)) + CLR(tp->t_state, TS_FLUSH); + else + ndflush(&tp->t_outq, cc); + ttyld_start(tp); + + return; + +error: + CLR(tp->t_state, TS_BUSY); + return; +} + +static usbd_status +ubserstartread(struct ubser_softc *sc) +{ + usbd_status err; + + DPRINTF(("ubserstartread: start\n")); + + if (sc->sc_bulkin_pipe == NULL) + return (USBD_NORMAL_COMPLETION); + + usbd_setup_xfer(sc->sc_ixfer, sc->sc_bulkin_pipe, + (usbd_private_handle)sc, + sc->sc_ibuf, sc->sc_ibufsizepad, + USBD_SHORT_XFER_OK | USBD_NO_COPY, + USBD_NO_TIMEOUT, ubserreadcb); + + err = usbd_transfer(sc->sc_ixfer); + if (err != USBD_IN_PROGRESS) { + DPRINTF(("ubserstartread: err = %s\n", usbd_errstr(err))); + return (err); + } + + return (USBD_NORMAL_COMPLETION); +} + +static void +ubserreadcb(usbd_xfer_handle xfer, usbd_private_handle p, usbd_status status) +{ + struct ubser_softc *sc = (struct ubser_softc *)p; + struct tty *tp; + usbd_status err; + u_int32_t cc; + u_char *cp; + int lostcc; + + if (status == USBD_IOERROR) { + device_printf(sc->sc_dev, "ubserreadcb: %s - restarting\n", + usbd_errstr(status)); + goto resubmit; + } + + DPRINTF(("ubserreadcb: status = %d\n", status)); + + if (status != USBD_NORMAL_COMPLETION) { + if (status != USBD_CANCELLED) { + device_printf(sc->sc_dev, "ubserreadcb: %s\n", + usbd_errstr(status)); + } + if (status == USBD_STALLED) + usbd_clear_endpoint_stall_async(sc->sc_bulkin_pipe); + return; + } + + usbd_get_xfer_status(xfer, NULL, (void **)&cp, &cc, NULL); + + DPRINTF(("ubserreadcb: got %d bytes from device\n", cc)); + if (cc == 0) + goto resubmit; + + if (cc > sc->sc_ibufsizepad) { + device_printf(sc->sc_dev, "invalid receive data size, %d chars\n", + cc); + goto resubmit; + } + + /* parse header */ + if (cc < 1) + goto resubmit; + DPRINTF(("ubserreadcb: got %d chars for serial %d\n", cc - 1, *cp)); + tp = sc->sc_port[*cp].p_tty; + cp++; + cc--; + + if (cc < 1) + goto resubmit; + + if (!(tp->t_state & TS_ISOPEN)) /* drop data for unused serials */ + goto resubmit; + + if (tp->t_state & TS_CAN_BYPASS_L_RINT) { + if (tp->t_rawq.c_cc + cc > tp->t_ihiwat + && (tp->t_iflag & IXOFF) + && !(tp->t_state & TS_TBLOCK)) + ttyblock(tp); + lostcc = b_to_q((char *)cp, cc, &tp->t_rawq); + tp->t_rawcc += cc; + ttwakeup(tp); + if (tp->t_state & TS_TTSTOP + && (tp->t_iflag & IXANY + || tp->t_cc[VSTART] == tp->t_cc[VSTOP])) { + tp->t_state &= ~TS_TTSTOP; + tp->t_lflag &= ~FLUSHO; + ubserstart(tp); + } + if (lostcc > 0) + device_printf(sc->sc_dev, "lost %d chars\n", lostcc); + } else { + /* Give characters to tty layer. */ + while (cc > 0) { + DPRINTFN(7, ("ubserreadcb: char = 0x%02x\n", *cp)); + if (ttyld_rint(tp, *cp) == -1) { + /* XXX what should we do? */ + device_printf(sc->sc_dev, "lost %d chars\n", + cc); + break; + } + cc--; + cp++; + } + } + +resubmit: + err = ubserstartread(sc); + if (err) { + device_printf(sc->sc_dev, "read start failed\n"); + /* XXX what should we do now? */ + } + +} + +static void +ubser_cleanup(struct ubser_softc *sc) +{ + int i; + struct ubser_port *pp; + + DPRINTF(("ubser_cleanup: closing pipes\n")); + + if (sc->sc_bulkin_pipe != NULL) { + usbd_abort_pipe(sc->sc_bulkin_pipe); + usbd_close_pipe(sc->sc_bulkin_pipe); + sc->sc_bulkin_pipe = NULL; + } + if (sc->sc_bulkout_pipe != NULL) { + usbd_abort_pipe(sc->sc_bulkout_pipe); + usbd_close_pipe(sc->sc_bulkout_pipe); + sc->sc_bulkout_pipe = NULL; + } + if (sc->sc_ixfer != NULL) { + usbd_free_xfer(sc->sc_ixfer); + sc->sc_ixfer = NULL; + } + for (i = 0; i < sc->sc_numser; i++) { + pp = &sc->sc_port[i]; + if (pp->p_oxfer != NULL) { + usbd_free_xfer(pp->p_oxfer); + pp->p_oxfer = NULL; + } + } +} + +static int +ubseropen(struct tty *tp, struct cdev *dev) +{ + struct ubser_softc *sc; + struct ubser_port *pp; + + pp = tp->t_sc; + sc = pp->p_sc; + + sc->sc_refcnt++; /* XXX: wrong refcnt on error later on */ + return (0); +} + +static void +ubserclose(struct tty *tp) +{ + struct ubser_softc *sc; + struct ubser_port *pp; + + pp = tp->t_sc; + sc = pp->p_sc; + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); +} + +static void +ubserbreak(struct tty *tp, int sig) +{ + usb_device_request_t req; + struct ubser_softc *sc; + struct ubser_port *pp; + int error; + int alen; + + pp = tp->t_sc; + sc = pp->p_sc; + if (sig) { + DPRINTF(("ubser_break: TIOCSBRK\n")); + req.bmRequestType = UT_READ_VENDOR_INTERFACE; + req.bRequest = VENDOR_SET_BREAK; + USETW(req.wValue, pp->p_port); + USETW(req.wIndex, sc->sc_ifaceno); + USETW(req.wLength, 0); + error = usbd_do_request_flags(sc->sc_udev, &req, &sc->sc_numser, + USBD_SHORT_XFER_OK, &alen, USBD_DEFAULT_TIMEOUT); + } +} + +static int +ubsermodem(struct tty *tp, int sigon, int sigoff) +{ + + return (SER_DTR | SER_RTS | SER_DCD); +} + +MODULE_DEPEND(ubser, usb, 1, 1, 1); +DRIVER_MODULE(ubser, uhub, ubser_driver, ubser_devclass, usbd_driver_load, 0); diff --git a/sys/legacy/dev/usb/ubser.h b/sys/legacy/dev/usb/ubser.h new file mode 100644 index 0000000..f256d4a --- /dev/null +++ b/sys/legacy/dev/usb/ubser.h @@ -0,0 +1,43 @@ +/*- + * Copyright (c) 2003 Bernd Walter <ticso@freebsd.org> + * + * $URL: https://devel.bwct.de/svn/projects/ubser/ubser.h $ + * $Date: 2004-02-29 01:53:10 +0100 (Sun, 29 Feb 2004) $ + * $Author: ticso $ + * $Rev: 1127 $ + * + * 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$ + */ + +#ifndef UBSER_H +#define UBSER_H + +#define MAX_SER 32 + +/* Vendor Interface Requests */ +#define VENDOR_GET_NUMSER 0x01 +#define VENDOR_SET_BREAK 0x02 +#define VENDOR_CLEAR_BREAK 0x03 + +#endif /* UBSER_H */ diff --git a/sys/legacy/dev/usb/uchcom.c b/sys/legacy/dev/usb/uchcom.c new file mode 100644 index 0000000..85b2629 --- /dev/null +++ b/sys/legacy/dev/usb/uchcom.c @@ -0,0 +1,1036 @@ +/* $NetBSD: uchcom.c,v 1.1 2007/09/03 17:57:37 tshiozak Exp $ */ + +/*- + * Copyright (c) 2007, Takanori Watanabe + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 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. + */ + +/* + * Copyright (c) 2007 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Takuya SHIOZAKI (tshiozak@netbsd.org). + * + * 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * driver for WinChipHead CH341/340, the worst USB-serial chip in the world. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/conf.h> +#include <sys/tty.h> +#include <sys/file.h> +#include <sys/select.h> +#include <sys/proc.h> +#include <sys/bus.h> +#include <sys/poll.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbcdc.h> + +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usb_quirks.h> + +#include <dev/usb/ucomvar.h> +#include "usbdevs.h" + +#ifdef UCHCOM_DEBUG +#define DPRINTFN(n, x) if (uchcomdebug > (n)) logprintf x +int uchcomdebug = 0; +#else +#define DPRINTFN(n, x) +#endif +#define DPRINTF(x) DPRINTFN(0, x) + +#define UCHCOM_IFACE_INDEX 0 +#define UCHCOM_CONFIG_INDEX 0 + +#define UCHCOM_REV_CH340 0x0250 +#define UCHCOM_INPUT_BUF_SIZE 8 + +#define UCHCOM_REQ_GET_VERSION 0x5F +#define UCHCOM_REQ_READ_REG 0x95 +#define UCHCOM_REQ_WRITE_REG 0x9A +#define UCHCOM_REQ_RESET 0xA1 +#define UCHCOM_REQ_SET_DTRRTS 0xA4 + +#define UCHCOM_REG_STAT1 0x06 +#define UCHCOM_REG_STAT2 0x07 +#define UCHCOM_REG_BPS_PRE 0x12 +#define UCHCOM_REG_BPS_DIV 0x13 +#define UCHCOM_REG_BPS_MOD 0x14 +#define UCHCOM_REG_BPS_PAD 0x0F +#define UCHCOM_REG_BREAK1 0x05 +#define UCHCOM_REG_BREAK2 0x18 +#define UCHCOM_REG_LCR1 0x18 +#define UCHCOM_REG_LCR2 0x25 + +#define UCHCOM_VER_20 0x20 + +#define UCHCOM_BASE_UNKNOWN 0 +#define UCHCOM_BPS_MOD_BASE 20000000 +#define UCHCOM_BPS_MOD_BASE_OFS 1100 + +#define UCHCOM_DTR_MASK 0x20 +#define UCHCOM_RTS_MASK 0x40 + +#define UCHCOM_BRK1_MASK 0x01 +#define UCHCOM_BRK2_MASK 0x40 + +#define UCHCOM_LCR1_MASK 0xAF +#define UCHCOM_LCR2_MASK 0x07 +#define UCHCOM_LCR1_PARENB 0x80 +#define UCHCOM_LCR2_PAREVEN 0x07 +#define UCHCOM_LCR2_PARODD 0x06 +#define UCHCOM_LCR2_PARMARK 0x05 +#define UCHCOM_LCR2_PARSPACE 0x04 + +#define UCHCOM_INTR_STAT1 0x02 +#define UCHCOM_INTR_STAT2 0x03 +#define UCHCOM_INTR_LEAST 4 + +#define UCHCOMIBUFSIZE 256 +#define UCHCOMOBUFSIZE 256 + +struct uchcom_softc +{ + struct ucom_softc sc_ucom; + + /* */ + int sc_intr_endpoint; + int sc_intr_size; + usbd_pipe_handle sc_intr_pipe; + u_char *sc_intr_buf; + /* */ + uint8_t sc_version; + int sc_dtr; + int sc_rts; + u_char sc_lsr; + u_char sc_msr; + int sc_lcr1; + int sc_lcr2; +}; + +struct uchcom_endpoints +{ + int ep_bulkin; + int ep_bulkout; + int ep_intr; + int ep_intr_size; +}; + +struct uchcom_divider +{ + uint8_t dv_prescaler; + uint8_t dv_div; + uint8_t dv_mod; +}; + +struct uchcom_divider_record +{ + uint32_t dvr_high; + uint32_t dvr_low; + uint32_t dvr_base_clock; + struct uchcom_divider dvr_divider; +}; + +static const struct uchcom_divider_record dividers[] = +{ + { 307200, 307200, UCHCOM_BASE_UNKNOWN, { 7, 0xD9, 0 } }, + { 921600, 921600, UCHCOM_BASE_UNKNOWN, { 7, 0xF3, 0 } }, + { 2999999, 23530, 6000000, { 3, 0, 0 } }, + { 23529, 2942, 750000, { 2, 0, 0 } }, + { 2941, 368, 93750, { 1, 0, 0 } }, + { 367, 1, 11719, { 0, 0, 0 } }, +}; +#define NUM_DIVIDERS (sizeof (dividers) / sizeof (dividers[0])) + +static const struct usb_devno uchcom_devs[] = { + { USB_VENDOR_WCH, USB_PRODUCT_WCH_CH341SER }, +}; +#define uchcom_lookup(v, p) usb_lookup(uchcom_devs, v, p) + +static void uchcom_get_status(void *, int, u_char *, u_char *); +static void uchcom_set(void *, int, int, int); +static int uchcom_param(void *, int, struct termios *); +static int uchcom_open(void *, int); +static void uchcom_close(void *, int); +static void uchcom_intr(usbd_xfer_handle, usbd_private_handle, + usbd_status); + +static int set_config(device_t ); +static int find_ifaces(struct uchcom_softc *, usbd_interface_handle *); +static int find_endpoints(struct uchcom_softc *, + struct uchcom_endpoints *); +static void close_intr_pipe(struct uchcom_softc *); + +static int uchcom_match(device_t ); +static int uchcom_attach(device_t ); +static int uchcom_detach(device_t ); + +struct ucom_callback uchcom_callback = { + .ucom_get_status = uchcom_get_status, + .ucom_set = uchcom_set, + .ucom_param = uchcom_param, + .ucom_ioctl = NULL, + .ucom_open = uchcom_open, + .ucom_close = uchcom_close, + .ucom_read = NULL, + .ucom_write = NULL, +}; + + + + +/* ---------------------------------------------------------------------- + * driver entry points + */ + +static int uchcom_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + + return (uchcom_lookup(uaa->vendor, uaa->product) != NULL ? + UMATCH_VENDOR_PRODUCT : UMATCH_NONE); +} + +static int uchcom_attach(device_t self) +{ + struct uchcom_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + usbd_device_handle dev = uaa->device; + + struct uchcom_endpoints endpoints; + struct ucom_softc *ucom = &sc->sc_ucom; + + ucom->sc_dev = self; + ucom->sc_udev = dev; + + ucom->sc_dying = 0; + sc->sc_dtr = sc->sc_rts = -1; + sc->sc_lsr = sc->sc_msr = 0; + + DPRINTF(("\n\nuchcom attach: sc=%p\n", sc)); + + if (set_config(self)) + goto failed; + + switch (uaa->release) { + case UCHCOM_REV_CH340: + device_printf(self, "CH340 detected\n"); + break; + default: + device_printf(self, "CH341 detected\n"); + break; + } + + if (find_ifaces(sc, &ucom->sc_iface)) + goto failed; + + if (find_endpoints(sc, &endpoints)) + goto failed; + + sc->sc_intr_endpoint = endpoints.ep_intr; + sc->sc_intr_size = endpoints.ep_intr_size; + + /* setup ucom layer */ + ucom->sc_portno = UCOM_UNK_PORTNO; + ucom->sc_bulkin_no = endpoints.ep_bulkin; + ucom->sc_bulkout_no = endpoints.ep_bulkout; + ucom->sc_ibufsize = UCHCOMIBUFSIZE; + ucom->sc_obufsize = UCHCOMOBUFSIZE; + ucom->sc_ibufsizepad = UCHCOMIBUFSIZE; + ucom->sc_opkthdrlen = 0; + ucom->sc_parent = sc; + + ucom->sc_callback = &uchcom_callback; + + ucom_attach(&sc->sc_ucom); + + return 0; + +failed: + ucom->sc_dying = 1; + return ENXIO; +} + +static int uchcom_detach(device_t self) +{ + struct uchcom_softc *sc = device_get_softc(self); + struct ucom_softc *ucom = &sc->sc_ucom ; + int rv = 0; + + DPRINTF(("uchcom_detach: sc=%p flags=%d\n", sc, flags)); + + close_intr_pipe(sc); + + ucom->sc_dying = 1; + + rv = ucom_detach(ucom); + + return rv; +} +static int +set_config(device_t dev) +{ + struct uchcom_softc *sc = device_get_softc(dev); + struct ucom_softc *ucom = &sc->sc_ucom; + usbd_status err; + + err = usbd_set_config_index(ucom->sc_udev, UCHCOM_CONFIG_INDEX, 1); + if (err) { + device_printf(dev, "failed to set configuration: %s\n", + usbd_errstr(err)); + return -1; + } + + return 0; +} + +static int +find_ifaces(struct uchcom_softc *sc, usbd_interface_handle *riface) +{ + usbd_status err; + struct ucom_softc *ucom = &sc->sc_ucom; + + err = usbd_device2interface_handle(ucom->sc_udev, UCHCOM_IFACE_INDEX, + riface); + if (err) { + device_printf(ucom->sc_dev, "failed to get interface: %s\n", + usbd_errstr(err)); + return -1; + } + + return 0; +} + +static int +find_endpoints(struct uchcom_softc *sc, struct uchcom_endpoints *endpoints) +{ + struct ucom_softc *ucom= &sc->sc_ucom; + int i, bin=-1, bout=-1, intr=-1, isize=0; + usb_interface_descriptor_t *id; + usb_endpoint_descriptor_t *ed; + + id = usbd_get_interface_descriptor(ucom->sc_iface); + + for (i = 0; i < id->bNumEndpoints; i++) { + ed = usbd_interface2endpoint_descriptor(ucom->sc_iface, i); + if (ed == NULL) { + device_printf(ucom->sc_dev, "no endpoint descriptor for %d\n", i); + return -1; + } + + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_INTERRUPT) { + intr = ed->bEndpointAddress; + isize = UGETW(ed->wMaxPacketSize); + } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + bin = ed->bEndpointAddress; + } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + bout = ed->bEndpointAddress; + } + } + + if (intr == -1 || bin == -1 || bout == -1) { + if (intr == -1) { + device_printf(ucom->sc_dev, "no interrupt end point\n"); + } + if (bin == -1) { + device_printf(ucom->sc_dev, "no data bulk in end point\n"); + + } + if (bout == -1) { + device_printf(ucom->sc_dev, "no data bulk out end point\n"); + } + return -1; + } + if (isize < UCHCOM_INTR_LEAST) { + device_printf(ucom->sc_dev, "intr pipe is too short"); + return -1; + } + + DPRINTF(("%s: bulkin=%d, bulkout=%d, intr=%d, isize=%d\n", + USBDEVNAME(sc->sc_dev), bin, bout, intr, isize)); + + endpoints->ep_intr = intr; + endpoints->ep_intr_size = isize; + endpoints->ep_bulkin = bin; + endpoints->ep_bulkout = bout; + + return 0; +} + + +/* ---------------------------------------------------------------------- + * low level i/o + */ + +static __inline usbd_status +generic_control_out(struct uchcom_softc *sc, uint8_t reqno, + uint16_t value, uint16_t index) +{ + usb_device_request_t req; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = reqno; + USETW(req.wValue, value); + USETW(req.wIndex, index); + USETW(req.wLength, 0); + + return usbd_do_request(sc->sc_ucom.sc_udev, &req, 0); +} + +static __inline usbd_status +generic_control_in(struct uchcom_softc *sc, uint8_t reqno, + uint16_t value, uint16_t index, void *buf, int buflen, + int *actlen) +{ + usb_device_request_t req; + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = reqno; + USETW(req.wValue, value); + USETW(req.wIndex, index); + USETW(req.wLength, (uint16_t)buflen); + + return usbd_do_request_flags(sc->sc_ucom.sc_udev, &req, buf, + USBD_SHORT_XFER_OK, actlen, + USBD_DEFAULT_TIMEOUT); +} + +static __inline usbd_status +write_reg(struct uchcom_softc *sc, + uint8_t reg1, uint8_t val1, uint8_t reg2, uint8_t val2) +{ + DPRINTF(("uchcom: write reg 0x%02X<-0x%02X, 0x%02X<-0x%02X\n", + (unsigned)reg1, (unsigned)val1, + (unsigned)reg2, (unsigned)val2)); + return generic_control_out( + sc, UCHCOM_REQ_WRITE_REG, + reg1|((uint16_t)reg2<<8), val1|((uint16_t)val2<<8)); +} + +static __inline usbd_status +read_reg(struct uchcom_softc *sc, + uint8_t reg1, uint8_t *rval1, uint8_t reg2, uint8_t *rval2) +{ + uint8_t buf[UCHCOM_INPUT_BUF_SIZE]; + usbd_status err; + int actin; + + err = generic_control_in( + sc, UCHCOM_REQ_READ_REG, + reg1|((uint16_t)reg2<<8), 0, buf, sizeof buf, &actin); + if (err) + return err; + + DPRINTF(("uchcom: read reg 0x%02X->0x%02X, 0x%02X->0x%02X\n", + (unsigned)reg1, (unsigned)buf[0], + (unsigned)reg2, (unsigned)buf[1])); + + if (rval1) *rval1 = buf[0]; + if (rval2) *rval2 = buf[1]; + + return USBD_NORMAL_COMPLETION; +} + +static __inline usbd_status +get_version(struct uchcom_softc *sc, uint8_t *rver) +{ + uint8_t buf[UCHCOM_INPUT_BUF_SIZE]; + usbd_status err; + int actin; + + err = generic_control_in( + sc, UCHCOM_REQ_GET_VERSION, 0, 0, buf, sizeof buf, &actin); + if (err) + return err; + + if (rver) *rver = buf[0]; + + return USBD_NORMAL_COMPLETION; +} + +static __inline usbd_status +get_status(struct uchcom_softc *sc, uint8_t *rval) +{ + return read_reg(sc, UCHCOM_REG_STAT1, rval, UCHCOM_REG_STAT2, NULL); +} + +static __inline usbd_status +set_dtrrts_10(struct uchcom_softc *sc, uint8_t val) +{ + return write_reg(sc, UCHCOM_REG_STAT1, val, UCHCOM_REG_STAT1, val); +} + +static __inline usbd_status +set_dtrrts_20(struct uchcom_softc *sc, uint8_t val) +{ + return generic_control_out(sc, UCHCOM_REQ_SET_DTRRTS, val, 0); +} + + +/* ---------------------------------------------------------------------- + * middle layer + */ + +static int +update_version(struct uchcom_softc *sc) +{ + usbd_status err; + + err = get_version(sc, &sc->sc_version); + if (err) { + device_printf(sc->sc_ucom.sc_dev, "cannot get version: %s\n", + usbd_errstr(err)); + return EIO; + } + + return 0; +} + +static void +convert_status(struct uchcom_softc *sc, uint8_t cur) +{ + sc->sc_dtr = !(cur & UCHCOM_DTR_MASK); + sc->sc_rts = !(cur & UCHCOM_RTS_MASK); + + cur = ~cur & 0x0F; + sc->sc_msr = (cur << 4) | ((sc->sc_msr >> 4) ^ cur); +} + +static int +update_status(struct uchcom_softc *sc) +{ + usbd_status err; + uint8_t cur; + + err = get_status(sc, &cur); + if (err) { + device_printf(sc->sc_ucom.sc_dev, + "cannot update status: %s\n", + usbd_errstr(err)); + return EIO; + } + convert_status(sc, cur); + + return 0; +} + + +static int +set_dtrrts(struct uchcom_softc *sc, int dtr, int rts) +{ + usbd_status err; + uint8_t val = 0; + + if (dtr) val |= UCHCOM_DTR_MASK; + if (rts) val |= UCHCOM_RTS_MASK; + + if (sc->sc_version < UCHCOM_VER_20) + err = set_dtrrts_10(sc, ~val); + else + err = set_dtrrts_20(sc, ~val); + + if (err) { + device_printf(sc->sc_ucom.sc_dev, "cannot set DTR/RTS: %s\n", + usbd_errstr(err)); + return EIO; + } + + return 0; +} + +static int +set_break(struct uchcom_softc *sc, int onoff) +{ + usbd_status err; + uint8_t brk1, brk2; + + err = read_reg(sc, UCHCOM_REG_BREAK1, &brk1, UCHCOM_REG_BREAK2, &brk2); + if (err) + return EIO; + if (onoff) { + /* on - clear bits */ + brk1 &= ~UCHCOM_BRK1_MASK; + brk2 &= ~UCHCOM_BRK2_MASK; + } else { + /* off - set bits */ + brk1 |= UCHCOM_BRK1_MASK; + brk2 |= UCHCOM_BRK2_MASK; + } + err = write_reg(sc, UCHCOM_REG_BREAK1, brk1, UCHCOM_REG_BREAK2, brk2); + if (err) + return EIO; + + return 0; +} + +static int +calc_divider_settings(struct uchcom_divider *dp, uint32_t rate) +{ + int i; + const struct uchcom_divider_record *rp; + uint32_t div, rem, mod; + + /* find record */ + for (i=0; i<NUM_DIVIDERS; i++) { + if (dividers[i].dvr_high >= rate && + dividers[i].dvr_low <= rate) { + rp = ÷rs[i]; + goto found; + } + } + return -1; + +found: + dp->dv_prescaler = rp->dvr_divider.dv_prescaler; + if (rp->dvr_base_clock == UCHCOM_BASE_UNKNOWN) + dp->dv_div = rp->dvr_divider.dv_div; + else { + div = rp->dvr_base_clock / rate; + rem = rp->dvr_base_clock % rate; + if (div==0 || div>=0xFF) + return -1; + if ((rem<<1) >= rate) + div += 1; + dp->dv_div = (uint8_t)-div; + } + + mod = UCHCOM_BPS_MOD_BASE/rate + UCHCOM_BPS_MOD_BASE_OFS; + mod = mod + mod/2; + + dp->dv_mod = mod / 0x100; + + return 0; +} + +static int +set_dte_rate(struct uchcom_softc *sc, uint32_t rate) +{ + usbd_status err; + struct uchcom_divider dv; + + if (calc_divider_settings(&dv, rate)) + return EINVAL; + + if ((err = write_reg(sc, + UCHCOM_REG_BPS_PRE, dv.dv_prescaler, + UCHCOM_REG_BPS_DIV, dv.dv_div)) || + (err = write_reg(sc, + UCHCOM_REG_BPS_MOD, dv.dv_mod, + UCHCOM_REG_BPS_PAD, 0))) { + device_printf(sc->sc_ucom.sc_dev, " cannot set DTE rate: %s\n", + usbd_errstr(err)); + return EIO; + } + + return 0; +} + +static int +set_line_control(struct uchcom_softc *sc, tcflag_t cflag) +{ + usbd_status err; + uint8_t lcr1 = 0, lcr2 = 0; + + err = read_reg(sc, UCHCOM_REG_LCR1, &lcr1, UCHCOM_REG_LCR2, &lcr2); + if (err) { + device_printf(sc->sc_ucom.sc_dev, " cannot get LCR: %s\n", + usbd_errstr(err)); + return EIO; + } + + lcr1 &= ~UCHCOM_LCR1_MASK; + lcr2 &= ~UCHCOM_LCR2_MASK; + + /* + * XXX: it is difficult to handle the line control appropriately: + * - CS8, !CSTOPB and any parity mode seems ok, but + * - the chip doesn't have the function to calculate parity + * in !CS8 mode. + * - it is unclear that the chip supports CS5,6 mode. + * - it is unclear how to handle stop bits. + */ + + switch (ISSET(cflag, CSIZE)) { + case CS5: + case CS6: + case CS7: + return EINVAL; + case CS8: + break; + } + + if (ISSET(cflag, PARENB)) { + lcr1 |= UCHCOM_LCR1_PARENB; + if (ISSET(cflag, PARODD)) + lcr2 |= UCHCOM_LCR2_PARODD; + else + lcr2 |= UCHCOM_LCR2_PAREVEN; + } + + err = write_reg(sc, UCHCOM_REG_LCR1, lcr1, UCHCOM_REG_LCR2, lcr2); + if (err) { + device_printf(sc->sc_ucom.sc_dev, "cannot set LCR: %s\n", + usbd_errstr(err)); + return EIO; + } + + return 0; +} + +static int +clear_chip(struct uchcom_softc *sc) +{ + usbd_status err; + + DPRINTF(("%s: clear\n", USBDEVNAME(sc->sc_dev))); + err = generic_control_out(sc, UCHCOM_REQ_RESET, 0, 0); + if (err) { + device_printf(sc->sc_ucom.sc_dev, "cannot clear: %s\n", + usbd_errstr(err)); + return EIO; + } + + return 0; +} + +static int +reset_chip(struct uchcom_softc *sc) +{ + usbd_status err; + uint8_t lcr1, lcr2, pre, div, mod; + uint16_t val=0, idx=0; + + err = read_reg(sc, UCHCOM_REG_LCR1, &lcr1, UCHCOM_REG_LCR2, &lcr2); + if (err) + goto failed; + + err = read_reg(sc, UCHCOM_REG_BPS_PRE, &pre, UCHCOM_REG_BPS_DIV, &div); + if (err) + goto failed; + + err = read_reg(sc, UCHCOM_REG_BPS_MOD, &mod, UCHCOM_REG_BPS_PAD, NULL); + if (err) + goto failed; + + val |= (uint16_t)(lcr1&0xF0) << 8; + val |= 0x01; + val |= (uint16_t)(lcr2&0x0F) << 8; + val |= 0x02; + idx |= pre & 0x07; + val |= 0x04; + idx |= (uint16_t)div << 8; + val |= 0x08; + idx |= mod & 0xF8; + val |= 0x10; + + DPRINTF(("%s: reset v=0x%04X, i=0x%04X\n", + USBDEVNAME(sc->sc_dev), val, idx)); + + err = generic_control_out(sc, UCHCOM_REQ_RESET, val, idx); + if (err) + goto failed; + + return 0; + +failed: + device_printf(sc->sc_ucom.sc_dev, "cannot reset: %s\n", + usbd_errstr(err)); + return EIO; +} + +static int +setup_comm(struct uchcom_softc *sc) +{ + int ret; + + ret = update_version(sc); + if (ret) + return ret; + + ret = clear_chip(sc); + if (ret) + return ret; + + ret = set_dte_rate(sc, TTYDEF_SPEED); + if (ret) + return ret; + + ret = set_line_control(sc, CS8); + if (ret) + return ret; + + ret = update_status(sc); + if (ret) + return ret; + + ret = reset_chip(sc); + if (ret) + return ret; + + ret = set_dte_rate(sc, TTYDEF_SPEED); /* XXX */ + if (ret) + return ret; + + sc->sc_dtr = sc->sc_rts = 1; + ret = set_dtrrts(sc, sc->sc_dtr, sc->sc_rts); + if (ret) + return ret; + + return 0; +} + +static int +setup_intr_pipe(struct uchcom_softc *sc) +{ + usbd_status err; + struct ucom_softc *ucom = &sc->sc_ucom; + if (sc->sc_intr_endpoint != -1 && sc->sc_intr_pipe == NULL) { + sc->sc_intr_buf = malloc(sc->sc_intr_size, M_USBDEV, M_WAITOK); + err = usbd_open_pipe_intr(ucom->sc_iface, + sc->sc_intr_endpoint, + USBD_SHORT_XFER_OK, + &sc->sc_intr_pipe, sc, + sc->sc_intr_buf, + sc->sc_intr_size, + uchcom_intr, USBD_DEFAULT_INTERVAL); + if (err) { + device_printf(ucom->sc_dev, + "cannot open interrupt pipe: %s\n", + usbd_errstr(err)); + return EIO; + } + } + return 0; +} + +static void +close_intr_pipe(struct uchcom_softc *sc) +{ + usbd_status err; + struct ucom_softc *ucom = &sc->sc_ucom; + if (ucom->sc_dying) + return; + + if (sc->sc_intr_pipe != NULL) { + err = usbd_abort_pipe(sc->sc_intr_pipe); + if (err) + device_printf(ucom->sc_dev, + "abort interrupt pipe failed: %s\n", + usbd_errstr(err)); + err = usbd_close_pipe(sc->sc_intr_pipe); + if (err) + device_printf(ucom->sc_dev, + " close interrupt pipe failed: %s\n", + usbd_errstr(err)); + free(sc->sc_intr_buf, M_USBDEV); + sc->sc_intr_pipe = NULL; + } +} + + +/* ---------------------------------------------------------------------- + * methods for ucom + */ +void +uchcom_get_status(void *arg, int portno, u_char *rlsr, u_char *rmsr) +{ + struct uchcom_softc *sc = arg; + + if (sc->sc_ucom.sc_dying) + return; + + *rlsr = sc->sc_lsr; + *rmsr = sc->sc_msr; +} + +void +uchcom_set(void *arg, int portno, int reg, int onoff) +{ + struct uchcom_softc *sc = arg; + + if (sc->sc_ucom.sc_dying) + return; + + switch (reg) { + case UCOM_SET_DTR: + sc->sc_dtr = !!onoff; + set_dtrrts(sc, sc->sc_dtr, sc->sc_rts); + break; + case UCOM_SET_RTS: + sc->sc_rts = !!onoff; + set_dtrrts(sc, sc->sc_dtr, sc->sc_rts); + break; + case UCOM_SET_BREAK: + set_break(sc, onoff); + break; + } +} + +int +uchcom_param(void *arg, int portno, struct termios *t) +{ + struct uchcom_softc *sc = arg; + int ret; + + if (sc->sc_ucom.sc_dying) + return 0; + + ret = set_line_control(sc, t->c_cflag); + if (ret) + return ret; + + ret = set_dte_rate(sc, t->c_ospeed); + if (ret) + return ret; + + return 0; +} + +int +uchcom_open(void *arg, int portno) +{ + int ret; + struct uchcom_softc *sc = arg; + + if (sc->sc_ucom.sc_dying) + return EIO; + + ret = setup_intr_pipe(sc); + if (ret) + return ret; + + ret = setup_comm(sc); + if (ret) + return ret; + + return 0; +} + +void +uchcom_close(void *arg, int portno) +{ + struct uchcom_softc *sc = arg; + + if (sc->sc_ucom.sc_dying) + return; + + close_intr_pipe(sc); +} + + +/* ---------------------------------------------------------------------- + * callback when the modem status is changed. + */ +void +uchcom_intr(usbd_xfer_handle xfer, usbd_private_handle priv, + usbd_status status) +{ + struct uchcom_softc *sc = priv; + u_char *buf = sc->sc_intr_buf; + + if (sc->sc_ucom.sc_dying) + return; + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) + return; + + DPRINTF(("%s: abnormal status: %s\n", + USBDEVNAME(sc->sc_dev), usbd_errstr(status))); + usbd_clear_endpoint_stall_async(sc->sc_intr_pipe); + return; + } + DPRINTF(("%s: intr: 0x%02X 0x%02X 0x%02X 0x%02X " + "0x%02X 0x%02X 0x%02X 0x%02X\n", + USBDEVNAME(sc->sc_dev), + (unsigned)buf[0], (unsigned)buf[1], + (unsigned)buf[2], (unsigned)buf[3], + (unsigned)buf[4], (unsigned)buf[5], + (unsigned)buf[6], (unsigned)buf[7])); + + convert_status(sc, buf[UCHCOM_INTR_STAT1]); + ucom_status_change(&sc->sc_ucom); +} + +static device_method_t uchcom_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, uchcom_match), + DEVMETHOD(device_attach, uchcom_attach), + DEVMETHOD(device_detach, uchcom_detach), + + { 0, 0 } +}; + +static driver_t uchcom_driver = { + "ucom", + uchcom_methods, + sizeof (struct uchcom_softc) +}; + +DRIVER_MODULE(uchcom, uhub, uchcom_driver, ucom_devclass, usbd_driver_load, 0); +MODULE_DEPEND(uchcom, usb, 1, 1, 1); +MODULE_DEPEND(uchcom, ucom, UCOM_MINVER, UCOM_PREFVER, UCOM_MAXVER); diff --git a/sys/legacy/dev/usb/ucom.c b/sys/legacy/dev/usb/ucom.c new file mode 100644 index 0000000..ae263a0 --- /dev/null +++ b/sys/legacy/dev/usb/ucom.c @@ -0,0 +1,829 @@ +/* $NetBSD: ucom.c,v 1.40 2001/11/13 06:24:54 lukem Exp $ */ + +/*- + * Copyright (c) 2001-2003, 2005, 2008 + * Shunsuke Akiyama <akiyama@jp.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. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 1998, 2000 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 <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/ioccom.h> +#include <sys/fcntl.h> +#include <sys/conf.h> +#include <sys/serial.h> +#include <sys/tty.h> +#include <sys/file.h> +#include <sys/selinfo.h> +#include <sys/proc.h> +#include <sys/poll.h> +#include <sys/sysctl.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbcdc.h> + +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include "usbdevs.h" +#include <dev/usb/usb_quirks.h> + +#include <dev/usb/ucomvar.h> + +#ifdef USB_DEBUG +static int ucomdebug = 0; +SYSCTL_NODE(_hw_usb, OID_AUTO, ucom, CTLFLAG_RW, 0, "USB ucom"); +SYSCTL_INT(_hw_usb_ucom, OID_AUTO, debug, CTLFLAG_RW, + &ucomdebug, 0, "ucom debug level"); +#define DPRINTF(x) do { \ + if (ucomdebug) \ + printf x; \ + } while (0) + +#define DPRINTFN(n, x) do { \ + if (ucomdebug > (n)) \ + printf x; \ + } while (0) +#else +#define DPRINTF(x) +#define DPRINTFN(n, x) +#endif + +static int ucom_modevent(module_t, int, void *); +static void ucom_cleanup(struct ucom_softc *); +static void ucom_shutdown(struct ucom_softc *); +static void ucom_dtr(struct ucom_softc *, int); +static void ucom_rts(struct ucom_softc *, int); +static void ucombreak(struct ucom_softc *, int); +static usbd_status ucomstartread(struct ucom_softc *); +static void ucomreadcb(usbd_xfer_handle, usbd_private_handle, usbd_status); +static void ucomwritecb(usbd_xfer_handle, usbd_private_handle, usbd_status); +static void ucomstopread(struct ucom_softc *); + +static tsw_open_t ucomtty_open; +static tsw_close_t ucomtty_close; +static tsw_outwakeup_t ucomtty_outwakeup; +static tsw_ioctl_t ucomtty_ioctl; +static tsw_param_t ucomtty_param; +static tsw_modem_t ucomtty_modem; +static tsw_free_t ucomtty_free; + +static struct ttydevsw ucomtty_class = { + .tsw_flags = TF_INITLOCK|TF_CALLOUT, + .tsw_open = ucomtty_open, + .tsw_close = ucomtty_close, + .tsw_outwakeup = ucomtty_outwakeup, + .tsw_ioctl = ucomtty_ioctl, + .tsw_param = ucomtty_param, + .tsw_modem = ucomtty_modem, + .tsw_free = ucomtty_free, +}; + +devclass_t ucom_devclass; + +static moduledata_t ucom_mod = { + "ucom", + ucom_modevent, + NULL +}; + +DECLARE_MODULE(ucom, ucom_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); +MODULE_DEPEND(ucom, usb, 1, 1, 1); +MODULE_VERSION(ucom, UCOM_MODVER); + +static int +ucom_modevent(module_t mod, int type, void *data) +{ + switch (type) { + case MOD_LOAD: + case MOD_UNLOAD: + return (0); + default: + return (EOPNOTSUPP); + } +} + +void +ucom_attach_tty(struct ucom_softc *sc, char* fmt, int unit) +{ + struct tty *tp; + + sc->sc_tty = tp = tty_alloc(&ucomtty_class, sc, &Giant); + tty_makedev(tp, NULL, fmt, unit); +} + +int +ucom_attach(struct ucom_softc *sc) +{ + + ucom_attach_tty(sc, "U%d", device_get_unit(sc->sc_dev)); + + DPRINTF(("ucom_attach: ttycreate: tp = %p, %s\n", + sc->sc_tty, sc->sc_tty->t_dev->si_name)); + + return (0); +} + +int +ucom_detach(struct ucom_softc *sc) +{ + DPRINTF(("ucom_detach: sc = %p, tp = %p\n", sc, sc->sc_tty)); + + tty_lock(sc->sc_tty); + sc->sc_dying = 1; + + if (sc->sc_bulkin_pipe != NULL) + usbd_abort_pipe(sc->sc_bulkin_pipe); + if (sc->sc_bulkout_pipe != NULL) + usbd_abort_pipe(sc->sc_bulkout_pipe); + + tty_rel_gone(sc->sc_tty); + + return (0); +} + +static void +ucom_shutdown(struct ucom_softc *sc) +{ + struct tty *tp = sc->sc_tty; + + DPRINTF(("ucom_shutdown\n")); + /* + * Hang up if necessary. Wait a bit, so the other side has time to + * notice even if we immediately open the port again. + */ + if (tp->t_termios.c_cflag & HUPCL) { + (void)ucomtty_modem(tp, 0, SER_DTR); +#if 0 + (void)tsleep(sc, TTIPRI, "ucomsd", hz); +#endif + } +} + +static int +ucomtty_open(struct tty *tp) +{ + struct ucom_softc *sc = tty_softc(tp); + usbd_status err; + int error; + + if (sc->sc_dying) + return (ENXIO); + + DPRINTF(("%s: ucomtty_open: tp = %p\n", device_get_nameunit(sc->sc_dev), tp)); + + sc->sc_poll = 0; + sc->sc_lsr = sc->sc_msr = sc->sc_mcr = 0; + + (void)ucomtty_modem(tp, SER_DTR | SER_RTS, 0); + + /* Device specific open */ + if (sc->sc_callback->ucom_open != NULL) { + error = sc->sc_callback->ucom_open(sc->sc_parent, + sc->sc_portno); + if (error) { + ucom_cleanup(sc); + return (error); + } + } + + DPRINTF(("ucomtty_open: open pipes in = %d out = %d\n", + sc->sc_bulkin_no, sc->sc_bulkout_no)); + + /* Open the bulk pipes */ + /* Bulk-in pipe */ + err = usbd_open_pipe(sc->sc_iface, sc->sc_bulkin_no, 0, + &sc->sc_bulkin_pipe); + if (err) { + printf("%s: open bulk in error (addr %d): %s\n", + device_get_nameunit(sc->sc_dev), sc->sc_bulkin_no, + usbd_errstr(err)); + error = EIO; + goto fail; + } + /* Bulk-out pipe */ + err = usbd_open_pipe(sc->sc_iface, sc->sc_bulkout_no, + USBD_EXCLUSIVE_USE, &sc->sc_bulkout_pipe); + if (err) { + printf("%s: open bulk out error (addr %d): %s\n", + device_get_nameunit(sc->sc_dev), sc->sc_bulkout_no, + usbd_errstr(err)); + error = EIO; + goto fail; + } + + /* Allocate a request and an input buffer and start reading. */ + sc->sc_ixfer = usbd_alloc_xfer(sc->sc_udev); + if (sc->sc_ixfer == NULL) { + error = ENOMEM; + goto fail; + } + + sc->sc_ibuf = usbd_alloc_buffer(sc->sc_ixfer, + sc->sc_ibufsizepad); + if (sc->sc_ibuf == NULL) { + error = ENOMEM; + goto fail; + } + + sc->sc_oxfer = usbd_alloc_xfer(sc->sc_udev); + if (sc->sc_oxfer == NULL) { + error = ENOMEM; + goto fail; + } + + sc->sc_obuf = usbd_alloc_buffer(sc->sc_oxfer, + sc->sc_obufsize + + sc->sc_opkthdrlen); + if (sc->sc_obuf == NULL) { + error = ENOMEM; + goto fail; + } + + sc->sc_state |= UCS_RXSTOP; + ucomstartread(sc); + + sc->sc_poll = 1; + + return (0); + +fail: + ucom_cleanup(sc); + return (error); +} + +static void +ucomtty_close(struct tty *tp) +{ + struct ucom_softc *sc = tty_softc(tp); + + DPRINTF(("%s: ucomtty_close \n", device_get_nameunit(sc->sc_dev))); + + ucom_cleanup(sc); + + if (sc->sc_callback->ucom_close != NULL) + sc->sc_callback->ucom_close(sc->sc_parent, sc->sc_portno); +} + +static int +ucomtty_ioctl(struct tty *tp, u_long cmd, caddr_t data, struct thread *p) +{ + struct ucom_softc *sc = tty_softc(tp); + int error; + + if (sc->sc_dying) + return (EIO); + + DPRINTF(("ucomioctl: cmd = 0x%08lx\n", cmd)); + + switch (cmd) { + case TIOCSBRK: + ucombreak(sc, 1); + return (0); + case TIOCCBRK: + ucombreak(sc, 0); + return (0); + } + + error = ENOIOCTL; + if (sc->sc_callback->ucom_ioctl != NULL) + error = sc->sc_callback->ucom_ioctl(sc->sc_parent, + sc->sc_portno, + cmd, data, p); + return (error); +} + +static int +ucomtty_modem(struct tty *tp, int sigon, int sigoff) +{ + struct ucom_softc *sc = tty_softc(tp); + int mcr; + int msr; + int onoff; + + if (sigon == 0 && sigoff == 0) { + mcr = sc->sc_mcr; + if (ISSET(mcr, SER_DTR)) + sigon |= SER_DTR; + if (ISSET(mcr, SER_RTS)) + sigon |= SER_RTS; + + msr = sc->sc_msr; + if (ISSET(msr, SER_CTS)) + sigon |= SER_CTS; + if (ISSET(msr, SER_DCD)) + sigon |= SER_DCD; + if (ISSET(msr, SER_DSR)) + sigon |= SER_DSR; + if (ISSET(msr, SER_RI)) + sigon |= SER_RI; + return (sigon); + } + + mcr = sc->sc_mcr; + if (ISSET(sigon, SER_DTR)) + mcr |= SER_DTR; + if (ISSET(sigoff, SER_DTR)) + mcr &= ~SER_DTR; + if (ISSET(sigon, SER_RTS)) + mcr |= SER_RTS; + if (ISSET(sigoff, SER_RTS)) + mcr &= ~SER_RTS; + sc->sc_mcr = mcr; + + onoff = ISSET(sc->sc_mcr, SER_DTR) ? 1 : 0; + ucom_dtr(sc, onoff); + + onoff = ISSET(sc->sc_mcr, SER_RTS) ? 1 : 0; + ucom_rts(sc, onoff); + + return (0); +} + +static void +ucombreak(struct ucom_softc *sc, int onoff) +{ + DPRINTF(("ucombreak: onoff = %d\n", onoff)); + + if (sc->sc_callback->ucom_set == NULL) + return; + sc->sc_callback->ucom_set(sc->sc_parent, sc->sc_portno, + UCOM_SET_BREAK, onoff); +} + +static void +ucom_dtr(struct ucom_softc *sc, int onoff) +{ + DPRINTF(("ucom_dtr: onoff = %d\n", onoff)); + + if (sc->sc_callback->ucom_set == NULL) + return; + sc->sc_callback->ucom_set(sc->sc_parent, sc->sc_portno, + UCOM_SET_DTR, onoff); +} + +static void +ucom_rts(struct ucom_softc *sc, int onoff) +{ + DPRINTF(("ucom_rts: onoff = %d\n", onoff)); + + if (sc->sc_callback->ucom_set == NULL) + return; + sc->sc_callback->ucom_set(sc->sc_parent, sc->sc_portno, + UCOM_SET_RTS, onoff); +} + +void +ucom_status_change(struct ucom_softc *sc) +{ + struct tty *tp = sc->sc_tty; + u_char old_msr; + int onoff; + + if (sc->sc_callback->ucom_get_status == NULL) { + sc->sc_lsr = 0; + sc->sc_msr = 0; + return; + } + + old_msr = sc->sc_msr; + sc->sc_callback->ucom_get_status(sc->sc_parent, sc->sc_portno, + &sc->sc_lsr, &sc->sc_msr); + if (ISSET((sc->sc_msr ^ old_msr), SER_DCD)) { + if (sc->sc_poll == 0) + return; + onoff = ISSET(sc->sc_msr, SER_DCD) ? 1 : 0; + DPRINTF(("ucom_status_change: DCD changed to %d\n", onoff)); + ttydisc_modem(tp, onoff); + } +} + +static int +ucomtty_param(struct tty *tp, struct termios *t) +{ + struct ucom_softc *sc = tty_softc(tp); + int error; + usbd_status uerr; + + if (sc->sc_dying) + return (EIO); + + DPRINTF(("ucomtty_param: sc = %p\n", sc)); + + /* Check requested parameters. */ + if (t->c_ospeed < 0) { + DPRINTF(("ucomtty_param: negative ospeed\n")); + return (EINVAL); + } + if (t->c_ispeed && t->c_ispeed != t->c_ospeed) { + DPRINTF(("ucomtty_param: mismatch ispeed and ospeed\n")); + return (EINVAL); + } + t->c_ispeed = t->c_ospeed; + + if (sc->sc_callback->ucom_param == NULL) + return (0); + + ucomstopread(sc); + + error = sc->sc_callback->ucom_param(sc->sc_parent, sc->sc_portno, t); + if (error) { + DPRINTF(("ucomtty_param: callback: error = %d\n", error)); + return (error); + } + +#if 0 + ttsetwater(tp); +#endif + + if (t->c_cflag & CRTS_IFLOW) { + sc->sc_state |= UCS_RTS_IFLOW; + } else if (sc->sc_state & UCS_RTS_IFLOW) { + sc->sc_state &= ~UCS_RTS_IFLOW; + (void)ucomtty_modem(tp, SER_RTS, 0); + } + +#if 0 + ttyldoptim(tp); +#endif + + uerr = ucomstartread(sc); + if (uerr != USBD_NORMAL_COMPLETION) + return (EIO); + + return (0); +} + +static void +ucomtty_free(void *sc) +{ + /* + * Our softc gets deallocated earlier on. + * XXX: we should make sure the TTY device name doesn't get + * recycled before we end up here! + */ +} + +static void +ucomtty_outwakeup(struct tty *tp) +{ + struct ucom_softc *sc = tty_softc(tp); + usbd_status err; + size_t cnt; + + DPRINTF(("ucomtty_outwakeup: sc = %p\n", sc)); + + if (sc->sc_dying) + return; + + /* + * If there's no sc_oxfer, then ucomclose has removed it. The buffer + * has just been flushed in the ttyflush() in ttyclose(). ttyflush() + * then calls tt_stop(). ucomstop calls ucomstart, so the right thing + * to do here is just abort if sc_oxfer is NULL, as everything else + * is cleaned up elsewhere. + */ + if (sc->sc_oxfer == NULL) + return; + + /* XXX: hardware flow control. We should use inwakeup here. */ +#if 0 + if (tp->t_state & TS_TBLOCK) { + if (ISSET(sc->sc_mcr, SER_RTS) && + ISSET(sc->sc_state, UCS_RTS_IFLOW)) { + DPRINTF(("ucomtty_outwakeup: clear RTS\n")); + (void)ucomtty_modem(tp, 0, SER_RTS); + } + } else { + if (!ISSET(sc->sc_mcr, SER_RTS) && + tp->t_rawq.c_cc <= tp->t_ilowat && + ISSET(sc->sc_state, UCS_RTS_IFLOW)) { + DPRINTF(("ucomtty_outwakeup: set RTS\n")); + (void)ucomtty_modem(tp, SER_RTS, 0); + } + } +#endif + + if (sc->sc_state & UCS_TXBUSY) + return; + + sc->sc_state |= UCS_TXBUSY; + if (sc->sc_callback->ucom_write != NULL) + cnt = sc->sc_callback->ucom_write(sc->sc_parent, + sc->sc_portno, tp, sc->sc_obuf, sc->sc_obufsize); + else + cnt = ttydisc_getc(tp, sc->sc_obuf, sc->sc_obufsize); + + if (cnt == 0) { + DPRINTF(("ucomtty_outwakeup: cnt == 0\n")); + sc->sc_state &= ~UCS_TXBUSY; + return; + } + sc->sc_obufactive = cnt; + + DPRINTF(("ucomtty_outwakeup: %zu chars\n", cnt)); + usbd_setup_xfer(sc->sc_oxfer, sc->sc_bulkout_pipe, + (usbd_private_handle)sc, sc->sc_obuf, cnt, + USBD_NO_COPY, USBD_NO_TIMEOUT, ucomwritecb); + /* What can we do on error? */ + err = usbd_transfer(sc->sc_oxfer); + if (err != USBD_IN_PROGRESS) { + printf("ucomtty_outwakeup: err=%s\n", usbd_errstr(err)); + sc->sc_state &= ~UCS_TXBUSY; + } +} + +#if 0 +static void +ucomstop(struct tty *tp, int flag) +{ + struct ucom_softc *sc = tty_softc(tp); + int s; + + DPRINTF(("ucomstop: %d\n", flag)); + + if ((flag & FREAD) && (sc->sc_state & UCS_RXSTOP) == 0) { + DPRINTF(("ucomstop: read\n")); + ucomstopread(sc); + ucomstartread(sc); + } + + if (flag & FWRITE) { + DPRINTF(("ucomstop: write\n")); + if (ISSET(tp->t_state, TS_BUSY)) { + /* XXX do what? */ + if (!ISSET(tp->t_state, TS_TTSTOP)) + SET(tp->t_state, TS_FLUSH); + } + } + + ucomtty_outwakeup(tp); + + DPRINTF(("ucomstop: done\n")); +} +#endif + +static void +ucomwritecb(usbd_xfer_handle xfer, usbd_private_handle p, usbd_status status) +{ + struct ucom_softc *sc = (struct ucom_softc *)p; + struct tty *tp = sc->sc_tty; + u_int32_t cc; + + DPRINTF(("ucomwritecb: status = %d\n", status)); + + if (status == USBD_CANCELLED || sc->sc_dying) + return; + + if (status != USBD_NORMAL_COMPLETION) { + printf("%s: ucomwritecb: %s\n", + device_get_nameunit(sc->sc_dev), usbd_errstr(status)); + if (status == USBD_STALLED) + usbd_clear_endpoint_stall_async(sc->sc_bulkin_pipe); + /* XXX we should restart after some delay. */ + return; + } + + usbd_get_xfer_status(xfer, NULL, NULL, &cc, NULL); + DPRINTF(("ucomwritecb: cc = %d\n", cc)); + if (cc <= sc->sc_opkthdrlen) { + printf("%s: sent size too small, cc = %d\n", + device_get_nameunit(sc->sc_dev), cc); + return; + } + + /* convert from USB bytes to tty bytes */ + cc -= sc->sc_opkthdrlen; + if (cc != sc->sc_obufactive) + panic("Partial write of %d of %d bytes, not supported\n", + cc, sc->sc_obufactive); + + sc->sc_state &= ~UCS_TXBUSY; +#if 0 + CLR(tp->t_state, TS_BUSY); + if (ISSET(tp->t_state, TS_FLUSH)) + CLR(tp->t_state, TS_FLUSH); + else + ndflush(&tp->t_outq, cc); +#endif + ucomtty_outwakeup(tp); +} + +static usbd_status +ucomstartread(struct ucom_softc *sc) +{ + usbd_status err; + + DPRINTF(("ucomstartread: start\n")); + + if (sc->sc_bulkin_pipe == NULL || (sc->sc_state & UCS_RXSTOP) == 0) + return (USBD_NORMAL_COMPLETION); + sc->sc_state &= ~UCS_RXSTOP; + + usbd_setup_xfer(sc->sc_ixfer, sc->sc_bulkin_pipe, + (usbd_private_handle)sc, + sc->sc_ibuf, sc->sc_ibufsize, + USBD_SHORT_XFER_OK | USBD_NO_COPY, + USBD_NO_TIMEOUT, ucomreadcb); + + err = usbd_transfer(sc->sc_ixfer); + if (err && err != USBD_IN_PROGRESS) { + sc->sc_state |= UCS_RXSTOP; + DPRINTF(("ucomstartread: err = %s\n", usbd_errstr(err))); + return (err); + } + + return (USBD_NORMAL_COMPLETION); +} + +void +ucomrxchars(struct ucom_softc *sc, u_char *cp, u_int32_t cc) +{ + struct tty *tp = sc->sc_tty; + + /* Give characters to tty layer. */ + if (ttydisc_can_bypass(tp)) { + DPRINTFN(7, ("ucomreadcb: buf = %*D\n", cc, cp, "")); + cc -= ttydisc_rint_bypass(tp, cp, cc); + } else { + while (cc > 0) { + DPRINTFN(7, ("ucomreadcb: char = 0x%02x\n", *cp)); + if (ttydisc_rint(tp, *cp, 0) == -1) + break; + cc--; + cp++; + } + } + if (cc > 0) + device_printf(sc->sc_dev, "lost %d chars\n", cc); + ttydisc_rint_done(tp); +} + +static void +ucomreadcb(usbd_xfer_handle xfer, usbd_private_handle p, usbd_status status) +{ + struct ucom_softc *sc = (struct ucom_softc *)p; + struct tty *tp = sc->sc_tty; + usbd_status err; + u_int32_t cc; + u_char *cp; + + (void)tp; /* Used for debugging */ + DPRINTF(("ucomreadcb: status = %d\n", status)); + + if (status != USBD_NORMAL_COMPLETION) { + if (!(sc->sc_state & UCS_RXSTOP)) + printf("%s: ucomreadcb: %s\n", + device_get_nameunit(sc->sc_dev), usbd_errstr(status)); + sc->sc_state |= UCS_RXSTOP; + if (status == USBD_STALLED) + usbd_clear_endpoint_stall_async(sc->sc_bulkin_pipe); + /* XXX we should restart after some delay. */ + return; + } + sc->sc_state |= UCS_RXSTOP; + + usbd_get_xfer_status(xfer, NULL, (void **)&cp, &cc, NULL); + DPRINTF(("ucomreadcb: got %d chars, tp = %p\n", cc, tp)); + if (cc == 0) + goto resubmit; + + if (sc->sc_callback->ucom_read != NULL) + sc->sc_callback->ucom_read(sc->sc_parent, sc->sc_portno, + &cp, &cc); + + if (cc > sc->sc_ibufsize) { + printf("%s: invalid receive data size, %d chars\n", + device_get_nameunit(sc->sc_dev), cc); + goto resubmit; + } + if (cc > 0) + ucomrxchars(sc, cp, cc); + + resubmit: + err = ucomstartread(sc); + if (err) { + printf("%s: read start failed\n", device_get_nameunit(sc->sc_dev)); + /* XXX what should we dow now? */ + } + +#if 0 + if ((sc->sc_state & UCS_RTS_IFLOW) && !ISSET(sc->sc_mcr, SER_RTS) + && !(tp->t_state & TS_TBLOCK)) + ucomtty_modem(tp, SER_RTS, 0); +#endif +} + +static void +ucom_cleanup(struct ucom_softc *sc) +{ + DPRINTF(("ucom_cleanup: closing pipes\n")); + + ucom_shutdown(sc); + if (sc->sc_bulkin_pipe != NULL) { + sc->sc_state |= UCS_RXSTOP; + usbd_abort_pipe(sc->sc_bulkin_pipe); + usbd_close_pipe(sc->sc_bulkin_pipe); + sc->sc_bulkin_pipe = NULL; + } + if (sc->sc_bulkout_pipe != NULL) { + usbd_abort_pipe(sc->sc_bulkout_pipe); + usbd_close_pipe(sc->sc_bulkout_pipe); + sc->sc_bulkout_pipe = NULL; + } + if (sc->sc_ixfer != NULL) { + usbd_free_xfer(sc->sc_ixfer); + sc->sc_ixfer = NULL; + } + if (sc->sc_oxfer != NULL) { + usbd_free_xfer(sc->sc_oxfer); + sc->sc_oxfer = NULL; + } +} + +static void +ucomstopread(struct ucom_softc *sc) +{ + usbd_status err; + + DPRINTF(("ucomstopread: enter\n")); + + if (!(sc->sc_state & UCS_RXSTOP)) { + sc->sc_state |= UCS_RXSTOP; + if (sc->sc_bulkin_pipe == NULL) { + DPRINTF(("ucomstopread: bulkin pipe NULL\n")); + return; + } + err = usbd_abort_pipe(sc->sc_bulkin_pipe); + if (err) { + DPRINTF(("ucomstopread: err = %s\n", + usbd_errstr(err))); + } + } + + DPRINTF(("ucomstopread: leave\n")); +} diff --git a/sys/legacy/dev/usb/ucomvar.h b/sys/legacy/dev/usb/ucomvar.h new file mode 100644 index 0000000..4433a1a --- /dev/null +++ b/sys/legacy/dev/usb/ucomvar.h @@ -0,0 +1,169 @@ +/* $NetBSD: ucomvar.h,v 1.9 2001/01/23 21:56:17 augustss Exp $ */ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 2001-2002, Shunsuke Akiyama <akiyama@jp.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. + */ + +/*- + * Copyright (c) 1999 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. + */ + +/* Module interface related macros */ +#define UCOM_MODVER 1 + +#define UCOM_MINVER 1 +#define UCOM_PREFVER UCOM_MODVER +#define UCOM_MAXVER 1 + +/* Macros to clear/set/test flags. */ +#define SET(t, f) (t) |= (f) +#define CLR(t, f) (t) &= ~((unsigned)(f)) +#define ISSET(t, f) ((t) & (f)) + +#define UCOM_CALLOUT_MASK 0x80 + +#define UCOMUNIT_MASK 0x3ff7f +#define UCOMDIALOUT_MASK 0x80000 +#define UCOMCALLUNIT_MASK 0x40000 + +#define UCOMUNIT(x) (dev2unit(x) & UCOMUNIT_MASK) +#define UCOMDIALOUT(x) (dev2unit(x) & UCOMDIALOUT_MASK) +#define UCOMCALLUNIT(x) (dev2unit(x) & UCOMCALLUNIT_MASK) + +#define UCOM_UNK_PORTNO -1 /* XXX */ + +struct tty; +struct ucom_softc; + +struct ucom_callback { + void (*ucom_get_status)(void *, int, u_char *, u_char *); + void (*ucom_set)(void *, int, int, int); +#define UCOM_SET_DTR 1 +#define UCOM_SET_RTS 2 +#define UCOM_SET_BREAK 3 + int (*ucom_param)(void *, int, struct termios *); + int (*ucom_ioctl)(void *, int, u_long, caddr_t, struct thread *); + int (*ucom_open)(void *, int); + void (*ucom_close)(void *, int); + void (*ucom_read)(void *, int, u_char **, u_int32_t *); + size_t (*ucom_write)(void *, int, struct tty *, u_char *, u_int32_t); +}; + +/* line status register */ +#define ULSR_RCV_FIFO 0x80 +#define ULSR_TSRE 0x40 /* Transmitter empty: byte sent */ +#define ULSR_TXRDY 0x20 /* Transmitter buffer empty */ +#define ULSR_BI 0x10 /* Break detected */ +#define ULSR_FE 0x08 /* Framing error: bad stop bit */ +#define ULSR_PE 0x04 /* Parity error */ +#define ULSR_OE 0x02 /* Overrun, lost incoming byte */ +#define ULSR_RXRDY 0x01 /* Byte ready in Receive Buffer */ +#define ULSR_RCV_MASK 0x1f /* Mask for incoming data or error */ + +/* ucom state declarations */ +#define UCS_RXSTOP 0x0001 /* Rx stopped */ +#define UCS_TXBUSY 0x0002 /* Tx busy */ +#define UCS_RTS_IFLOW 0x0008 /* use RTS input flow control */ + +struct ucom_softc { + device_t sc_dev; /* base device */ + usbd_device_handle sc_udev; /* USB device */ + usbd_interface_handle sc_iface; /* data interface */ + + int sc_bulkin_no; /* bulk in endpoint address */ + usbd_pipe_handle sc_bulkin_pipe; /* bulk in pipe */ + usbd_xfer_handle sc_ixfer; /* read request */ + u_char *sc_ibuf; /* read buffer */ + u_int sc_ibufsize; /* read buffer size */ + u_int sc_ibufsizepad; /* read buffer size padded */ + + int sc_bulkout_no; /* bulk out endpoint address */ + usbd_pipe_handle sc_bulkout_pipe;/* bulk out pipe */ + usbd_xfer_handle sc_oxfer; /* write request */ + u_char *sc_obuf; /* write buffer */ + u_int sc_obufsize; /* write buffer size */ + u_int sc_opkthdrlen; /* header length of + output packet */ + u_int sc_obufactive; /* Active bytes in buffer */ + + struct ucom_callback *sc_callback; + void *sc_parent; + int sc_portno; + + struct tty *sc_tty; /* our tty */ + + int sc_state; + + int sc_poll; + + u_char sc_lsr; + u_char sc_msr; + u_char sc_mcr; + + u_char sc_dying; /* disconnecting */ + +}; + +extern devclass_t ucom_devclass; + +void ucom_attach_tty(struct ucom_softc *, char*, int); +int ucom_attach(struct ucom_softc *); +int ucom_detach(struct ucom_softc *); +void ucom_status_change(struct ucom_softc *); +void ucomrxchars(struct ucom_softc *sc, u_char *cp, u_int32_t cc); diff --git a/sys/legacy/dev/usb/ucycom.c b/sys/legacy/dev/usb/ucycom.c new file mode 100644 index 0000000..ecf115f --- /dev/null +++ b/sys/legacy/dev/usb/ucycom.c @@ -0,0 +1,543 @@ +/*- + * Copyright (c) 2004 Dag-Erling Coïdan Smørgrav + * 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 + * in this position and unchanged. + * 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. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * 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$ + */ + +/* + * Device driver for Cypress CY7C637xx and CY7C640/1xx series USB to + * RS232 bridges. + * + * Normally, a driver for a USB-to-serial chip would hang off the ucom(4) + * driver, but ucom(4) was written under the assumption that all USB-to- + * serial chips use bulk pipes for I/O, while the Cypress parts use HID + * reports. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/conf.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/sysctl.h> +#include <sys/bus.h> +#include <sys/tty.h> + +#include "usbdevs.h" +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usbhid.h> +#include <dev/usb/hid.h> + +#define UCYCOM_EP_INPUT 0 +#define UCYCOM_EP_OUTPUT 1 + +#define UCYCOM_MAX_IOLEN 32U + +struct ucycom_softc { + device_t sc_dev; + struct tty *sc_tty; + int sc_error; + unsigned long sc_cintr; + unsigned long sc_cin; + unsigned long sc_clost; + unsigned long sc_cout; + + /* usb parameters */ + usbd_device_handle sc_usbdev; + usbd_interface_handle sc_iface; + usbd_pipe_handle sc_pipe; + uint8_t sc_iep; /* input endpoint */ + uint8_t sc_fid; /* feature report id*/ + uint8_t sc_iid; /* input report id */ + uint8_t sc_oid; /* output report id */ + size_t sc_flen; /* feature report length */ + size_t sc_ilen; /* input report length */ + size_t sc_olen; /* output report length */ + uint8_t sc_ibuf[UCYCOM_MAX_IOLEN]; + + /* model and settings */ + uint32_t sc_model; +#define MODEL_CY7C63743 0x63743 +#define MODEL_CY7C64013 0x64013 + uint32_t sc_baud; + uint8_t sc_cfg; +#define UCYCOM_CFG_RESET 0x80 +#define UCYCOM_CFG_PARODD 0x20 +#define UCYCOM_CFG_PAREN 0x10 +#define UCYCOM_CFG_STOPB 0x08 +#define UCYCOM_CFG_DATAB 0x03 + uint8_t sc_ist; /* status flags from last input */ + uint8_t sc_ost; /* status flags for next output */ + + /* flags */ + char sc_dying; +}; + +static device_probe_t ucycom_probe; +static device_attach_t ucycom_attach; +static device_detach_t ucycom_detach; +static t_open_t ucycom_open; +static t_close_t ucycom_close; +static void ucycom_start(struct tty *); +static void ucycom_stop(struct tty *, int); +static int ucycom_param(struct tty *, struct termios *); +static int ucycom_configure(struct ucycom_softc *, uint32_t, uint8_t); +static void ucycom_intr(usbd_xfer_handle, usbd_private_handle, usbd_status); + +static device_method_t ucycom_methods[] = { + DEVMETHOD(device_probe, ucycom_probe), + DEVMETHOD(device_attach, ucycom_attach), + DEVMETHOD(device_detach, ucycom_detach), + { 0, 0 } +}; + +static driver_t ucycom_driver = { + "ucycom", + ucycom_methods, + sizeof(struct ucycom_softc), +}; + +static devclass_t ucycom_devclass; + +DRIVER_MODULE(ucycom, uhub, ucycom_driver, ucycom_devclass, usbd_driver_load, 0); +MODULE_VERSION(ucycom, 1); +MODULE_DEPEND(ucycom, usb, 1, 1, 1); + +/* + * Supported devices + */ + +static struct ucycom_device { + uint16_t vendor; + uint16_t product; + uint32_t model; +} ucycom_devices[] = { + { USB_VENDOR_DELORME, USB_PRODUCT_DELORME_EARTHMATE, MODEL_CY7C64013 }, + { 0, 0, 0 }, +}; + +#define UCYCOM_DEFAULT_RATE 4800 +#define UCYCOM_DEFAULT_CFG 0x03 /* N-8-1 */ + +/***************************************************************************** + * + * Driver interface + * + */ + +static int +ucycom_probe(device_t dev) +{ + struct usb_attach_arg *uaa; + struct ucycom_device *ud; + + uaa = device_get_ivars(dev); + if (uaa->iface != NULL) + return (UMATCH_NONE); + for (ud = ucycom_devices; ud->model != 0; ++ud) + if (ud->vendor == uaa->vendor && ud->product == uaa->product) + return (UMATCH_VENDOR_PRODUCT); + return (UMATCH_NONE); +} + +static int +ucycom_attach(device_t dev) +{ + struct usb_attach_arg *uaa; + struct ucycom_softc *sc; + struct ucycom_device *ud; + usb_endpoint_descriptor_t *ued; + void *urd; + int error, urdlen; + + /* get arguments and softc */ + uaa = device_get_ivars(dev); + sc = device_get_softc(dev); + bzero(sc, sizeof *sc); + sc->sc_dev = dev; + sc->sc_usbdev = uaa->device; + + /* get chip model */ + for (ud = ucycom_devices; ud->model != 0; ++ud) + if (ud->vendor == uaa->vendor && ud->product == uaa->product) + sc->sc_model = ud->model; + if (sc->sc_model == 0) { + device_printf(dev, "unsupported device\n"); + return (ENXIO); + } + device_printf(dev, "Cypress CY7C%X USB to RS232 bridge\n", sc->sc_model); + + /* select configuration */ + error = usbd_set_config_index(sc->sc_usbdev, 0, 1 /* verbose */); + if (error != 0) { + device_printf(dev, "failed to select configuration: %s\n", + usbd_errstr(error)); + return (ENXIO); + } + + /* get first interface handle */ + error = usbd_device2interface_handle(sc->sc_usbdev, 0, &sc->sc_iface); + if (error != 0) { + device_printf(dev, "failed to get interface handle: %s\n", + usbd_errstr(error)); + return (ENXIO); + } + + /* get report descriptor */ + error = usbd_read_report_desc(sc->sc_iface, &urd, &urdlen, M_USBDEV); + if (error != 0) { + device_printf(dev, "failed to get report descriptor: %s\n", + usbd_errstr(error)); + return (ENXIO); + } + + /* get report sizes */ + sc->sc_flen = hid_report_size(urd, urdlen, hid_feature, &sc->sc_fid); + sc->sc_ilen = hid_report_size(urd, urdlen, hid_input, &sc->sc_iid); + sc->sc_olen = hid_report_size(urd, urdlen, hid_output, &sc->sc_oid); + + if (sc->sc_ilen > UCYCOM_MAX_IOLEN || sc->sc_olen > UCYCOM_MAX_IOLEN) { + device_printf(dev, "I/O report size too big (%zu, %zu, %u)\n", + sc->sc_ilen, sc->sc_olen, UCYCOM_MAX_IOLEN); + return (ENXIO); + } + + /* get and verify input endpoint descriptor */ + ued = usbd_interface2endpoint_descriptor(sc->sc_iface, UCYCOM_EP_INPUT); + if (ued == NULL) { + device_printf(dev, "failed to get input endpoint descriptor\n"); + return (ENXIO); + } + if (UE_GET_DIR(ued->bEndpointAddress) != UE_DIR_IN) { + device_printf(dev, "not an input endpoint\n"); + return (ENXIO); + } + if ((ued->bmAttributes & UE_XFERTYPE) != UE_INTERRUPT) { + device_printf(dev, "not an interrupt endpoint\n"); + return (ENXIO); + } + sc->sc_iep = ued->bEndpointAddress; + + /* set up tty */ + sc->sc_tty = ttyalloc(); + sc->sc_tty->t_sc = sc; + sc->sc_tty->t_oproc = ucycom_start; + sc->sc_tty->t_stop = ucycom_stop; + sc->sc_tty->t_param = ucycom_param; + sc->sc_tty->t_open = ucycom_open; + sc->sc_tty->t_close = ucycom_close; + + SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "intr", CTLFLAG_RD, &sc->sc_cintr, 0, + "interrupt count"); + SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "in", CTLFLAG_RD, &sc->sc_cin, 0, + "input bytes read"); + SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "lost", CTLFLAG_RD, &sc->sc_clost, 0, + "input bytes lost"); + SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "out", CTLFLAG_RD, &sc->sc_cout, 0, + "output bytes"); + + /* create character device node */ + ttycreate(sc->sc_tty, 0, "y%r", device_get_unit(sc->sc_dev)); + + return (0); +} + +static int +ucycom_detach(device_t dev) +{ + struct ucycom_softc *sc; + + sc = device_get_softc(dev); + + ttyfree(sc->sc_tty); + + return (0); +} + +/***************************************************************************** + * + * Device interface + * + */ + +static int +ucycom_open(struct tty *tp, struct cdev *cdev) +{ + struct ucycom_softc *sc = tp->t_sc; + int error; + + /* set default configuration */ + ucycom_configure(sc, UCYCOM_DEFAULT_RATE, UCYCOM_DEFAULT_CFG); + + /* open interrupt pipe */ + error = usbd_open_pipe_intr(sc->sc_iface, sc->sc_iep, 0, + &sc->sc_pipe, sc, sc->sc_ibuf, sc->sc_ilen, + ucycom_intr, USBD_DEFAULT_INTERVAL); + if (error != 0) { + device_printf(sc->sc_dev, "failed to open interrupt pipe: %s\n", + usbd_errstr(error)); + return (ENXIO); + } + + if (bootverbose) + device_printf(sc->sc_dev, "%s bypass l_rint()\n", + (sc->sc_tty->t_state & TS_CAN_BYPASS_L_RINT) ? + "can" : "can't"); + + /* done! */ + return (0); +} + +static void +ucycom_close(struct tty *tp) +{ + struct ucycom_softc *sc = tp->t_sc; + + /* stop interrupts and close the interrupt pipe */ + usbd_abort_pipe(sc->sc_pipe); + usbd_close_pipe(sc->sc_pipe); + sc->sc_pipe = 0; + + return; +} + +/***************************************************************************** + * + * TTY interface + * + */ + +static void +ucycom_start(struct tty *tty) +{ + struct ucycom_softc *sc = tty->t_sc; + uint8_t report[sc->sc_olen]; + int error, len; + + while (sc->sc_error == 0 && sc->sc_tty->t_outq.c_cc > 0) { + switch (sc->sc_model) { + case MODEL_CY7C63743: + len = q_to_b(&sc->sc_tty->t_outq, + report + 1, sc->sc_olen - 1); + sc->sc_cout += len; + report[0] = len; + len += 1; + break; + case MODEL_CY7C64013: + len = q_to_b(&sc->sc_tty->t_outq, + report + 2, sc->sc_olen - 2); + sc->sc_cout += len; + report[0] = 0; + report[1] = len; + len += 2; + break; + default: + panic("unsupported model (driver error)"); + } + + while (len < sc->sc_olen) + report[len++] = 0; + error = usbd_set_report(sc->sc_iface, UHID_OUTPUT_REPORT, + sc->sc_oid, report, sc->sc_olen); +#if 0 + if (error != 0) { + device_printf(sc->sc_dev, + "failed to set output report: %s\n", + usbd_errstr(error)); + sc->sc_error = error; + } +#endif + } +} + +static void +ucycom_stop(struct tty *tty, int flags) +{ + struct ucycom_softc *sc; + + sc = tty->t_sc; + if (bootverbose) + device_printf(sc->sc_dev, "%s()\n", __func__); +} + +static int +ucycom_param(struct tty *tty, struct termios *t) +{ + struct ucycom_softc *sc; + uint32_t baud; + uint8_t cfg; + int error; + + sc = tty->t_sc; + + if (t->c_ispeed != t->c_ospeed) + return (EINVAL); + baud = t->c_ispeed; + + if (t->c_cflag & CIGNORE) { + cfg = sc->sc_cfg; + } else { + cfg = 0; + switch (t->c_cflag & CSIZE) { + case CS8: + ++cfg; + case CS7: + ++cfg; + case CS6: + ++cfg; + case CS5: + break; + default: + return (EINVAL); + } + if (t->c_cflag & CSTOPB) + cfg |= UCYCOM_CFG_STOPB; + if (t->c_cflag & PARENB) + cfg |= UCYCOM_CFG_PAREN; + if (t->c_cflag & PARODD) + cfg |= UCYCOM_CFG_PARODD; + } + + error = ucycom_configure(sc, baud, cfg); + return (error); +} + +/***************************************************************************** + * + * Hardware interface + * + */ + +static int +ucycom_configure(struct ucycom_softc *sc, uint32_t baud, uint8_t cfg) +{ + uint8_t report[sc->sc_flen]; + int error; + + switch (baud) { + case 600: + case 1200: + case 2400: + case 4800: + case 9600: + case 19200: + case 38400: + case 57600: +#if 0 + /* + * Stock chips only support standard baud rates in the 600 - 57600 + * range, but higher rates can be achieved using custom firmware. + */ + case 115200: + case 153600: + case 192000: +#endif + break; + default: + return (EINVAL); + } + + if (bootverbose) + device_printf(sc->sc_dev, "%d baud, %c-%d-%d\n", baud, + (cfg & UCYCOM_CFG_PAREN) ? + ((cfg & UCYCOM_CFG_PARODD) ? 'O' : 'E') : 'N', + 5 + (cfg & UCYCOM_CFG_DATAB), + (cfg & UCYCOM_CFG_STOPB) ? 2 : 1); + report[0] = baud & 0xff; + report[1] = (baud >> 8) & 0xff; + report[2] = (baud >> 16) & 0xff; + report[3] = (baud >> 24) & 0xff; + report[4] = cfg; + error = usbd_set_report(sc->sc_iface, UHID_FEATURE_REPORT, + sc->sc_fid, report, sc->sc_flen); + if (error != 0) { + device_printf(sc->sc_dev, "%s\n", usbd_errstr(error)); + return (EIO); + } + sc->sc_baud = baud; + sc->sc_cfg = cfg; + return (0); +} + +static void +ucycom_intr(usbd_xfer_handle xfer, usbd_private_handle scp, usbd_status status) +{ + struct ucycom_softc *sc = scp; + uint8_t *data; + int i, len, lost; + + sc->sc_cintr++; + + switch (sc->sc_model) { + case MODEL_CY7C63743: + sc->sc_ist = sc->sc_ibuf[0] & ~0x07; + len = sc->sc_ibuf[0] & 0x07; + data = sc->sc_ibuf + 1; + break; + case MODEL_CY7C64013: + sc->sc_ist = sc->sc_ibuf[0] & ~0x07; + len = sc->sc_ibuf[1]; + data = sc->sc_ibuf + 2; + break; + default: + panic("unsupported model (driver error)"); + } + + switch (status) { + case USBD_NORMAL_COMPLETION: + break; + default: + /* XXX */ + return; + } + + if (sc->sc_tty->t_state & TS_CAN_BYPASS_L_RINT) { + /* XXX flow control! */ + lost = b_to_q(data, len, &sc->sc_tty->t_rawq); + sc->sc_tty->t_rawcc += len - lost; + ttwakeup(sc->sc_tty); + } else { + for (i = 0, lost = len; i < len; ++i, --lost) + if (ttyld_rint(sc->sc_tty, data[i]) != 0) + break; + } + sc->sc_cin += len - lost; + sc->sc_clost += lost; +} diff --git a/sys/legacy/dev/usb/udbp.c b/sys/legacy/dev/usb/udbp.c new file mode 100644 index 0000000..523184f --- /dev/null +++ b/sys/legacy/dev/usb/udbp.c @@ -0,0 +1,860 @@ +/*- + * Copyright (c) 1996-2000 Whistle Communications, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of author 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 NICK HIBMA 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* Driver for arbitrary double bulk pipe devices. + * The driver assumes that there will be the same driver on the other side. + * + * XXX Some more information on what the framing of the IP packets looks like. + * + * To take full advantage of bulk transmission, packets should be chosen + * between 1k and 5k in size (1k to make sure the sending side starts + * straming, and <5k to avoid overflowing the system with small TDs). + */ + + +/* probe/attach/detach: + * Connect the driver to the hardware and netgraph + * + * udbp_setup_out_transfer(sc); + * Setup an outbound transfer. Only one transmit can be active at the same + * time. + * XXX If it is required that the driver is able to queue multiple requests + * let me know. That is slightly difficult, due to the fact that we + * cannot call usbd_alloc_xfer in int context. + * + * udbp_setup_in_transfer(sc) + * Prepare an in transfer that will be waiting for data to come in. It + * is submitted and sits there until data is available. + * The callback resubmits a new transfer on completion. + * + * The reason we submit a bulk in transfer is that USB does not know about + * interrupts. The bulk transfer continuously polls the device for data. + * While the device has no data available, the device NAKs the TDs. As soon + * as there is data, the transfer happens and the data comes flowing in. + * + * In case you were wondering, interrupt transfers happen exactly that way. + * It therefore doesn't make sense to use the interrupt pipe to signal + * 'data ready' and then schedule a bulk transfer to fetch it. That would + * incur a 2ms delay at least, without reducing bandwidth requirements. + * + */ + + + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/conf.h> +#include <sys/file.h> +#include <sys/selinfo.h> +#include <sys/poll.h> +#include <sys/mbuf.h> +#include <sys/socket.h> +#include <sys/ctype.h> +#include <sys/errno.h> +#include <sys/sysctl.h> +#include <net/if.h> +#include <machine/bus.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usbdivar.h> +#include <dev/usb/usbhid.h> + +#include "usbdevs.h" + + +#include <netgraph/ng_message.h> +#include <netgraph/ng_parse.h> +#include <dev/usb/udbp.h> +#include <netgraph/netgraph.h> + +#ifdef USB_DEBUG +#define DPRINTF(x) if (udbpdebug) printf x +#define DPRINTFN(n,x) if (udbpdebug>(n)) printf x +int udbpdebug = 0; +SYSCTL_NODE(_hw_usb, OID_AUTO, udbp, CTLFLAG_RW, 0, "USB udbp"); +SYSCTL_INT(_hw_usb_udbp, OID_AUTO, debug, CTLFLAG_RW, + &udbpdebug, 0, "udbp debug level"); +#else +#define DPRINTF(x) +#define DPRINTFN(n,x) +#endif + +#define MS_TO_TICKS(ms) ((ms) * hz / 1000) + +#define UDBP_TIMEOUT 2000 /* timeout on outbound transfers, in msecs */ +#define UDBP_BUFFERSIZE 2048 /* maximum number of bytes in one transfer */ + + +struct udbp_softc { + device_t sc_dev; /* base device */ + usbd_interface_handle sc_iface; + + usbd_pipe_handle sc_bulkin_pipe; + int sc_bulkin; + usbd_xfer_handle sc_bulkin_xfer; + void *sc_bulkin_buffer; + int sc_bulkin_bufferlen; + int sc_bulkin_datalen; + + usbd_pipe_handle sc_bulkout_pipe; + int sc_bulkout; + usbd_xfer_handle sc_bulkout_xfer; + void *sc_bulkout_buffer; + int sc_bulkout_bufferlen; + int sc_bulkout_datalen; + + int flags; +# define DISCONNECTED 0x01 +# define OUT_BUSY 0x02 +# define NETGRAPH_INITIALISED 0x04 + node_p node; /* back pointer to node */ + hook_p hook; /* pointer to the hook */ + u_int packets_in; /* packets in from downstream */ + u_int packets_out; /* packets out towards downstream */ + struct ifqueue xmitq_hipri; /* hi-priority transmit queue */ + struct ifqueue xmitq; /* low-priority transmit queue */ + +}; +typedef struct udbp_softc *udbp_p; + + + +static ng_constructor_t ng_udbp_constructor; +static ng_rcvmsg_t ng_udbp_rcvmsg; +static ng_shutdown_t ng_udbp_rmnode; +static ng_newhook_t ng_udbp_newhook; +static ng_connect_t ng_udbp_connect; +static ng_rcvdata_t ng_udbp_rcvdata; +static ng_disconnect_t ng_udbp_disconnect; + +/* Parse type for struct ngudbpstat */ +static const struct ng_parse_struct_field + ng_udbp_stat_type_fields[] = NG_UDBP_STATS_TYPE_INFO; +static const struct ng_parse_type ng_udbp_stat_type = { + &ng_parse_struct_type, + &ng_udbp_stat_type_fields +}; + +/* List of commands and how to convert arguments to/from ASCII */ +static const struct ng_cmdlist ng_udbp_cmdlist[] = { + { + NGM_UDBP_COOKIE, + NGM_UDBP_GET_STATUS, + "getstatus", + NULL, + &ng_udbp_stat_type, + }, + { + NGM_UDBP_COOKIE, + NGM_UDBP_SET_FLAG, + "setflag", + &ng_parse_int32_type, + NULL + }, + { 0 } +}; + +/* Netgraph node type descriptor */ +static struct ng_type ng_udbp_typestruct = { + .version = NG_ABI_VERSION, + .name = NG_UDBP_NODE_TYPE, + .constructor = ng_udbp_constructor, + .rcvmsg = ng_udbp_rcvmsg, + .shutdown = ng_udbp_rmnode, + .newhook = ng_udbp_newhook, + .connect = ng_udbp_connect, + .rcvdata = ng_udbp_rcvdata, + .disconnect = ng_udbp_disconnect, + .cmdlist = ng_udbp_cmdlist, +}; + +static int udbp_setup_in_transfer (udbp_p sc); +static void udbp_in_transfer_cb (usbd_xfer_handle xfer, + usbd_private_handle priv, + usbd_status err); + +static int udbp_setup_out_transfer (udbp_p sc); +static void udbp_out_transfer_cb (usbd_xfer_handle xfer, + usbd_private_handle priv, + usbd_status err); + +static device_probe_t udbp_match; +static device_attach_t udbp_attach; +static device_detach_t udbp_detach; + +static device_method_t udbp_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, udbp_match), + DEVMETHOD(device_attach, udbp_attach), + DEVMETHOD(device_detach, udbp_detach), + + { 0, 0 } +}; + +static driver_t udbp_driver = { + "udbp", + udbp_methods, + sizeof(struct udbp_softc) +}; + +static devclass_t udbp_devclass; + +static int +udbp_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + usb_interface_descriptor_t *id; + if (!uaa->iface) + return (UMATCH_NONE); + id = usbd_get_interface_descriptor(uaa->iface); + + /* XXX Julian, add the id of the device if you have one to test + * things with. run 'usbdevs -v' and note the 3 ID's that appear. + * The Vendor Id and Product Id are in hex and the Revision Id is in + * bcd. But as usual if the revision is 0x101 then you should compare + * the revision id in the device descriptor with 0x101 + * Or go search the file usbdevs.h. Maybe the device is already in + * there. + */ + if ((uaa->vendor == USB_VENDOR_NETCHIP && + uaa->product == USB_PRODUCT_NETCHIP_TURBOCONNECT)) + return (UMATCH_VENDOR_PRODUCT); + + if ((uaa->vendor == USB_VENDOR_PROLIFIC && + (uaa->product == USB_PRODUCT_PROLIFIC_PL2301 || + uaa->product == USB_PRODUCT_PROLIFIC_PL2302))) + return (UMATCH_VENDOR_PRODUCT); + + if ((uaa->vendor == USB_VENDOR_ANCHOR && + uaa->product == USB_PRODUCT_ANCHOR_EZLINK)) + return (UMATCH_VENDOR_PRODUCT); + + if ((uaa->vendor == USB_VENDOR_GENESYS && + uaa->product == USB_PRODUCT_GENESYS_GL620USB)) + return (UMATCH_VENDOR_PRODUCT); + + return (UMATCH_NONE); +} + +static int +udbp_attach(device_t self) +{ + struct udbp_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + usbd_interface_handle iface = uaa->iface; + usb_interface_descriptor_t *id; + usb_endpoint_descriptor_t *ed, *ed_bulkin = NULL, *ed_bulkout = NULL; + usbd_status err; + int i; + static int ngudbp_done_init=0; + + sc->flags |= DISCONNECTED; + /* fetch the interface handle for the first interface */ + (void) usbd_device2interface_handle(uaa->device, 0, &iface); + id = usbd_get_interface_descriptor(iface); + sc->sc_dev = self; + + /* Find the two first bulk endpoints */ + for (i = 0 ; i < id->bNumEndpoints; i++) { + ed = usbd_interface2endpoint_descriptor(iface, i); + if (!ed) { + device_printf(self, "could not read endpoint descriptor\n"); + return ENXIO; + } + + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN + && (ed->bmAttributes & UE_XFERTYPE) == UE_BULK) { + ed_bulkin = ed; + } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT + && (ed->bmAttributes & UE_XFERTYPE) == UE_BULK) { + ed_bulkout = ed; + } + + if (ed_bulkin && ed_bulkout) /* found all we need */ + break; + } + + /* Verify that we goething sensible */ + if (ed_bulkin == NULL || ed_bulkout == NULL) { + device_printf(self, "bulk-in and/or bulk-out endpoint not found\n"); + return ENXIO; + } + + if (ed_bulkin->wMaxPacketSize[0] != ed_bulkout->wMaxPacketSize[0] || + ed_bulkin->wMaxPacketSize[1] != ed_bulkout->wMaxPacketSize[1]) { + device_printf(self, + "bulk-in and bulk-out have different packet sizes %d %d %d %d\n", + ed_bulkin->wMaxPacketSize[0], + ed_bulkout->wMaxPacketSize[0], + ed_bulkin->wMaxPacketSize[1], + ed_bulkout->wMaxPacketSize[1]); + return ENXIO; + } + + sc->sc_bulkin = ed_bulkin->bEndpointAddress; + sc->sc_bulkout = ed_bulkout->bEndpointAddress; + + DPRINTF(("%s: Bulk-in: 0x%02x, bulk-out 0x%02x, packet size = %d\n", + device_get_nameunit(sc->sc_dev), sc->sc_bulkin, sc->sc_bulkout, + ed_bulkin->wMaxPacketSize[0])); + + /* Allocate the in transfer struct */ + sc->sc_bulkin_xfer = usbd_alloc_xfer(uaa->device); + if (!sc->sc_bulkin_xfer) { + goto bad; + } + sc->sc_bulkout_xfer = usbd_alloc_xfer(uaa->device); + if (!sc->sc_bulkout_xfer) { + goto bad; + } + sc->sc_bulkin_buffer = malloc(UDBP_BUFFERSIZE, M_USBDEV, M_WAITOK); + if (!sc->sc_bulkin_buffer) { + goto bad; + } + sc->sc_bulkout_buffer = malloc(UDBP_BUFFERSIZE, M_USBDEV, M_WAITOK); + if (!sc->sc_bulkout_xfer || !sc->sc_bulkout_buffer) { + goto bad; + } + sc->sc_bulkin_bufferlen = UDBP_BUFFERSIZE; + sc->sc_bulkout_bufferlen = UDBP_BUFFERSIZE; + + /* We have decided on which endpoints to use, now open the pipes */ + err = usbd_open_pipe(iface, sc->sc_bulkin, + USBD_EXCLUSIVE_USE, &sc->sc_bulkin_pipe); + if (err) { + device_printf(self, "cannot open bulk-in pipe (addr %d)\n", + sc->sc_bulkin); + goto bad; + } + err = usbd_open_pipe(iface, sc->sc_bulkout, + USBD_EXCLUSIVE_USE, &sc->sc_bulkout_pipe); + if (err) { + device_printf(self, "cannot open bulk-out pipe (addr %d)\n", + sc->sc_bulkout); + goto bad; + } + + if (!ngudbp_done_init){ + ngudbp_done_init=1; + if (ng_newtype(&ng_udbp_typestruct)) { + printf("ngudbp install failed\n"); + goto bad; + } + } + + if ((err = ng_make_node_common(&ng_udbp_typestruct, &sc->node)) == 0) { + char nodename[128]; + sprintf(nodename, "%s", device_get_nameunit(sc->sc_dev)); + if ((err = ng_name_node(sc->node, nodename))) { + NG_NODE_UNREF(sc->node); + sc->node = NULL; + goto bad; + } else { + NG_NODE_SET_PRIVATE(sc->node, sc); + sc->xmitq.ifq_maxlen = IFQ_MAXLEN; + sc->xmitq_hipri.ifq_maxlen = IFQ_MAXLEN; + mtx_init(&sc->xmitq.ifq_mtx, "usb_xmitq", NULL, + MTX_DEF); + mtx_init(&sc->xmitq_hipri.ifq_mtx, + "usb_xmitq_hipri", NULL, MTX_DEF); + } + } + sc->flags = NETGRAPH_INITIALISED; + /* sc->flags &= ~DISCONNECTED; */ /* XXX */ + + + /* the device is now operational */ + + + /* schedule the first incoming xfer */ + err = udbp_setup_in_transfer(sc); + if (err) { + goto bad; + } + return 0; +bad: +#if 0 /* probably done in udbp_detach() */ + if (sc->sc_bulkout_buffer) { + free(sc->sc_bulkout_buffer, M_USBDEV); + } + if (sc->sc_bulkin_buffer) { + free(sc->sc_bulkin_buffer, M_USBDEV); + } + if (sc->sc_bulkout_xfer) { + usbd_free_xfer(sc->sc_bulkout_xfer); + } + if (sc->sc_bulkin_xfer) { + usbd_free_xfer(sc->sc_bulkin_xfer); + } +#endif + udbp_detach(self); + return ENXIO; +} + + +static int +udbp_detach(device_t self) +{ + struct udbp_softc *sc = device_get_softc(self); + + sc->flags |= DISCONNECTED; + + DPRINTF(("%s: disconnected\n", device_get_nameunit(self))); + + if (sc->sc_bulkin_pipe) { + usbd_abort_pipe(sc->sc_bulkin_pipe); + usbd_close_pipe(sc->sc_bulkin_pipe); + } + if (sc->sc_bulkout_pipe) { + usbd_abort_pipe(sc->sc_bulkout_pipe); + usbd_close_pipe(sc->sc_bulkout_pipe); + } + + if (sc->flags & NETGRAPH_INITIALISED) { + ng_rmnode_self(sc->node); + NG_NODE_SET_PRIVATE(sc->node, NULL); + NG_NODE_UNREF(sc->node); + sc->node = NULL; /* Paranoid */ + } + + if (sc->sc_bulkin_xfer) + usbd_free_xfer(sc->sc_bulkin_xfer); + if (sc->sc_bulkout_xfer) + usbd_free_xfer(sc->sc_bulkout_xfer); + + if (sc->sc_bulkin_buffer) + free(sc->sc_bulkin_buffer, M_USBDEV); + if (sc->sc_bulkout_buffer) + free(sc->sc_bulkout_buffer, M_USBDEV); + return 0; +} + + +static int +udbp_setup_in_transfer(udbp_p sc) +{ + void *priv = sc; /* XXX this should probably be some pointer to + * struct describing the transfer (mbuf?) + * See also below. + */ + usbd_status err; + + /* XXX + * How should we arrange for 2 extra bytes at the start of the + * packet? + */ + + /* Initialise a USB transfer and then schedule it */ + + (void) usbd_setup_xfer( sc->sc_bulkin_xfer, + sc->sc_bulkin_pipe, + priv, + sc->sc_bulkin_buffer, + sc->sc_bulkin_bufferlen, + USBD_SHORT_XFER_OK, + USBD_NO_TIMEOUT, + udbp_in_transfer_cb); + + err = usbd_transfer(sc->sc_bulkin_xfer); + if (err && err != USBD_IN_PROGRESS) { + DPRINTF(("%s: failed to setup in-transfer, %s\n", + device_get_nameunit(sc->sc_dev), usbd_errstr(err))); + return(err); + } + + return (USBD_NORMAL_COMPLETION); +} + +static void +udbp_in_transfer_cb(usbd_xfer_handle xfer, usbd_private_handle priv, + usbd_status err) +{ + udbp_p sc = priv; /* XXX see priv above */ + int s; + int len; + struct mbuf *m; + + if (err) { + if (err != USBD_CANCELLED) { + DPRINTF(("%s: bulk-out transfer failed: %s\n", + device_get_nameunit(sc->sc_dev), usbd_errstr(err))); + } else { + /* USBD_CANCELLED happens at unload of the driver */ + return; + } + + /* Transfer has failed, packet is not received */ + } else { + + len = xfer->actlen; + + s = splimp(); /* block network stuff too */ + if (sc->hook) { + /* get packet from device and send on */ + m = m_devget(sc->sc_bulkin_buffer, len, 0, NULL, NULL); + NG_SEND_DATA_ONLY(err, sc->hook, m); + } + splx(s); + + } + /* schedule the next in transfer */ + udbp_setup_in_transfer(sc); +} + + +static int +udbp_setup_out_transfer(udbp_p sc) +{ + void *priv = sc; /* XXX this should probably be some pointer to + * struct describing the transfer (mbuf?) + * See also below. + */ + int pktlen; + usbd_status err; + int s, s1; + struct mbuf *m; + + + s = splusb(); + if (sc->flags & OUT_BUSY) + panic("out transfer already in use, we should add queuing"); + sc->flags |= OUT_BUSY; + splx(s); + s1 = splimp(); /* Queueing happens at splnet */ + IF_DEQUEUE(&sc->xmitq_hipri, m); + if (m == NULL) { + IF_DEQUEUE(&sc->xmitq, m); + } + splx(s1); + + if (!m) { + sc->flags &= ~OUT_BUSY; + return (USBD_NORMAL_COMPLETION); + } + + pktlen = m->m_pkthdr.len; + if (pktlen > sc->sc_bulkout_bufferlen) { + device_printf(sc->sc_dev, "Packet too large, %d > %d\n", + pktlen, sc->sc_bulkout_bufferlen); + return (USBD_IOERROR); + } + + m_copydata(m, 0, pktlen, sc->sc_bulkout_buffer); + m_freem(m); + + /* Initialise a USB transfer and then schedule it */ + + (void) usbd_setup_xfer( sc->sc_bulkout_xfer, + sc->sc_bulkout_pipe, + priv, + sc->sc_bulkout_buffer, + pktlen, + USBD_SHORT_XFER_OK, + UDBP_TIMEOUT, + udbp_out_transfer_cb); + + err = usbd_transfer(sc->sc_bulkout_xfer); + if (err && err != USBD_IN_PROGRESS) { + DPRINTF(("%s: failed to setup out-transfer, %s\n", + device_get_nameunit(sc->sc_dev), usbd_errstr(err))); + return(err); + } + + return (USBD_NORMAL_COMPLETION); +} + +static void +udbp_out_transfer_cb(usbd_xfer_handle xfer, usbd_private_handle priv, + usbd_status err) +{ + udbp_p sc = priv; /* XXX see priv above */ + int s; + + if (err) { + DPRINTF(("%s: bulk-out transfer failed: %s\n", + device_get_nameunit(sc->sc_dev), usbd_errstr(err))); + /* Transfer has failed, packet is not transmitted */ + /* XXX Invalidate packet */ + return; + } + + /* packet has been transmitted */ + + s = splusb(); /* mark the buffer available */ + sc->flags &= ~OUT_BUSY; + udbp_setup_out_transfer(sc); + splx(s); +} + +DRIVER_MODULE(udbp, uhub, udbp_driver, udbp_devclass, usbd_driver_load, 0); +MODULE_DEPEND(udbp, netgraph, NG_ABI_VERSION, NG_ABI_VERSION, NG_ABI_VERSION); +MODULE_DEPEND(udbp, usb, 1, 1, 1); + + +/*********************************************************************** + * Start of Netgraph methods + **********************************************************************/ + +/* + * If this is a device node so this work is done in the attach() + * routine and the constructor will return EINVAL as you should not be able + * to create nodes that depend on hardware (unless you can add the hardware :) + */ +static int +ng_udbp_constructor(node_p node) +{ + return (EINVAL); +} + +/* + * Give our ok for a hook to be added... + * If we are not running this might kick a device into life. + * Possibly decode information out of the hook name. + * Add the hook's private info to the hook structure. + * (if we had some). In this example, we assume that there is a + * an array of structs, called 'channel' in the private info, + * one for each active channel. The private + * pointer of each hook points to the appropriate UDBP_hookinfo struct + * so that the source of an input packet is easily identified. + */ +static int +ng_udbp_newhook(node_p node, hook_p hook, const char *name) +{ + const udbp_p sc = NG_NODE_PRIVATE(node); + +#if 0 + /* Possibly start up the device if it's not already going */ + if ((sc->flags & SCF_RUNNING) == 0) { + ng_udbp_start_hardware(sc); + } +#endif + + if (strcmp(name, NG_UDBP_HOOK_NAME) == 0) { + sc->hook = hook; + NG_HOOK_SET_PRIVATE(hook, NULL); + } else { + return (EINVAL); /* not a hook we know about */ + } + return(0); +} + +/* + * Get a netgraph control message. + * Check it is one we understand. If needed, send a response. + * We could save the address for an async action later, but don't here. + * Always free the message. + * The response should be in a malloc'd region that the caller can 'free'. + * A response is not required. + * Theoretically you could respond defferently to old message types if + * the cookie in the header didn't match what we consider to be current + * (so that old userland programs could continue to work). + */ +static int +ng_udbp_rcvmsg(node_p node, item_p item, hook_p lasthook) +{ + const udbp_p sc = NG_NODE_PRIVATE(node); + struct ng_mesg *resp = NULL; + int error = 0; + struct ng_mesg *msg; + + NGI_GET_MSG(item, msg); + /* Deal with message according to cookie and command */ + switch (msg->header.typecookie) { + case NGM_UDBP_COOKIE: + switch (msg->header.cmd) { + case NGM_UDBP_GET_STATUS: + { + struct ngudbpstat *stats; + + NG_MKRESPONSE(resp, msg, sizeof(*stats), M_NOWAIT); + if (!resp) { + error = ENOMEM; + break; + } + stats = (struct ngudbpstat *) resp->data; + stats->packets_in = sc->packets_in; + stats->packets_out = sc->packets_out; + break; + } + case NGM_UDBP_SET_FLAG: + if (msg->header.arglen != sizeof(u_int32_t)) { + error = EINVAL; + break; + } + sc->flags = *((u_int32_t *) msg->data); + break; + default: + error = EINVAL; /* unknown command */ + break; + } + break; + default: + error = EINVAL; /* unknown cookie type */ + break; + } + + /* Take care of synchronous response, if any */ + NG_RESPOND_MSG(error, node, item, resp); + NG_FREE_MSG(msg); + return(error); +} + +/* + * Accept data from the hook and queue it for output. + */ +static int +ng_udbp_rcvdata(hook_p hook, item_p item) +{ + const udbp_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + int error; + struct ifqueue *xmitq_p; + int s; + struct mbuf *m; + struct ng_tag_prio *ptag; + + NGI_GET_M(item, m); + NG_FREE_ITEM(item); + + /* + * Now queue the data for when it can be sent + */ + if ((ptag = (struct ng_tag_prio *)m_tag_locate(m, NGM_GENERIC_COOKIE, + NG_TAG_PRIO, NULL)) != NULL && (ptag->priority > NG_PRIO_CUTOFF) ) + xmitq_p = (&sc->xmitq_hipri); + else + xmitq_p = (&sc->xmitq); + + s = splusb(); + IF_LOCK(xmitq_p); + if (_IF_QFULL(xmitq_p)) { + _IF_DROP(xmitq_p); + IF_UNLOCK(xmitq_p); + splx(s); + error = ENOBUFS; + goto bad; + } + _IF_ENQUEUE(xmitq_p, m); + IF_UNLOCK(xmitq_p); + if (!(sc->flags & OUT_BUSY)) + udbp_setup_out_transfer(sc); + splx(s); + return (0); + +bad: /* + * It was an error case. + * check if we need to free the mbuf, and then return the error + */ + NG_FREE_M(m); + return (error); +} + +/* + * Do local shutdown processing.. + * We are a persistant device, we refuse to go away, and + * only remove our links and reset ourself. + */ +static int +ng_udbp_rmnode(node_p node) +{ + const udbp_p sc = NG_NODE_PRIVATE(node); + int err; + + if (sc->flags & DISCONNECTED) { + /* + * WE are really going away.. hardware must have gone. + * Assume that the hardware drive part will clear up the + * sc, in fact it may already have done so.. + * In which case we may have just segfaulted..XXX + */ + return (0); + } + + /* stolen from attach routine */ + /* Drain the queues */ + IF_DRAIN(&sc->xmitq_hipri); + IF_DRAIN(&sc->xmitq); + + sc->packets_in = 0; /* reset stats */ + sc->packets_out = 0; + NG_NODE_UNREF(node); /* forget it ever existed */ + + if ((err = ng_make_node_common(&ng_udbp_typestruct, &sc->node)) == 0) { + char nodename[128]; + sprintf(nodename, "%s", device_get_nameunit(sc->sc_dev)); + if ((err = ng_name_node(sc->node, nodename))) { + NG_NODE_UNREF(sc->node); /* out damned spot! */ + sc->flags &= ~NETGRAPH_INITIALISED; + sc->node = NULL; + } else { + NG_NODE_SET_PRIVATE(sc->node, sc); + } + } + return (err); +} + +/* + * This is called once we've already connected a new hook to the other node. + * It gives us a chance to balk at the last minute. + */ +static int +ng_udbp_connect(hook_p hook) +{ + /* probably not at splnet, force outward queueing */ + NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook)); + /* be really amiable and just say "YUP that's OK by me! " */ + return (0); +} + +/* + * Dook disconnection + * + * For this type, removal of the last link destroys the node + */ +static int +ng_udbp_disconnect(hook_p hook) +{ + const udbp_p sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook)); + sc->hook = NULL; + + if ((NG_NODE_NUMHOOKS(NG_HOOK_NODE(hook)) == 0) + && (NG_NODE_IS_VALID(NG_HOOK_NODE(hook)))) + ng_rmnode_self(NG_HOOK_NODE(hook)); + return (0); +} + diff --git a/sys/legacy/dev/usb/udbp.h b/sys/legacy/dev/usb/udbp.h new file mode 100644 index 0000000..97ef945 --- /dev/null +++ b/sys/legacy/dev/usb/udbp.h @@ -0,0 +1,80 @@ +/*- + * Copyright (c) 1996-2000 Whistle Communications, Inc. + * All rights reserved. + * + * Subject to the following obligations and disclaimer of warranty, use and + * redistribution of this software, in source or object code forms, with or + * without modifications are expressly permitted by Whistle Communications; + * provided, however, that: + * 1. Any and all reproductions of the source or object code must include the + * copyright notice above and the following disclaimer of warranties; and + * 2. No rights are granted, in any manner or form, to use Whistle + * Communications, Inc. trademarks, including the mark "WHISTLE + * COMMUNICATIONS" on advertising, endorsements, or otherwise except as + * such appears in the above copyright notice or in the software. + * + * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND + * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE, + * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY + * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS + * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE. + * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES + * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING + * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER 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 WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * This file was derived from src/sys/netgraph/ng_sample.h, revision 1.1 + * written by Julian Elischer, Whistle Communications. + * + * $FreeBSD$ + */ + +#ifndef _NETGRAPH_UDBP_H_ +#define _NETGRAPH_UDBP_H_ + +/* Node type name. This should be unique among all netgraph node types */ +#define NG_UDBP_NODE_TYPE "udbp" + +/* Node type cookie. Should also be unique. This value MUST change whenever + an incompatible change is made to this header file, to insure consistency. + The de facto method for generating cookies is to take the output of the + date command: date -u +'%s' */ +#define NGM_UDBP_COOKIE 944609300 + + +#define NG_UDBP_HOOK_NAME "data" + +/* Netgraph commands understood by this node type */ +enum { + NGM_UDBP_SET_FLAG = 1, + NGM_UDBP_GET_STATUS, +}; + +/* This structure is returned by the NGM_UDBP_GET_STATUS command */ +struct ngudbpstat { + u_int packets_in; /* packets in from downstream */ + u_int packets_out; /* packets out towards downstream */ +}; + +/* + * This is used to define the 'parse type' for a struct ngudbpstat, which + * is bascially a description of how to convert a binary struct ngudbpstat + * to an ASCII string and back. See ng_parse.h for more info. + * + * This needs to be kept in sync with the above structure definition + */ +#define NG_UDBP_STATS_TYPE_INFO { \ + { "packets_in", &ng_parse_int32_type }, \ + { "packets_out", &ng_parse_int32_type }, \ + { NULL }, \ +} + +#endif /* _NETGRAPH_UDBP_H_ */ diff --git a/sys/legacy/dev/usb/ufm.c b/sys/legacy/dev/usb/ufm.c new file mode 100644 index 0000000..2635827 --- /dev/null +++ b/sys/legacy/dev/usb/ufm.c @@ -0,0 +1,376 @@ +/*- + * Copyright (c) 2001-2007 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. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/ioccom.h> +#include <sys/fcntl.h> +#include <sys/filio.h> +#include <sys/conf.h> +#include <sys/uio.h> +#include <sys/tty.h> +#include <sys/file.h> +#include <sys/selinfo.h> +#include <sys/poll.h> +#include <sys/sysctl.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> + +#include "usbdevs.h" +#include <dev/usb/dsbr100io.h> + +#ifdef USB_DEBUG +#define DPRINTF(x) if (ufmdebug) printf x +#define DPRINTFN(n,x) if (ufmdebug>(n)) printf x +int ufmdebug = 0; +SYSCTL_NODE(_hw_usb, OID_AUTO, ufm, CTLFLAG_RW, 0, "USB ufm"); +SYSCTL_INT(_hw_usb_ufm, OID_AUTO, debug, CTLFLAG_RW, + &ufmdebug, 0, "ufm debug level"); +#else +#define DPRINTF(x) +#define DPRINTFN(n,x) +#endif + +d_open_t ufmopen; +d_close_t ufmclose; +d_ioctl_t ufmioctl; + +static struct cdevsw ufm_cdevsw = { + .d_version = D_VERSION, + .d_flags = D_NEEDGIANT, + .d_open = ufmopen, + .d_close = ufmclose, + .d_ioctl = ufmioctl, + .d_name = "ufm", +}; + +#define FM_CMD0 0x00 +#define FM_CMD_SET_FREQ 0x01 +#define FM_CMD2 0x02 + +struct ufm_softc { + device_t sc_dev; + usbd_device_handle sc_udev; + usbd_interface_handle sc_iface; + + int sc_opened; + int sc_epaddr; + int sc_freq; + + int sc_refcnt; +}; + +#define UFMUNIT(n) (dev2unit(n)) + +static device_probe_t ufm_match; +static device_attach_t ufm_attach; +static device_detach_t ufm_detach; + +static device_method_t ufm_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ufm_match), + DEVMETHOD(device_attach, ufm_attach), + DEVMETHOD(device_detach, ufm_detach), + + { 0, 0 } +}; + +static driver_t ufm_driver = { + "ufm", + ufm_methods, + sizeof(struct ufm_softc) +}; + +static devclass_t ufm_devclass; + +static int +ufm_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + usb_device_descriptor_t *dd; + + DPRINTFN(10,("ufm_match\n")); + if (!uaa->iface) + return UMATCH_NONE; + + dd = usbd_get_device_descriptor(uaa->device); + + if (dd && + ((UGETW(dd->idVendor) == USB_VENDOR_CYPRESS && + UGETW(dd->idProduct) == USB_PRODUCT_CYPRESS_FMRADIO))) + return UMATCH_VENDOR_PRODUCT; + else + return UMATCH_NONE; +} + +static int +ufm_attach(device_t self) +{ + struct ufm_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + usb_endpoint_descriptor_t *edesc; + usbd_device_handle udev; + usbd_interface_handle iface; + u_int8_t epcount; + usbd_status r; + char * ermsg = "<none>"; + + DPRINTFN(10,("ufm_attach: sc=%p\n", sc)); + sc->sc_dev = self; + sc->sc_udev = udev = uaa->device; + + if ((!uaa->device) || (!uaa->iface)) { + ermsg = "device or iface"; + goto nobulk; + } + sc->sc_iface = iface = uaa->iface; + sc->sc_opened = 0; + sc->sc_refcnt = 0; + + r = usbd_endpoint_count(iface, &epcount); + if (r != USBD_NORMAL_COMPLETION) { + ermsg = "endpoints"; + goto nobulk; + } + + edesc = usbd_interface2endpoint_descriptor(iface, 0); + if (!edesc) { + ermsg = "interface endpoint"; + goto nobulk; + } + sc->sc_epaddr = edesc->bEndpointAddress; + + /* XXX no error trapping, no storing of struct cdev **/ + (void) make_dev(&ufm_cdevsw, device_get_unit(self), + UID_ROOT, GID_OPERATOR, + 0644, "ufm%d", device_get_unit(self)); + DPRINTFN(10, ("ufm_attach: %p\n", sc->sc_udev)); + return 0; + + nobulk: + device_printf(sc->sc_dev, "could not find %s\n", ermsg); + return ENXIO; +} + + +int +ufmopen(struct cdev *dev, int flag, int mode, struct thread *td) +{ + struct ufm_softc *sc; + + int unit = UFMUNIT(dev); + sc = devclass_get_softc(ufm_devclass, unit); + if (sc == NULL) + return (ENXIO); + + DPRINTFN(5, ("ufmopen: flag=%d, mode=%d, unit=%d\n", + flag, mode, unit)); + + if (sc->sc_opened) + return (EBUSY); + + if ((flag & (FWRITE|FREAD)) != (FWRITE|FREAD)) + return (EACCES); + + sc->sc_opened = 1; + return (0); +} + +int +ufmclose(struct cdev *dev, int flag, int mode, struct thread *td) +{ + struct ufm_softc *sc; + + int unit = UFMUNIT(dev); + sc = devclass_get_softc(ufm_devclass, unit); + + DPRINTFN(5, ("ufmclose: flag=%d, mode=%d, unit=%d\n", flag, mode, unit)); + sc->sc_opened = 0; + sc->sc_refcnt = 0; + return 0; +} + +static int +ufm_do_req(struct ufm_softc *sc, u_int8_t reqtype, u_int8_t request, + u_int16_t value, u_int16_t index, u_int8_t len, void *retbuf) +{ + int s; + usb_device_request_t req; + usbd_status err; + + s = splusb(); + req.bmRequestType = reqtype; + req.bRequest = request; + USETW(req.wValue, value); + USETW(req.wIndex, index); + USETW(req.wLength, len); + err = usbd_do_request_flags(sc->sc_udev, &req, retbuf, 0, NULL, + USBD_DEFAULT_TIMEOUT); + splx(s); + if (err) + return (EIO); + return (0); +} + +static int +ufm_set_freq(struct ufm_softc *sc, caddr_t addr) +{ + int freq = *(int *)addr; + u_int8_t ret; + + /* + * Freq now is in Hz. We need to convert it to the frequency + * that the radio wants. This frequency is 10.7MHz above + * the actual frequency. We then need to convert to + * units of 12.5kHz. We add one to the IFM to make rounding + * easier. + */ + sc->sc_freq = freq; + freq = (freq + 10700001) / 12500; + /* This appears to set the frequency */ + if (ufm_do_req(sc, UT_READ_VENDOR_DEVICE, FM_CMD_SET_FREQ, freq >> 8, + freq, 1, &ret) != 0) + return (EIO); + /* Not sure what this does */ + if (ufm_do_req(sc, UT_READ_VENDOR_DEVICE, FM_CMD0, 0x96, 0xb7, 1, + &ret) != 0) + return (EIO); + return (0); +} + +static int +ufm_get_freq(struct ufm_softc *sc, caddr_t addr) +{ + int *valp = (int *)addr; + *valp = sc->sc_freq; + return (0); +} + +static int +ufm_start(struct ufm_softc *sc, caddr_t addr) +{ + u_int8_t ret; + + if (ufm_do_req(sc, UT_READ_VENDOR_DEVICE, FM_CMD0, 0x00, 0xc7, + 1, &ret)) + return (EIO); + if (ufm_do_req(sc, UT_READ_VENDOR_DEVICE, FM_CMD2, 0x01, 0x00, + 1, &ret)) + return (EIO); + if (ret & 0x1) + return (EIO); + return (0); +} + +static int +ufm_stop(struct ufm_softc *sc, caddr_t addr) +{ + u_int8_t ret; + + if (ufm_do_req(sc, UT_READ_VENDOR_DEVICE, FM_CMD0, 0x16, 0x1C, + 1, &ret)) + return (EIO); + if (ufm_do_req(sc, UT_READ_VENDOR_DEVICE, FM_CMD2, 0x00, 0x00, + 1, &ret)) + return (EIO); + return (0); +} + +static int +ufm_get_stat(struct ufm_softc *sc, caddr_t addr) +{ + u_int8_t ret; + + /* + * Note, there's a 240ms settle time before the status + * will be valid, so tsleep that amount. hz/4 is a good + * approximation of that. Since this is a short sleep + * we don't try to catch any signals to keep things + * simple. + */ + tsleep(sc, 0, "ufmwait", hz/4); + if (ufm_do_req(sc, UT_READ_VENDOR_DEVICE, FM_CMD0, 0x00, 0x24, + 1, &ret)) + return (EIO); + *(int *)addr = ret; + + return (0); +} + +int +ufmioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, struct thread *td) +{ + struct ufm_softc *sc; + + int unit = UFMUNIT(dev); + int error = 0; + + sc = devclass_get_softc(ufm_devclass, unit); + + switch (cmd) { + case FM_SET_FREQ: + error = ufm_set_freq(sc, addr); + break; + case FM_GET_FREQ: + error = ufm_get_freq(sc, addr); + break; + case FM_START: + error = ufm_start(sc, addr); + break; + case FM_STOP: + error = ufm_stop(sc, addr); + break; + case FM_GET_STAT: + error = ufm_get_stat(sc, addr); + break; + default: + return ENOTTY; + break; + } + return error; +} + +static int +ufm_detach(device_t self) +{ + return 0; +} + +MODULE_DEPEND(ufm, usb, 1, 1, 1); +DRIVER_MODULE(ufm, uhub, ufm_driver, ufm_devclass, usbd_driver_load, 0); diff --git a/sys/legacy/dev/usb/ufoma.c b/sys/legacy/dev/usb/ufoma.c new file mode 100644 index 0000000..e5704a7 --- /dev/null +++ b/sys/legacy/dev/usb/ufoma.c @@ -0,0 +1,1192 @@ +/* $NetBSD: umodem.c,v 1.45 2002/09/23 05:51:23 simonb Exp $ */ +#define UFOMA_HANDSFREE +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2005, Takanori Watanabe + * Copyright (c) 2003, M. Warner Losh <imp@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. + */ + +/*- + * 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. + */ + +/* + * Comm Class spec: http://www.usb.org/developers/devclass_docs/usbccs10.pdf + * http://www.usb.org/developers/devclass_docs/usbcdc11.pdf + */ + +/* + * TODO: + * - Add error recovery in various places; the big problem is what + * to do in a callback if there is an error. + * - Implement a Call Device for modems without multiplexed commands. + * + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/ioccom.h> +#include <sys/conf.h> +#include <sys/serial.h> +#include <sys/tty.h> +#include <sys/file.h> +#include <sys/select.h> +#include <sys/sysctl.h> +#include <sys/proc.h> +#include <sys/bus.h> +#include <sys/sbuf.h> +#include <sys/poll.h> +#include <sys/uio.h> +#include <sys/taskqueue.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbcdc.h> + +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usb_quirks.h> + +#include <dev/usb/ucomvar.h> + +#include "usbdevs.h" + +typedef struct ufoma_mobile_acm_descriptor{ + uByte bFunctionLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bType; + uByte bMode[1]; +}usb_mcpc_acm_descriptor; + +#define UISUBCLASS_MCPC 0x88 + +#define UDESC_VS_INTERFACE 0x44 +#define UDESCSUB_MCPC_ACM 0x11 + +#define UMCPC_ACM_TYPE_AB1 0x1 +#define UMCPC_ACM_TYPE_AB2 0x2 +#define UMCPC_ACM_TYPE_AB5 0x5 +#define UMCPC_ACM_TYPE_AB6 0x6 + +#define UMCPC_ACM_MODE_DEACTIVATED 0x0 +#define UMCPC_ACM_MODE_MODEM 0x1 +#define UMCPC_ACM_MODE_ATCOMMAND 0x2 +#define UMCPC_ACM_MODE_OBEX 0x60 +#define UMCPC_ACM_MODE_VENDOR1 0xc0 +#define UMCPC_ACM_MODE_VENDOR2 0xfe +#define UMCPC_ACM_MODE_UNLINKED 0xff + +#define UMCPC_CM_MOBILE_ACM 0x0 + +#define UMCPC_ACTIVATE_MODE 0x60 +#define UMCPC_GET_MODETABLE 0x61 +#define UMCPC_SET_LINK 0x62 +#define UMCPC_CLEAR_LINK 0x63 + +#define UMCPC_REQUEST_ACKNOLEDGE 0x31 + +#define UFOMA_MAX_TIMEOUT 15 /*Standard says 10(sec)*/ +#define UFOMA_CMD_BUF_SIZE 64 + +#define UMODEMIBUFSIZE 1024 +#define UMODEMOBUFSIZE 1024 +#define DPRINTF(a) + +struct ufoma_softc{ + struct ucom_softc sc_ucom; + int sc_is_ucom; + int sc_isopen; + + struct mtx sc_mtx; + int sc_ctl_iface_no; + usbd_interface_handle sc_ctl_iface; + usbd_interface_handle sc_data_iface; + int sc_data_iface_no; + int sc_cm_cap; + int sc_acm_cap; + usb_cdc_line_state_t sc_line_state; /* current line state */ + usb_cdc_line_state_t sc_line_state_init; /* pre open line state*/ + u_char sc_dtr; /* current DTR state */ + u_char sc_rts; /* current RTS state */ + + usbd_pipe_handle sc_notify_pipe; + usb_cdc_notification_t sc_notify_buf; + u_char sc_lsr; + u_char sc_msr; + + struct task sc_task; + uByte *sc_modetable; + uByte sc_modetoactivate; + uByte sc_currentmode; + char sc_resbuffer[UFOMA_CMD_BUF_SIZE+1]; + int sc_cmdbp; + int sc_nummsg; + usbd_xfer_handle sc_msgxf; +}; +static usbd_status +ufoma_set_line_coding(struct ufoma_softc *sc, usb_cdc_line_state_t *state); +static device_probe_t ufoma_match; +static device_attach_t ufoma_attach; +static device_detach_t ufoma_detach; +static void *ufoma_get_intconf(usb_config_descriptor_t *cd, usb_interface_descriptor_t *id,int type, int subtype); +static void ufoma_notify(void * ,int count); +static void ufoma_intr(usbd_xfer_handle, usbd_private_handle, usbd_status); +static char *ufoma_mode_to_str(int); +static int ufoma_str_to_mode(char *); + +#ifdef UFOMA_HANDSFREE +/*Pseudo ucom stuff(for Handsfree interface)*/ +static int ufoma_init_pseudo_ucom(struct ufoma_softc *); +static tsw_open_t ufoma_open; +static tsw_close_t ufoma_close; +static tsw_outwakeup_t ufoma_outwakeup; +static tsw_free_t ufoma_free; +#endif + +/*umodem like stuff*/ +static int ufoma_init_modem(struct ufoma_softc *, struct usb_attach_arg *); +static void ufoma_get_status(void *, int portno, u_char *lst, u_char *msr); +static void ufoma_set(void *, int portno, int reg, int onoff); +static int ufoma_param(void *, int portno, struct termios *); +static int ufoma_ucom_open(void *, int portno); +static void ufoma_ucom_close(void *, int portno); +static void ufoma_break(struct ufoma_softc *sc, int onoff); +static void ufoma_dtr(struct ufoma_softc *sc, int onoff); +static void ufoma_rts(struct ufoma_softc *sc, int onoff); + +/*sysctl stuff*/ +static int ufoma_sysctl_support(SYSCTL_HANDLER_ARGS); +static int ufoma_sysctl_current(SYSCTL_HANDLER_ARGS); +static int ufoma_sysctl_open(SYSCTL_HANDLER_ARGS); +static void ufoma_set_line_state(struct ufoma_softc *sc); + +static struct ucom_callback ufoma_callback = { + .ucom_get_status = ufoma_get_status, + .ucom_set = ufoma_set, + .ucom_param = ufoma_param, + .ucom_open = ufoma_ucom_open, + .ucom_close = ufoma_ucom_close, +}; + + +static device_method_t ufoma_methods[] = { + /**/ + DEVMETHOD(device_probe, ufoma_match), + DEVMETHOD(device_attach, ufoma_attach), + DEVMETHOD(device_detach, ufoma_detach), + {0, 0} +}; +struct umcpc_modetostr_tab{ + int mode; + char *str; +}umcpc_modetostr_tab[]={ + {UMCPC_ACM_MODE_DEACTIVATED, "deactivated"}, + {UMCPC_ACM_MODE_MODEM, "modem"}, + {UMCPC_ACM_MODE_ATCOMMAND, "handsfree"}, + {UMCPC_ACM_MODE_OBEX, "obex"}, + {UMCPC_ACM_MODE_VENDOR1, "vendor1"}, + {UMCPC_ACM_MODE_VENDOR2, "vendor2"}, + {UMCPC_ACM_MODE_UNLINKED, "unlinked"}, + {0, NULL} +}; + +static driver_t ufoma_driver = { + "ucom", + ufoma_methods, + sizeof(struct ufoma_softc) +}; + + +DRIVER_MODULE(ufoma, uhub, ufoma_driver, ucom_devclass, usbd_driver_load, 0); +MODULE_DEPEND(ufoma, usb, 1, 1, 1); +MODULE_DEPEND(ufoma, ucom, UCOM_MINVER, UCOM_PREFVER, UCOM_MAXVER); + +static int +ufoma_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + usb_interface_descriptor_t *id; + usb_config_descriptor_t *cd; + usb_mcpc_acm_descriptor *mad; + int ret; + + ret = UMATCH_NONE; + + if(uaa->iface == NULL) + return(UMATCH_NONE); + id = usbd_get_interface_descriptor(uaa->iface); + cd = usbd_get_config_descriptor(uaa->device); + + if(id == NULL || cd == NULL) + return (UMATCH_NONE); + + if( id->bInterfaceClass == UICLASS_CDC && + id->bInterfaceSubClass == UISUBCLASS_MCPC){ + ret = (UMATCH_IFACECLASS_IFACESUBCLASS); + }else{ + return UMATCH_NONE; + } + + mad = ufoma_get_intconf(cd, id , UDESC_VS_INTERFACE, UDESCSUB_MCPC_ACM); + if(mad == NULL){ + return (UMATCH_NONE); + } + +#ifndef UFOMA_HANDSFREE + if((mad->bType == UMCPC_ACM_TYPE_AB5)|| + (mad->bType == UMCPC_ACM_TYPE_AB6)){ + return UMATCH_NONE; + } +#endif + return ret; +} + +static int +ufoma_attach(device_t self) +{ + struct ufoma_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + usbd_device_handle dev = uaa->device; + usb_config_descriptor_t *cd; + usb_interface_descriptor_t *id; + usb_endpoint_descriptor_t *ed; + usb_mcpc_acm_descriptor *mad; + struct ucom_softc *ucom = &sc->sc_ucom; + const char *devname,*modename; + int ctl_notify; + int i,err; + int elements; + uByte *mode; + struct sysctl_ctx_list *sctx; + struct sysctl_oid *soid; + + ucom->sc_dev = self; + ucom->sc_udev = dev; + sc->sc_ctl_iface = uaa->iface; + mtx_init(&sc->sc_mtx, "ufoma", NULL, MTX_DEF); + + cd = usbd_get_config_descriptor(ucom->sc_udev); + id = usbd_get_interface_descriptor(sc->sc_ctl_iface); + sc->sc_ctl_iface_no = id->bInterfaceNumber; + + devname = device_get_nameunit(self); + device_printf(self, "iclass %d/%d ifno:%d\n", + id->bInterfaceClass, id->bInterfaceSubClass, sc->sc_ctl_iface_no); + + ctl_notify = -1; + for (i = 0; i < id->bNumEndpoints; i++) { + ed = usbd_interface2endpoint_descriptor(sc->sc_ctl_iface, i); + if (ed == NULL) + continue; + + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + (ed->bmAttributes & UE_XFERTYPE) == UE_INTERRUPT) { + ctl_notify = ed->bEndpointAddress; + } + } + + if(ctl_notify== -1){ + /*NOTIFY is mandatory.*/ + printf("NOTIFY interface not found\n"); + goto error; + } + + err = usbd_open_pipe_intr(sc->sc_ctl_iface, ctl_notify, + USBD_SHORT_XFER_OK, &sc->sc_notify_pipe, sc, &sc->sc_notify_buf, + sizeof(sc->sc_notify_buf), ufoma_intr, USBD_DEFAULT_INTERVAL); + if(err){ + printf("PIPE open error %d\n", err); + goto error; + } + mad = ufoma_get_intconf(cd, id , UDESC_VS_INTERFACE, UDESCSUB_MCPC_ACM); + if(mad ==NULL){ + goto error; + } + + printf("%s:Supported Mode:", devname); + for(mode = mad->bMode; + mode < ((uByte *)mad + mad->bFunctionLength); mode++){ + modename = ufoma_mode_to_str(*mode); + if(modename){ + printf("%s", ufoma_mode_to_str(*mode)); + }else{ + printf("(%x)", *mode); + } + if(mode != ((uByte*)mad + mad->bFunctionLength-1)){ + printf(","); + } + } + printf("\n"); + if((mad->bType == UMCPC_ACM_TYPE_AB5) + ||(mad->bType == UMCPC_ACM_TYPE_AB6)){ +#ifdef UFOMA_HANDSFREE + /*These does not have data interface*/ + sc->sc_is_ucom = 0; + ufoma_init_pseudo_ucom(sc); +#else + /*Should not happen*/ + goto error; +#endif + + }else{ + if(ufoma_init_modem(sc, uaa)){ + goto error; + } + } + elements = mad->bFunctionLength - sizeof(*mad)+1; + sc->sc_msgxf = usbd_alloc_xfer(ucom->sc_udev); + sc->sc_nummsg = 0; + + /*Initialize Mode vars.*/ + sc->sc_modetable = malloc(elements + 1, M_USBDEV, M_WAITOK); + sc->sc_modetable[0] = elements + 1; + bcopy(mad->bMode, &sc->sc_modetable[1], elements); + sc->sc_currentmode = UMCPC_ACM_MODE_UNLINKED; + sc->sc_modetoactivate = mad->bMode[0]; + + /*Sysctls*/ + sctx = device_get_sysctl_ctx(self); + soid = device_get_sysctl_tree(self); + + SYSCTL_ADD_PROC(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "supportmode", + CTLFLAG_RD|CTLTYPE_STRING, sc, 0, ufoma_sysctl_support, + "A", "Supporting port role"); + + SYSCTL_ADD_PROC(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "currentmode", + CTLFLAG_RD|CTLTYPE_STRING, sc, 0, ufoma_sysctl_current, + "A", "Current port role"); + + SYSCTL_ADD_PROC(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "openmode", + CTLFLAG_RW|CTLTYPE_STRING, sc, 0, ufoma_sysctl_open, + "A", "Mode to transit when port is opened"); + + return 0; + error: + if(sc->sc_modetable) + free(sc->sc_modetable, M_USBDEV); + return EIO; +} + +static int +ufoma_detach(device_t self) +{ + struct ufoma_softc *sc = device_get_softc(self); + int rv = 0; + + usbd_free_xfer(sc->sc_msgxf); + sc->sc_ucom.sc_dying = 1; + usbd_abort_pipe(sc->sc_notify_pipe); + usbd_close_pipe(sc->sc_notify_pipe); + if(sc->sc_is_ucom){ + ucom_detach(&sc->sc_ucom); + } +#ifdef UFOMA_HANDSFREE + else{ + tty_lock(sc->sc_ucom.sc_tty); + tty_rel_gone(sc->sc_ucom.sc_tty); + } + +#endif + free(sc->sc_modetable, M_USBDEV); + return rv; +} + + +static char *ufoma_mode_to_str(int mode) +{ + int i; + for(i = 0 ;umcpc_modetostr_tab[i].str != NULL; i++){ + if(umcpc_modetostr_tab[i].mode == mode){ + return umcpc_modetostr_tab[i].str; + } + } + return NULL; +} + +static int ufoma_str_to_mode(char *str) +{ + int i; + for(i = 0 ;umcpc_modetostr_tab[i].str != NULL; i++){ + if(strcmp(str, umcpc_modetostr_tab[i].str)==0){ + return umcpc_modetostr_tab[i].mode; + } + } + return -1; +} + +static void *ufoma_get_intconf( usb_config_descriptor_t *cd, + usb_interface_descriptor_t *id, int type, int subtype) +{ + uByte *p, *end; + usb_descriptor_t *ud=NULL; + int flag=0; + + + for(p = (uByte *)cd,end = p + UGETW(cd->wTotalLength); p < end; + p += ud->bLength){ + ud = (usb_descriptor_t *)p; + if(flag && ud->bDescriptorType==UDESC_INTERFACE){ + return NULL; + } + /*Read through this interface desc.*/ + if(bcmp(p, id, sizeof(*id))==0){ + flag=1; + continue; + } + if(flag==0) + continue; + if(ud->bDescriptorType == type + && ud->bDescriptorSubtype == subtype){ + break; + } + } + return ud; +} + + + +static int ufoma_link_state(struct ufoma_softc *sc) +{ + usb_device_request_t req; + struct ucom_softc *ucom = &sc->sc_ucom; + int err; + + req.bmRequestType = UT_WRITE_VENDOR_INTERFACE; + req.bRequest = UMCPC_SET_LINK; + USETW(req.wValue, UMCPC_CM_MOBILE_ACM); + USETW(req.wIndex, sc->sc_ctl_iface_no); + USETW(req.wLength, sc->sc_modetable[0]); + + err = usbd_do_request(ucom->sc_udev, &req, sc->sc_modetable); + if(err){ + printf("SET_LINK:%s\n",usbd_errstr(err)); + return EIO; + } + err = tsleep(&sc->sc_currentmode, PZERO|PCATCH, "fmalnk", hz); + if(err){ + printf("NO response"); + return EIO; + } + if(sc->sc_currentmode != UMCPC_ACM_MODE_DEACTIVATED){ + return EIO; + } + return 0; +} + +static int ufoma_activate_state(struct ufoma_softc *sc, int state) +{ + usb_device_request_t req; + int err; + struct ucom_softc *ucom = &sc->sc_ucom; + + req.bmRequestType = UT_WRITE_VENDOR_INTERFACE; + req.bRequest = UMCPC_ACTIVATE_MODE; + USETW(req.wValue, state); + USETW(req.wIndex, sc->sc_ctl_iface_no); + USETW(req.wLength, 0); + + err = usbd_do_request(ucom->sc_udev, &req, NULL); + if(err){ + printf("%s:ACTIVATE(%x):%s\n", + device_get_nameunit(ucom->sc_dev), state, + usbd_errstr(err)); + return EIO; + } + + err = tsleep(&sc->sc_currentmode, PZERO|PCATCH, "fmaact", UFOMA_MAX_TIMEOUT*hz); + if(err){ + printf("%s:NO response", device_get_nameunit(ucom->sc_dev)); + return EIO; + } + if(sc->sc_currentmode != state){ + return EIO; + } + return 0; +} + +#ifdef UFOMA_HANDSFREE +static inline void ufoma_setup_msg_req(struct ufoma_softc *sc, usb_device_request_t *req) +{ + req->bmRequestType = UT_READ_CLASS_INTERFACE; + req->bRequest = UCDC_GET_ENCAPSULATED_RESPONSE; + USETW(req->wIndex, sc->sc_ctl_iface_no); + USETW(req->wValue, 0); + USETW(req->wLength, UFOMA_CMD_BUF_SIZE); +} + + +static void ufoma_msg(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + usb_device_request_t req; + struct ufoma_softc *sc = priv; + int actlen,i; + struct ucom_softc *ucom= &sc->sc_ucom; + usbd_get_xfer_status(xfer, NULL, NULL, &actlen ,NULL); + ufoma_setup_msg_req(sc, &req); + mtx_lock(&sc->sc_mtx); + for(i = 0;i < actlen; i++){ + + if(ttydisc_rint(sc->sc_ucom.sc_tty, sc->sc_resbuffer[i], 0) + == -1){ + break; + } + } + + ttydisc_rint_done(sc->sc_ucom.sc_tty); + + sc->sc_nummsg--; + if(sc->sc_nummsg){ + usbd_setup_default_xfer(sc->sc_msgxf, ucom->sc_udev, + priv, USBD_DEFAULT_TIMEOUT, &req, + sc->sc_resbuffer, + UFOMA_CMD_BUF_SIZE, + 0, ufoma_msg); + usbd_transfer(sc->sc_msgxf); + } + mtx_unlock(&sc->sc_mtx); + +} +#endif +static void ufoma_intr(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + struct ufoma_softc *sc = priv; + unsigned int a; + struct ucom_softc *ucom =&sc->sc_ucom; + u_char mstatus; + +#ifdef UFOMA_HANDSFREE + usb_device_request_t req; + + ufoma_setup_msg_req(sc, &req); +#endif + + if (sc->sc_ucom.sc_dying) + return; + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) + return; + printf("%s: abnormal status: %s\n", device_get_nameunit(ucom->sc_dev), + usbd_errstr(status)); + return; + } + if((sc->sc_notify_buf.bmRequestType == UT_READ_VENDOR_INTERFACE)&& + (sc->sc_notify_buf.bNotification == UMCPC_REQUEST_ACKNOLEDGE)){ + a = UGETW(sc->sc_notify_buf.wValue); + sc->sc_currentmode = a>>8; + if(!(a&0xff)){ + printf("%s:Mode change Failed\n", device_get_nameunit(ucom->sc_dev)); + } + wakeup(&sc->sc_currentmode); + } + if(sc->sc_notify_buf.bmRequestType != UCDC_NOTIFICATION){ + return; + } + switch(sc->sc_notify_buf.bNotification){ +#ifdef UFOMA_HANDSFREE + case UCDC_N_RESPONSE_AVAILABLE: + if(sc->sc_is_ucom){ + printf("%s:wrong response request?\n", device_get_nameunit(ucom->sc_dev)); + break; + } + mtx_lock(&sc->sc_mtx); + if(!sc->sc_nummsg){ + usbd_setup_default_xfer(sc->sc_msgxf, ucom->sc_udev, + priv, USBD_DEFAULT_TIMEOUT, &req, sc->sc_resbuffer, + UFOMA_CMD_BUF_SIZE, + 0, ufoma_msg); + usbd_transfer(sc->sc_msgxf); + } + sc->sc_nummsg++; + mtx_unlock(&sc->sc_mtx); + break; +#endif + case UCDC_N_SERIAL_STATE: + if(!sc->sc_is_ucom){ + printf("%s:wrong sereal request?\n",device_get_nameunit(ucom->sc_dev)); + break; + } + + /* + * Set the serial state in ucom driver based on + * the bits from the notify message + */ + if (UGETW(sc->sc_notify_buf.wLength) != 2) { + printf("%s: Invalid notification length! (%d)\n", + device_get_nameunit(ucom->sc_dev), + UGETW(sc->sc_notify_buf.wLength)); + break; + } + DPRINTF(("%s: notify bytes = %02x%02x\n", + device_get_nameunit(ucom->sc_dev), + sc->sc_notify_buf.data[0], + sc->sc_notify_buf.data[1])); + /* Currently, lsr is always zero. */ + sc->sc_lsr = sc->sc_msr = 0; + mstatus = sc->sc_notify_buf.data[0]; + + if (ISSET(mstatus, UCDC_N_SERIAL_RI)) + sc->sc_msr |= SER_RI; + if (ISSET(mstatus, UCDC_N_SERIAL_DSR)) + sc->sc_msr |= SER_DSR; + if (ISSET(mstatus, UCDC_N_SERIAL_DCD)) + sc->sc_msr |= SER_DCD; + /* Deferred notifying to the ucom layer */ + taskqueue_enqueue(taskqueue_swi_giant, &sc->sc_task); + break; + default: + break; + } +} + +#ifdef UFOMA_HANDSFREE +struct ttydevsw ufomatty_class ={ + .tsw_flags = TF_INITLOCK|TF_CALLOUT, + .tsw_open = ufoma_open, + .tsw_close = ufoma_close, + .tsw_outwakeup = ufoma_outwakeup, + .tsw_free = ufoma_free +}; +static void ufoma_free(void *sc) +{ + +} +static int ufoma_init_pseudo_ucom(struct ufoma_softc *sc) +{ + struct tty *tp; + struct ucom_softc *ucom = &sc->sc_ucom; + tp = ucom->sc_tty = tty_alloc(&ufomatty_class, sc, &Giant); + tty_makedev(tp, NULL, "U%d", device_get_unit(ucom->sc_dev)); + + return 0; +} + + +static int ufoma_open(struct tty * tty) +{ + + struct ufoma_softc *sc = tty_softc(tty); + + if(sc->sc_ucom.sc_dying) + return (ENXIO); + + mtx_lock(&sc->sc_mtx); + if(sc->sc_isopen){ + mtx_unlock(&sc->sc_mtx); + return EBUSY; + } + mtx_unlock(&sc->sc_mtx); + + return ufoma_ucom_open(sc, 0); +} + +static void ufoma_close(struct tty *tty) +{ + struct ufoma_softc *sc = tty_softc(tty); + + ufoma_ucom_close(sc, 0); +} + +static void ufoma_outwakeup(struct tty *tp) +{ + struct ufoma_softc *sc = tty_softc(tp); + struct ucom_softc *ucom = &sc->sc_ucom; + usb_device_request_t req; + int len,i; + unsigned char buf[128]; + + uByte c; + if(ucom->sc_dying) + return; + if(ucom->sc_state &UCS_TXBUSY) + return; + + ucom->sc_state |= UCS_TXBUSY; + for(;;){ + len = ttydisc_getc(tp, buf, sizeof(buf)); + if(len == 0){ + break; + } + + for(i=0; i < len; i++){ + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + c = buf[i]; + req.bRequest = UCDC_SEND_ENCAPSULATED_COMMAND; + USETW(req.wIndex, sc->sc_ctl_iface_no); + USETW(req.wValue, 0); + USETW(req.wLength, 1); + usbd_do_request(ucom->sc_udev, &req, &c); + } + } + ucom->sc_state &= ~(UCS_TXBUSY); + +} +#endif + + +static int ufoma_ucom_open(void *p, int portno) +{ + struct ufoma_softc *sc = p; + int res; + + if(sc->sc_currentmode == UMCPC_ACM_MODE_UNLINKED){ + if((res = ufoma_link_state(sc))){ + return res; + } + } + + sc->sc_cmdbp = 0; + if(sc->sc_currentmode == UMCPC_ACM_MODE_DEACTIVATED){ + if((res = ufoma_activate_state(sc, sc->sc_modetoactivate))){ + return res; + } + } + mtx_lock(&sc->sc_mtx); + sc->sc_isopen = 1; + mtx_unlock(&sc->sc_mtx); + /*Now line coding should be set.*/ + if(sc->sc_is_ucom){ + ufoma_set_line_state(sc); + ufoma_set_line_coding(sc, NULL); + } + return 0; +} + +static void ufoma_ucom_close(void *p, int portno) +{ + struct ufoma_softc *sc = p; + ufoma_activate_state(sc, UMCPC_ACM_MODE_DEACTIVATED); + mtx_lock(&sc->sc_mtx); + sc->sc_isopen = 0; + mtx_unlock(&sc->sc_mtx); + return ; +} + +void +ufoma_break(struct ufoma_softc *sc, int onoff) +{ + usb_device_request_t req; + struct ucom_softc *ucom = &sc->sc_ucom; + DPRINTF(("ufoma_break: onoff=%d\n", onoff)); + + if (!(sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK)) + return; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SEND_BREAK; + USETW(req.wValue, onoff ? UCDC_BREAK_ON : UCDC_BREAK_OFF); + USETW(req.wIndex, sc->sc_ctl_iface_no); + USETW(req.wLength, 0); + + (void)usbd_do_request(ucom->sc_udev, &req, 0); +} + +void +ufoma_get_status(void *addr, int portno, u_char *lsr, u_char *msr) +{ + struct ufoma_softc *sc = addr; + + if (lsr != NULL) + *lsr = sc->sc_lsr; + if (msr != NULL) + *msr = sc->sc_msr; +} + +static void ufoma_set(void * addr, int portno, int reg, int onoff) +{ + struct ufoma_softc *sc = addr; + + switch (reg) { + case UCOM_SET_DTR: + ufoma_dtr(sc, onoff); + break; + case UCOM_SET_RTS: + ufoma_rts(sc, onoff); + break; + case UCOM_SET_BREAK: + ufoma_break(sc, onoff); + break; + default: + break; + } + +} + +static void +ufoma_set_line_state(struct ufoma_softc *sc) +{ + usb_device_request_t req; + struct ucom_softc *ucom = &sc->sc_ucom; + int ls; + int err; + + if(!sc->sc_isopen){ + return ; /*Set it later*/ + } + + /*Don't send line state emulation request for OBEX port*/ + if(sc->sc_currentmode == UMCPC_ACM_MODE_OBEX){ + return; + } + + ls = (sc->sc_dtr ? UCDC_LINE_DTR : 0) | + (sc->sc_rts ? UCDC_LINE_RTS : 0); + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + USETW(req.wValue, ls); + USETW(req.wIndex, sc->sc_ctl_iface_no); + USETW(req.wLength, 0); + + err = usbd_do_request(ucom->sc_udev, &req, 0); + if(err){ + printf("LINE_STATE:%s\n", usbd_errstr(err)); + } + + +} + +void +ufoma_dtr(struct ufoma_softc *sc, int onoff) +{ + DPRINTF(("ufoma_foma: onoff=%d\n", onoff)); + + if (sc->sc_dtr == onoff) + return; + sc->sc_dtr = onoff; + + ufoma_set_line_state(sc); +} + +void +ufoma_rts(struct ufoma_softc *sc, int onoff) +{ + DPRINTF(("ufoma_foma: onoff=%d\n", onoff)); + + if (sc->sc_rts == onoff) + return; + sc->sc_rts = onoff; + + ufoma_set_line_state(sc); +} + +usbd_status +ufoma_set_line_coding(struct ufoma_softc *sc, usb_cdc_line_state_t *state) +{ + + usbd_status err; + usb_device_request_t req; + struct ucom_softc *ucom = &sc->sc_ucom; + if(!sc->sc_isopen){ + sc->sc_line_state_init = *state; + return (USBD_NORMAL_COMPLETION); + } + + DPRINTF(("ufoma_set_line_coding: rate=%d fmt=%d parity=%d bits=%d\n", + UGETDW(state->dwDTERate), state->bCharFormat, + state->bParityType, state->bDataBits)); + if(state == NULL){ + state = &sc->sc_line_state_init; + }else if (memcmp(state, &sc->sc_line_state, + UCDC_LINE_STATE_LENGTH) == 0) { + DPRINTF(("ufoma_set_line_coding: already set\n")); + return (USBD_NORMAL_COMPLETION); + } + + /*Don't send line state emulation request for OBEX port*/ + if(sc->sc_currentmode == UMCPC_ACM_MODE_OBEX){ + sc->sc_line_state = *state; + return (USBD_NORMAL_COMPLETION); + } + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_LINE_CODING; + USETW(req.wValue, 0); + USETW(req.wIndex, sc->sc_ctl_iface_no); + USETW(req.wLength, UCDC_LINE_STATE_LENGTH); + + err = usbd_do_request(ucom->sc_udev, &req, state); + if (err) { + DPRINTF(("ufoma_set_line_coding: failed, err=%s\n", + usbd_errstr(err))); + return (err); + } + + sc->sc_line_state = *state; + + return (USBD_NORMAL_COMPLETION); +} + +static int +ufoma_param(void *addr, int portno, struct termios *t) +{ + + struct ufoma_softc *sc = addr; + usbd_status err; + usb_cdc_line_state_t ls; + + DPRINTF(("ufoma_param: sc=%p\n", sc)); + + USETDW(ls.dwDTERate, t->c_ospeed); + if (ISSET(t->c_cflag, CSTOPB)) + ls.bCharFormat = UCDC_STOP_BIT_2; + else + ls.bCharFormat = UCDC_STOP_BIT_1; + if (ISSET(t->c_cflag, PARENB)) { + if (ISSET(t->c_cflag, PARODD)) + ls.bParityType = UCDC_PARITY_ODD; + else + ls.bParityType = UCDC_PARITY_EVEN; + } else + ls.bParityType = UCDC_PARITY_NONE; + switch (ISSET(t->c_cflag, CSIZE)) { + case CS5: + ls.bDataBits = 5; + break; + case CS6: + ls.bDataBits = 6; + break; + case CS7: + ls.bDataBits = 7; + break; + case CS8: + ls.bDataBits = 8; + break; + } + + err = ufoma_set_line_coding(sc, &ls); + if (err) { + DPRINTF(("ufoma_param: err=%s\n", usbd_errstr(err))); + return (EIO); + } + + return (0); +} + +static int ufoma_init_modem(struct ufoma_softc *sc,struct usb_attach_arg *uaa) +{ + struct ucom_softc *ucom = &sc->sc_ucom; + usb_config_descriptor_t *cd; + usb_cdc_acm_descriptor_t *acm; + usb_cdc_cm_descriptor_t *cmd; + usb_endpoint_descriptor_t *ed; + usb_interface_descriptor_t *id; + const char *devname = device_get_nameunit(ucom->sc_dev); + int i; + cd = usbd_get_config_descriptor(ucom->sc_udev); + id = usbd_get_interface_descriptor(sc->sc_ctl_iface); + + sc->sc_is_ucom = 1; + + cmd = ufoma_get_intconf(cd, id, UDESC_CS_INTERFACE, UDESCSUB_CDC_CM); + if(cmd == NULL) + return -1; + sc->sc_cm_cap = cmd->bmCapabilities; + + acm = ufoma_get_intconf(cd, id, UDESC_CS_INTERFACE, UDESCSUB_CDC_ACM); + if(acm == NULL) + return -1; + sc->sc_acm_cap = acm->bmCapabilities; + + sc->sc_data_iface_no = cmd->bDataInterface; + printf("%s: data interface %d, has %sCM over data, has %sbreak\n", + devname, sc->sc_data_iface_no, + sc->sc_cm_cap & USB_CDC_CM_OVER_DATA ? "" : "no ", + sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK ? "" : "no "); + + for(i = 0; i < uaa->nifaces; i++){ + if(!uaa->ifaces[i]) + continue; + id = usbd_get_interface_descriptor(uaa->ifaces[i]); + if(id != NULL && + id->bInterfaceNumber == sc->sc_data_iface_no){ + sc->sc_data_iface = uaa->ifaces[i]; + //uaa->ifaces[i] = NULL; + } + } + + ucom->sc_iface = sc->sc_data_iface; + ucom->sc_bulkin_no = ucom->sc_bulkout_no = -1; + id = usbd_get_interface_descriptor(sc->sc_data_iface); + for(i = 0 ; i < id->bNumEndpoints; i++){ + ed = usbd_interface2endpoint_descriptor(sc->sc_data_iface, i); + if(ed == NULL){ + printf("%s: endpoint descriptor for %d\n", + devname,i); + return -1; + } + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + ucom->sc_bulkin_no = ed->bEndpointAddress; + } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + ucom->sc_bulkout_no = ed->bEndpointAddress; + } + } + + if (ucom->sc_bulkin_no == -1) { + printf("%s: Could not find data bulk in\n", devname); + return -1; + } + if (ucom->sc_bulkout_no == -1) { + printf("%s: Could not find data bulk out\n", devname); + return -1; + } + + sc->sc_dtr = -1; + + ucom->sc_parent = sc; + ucom->sc_portno = UCOM_UNK_PORTNO; + /* bulkin, bulkout set above */ + ucom->sc_ibufsize = UMODEMIBUFSIZE; + ucom->sc_obufsize = UMODEMOBUFSIZE; + ucom->sc_ibufsizepad = UMODEMIBUFSIZE; + ucom->sc_opkthdrlen = 0; + ucom->sc_callback = &ufoma_callback; + TASK_INIT(&sc->sc_task, 0, ufoma_notify, sc); + ucom_attach(&sc->sc_ucom); + + return 0; +} + +static void +ufoma_notify(void *arg, int count) +{ + struct ufoma_softc *sc; + + sc = (struct ufoma_softc *)arg; + if (sc->sc_ucom.sc_dying) + return; + ucom_status_change(&sc->sc_ucom); +} +static int ufoma_sysctl_support(SYSCTL_HANDLER_ARGS) +{ + struct ufoma_softc *sc = (struct ufoma_softc *)oidp->oid_arg1; + struct sbuf sb; + int i; + char *mode; + + sbuf_new(&sb, NULL, 1, SBUF_AUTOEXTEND); + for(i = 1; i < sc->sc_modetable[0]; i++){ + mode = ufoma_mode_to_str(sc->sc_modetable[i]); + if(mode !=NULL){ + sbuf_cat(&sb, mode); + }else{ + sbuf_printf(&sb, "(%02x)", sc->sc_modetable[i]); + } + if(i < (sc->sc_modetable[0]-1)) + sbuf_cat(&sb, ","); + } + sbuf_trim(&sb); + sbuf_finish(&sb); + sysctl_handle_string(oidp, sbuf_data(&sb), sbuf_len(&sb), req); + sbuf_delete(&sb); + + return 0; +} +static int ufoma_sysctl_current(SYSCTL_HANDLER_ARGS) +{ + struct ufoma_softc *sc = (struct ufoma_softc *)oidp->oid_arg1; + char *mode; + char subbuf[]="(XXX)"; + mode = ufoma_mode_to_str(sc->sc_currentmode); + if(!mode){ + mode = subbuf; + snprintf(subbuf, sizeof(subbuf), "(%02x)", sc->sc_currentmode); + } + sysctl_handle_string(oidp, mode, strlen(mode), req); + + return 0; + +} +static int ufoma_sysctl_open(SYSCTL_HANDLER_ARGS) +{ + struct ufoma_softc *sc = (struct ufoma_softc *)oidp->oid_arg1; + char *mode; + char subbuf[40]; + int newmode; + int error; + int i; + + mode = ufoma_mode_to_str(sc->sc_modetoactivate); + if(mode){ + strncpy(subbuf, mode, sizeof(subbuf)); + }else{ + snprintf(subbuf, sizeof(subbuf), "(%02x)", sc->sc_modetoactivate); + } + error = sysctl_handle_string(oidp, subbuf, sizeof(subbuf), req); + if(error != 0 || req->newptr == NULL){ + return error; + } + + if((newmode = ufoma_str_to_mode(subbuf)) == -1){ + return EINVAL; + } + + for(i = 1 ; i < sc->sc_modetable[0] ; i++){ + if(sc->sc_modetable[i] == newmode){ + sc->sc_modetoactivate = newmode; + return 0; + } + } + + return EINVAL; +} diff --git a/sys/legacy/dev/usb/uftdi.c b/sys/legacy/dev/usb/uftdi.c new file mode 100644 index 0000000..c652977 --- /dev/null +++ b/sys/legacy/dev/usb/uftdi.c @@ -0,0 +1,793 @@ +/* $NetBSD: uftdi.c,v 1.13 2002/09/23 05:51:23 simonb Exp $ */ + +/*- + * Copyright (c) 2000 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. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * FTDI FT8U100AX serial adapter driver + */ + +#include <sys/cdefs.h> + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/ioccom.h> +#include <sys/fcntl.h> +#include <sys/conf.h> +#include <sys/tty.h> +#include <sys/file.h> + +#include <sys/selinfo.h> + +#include <sys/sysctl.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbhid.h> + +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include "usbdevs.h" + +#include <dev/usb/ucomvar.h> + +#include <dev/usb/uftdireg.h> + +#ifdef USB_DEBUG +static int uftdidebug = 0; +SYSCTL_NODE(_hw_usb, OID_AUTO, uftdi, CTLFLAG_RW, 0, "USB uftdi"); +SYSCTL_INT(_hw_usb_uftdi, OID_AUTO, debug, CTLFLAG_RW, + &uftdidebug, 0, "uftdi debug level"); +#define DPRINTF(x) do { \ + if (uftdidebug) \ + printf x; \ + } while (0) + +#define DPRINTFN(n, x) do { \ + if (uftdidebug > (n)) \ + printf x; \ + } while (0) + +#else +#define DPRINTF(x) +#define DPRINTFN(n,x) +#endif + +#define UFTDI_CONFIG_INDEX 0 +#define UFTDI_IFACE_INDEX 0 + + +/* + * These are the maximum number of bytes transferred per frame. + * The output buffer size cannot be increased due to the size encoding. + */ +#define UFTDIIBUFSIZE 256 +#define UFTDIOBUFSIZE 64 + +struct uftdi_softc { + struct ucom_softc sc_ucom; + usbd_interface_handle sc_iface; /* interface */ + enum uftdi_type sc_type; + u_int sc_hdrlen; + u_char sc_msr; + u_char sc_lsr; + u_int last_lcr; +}; + +static void uftdi_get_status(void *, int portno, u_char *lsr, u_char *msr); +static void uftdi_set(void *, int, int, int); +static int uftdi_param(void *, int, struct termios *); +static int uftdi_open(void *sc, int portno); +static void uftdi_read(void *sc, int portno, u_char **ptr,u_int32_t *count); +static size_t uftdi_write(void *sc, int portno, struct tty *, + u_char *to, u_int32_t count); +static void uftdi_break(void *sc, int portno, int onoff); +static int uftdi_8u232am_getrate(speed_t speed, int *rate); + +struct ucom_callback uftdi_callback = { + uftdi_get_status, + uftdi_set, + uftdi_param, + NULL, + uftdi_open, + NULL, + uftdi_read, + uftdi_write, +}; + +static int +uftdi_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + + if (uaa->iface != NULL) { + if (uaa->vendor == USB_VENDOR_FTDI && + (uaa->product == USB_PRODUCT_FTDI_SERIAL_2232C)) + return (UMATCH_VENDOR_IFACESUBCLASS); + return (UMATCH_NONE); + } + + DPRINTFN(20,("uftdi: vendor=0x%x, product=0x%x\n", + uaa->vendor, uaa->product)); + + if (uaa->vendor == USB_VENDOR_FTDI && + (uaa->product == USB_PRODUCT_FTDI_SERIAL_8U100AX || + uaa->product == USB_PRODUCT_FTDI_SERIAL_8U232AM || + uaa->product == USB_PRODUCT_FTDI_SEMC_DSS20 || + uaa->product == USB_PRODUCT_FTDI_CFA_631 || + uaa->product == USB_PRODUCT_FTDI_CFA_632 || + uaa->product == USB_PRODUCT_FTDI_CFA_633 || + uaa->product == USB_PRODUCT_FTDI_CFA_634 || + uaa->product == USB_PRODUCT_FTDI_CFA_635 || + uaa->product == USB_PRODUCT_FTDI_USBSERIAL || + uaa->product == USB_PRODUCT_FTDI_MX2_3 || + uaa->product == USB_PRODUCT_FTDI_MX4_5 || + uaa->product == USB_PRODUCT_FTDI_LK202 || + uaa->product == USB_PRODUCT_FTDI_LK204 || + uaa->product == USB_PRODUCT_FTDI_TACTRIX_OPENPORT_13M || + uaa->product == USB_PRODUCT_FTDI_TACTRIX_OPENPORT_13S || + uaa->product == USB_PRODUCT_FTDI_TACTRIX_OPENPORT_13U || + uaa->product == USB_PRODUCT_FTDI_EISCOU || + uaa->product == USB_PRODUCT_FTDI_UOPTBR || + uaa->product == USB_PRODUCT_FTDI_EMCU2D || + uaa->product == USB_PRODUCT_FTDI_PCMSFU || + uaa->product == USB_PRODUCT_FTDI_EMCU2H || + uaa->product == USB_PRODUCT_FTDI_MAXSTREAM )) + return (UMATCH_VENDOR_PRODUCT); + if (uaa->vendor == USB_VENDOR_SIIG2 && + (uaa->product == USB_PRODUCT_SIIG2_US2308)) + return (UMATCH_VENDOR_PRODUCT); + if (uaa->vendor == USB_VENDOR_INTREPIDCS && + (uaa->product == USB_PRODUCT_INTREPIDCS_VALUECAN || + uaa->product == USB_PRODUCT_INTREPIDCS_NEOVI)) + return (UMATCH_VENDOR_PRODUCT); + if (uaa->vendor == USB_VENDOR_BBELECTRONICS && + (uaa->product == USB_PRODUCT_BBELECTRONICS_USOTL4)) + return (UMATCH_VENDOR_PRODUCT); + if (uaa->vendor == USB_VENDOR_MELCO && + (uaa->product == USB_PRODUCT_MELCO_PCOPRS1)) + return (UMATCH_VENDOR_PRODUCT); + if (uaa->vendor == USB_VENDOR_DRESDENELEKTRONIK && + (uaa->product == USB_PRODUCT_DRESDENELEKTRONIK_SENSORTERMINALBOARD)) + return (UMATCH_VENDOR_PRODUCT); + + return (UMATCH_NONE); +} + +static int +uftdi_attach(device_t self) +{ + struct uftdi_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + usbd_device_handle dev = uaa->device; + usbd_interface_handle iface; + usb_interface_descriptor_t *id; + usb_endpoint_descriptor_t *ed; + int i; + usbd_status err; + struct ucom_softc *ucom = &sc->sc_ucom; + DPRINTFN(10,("\nuftdi_attach: sc=%p\n", sc)); + + ucom->sc_dev = self; + ucom->sc_udev = dev; + + if (uaa->iface == NULL) { + /* Move the device into the configured state. */ + err = usbd_set_config_index(dev, UFTDI_CONFIG_INDEX, 1); + if (err) { + device_printf(ucom->sc_dev, + "failed to set configuration, err=%s\n", + usbd_errstr(err)); + goto bad; + } + + err = usbd_device2interface_handle(dev, UFTDI_IFACE_INDEX, &iface); + if (err) { + device_printf(ucom->sc_dev, + "failed to get interface, err=%s\n", usbd_errstr(err)); + goto bad; + } + } else { + iface = uaa->iface; + } + + id = usbd_get_interface_descriptor(iface); + ucom->sc_iface = iface; + switch( uaa->vendor ){ + case USB_VENDOR_FTDI: + switch( uaa->product ){ + case USB_PRODUCT_FTDI_SERIAL_8U100AX: + sc->sc_type = UFTDI_TYPE_SIO; + sc->sc_hdrlen = 1; + break; + case USB_PRODUCT_FTDI_SEMC_DSS20: + case USB_PRODUCT_FTDI_SERIAL_8U232AM: + case USB_PRODUCT_FTDI_SERIAL_2232C: + case USB_PRODUCT_FTDI_CFA_631: + case USB_PRODUCT_FTDI_CFA_632: + case USB_PRODUCT_FTDI_CFA_633: + case USB_PRODUCT_FTDI_CFA_634: + case USB_PRODUCT_FTDI_CFA_635: + case USB_PRODUCT_FTDI_USBSERIAL: + case USB_PRODUCT_FTDI_MX2_3: + case USB_PRODUCT_FTDI_MX4_5: + case USB_PRODUCT_FTDI_LK202: + case USB_PRODUCT_FTDI_LK204: + case USB_PRODUCT_FTDI_TACTRIX_OPENPORT_13M: + case USB_PRODUCT_FTDI_TACTRIX_OPENPORT_13S: + case USB_PRODUCT_FTDI_TACTRIX_OPENPORT_13U: + case USB_PRODUCT_FTDI_EISCOU: + case USB_PRODUCT_FTDI_UOPTBR: + case USB_PRODUCT_FTDI_EMCU2D: + case USB_PRODUCT_FTDI_PCMSFU: + case USB_PRODUCT_FTDI_EMCU2H: + case USB_PRODUCT_FTDI_MAXSTREAM: + sc->sc_type = UFTDI_TYPE_8U232AM; + sc->sc_hdrlen = 0; + break; + + default: /* Can't happen */ + goto bad; + } + break; + + case USB_VENDOR_INTREPIDCS: + switch( uaa->product ){ + case USB_PRODUCT_INTREPIDCS_VALUECAN: + case USB_PRODUCT_INTREPIDCS_NEOVI: + sc->sc_type = UFTDI_TYPE_8U232AM; + sc->sc_hdrlen = 0; + break; + + default: /* Can't happen */ + goto bad; + } + break; + + case USB_VENDOR_SIIG2: + switch( uaa->product ){ + case USB_PRODUCT_SIIG2_US2308: + sc->sc_type = UFTDI_TYPE_8U232AM; + sc->sc_hdrlen = 0; + break; + + default: /* Can't happen */ + goto bad; + } + break; + + case USB_VENDOR_BBELECTRONICS: + switch( uaa->product ){ + case USB_PRODUCT_BBELECTRONICS_USOTL4: + sc->sc_type = UFTDI_TYPE_8U232AM; + sc->sc_hdrlen = 0; + break; + + default: /* Can't happen */ + goto bad; + } + break; + + case USB_VENDOR_MELCO: + switch( uaa->product ){ + case USB_PRODUCT_MELCO_PCOPRS1: + sc->sc_type = UFTDI_TYPE_8U232AM; + sc->sc_hdrlen = 0; + break; + + default: /* Can't happen */ + goto bad; + } + break; + + case USB_VENDOR_DRESDENELEKTRONIK: + switch( uaa->product ){ + case USB_PRODUCT_DRESDENELEKTRONIK_SENSORTERMINALBOARD: + sc->sc_type = UFTDI_TYPE_8U232AM; + sc->sc_hdrlen = 0; + break; + + default: /* Can't happen */ + goto bad; + } + break; + + default: /* Can't happen */ + goto bad; + } + + ucom->sc_bulkin_no = ucom->sc_bulkout_no = -1; + + for (i = 0; i < id->bNumEndpoints; i++) { + int addr, dir, attr; + ed = usbd_interface2endpoint_descriptor(iface, i); + if (ed == NULL) { + device_printf(ucom->sc_dev, + "could not read endpoint descriptor\n"); + goto bad; + } + + addr = ed->bEndpointAddress; + dir = UE_GET_DIR(ed->bEndpointAddress); + attr = ed->bmAttributes & UE_XFERTYPE; + if (dir == UE_DIR_IN && attr == UE_BULK) + ucom->sc_bulkin_no = addr; + else if (dir == UE_DIR_OUT && attr == UE_BULK) + ucom->sc_bulkout_no = addr; + else { + device_printf(ucom->sc_dev, "unexpected endpoint\n"); + goto bad; + } + } + if (ucom->sc_bulkin_no == -1) { + device_printf(ucom->sc_dev, "Could not find data bulk in\n"); + goto bad; + } + if (ucom->sc_bulkout_no == -1) { + device_printf(ucom->sc_dev, "Could not find data bulk out\n"); + goto bad; + } + ucom->sc_parent = sc; + if (uaa->iface == NULL) + ucom->sc_portno = FTDI_PIT_SIOA; + else + ucom->sc_portno = FTDI_PIT_SIOA + id->bInterfaceNumber; + /* bulkin, bulkout set above */ + + ucom->sc_ibufsize = UFTDIIBUFSIZE; + ucom->sc_obufsize = UFTDIOBUFSIZE - sc->sc_hdrlen; + ucom->sc_ibufsizepad = UFTDIIBUFSIZE; + ucom->sc_opkthdrlen = sc->sc_hdrlen; + + + ucom->sc_callback = &uftdi_callback; +#if 0 + usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, ucom->sc_udev, + ucom->sc_dev); +#endif + DPRINTF(("uftdi: in=0x%x out=0x%x\n", ucom->sc_bulkin_no, ucom->sc_bulkout_no)); + ucom_attach(&sc->sc_ucom); + return 0; + +bad: + DPRINTF(("uftdi_attach: ATTACH ERROR\n")); + ucom->sc_dying = 1; + return ENXIO; +} +#if 0 +int +uftdi_activate(device_t self, enum devact act) +{ + struct uftdi_softc *sc = (struct uftdi_softc *)self; + int rv = 0; + + switch (act) { + case DVACT_ACTIVATE: + return (EOPNOTSUPP); + + case DVACT_DEACTIVATE: + if (sc->sc_subdev != NULL) + rv = config_deactivate(sc->sc_subdev); + sc->sc_ucom.sc_dying = 1; + break; + } + return (rv); +} +#endif + +static int +uftdi_detach(device_t self) +{ + struct uftdi_softc *sc = device_get_softc(self); + + int rv = 0; + + DPRINTF(("uftdi_detach: sc=%p\n", sc)); + sc->sc_ucom.sc_dying = 1; + rv = ucom_detach(&sc->sc_ucom); + + return rv; +} + +static int +uftdi_open(void *vsc, int portno) +{ + struct uftdi_softc *sc = vsc; + struct ucom_softc *ucom = &sc->sc_ucom; + usb_device_request_t req; + usbd_status err; + struct termios t; + + DPRINTF(("uftdi_open: sc=%p\n", sc)); + + if (ucom->sc_dying) + return (EIO); + + /* Perform a full reset on the device */ + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_RESET; + USETW(req.wValue, FTDI_SIO_RESET_SIO); + USETW(req.wIndex, portno); + USETW(req.wLength, 0); + err = usbd_do_request(ucom->sc_udev, &req, NULL); + if (err) + return (EIO); + + /* Set 9600 baud, 2 stop bits, no parity, 8 bits */ + t.c_ospeed = 9600; + t.c_cflag = CSTOPB | CS8; + (void)uftdi_param(sc, portno, &t); + + /* Turn on RTS/CTS flow control */ + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_SET_FLOW_CTRL; + USETW(req.wValue, 0); + USETW2(req.wIndex, FTDI_SIO_RTS_CTS_HS, portno); + USETW(req.wLength, 0); + err = usbd_do_request(ucom->sc_udev, &req, NULL); + if (err) + return (EIO); + + return (0); +} + +static void +uftdi_read(void *vsc, int portno, u_char **ptr, u_int32_t *count) +{ + struct uftdi_softc *sc = vsc; + u_char msr, lsr; + unsigned l; + + DPRINTFN(15,("uftdi_read: sc=%p, port=%d count=%d\n", + sc, portno, *count)); + while (*count > 0) { + l = *count; + if (l > 64) + l = 64; + + msr = FTDI_GET_MSR(*ptr); + lsr = FTDI_GET_LSR(*ptr); + + if (sc->sc_msr != msr || + (sc->sc_lsr & FTDI_LSR_MASK) != (lsr & FTDI_LSR_MASK)) { + DPRINTF(("uftdi_read: status change msr=0x%02x(0x%02x) " + "lsr=0x%02x(0x%02x)\n", msr, sc->sc_msr, + lsr, sc->sc_lsr)); + sc->sc_msr = msr; + sc->sc_lsr = lsr; + ucom_status_change(&sc->sc_ucom); + } + + if (l > 2) + ucomrxchars(&sc->sc_ucom, (*ptr) + 2, l - 2); + *ptr += l; + *count -= l; + } +} + +static size_t +uftdi_write(void *vsc, int portno, struct tty *tp, u_char *to, u_int32_t count) +{ + struct uftdi_softc *sc = vsc; + size_t l; + + DPRINTFN(10,("uftdi_write: sc=%p, port=%d tp=%p, count=%u\n", + vsc, portno, tp, count)); + + /* Leave space for the length tag. */ + l = ttydisc_getc(tp, to + sc->sc_hdrlen, count - sc->sc_hdrlen); + if (l == 0) + return (0); + + /* Make length tag. */ + if (sc->sc_hdrlen > 0) + *to = FTDI_OUT_TAG(l, portno); + + return (l + sc->sc_hdrlen); +} + +static void +uftdi_set(void *vsc, int portno, int reg, int onoff) +{ + struct uftdi_softc *sc = vsc; + struct ucom_softc *ucom = vsc; + usb_device_request_t req; + int ctl; + + DPRINTF(("uftdi_set: sc=%p, port=%d reg=%d onoff=%d\n", vsc, portno, + reg, onoff)); + + switch (reg) { + case UCOM_SET_DTR: + ctl = onoff ? FTDI_SIO_SET_DTR_HIGH : FTDI_SIO_SET_DTR_LOW; + break; + case UCOM_SET_RTS: + ctl = onoff ? FTDI_SIO_SET_RTS_HIGH : FTDI_SIO_SET_RTS_LOW; + break; + case UCOM_SET_BREAK: + uftdi_break(sc, portno, onoff); + return; + default: + return; + } + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_MODEM_CTRL; + USETW(req.wValue, ctl); + USETW(req.wIndex, portno); + USETW(req.wLength, 0); + DPRINTFN(2,("uftdi_set: reqtype=0x%02x req=0x%02x value=0x%04x " + "index=0x%04x len=%d\n", req.bmRequestType, req.bRequest, + UGETW(req.wValue), UGETW(req.wIndex), UGETW(req.wLength))); + (void)usbd_do_request(ucom->sc_udev, &req, NULL); +} + +static int +uftdi_param(void *vsc, int portno, struct termios *t) +{ + struct uftdi_softc *sc = vsc; + struct ucom_softc *ucom = &sc->sc_ucom; + usb_device_request_t req; + usbd_status err; + int rate=0, data, flow; + + DPRINTF(("uftdi_param: sc=%p\n", sc)); + + if (ucom->sc_dying) + return (EIO); + + switch (sc->sc_type) { + case UFTDI_TYPE_SIO: + switch (t->c_ospeed) { + case 300: rate = ftdi_sio_b300; break; + case 600: rate = ftdi_sio_b600; break; + case 1200: rate = ftdi_sio_b1200; break; + case 2400: rate = ftdi_sio_b2400; break; + case 4800: rate = ftdi_sio_b4800; break; + case 9600: rate = ftdi_sio_b9600; break; + case 19200: rate = ftdi_sio_b19200; break; + case 38400: rate = ftdi_sio_b38400; break; + case 57600: rate = ftdi_sio_b57600; break; + case 115200: rate = ftdi_sio_b115200; break; + default: + return (EINVAL); + } + break; + + case UFTDI_TYPE_8U232AM: + if (uftdi_8u232am_getrate(t->c_ospeed, &rate) == -1) + return (EINVAL); + break; + } + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_SET_BAUD_RATE; + USETW(req.wValue, rate); + USETW(req.wIndex, portno); + USETW(req.wLength, 0); + DPRINTFN(2,("uftdi_param: reqtype=0x%02x req=0x%02x value=0x%04x " + "index=0x%04x len=%d\n", req.bmRequestType, req.bRequest, + UGETW(req.wValue), UGETW(req.wIndex), UGETW(req.wLength))); + err = usbd_do_request(ucom->sc_udev, &req, NULL); + if (err) + return (EIO); + + if (ISSET(t->c_cflag, CSTOPB)) + data = FTDI_SIO_SET_DATA_STOP_BITS_2; + else + data = FTDI_SIO_SET_DATA_STOP_BITS_1; + if (ISSET(t->c_cflag, PARENB)) { + if (ISSET(t->c_cflag, PARODD)) + data |= FTDI_SIO_SET_DATA_PARITY_ODD; + else + data |= FTDI_SIO_SET_DATA_PARITY_EVEN; + } else + data |= FTDI_SIO_SET_DATA_PARITY_NONE; + switch (ISSET(t->c_cflag, CSIZE)) { + case CS5: + data |= FTDI_SIO_SET_DATA_BITS(5); + break; + case CS6: + data |= FTDI_SIO_SET_DATA_BITS(6); + break; + case CS7: + data |= FTDI_SIO_SET_DATA_BITS(7); + break; + case CS8: + data |= FTDI_SIO_SET_DATA_BITS(8); + break; + } + sc->last_lcr = data; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_SET_DATA; + USETW(req.wValue, data); + USETW(req.wIndex, portno); + USETW(req.wLength, 0); + DPRINTFN(2,("uftdi_param: reqtype=0x%02x req=0x%02x value=0x%04x " + "index=0x%04x len=%d\n", req.bmRequestType, req.bRequest, + UGETW(req.wValue), UGETW(req.wIndex), UGETW(req.wLength))); + err = usbd_do_request(ucom->sc_udev, &req, NULL); + if (err) + return (EIO); + + if (ISSET(t->c_cflag, CRTSCTS)) { + flow = FTDI_SIO_RTS_CTS_HS; + USETW(req.wValue, 0); + } else if (ISSET(t->c_iflag, IXON|IXOFF)) { + flow = FTDI_SIO_XON_XOFF_HS; + USETW2(req.wValue, t->c_cc[VSTOP], t->c_cc[VSTART]); + } else { + flow = FTDI_SIO_DISABLE_FLOW_CTRL; + USETW(req.wValue, 0); + } + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_SET_FLOW_CTRL; + USETW2(req.wIndex, flow, portno); + USETW(req.wLength, 0); + err = usbd_do_request(ucom->sc_udev, &req, NULL); + if (err) + return (EIO); + + return (0); +} + +void +uftdi_get_status(void *vsc, int portno, u_char *lsr, u_char *msr) +{ + struct uftdi_softc *sc = vsc; + + DPRINTF(("uftdi_status: msr=0x%02x lsr=0x%02x\n", + sc->sc_msr, sc->sc_lsr)); + + if (msr != NULL) + *msr = sc->sc_msr; + if (lsr != NULL) + *lsr = sc->sc_lsr; +} + +void +uftdi_break(void *vsc, int portno, int onoff) +{ + struct uftdi_softc *sc = vsc; + struct ucom_softc *ucom = vsc; + + usb_device_request_t req; + int data; + + DPRINTF(("uftdi_break: sc=%p, port=%d onoff=%d\n", vsc, portno, + onoff)); + + if (onoff) { + data = sc->last_lcr | FTDI_SIO_SET_BREAK; + } else { + data = sc->last_lcr; + } + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = FTDI_SIO_SET_DATA; + USETW(req.wValue, data); + USETW(req.wIndex, portno); + USETW(req.wLength, 0); + (void)usbd_do_request(ucom->sc_udev, &req, NULL); +} + +static int +uftdi_8u232am_getrate(speed_t speed, int *rate) +{ + /* Table of the nearest even powers-of-2 for values 0..15. */ + static const unsigned char roundoff[16] = { + 0, 2, 2, 4, 4, 4, 8, 8, + 8, 8, 8, 8, 16, 16, 16, 16, + }; + + unsigned int d, freq; + int result; + + if (speed <= 0) + return (-1); + + /* Special cases for 2M and 3M. */ + if (speed >= 3000000 * 100 / 103 && + speed <= 3000000 * 100 / 97) { + result = 0; + goto done; + } + if (speed >= 2000000 * 100 / 103 && + speed <= 2000000 * 100 / 97) { + result = 1; + goto done; + } + + d = (FTDI_8U232AM_FREQ << 4) / speed; + d = (d & ~15) + roundoff[d & 15]; + + if (d < FTDI_8U232AM_MIN_DIV) + d = FTDI_8U232AM_MIN_DIV; + else if (d > FTDI_8U232AM_MAX_DIV) + d = FTDI_8U232AM_MAX_DIV; + + /* + * Calculate the frequency needed for d to exactly divide down + * to our target speed, and check that the actual frequency is + * within 3% of this. + */ + freq = speed * d; + if (freq < (quad_t)(FTDI_8U232AM_FREQ << 4) * 100 / 103 || + freq > (quad_t)(FTDI_8U232AM_FREQ << 4) * 100 / 97) + return (-1); + + /* + * Pack the divisor into the resultant value. The lower + * 14-bits hold the integral part, while the upper 2 bits + * encode the fractional component: either 0, 0.5, 0.25, or + * 0.125. + */ + result = d >> 4; + if (d & 8) + result |= 0x4000; + else if (d & 4) + result |= 0x8000; + else if (d & 2) + result |= 0xc000; + +done: + *rate = result; + return (0); +} + +static device_method_t uftdi_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, uftdi_match), + DEVMETHOD(device_attach, uftdi_attach), + DEVMETHOD(device_detach, uftdi_detach), + + { 0, 0 } +}; + +static driver_t uftdi_driver = { + "ucom", + uftdi_methods, + sizeof (struct uftdi_softc) +}; + +DRIVER_MODULE(uftdi, uhub, uftdi_driver, ucom_devclass, usbd_driver_load, 0); +MODULE_DEPEND(uftdi, usb, 1, 1, 1); +MODULE_DEPEND(uftdi, ucom,UCOM_MINVER, UCOM_PREFVER, UCOM_MAXVER); diff --git a/sys/legacy/dev/usb/uftdireg.h b/sys/legacy/dev/usb/uftdireg.h new file mode 100644 index 0000000..78c9349 --- /dev/null +++ b/sys/legacy/dev/usb/uftdireg.h @@ -0,0 +1,338 @@ +/* $NetBSD: uftdireg.h,v 1.6 2002/07/11 21:14:28 augustss Exp $ */ +/* $FreeBSD$ */ + +/* + * Definitions for the FTDI USB Single Port Serial Converter - + * known as FTDI_SIO (Serial Input/Output application of the chipset) + * + * The device is based on the FTDI FT8U100AX chip. It has a DB25 on one side, + * USB on the other. + * + * Thanx to FTDI (http://www.ftdi.co.uk) for so kindly providing details + * of the protocol required to talk to the device and ongoing assistence + * during development. + * + * Bill Ryder - bryder@sgi.com of Silicon Graphics, Inc. is the original + * author of this file. + */ +/* Modified by Lennart Augustsson */ + +/* Vendor Request Interface */ +#define FTDI_SIO_RESET 0 /* Reset the port */ +#define FTDI_SIO_MODEM_CTRL 1 /* Set the modem control register */ +#define FTDI_SIO_SET_FLOW_CTRL 2 /* Set flow control register */ +#define FTDI_SIO_SET_BAUD_RATE 3 /* Set baud rate */ +#define FTDI_SIO_SET_DATA 4 /* Set the data characteristics of the port */ +#define FTDI_SIO_GET_STATUS 5 /* Retrieve current value of status reg */ +#define FTDI_SIO_SET_EVENT_CHAR 6 /* Set the event character */ +#define FTDI_SIO_SET_ERROR_CHAR 7 /* Set the error character */ + +/* Port Identifier Table */ +#define FTDI_PIT_DEFAULT 0 /* SIOA */ +#define FTDI_PIT_SIOA 1 /* SIOA */ +#define FTDI_PIT_SIOB 2 /* SIOB */ +#define FTDI_PIT_PARALLEL 3 /* Parallel */ + +enum uftdi_type { + UFTDI_TYPE_SIO, + UFTDI_TYPE_8U232AM +}; + +/* + * BmRequestType: 0100 0000B + * bRequest: FTDI_SIO_RESET + * wValue: Control Value + * 0 = Reset SIO + * 1 = Purge RX buffer + * 2 = Purge TX buffer + * wIndex: Port + * wLength: 0 + * Data: None + * + * The Reset SIO command has this effect: + * + * Sets flow control set to 'none' + * Event char = 0x0d + * Event trigger = disabled + * Purge RX buffer + * Purge TX buffer + * Clear DTR + * Clear RTS + * baud and data format not reset + * + * The Purge RX and TX buffer commands affect nothing except the buffers + * + */ +/* FTDI_SIO_RESET */ +#define FTDI_SIO_RESET_SIO 0 +#define FTDI_SIO_RESET_PURGE_RX 1 +#define FTDI_SIO_RESET_PURGE_TX 2 + + +/* + * BmRequestType: 0100 0000B + * bRequest: FTDI_SIO_SET_BAUDRATE + * wValue: BaudRate value - see below + * wIndex: Port + * wLength: 0 + * Data: None + */ +/* FTDI_SIO_SET_BAUDRATE */ +enum { + ftdi_sio_b300 = 0, + ftdi_sio_b600 = 1, + ftdi_sio_b1200 = 2, + ftdi_sio_b2400 = 3, + ftdi_sio_b4800 = 4, + ftdi_sio_b9600 = 5, + ftdi_sio_b19200 = 6, + ftdi_sio_b38400 = 7, + ftdi_sio_b57600 = 8, + ftdi_sio_b115200 = 9 +}; + +#define FTDI_8U232AM_FREQ 3000000 + +/* Bounds for normal divisors as 4-bit fixed precision ints. */ +#define FTDI_8U232AM_MIN_DIV 0x20 +#define FTDI_8U232AM_MAX_DIV 0x3fff8 + +/* + * BmRequestType: 0100 0000B + * bRequest: FTDI_SIO_SET_DATA + * wValue: Data characteristics (see below) + * wIndex: Port + * wLength: 0 + * Data: No + * + * Data characteristics + * + * B0..7 Number of data bits + * B8..10 Parity + * 0 = None + * 1 = Odd + * 2 = Even + * 3 = Mark + * 4 = Space + * B11..13 Stop Bits + * 0 = 1 + * 1 = 1.5 + * 2 = 2 + * B14..15 Reserved + * + */ +/* FTDI_SIO_SET_DATA */ +#define FTDI_SIO_SET_DATA_BITS(n) (n) +#define FTDI_SIO_SET_DATA_PARITY_NONE (0x0 << 8) +#define FTDI_SIO_SET_DATA_PARITY_ODD (0x1 << 8) +#define FTDI_SIO_SET_DATA_PARITY_EVEN (0x2 << 8) +#define FTDI_SIO_SET_DATA_PARITY_MARK (0x3 << 8) +#define FTDI_SIO_SET_DATA_PARITY_SPACE (0x4 << 8) +#define FTDI_SIO_SET_DATA_STOP_BITS_1 (0x0 << 11) +#define FTDI_SIO_SET_DATA_STOP_BITS_15 (0x1 << 11) +#define FTDI_SIO_SET_DATA_STOP_BITS_2 (0x2 << 11) +#define FTDI_SIO_SET_BREAK (0x1 << 14) + + +/* + * BmRequestType: 0100 0000B + * bRequest: FTDI_SIO_MODEM_CTRL + * wValue: ControlValue (see below) + * wIndex: Port + * wLength: 0 + * Data: None + * + * NOTE: If the device is in RTS/CTS flow control, the RTS set by this + * command will be IGNORED without an error being returned + * Also - you can not set DTR and RTS with one control message + * + * ControlValue + * B0 DTR state + * 0 = reset + * 1 = set + * B1 RTS state + * 0 = reset + * 1 = set + * B2..7 Reserved + * B8 DTR state enable + * 0 = ignore + * 1 = use DTR state + * B9 RTS state enable + * 0 = ignore + * 1 = use RTS state + * B10..15 Reserved + */ +/* FTDI_SIO_MODEM_CTRL */ +#define FTDI_SIO_SET_DTR_MASK 0x1 +#define FTDI_SIO_SET_DTR_HIGH (1 | ( FTDI_SIO_SET_DTR_MASK << 8)) +#define FTDI_SIO_SET_DTR_LOW (0 | ( FTDI_SIO_SET_DTR_MASK << 8)) +#define FTDI_SIO_SET_RTS_MASK 0x2 +#define FTDI_SIO_SET_RTS_HIGH (2 | ( FTDI_SIO_SET_RTS_MASK << 8)) +#define FTDI_SIO_SET_RTS_LOW (0 | ( FTDI_SIO_SET_RTS_MASK << 8)) + + +/* + * BmRequestType: 0100 0000b + * bRequest: FTDI_SIO_SET_FLOW_CTRL + * wValue: Xoff/Xon + * wIndex: Protocol/Port - hIndex is protocl / lIndex is port + * wLength: 0 + * Data: None + * + * hIndex protocol is: + * B0 Output handshaking using RTS/CTS + * 0 = disabled + * 1 = enabled + * B1 Output handshaking using DTR/DSR + * 0 = disabled + * 1 = enabled + * B2 Xon/Xoff handshaking + * 0 = disabled + * 1 = enabled + * + * A value of zero in the hIndex field disables handshaking + * + * If Xon/Xoff handshaking is specified, the hValue field should contain the + * XOFF character and the lValue field contains the XON character. + */ +/* FTDI_SIO_SET_FLOW_CTRL */ +#define FTDI_SIO_DISABLE_FLOW_CTRL 0x0 +#define FTDI_SIO_RTS_CTS_HS 0x1 +#define FTDI_SIO_DTR_DSR_HS 0x2 +#define FTDI_SIO_XON_XOFF_HS 0x4 + + +/* + * BmRequestType: 0100 0000b + * bRequest: FTDI_SIO_SET_EVENT_CHAR + * wValue: Event Char + * wIndex: Port + * wLength: 0 + * Data: None + * + * wValue: + * B0..7 Event Character + * B8 Event Character Processing + * 0 = disabled + * 1 = enabled + * B9..15 Reserved + * + * FTDI_SIO_SET_EVENT_CHAR + * + * Set the special event character for the specified communications port. + * If the device sees this character it will immediately return the + * data read so far - rather than wait 40ms or until 62 bytes are read + * which is what normally happens. + */ + + + +/* + * BmRequestType: 0100 0000b + * bRequest: FTDI_SIO_SET_ERROR_CHAR + * wValue: Error Char + * wIndex: Port + * wLength: 0 + * Data: None + * + * Error Char + * B0..7 Error Character + * B8 Error Character Processing + * 0 = disabled + * 1 = enabled + * B9..15 Reserved + * + * + * FTDI_SIO_SET_ERROR_CHAR + * Set the parity error replacement character for the specified communications + * port. + */ + + +/* + * BmRequestType: 1100 0000b + * bRequest: FTDI_SIO_GET_MODEM_STATUS + * wValue: zero + * wIndex: Port + * wLength: 1 + * Data: Status + * + * One byte of data is returned + * B0..3 0 + * B4 CTS + * 0 = inactive + * 1 = active + * B5 DSR + * 0 = inactive + * 1 = active + * B6 Ring Indicator (RI) + * 0 = inactive + * 1 = active + * B7 Receive Line Signal Detect (RLSD) + * 0 = inactive + * 1 = active + * + * FTDI_SIO_GET_MODEM_STATUS + * Retrieve the current value of the modem status register. + */ +#define FTDI_SIO_CTS_MASK 0x10 +#define FTDI_SIO_DSR_MASK 0x20 +#define FTDI_SIO_RI_MASK 0x40 +#define FTDI_SIO_RLSD_MASK 0x80 + + + +/* + * + * DATA FORMAT + * + * IN Endpoint + * + * The device reserves the first two bytes of data on this endpoint to contain + * the current values of the modem and line status registers. In the absence of + * data, the device generates a message consisting of these two status bytes + * every 40 ms. + * + * Byte 0: Modem Status + * NOTE: 4 upper bits have same layout as the MSR register in a 16550 + * + * Offset Description + * B0..3 Port + * B4 Clear to Send (CTS) + * B5 Data Set Ready (DSR) + * B6 Ring Indicator (RI) + * B7 Receive Line Signal Detect (RLSD) + * + * Byte 1: Line Status + * NOTE: same layout as the LSR register in a 16550 + * + * Offset Description + * B0 Data Ready (DR) + * B1 Overrun Error (OE) + * B2 Parity Error (PE) + * B3 Framing Error (FE) + * B4 Break Interrupt (BI) + * B5 Transmitter Holding Register (THRE) + * B6 Transmitter Empty (TEMT) + * B7 Error in RCVR FIFO + * + * + * OUT Endpoint + * + * This device reserves the first bytes of data on this endpoint contain the + * length and port identifier of the message. For the FTDI USB Serial converter + * the port identifier is always 1. + * + * Byte 0: Port & length + * + * Offset Description + * B0..1 Port + * B2..7 Length of message - (not including Byte 0) + * + */ +#define FTDI_PORT_MASK 0x0f +#define FTDI_MSR_MASK 0xf0 +#define FTDI_GET_MSR(p) (((p)[0]) & FTDI_MSR_MASK) +#define FTDI_GET_LSR(p) ((p)[1]) +#define FTDI_LSR_MASK (~0x60) /* interesting bits */ +#define FTDI_OUT_TAG(len, port) (((len) << 2) | (port)) diff --git a/sys/legacy/dev/usb/ugen.c b/sys/legacy/dev/usb/ugen.c new file mode 100644 index 0000000..4f03cfb --- /dev/null +++ b/sys/legacy/dev/usb/ugen.c @@ -0,0 +1,1590 @@ +/* $NetBSD: ugen.c,v 1.79 2006/03/01 12:38:13 yamt Exp $ */ + +/* Also already merged from NetBSD: + * $NetBSD: ugen.c,v 1.61 2002/09/23 05:51:20 simonb Exp $ + * $NetBSD: ugen.c,v 1.64 2003/06/28 14:21:46 darrenr Exp $ + * $NetBSD: ugen.c,v 1.65 2003/06/29 22:30:56 fvdl Exp $ + * $NetBSD: ugen.c,v 1.68 2004/06/23 02:30:52 mycroft Exp $ + */ + +#include <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 <sys/param.h> +#include <sys/systm.h> +#include <sys/clist.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/ioccom.h> +#include <sys/conf.h> +#include <sys/fcntl.h> +#include <sys/filio.h> +#include <sys/file.h> +#include <sys/selinfo.h> +#include <sys/poll.h> +#include <sys/sysctl.h> +#include <sys/uio.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> + +#ifdef USB_DEBUG +#define DPRINTF(x) if (ugendebug) printf x +#define DPRINTFN(n,x) if (ugendebug>(n)) printf x +int ugendebug = 0; +SYSCTL_NODE(_hw_usb, OID_AUTO, ugen, CTLFLAG_RW, 0, "USB ugen"); +SYSCTL_INT(_hw_usb_ugen, OID_AUTO, debug, CTLFLAG_RW, + &ugendebug, 0, "ugen debug level"); +#else +#define DPRINTF(x) +#define DPRINTFN(n,x) +#endif + +#define UGEN_CHUNK 128 /* chunk size for read */ +#define UGEN_IBSIZE 1020 /* buffer size */ +#define UGEN_BBSIZE 1024 + +#define UGEN_NISOFRAMES 500 /* 0.5 seconds worth */ +#define UGEN_NISOREQS 6 /* number of outstanding xfer requests */ +#define UGEN_NISORFRMS 4 /* number of frames (miliseconds) per req */ + +struct ugen_endpoint { + struct ugen_softc *sc; + struct cdev *dev; + usb_endpoint_descriptor_t *edesc; + usbd_interface_handle iface; + int state; +#define UGEN_ASLP 0x02 /* waiting for data */ +#define UGEN_SHORT_OK 0x04 /* short xfers are OK */ + usbd_pipe_handle pipeh; + struct clist q; + struct selinfo rsel; + u_char *ibuf; /* start of buffer (circular for isoc) */ + u_char *fill; /* location for input (isoc) */ + u_char *limit; /* end of circular buffer (isoc) */ + u_char *cur; /* current read location (isoc) */ + u_int32_t timeout; + struct isoreq { + struct ugen_endpoint *sce; + usbd_xfer_handle xfer; + void *dmabuf; + u_int16_t sizes[UGEN_NISORFRMS]; + } isoreqs[UGEN_NISOREQS]; +}; + +struct ugen_softc { + device_t sc_dev; /* base device */ + usbd_device_handle sc_udev; + struct cdev *dev; + + char sc_is_open[USB_MAX_ENDPOINTS]; + struct ugen_endpoint sc_endpoints[USB_MAX_ENDPOINTS][2]; +#define OUT 0 +#define IN 1 + +#define UGEN_DEV_REF(dev, sc) \ + if ((sc)->sc_dying || dev_refthread(dev) == NULL) \ + return (ENXIO) +#define UGEN_DEV_RELE(dev, sc) \ + dev_relthread(dev) +#define UGEN_DEV_OPEN(dev, sc) \ + /* handled by dev layer */ +#define UGEN_DEV_CLOSE(dev, sc) \ + /* handled by dev layer */ + u_char sc_dying; +}; + +d_open_t ugenopen; +d_close_t ugenclose; +d_read_t ugenread; +d_write_t ugenwrite; +d_ioctl_t ugenioctl; +d_poll_t ugenpoll; +d_purge_t ugenpurge; + +static struct cdevsw ugenctl_cdevsw = { + .d_version = D_VERSION, + .d_flags = D_NEEDGIANT, + .d_open = ugenopen, + .d_close = ugenclose, + .d_ioctl = ugenioctl, + .d_purge = ugenpurge, + .d_name = "ugenctl", +}; + +static struct cdevsw ugen_cdevsw = { + .d_version = D_VERSION, + .d_flags = D_NEEDGIANT, + .d_open = ugenopen, + .d_close = ugenclose, + .d_read = ugenread, + .d_write = ugenwrite, + .d_ioctl = ugenioctl, + .d_poll = ugenpoll, + .d_purge = ugenpurge, + .d_name = "ugen", +}; + +static void ugenintr(usbd_xfer_handle xfer, usbd_private_handle addr, + usbd_status status); +static void ugen_isoc_rintr(usbd_xfer_handle xfer, usbd_private_handle addr, + usbd_status status); +static int ugen_do_read(struct ugen_softc *, int, struct uio *, int); +static int ugen_do_write(struct ugen_softc *, int, struct uio *, int); +static int ugen_do_ioctl(struct ugen_softc *, int, u_long, + caddr_t, int, struct thread *); +static void ugen_make_devnodes(struct ugen_softc *sc); +static void ugen_destroy_devnodes(struct ugen_softc *sc); +static int ugen_set_config(struct ugen_softc *sc, int configno); +static usb_config_descriptor_t *ugen_get_cdesc(struct ugen_softc *sc, + int index, int *lenp); +static usbd_status ugen_set_interface(struct ugen_softc *, int, int); +static int ugen_get_alt_index(struct ugen_softc *sc, int ifaceidx); + +#define UGENUNIT(n) ((dev2unit(n) >> 4) & 0xf) +#define UGENENDPOINT(n) (dev2unit(n) & 0xf) +#define UGENMINOR(u, e) (((u) << 4) | (e)) + +static device_probe_t ugen_match; +static device_attach_t ugen_attach; +static device_detach_t ugen_detach; + +static devclass_t ugen_devclass; + +static device_method_t ugen_methods[] = { + DEVMETHOD(device_probe, ugen_match), + DEVMETHOD(device_attach, ugen_attach), + DEVMETHOD(device_detach, ugen_detach), + {0,0}, + {0,0} +}; + +static driver_t ugen_driver = { + "ugen", + ugen_methods, + sizeof(struct ugen_softc) +}; + +MODULE_DEPEND(ugen, usb, 1, 1, 1); + +static int +ugen_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + +#if 0 + if (uaa->matchlvl) + return (uaa->matchlvl); +#endif + if (uaa->usegeneric) + return (UMATCH_GENERIC); + else + return (UMATCH_NONE); +} + +static int +ugen_attach(device_t self) +{ + struct ugen_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + usbd_device_handle udev; + usbd_status err; + int conf; + + sc->sc_dev = self; + sc->sc_udev = udev = uaa->device; + + memset(sc->sc_endpoints, 0, sizeof sc->sc_endpoints); + + /* First set configuration index 0, the default one for ugen. */ + err = usbd_set_config_index(udev, 0, 0); + if (err) { + printf("%s: setting configuration index 0 failed\n", + device_get_nameunit(sc->sc_dev)); + sc->sc_dying = 1; + return (ENXIO); + } + conf = usbd_get_config_descriptor(udev)->bConfigurationValue; + + /* Set up all the local state for this configuration. */ + err = ugen_set_config(sc, conf); + if (err) { + printf("%s: setting configuration %d failed\n", + device_get_nameunit(sc->sc_dev), conf); + sc->sc_dying = 1; + return (ENXIO); + } + + /* the main device, ctrl endpoint */ + sc->dev = make_dev(&ugenctl_cdevsw, + UGENMINOR(device_get_unit(sc->sc_dev), 0), UID_ROOT, GID_OPERATOR, 0644, + "%s", device_get_nameunit(sc->sc_dev)); + ugen_make_devnodes(sc); + usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, sc->sc_udev, sc->sc_dev); + return (0); +} + +static void +ugen_make_devnodes(struct ugen_softc *sc) +{ + int endptno; + struct cdev *dev; + + for (endptno = 1; endptno < USB_MAX_ENDPOINTS; endptno++) { + if (sc->sc_endpoints[endptno][IN].sc != NULL || + sc->sc_endpoints[endptno][OUT].sc != NULL ) { + /* endpt can be 0x81 and 0x01, representing + * endpoint address 0x01 and IN/OUT directions. + * We map both endpts to the same device, + * IN is reading from it, OUT is writing to it. + * + * In the if clause above we check whether one + * of the structs is populated. + */ + dev = make_dev(&ugen_cdevsw, + UGENMINOR(device_get_unit(sc->sc_dev), endptno), + UID_ROOT, GID_OPERATOR, 0644, + "%s.%d", + device_get_nameunit(sc->sc_dev), endptno); + dev_depends(sc->dev, dev); + if (sc->sc_endpoints[endptno][IN].sc != NULL) + sc->sc_endpoints[endptno][IN].dev = dev; + if (sc->sc_endpoints[endptno][OUT].sc != NULL) + sc->sc_endpoints[endptno][OUT].dev = dev; + } + } +} + +static void +ugen_destroy_devnodes(struct ugen_softc *sc) +{ + int endptno, prev_sc_dying; + struct cdev *dev; + + prev_sc_dying = sc->sc_dying; + sc->sc_dying = 1; + /* destroy all devices for the other (existing) endpoints as well */ + for (endptno = 1; endptno < USB_MAX_ENDPOINTS; endptno++) { + if (sc->sc_endpoints[endptno][IN].sc != NULL || + sc->sc_endpoints[endptno][OUT].sc != NULL ) { + /* endpt can be 0x81 and 0x01, representing + * endpoint address 0x01 and IN/OUT directions. + * We map both endpoint addresses to the same device, + * IN is reading from it, OUT is writing to it. + * + * In the if clause above we check whether one + * of the structs is populated. + */ + if (sc->sc_endpoints[endptno][IN].sc != NULL) + dev = sc->sc_endpoints[endptno][IN].dev; + else + dev = sc->sc_endpoints[endptno][OUT].dev; + + KASSERT(dev != NULL, + ("ugen_destroy_devnodes: NULL dev")); + if(dev != NULL) + destroy_dev(dev); + + sc->sc_endpoints[endptno][IN].sc = NULL; + sc->sc_endpoints[endptno][OUT].sc = NULL; + } + } + sc->sc_dying = prev_sc_dying; +} + +static int +ugen_set_config(struct ugen_softc *sc, int configno) +{ + usbd_device_handle dev = sc->sc_udev; + usbd_interface_handle iface; + usb_endpoint_descriptor_t *ed; + struct ugen_endpoint *sce, **sce_cache, ***sce_cache_arr; + u_int8_t niface, niface_cache, nendpt, *nendpt_cache; + int ifaceno, endptno, endpt; + usbd_status err; + int dir; + + DPRINTFN(1,("ugen_set_config: %s to configno %d, sc=%p\n", + device_get_nameunit(sc->sc_dev), configno, sc)); + + /* We start at 1, not 0, because we don't care whether the + * control endpoint is open or not. It is always present. + */ + for (endptno = 1; endptno < USB_MAX_ENDPOINTS; endptno++) + if (sc->sc_is_open[endptno]) { + DPRINTFN(1, + ("ugen_set_config: %s - endpoint %d is open\n", + device_get_nameunit(sc->sc_dev), endptno)); + return (USBD_IN_USE); + } + + err = usbd_interface_count(dev, &niface); + if (err) + return (err); + /* store an array of endpoint descriptors to clear if the configuration + * change succeeds - these aren't available afterwards */ + nendpt_cache = malloc(sizeof(u_int8_t) * niface, M_TEMP, M_WAITOK | + M_ZERO); + sce_cache_arr = malloc(sizeof(struct ugen_endpoint **) * niface, M_TEMP, + M_WAITOK | M_ZERO); + niface_cache = niface; + + for (ifaceno = 0; ifaceno < niface; ifaceno++) { + DPRINTFN(1,("ugen_set_config: ifaceno %d\n", ifaceno)); + err = usbd_device2interface_handle(dev, ifaceno, &iface); + if (err) + panic("ugen_set_config: can't obtain interface handle"); + err = usbd_endpoint_count(iface, &nendpt); + if (err) + panic("ugen_set_config: endpoint count failed"); + + /* store endpoint descriptors for each interface */ + nendpt_cache[ifaceno] = nendpt; + sce_cache = malloc(sizeof(struct ugen_endpoint *) * nendpt, M_TEMP, + M_WAITOK); + sce_cache_arr[ifaceno] = sce_cache; + + for (endptno = 0; endptno < nendpt; endptno++) { + ed = usbd_interface2endpoint_descriptor(iface,endptno); + endpt = ed->bEndpointAddress; + dir = UE_GET_DIR(endpt) == UE_DIR_IN ? IN : OUT; + sce_cache[endptno] = &sc->sc_endpoints[UE_GET_ADDR(endpt)][dir]; + } + } + + /* Avoid setting the current value. */ + if (usbd_get_config_descriptor(dev)->bConfigurationValue != configno) { + /* attempt to perform the configuration change */ + err = usbd_set_config_no(dev, configno, 1); + if (err) { + for(ifaceno = 0; ifaceno < niface_cache; ifaceno++) + free(sce_cache_arr[ifaceno], M_TEMP); + free(sce_cache_arr, M_TEMP); + free(nendpt_cache, M_TEMP); + return (err); + } + } + + ugen_destroy_devnodes(sc); + + /* now we can clear the old interface's ugen_endpoints */ + for(ifaceno = 0; ifaceno < niface_cache; ifaceno++) { + sce_cache = sce_cache_arr[ifaceno]; + for(endptno = 0; endptno < nendpt_cache[ifaceno]; endptno++) { + sce = sce_cache[endptno]; + sce->sc = 0; + sce->edesc = 0; + sce->iface = 0; + } + } + + /* and free the cache storing them */ + for(ifaceno = 0; ifaceno < niface_cache; ifaceno++) + free(sce_cache_arr[ifaceno], M_TEMP); + free(sce_cache_arr, M_TEMP); + free(nendpt_cache, M_TEMP); + + /* no endpoints if the device is in the unconfigured state */ + if (configno != USB_UNCONFIG_NO) + { + /* set the new configuration's ugen_endpoints */ + err = usbd_interface_count(dev, &niface); + if (err) + panic("ugen_set_config: interface count failed"); + + memset(sc->sc_endpoints, 0, sizeof sc->sc_endpoints); + for (ifaceno = 0; ifaceno < niface; ifaceno++) { + DPRINTFN(1,("ugen_set_config: ifaceno %d\n", ifaceno)); + err = usbd_device2interface_handle(dev, ifaceno, &iface); + if (err) + panic("ugen_set_config: can't obtain interface handle"); + err = usbd_endpoint_count(iface, &nendpt); + if (err) + panic("ugen_set_config: endpoint count failed"); + for (endptno = 0; endptno < nendpt; endptno++) { + ed = usbd_interface2endpoint_descriptor(iface,endptno); + endpt = ed->bEndpointAddress; + dir = UE_GET_DIR(endpt) == UE_DIR_IN ? IN : OUT; + sce = &sc->sc_endpoints[UE_GET_ADDR(endpt)][dir]; + DPRINTFN(1,("ugen_set_config: endptno %d, endpt=0x%02x" + "(%d,%d), sce=%p\n", + endptno, endpt, UE_GET_ADDR(endpt), + UE_GET_DIR(endpt), sce)); + sce->sc = sc; + sce->edesc = ed; + sce->iface = iface; + } + } + } + + return (USBD_NORMAL_COMPLETION); +} + +int +ugenopen(struct cdev *dev, int flag, int mode, struct thread *p) +{ + struct ugen_softc *sc; + int unit = UGENUNIT(dev); + int endpt = UGENENDPOINT(dev); + usb_endpoint_descriptor_t *edesc; + struct ugen_endpoint *sce; + int dir, isize; + usbd_status err; + usbd_xfer_handle xfer; + void *buf; + int i, j; + + sc = devclass_get_softc(ugen_devclass, unit); + if (sc == NULL) + return (ENXIO); + + DPRINTFN(5, ("ugenopen: flag=%d, mode=%d, unit=%d endpt=%d\n", + flag, mode, unit, endpt)); + + if (sc == NULL || sc->sc_dying) + return (ENXIO); + + if (sc->sc_is_open[endpt]) + return (EBUSY); + + if (endpt == USB_CONTROL_ENDPOINT) { + sc->sc_is_open[USB_CONTROL_ENDPOINT] = 1; + UGEN_DEV_OPEN(dev, sc); + return (0); + } + + /* Make sure there are pipes for all directions. */ + for (dir = OUT; dir <= IN; dir++) { + if (flag & (dir == OUT ? FWRITE : FREAD)) { + sce = &sc->sc_endpoints[endpt][dir]; + if (sce->edesc == 0) + return (ENXIO); + } + } + + /* Actually open the pipes. */ + /* XXX Should back out properly if it fails. */ + for (dir = OUT; dir <= IN; dir++) { + if (!(flag & (dir == OUT ? FWRITE : FREAD))) + continue; + sce = &sc->sc_endpoints[endpt][dir]; + sce->state = 0; + sce->timeout = USBD_NO_TIMEOUT; + DPRINTFN(5, ("ugenopen: sc=%p, endpt=%d, dir=%d, sce=%p\n", + sc, endpt, dir, sce)); + edesc = sce->edesc; + switch (edesc->bmAttributes & UE_XFERTYPE) { + case UE_INTERRUPT: + if (dir == OUT) { + err = usbd_open_pipe(sce->iface, + edesc->bEndpointAddress, 0, &sce->pipeh); + if (err) + return (EIO); + break; + } + isize = UGETW(edesc->wMaxPacketSize); + if (isize == 0) /* shouldn't happen */ + return (EINVAL); + sce->ibuf = malloc(isize, M_USBDEV, M_WAITOK); + DPRINTFN(5, ("ugenopen: intr endpt=%d,isize=%d\n", + endpt, isize)); + if ((clist_alloc_cblocks(&sce->q, UGEN_IBSIZE, + UGEN_IBSIZE), 0) == -1) + return (ENOMEM); + err = usbd_open_pipe_intr(sce->iface, + edesc->bEndpointAddress, + USBD_SHORT_XFER_OK, &sce->pipeh, sce, + sce->ibuf, isize, ugenintr, + USBD_DEFAULT_INTERVAL); + if (err) { + free(sce->ibuf, M_USBDEV); + clist_free_cblocks(&sce->q); + return (EIO); + } + DPRINTFN(5, ("ugenopen: interrupt open done\n")); + break; + case UE_BULK: + err = usbd_open_pipe(sce->iface, + edesc->bEndpointAddress, 0, &sce->pipeh); + if (err) + return (EIO); + break; + case UE_ISOCHRONOUS: + if (dir == OUT) + return (EINVAL); + isize = UGETW(edesc->wMaxPacketSize); + if (isize == 0) /* shouldn't happen */ + return (EINVAL); + sce->ibuf = malloc(isize * UGEN_NISOFRAMES, + M_USBDEV, M_WAITOK); + sce->cur = sce->fill = sce->ibuf; + sce->limit = sce->ibuf + isize * UGEN_NISOFRAMES; + DPRINTFN(5, ("ugenopen: isoc endpt=%d, isize=%d\n", + endpt, isize)); + err = usbd_open_pipe(sce->iface, + edesc->bEndpointAddress, 0, &sce->pipeh); + if (err) { + free(sce->ibuf, M_USBDEV); + return (EIO); + } + for(i = 0; i < UGEN_NISOREQS; ++i) { + sce->isoreqs[i].sce = sce; + xfer = usbd_alloc_xfer(sc->sc_udev); + if (xfer == 0) + goto bad; + sce->isoreqs[i].xfer = xfer; + buf = usbd_alloc_buffer + (xfer, isize * UGEN_NISORFRMS); + if (buf == 0) { + i++; + goto bad; + } + sce->isoreqs[i].dmabuf = buf; + for(j = 0; j < UGEN_NISORFRMS; ++j) + sce->isoreqs[i].sizes[j] = isize; + usbd_setup_isoc_xfer + (xfer, sce->pipeh, &sce->isoreqs[i], + sce->isoreqs[i].sizes, + UGEN_NISORFRMS, USBD_NO_COPY, + ugen_isoc_rintr); + (void)usbd_transfer(xfer); + } + DPRINTFN(5, ("ugenopen: isoc open done\n")); + break; + bad: + while (--i >= 0) /* implicit buffer free */ + usbd_free_xfer(sce->isoreqs[i].xfer); + return (ENOMEM); + case UE_CONTROL: + sce->timeout = USBD_DEFAULT_TIMEOUT; + return (EINVAL); + } + } + sc->sc_is_open[endpt] = 1; + UGEN_DEV_OPEN(dev, sc); + return (0); +} + +int +ugenclose(struct cdev *dev, int flag, int mode, struct thread *p) +{ + int endpt = UGENENDPOINT(dev); + struct ugen_softc *sc; + struct ugen_endpoint *sce; + int dir; + int i; + + sc = devclass_get_softc(ugen_devclass, UGENUNIT(dev)); + + DPRINTFN(5, ("ugenclose: flag=%d, mode=%d, unit=%d, endpt=%d\n", + flag, mode, UGENUNIT(dev), endpt)); + +#ifdef DIAGNOSTIC + if (!sc->sc_is_open[endpt]) { + printf("ugenclose: not open\n"); + return (EINVAL); + } +#endif + + if (endpt == USB_CONTROL_ENDPOINT) { + DPRINTFN(5, ("ugenclose: close control\n")); + sc->sc_is_open[endpt] = 0; + UGEN_DEV_CLOSE(dev, sc); + return (0); + } + + for (dir = OUT; dir <= IN; dir++) { + if (!(flag & (dir == OUT ? FWRITE : FREAD))) + continue; + sce = &sc->sc_endpoints[endpt][dir]; + if (sce->pipeh == NULL) + continue; + DPRINTFN(5, ("ugenclose: endpt=%d dir=%d sce=%p\n", + endpt, dir, sce)); + + usbd_abort_pipe(sce->pipeh); + usbd_close_pipe(sce->pipeh); + sce->pipeh = NULL; + + switch (sce->edesc->bmAttributes & UE_XFERTYPE) { + case UE_INTERRUPT: + ndflush(&sce->q, sce->q.c_cc); + clist_free_cblocks(&sce->q); + break; + case UE_ISOCHRONOUS: + for (i = 0; i < UGEN_NISOREQS; ++i) + usbd_free_xfer(sce->isoreqs[i].xfer); + + default: + break; + } + + if (sce->ibuf != NULL) { + free(sce->ibuf, M_USBDEV); + sce->ibuf = NULL; + clist_free_cblocks(&sce->q); + } + } + sc->sc_is_open[endpt] = 0; + UGEN_DEV_CLOSE(dev, sc); + + return (0); +} + +static int +ugen_do_read(struct ugen_softc *sc, int endpt, struct uio *uio, int flag) +{ + struct ugen_endpoint *sce = &sc->sc_endpoints[endpt][IN]; + u_int32_t n, tn; + char buf[UGEN_BBSIZE]; + usbd_xfer_handle xfer; + usbd_status err; + int s; + int error = 0, doneone = 0; + u_char buffer[UGEN_CHUNK]; + + DPRINTFN(5, ("%s: ugenread: %d\n", device_get_nameunit(sc->sc_dev), endpt)); + + if (sc->sc_dying) + return (EIO); + + if (endpt == USB_CONTROL_ENDPOINT) + return (ENODEV); + +#ifdef DIAGNOSTIC + if (sce->edesc == NULL) { + printf("ugenread: no edesc\n"); + return (EIO); + } + if (sce->pipeh == NULL) { + printf("ugenread: no pipe\n"); + return (EIO); + } +#endif + + switch (sce->edesc->bmAttributes & UE_XFERTYPE) { + case UE_INTERRUPT: + /* Block until activity occurred. */ + s = splusb(); + while (sce->q.c_cc == 0) { + if (flag & O_NONBLOCK) { + splx(s); + return (EWOULDBLOCK); + } + sce->state |= UGEN_ASLP; + DPRINTFN(5, ("ugenread: sleep on %p\n", sce)); + error = tsleep(sce, PZERO | PCATCH, "ugenri", + (sce->timeout * hz + 999) / 1000); + sce->state &= ~UGEN_ASLP; + DPRINTFN(5, ("ugenread: woke, error=%d\n", error)); + if (sc->sc_dying) + error = EIO; + if (error == EAGAIN) { + error = 0; /* timeout, return 0 bytes */ + break; + } + if (error) + break; + } + splx(s); + + /* Transfer as many chunks as possible. */ + while (sce->q.c_cc > 0 && uio->uio_resid > 0 && !error) { + n = min(sce->q.c_cc, uio->uio_resid); + if (n > sizeof(buffer)) + n = sizeof(buffer); + + /* Remove a small chunk from the input queue. */ + q_to_b(&sce->q, buffer, n); + DPRINTFN(5, ("ugenread: got %d chars\n", n)); + + /* Copy the data to the user process. */ + error = uiomove(buffer, n, uio); + if (error) + break; + } + break; + case UE_BULK: + xfer = usbd_alloc_xfer(sc->sc_udev); + if (xfer == 0) + return (ENOMEM); + while ((n = min(UGEN_BBSIZE, uio->uio_resid)) != 0 || + !doneone) { + DPRINTFN(1, ("ugenread: start transfer %d bytes\n",n)); + tn = n; + doneone = 1; + err = usbd_bulk_transfer( + xfer, sce->pipeh, + sce->state & UGEN_SHORT_OK ? + USBD_SHORT_XFER_OK : 0, + sce->timeout, buf, &tn, "ugenrb"); + if (err) { + if (err == USBD_INTERRUPTED) + error = EINTR; + else if (err == USBD_TIMEOUT) + error = ETIMEDOUT; + else + error = EIO; + break; + } + DPRINTFN(1, ("ugenread: got %d bytes\n", tn)); + error = uiomove(buf, tn, uio); + if (error || tn < n) + break; + } + usbd_free_xfer(xfer); + break; + case UE_ISOCHRONOUS: + s = splusb(); + while (sce->cur == sce->fill) { + if (flag & O_NONBLOCK) { + splx(s); + return (EWOULDBLOCK); + } + sce->state |= UGEN_ASLP; + DPRINTFN(5, ("ugenread: sleep on %p\n", sce)); + error = tsleep(sce, PZERO | PCATCH, "ugenri", + (sce->timeout * hz + 999) / 1000); + sce->state &= ~UGEN_ASLP; + DPRINTFN(5, ("ugenread: woke, error=%d\n", error)); + if (sc->sc_dying) + error = EIO; + if (error == EAGAIN) { + error = 0; /* timeout, return 0 bytes */ + break; + } + if (error) + break; + } + + while (sce->cur != sce->fill && uio->uio_resid > 0 && !error) { + if (sce->fill > sce->cur) + n = min(sce->fill - sce->cur, uio->uio_resid); + else + n = min(sce->limit - sce->cur, uio->uio_resid); + + DPRINTFN(5, ("ugenread: isoc got %d chars\n", n)); + + /* Copy the data to the user process. */ + error = uiomove(sce->cur, n, uio); + if (error) + break; + sce->cur += n; + if(sce->cur >= sce->limit) + sce->cur = sce->ibuf; + } + splx(s); + break; + + + default: + return (ENXIO); + } + return (error); +} + +int +ugenread(struct cdev *dev, struct uio *uio, int flag) +{ + int endpt = UGENENDPOINT(dev); + struct ugen_softc *sc; + int error; + + sc = devclass_get_softc(ugen_devclass, UGENUNIT(dev)); + + if (sc->sc_dying) + return (EIO); + + UGEN_DEV_REF(dev, sc); + error = ugen_do_read(sc, endpt, uio, flag); + UGEN_DEV_RELE(dev, sc); + return (error); +} + +static int +ugen_do_write(struct ugen_softc *sc, int endpt, struct uio *uio, int flag) +{ + struct ugen_endpoint *sce = &sc->sc_endpoints[endpt][OUT]; + u_int32_t n; + int error = 0, doneone = 0; + char buf[UGEN_BBSIZE]; + usbd_xfer_handle xfer; + usbd_status err; + + DPRINTFN(5, ("%s: ugenwrite: %d\n", device_get_nameunit(sc->sc_dev), endpt)); + + if (sc->sc_dying) + return (EIO); + + if (endpt == USB_CONTROL_ENDPOINT) + return (ENODEV); + +#ifdef DIAGNOSTIC + if (sce->edesc == NULL) { + printf("ugenwrite: no edesc\n"); + return (EIO); + } + if (sce->pipeh == NULL) { + printf("ugenwrite: no pipe\n"); + return (EIO); + } +#endif + + switch (sce->edesc->bmAttributes & UE_XFERTYPE) { + case UE_BULK: + xfer = usbd_alloc_xfer(sc->sc_udev); + if (xfer == 0) + return (EIO); + while ((n = min(UGEN_BBSIZE, uio->uio_resid)) != 0 || + !doneone) { + doneone = 1; + error = uiomove(buf, n, uio); + if (error) + break; + DPRINTFN(1, ("ugenwrite: transfer %d bytes\n", n)); + err = usbd_bulk_transfer(xfer, sce->pipeh, 0, + sce->timeout, buf, &n,"ugenwb"); + if (err) { + if (err == USBD_INTERRUPTED) + error = EINTR; + else if (err == USBD_TIMEOUT) + error = ETIMEDOUT; + else + error = EIO; + break; + } + } + usbd_free_xfer(xfer); + break; + case UE_INTERRUPT: + xfer = usbd_alloc_xfer(sc->sc_udev); + if (xfer == 0) + return (EIO); + while ((n = min(UGETW(sce->edesc->wMaxPacketSize), + uio->uio_resid)) != 0 || !doneone) { + doneone = 1; + error = uiomove(buf, n, uio); + if (error) + break; + DPRINTFN(1, ("ugenwrite: transfer %d bytes\n", n)); + err = usbd_intr_transfer(xfer, sce->pipeh, 0, + sce->timeout, buf, &n, "ugenwi"); + if (err) { + if (err == USBD_INTERRUPTED) + error = EINTR; + else if (err == USBD_TIMEOUT) + error = ETIMEDOUT; + else + error = EIO; + break; + } + } + usbd_free_xfer(xfer); + break; + default: + return (ENXIO); + } + return (error); +} + +int +ugenwrite(struct cdev *dev, struct uio *uio, int flag) +{ + int endpt = UGENENDPOINT(dev); + struct ugen_softc *sc; + int error; + + sc = devclass_get_softc(ugen_devclass, UGENUNIT(dev)); + + if (sc->sc_dying) + return (EIO); + + UGEN_DEV_REF(dev, sc); + error = ugen_do_write(sc, endpt, uio, flag); + UGEN_DEV_RELE(dev, sc); + return (error); +} + +void +ugenpurge(struct cdev *dev) +{ + int endpt = UGENENDPOINT(dev); + struct ugen_endpoint *sce; + struct ugen_softc *sc; + + if (endpt == USB_CONTROL_ENDPOINT) + return; + sc = devclass_get_softc(ugen_devclass, UGENUNIT(dev)); + sce = &sc->sc_endpoints[endpt][IN]; + if (sce->pipeh) + usbd_abort_pipe(sce->pipeh); + if (sce->state & UGEN_ASLP) { + DPRINTFN(5, ("ugenpurge: waking %p\n", sce)); + wakeup(sce); + } + selwakeuppri(&sce->rsel, PZERO); + + sce = &sc->sc_endpoints[endpt][OUT]; + if (sce->pipeh) + usbd_abort_pipe(sce->pipeh); + if (sce->state & UGEN_ASLP) { + DPRINTFN(5, ("ugenpurge: waking %p\n", sce)); + wakeup(sce); + } + selwakeuppri(&sce->rsel, PZERO); +} + +static int +ugen_detach(device_t self) +{ + struct ugen_softc *sc = device_get_softc(self); + struct ugen_endpoint *sce; + int i, dir; + + DPRINTF(("ugen_detach: sc=%p\n", sc)); + + sc->sc_dying = 1; + /* Abort all pipes. Causes processes waiting for transfer to wake. */ + for (i = 0; i < USB_MAX_ENDPOINTS; i++) { + for (dir = OUT; dir <= IN; dir++) { + sce = &sc->sc_endpoints[i][dir]; + if (sce->pipeh) + usbd_abort_pipe(sce->pipeh); + selwakeuppri(&sce->rsel, PZERO); + } + } + + /* destroy the device for the control endpoint */ + destroy_dev(sc->dev); + usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_udev, sc->sc_dev); + return (0); +} + +static void +ugenintr(usbd_xfer_handle xfer, usbd_private_handle addr, usbd_status status) +{ + struct ugen_endpoint *sce = addr; + /*struct ugen_softc *sc = sce->sc;*/ + u_int32_t count; + u_char *ibuf; + + if (status == USBD_CANCELLED) + return; + + if (status != USBD_NORMAL_COMPLETION) { + DPRINTF(("ugenintr: status=%d\n", status)); + if (status == USBD_STALLED) + usbd_clear_endpoint_stall_async(sce->pipeh); + return; + } + + usbd_get_xfer_status(xfer, NULL, NULL, &count, NULL); + ibuf = sce->ibuf; + + DPRINTFN(5, ("ugenintr: xfer=%p status=%d count=%d\n", + xfer, status, count)); + DPRINTFN(5, (" data = %02x %02x %02x\n", + ibuf[0], ibuf[1], ibuf[2])); + + (void)b_to_q(ibuf, count, &sce->q); + + if (sce->state & UGEN_ASLP) { + sce->state &= ~UGEN_ASLP; + DPRINTFN(5, ("ugen_intr: waking %p\n", sce)); + wakeup(sce); + } + selwakeuppri(&sce->rsel, PZERO); +} + +static void +ugen_isoc_rintr(usbd_xfer_handle xfer, usbd_private_handle addr, + usbd_status status) +{ + struct isoreq *req = addr; + struct ugen_endpoint *sce = req->sce; + u_int32_t count, n; + int i, isize; + + /* Return if we are aborting. */ + if (status == USBD_CANCELLED) + return; + + usbd_get_xfer_status(xfer, NULL, NULL, &count, NULL); + DPRINTFN(5,("ugen_isoc_rintr: xfer %td, count=%d\n", req - sce->isoreqs, + count)); + + /* throw away oldest input if the buffer is full */ + if(sce->fill < sce->cur && sce->cur <= sce->fill + count) { + sce->cur += count; + if(sce->cur >= sce->limit) + sce->cur = sce->ibuf + (sce->limit - sce->cur); + DPRINTF(("ugen_isoc_rintr: throwing away %d bytes\n", + count)); + } + + isize = UGETW(sce->edesc->wMaxPacketSize); + for (i = 0; i < UGEN_NISORFRMS; i++) { + u_int32_t actlen = req->sizes[i]; + char const *buf = (char const *)req->dmabuf + isize * i; + + /* copy data to buffer */ + while (actlen > 0) { + n = min(actlen, sce->limit - sce->fill); + memcpy(sce->fill, buf, n); + + buf += n; + actlen -= n; + sce->fill += n; + if(sce->fill == sce->limit) + sce->fill = sce->ibuf; + } + + /* setup size for next transfer */ + req->sizes[i] = isize; + } + + usbd_setup_isoc_xfer(xfer, sce->pipeh, req, req->sizes, UGEN_NISORFRMS, + USBD_NO_COPY, ugen_isoc_rintr); + (void)usbd_transfer(xfer); + + if (sce->state & UGEN_ASLP) { + sce->state &= ~UGEN_ASLP; + DPRINTFN(5, ("ugen_isoc_rintr: waking %p\n", sce)); + wakeup(sce); + } + selwakeuppri(&sce->rsel, PZERO); +} + +static usbd_status +ugen_set_interface(struct ugen_softc *sc, int ifaceidx, int altno) +{ + usbd_interface_handle iface; + usb_endpoint_descriptor_t *ed; + usbd_status err; + struct ugen_endpoint *sce, **sce_cache; + u_int8_t niface, nendpt, nendpt_cache, endptno, endpt; + int dir; + + DPRINTFN(15, ("ugen_set_interface %d %d\n", ifaceidx, altno)); + + err = usbd_interface_count(sc->sc_udev, &niface); + if (err) + return (err); + if (ifaceidx < 0 || ifaceidx >= niface) + return (USBD_INVAL); + + err = usbd_device2interface_handle(sc->sc_udev, ifaceidx, &iface); + if (err) + return (err); + err = usbd_endpoint_count(iface, &nendpt); + if (err) + return (err); + + /* store an array of endpoint descriptors to clear if the interface + * change succeeds - these aren't available afterwards */ + sce_cache = malloc(sizeof(struct ugen_endpoint *) * nendpt, M_TEMP, + M_WAITOK); + nendpt_cache = nendpt; + + for (endptno = 0; endptno < nendpt; endptno++) { + ed = usbd_interface2endpoint_descriptor(iface,endptno); + endpt = ed->bEndpointAddress; + dir = UE_GET_DIR(endpt) == UE_DIR_IN ? IN : OUT; + sce_cache[endptno] = &sc->sc_endpoints[UE_GET_ADDR(endpt)][dir]; + } + + /* change setting */ + err = usbd_set_interface(iface, altno); + if (err) { + free(sce_cache, M_TEMP); + return (err); + } + err = usbd_endpoint_count(iface, &nendpt); + if (err) + panic("ugen_set_interface: endpoint count failed"); + + /* destroy the existing devices, we remake the new ones in a moment */ + ugen_destroy_devnodes(sc); + + /* now we can clear the old interface's ugen_endpoints */ + for (endptno = 0; endptno < nendpt_cache; endptno++) { + sce = sce_cache[endptno]; + sce->sc = 0; + sce->edesc = 0; + sce->iface = 0; + } + free(sce_cache, M_TEMP); + + /* set the new interface's ugen_endpoints */ + for (endptno = 0; endptno < nendpt; endptno++) { + ed = usbd_interface2endpoint_descriptor(iface,endptno); + endpt = ed->bEndpointAddress; + dir = UE_GET_DIR(endpt) == UE_DIR_IN ? IN : OUT; + sce = &sc->sc_endpoints[UE_GET_ADDR(endpt)][dir]; + sce->sc = sc; + sce->edesc = ed; + sce->iface = iface; + } + + /* make the new devices */ + ugen_make_devnodes(sc); + + return (0); +} + +/* Retrieve a complete descriptor for a certain device and index. */ +static usb_config_descriptor_t * +ugen_get_cdesc(struct ugen_softc *sc, int index, int *lenp) +{ + usb_config_descriptor_t *cdesc, *tdesc, cdescr; + int len; + usbd_status err; + + if (index == USB_CURRENT_CONFIG_INDEX) { + tdesc = usbd_get_config_descriptor(sc->sc_udev); + len = UGETW(tdesc->wTotalLength); + if (lenp) + *lenp = len; + cdesc = malloc(len, M_TEMP, M_WAITOK); + memcpy(cdesc, tdesc, len); + DPRINTFN(5,("ugen_get_cdesc: current, len=%d\n", len)); + } else { + err = usbd_get_config_desc(sc->sc_udev, index, &cdescr); + if (err) + return (0); + len = UGETW(cdescr.wTotalLength); + DPRINTFN(5,("ugen_get_cdesc: index=%d, len=%d\n", index, len)); + if (lenp) + *lenp = len; + cdesc = malloc(len, M_TEMP, M_WAITOK); + err = usbd_get_config_desc_full(sc->sc_udev, index, cdesc, len); + if (err) { + free(cdesc, M_TEMP); + return (0); + } + } + return (cdesc); +} + +static int +ugen_get_alt_index(struct ugen_softc *sc, int ifaceidx) +{ + usbd_interface_handle iface; + usbd_status err; + + err = usbd_device2interface_handle(sc->sc_udev, ifaceidx, &iface); + if (err) + return (-1); + return (usbd_get_interface_altindex(iface)); +} + +static int +ugen_do_ioctl(struct ugen_softc *sc, int endpt, u_long cmd, + caddr_t addr, int flag, struct thread *p) +{ + struct ugen_endpoint *sce; + usbd_status err; + usbd_interface_handle iface; + struct usb_config_desc *cd; + usb_config_descriptor_t *cdesc; + struct usb_interface_desc *id; + usb_interface_descriptor_t *idesc; + struct usb_endpoint_desc *ed; + usb_endpoint_descriptor_t *edesc; + struct usb_alt_interface *ai; + struct usb_string_desc *si; + u_int8_t conf, alt; + + DPRINTFN(5, ("ugenioctl: cmd=%08lx\n", cmd)); + if (sc->sc_dying) + return (EIO); + + switch (cmd) { + case FIONBIO: + case FIOASYNC: + /* All handled in the upper FS layer. */ + return (0); + case USB_SET_SHORT_XFER: + if (endpt == USB_CONTROL_ENDPOINT) + return (EINVAL); + /* This flag only affects read */ + sce = &sc->sc_endpoints[endpt][IN]; + + if (sce->pipeh == NULL) { + printf("ugenioctl: USB_SET_SHORT_XFER, no pipe\n"); + return (EIO); + } + + if (*(int *)addr) + sce->state |= UGEN_SHORT_OK; + else + sce->state &= ~UGEN_SHORT_OK; + return (0); + case USB_SET_TIMEOUT: + sce = &sc->sc_endpoints[endpt][IN]; + sce->timeout = *(int *)addr; + sce = &sc->sc_endpoints[endpt][OUT]; + sce->timeout = *(int *)addr; + return (0); + default: + break; + } + + if (endpt != USB_CONTROL_ENDPOINT) + return (EINVAL); + + switch (cmd) { +#ifdef USB_DEBUG + case USB_SETDEBUG: + ugendebug = *(int *)addr; + break; +#endif + case USB_GET_CONFIG: + err = usbd_get_config(sc->sc_udev, &conf); + if (err) + return (EIO); + *(int *)addr = conf; + break; + case USB_SET_CONFIG: + if (!(flag & FWRITE)) + return (EPERM); + err = ugen_set_config(sc, *(int *)addr); + switch (err) { + case USBD_NORMAL_COMPLETION: + ugen_make_devnodes(sc); + break; + case USBD_IN_USE: + return (EBUSY); + default: + return (EIO); + } + break; + case USB_GET_ALTINTERFACE: + ai = (struct usb_alt_interface *)addr; + err = usbd_device2interface_handle(sc->sc_udev, + ai->uai_interface_index, &iface); + if (err) + return (EINVAL); + idesc = usbd_get_interface_descriptor(iface); + if (idesc == NULL) + return (EIO); + ai->uai_alt_no = idesc->bAlternateSetting; + break; + case USB_SET_ALTINTERFACE: + if (!(flag & FWRITE)) + return (EPERM); + ai = (struct usb_alt_interface *)addr; + err = usbd_device2interface_handle(sc->sc_udev, + ai->uai_interface_index, &iface); + if (err) + return (EINVAL); + err = ugen_set_interface(sc, ai->uai_interface_index, + ai->uai_alt_no); + if (err) + return (EINVAL); + break; + case USB_GET_NO_ALT: + ai = (struct usb_alt_interface *)addr; + cdesc = ugen_get_cdesc(sc, ai->uai_config_index, 0); + if (cdesc == NULL) + return (EINVAL); + idesc = usbd_find_idesc(cdesc, ai->uai_interface_index, 0); + if (idesc == NULL) { + free(cdesc, M_TEMP); + return (EINVAL); + } + ai->uai_alt_no = usbd_get_no_alts(cdesc, + idesc->bInterfaceNumber); + free(cdesc, M_TEMP); + break; + case USB_GET_DEVICE_DESC: + *(usb_device_descriptor_t *)addr = + *usbd_get_device_descriptor(sc->sc_udev); + break; + case USB_GET_CONFIG_DESC: + cd = (struct usb_config_desc *)addr; + cdesc = ugen_get_cdesc(sc, cd->ucd_config_index, 0); + if (cdesc == NULL) + return (EINVAL); + cd->ucd_desc = *cdesc; + free(cdesc, M_TEMP); + break; + case USB_GET_INTERFACE_DESC: + id = (struct usb_interface_desc *)addr; + cdesc = ugen_get_cdesc(sc, id->uid_config_index, 0); + if (cdesc == NULL) + return (EINVAL); + if (id->uid_config_index == USB_CURRENT_CONFIG_INDEX && + id->uid_alt_index == USB_CURRENT_ALT_INDEX) + alt = ugen_get_alt_index(sc, id->uid_interface_index); + else + alt = id->uid_alt_index; + idesc = usbd_find_idesc(cdesc, id->uid_interface_index, alt); + if (idesc == NULL) { + free(cdesc, M_TEMP); + return (EINVAL); + } + id->uid_desc = *idesc; + free(cdesc, M_TEMP); + break; + case USB_GET_ENDPOINT_DESC: + ed = (struct usb_endpoint_desc *)addr; + cdesc = ugen_get_cdesc(sc, ed->ued_config_index, 0); + if (cdesc == NULL) + return (EINVAL); + if (ed->ued_config_index == USB_CURRENT_CONFIG_INDEX && + ed->ued_alt_index == USB_CURRENT_ALT_INDEX) + alt = ugen_get_alt_index(sc, ed->ued_interface_index); + else + alt = ed->ued_alt_index; + edesc = usbd_find_edesc(cdesc, ed->ued_interface_index, + alt, ed->ued_endpoint_index); + if (edesc == NULL) { + free(cdesc, M_TEMP); + return (EINVAL); + } + ed->ued_desc = *edesc; + free(cdesc, M_TEMP); + break; + case USB_GET_FULL_DESC: + { + int len; + struct iovec iov; + struct uio uio; + struct usb_full_desc *fd = (struct usb_full_desc *)addr; + int error; + + cdesc = ugen_get_cdesc(sc, fd->ufd_config_index, &len); + if (len > fd->ufd_size) + len = fd->ufd_size; + iov.iov_base = (caddr_t)fd->ufd_data; + iov.iov_len = len; + uio.uio_iov = &iov; + uio.uio_iovcnt = 1; + uio.uio_resid = len; + uio.uio_offset = 0; + uio.uio_segflg = UIO_USERSPACE; + uio.uio_rw = UIO_READ; + uio.uio_td = p; + error = uiomove((void *)cdesc, len, &uio); + free(cdesc, M_TEMP); + return (error); + } + case USB_GET_STRING_DESC: { + int len; + si = (struct usb_string_desc *)addr; + err = usbd_get_string_desc(sc->sc_udev, si->usd_string_index, + si->usd_language_id, &si->usd_desc, &len); + if (err) + return (EINVAL); + break; + } + case USB_DO_REQUEST: + { + struct usb_ctl_request *ur = (void *)addr; + int len = UGETW(ur->ucr_request.wLength); + struct iovec iov; + struct uio uio; + void *ptr = 0; + int error = 0; + + if (!(flag & FWRITE)) + return (EPERM); + /* Avoid requests that would damage the bus integrity. */ + if ((ur->ucr_request.bmRequestType == UT_WRITE_DEVICE && + ur->ucr_request.bRequest == UR_SET_ADDRESS) || + (ur->ucr_request.bmRequestType == UT_WRITE_DEVICE && + ur->ucr_request.bRequest == UR_SET_CONFIG) || + (ur->ucr_request.bmRequestType == UT_WRITE_INTERFACE && + ur->ucr_request.bRequest == UR_SET_INTERFACE)) + return (EINVAL); + + if (len < 0 || len > 32767) + return (EINVAL); + if (len != 0) { + iov.iov_base = (caddr_t)ur->ucr_data; + iov.iov_len = len; + uio.uio_iov = &iov; + uio.uio_iovcnt = 1; + uio.uio_resid = len; + uio.uio_offset = 0; + uio.uio_segflg = UIO_USERSPACE; + uio.uio_rw = + ur->ucr_request.bmRequestType & UT_READ ? + UIO_READ : UIO_WRITE; + uio.uio_td = p; + ptr = malloc(len, M_TEMP, M_WAITOK); + if (uio.uio_rw == UIO_WRITE) { + error = uiomove(ptr, len, &uio); + if (error) + goto ret; + } + } + sce = &sc->sc_endpoints[endpt][IN]; + err = usbd_do_request_flags(sc->sc_udev, &ur->ucr_request, + ptr, ur->ucr_flags, &ur->ucr_actlen, sce->timeout); + if (err) { + error = EIO; + goto ret; + } + if (len != 0) { + if (uio.uio_rw == UIO_READ) { + error = uiomove(ptr, len, &uio); + if (error) + goto ret; + } + } + ret: + if (ptr) + free(ptr, M_TEMP); + return (error); + } + case USB_GET_DEVICEINFO: + usbd_fill_deviceinfo(sc->sc_udev, + (struct usb_device_info *)addr, 1); + break; + case USB_RESET_DEVICE: + err = usbd_reset_device(sc->sc_udev); + if (err) + return EIO; + break; + default: + return (EINVAL); + } + return (0); +} + +int +ugenioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, + struct thread *p) +{ + int endpt = UGENENDPOINT(dev); + struct ugen_softc *sc; + int error; + + sc = devclass_get_softc(ugen_devclass, UGENUNIT(dev)); + + if (sc->sc_dying) + return (EIO); + + UGEN_DEV_REF(dev, sc); + error = ugen_do_ioctl(sc, endpt, cmd, addr, flag, p); + UGEN_DEV_RELE(dev, sc); + return (error); +} + +int +ugenpoll(struct cdev *dev, int events, struct thread *p) +{ + struct ugen_softc *sc; + struct ugen_endpoint *sce_in, *sce_out; + usb_endpoint_descriptor_t *edesc; + int revents = 0; + int s; + + sc = devclass_get_softc(ugen_devclass, UGENUNIT(dev)); + + if (sc->sc_dying) + return ((events & (POLLIN | POLLOUT | POLLRDNORM | + POLLWRNORM)) | POLLHUP); + /* Do not allow to poll a control endpoint */ + if (UGENENDPOINT(dev) == USB_CONTROL_ENDPOINT) + return (events & (POLLIN | POLLOUT | POLLRDNORM | POLLWRNORM)); + + sce_in = &sc->sc_endpoints[UGENENDPOINT(dev)][IN]; + sce_out = &sc->sc_endpoints[UGENENDPOINT(dev)][OUT]; + edesc = (sce_in->edesc != NULL) ? sce_in->edesc : sce_out->edesc; + KASSERT(edesc != NULL, ("ugenpoll: NULL edesc")); + if (sce_in->edesc == NULL || sce_in->pipeh == NULL) + sce_in = NULL; + if (sce_out->edesc == NULL || sce_out->pipeh == NULL) + sce_out = NULL; + + s = splusb(); + switch (edesc->bmAttributes & UE_XFERTYPE) { + case UE_INTERRUPT: + if (sce_in != NULL && (events & (POLLIN | POLLRDNORM))) { + if (sce_in->q.c_cc > 0) + revents |= events & (POLLIN | POLLRDNORM); + else + selrecord(p, &sce_in->rsel); + } + if (sce_out != NULL && (events & (POLLOUT | POLLWRNORM))) { + if (sce_out->q.c_cc > 0) + revents |= events & (POLLOUT | POLLWRNORM); + else + selrecord(p, &sce_out->rsel); + } + break; + case UE_ISOCHRONOUS: + if (sce_in != NULL && (events & (POLLIN | POLLRDNORM))) { + if (sce_in->cur != sce_in->fill) + revents |= events & (POLLIN | POLLRDNORM); + else + selrecord(p, &sce_in->rsel); + } + if (sce_out != NULL && (events & (POLLOUT | POLLWRNORM))) { + if (sce_out->cur != sce_out->fill) + revents |= events & (POLLOUT | POLLWRNORM); + else + selrecord(p, &sce_out->rsel); + } + break; + case UE_BULK: + /* + * We have no easy way of determining if a read will + * yield any data or a write will happen. + * Pretend they will. + */ + revents |= events & + (POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM); + break; + default: + break; + } + splx(s); + return (revents); +} + +DRIVER_MODULE(ugen, uhub, ugen_driver, ugen_devclass, usbd_driver_load, 0); diff --git a/sys/legacy/dev/usb/ugraphire_rdesc.h b/sys/legacy/dev/usb/ugraphire_rdesc.h new file mode 100644 index 0000000..9c5a0c1 --- /dev/null +++ b/sys/legacy/dev/usb/ugraphire_rdesc.h @@ -0,0 +1,176 @@ +/* $NetBSD: usb/ugraphire_rdesc.h,v 1.1 2000/12/29 01:47:49 augustss Exp $ */ +/* $FreeBSD$ */ +/*- + * Copyright (c) 2000 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. + */ + +static const uByte uhid_graphire_report_descr[] = { + 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */ + 0x09, 0x01, /* USAGE (Digitizer) */ + 0xa1, 0x01, /* COLLECTION (Application) */ + 0x85, 0x02, /* REPORT_ID (2) */ + 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */ + 0x09, 0x01, /* USAGE (Digitizer) */ + 0xa1, 0x00, /* COLLECTION (Physical) */ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ + 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */ + 0x09, 0x33, /* USAGE (Touch) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0x75, 0x01, /* REPORT_SIZE (1) */ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */ + 0x09, 0x44, /* USAGE (Barrel Switch) */ + 0x95, 0x02, /* REPORT_COUNT (2) */ + 0x75, 0x01, /* REPORT_SIZE (1) */ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */ + 0x09, 0x00, /* USAGE (Undefined) */ + 0x95, 0x02, /* REPORT_COUNT (2) */ + 0x75, 0x01, /* REPORT_SIZE (1) */ + 0x81, 0x03, /* INPUT (Cnst,Var,Abs) */ + 0x09, 0x3c, /* USAGE (Invert) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0x75, 0x01, /* REPORT_SIZE (1) */ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */ + 0x09, 0x38, /* USAGE (Transducer Index) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0x75, 0x01, /* REPORT_SIZE (1) */ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */ + 0x09, 0x32, /* USAGE (In Range) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0x75, 0x01, /* REPORT_SIZE (1) */ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */ + 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */ + 0x09, 0x30, /* USAGE (X) */ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ + 0x26, 0xde, 0x27, /* LOGICAL_MAXIMUM (10206) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0x75, 0x10, /* REPORT_SIZE (16) */ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */ + 0x09, 0x31, /* USAGE (Y) */ + 0x26, 0xfe, 0x1c, /* LOGICAL_MAXIMUM (7422) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0x75, 0x10, /* REPORT_SIZE (16) */ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */ + 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */ + 0x09, 0x30, /* USAGE (Tip Pressure) */ + 0x26, 0xff, 0x01, /* LOGICAL_MAXIMUM (511) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0x75, 0x10, /* REPORT_SIZE (16) */ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */ + 0xc0, /* END_COLLECTION */ + 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */ + 0x09, 0x00, /* USAGE (Undefined) */ + 0x85, 0x02, /* REPORT_ID (2) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0xb1, 0x02, /* FEATURE (Data,Var,Abs) */ + 0x09, 0x00, /* USAGE (Undefined) */ + 0x85, 0x03, /* REPORT_ID (3) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0xb1, 0x02, /* FEATURE (Data,Var,Abs) */ + 0xc0, /* END_COLLECTION */ +}; + +static const uByte uhid_graphire3_4x5_report_descr[] = { + 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */ + 0x09, 0x02, /* USAGE (Mouse) */ + 0xa1, 0x01, /* COLLECTION (Application) */ + 0x85, 0x01, /* REPORT_ID (1) */ + 0x09, 0x01, /* USAGE (Pointer) */ + 0xa1, 0x00, /* COLLECTION (Physical) */ + 0x05, 0x09, /* USAGE_PAGE (Button) */ + 0x19, 0x01, /* USAGE_MINIMUM (Button 1) */ + 0x29, 0x03, /* USAGE_MAXIMUM (Button 3) */ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ + 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */ + 0x95, 0x03, /* REPORT_COUNT (3) */ + 0x75, 0x01, /* REPORT_SIZE (1) */ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0x75, 0x05, /* REPORT_SIZE (5) */ + 0x81, 0x01, /* INPUT (Cnst,Ary,Abs) */ + 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */ + 0x09, 0x30, /* USAGE (X) */ + 0x09, 0x31, /* USAGE (Y) */ + 0x09, 0x38, /* USAGE (Wheel) */ + 0x15, 0x81, /* LOGICAL_MINIMUM (-127) */ + 0x25, 0x7f, /* LOGICAL_MAXIMUM (127) */ + 0x75, 0x08, /* REPORT_SIZE (8) */ + 0x95, 0x03, /* REPORT_COUNT (3) */ + 0x81, 0x06, /* INPUT (Data,Var,Rel) */ + 0xc0, /* END_COLLECTION */ + 0xc0, /* END_COLLECTION */ + 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */ + 0x09, 0x01, /* USAGE (Pointer) */ + 0xa1, 0x01, /* COLLECTION (Applicaption) */ + 0x85, 0x02, /* REPORT_ID (2) */ + 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */ + 0x09, 0x01, /* USAGE (Digitizer) */ + 0xa1, 0x00, /* COLLECTION (Physical) */ + 0x09, 0x33, /* USAGE (Touch) */ + 0x09, 0x44, /* USAGE (Barrel Switch) */ + 0x09, 0x44, /* USAGE (Barrel Switch) */ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ + 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */ + 0x75, 0x01, /* REPORT_SIZE (1) */ + 0x95, 0x03, /* REPORT_COUNT (3) */ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */ + 0x75, 0x01, /* REPORT_SIZE (1) */ + 0x95, 0x02, /* REPORT_COUNT (2) */ + 0x81, 0x01, /* INPUT (Cnst,Ary,Abs) */ + 0x09, 0x3c, /* USAGE (Invert) */ + 0x09, 0x38, /* USAGE (Transducer Index) */ + 0x09, 0x32, /* USAGE (In Range) */ + 0x75, 0x01, /* REPORT_SIZE (1) */ + 0x95, 0x03, /* REPORT_COUNT (3) */ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */ + 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */ + 0x09, 0x30, /* USAGE (X) */ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ + 0x26, 0xde, 0x27, /* LOGICAL_MAXIMUM (10206) */ + 0x75, 0x10, /* REPORT_SIZE (16) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */ + 0x09, 0x31, /* USAGE (Y) */ + 0x26, 0xfe, 0x1c, /* LOGICAL_MAXIMUM (7422) */ + 0x75, 0x10, /* REPORT_SIZE (16) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */ + 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */ + 0x09, 0x30, /* USAGE (Tip Pressure) */ + 0x26, 0xff, 0x01, /* LOGICAL_MAXIMUM (511) */ + 0x75, 0x10, /* REPORT_SIZE (16) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */ + 0xc0, /* END_COLLECTION */ + 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */ + 0x09, 0x00, /* USAGE (Undefined) */ + 0x85, 0x02, /* REPORT_ID (2) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0xb1, 0x02, /* FEATURE (Data,Var,Abs) */ + 0x09, 0x00, /* USAGE (Undefined) */ + 0x85, 0x03, /* REPORT_ID (3) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0xb1, 0x02, /* FEATURE (Data,Var,Abs) */ + 0xc0 /* END_COLLECTION */ +}; diff --git a/sys/legacy/dev/usb/uhci.c b/sys/legacy/dev/usb/uhci.c new file mode 100644 index 0000000..4d4c61b --- /dev/null +++ b/sys/legacy/dev/usb/uhci.c @@ -0,0 +1,3704 @@ +/* $NetBSD: uhci.c,v 1.170 2003/02/19 01:35:04 augustss Exp $ */ + +/* Also already incorporated from NetBSD: + * $NetBSD: uhci.c,v 1.172 2003/02/23 04:19:26 simonb Exp $ + * $NetBSD: uhci.c,v 1.173 2003/05/13 04:41:59 gson Exp $ + * $NetBSD: uhci.c,v 1.175 2003/09/12 16:18:08 mycroft Exp $ + * $NetBSD: uhci.c,v 1.176 2003/11/04 19:11:21 mycroft Exp $ + * $NetBSD: uhci.c,v 1.177 2003/12/29 08:17:10 toshii Exp $ + * $NetBSD: uhci.c,v 1.178 2004/03/02 16:32:05 martin Exp $ + * $NetBSD: uhci.c,v 1.180 2004/07/17 20:12:03 mycroft Exp $ + */ + +#include <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. + */ + +/* + * USB Universal Host Controller driver. + * Handles e.g. PIIX3 and PIIX4. + * + * UHCI spec: http://developer.intel.com/design/USB/UHCI11D.htm + * USB spec: http://www.usb.org/developers/docs/usbspec.zip + * PIIXn spec: ftp://download.intel.com/design/intarch/datashts/29055002.pdf + * ftp://download.intel.com/design/intarch/datashts/29056201.pdf + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/endian.h> +#include <sys/module.h> +#include <sys/bus.h> +#if defined(DIAGNOSTIC) && defined(__i386__) +#include <machine/cpu.h> +#endif +#include <sys/proc.h> +#include <sys/queue.h> +#include <sys/sysctl.h> + +#include <machine/bus.h> +#include <machine/endian.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdivar.h> +#include <dev/usb/usb_mem.h> +#include <dev/usb/usb_quirks.h> + +#include <dev/usb/uhcireg.h> +#include <dev/usb/uhcivar.h> + +/* Use bandwidth reclamation for control transfers. Some devices choke on it. */ +/*#define UHCI_CTL_LOOP */ + +#define delay(d) DELAY(d) + +#define MS_TO_TICKS(ms) ((ms) * hz / 1000) + +#ifdef USB_DEBUG +uhci_softc_t *thesc; +#define DPRINTF(x) if (uhcidebug) printf x +#define DPRINTFN(n,x) if (uhcidebug>(n)) printf x +int uhcidebug = 0; +int uhcinoloop = 0; +SYSCTL_NODE(_hw_usb, OID_AUTO, uhci, CTLFLAG_RW, 0, "USB uhci"); +SYSCTL_INT(_hw_usb_uhci, OID_AUTO, debug, CTLFLAG_RW, + &uhcidebug, 0, "uhci debug level"); +SYSCTL_INT(_hw_usb_uhci, OID_AUTO, loop, CTLFLAG_RW, + &uhcinoloop, 0, "uhci noloop"); +#define bitmask_snprintf(q,f,b,l) snprintf((b), (l), "%b", (q), (f)) +#else +#define DPRINTF(x) +#define DPRINTFN(n,x) +#endif + +struct uhci_pipe { + struct usbd_pipe pipe; + int nexttoggle; + + u_char aborting; + usbd_xfer_handle abortstart, abortend; + + /* Info needed for different pipe kinds. */ + union { + /* Control pipe */ + struct { + uhci_soft_qh_t *sqh; + usb_dma_t reqdma; + uhci_soft_td_t *setup, *stat; + u_int length; + } ctl; + /* Interrupt pipe */ + struct { + int npoll; + int isread; + uhci_soft_qh_t **qhs; + } intr; + /* Bulk pipe */ + struct { + uhci_soft_qh_t *sqh; + u_int length; + int isread; + } bulk; + /* Iso pipe */ + struct iso { + uhci_soft_td_t **stds; + int next, inuse; + } iso; + } u; +}; + +static void uhci_globalreset(uhci_softc_t *); +static usbd_status uhci_portreset(uhci_softc_t*, int); +static void uhci_reset(uhci_softc_t *); +#if defined(__NetBSD__) || defined(__OpenBSD__) +static void uhci_shutdown(void *v); +static void uhci_power(int, void *); +#endif +static usbd_status uhci_run(uhci_softc_t *, int run); +static uhci_soft_td_t *uhci_alloc_std(uhci_softc_t *); +static void uhci_free_std(uhci_softc_t *, uhci_soft_td_t *); +static uhci_soft_qh_t *uhci_alloc_sqh(uhci_softc_t *); +static void uhci_free_sqh(uhci_softc_t *, uhci_soft_qh_t *); +static usbd_status uhci_aux_dma_alloc(uhci_softc_t *, uhci_soft_td_t *, + void *data, int len); +static uhci_physaddr_t uhci_aux_dma_prepare(uhci_soft_td_t *, int); +static void uhci_aux_dma_complete(uhci_soft_td_t *, int); +#if 0 +static void uhci_enter_ctl_q(uhci_softc_t *, uhci_soft_qh_t *, + uhci_intr_info_t *); +static void uhci_exit_ctl_q(uhci_softc_t *, uhci_soft_qh_t *); +#endif + +static void uhci_free_std_chain(uhci_softc_t *, + uhci_soft_td_t *, uhci_soft_td_t *); +static usbd_status uhci_alloc_std_chain(struct uhci_pipe *, + uhci_softc_t *, int, int, u_int16_t, + usbd_xfer_handle xfer, + uhci_soft_td_t **, uhci_soft_td_t **); +static void uhci_poll_hub(void *); +static void uhci_waitintr(uhci_softc_t *, usbd_xfer_handle); +static void uhci_check_intr(uhci_softc_t *, uhci_intr_info_t *); +static void uhci_idone(uhci_intr_info_t *); + +static void uhci_abort_xfer(usbd_xfer_handle, usbd_status status); +static void uhci_transfer_complete(usbd_xfer_handle xfer); + +static void uhci_timeout(void *); +static void uhci_timeout_task(void *); +static void uhci_add_ls_ctrl(uhci_softc_t *, uhci_soft_qh_t *); +static void uhci_add_hs_ctrl(uhci_softc_t *, uhci_soft_qh_t *); +static void uhci_add_bulk(uhci_softc_t *, uhci_soft_qh_t *); +static void uhci_remove_ls_ctrl(uhci_softc_t *,uhci_soft_qh_t *); +static void uhci_remove_hs_ctrl(uhci_softc_t *,uhci_soft_qh_t *); +static void uhci_remove_bulk(uhci_softc_t *,uhci_soft_qh_t *); +static int uhci_str(usb_string_descriptor_t *, int, char *); +static void uhci_add_loop(uhci_softc_t *sc); +static void uhci_rem_loop(uhci_softc_t *sc); + +static usbd_status uhci_setup_isoc(usbd_pipe_handle pipe); +static void uhci_device_isoc_enter(usbd_xfer_handle); + +static usbd_status uhci_allocm(struct usbd_bus *, usb_dma_t *, u_int32_t); +static void uhci_freem(struct usbd_bus *, usb_dma_t *); + +static usbd_xfer_handle uhci_allocx(struct usbd_bus *); +static void uhci_freex(struct usbd_bus *, usbd_xfer_handle); + +static usbd_status uhci_device_ctrl_transfer(usbd_xfer_handle); +static usbd_status uhci_device_ctrl_start(usbd_xfer_handle); +static void uhci_device_ctrl_abort(usbd_xfer_handle); +static void uhci_device_ctrl_close(usbd_pipe_handle); +static void uhci_device_ctrl_done(usbd_xfer_handle); + +static usbd_status uhci_device_intr_transfer(usbd_xfer_handle); +static usbd_status uhci_device_intr_start(usbd_xfer_handle); +static void uhci_device_intr_abort(usbd_xfer_handle); +static void uhci_device_intr_close(usbd_pipe_handle); +static void uhci_device_intr_done(usbd_xfer_handle); + +static usbd_status uhci_device_bulk_transfer(usbd_xfer_handle); +static usbd_status uhci_device_bulk_start(usbd_xfer_handle); +static void uhci_device_bulk_abort(usbd_xfer_handle); +static void uhci_device_bulk_close(usbd_pipe_handle); +static void uhci_device_bulk_done(usbd_xfer_handle); + +static usbd_status uhci_device_isoc_transfer(usbd_xfer_handle); +static usbd_status uhci_device_isoc_start(usbd_xfer_handle); +static void uhci_device_isoc_abort(usbd_xfer_handle); +static void uhci_device_isoc_close(usbd_pipe_handle); +static void uhci_device_isoc_done(usbd_xfer_handle); + +static usbd_status uhci_root_ctrl_transfer(usbd_xfer_handle); +static usbd_status uhci_root_ctrl_start(usbd_xfer_handle); +static void uhci_root_ctrl_abort(usbd_xfer_handle); +static void uhci_root_ctrl_close(usbd_pipe_handle); +static void uhci_root_ctrl_done(usbd_xfer_handle); + +static usbd_status uhci_root_intr_transfer(usbd_xfer_handle); +static usbd_status uhci_root_intr_start(usbd_xfer_handle); +static void uhci_root_intr_abort(usbd_xfer_handle); +static void uhci_root_intr_close(usbd_pipe_handle); +static void uhci_root_intr_done(usbd_xfer_handle); + +static usbd_status uhci_open(usbd_pipe_handle); +static void uhci_poll(struct usbd_bus *); +static void uhci_softintr(void *); + +static usbd_status uhci_device_request(usbd_xfer_handle xfer); + +static void uhci_add_intr(uhci_softc_t *, uhci_soft_qh_t *); +static void uhci_remove_intr(uhci_softc_t *, uhci_soft_qh_t *); +static usbd_status uhci_device_setintr(uhci_softc_t *sc, + struct uhci_pipe *pipe, int ival); + +static void uhci_device_clear_toggle(usbd_pipe_handle pipe); +static void uhci_noop(usbd_pipe_handle pipe); + +static __inline uhci_soft_qh_t *uhci_find_prev_qh(uhci_soft_qh_t *, + uhci_soft_qh_t *); + +#ifdef USB_DEBUG +static void uhci_dump_all(uhci_softc_t *); +static void uhci_dumpregs(uhci_softc_t *); +static void uhci_dump_qhs(uhci_soft_qh_t *); +static void uhci_dump_qh(uhci_soft_qh_t *); +static void uhci_dump_tds(uhci_soft_td_t *); +static void uhci_dump_td(uhci_soft_td_t *); +static void uhci_dump_ii(uhci_intr_info_t *ii); +void uhci_dump(void); +#endif + +#define UBARR(sc) bus_space_barrier((sc)->iot, (sc)->ioh, 0, (sc)->sc_size, \ + BUS_SPACE_BARRIER_READ|BUS_SPACE_BARRIER_WRITE) +#define UWRITE1(sc, r, x) \ + do { UBARR(sc); bus_space_write_1((sc)->iot, (sc)->ioh, (r), (x)); \ + } while (/*CONSTCOND*/0) +#define UWRITE2(sc, r, x) \ + do { UBARR(sc); bus_space_write_2((sc)->iot, (sc)->ioh, (r), (x)); \ + } while (/*CONSTCOND*/0) +#define UWRITE4(sc, r, x) \ + do { UBARR(sc); bus_space_write_4((sc)->iot, (sc)->ioh, (r), (x)); \ + } while (/*CONSTCOND*/0) +#define UREAD1(sc, r) (UBARR(sc), bus_space_read_1((sc)->iot, (sc)->ioh, (r))) +#define UREAD2(sc, r) (UBARR(sc), bus_space_read_2((sc)->iot, (sc)->ioh, (r))) +#define UREAD4(sc, r) (UBARR(sc), bus_space_read_4((sc)->iot, (sc)->ioh, (r))) + +#define UHCICMD(sc, cmd) UWRITE2(sc, UHCI_CMD, cmd) +#define UHCISTS(sc) UREAD2(sc, UHCI_STS) + +#define UHCI_RESET_TIMEOUT 100 /* ms, reset timeout */ + +#define UHCI_CURFRAME(sc) (UREAD2(sc, UHCI_FRNUM) & UHCI_FRNUM_MASK) + +#define UHCI_INTR_ENDPT 1 + +struct usbd_bus_methods uhci_bus_methods = { + uhci_open, + uhci_softintr, + uhci_poll, + uhci_allocm, + uhci_freem, + uhci_allocx, + uhci_freex, +}; + +struct usbd_pipe_methods uhci_root_ctrl_methods = { + uhci_root_ctrl_transfer, + uhci_root_ctrl_start, + uhci_root_ctrl_abort, + uhci_root_ctrl_close, + uhci_noop, + uhci_root_ctrl_done, +}; + +struct usbd_pipe_methods uhci_root_intr_methods = { + uhci_root_intr_transfer, + uhci_root_intr_start, + uhci_root_intr_abort, + uhci_root_intr_close, + uhci_noop, + uhci_root_intr_done, +}; + +struct usbd_pipe_methods uhci_device_ctrl_methods = { + uhci_device_ctrl_transfer, + uhci_device_ctrl_start, + uhci_device_ctrl_abort, + uhci_device_ctrl_close, + uhci_noop, + uhci_device_ctrl_done, +}; + +struct usbd_pipe_methods uhci_device_intr_methods = { + uhci_device_intr_transfer, + uhci_device_intr_start, + uhci_device_intr_abort, + uhci_device_intr_close, + uhci_device_clear_toggle, + uhci_device_intr_done, +}; + +struct usbd_pipe_methods uhci_device_bulk_methods = { + uhci_device_bulk_transfer, + uhci_device_bulk_start, + uhci_device_bulk_abort, + uhci_device_bulk_close, + uhci_device_clear_toggle, + uhci_device_bulk_done, +}; + +struct usbd_pipe_methods uhci_device_isoc_methods = { + uhci_device_isoc_transfer, + uhci_device_isoc_start, + uhci_device_isoc_abort, + uhci_device_isoc_close, + uhci_noop, + uhci_device_isoc_done, +}; + +#define uhci_add_intr_info(sc, ii) \ + LIST_INSERT_HEAD(&(sc)->sc_intrhead, (ii), list) +#define uhci_del_intr_info(ii) \ + do { \ + LIST_REMOVE((ii), list); \ + (ii)->list.le_prev = NULL; \ + } while (0) +#define uhci_active_intr_info(ii) ((ii)->list.le_prev != NULL) + +static __inline uhci_soft_qh_t * +uhci_find_prev_qh(uhci_soft_qh_t *pqh, uhci_soft_qh_t *sqh) +{ + DPRINTFN(15,("uhci_find_prev_qh: pqh=%p sqh=%p\n", pqh, sqh)); + + for (; pqh->hlink != sqh; pqh = pqh->hlink) { +#if defined(DIAGNOSTIC) || defined(USB_DEBUG) + if (le32toh(pqh->qh.qh_hlink) & UHCI_PTR_T) { + printf("uhci_find_prev_qh: QH not found\n"); + return (NULL); + } +#endif + } + return (pqh); +} + +void +uhci_globalreset(uhci_softc_t *sc) +{ + UHCICMD(sc, UHCI_CMD_GRESET); /* global reset */ + usb_delay_ms(&sc->sc_bus, USB_BUS_RESET_DELAY); /* wait a little */ + UHCICMD(sc, 0); /* do nothing */ +} + +usbd_status +uhci_init(uhci_softc_t *sc) +{ + usbd_status err; + int i, j; + uhci_soft_qh_t *clsqh, *chsqh, *bsqh, *sqh, *lsqh; + uhci_soft_td_t *std; + + DPRINTFN(1,("uhci_init: start\n")); + +#ifdef USB_DEBUG + thesc = sc; + + if (uhcidebug > 2) + uhci_dumpregs(sc); +#endif + + UWRITE2(sc, UHCI_INTR, 0); /* disable interrupts */ + uhci_globalreset(sc); /* reset the controller */ + uhci_reset(sc); + + /* Allocate and initialize real frame array. */ + err = usb_allocmem(&sc->sc_bus, + UHCI_FRAMELIST_COUNT * sizeof(uhci_physaddr_t), + UHCI_FRAMELIST_ALIGN, &sc->sc_dma); + if (err) + return (err); + sc->sc_pframes = KERNADDR(&sc->sc_dma, 0); + UWRITE2(sc, UHCI_FRNUM, 0); /* set frame number to 0 */ + UWRITE4(sc, UHCI_FLBASEADDR, DMAADDR(&sc->sc_dma, 0)); /* set frame list*/ + + /* + * Allocate a TD, inactive, that hangs from the last QH. + * This is to avoid a bug in the PIIX that makes it run berserk + * otherwise. + */ + std = uhci_alloc_std(sc); + if (std == NULL) + return (USBD_NOMEM); + std->link.std = NULL; + std->td.td_link = htole32(UHCI_PTR_T); + std->td.td_status = htole32(0); /* inactive */ + std->td.td_token = htole32(0); + std->td.td_buffer = htole32(0); + + /* Allocate the dummy QH marking the end and used for looping the QHs.*/ + lsqh = uhci_alloc_sqh(sc); + if (lsqh == NULL) + return (USBD_NOMEM); + lsqh->hlink = NULL; + lsqh->qh.qh_hlink = htole32(UHCI_PTR_T); /* end of QH chain */ + lsqh->elink = std; + lsqh->qh.qh_elink = htole32(std->physaddr | UHCI_PTR_TD); + sc->sc_last_qh = lsqh; + + /* Allocate the dummy QH where bulk traffic will be queued. */ + bsqh = uhci_alloc_sqh(sc); + if (bsqh == NULL) + return (USBD_NOMEM); + bsqh->hlink = lsqh; + bsqh->qh.qh_hlink = htole32(lsqh->physaddr | UHCI_PTR_QH); + bsqh->elink = NULL; + bsqh->qh.qh_elink = htole32(UHCI_PTR_T); + sc->sc_bulk_start = sc->sc_bulk_end = bsqh; + + /* Allocate dummy QH where high speed control traffic will be queued. */ + chsqh = uhci_alloc_sqh(sc); + if (chsqh == NULL) + return (USBD_NOMEM); + chsqh->hlink = bsqh; + chsqh->qh.qh_hlink = htole32(bsqh->physaddr | UHCI_PTR_QH); + chsqh->elink = NULL; + chsqh->qh.qh_elink = htole32(UHCI_PTR_T); + sc->sc_hctl_start = sc->sc_hctl_end = chsqh; + + /* Allocate dummy QH where control traffic will be queued. */ + clsqh = uhci_alloc_sqh(sc); + if (clsqh == NULL) + return (USBD_NOMEM); + clsqh->hlink = chsqh; + clsqh->qh.qh_hlink = htole32(chsqh->physaddr | UHCI_PTR_QH); + clsqh->elink = NULL; + clsqh->qh.qh_elink = htole32(UHCI_PTR_T); + sc->sc_lctl_start = sc->sc_lctl_end = clsqh; + + /* + * Make all (virtual) frame list pointers point to the interrupt + * queue heads and the interrupt queue heads at the control + * queue head and point the physical frame list to the virtual. + */ + for(i = 0; i < UHCI_VFRAMELIST_COUNT; i++) { + std = uhci_alloc_std(sc); + sqh = uhci_alloc_sqh(sc); + if (std == NULL || sqh == NULL) + return (USBD_NOMEM); + std->link.sqh = sqh; + std->td.td_link = htole32(sqh->physaddr | UHCI_PTR_QH); + std->td.td_status = htole32(UHCI_TD_IOS); /* iso, inactive */ + std->td.td_token = htole32(0); + std->td.td_buffer = htole32(0); + sqh->hlink = clsqh; + sqh->qh.qh_hlink = htole32(clsqh->physaddr | UHCI_PTR_QH); + sqh->elink = NULL; + sqh->qh.qh_elink = htole32(UHCI_PTR_T); + sc->sc_vframes[i].htd = std; + sc->sc_vframes[i].etd = std; + sc->sc_vframes[i].hqh = sqh; + sc->sc_vframes[i].eqh = sqh; + for (j = i; + j < UHCI_FRAMELIST_COUNT; + j += UHCI_VFRAMELIST_COUNT) + sc->sc_pframes[j] = htole32(std->physaddr); + } + + LIST_INIT(&sc->sc_intrhead); + + STAILQ_INIT(&sc->sc_free_xfers); + + callout_init(&sc->sc_poll_handle, 0); + + /* Set up the bus struct. */ + sc->sc_bus.methods = &uhci_bus_methods; + sc->sc_bus.pipe_size = sizeof(struct uhci_pipe); + +#if defined(__NetBSD__) || defined(__OpenBSD__) + sc->sc_suspend = PWR_RESUME; + sc->sc_powerhook = powerhook_establish(uhci_power, sc); + sc->sc_shutdownhook = shutdownhook_establish(uhci_shutdown, sc); +#endif + + DPRINTFN(1,("uhci_init: enabling\n")); + UWRITE2(sc, UHCI_INTR, UHCI_INTR_TOCRCIE | UHCI_INTR_RIE | + UHCI_INTR_IOCE | UHCI_INTR_SPIE); /* enable interrupts */ + + UHCICMD(sc, UHCI_CMD_MAXP); /* Assume 64 byte packets at frame end */ + + return (uhci_run(sc, 1)); /* and here we go... */ +} + +int +uhci_detach(struct uhci_softc *sc, int flags) +{ + usbd_xfer_handle xfer; + int rv = 0; + + sc->sc_dying = 1; + + UWRITE2(sc, UHCI_INTR, 0); /* disable interrupts */ + uhci_run(sc, 0); + +#if defined(__NetBSD__) || defined(__OpenBSD__) + powerhook_disestablish(sc->sc_powerhook); + shutdownhook_disestablish(sc->sc_shutdownhook); +#endif + + /* Free all xfers associated with this HC. */ + for (;;) { + xfer = STAILQ_FIRST(&sc->sc_free_xfers); + if (xfer == NULL) + break; + STAILQ_REMOVE_HEAD(&sc->sc_free_xfers, next); + free(xfer, M_USB); + } + + /* XXX free other data structures XXX */ + usb_freemem(&sc->sc_bus, &sc->sc_dma); + + return (rv); +} + +usbd_status +uhci_allocm(struct usbd_bus *bus, usb_dma_t *dma, u_int32_t size) +{ + return (usb_allocmem(bus, size, 0, dma)); +} + +void +uhci_freem(struct usbd_bus *bus, usb_dma_t *dma) +{ + usb_freemem(bus, dma); +} + +usbd_xfer_handle +uhci_allocx(struct usbd_bus *bus) +{ + struct uhci_softc *sc = (struct uhci_softc *)bus; + usbd_xfer_handle xfer; + + xfer = STAILQ_FIRST(&sc->sc_free_xfers); + if (xfer != NULL) { + STAILQ_REMOVE_HEAD(&sc->sc_free_xfers, next); +#ifdef DIAGNOSTIC + if (xfer->busy_free != XFER_FREE) { + printf("uhci_allocx: xfer=%p not free, 0x%08x\n", xfer, + xfer->busy_free); + } +#endif + } else { + xfer = malloc(sizeof(struct uhci_xfer), M_USB, M_NOWAIT); + } + if (xfer != NULL) { + memset(xfer, 0, sizeof (struct uhci_xfer)); + UXFER(xfer)->iinfo.sc = sc; + usb_init_task(&UXFER(xfer)->abort_task, uhci_timeout_task, + xfer); + UXFER(xfer)->uhci_xfer_flags = 0; +#ifdef DIAGNOSTIC + UXFER(xfer)->iinfo.isdone = 1; + xfer->busy_free = XFER_BUSY; +#endif + } + return (xfer); +} + +void +uhci_freex(struct usbd_bus *bus, usbd_xfer_handle xfer) +{ + struct uhci_softc *sc = (struct uhci_softc *)bus; + +#ifdef DIAGNOSTIC + if (xfer->busy_free != XFER_BUSY) { + printf("uhci_freex: xfer=%p not busy, 0x%08x\n", xfer, + xfer->busy_free); + return; + } + xfer->busy_free = XFER_FREE; + if (!UXFER(xfer)->iinfo.isdone) { + printf("uhci_freex: !isdone\n"); + return; + } +#endif + STAILQ_INSERT_HEAD(&sc->sc_free_xfers, xfer, next); +} + +/* + * Shut down the controller when the system is going down. + */ +void +uhci_shutdown(void *v) +{ + uhci_softc_t *sc = v; + + DPRINTF(("uhci_shutdown: stopping the HC\n")); + uhci_run(sc, 0); /* stop the controller */ +} + +/* + * Handle suspend/resume. + * + * We need to switch to polling mode here, because this routine is + * called from an interrupt context. This is all right since we + * are almost suspended anyway. + */ +void +uhci_power(int why, void *v) +{ + uhci_softc_t *sc = v; + int cmd; + int s; + + s = splhardusb(); + cmd = UREAD2(sc, UHCI_CMD); + + DPRINTF(("uhci_power: sc=%p, why=%d (was %d), cmd=0x%x\n", + sc, why, sc->sc_suspend, cmd)); + + if (why != PWR_RESUME) { +#ifdef USB_DEBUG + if (uhcidebug > 2) + uhci_dumpregs(sc); +#endif + if (sc->sc_intr_xfer != NULL) + callout_stop(&sc->sc_poll_handle); + sc->sc_bus.use_polling++; + uhci_run(sc, 0); /* stop the controller */ + cmd &= ~UHCI_CMD_RS; + + /* save some state if BIOS doesn't */ + sc->sc_saved_frnum = UREAD2(sc, UHCI_FRNUM); + sc->sc_saved_sof = UREAD1(sc, UHCI_SOF); + + UWRITE2(sc, UHCI_INTR, 0); /* disable intrs */ + + UHCICMD(sc, cmd | UHCI_CMD_EGSM); /* enter global suspend */ + usb_delay_ms(&sc->sc_bus, USB_RESUME_WAIT); + sc->sc_suspend = why; + sc->sc_bus.use_polling--; + DPRINTF(("uhci_power: cmd=0x%x\n", UREAD2(sc, UHCI_CMD))); + } else { +#ifdef DIAGNOSTIC + if (sc->sc_suspend == PWR_RESUME) + printf("uhci_power: weird, resume without suspend.\n"); +#endif + sc->sc_bus.use_polling++; + sc->sc_suspend = why; + UWRITE2(sc, UHCI_INTR, 0); /* disable interrupts */ + uhci_globalreset(sc); /* reset the controller */ + uhci_reset(sc); + if (cmd & UHCI_CMD_RS) + uhci_run(sc, 0); /* in case BIOS has started it */ + + uhci_globalreset(sc); + uhci_reset(sc); + + /* restore saved state */ + UWRITE4(sc, UHCI_FLBASEADDR, DMAADDR(&sc->sc_dma, 0)); + UWRITE2(sc, UHCI_FRNUM, sc->sc_saved_frnum); + UWRITE1(sc, UHCI_SOF, sc->sc_saved_sof); + + UHCICMD(sc, cmd | UHCI_CMD_FGR); /* force global resume */ + usb_delay_ms(&sc->sc_bus, USB_RESUME_DELAY); + UHCICMD(sc, cmd & ~UHCI_CMD_EGSM); /* back to normal */ + UWRITE2(sc, UHCI_INTR, UHCI_INTR_TOCRCIE | UHCI_INTR_RIE | + UHCI_INTR_IOCE | UHCI_INTR_SPIE); /* re-enable intrs */ + UHCICMD(sc, UHCI_CMD_MAXP); + uhci_run(sc, 1); /* and start traffic again */ + usb_delay_ms(&sc->sc_bus, USB_RESUME_RECOVERY); + sc->sc_bus.use_polling--; + if (sc->sc_intr_xfer != NULL) + callout_reset(&sc->sc_poll_handle, sc->sc_ival, + uhci_poll_hub, sc->sc_intr_xfer); +#ifdef USB_DEBUG + if (uhcidebug > 2) + uhci_dumpregs(sc); +#endif + } + splx(s); +} + +#ifdef USB_DEBUG +static void +uhci_dumpregs(uhci_softc_t *sc) +{ + DPRINTFN(-1,("%s regs: cmd=%04x, sts=%04x, intr=%04x, frnum=%04x, " + "flbase=%08x, sof=%04x, portsc1=%04x, portsc2=%04x\n", + device_get_nameunit(sc->sc_bus.bdev), + UREAD2(sc, UHCI_CMD), + UREAD2(sc, UHCI_STS), + UREAD2(sc, UHCI_INTR), + UREAD2(sc, UHCI_FRNUM), + UREAD4(sc, UHCI_FLBASEADDR), + UREAD1(sc, UHCI_SOF), + UREAD2(sc, UHCI_PORTSC1), + UREAD2(sc, UHCI_PORTSC2))); +} + +void +uhci_dump_td(uhci_soft_td_t *p) +{ + char sbuf[128], sbuf2[128]; + + DPRINTFN(-1,("TD(%p) at %08lx = link=0x%08lx status=0x%08lx " + "token=0x%08lx buffer=0x%08lx\n", + p, (long)p->physaddr, + (long)le32toh(p->td.td_link), + (long)le32toh(p->td.td_status), + (long)le32toh(p->td.td_token), + (long)le32toh(p->td.td_buffer))); + + bitmask_snprintf((u_int32_t)le32toh(p->td.td_link), "\20\1T\2Q\3VF", + sbuf, sizeof(sbuf)); + bitmask_snprintf((u_int32_t)le32toh(p->td.td_status), + "\20\22BITSTUFF\23CRCTO\24NAK\25BABBLE\26DBUFFER\27" + "STALLED\30ACTIVE\31IOC\32ISO\33LS\36SPD", + sbuf2, sizeof(sbuf2)); + + DPRINTFN(-1,(" %s %s,errcnt=%d,actlen=%d pid=%02x,addr=%d,endpt=%d," + "D=%d,maxlen=%d\n", sbuf, sbuf2, + UHCI_TD_GET_ERRCNT(le32toh(p->td.td_status)), + UHCI_TD_GET_ACTLEN(le32toh(p->td.td_status)), + UHCI_TD_GET_PID(le32toh(p->td.td_token)), + UHCI_TD_GET_DEVADDR(le32toh(p->td.td_token)), + UHCI_TD_GET_ENDPT(le32toh(p->td.td_token)), + UHCI_TD_GET_DT(le32toh(p->td.td_token)), + UHCI_TD_GET_MAXLEN(le32toh(p->td.td_token)))); +} + +void +uhci_dump_qh(uhci_soft_qh_t *sqh) +{ + DPRINTFN(-1,("QH(%p) at %08x: hlink=%08x elink=%08x\n", sqh, + (int)sqh->physaddr, le32toh(sqh->qh.qh_hlink), + le32toh(sqh->qh.qh_elink))); +} + + +#if 1 +void +uhci_dump(void) +{ + uhci_dump_all(thesc); +} +#endif + +void +uhci_dump_all(uhci_softc_t *sc) +{ + uhci_dumpregs(sc); + printf("intrs=%d\n", sc->sc_bus.no_intrs); + /*printf("framelist[i].link = %08x\n", sc->sc_framelist[0].link);*/ + uhci_dump_qh(sc->sc_lctl_start); +} + + +void +uhci_dump_qhs(uhci_soft_qh_t *sqh) +{ + uhci_dump_qh(sqh); + + /* uhci_dump_qhs displays all the QHs and TDs from the given QH onwards + * Traverses sideways first, then down. + * + * QH1 + * QH2 + * No QH + * TD2.1 + * TD2.2 + * TD1.1 + * etc. + * + * TD2.x being the TDs queued at QH2 and QH1 being referenced from QH1. + */ + + + if (sqh->hlink != NULL && !(le32toh(sqh->qh.qh_hlink) & UHCI_PTR_T)) + uhci_dump_qhs(sqh->hlink); + else + DPRINTF(("No QH\n")); + + if (sqh->elink != NULL && !(le32toh(sqh->qh.qh_elink) & UHCI_PTR_T)) + uhci_dump_tds(sqh->elink); + else + DPRINTF(("No TD\n")); +} + +void +uhci_dump_tds(uhci_soft_td_t *std) +{ + uhci_soft_td_t *td; + + for(td = std; td != NULL; td = td->link.std) { + uhci_dump_td(td); + + /* Check whether the link pointer in this TD marks + * the link pointer as end of queue. This avoids + * printing the free list in case the queue/TD has + * already been moved there (seatbelt). + */ + if (le32toh(td->td.td_link) & UHCI_PTR_T || + le32toh(td->td.td_link) == 0) + break; + } +} + +static void +uhci_dump_ii(uhci_intr_info_t *ii) +{ + usbd_pipe_handle pipe; + usb_endpoint_descriptor_t *ed; + usbd_device_handle dev; + +#ifdef DIAGNOSTIC +#define DONE ii->isdone +#else +#define DONE 0 +#endif + if (ii == NULL) { + printf("ii NULL\n"); + return; + } + if (ii->xfer == NULL) { + printf("ii %p: done=%d xfer=NULL\n", + ii, DONE); + return; + } + pipe = ii->xfer->pipe; + if (pipe == NULL) { + printf("ii %p: done=%d xfer=%p pipe=NULL\n", + ii, DONE, ii->xfer); + return; + } + if (pipe->endpoint == NULL) { + printf("ii %p: done=%d xfer=%p pipe=%p pipe->endpoint=NULL\n", + ii, DONE, ii->xfer, pipe); + return; + } + if (pipe->device == NULL) { + printf("ii %p: done=%d xfer=%p pipe=%p pipe->device=NULL\n", + ii, DONE, ii->xfer, pipe); + return; + } + ed = pipe->endpoint->edesc; + dev = pipe->device; + printf("ii %p: done=%d xfer=%p dev=%p vid=0x%04x pid=0x%04x addr=%d pipe=%p ep=0x%02x attr=0x%02x\n", + ii, DONE, ii->xfer, dev, + UGETW(dev->ddesc.idVendor), + UGETW(dev->ddesc.idProduct), + dev->address, pipe, + ed->bEndpointAddress, ed->bmAttributes); +#undef DONE +} + +void uhci_dump_iis(struct uhci_softc *sc); +void +uhci_dump_iis(struct uhci_softc *sc) +{ + uhci_intr_info_t *ii; + + printf("intr_info list:\n"); + for (ii = LIST_FIRST(&sc->sc_intrhead); ii; ii = LIST_NEXT(ii, list)) + uhci_dump_ii(ii); +} + +void iidump(void); +void iidump(void) { uhci_dump_iis(thesc); } + +#endif + +/* + * This routine is executed periodically and simulates interrupts + * from the root controller interrupt pipe for port status change. + */ +void +uhci_poll_hub(void *addr) +{ + usbd_xfer_handle xfer = addr; + usbd_pipe_handle pipe = xfer->pipe; + usbd_device_handle dev = pipe->device; + uhci_softc_t *sc = (uhci_softc_t *)dev->bus; + int s; + u_char *p; + + DPRINTFN(20, ("uhci_poll_hub\n")); + + callout_reset(&sc->sc_poll_handle, sc->sc_ival, uhci_poll_hub, xfer); + + p = xfer->buffer; + p[0] = 0; + if (UREAD2(sc, UHCI_PORTSC1) & (UHCI_PORTSC_CSC|UHCI_PORTSC_OCIC)) + p[0] |= 1<<1; + if (UREAD2(sc, UHCI_PORTSC2) & (UHCI_PORTSC_CSC|UHCI_PORTSC_OCIC)) + p[0] |= 1<<2; + if (p[0] == 0) + /* No change, try again in a while */ + return; + + xfer->actlen = 1; + xfer->status = USBD_NORMAL_COMPLETION; + s = splusb(); + dev->bus->intr_context++; + uhci_transfer_complete(xfer); + dev->bus->intr_context--; + splx(s); +} + +void +uhci_root_intr_done(usbd_xfer_handle xfer) +{ +} + +void +uhci_root_ctrl_done(usbd_xfer_handle xfer) +{ +} + +/* + * Let the last QH loop back to the high speed control transfer QH. + * This is what intel calls "bandwidth reclamation" and improves + * USB performance a lot for some devices. + * If we are already looping, just count it. + */ +void +uhci_add_loop(uhci_softc_t *sc) { +#ifdef USB_DEBUG + if (uhcinoloop) + return; +#endif + if (++sc->sc_loops == 1) { + DPRINTFN(5,("uhci_start_loop: add\n")); + /* Note, we don't loop back the soft pointer. */ + sc->sc_last_qh->qh.qh_hlink = + htole32(sc->sc_hctl_start->physaddr | UHCI_PTR_QH); + } +} + +void +uhci_rem_loop(uhci_softc_t *sc) { +#ifdef USB_DEBUG + if (uhcinoloop) + return; +#endif + if (--sc->sc_loops == 0) { + DPRINTFN(5,("uhci_end_loop: remove\n")); + sc->sc_last_qh->qh.qh_hlink = htole32(UHCI_PTR_T); + } +} + +/* Add high speed control QH, called at splusb(). */ +void +uhci_add_hs_ctrl(uhci_softc_t *sc, uhci_soft_qh_t *sqh) +{ + uhci_soft_qh_t *eqh; + + SPLUSBCHECK; + + DPRINTFN(10, ("uhci_add_ctrl: sqh=%p\n", sqh)); + eqh = sc->sc_hctl_end; + sqh->hlink = eqh->hlink; + sqh->qh.qh_hlink = eqh->qh.qh_hlink; + eqh->hlink = sqh; + eqh->qh.qh_hlink = htole32(sqh->physaddr | UHCI_PTR_QH); + sc->sc_hctl_end = sqh; +#ifdef UHCI_CTL_LOOP + uhci_add_loop(sc); +#endif +} + +/* Remove high speed control QH, called at splusb(). */ +void +uhci_remove_hs_ctrl(uhci_softc_t *sc, uhci_soft_qh_t *sqh) +{ + uhci_soft_qh_t *pqh; + + SPLUSBCHECK; + + DPRINTFN(10, ("uhci_remove_hs_ctrl: sqh=%p\n", sqh)); +#ifdef UHCI_CTL_LOOP + uhci_rem_loop(sc); +#endif + /* + * The T bit should be set in the elink of the QH so that the HC + * doesn't follow the pointer. This condition may fail if the + * the transferred packet was short so that the QH still points + * at the last used TD. + * In this case we set the T bit and wait a little for the HC + * to stop looking at the TD. + */ + if (!(sqh->qh.qh_elink & htole32(UHCI_PTR_T))) { + sqh->qh.qh_elink = htole32(UHCI_PTR_T); + delay(UHCI_QH_REMOVE_DELAY); + } + + pqh = uhci_find_prev_qh(sc->sc_hctl_start, sqh); + pqh->hlink = sqh->hlink; + pqh->qh.qh_hlink = sqh->qh.qh_hlink; + delay(UHCI_QH_REMOVE_DELAY); + if (sc->sc_hctl_end == sqh) + sc->sc_hctl_end = pqh; +} + +/* Add low speed control QH, called at splusb(). */ +void +uhci_add_ls_ctrl(uhci_softc_t *sc, uhci_soft_qh_t *sqh) +{ + uhci_soft_qh_t *eqh; + + SPLUSBCHECK; + + DPRINTFN(10, ("uhci_add_ls_ctrl: sqh=%p\n", sqh)); + eqh = sc->sc_lctl_end; + sqh->hlink = eqh->hlink; + sqh->qh.qh_hlink = eqh->qh.qh_hlink; + eqh->hlink = sqh; + eqh->qh.qh_hlink = htole32(sqh->physaddr | UHCI_PTR_QH); + sc->sc_lctl_end = sqh; +} + +/* Remove low speed control QH, called at splusb(). */ +void +uhci_remove_ls_ctrl(uhci_softc_t *sc, uhci_soft_qh_t *sqh) +{ + uhci_soft_qh_t *pqh; + + SPLUSBCHECK; + + DPRINTFN(10, ("uhci_remove_ls_ctrl: sqh=%p\n", sqh)); + /* See comment in uhci_remove_hs_ctrl() */ + if (!(sqh->qh.qh_elink & htole32(UHCI_PTR_T))) { + sqh->qh.qh_elink = htole32(UHCI_PTR_T); + delay(UHCI_QH_REMOVE_DELAY); + } + pqh = uhci_find_prev_qh(sc->sc_lctl_start, sqh); + pqh->hlink = sqh->hlink; + pqh->qh.qh_hlink = sqh->qh.qh_hlink; + delay(UHCI_QH_REMOVE_DELAY); + if (sc->sc_lctl_end == sqh) + sc->sc_lctl_end = pqh; +} + +/* Add bulk QH, called at splusb(). */ +void +uhci_add_bulk(uhci_softc_t *sc, uhci_soft_qh_t *sqh) +{ + uhci_soft_qh_t *eqh; + + SPLUSBCHECK; + + DPRINTFN(10, ("uhci_add_bulk: sqh=%p\n", sqh)); + eqh = sc->sc_bulk_end; + sqh->hlink = eqh->hlink; + sqh->qh.qh_hlink = eqh->qh.qh_hlink; + eqh->hlink = sqh; + eqh->qh.qh_hlink = htole32(sqh->physaddr | UHCI_PTR_QH); + sc->sc_bulk_end = sqh; + uhci_add_loop(sc); +} + +/* Remove bulk QH, called at splusb(). */ +void +uhci_remove_bulk(uhci_softc_t *sc, uhci_soft_qh_t *sqh) +{ + uhci_soft_qh_t *pqh; + + SPLUSBCHECK; + + DPRINTFN(10, ("uhci_remove_bulk: sqh=%p\n", sqh)); + uhci_rem_loop(sc); + /* See comment in uhci_remove_hs_ctrl() */ + if (!(sqh->qh.qh_elink & htole32(UHCI_PTR_T))) { + sqh->qh.qh_elink = htole32(UHCI_PTR_T); + delay(UHCI_QH_REMOVE_DELAY); + } + pqh = uhci_find_prev_qh(sc->sc_bulk_start, sqh); + pqh->hlink = sqh->hlink; + pqh->qh.qh_hlink = sqh->qh.qh_hlink; + delay(UHCI_QH_REMOVE_DELAY); + if (sc->sc_bulk_end == sqh) + sc->sc_bulk_end = pqh; +} + +static int uhci_intr1(uhci_softc_t *); + +int +uhci_intr(void *arg) +{ + uhci_softc_t *sc = arg; + + if (sc->sc_dying) + return (0); + + DPRINTFN(15,("uhci_intr: real interrupt\n")); + if (sc->sc_bus.use_polling) { +#ifdef DIAGNOSTIC + printf("uhci_intr: ignored interrupt while polling\n"); +#endif + return (0); + } + return (uhci_intr1(sc)); +} + +int +uhci_intr1(uhci_softc_t *sc) +{ + + int status; + int ack; + + /* + * It can happen that an interrupt will be delivered to + * us before the device has been fully attached and the + * softc struct has been configured. Usually this happens + * when kldloading the USB support as a module after the + * system has been booted. If we detect this condition, + * we need to squelch the unwanted interrupts until we're + * ready for them. + */ + if (sc->sc_bus.bdev == NULL) { + UWRITE2(sc, UHCI_STS, 0xFFFF); /* ack pending interrupts */ + uhci_run(sc, 0); /* stop the controller */ + UWRITE2(sc, UHCI_INTR, 0); /* disable interrupts */ + return(0); + } + +#ifdef USB_DEBUG + if (uhcidebug > 15) { + DPRINTF(("%s: uhci_intr1\n", device_get_nameunit(sc->sc_bus.bdev))); + uhci_dumpregs(sc); + } +#endif + status = UREAD2(sc, UHCI_STS) & UHCI_STS_ALLINTRS; + if (status == 0) /* The interrupt was not for us. */ + return (0); + +#if defined(DIAGNOSTIC) && defined(__NetBSD__) + if (sc->sc_suspend != PWR_RESUME) + printf("uhci_intr: suspended sts=0x%x\n", status); +#endif + + if (sc->sc_suspend != PWR_RESUME) { + printf("%s: interrupt while not operating ignored\n", + device_get_nameunit(sc->sc_bus.bdev)); + UWRITE2(sc, UHCI_STS, status); /* acknowledge the ints */ + return (0); + } + + ack = 0; + if (status & UHCI_STS_USBINT) + ack |= UHCI_STS_USBINT; + if (status & UHCI_STS_USBEI) + ack |= UHCI_STS_USBEI; + if (status & UHCI_STS_RD) { + ack |= UHCI_STS_RD; +#ifdef USB_DEBUG + printf("%s: resume detect\n", device_get_nameunit(sc->sc_bus.bdev)); +#endif + } + if (status & UHCI_STS_HSE) { + ack |= UHCI_STS_HSE; + printf("%s: host system error\n", device_get_nameunit(sc->sc_bus.bdev)); + } + if (status & UHCI_STS_HCPE) { + ack |= UHCI_STS_HCPE; + printf("%s: host controller process error\n", + device_get_nameunit(sc->sc_bus.bdev)); + } + if (status & UHCI_STS_HCH) { + /* no acknowledge needed */ + if (!sc->sc_dying) { + printf("%s: host controller halted\n", + device_get_nameunit(sc->sc_bus.bdev)); +#ifdef USB_DEBUG + uhci_dump_all(sc); +#endif + } + sc->sc_dying = 1; + } + + if (!ack) + return (0); /* nothing to acknowledge */ + UWRITE2(sc, UHCI_STS, ack); /* acknowledge the ints */ + + sc->sc_bus.no_intrs++; + usb_schedsoftintr(&sc->sc_bus); + + DPRINTFN(15, ("%s: uhci_intr: exit\n", device_get_nameunit(sc->sc_bus.bdev))); + + return (1); +} + +void +uhci_softintr(void *v) +{ + uhci_softc_t *sc = v; + uhci_intr_info_t *ii, *nextii; + + DPRINTFN(10,("%s: uhci_softintr (%d)\n", device_get_nameunit(sc->sc_bus.bdev), + sc->sc_bus.intr_context)); + + sc->sc_bus.intr_context++; + + /* + * Interrupts on UHCI really suck. When the host controller + * interrupts because a transfer is completed there is no + * way of knowing which transfer it was. You can scan down + * the TDs and QHs of the previous frame to limit the search, + * but that assumes that the interrupt was not delayed by more + * than 1 ms, which may not always be true (e.g. after debug + * output on a slow console). + * We scan all interrupt descriptors to see if any have + * completed. + */ + LIST_FOREACH_SAFE(ii, &sc->sc_intrhead, list, nextii) + uhci_check_intr(sc, ii); + +#ifdef USB_USE_SOFTINTR + if (sc->sc_softwake) { + sc->sc_softwake = 0; + wakeup(&sc->sc_softwake); + } +#endif /* USB_USE_SOFTINTR */ + + sc->sc_bus.intr_context--; +} + +/* Check for an interrupt. */ +void +uhci_check_intr(uhci_softc_t *sc, uhci_intr_info_t *ii) +{ + uhci_soft_td_t *std, *lstd; + u_int32_t status; + + DPRINTFN(15, ("uhci_check_intr: ii=%p\n", ii)); +#ifdef DIAGNOSTIC + if (ii == NULL) { + printf("uhci_check_intr: no ii? %p\n", ii); + return; + } +#endif + if (ii->xfer->status == USBD_CANCELLED || + ii->xfer->status == USBD_TIMEOUT) { + DPRINTF(("uhci_check_intr: aborted xfer=%p\n", ii->xfer)); + return; + } + + if (ii->stdstart == NULL) + return; + lstd = ii->stdend; +#ifdef DIAGNOSTIC + if (lstd == NULL) { + printf("uhci_check_intr: std==0\n"); + return; + } +#endif + /* + * If the last TD is still active we need to check whether there + * is an error somewhere in the middle, or whether there was a + * short packet (SPD and not ACTIVE). + */ + if (le32toh(lstd->td.td_status) & UHCI_TD_ACTIVE) { + DPRINTFN(12, ("uhci_check_intr: active ii=%p\n", ii)); + for (std = ii->stdstart; std != lstd; std = std->link.std) { + status = le32toh(std->td.td_status); + /* If there's an active TD the xfer isn't done. */ + if (status & UHCI_TD_ACTIVE) + break; + /* Any kind of error makes the xfer done. */ + if (status & UHCI_TD_STALLED) + goto done; + /* We want short packets, and it is short: it's done */ + if ((status & UHCI_TD_SPD) && + UHCI_TD_GET_ACTLEN(status) < + UHCI_TD_GET_MAXLEN(le32toh(std->td.td_token))) + goto done; + } + DPRINTFN(12, ("uhci_check_intr: ii=%p std=%p still active\n", + ii, ii->stdstart)); + return; + } + done: + DPRINTFN(12, ("uhci_check_intr: ii=%p done\n", ii)); + callout_stop(&ii->xfer->timeout_handle); + usb_rem_task(ii->xfer->pipe->device, &UXFER(ii->xfer)->abort_task); + uhci_idone(ii); +} + +/* Called at splusb() */ +void +uhci_idone(uhci_intr_info_t *ii) +{ + usbd_xfer_handle xfer = ii->xfer; + struct uhci_pipe *upipe = (struct uhci_pipe *)xfer->pipe; + uhci_soft_td_t *std; + u_int32_t status = 0, nstatus; + int actlen; + + DPRINTFN(12, ("uhci_idone: ii=%p\n", ii)); +#ifdef DIAGNOSTIC + { + int s = splhigh(); + if (ii->isdone) { + splx(s); +#ifdef USB_DEBUG + printf("uhci_idone: ii is done!\n "); + uhci_dump_ii(ii); +#else + printf("uhci_idone: ii=%p is done!\n", ii); +#endif + return; + } + ii->isdone = 1; + splx(s); + } +#endif + + if (xfer->nframes != 0) { + /* Isoc transfer, do things differently. */ + uhci_soft_td_t **stds = upipe->u.iso.stds; + int i, n, nframes, len; + + DPRINTFN(5,("uhci_idone: ii=%p isoc ready\n", ii)); + + nframes = xfer->nframes; + actlen = 0; + n = UXFER(xfer)->curframe; + for (i = 0; i < nframes; i++) { + std = stds[n]; +#ifdef USB_DEBUG + if (uhcidebug > 5) { + DPRINTFN(-1,("uhci_idone: isoc TD %d\n", i)); + uhci_dump_td(std); + } +#endif + if (++n >= UHCI_VFRAMELIST_COUNT) + n = 0; + status = le32toh(std->td.td_status); + len = UHCI_TD_GET_ACTLEN(status); + xfer->frlengths[i] = len; + actlen += len; + } + upipe->u.iso.inuse -= nframes; + xfer->actlen = actlen; + xfer->status = USBD_NORMAL_COMPLETION; + goto end; + } + +#ifdef USB_DEBUG + DPRINTFN(10, ("uhci_idone: ii=%p, xfer=%p, pipe=%p ready\n", + ii, xfer, upipe)); + if (uhcidebug > 10) + uhci_dump_tds(ii->stdstart); +#endif + + /* The transfer is done, compute actual length and status. */ + actlen = 0; + for (std = ii->stdstart; std != NULL; std = std->link.std) { + nstatus = le32toh(std->td.td_status); + if (nstatus & UHCI_TD_ACTIVE) + break; + + status = nstatus; + if (UHCI_TD_GET_PID(le32toh(std->td.td_token)) != + UHCI_TD_PID_SETUP) + actlen += UHCI_TD_GET_ACTLEN(status); + else { + /* + * UHCI will report CRCTO in addition to a STALL or NAK + * for a SETUP transaction. See section 3.2.2, "TD + * CONTROL AND STATUS". + */ + if (status & (UHCI_TD_STALLED | UHCI_TD_NAK)) + status &= ~UHCI_TD_CRCTO; + } + } + /* If there are left over TDs we need to update the toggle. */ + if (std != NULL) + upipe->nexttoggle = UHCI_TD_GET_DT(le32toh(std->td.td_token)); + + status &= UHCI_TD_ERROR; + DPRINTFN(10, ("uhci_idone: actlen=%d, status=0x%x\n", + actlen, status)); + xfer->actlen = actlen; + if (status != 0) { +#ifdef USB_DEBUG + char sbuf[128]; + + bitmask_snprintf((u_int32_t)status, + "\20\22BITSTUFF\23CRCTO\24NAK\25" + "BABBLE\26DBUFFER\27STALLED\30ACTIVE", + sbuf, sizeof(sbuf)); + + DPRINTFN((status == UHCI_TD_STALLED)*10, + ("uhci_idone: error, addr=%d, endpt=0x%02x, " + "status 0x%s\n", + xfer->pipe->device->address, + xfer->pipe->endpoint->edesc->bEndpointAddress, + sbuf)); +#endif + + if (status == UHCI_TD_STALLED) + xfer->status = USBD_STALLED; + else + xfer->status = USBD_IOERROR; /* more info XXX */ + } else { + xfer->status = USBD_NORMAL_COMPLETION; + } + + end: + uhci_transfer_complete(xfer); + DPRINTFN(12, ("uhci_idone: ii=%p done\n", ii)); +} + +/* + * Called when a request does not complete. + */ +void +uhci_timeout(void *addr) +{ + uhci_intr_info_t *ii = addr; + struct uhci_xfer *uxfer = UXFER(ii->xfer); + struct uhci_pipe *upipe = (struct uhci_pipe *)uxfer->xfer.pipe; + uhci_softc_t *sc = (uhci_softc_t *)upipe->pipe.device->bus; + + DPRINTF(("uhci_timeout: uxfer=%p\n", uxfer)); + + if (sc->sc_dying) { + uhci_abort_xfer(&uxfer->xfer, USBD_TIMEOUT); + return; + } + + /* Execute the abort in a process context. */ + usb_add_task(uxfer->xfer.pipe->device, &uxfer->abort_task, + USB_TASKQ_HC); +} + +void +uhci_timeout_task(void *addr) +{ + usbd_xfer_handle xfer = addr; + int s; + + DPRINTF(("uhci_timeout_task: xfer=%p\n", xfer)); + + s = splusb(); + uhci_abort_xfer(xfer, USBD_TIMEOUT); + splx(s); +} + +/* + * Wait here until controller claims to have an interrupt. + * Then call uhci_intr and return. Use timeout to avoid waiting + * too long. + * Only used during boot when interrupts are not enabled yet. + */ +void +uhci_waitintr(uhci_softc_t *sc, usbd_xfer_handle xfer) +{ + int timo = xfer->timeout; + uhci_intr_info_t *ii; + + DPRINTFN(10,("uhci_waitintr: timeout = %dms\n", timo)); + + xfer->status = USBD_IN_PROGRESS; + for (; timo >= 0; timo--) { + usb_delay_ms(&sc->sc_bus, 1); + DPRINTFN(20,("uhci_waitintr: 0x%04x\n", UREAD2(sc, UHCI_STS))); + if (UREAD2(sc, UHCI_STS) & UHCI_STS_ALLINTRS) + uhci_intr1(sc); + if (xfer->status != USBD_IN_PROGRESS) + return; + } + + /* Timeout */ + DPRINTF(("uhci_waitintr: timeout\n")); + for (ii = LIST_FIRST(&sc->sc_intrhead); + ii != NULL && ii->xfer != xfer; + ii = LIST_NEXT(ii, list)) + ; +#ifdef DIAGNOSTIC + if (ii == NULL) + panic("uhci_waitintr: lost intr_info"); +#endif + uhci_idone(ii); +} + +void +uhci_poll(struct usbd_bus *bus) +{ + uhci_softc_t *sc = (uhci_softc_t *)bus; + + if (UREAD2(sc, UHCI_STS) & UHCI_STS_ALLINTRS) + uhci_intr1(sc); +} + +void +uhci_reset(uhci_softc_t *sc) +{ + int n; + + UHCICMD(sc, UHCI_CMD_HCRESET); + /* The reset bit goes low when the controller is done. */ + for (n = 0; n < UHCI_RESET_TIMEOUT && + (UREAD2(sc, UHCI_CMD) & UHCI_CMD_HCRESET); n++) + usb_delay_ms(&sc->sc_bus, 1); + if (n >= UHCI_RESET_TIMEOUT) + printf("%s: controller did not reset\n", + device_get_nameunit(sc->sc_bus.bdev)); +} + +usbd_status +uhci_run(uhci_softc_t *sc, int run) +{ + int s, n, running; + u_int16_t cmd; + + run = run != 0; + s = splhardusb(); + DPRINTF(("uhci_run: setting run=%d\n", run)); + cmd = UREAD2(sc, UHCI_CMD); + if (run) + cmd |= UHCI_CMD_RS; + else + cmd &= ~UHCI_CMD_RS; + UHCICMD(sc, cmd); + for(n = 0; n < 10; n++) { + running = !(UREAD2(sc, UHCI_STS) & UHCI_STS_HCH); + /* return when we've entered the state we want */ + if (run == running) { + splx(s); + DPRINTF(("uhci_run: done cmd=0x%x sts=0x%x\n", + UREAD2(sc, UHCI_CMD), UREAD2(sc, UHCI_STS))); + return (USBD_NORMAL_COMPLETION); + } + usb_delay_ms(&sc->sc_bus, 1); + } + splx(s); + printf("%s: cannot %s\n", device_get_nameunit(sc->sc_bus.bdev), + run ? "start" : "stop"); + return (USBD_IOERROR); +} + +/* + * Memory management routines. + * uhci_alloc_std allocates TDs + * uhci_alloc_sqh allocates QHs + * These two routines do their own free list management, + * partly for speed, partly because allocating DMAable memory + * has page size granularaity so much memory would be wasted if + * only one TD/QH (32 bytes) was placed in each allocated chunk. + */ + +uhci_soft_td_t * +uhci_alloc_std(uhci_softc_t *sc) +{ + uhci_soft_td_t *std; + usbd_status err; + int i, offs; + usb_dma_t dma; + + if (sc->sc_freetds == NULL) { + DPRINTFN(2,("uhci_alloc_std: allocating chunk\n")); + err = usb_allocmem(&sc->sc_bus, UHCI_STD_SIZE * UHCI_STD_CHUNK, + UHCI_TD_ALIGN, &dma); + if (err) + return (0); + for(i = 0; i < UHCI_STD_CHUNK; i++) { + offs = i * UHCI_STD_SIZE; + std = KERNADDR(&dma, offs); + std->physaddr = DMAADDR(&dma, offs); + std->link.std = sc->sc_freetds; + std->aux_dma.block = NULL; + std->aux_data = NULL; + std->aux_len = 0; + sc->sc_freetds = std; + } + } + std = sc->sc_freetds; + sc->sc_freetds = std->link.std; + memset(&std->td, 0, sizeof(uhci_td_t)); + return std; +} + +void +uhci_free_std(uhci_softc_t *sc, uhci_soft_td_t *std) +{ +#ifdef DIAGNOSTIC +#define TD_IS_FREE 0x12345678 + if (le32toh(std->td.td_token) == TD_IS_FREE) { + printf("uhci_free_std: freeing free TD %p\n", std); + return; + } + std->td.td_token = htole32(TD_IS_FREE); +#endif + if (std->aux_dma.block != NULL) { + usb_freemem(&sc->sc_bus, &std->aux_dma); + std->aux_dma.block = NULL; + std->aux_data = NULL; + std->aux_len = 0; + } + std->link.std = sc->sc_freetds; + sc->sc_freetds = std; +} + +uhci_soft_qh_t * +uhci_alloc_sqh(uhci_softc_t *sc) +{ + uhci_soft_qh_t *sqh; + usbd_status err; + int i, offs; + usb_dma_t dma; + + if (sc->sc_freeqhs == NULL) { + DPRINTFN(2, ("uhci_alloc_sqh: allocating chunk\n")); + err = usb_allocmem(&sc->sc_bus, UHCI_SQH_SIZE * UHCI_SQH_CHUNK, + UHCI_QH_ALIGN, &dma); + if (err) + return (0); + for(i = 0; i < UHCI_SQH_CHUNK; i++) { + offs = i * UHCI_SQH_SIZE; + sqh = KERNADDR(&dma, offs); + sqh->physaddr = DMAADDR(&dma, offs); + sqh->hlink = sc->sc_freeqhs; + sc->sc_freeqhs = sqh; + } + } + sqh = sc->sc_freeqhs; + sc->sc_freeqhs = sqh->hlink; + memset(&sqh->qh, 0, sizeof(uhci_qh_t)); + return (sqh); +} + +void +uhci_free_sqh(uhci_softc_t *sc, uhci_soft_qh_t *sqh) +{ + sqh->hlink = sc->sc_freeqhs; + sc->sc_freeqhs = sqh; +} + +void +uhci_free_std_chain(uhci_softc_t *sc, uhci_soft_td_t *std, + uhci_soft_td_t *stdend) +{ + uhci_soft_td_t *p; + + for (; std != stdend; std = p) { + p = std->link.std; + uhci_free_std(sc, std); + } +} + +usbd_status +uhci_alloc_std_chain(struct uhci_pipe *upipe, uhci_softc_t *sc, int len, + int rd, u_int16_t flags, usbd_xfer_handle xfer, + uhci_soft_td_t **sp, uhci_soft_td_t **ep) +{ + struct usb_dma_mapping *dma = &xfer->dmamap; + uhci_soft_td_t *p, *prevp, *startp; + int err, i, ntd, l, tog, maxp, seg, segoff; + u_int32_t status; + int addr = upipe->pipe.device->address; + int endpt = upipe->pipe.endpoint->edesc->bEndpointAddress; + + DPRINTFN(8, ("uhci_alloc_std_chain: addr=%d endpt=%d len=%d speed=%d " + "flags=0x%x\n", addr, UE_GET_ADDR(endpt), len, + upipe->pipe.device->speed, flags)); + maxp = UGETW(upipe->pipe.endpoint->edesc->wMaxPacketSize); + if (maxp == 0) { + printf("uhci_alloc_std_chain: maxp=0\n"); + return (USBD_INVAL); + } + ntd = (len + maxp - 1) / maxp; + if (len == 0) + flags |= USBD_FORCE_SHORT_XFER; + if ((flags & USBD_FORCE_SHORT_XFER) && len % maxp == 0) + ntd++; + DPRINTFN(10, ("uhci_alloc_std_chain: maxp=%d ntd=%d\n", maxp, ntd)); + KASSERT(ntd > 0, ("uhci_alloc_std_chain: ntd=0")); + tog = upipe->nexttoggle; + prevp = NULL; + startp = NULL; + status = UHCI_TD_ZERO_ACTLEN(UHCI_TD_SET_ERRCNT(3) | UHCI_TD_ACTIVE); + if (upipe->pipe.device->speed == USB_SPEED_LOW) + status |= UHCI_TD_LS; + if (flags & USBD_SHORT_XFER_OK) + status |= UHCI_TD_SPD; + seg = 0; + segoff = 0; + for (i = 0; i < ntd; i++) { + p = uhci_alloc_std(sc); + if (p == NULL) { + uhci_free_std_chain(sc, startp, NULL); + return (USBD_NOMEM); + } + p->link.std = NULL; + if (prevp != NULL) { + prevp->link.std = p; + prevp->td.td_link = htole32(p->physaddr | UHCI_PTR_VF | + UHCI_PTR_TD); + } else { + startp = p; + } + p->td.td_status = htole32(status); + if (i == ntd - 1) { + /* last TD */ + l = len % maxp; + if (l == 0 && !(flags & USBD_FORCE_SHORT_XFER)) + l = maxp; + *ep = p; + } else + l = maxp; + p->td.td_token = + htole32(rd ? UHCI_TD_IN (l, endpt, addr, tog) : + UHCI_TD_OUT(l, endpt, addr, tog)); + + KASSERT(seg < dma->nsegs || l == 0, + ("uhci_alloc_std_chain: too few segments")); + if (l == 0) { + p->td.td_buffer = 0; + } else if (l > dma->segs[seg].ds_len - segoff) { + /* UHCI can't handle non-contiguous data. */ + err = uhci_aux_dma_alloc(sc, p, (char *)xfer->buffer + + i * maxp, l); + if (err) { + uhci_free_std_chain(sc, startp, NULL); + return (err); + } + p->td.td_buffer = htole32(uhci_aux_dma_prepare(p, rd)); + l -= dma->segs[seg].ds_len - segoff; + seg++; + KASSERT(seg < dma->nsegs, + ("uhci_alloc_std_chain: too few segments 2")); + segoff = 0; + } else { + p->td.td_buffer = htole32(dma->segs[seg].ds_addr + + segoff); + } + segoff += l; + if (l > 0 && segoff >= dma->segs[seg].ds_len) { + KASSERT(segoff == dma->segs[seg].ds_len, + ("uhci_alloc_std_chain: overlap")); + if (i * maxp + l != len) { + seg++; + segoff = 0; + } + } + prevp = p; + tog ^= 1; + } + prevp->td.td_link = htole32(UHCI_PTR_T | UHCI_PTR_VF | UHCI_PTR_TD); + upipe->nexttoggle = tog; + *sp = startp; + DPRINTFN(10, ("uhci_alloc_std_chain: nexttog=%d\n", + upipe->nexttoggle)); + return (USBD_NORMAL_COMPLETION); +} + +/* + * Allocate a physically contiguous buffer to handle cases where UHCI + * cannot handle a packet because it is not physically contiguous. + * If the usb_dma_t was already allocated this just ensures it is + * large enough for the specified size. + */ +static usbd_status +uhci_aux_dma_alloc(uhci_softc_t *sc, uhci_soft_td_t *std, void *data, int len) +{ + int err, align; + + if (std->aux_dma.block == NULL || std->aux_dma.block->size < len) { + /* Align to avoid crossing a page boundary. */ + if (powerof2(len)) + align = len; + else + align = 1 << fls(len); + + if (std->aux_dma.block != NULL) + usb_freemem(&sc->sc_bus, &std->aux_dma); + std->aux_dma.block = NULL; + err = usb_allocmem(&sc->sc_bus, len, align, &std->aux_dma); + if (err) + return (err); + } + std->aux_data = data; + std->aux_len = len; + + return (USBD_NORMAL_COMPLETION); +} + +static uhci_physaddr_t +uhci_aux_dma_prepare(uhci_soft_td_t *std, int isread) +{ + if (!isread) { + bcopy(std->aux_data, KERNADDR(&std->aux_dma, 0), std->aux_len); + bus_dmamap_sync(std->aux_dma.block->tag, + std->aux_dma.block->map, BUS_DMASYNC_PREWRITE); + } + + return (DMAADDR(&std->aux_dma, 0)); +} + +static void +uhci_aux_dma_complete(uhci_soft_td_t *std, int isread) +{ + if (isread) { + bus_dmamap_sync(std->aux_dma.block->tag, + std->aux_dma.block->map, BUS_DMASYNC_POSTREAD); + bcopy(KERNADDR(&std->aux_dma, 0), std->aux_data, std->aux_len); + } +} + +void +uhci_device_clear_toggle(usbd_pipe_handle pipe) +{ + struct uhci_pipe *upipe = (struct uhci_pipe *)pipe; + upipe->nexttoggle = 0; +} + +void +uhci_noop(usbd_pipe_handle pipe) +{ +} + +usbd_status +uhci_device_bulk_transfer(usbd_xfer_handle xfer) +{ + usbd_status err; + + /* Insert last in queue. */ + err = usb_insert_transfer(xfer); + if (err) + return (err); + + /* + * Pipe isn't running (otherwise err would be USBD_INPROG), + * so start it first. + */ + return (uhci_device_bulk_start(STAILQ_FIRST(&xfer->pipe->queue))); +} + +usbd_status +uhci_device_bulk_start(usbd_xfer_handle xfer) +{ + struct uhci_pipe *upipe = (struct uhci_pipe *)xfer->pipe; + usbd_device_handle dev = upipe->pipe.device; + uhci_softc_t *sc = (uhci_softc_t *)dev->bus; + uhci_intr_info_t *ii = &UXFER(xfer)->iinfo; + uhci_soft_td_t *data, *dataend; + uhci_soft_qh_t *sqh; + usbd_status err; + int len, isread, endpt; + int s; + + DPRINTFN(3, ("uhci_device_bulk_start: xfer=%p len=%d flags=%d ii=%p\n", + xfer, xfer->length, xfer->flags, ii)); + + if (sc->sc_dying) + return (USBD_IOERROR); + +#ifdef DIAGNOSTIC + if (xfer->rqflags & URQ_REQUEST) + panic("uhci_device_bulk_transfer: a request"); +#endif + + len = xfer->length; + endpt = upipe->pipe.endpoint->edesc->bEndpointAddress; + isread = UE_GET_DIR(endpt) == UE_DIR_IN; + sqh = upipe->u.bulk.sqh; + + upipe->u.bulk.isread = isread; + upipe->u.bulk.length = len; + + err = uhci_alloc_std_chain(upipe, sc, len, isread, xfer->flags, xfer, + &data, &dataend); + if (err) + return (err); + dataend->td.td_status |= htole32(UHCI_TD_IOC); + +#ifdef USB_DEBUG + if (uhcidebug > 8) { + DPRINTF(("uhci_device_bulk_transfer: data(1)\n")); + uhci_dump_tds(data); + } +#endif + + /* Set up interrupt info. */ + ii->xfer = xfer; + ii->stdstart = data; + ii->stdend = dataend; +#ifdef DIAGNOSTIC + if (!ii->isdone) { + printf("uhci_device_bulk_transfer: not done, ii=%p\n", ii); + } + ii->isdone = 0; +#endif + + sqh->elink = data; + sqh->qh.qh_elink = htole32(data->physaddr | UHCI_PTR_TD); + + s = splusb(); + uhci_add_bulk(sc, sqh); + uhci_add_intr_info(sc, ii); + + if (xfer->timeout && !sc->sc_bus.use_polling) { + callout_reset(&xfer->timeout_handle, MS_TO_TICKS(xfer->timeout), + uhci_timeout, ii); + } + xfer->status = USBD_IN_PROGRESS; + splx(s); + +#ifdef USB_DEBUG + if (uhcidebug > 10) { + DPRINTF(("uhci_device_bulk_transfer: data(2)\n")); + uhci_dump_tds(data); + } +#endif + + if (sc->sc_bus.use_polling) + uhci_waitintr(sc, xfer); + + return (USBD_IN_PROGRESS); +} + +/* Abort a device bulk request. */ +void +uhci_device_bulk_abort(usbd_xfer_handle xfer) +{ + DPRINTF(("uhci_device_bulk_abort:\n")); + uhci_abort_xfer(xfer, USBD_CANCELLED); +} + +/* + * Abort a device request. + * If this routine is called at splusb() it guarantees that the request + * will be removed from the hardware scheduling and that the callback + * for it will be called with USBD_CANCELLED status. + * It's impossible to guarantee that the requested transfer will not + * have happened since the hardware runs concurrently. + * If the transaction has already happened we rely on the ordinary + * interrupt processing to process it. + */ +void +uhci_abort_xfer(usbd_xfer_handle xfer, usbd_status status) +{ + struct uhci_xfer *uxfer = UXFER(xfer); + uhci_intr_info_t *ii = &uxfer->iinfo; + struct uhci_pipe *upipe = (struct uhci_pipe *)xfer->pipe; + uhci_softc_t *sc = (uhci_softc_t *)upipe->pipe.device->bus; + uhci_soft_td_t *std; + int s; + + DPRINTFN(1,("uhci_abort_xfer: xfer=%p, status=%d\n", xfer, status)); + + if (sc->sc_dying) { + /* If we're dying, just do the software part. */ + s = splusb(); + xfer->status = status; /* make software ignore it */ + callout_stop(&xfer->timeout_handle); + usb_rem_task(xfer->pipe->device, &UXFER(xfer)->abort_task); + uhci_transfer_complete(xfer); + splx(s); + return; + } + + if (xfer->device->bus->intr_context || !curproc) + panic("uhci_abort_xfer: not in process context"); + + /* + * If an abort is already in progress then just wait for it to + * complete and return. + */ + if (uxfer->uhci_xfer_flags & UHCI_XFER_ABORTING) { + DPRINTFN(2, ("uhci_abort_xfer: already aborting\n")); + /* No need to wait if we're aborting from a timeout. */ + if (status == USBD_TIMEOUT) + return; + /* Override the status which might be USBD_TIMEOUT. */ + xfer->status = status; + DPRINTFN(2, ("uhci_abort_xfer: waiting for abort to finish\n")); + uxfer->uhci_xfer_flags |= UHCI_XFER_ABORTWAIT; + while (uxfer->uhci_xfer_flags & UHCI_XFER_ABORTING) + tsleep(&uxfer->uhci_xfer_flags, PZERO, "uhciaw", 0); + return; + } + + /* + * Step 1: Make interrupt routine and hardware ignore xfer. + */ + s = splusb(); + uxfer->uhci_xfer_flags |= UHCI_XFER_ABORTING; + xfer->status = status; /* make software ignore it */ + callout_stop(&xfer->timeout_handle); + usb_rem_task(xfer->pipe->device, &UXFER(xfer)->abort_task); + DPRINTFN(1,("uhci_abort_xfer: stop ii=%p\n", ii)); + for (std = ii->stdstart; std != NULL; std = std->link.std) + std->td.td_status &= htole32(~(UHCI_TD_ACTIVE | UHCI_TD_IOC)); + splx(s); + + /* + * Step 2: Wait until we know hardware has finished any possible + * use of the xfer. Also make sure the soft interrupt routine + * has run. + */ + usb_delay_ms(upipe->pipe.device->bus, 2); /* Hardware finishes in 1ms */ + s = splusb(); +#ifdef USB_USE_SOFTINTR + sc->sc_softwake = 1; +#endif /* USB_USE_SOFTINTR */ + usb_schedsoftintr(&sc->sc_bus); +#ifdef USB_USE_SOFTINTR + DPRINTFN(1,("uhci_abort_xfer: tsleep\n")); + tsleep(&sc->sc_softwake, PZERO, "uhciab", 0); +#endif /* USB_USE_SOFTINTR */ + splx(s); + + /* + * Step 3: Execute callback. + */ + DPRINTFN(1,("uhci_abort_xfer: callback\n")); + s = splusb(); +#ifdef DIAGNOSTIC + ii->isdone = 1; +#endif + /* Do the wakeup first to avoid touching the xfer after the callback. */ + uxfer->uhci_xfer_flags &= ~UHCI_XFER_ABORTING; + if (uxfer->uhci_xfer_flags & UHCI_XFER_ABORTWAIT) { + uxfer->uhci_xfer_flags &= ~UHCI_XFER_ABORTWAIT; + wakeup(&uxfer->uhci_xfer_flags); + } + uhci_transfer_complete(xfer); + splx(s); +} + +/* + * Perform any UHCI-specific transfer completion operations, then + * call usb_transfer_complete(). + */ +static void +uhci_transfer_complete(usbd_xfer_handle xfer) +{ + uhci_intr_info_t *ii = &UXFER(xfer)->iinfo; + struct uhci_pipe *upipe = (struct uhci_pipe *)xfer->pipe; + uhci_soft_td_t *p; + int i, isread, n; + + /* XXX, must be an easier way to detect reads... */ + isread = ((xfer->rqflags & URQ_REQUEST) && + (xfer->request.bmRequestType & UT_READ)) || + (xfer->pipe->endpoint->edesc->bEndpointAddress & UE_DIR_IN); + + /* Copy back from any auxillary buffers after a read operation. */ + if (xfer->nframes == 0) { + for (p = ii->stdstart; p != NULL; p = p->link.std) { + if (p->aux_data != NULL) + uhci_aux_dma_complete(p, isread); + } + } else { + if (xfer->nframes != 0) { + /* Isoc transfer, do things differently. */ + n = UXFER(xfer)->curframe; + for (i = 0; i < xfer->nframes; i++) { + p = upipe->u.iso.stds[n]; + if (p->aux_data != NULL) + uhci_aux_dma_complete(p, isread); + if (++n >= UHCI_VFRAMELIST_COUNT) + n = 0; + } + } + } + + usb_transfer_complete(xfer); +} + +/* Close a device bulk pipe. */ +void +uhci_device_bulk_close(usbd_pipe_handle pipe) +{ + struct uhci_pipe *upipe = (struct uhci_pipe *)pipe; + usbd_device_handle dev = upipe->pipe.device; + uhci_softc_t *sc = (uhci_softc_t *)dev->bus; + + uhci_free_sqh(sc, upipe->u.bulk.sqh); + pipe->endpoint->savedtoggle = upipe->nexttoggle; +} + +usbd_status +uhci_device_ctrl_transfer(usbd_xfer_handle xfer) +{ + usbd_status err; + + /* Insert last in queue. */ + err = usb_insert_transfer(xfer); + if (err) + return (err); + + /* + * Pipe isn't running (otherwise err would be USBD_INPROG), + * so start it first. + */ + return (uhci_device_ctrl_start(STAILQ_FIRST(&xfer->pipe->queue))); +} + +usbd_status +uhci_device_ctrl_start(usbd_xfer_handle xfer) +{ + uhci_softc_t *sc = (uhci_softc_t *)xfer->pipe->device->bus; + usbd_status err; + + if (sc->sc_dying) + return (USBD_IOERROR); + +#ifdef DIAGNOSTIC + if (!(xfer->rqflags & URQ_REQUEST)) + panic("uhci_device_ctrl_transfer: not a request"); +#endif + + err = uhci_device_request(xfer); + if (err) + return (err); + + if (sc->sc_bus.use_polling) + uhci_waitintr(sc, xfer); + return (USBD_IN_PROGRESS); +} + +usbd_status +uhci_device_intr_transfer(usbd_xfer_handle xfer) +{ + usbd_status err; + + /* Insert last in queue. */ + err = usb_insert_transfer(xfer); + if (err) + return (err); + + /* + * Pipe isn't running (otherwise err would be USBD_INPROG), + * so start it first. + */ + return (uhci_device_intr_start(STAILQ_FIRST(&xfer->pipe->queue))); +} + +usbd_status +uhci_device_intr_start(usbd_xfer_handle xfer) +{ + struct uhci_pipe *upipe = (struct uhci_pipe *)xfer->pipe; + usbd_device_handle dev = upipe->pipe.device; + uhci_softc_t *sc = (uhci_softc_t *)dev->bus; + uhci_intr_info_t *ii = &UXFER(xfer)->iinfo; + uhci_soft_td_t *data, *dataend; + uhci_soft_qh_t *sqh; + usbd_status err; + int isread, endpt; + int i, s; + + if (sc->sc_dying) + return (USBD_IOERROR); + + DPRINTFN(3,("uhci_device_intr_transfer: xfer=%p len=%d flags=%d\n", + xfer, xfer->length, xfer->flags)); + +#ifdef DIAGNOSTIC + if (xfer->rqflags & URQ_REQUEST) + panic("uhci_device_intr_transfer: a request"); +#endif + + endpt = upipe->pipe.endpoint->edesc->bEndpointAddress; + isread = UE_GET_DIR(endpt) == UE_DIR_IN; + sqh = upipe->u.bulk.sqh; + + upipe->u.intr.isread = isread; + + err = uhci_alloc_std_chain(upipe, sc, xfer->length, isread, xfer->flags, + xfer, &data, &dataend); + if (err) + return (err); + dataend->td.td_status |= htole32(UHCI_TD_IOC); + +#ifdef USB_DEBUG + if (uhcidebug > 10) { + DPRINTF(("uhci_device_intr_transfer: data(1)\n")); + uhci_dump_tds(data); + uhci_dump_qh(upipe->u.intr.qhs[0]); + } +#endif + + s = splusb(); + /* Set up interrupt info. */ + ii->xfer = xfer; + ii->stdstart = data; + ii->stdend = dataend; +#ifdef DIAGNOSTIC + if (!ii->isdone) { + printf("uhci_device_intr_transfer: not done, ii=%p\n", ii); + } + ii->isdone = 0; +#endif + + DPRINTFN(10,("uhci_device_intr_transfer: qhs[0]=%p\n", + upipe->u.intr.qhs[0])); + for (i = 0; i < upipe->u.intr.npoll; i++) { + sqh = upipe->u.intr.qhs[i]; + sqh->elink = data; + sqh->qh.qh_elink = htole32(data->physaddr | UHCI_PTR_TD); + } + uhci_add_intr_info(sc, ii); + xfer->status = USBD_IN_PROGRESS; + splx(s); + +#ifdef USB_DEBUG + if (uhcidebug > 10) { + DPRINTF(("uhci_device_intr_transfer: data(2)\n")); + uhci_dump_tds(data); + uhci_dump_qh(upipe->u.intr.qhs[0]); + } +#endif + + return (USBD_IN_PROGRESS); +} + +/* Abort a device control request. */ +void +uhci_device_ctrl_abort(usbd_xfer_handle xfer) +{ + DPRINTF(("uhci_device_ctrl_abort:\n")); + uhci_abort_xfer(xfer, USBD_CANCELLED); +} + +/* Close a device control pipe. */ +void +uhci_device_ctrl_close(usbd_pipe_handle pipe) +{ +} + +/* Abort a device interrupt request. */ +void +uhci_device_intr_abort(usbd_xfer_handle xfer) +{ + DPRINTFN(1,("uhci_device_intr_abort: xfer=%p\n", xfer)); + if (xfer->pipe->intrxfer == xfer) { + DPRINTFN(1,("uhci_device_intr_abort: remove\n")); + xfer->pipe->intrxfer = NULL; + } + uhci_abort_xfer(xfer, USBD_CANCELLED); +} + +/* Close a device interrupt pipe. */ +void +uhci_device_intr_close(usbd_pipe_handle pipe) +{ + struct uhci_pipe *upipe = (struct uhci_pipe *)pipe; + uhci_softc_t *sc = (uhci_softc_t *)pipe->device->bus; + int i, npoll; + int s; + + /* Unlink descriptors from controller data structures. */ + npoll = upipe->u.intr.npoll; + s = splusb(); + for (i = 0; i < npoll; i++) + uhci_remove_intr(sc, upipe->u.intr.qhs[i]); + splx(s); + + /* + * We now have to wait for any activity on the physical + * descriptors to stop. + */ + usb_delay_ms(&sc->sc_bus, 2); + + for(i = 0; i < npoll; i++) + uhci_free_sqh(sc, upipe->u.intr.qhs[i]); + free(upipe->u.intr.qhs, M_USBHC); + + /* XXX free other resources */ +} + +usbd_status +uhci_device_request(usbd_xfer_handle xfer) +{ + struct uhci_pipe *upipe = (struct uhci_pipe *)xfer->pipe; + usb_device_request_t *req = &xfer->request; + usbd_device_handle dev = upipe->pipe.device; + uhci_softc_t *sc = (uhci_softc_t *)dev->bus; + int addr = dev->address; + int endpt = upipe->pipe.endpoint->edesc->bEndpointAddress; + uhci_intr_info_t *ii = &UXFER(xfer)->iinfo; + uhci_soft_td_t *setup, *data, *stat, *next, *dataend; + uhci_soft_qh_t *sqh; + int len; + u_int32_t ls; + usbd_status err; + int isread; + int s; + + DPRINTFN(3,("uhci_device_control type=0x%02x, request=0x%02x, " + "wValue=0x%04x, wIndex=0x%04x len=%d, addr=%d, endpt=%d\n", + req->bmRequestType, req->bRequest, UGETW(req->wValue), + UGETW(req->wIndex), UGETW(req->wLength), + addr, endpt)); + + ls = dev->speed == USB_SPEED_LOW ? UHCI_TD_LS : 0; + isread = req->bmRequestType & UT_READ; + len = UGETW(req->wLength); + + setup = upipe->u.ctl.setup; + stat = upipe->u.ctl.stat; + sqh = upipe->u.ctl.sqh; + + /* Set up data transaction */ + if (len != 0) { + upipe->nexttoggle = 1; + err = uhci_alloc_std_chain(upipe, sc, len, isread, xfer->flags, + xfer, &data, &dataend); + if (err) + return (err); + next = data; + dataend->link.std = stat; + dataend->td.td_link = htole32(stat->physaddr | UHCI_PTR_VF | UHCI_PTR_TD); + } else { + next = stat; + } + upipe->u.ctl.length = len; + + memcpy(KERNADDR(&upipe->u.ctl.reqdma, 0), req, sizeof *req); + + setup->link.std = next; + setup->td.td_link = htole32(next->physaddr | UHCI_PTR_VF | UHCI_PTR_TD); + setup->td.td_status = htole32(UHCI_TD_SET_ERRCNT(3) | ls | + UHCI_TD_ACTIVE); + setup->td.td_token = htole32(UHCI_TD_SETUP(sizeof *req, endpt, addr)); + setup->td.td_buffer = htole32(DMAADDR(&upipe->u.ctl.reqdma, 0)); + + stat->link.std = NULL; + stat->td.td_link = htole32(UHCI_PTR_T); + stat->td.td_status = htole32(UHCI_TD_SET_ERRCNT(3) | ls | + UHCI_TD_ACTIVE | UHCI_TD_IOC); + stat->td.td_token = + htole32(isread ? UHCI_TD_OUT(0, endpt, addr, 1) : + UHCI_TD_IN (0, endpt, addr, 1)); + stat->td.td_buffer = htole32(0); + +#ifdef USB_DEBUG + if (uhcidebug > 10) { + DPRINTF(("uhci_device_request: before transfer\n")); + uhci_dump_tds(setup); + } +#endif + + /* Set up interrupt info. */ + ii->xfer = xfer; + ii->stdstart = setup; + ii->stdend = stat; +#ifdef DIAGNOSTIC + if (!ii->isdone) { + printf("uhci_device_request: not done, ii=%p\n", ii); + } + ii->isdone = 0; +#endif + + sqh->elink = setup; + sqh->qh.qh_elink = htole32(setup->physaddr | UHCI_PTR_TD); + + s = splusb(); + if (dev->speed == USB_SPEED_LOW) + uhci_add_ls_ctrl(sc, sqh); + else + uhci_add_hs_ctrl(sc, sqh); + uhci_add_intr_info(sc, ii); +#ifdef USB_DEBUG + if (uhcidebug > 12) { + uhci_soft_td_t *std; + uhci_soft_qh_t *xqh; + uhci_soft_qh_t *sxqh; + int maxqh = 0; + uhci_physaddr_t link; + DPRINTF(("uhci_enter_ctl_q: follow from [0]\n")); + for (std = sc->sc_vframes[0].htd, link = 0; + (link & UHCI_PTR_QH) == 0; + std = std->link.std) { + link = le32toh(std->td.td_link); + uhci_dump_td(std); + } + sxqh = (uhci_soft_qh_t *)std; + uhci_dump_qh(sxqh); + for (xqh = sxqh; + xqh != NULL; + xqh = (maxqh++ == 5 || xqh->hlink == sxqh || + xqh->hlink == xqh ? NULL : xqh->hlink)) { + uhci_dump_qh(xqh); + } + DPRINTF(("Enqueued QH:\n")); + uhci_dump_qh(sqh); + uhci_dump_tds(sqh->elink); + } +#endif + if (xfer->timeout && !sc->sc_bus.use_polling) { + callout_reset(&xfer->timeout_handle, MS_TO_TICKS(xfer->timeout), + uhci_timeout, ii); + } + xfer->status = USBD_IN_PROGRESS; + splx(s); + + return (USBD_NORMAL_COMPLETION); +} + +usbd_status +uhci_device_isoc_transfer(usbd_xfer_handle xfer) +{ + usbd_status err; + + DPRINTFN(5,("uhci_device_isoc_transfer: xfer=%p\n", xfer)); + + /* Put it on our queue, */ + err = usb_insert_transfer(xfer); + + /* bail out on error, */ + if (err && err != USBD_IN_PROGRESS) + return (err); + + /* XXX should check inuse here */ + + /* insert into schedule, */ + uhci_device_isoc_enter(xfer); + + /* and start if the pipe wasn't running */ + if (!err) + uhci_device_isoc_start(STAILQ_FIRST(&xfer->pipe->queue)); + + return (err); +} + +void +uhci_device_isoc_enter(usbd_xfer_handle xfer) +{ + struct uhci_pipe *upipe = (struct uhci_pipe *)xfer->pipe; + usbd_device_handle dev = upipe->pipe.device; + uhci_softc_t *sc = (uhci_softc_t *)dev->bus; + struct iso *iso = &upipe->u.iso; + uhci_soft_td_t *std; + void *dataptr; + u_int32_t len, status; + int err, s, i, isread, next, nframes, seg, segoff; + + DPRINTFN(5,("uhci_device_isoc_enter: used=%d next=%d xfer=%p " + "nframes=%d\n", + iso->inuse, iso->next, xfer, xfer->nframes)); + + if (sc->sc_dying) + return; + + if (xfer->status == USBD_IN_PROGRESS) { + /* This request has already been entered into the frame list */ + printf("uhci_device_isoc_enter: xfer=%p in frame list\n", xfer); + /* XXX */ + } + +#ifdef DIAGNOSTIC + if (iso->inuse >= UHCI_VFRAMELIST_COUNT) + printf("uhci_device_isoc_enter: overflow!\n"); +#endif + + next = iso->next; + if (next == -1) { + /* Not in use yet, schedule it a few frames ahead. */ + next = (UREAD2(sc, UHCI_FRNUM) + 3) % UHCI_VFRAMELIST_COUNT; + DPRINTFN(2,("uhci_device_isoc_enter: start next=%d\n", next)); + } + + xfer->status = USBD_IN_PROGRESS; + UXFER(xfer)->curframe = next; + + seg = 0; + segoff = 0; + dataptr = xfer->allocbuf; /* Normal buffers not possible for isoc? */ + isread = xfer->pipe->endpoint->edesc->bEndpointAddress & UE_DIR_IN; + status = UHCI_TD_ZERO_ACTLEN(UHCI_TD_SET_ERRCNT(0) | + UHCI_TD_ACTIVE | + UHCI_TD_IOS); + nframes = xfer->nframes; + s = splusb(); + for (i = 0; i < nframes; i++) { + std = iso->stds[next]; + if (++next >= UHCI_VFRAMELIST_COUNT) + next = 0; + len = xfer->frlengths[i]; + KASSERT(seg < xfer->dmamap.nsegs, + ("uhci_device_isoc_enter: too few segments")); + if (len + segoff > xfer->dmamap.segs[seg].ds_len) { + /* UHCI can't handle non-contiguous data. */ + err = uhci_aux_dma_alloc(sc, std, dataptr, len); + /* XXX */ + if (err) + printf("uhci_device_isoc_enter: aux alloc\n"); + std->td.td_buffer = htole32(uhci_aux_dma_prepare(std, + isread)); + segoff += len; + while (segoff >= xfer->dmamap.segs[seg].ds_len) { + KASSERT(seg < xfer->dmamap.nsegs - 1 || + segoff == xfer->dmamap.segs[seg].ds_len, + ("uhci_device_isoc_enter: overlap2")); + segoff -= xfer->dmamap.segs[seg].ds_len; + seg++; + } + } else { + std->td.td_buffer = + htole32(xfer->dmamap.segs[seg].ds_addr + segoff); + segoff += len; + if (segoff >= xfer->dmamap.segs[seg].ds_len) { + KASSERT(segoff == xfer->dmamap.segs[seg].ds_len, + ("uhci_device_isoc_enter: overlap")); + segoff = 0; + seg++; + } + } + if (i == nframes - 1) + status |= UHCI_TD_IOC; + std->td.td_status = htole32(status); + std->td.td_token &= htole32(~UHCI_TD_MAXLEN_MASK); + std->td.td_token |= htole32(UHCI_TD_SET_MAXLEN(len)); +#ifdef USB_DEBUG + if (uhcidebug > 5) { + DPRINTFN(5,("uhci_device_isoc_enter: TD %d\n", i)); + uhci_dump_td(std); + } +#endif + dataptr = (char *)dataptr + len; + } + iso->next = next; + iso->inuse += xfer->nframes; + + splx(s); +} + +usbd_status +uhci_device_isoc_start(usbd_xfer_handle xfer) +{ + struct uhci_pipe *upipe = (struct uhci_pipe *)xfer->pipe; + uhci_softc_t *sc = (uhci_softc_t *)upipe->pipe.device->bus; + uhci_intr_info_t *ii = &UXFER(xfer)->iinfo; + uhci_soft_td_t *end; + int s, i; + + DPRINTFN(5,("uhci_device_isoc_start: xfer=%p\n", xfer)); + + if (sc->sc_dying) + return (USBD_IOERROR); + +#ifdef DIAGNOSTIC + if (xfer->status != USBD_IN_PROGRESS) + printf("uhci_device_isoc_start: not in progress %p\n", xfer); +#endif + + /* Find the last TD */ + i = UXFER(xfer)->curframe + xfer->nframes; + if (i >= UHCI_VFRAMELIST_COUNT) + i -= UHCI_VFRAMELIST_COUNT; + end = upipe->u.iso.stds[i]; + +#ifdef DIAGNOSTIC + if (end == NULL) { + printf("uhci_device_isoc_start: end == NULL\n"); + return (USBD_INVAL); + } +#endif + + s = splusb(); + + /* Set up interrupt info. */ + ii->xfer = xfer; + ii->stdstart = end; + ii->stdend = end; +#ifdef DIAGNOSTIC + if (!ii->isdone) + printf("uhci_device_isoc_start: not done, ii=%p\n", ii); + ii->isdone = 0; +#endif + uhci_add_intr_info(sc, ii); + + splx(s); + + return (USBD_IN_PROGRESS); +} + +void +uhci_device_isoc_abort(usbd_xfer_handle xfer) +{ + struct uhci_pipe *upipe = (struct uhci_pipe *)xfer->pipe; + uhci_soft_td_t **stds = upipe->u.iso.stds; + uhci_soft_td_t *std; + int i, n, s, nframes, maxlen, len; + + s = splusb(); + + /* Transfer is already done. */ + if (xfer->status != USBD_NOT_STARTED && + xfer->status != USBD_IN_PROGRESS) { + splx(s); + return; + } + + /* Give xfer the requested abort code. */ + xfer->status = USBD_CANCELLED; + + /* make hardware ignore it, */ + nframes = xfer->nframes; + n = UXFER(xfer)->curframe; + maxlen = 0; + for (i = 0; i < nframes; i++) { + std = stds[n]; + std->td.td_status &= htole32(~(UHCI_TD_ACTIVE | UHCI_TD_IOC)); + len = UHCI_TD_GET_MAXLEN(le32toh(std->td.td_token)); + if (len > maxlen) + maxlen = len; + if (++n >= UHCI_VFRAMELIST_COUNT) + n = 0; + } + + /* and wait until we are sure the hardware has finished. */ + delay(maxlen); + +#ifdef DIAGNOSTIC + UXFER(xfer)->iinfo.isdone = 1; +#endif + /* Run callback and remove from interrupt list. */ + uhci_transfer_complete(xfer); + + splx(s); +} + +void +uhci_device_isoc_close(usbd_pipe_handle pipe) +{ + struct uhci_pipe *upipe = (struct uhci_pipe *)pipe; + usbd_device_handle dev = upipe->pipe.device; + uhci_softc_t *sc = (uhci_softc_t *)dev->bus; + uhci_soft_td_t *std, *vstd; + struct iso *iso; + int i, s; + + /* + * Make sure all TDs are marked as inactive. + * Wait for completion. + * Unschedule. + * Deallocate. + */ + iso = &upipe->u.iso; + + for (i = 0; i < UHCI_VFRAMELIST_COUNT; i++) + iso->stds[i]->td.td_status &= htole32(~UHCI_TD_ACTIVE); + usb_delay_ms(&sc->sc_bus, 2); /* wait for completion */ + + s = splusb(); + for (i = 0; i < UHCI_VFRAMELIST_COUNT; i++) { + std = iso->stds[i]; + for (vstd = sc->sc_vframes[i].htd; + vstd != NULL && vstd->link.std != std; + vstd = vstd->link.std) + ; + if (vstd == NULL) { + /*panic*/ + printf("uhci_device_isoc_close: %p not found\n", std); + splx(s); + return; + } + vstd->link = std->link; + vstd->td.td_link = std->td.td_link; + uhci_free_std(sc, std); + } + splx(s); + + free(iso->stds, M_USBHC); +} + +usbd_status +uhci_setup_isoc(usbd_pipe_handle pipe) +{ + struct uhci_pipe *upipe = (struct uhci_pipe *)pipe; + usbd_device_handle dev = upipe->pipe.device; + uhci_softc_t *sc = (uhci_softc_t *)dev->bus; + int addr = upipe->pipe.device->address; + int endpt = upipe->pipe.endpoint->edesc->bEndpointAddress; + int rd = UE_GET_DIR(endpt) == UE_DIR_IN; + uhci_soft_td_t *std, *vstd; + u_int32_t token; + struct iso *iso; + int i, s; + + iso = &upipe->u.iso; + iso->stds = malloc(UHCI_VFRAMELIST_COUNT * sizeof (uhci_soft_td_t *), + M_USBHC, M_WAITOK); + + token = rd ? UHCI_TD_IN (0, endpt, addr, 0) : + UHCI_TD_OUT(0, endpt, addr, 0); + + /* Allocate the TDs and mark as inactive; */ + for (i = 0; i < UHCI_VFRAMELIST_COUNT; i++) { + std = uhci_alloc_std(sc); + if (std == 0) + goto bad; + std->td.td_status = htole32(UHCI_TD_IOS); /* iso, inactive */ + std->td.td_token = htole32(token); + iso->stds[i] = std; + } + + /* Insert TDs into schedule. */ + s = splusb(); + for (i = 0; i < UHCI_VFRAMELIST_COUNT; i++) { + std = iso->stds[i]; + vstd = sc->sc_vframes[i].htd; + std->link = vstd->link; + std->td.td_link = vstd->td.td_link; + vstd->link.std = std; + vstd->td.td_link = htole32(std->physaddr | UHCI_PTR_TD); + } + splx(s); + + iso->next = -1; + iso->inuse = 0; + + return (USBD_NORMAL_COMPLETION); + + bad: + while (--i >= 0) + uhci_free_std(sc, iso->stds[i]); + free(iso->stds, M_USBHC); + return (USBD_NOMEM); +} + +void +uhci_device_isoc_done(usbd_xfer_handle xfer) +{ + uhci_intr_info_t *ii = &UXFER(xfer)->iinfo; + + DPRINTFN(4, ("uhci_isoc_done: length=%d\n", xfer->actlen)); + + if (ii->xfer != xfer) + /* Not on interrupt list, ignore it. */ + return; + + if (!uhci_active_intr_info(ii)) + return; + +#ifdef DIAGNOSTIC + if (xfer->busy_free != XFER_BUSY) { + printf("uhci_device_isoc_done: xfer=%p not busy 0x%08x\n", + xfer, xfer->busy_free); + return; + } + + if (ii->stdend == NULL) { + printf("uhci_device_isoc_done: xfer=%p stdend==NULL\n", xfer); +#ifdef USB_DEBUG + uhci_dump_ii(ii); +#endif + return; + } +#endif + + /* Turn off the interrupt since it is active even if the TD is not. */ + ii->stdend->td.td_status &= htole32(~UHCI_TD_IOC); + + uhci_del_intr_info(ii); /* remove from active list */ + +#ifdef DIAGNOSTIC + if (ii->stdend == NULL) { + printf("uhci_device_isoc_done: xfer=%p stdend==NULL\n", xfer); +#ifdef USB_DEBUG + uhci_dump_ii(ii); +#endif + return; + } +#endif + ii->stdstart = NULL; + ii->stdend = NULL; +} + +void +uhci_device_intr_done(usbd_xfer_handle xfer) +{ + uhci_intr_info_t *ii = &UXFER(xfer)->iinfo; + uhci_softc_t *sc = ii->sc; + struct uhci_pipe *upipe = (struct uhci_pipe *)xfer->pipe; + uhci_soft_qh_t *sqh; + int i, npoll; + + DPRINTFN(5, ("uhci_device_intr_done: length=%d\n", xfer->actlen)); + + npoll = upipe->u.intr.npoll; + for(i = 0; i < npoll; i++) { + sqh = upipe->u.intr.qhs[i]; + sqh->elink = NULL; + sqh->qh.qh_elink = htole32(UHCI_PTR_T); + } + uhci_free_std_chain(sc, ii->stdstart, NULL); + + /* XXX Wasteful. */ + if (xfer->pipe->repeat) { + uhci_soft_td_t *data, *dataend; + + DPRINTFN(5,("uhci_device_intr_done: requeing\n")); + + /* This alloc cannot fail since we freed the chain above. */ + uhci_alloc_std_chain(upipe, sc, xfer->length, + upipe->u.intr.isread, xfer->flags, xfer, + &data, &dataend); + dataend->td.td_status |= htole32(UHCI_TD_IOC); + +#ifdef USB_DEBUG + if (uhcidebug > 10) { + DPRINTF(("uhci_device_intr_done: data(1)\n")); + uhci_dump_tds(data); + uhci_dump_qh(upipe->u.intr.qhs[0]); + } +#endif + + ii->stdstart = data; + ii->stdend = dataend; +#ifdef DIAGNOSTIC + if (!ii->isdone) { + printf("uhci_device_intr_done: not done, ii=%p\n", ii); + } + ii->isdone = 0; +#endif + for (i = 0; i < npoll; i++) { + sqh = upipe->u.intr.qhs[i]; + sqh->elink = data; + sqh->qh.qh_elink = htole32(data->physaddr | UHCI_PTR_TD); + } + xfer->status = USBD_IN_PROGRESS; + /* The ii is already on the examined list, just leave it. */ + } else { + DPRINTFN(5,("uhci_device_intr_done: removing\n")); + if (uhci_active_intr_info(ii)) { + uhci_del_intr_info(ii); + ii->stdstart = NULL; + ii->stdend = NULL; + } + } +} + +/* Deallocate request data structures */ +void +uhci_device_ctrl_done(usbd_xfer_handle xfer) +{ + uhci_intr_info_t *ii = &UXFER(xfer)->iinfo; + uhci_softc_t *sc = ii->sc; + struct uhci_pipe *upipe = (struct uhci_pipe *)xfer->pipe; + +#ifdef DIAGNOSTIC + if (!(xfer->rqflags & URQ_REQUEST)) + panic("uhci_device_ctrl_done: not a request"); +#endif + + if (!uhci_active_intr_info(ii)) + return; + + uhci_del_intr_info(ii); /* remove from active list */ + + if (upipe->pipe.device->speed == USB_SPEED_LOW) + uhci_remove_ls_ctrl(sc, upipe->u.ctl.sqh); + else + uhci_remove_hs_ctrl(sc, upipe->u.ctl.sqh); + + if (upipe->u.ctl.length != 0) + uhci_free_std_chain(sc, ii->stdstart->link.std, ii->stdend); + ii->stdstart = NULL; + ii->stdend = NULL; + + DPRINTFN(5, ("uhci_device_ctrl_done: length=%d\n", xfer->actlen)); +} + +/* Deallocate request data structures */ +void +uhci_device_bulk_done(usbd_xfer_handle xfer) +{ + uhci_intr_info_t *ii = &UXFER(xfer)->iinfo; + uhci_softc_t *sc = ii->sc; + struct uhci_pipe *upipe = (struct uhci_pipe *)xfer->pipe; + + DPRINTFN(5,("uhci_device_bulk_done: xfer=%p ii=%p sc=%p upipe=%p\n", + xfer, ii, sc, upipe)); + + if (!uhci_active_intr_info(ii)) + return; + + uhci_del_intr_info(ii); /* remove from active list */ + + uhci_remove_bulk(sc, upipe->u.bulk.sqh); + + uhci_free_std_chain(sc, ii->stdstart, NULL); + ii->stdstart = NULL; + ii->stdend = NULL; + + DPRINTFN(5, ("uhci_device_bulk_done: length=%d\n", xfer->actlen)); +} + +/* Add interrupt QH, called with vflock. */ +void +uhci_add_intr(uhci_softc_t *sc, uhci_soft_qh_t *sqh) +{ + struct uhci_vframe *vf = &sc->sc_vframes[sqh->pos]; + uhci_soft_qh_t *eqh; + + DPRINTFN(4, ("uhci_add_intr: n=%d sqh=%p\n", sqh->pos, sqh)); + + eqh = vf->eqh; + sqh->hlink = eqh->hlink; + sqh->qh.qh_hlink = eqh->qh.qh_hlink; + eqh->hlink = sqh; + eqh->qh.qh_hlink = htole32(sqh->physaddr | UHCI_PTR_QH); + vf->eqh = sqh; + vf->bandwidth++; +} + +/* Remove interrupt QH. */ +void +uhci_remove_intr(uhci_softc_t *sc, uhci_soft_qh_t *sqh) +{ + struct uhci_vframe *vf = &sc->sc_vframes[sqh->pos]; + uhci_soft_qh_t *pqh; + + DPRINTFN(4, ("uhci_remove_intr: n=%d sqh=%p\n", sqh->pos, sqh)); + + /* See comment in uhci_remove_ctrl() */ + if (!(sqh->qh.qh_elink & htole32(UHCI_PTR_T))) { + sqh->qh.qh_elink = htole32(UHCI_PTR_T); + delay(UHCI_QH_REMOVE_DELAY); + } + + pqh = uhci_find_prev_qh(vf->hqh, sqh); + pqh->hlink = sqh->hlink; + pqh->qh.qh_hlink = sqh->qh.qh_hlink; + delay(UHCI_QH_REMOVE_DELAY); + if (vf->eqh == sqh) + vf->eqh = pqh; + vf->bandwidth--; +} + +usbd_status +uhci_device_setintr(uhci_softc_t *sc, struct uhci_pipe *upipe, int ival) +{ + uhci_soft_qh_t *sqh; + int i, npoll, s; + u_int bestbw, bw, bestoffs, offs; + + DPRINTFN(2, ("uhci_device_setintr: pipe=%p\n", upipe)); + if (ival == 0) { + printf("uhci_setintr: 0 interval\n"); + return (USBD_INVAL); + } + + if (ival > UHCI_VFRAMELIST_COUNT) + ival = UHCI_VFRAMELIST_COUNT; + npoll = (UHCI_VFRAMELIST_COUNT + ival - 1) / ival; + DPRINTFN(2, ("uhci_device_setintr: ival=%d npoll=%d\n", ival, npoll)); + + upipe->u.intr.npoll = npoll; + upipe->u.intr.qhs = + malloc(npoll * sizeof(uhci_soft_qh_t *), M_USBHC, M_WAITOK); + + /* + * Figure out which offset in the schedule that has most + * bandwidth left over. + */ +#define MOD(i) ((i) & (UHCI_VFRAMELIST_COUNT-1)) + for (bestoffs = offs = 0, bestbw = ~0; offs < ival; offs++) { + for (bw = i = 0; i < npoll; i++) + bw += sc->sc_vframes[MOD(i * ival + offs)].bandwidth; + if (bw < bestbw) { + bestbw = bw; + bestoffs = offs; + } + } + DPRINTFN(1, ("uhci_device_setintr: bw=%d offs=%d\n", bestbw, bestoffs)); + + for(i = 0; i < npoll; i++) { + upipe->u.intr.qhs[i] = sqh = uhci_alloc_sqh(sc); + sqh->elink = NULL; + sqh->qh.qh_elink = htole32(UHCI_PTR_T); + sqh->pos = MOD(i * ival + bestoffs); + } +#undef MOD + + s = splusb(); + /* Enter QHs into the controller data structures. */ + for(i = 0; i < npoll; i++) + uhci_add_intr(sc, upipe->u.intr.qhs[i]); + splx(s); + + DPRINTFN(5, ("uhci_device_setintr: returns %p\n", upipe)); + return (USBD_NORMAL_COMPLETION); +} + +/* Open a new pipe. */ +usbd_status +uhci_open(usbd_pipe_handle pipe) +{ + uhci_softc_t *sc = (uhci_softc_t *)pipe->device->bus; + struct uhci_pipe *upipe = (struct uhci_pipe *)pipe; + usb_endpoint_descriptor_t *ed = pipe->endpoint->edesc; + usbd_status err; + int ival; + + DPRINTFN(1, ("uhci_open: pipe=%p, addr=%d, endpt=%d (%d)\n", + pipe, pipe->device->address, + ed->bEndpointAddress, sc->sc_addr)); + + upipe->aborting = 0; + upipe->nexttoggle = pipe->endpoint->savedtoggle; + + if (pipe->device->address == sc->sc_addr) { + switch (ed->bEndpointAddress) { + case USB_CONTROL_ENDPOINT: + pipe->methods = &uhci_root_ctrl_methods; + break; + case UE_DIR_IN | UHCI_INTR_ENDPT: + pipe->methods = &uhci_root_intr_methods; + break; + default: + return (USBD_INVAL); + } + } else { + switch (ed->bmAttributes & UE_XFERTYPE) { + case UE_CONTROL: + pipe->methods = &uhci_device_ctrl_methods; + upipe->u.ctl.sqh = uhci_alloc_sqh(sc); + if (upipe->u.ctl.sqh == NULL) + goto bad; + upipe->u.ctl.setup = uhci_alloc_std(sc); + if (upipe->u.ctl.setup == NULL) { + uhci_free_sqh(sc, upipe->u.ctl.sqh); + goto bad; + } + upipe->u.ctl.stat = uhci_alloc_std(sc); + if (upipe->u.ctl.stat == NULL) { + uhci_free_sqh(sc, upipe->u.ctl.sqh); + uhci_free_std(sc, upipe->u.ctl.setup); + goto bad; + } + err = usb_allocmem(&sc->sc_bus, + sizeof(usb_device_request_t), + 0, &upipe->u.ctl.reqdma); + if (err) { + uhci_free_sqh(sc, upipe->u.ctl.sqh); + uhci_free_std(sc, upipe->u.ctl.setup); + uhci_free_std(sc, upipe->u.ctl.stat); + goto bad; + } + break; + case UE_INTERRUPT: + pipe->methods = &uhci_device_intr_methods; + ival = pipe->interval; + if (ival == USBD_DEFAULT_INTERVAL) + ival = ed->bInterval; + return (uhci_device_setintr(sc, upipe, ival)); + case UE_ISOCHRONOUS: + pipe->methods = &uhci_device_isoc_methods; + return (uhci_setup_isoc(pipe)); + case UE_BULK: + pipe->methods = &uhci_device_bulk_methods; + upipe->u.bulk.sqh = uhci_alloc_sqh(sc); + if (upipe->u.bulk.sqh == NULL) + goto bad; + break; + } + } + return (USBD_NORMAL_COMPLETION); + + bad: + return (USBD_NOMEM); +} + +/* + * Data structures and routines to emulate the root hub. + */ +usb_device_descriptor_t uhci_devd = { + USB_DEVICE_DESCRIPTOR_SIZE, + UDESC_DEVICE, /* type */ + {0x00, 0x01}, /* USB version */ + UDCLASS_HUB, /* class */ + UDSUBCLASS_HUB, /* subclass */ + UDPROTO_FSHUB, /* protocol */ + 64, /* max packet */ + {0},{0},{0x00,0x01}, /* device id */ + 1,2,0, /* string indicies */ + 1 /* # of configurations */ +}; + +usb_config_descriptor_t uhci_confd = { + USB_CONFIG_DESCRIPTOR_SIZE, + UDESC_CONFIG, + {USB_CONFIG_DESCRIPTOR_SIZE + + USB_INTERFACE_DESCRIPTOR_SIZE + + USB_ENDPOINT_DESCRIPTOR_SIZE}, + 1, + 1, + 0, + UC_SELF_POWERED, + 0 /* max power */ +}; + +usb_interface_descriptor_t uhci_ifcd = { + USB_INTERFACE_DESCRIPTOR_SIZE, + UDESC_INTERFACE, + 0, + 0, + 1, + UICLASS_HUB, + UISUBCLASS_HUB, + UIPROTO_FSHUB, + 0 +}; + +usb_endpoint_descriptor_t uhci_endpd = { + USB_ENDPOINT_DESCRIPTOR_SIZE, + UDESC_ENDPOINT, + UE_DIR_IN | UHCI_INTR_ENDPT, + UE_INTERRUPT, + {8}, + 255 +}; + +usb_hub_descriptor_t uhci_hubd_piix = { + USB_HUB_DESCRIPTOR_SIZE, + UDESC_HUB, + 2, + { UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL, 0 }, + 50, /* power on to power good */ + 0, + { 0x00 }, /* both ports are removable */ +}; + +int +uhci_str(usb_string_descriptor_t *p, int l, char *s) +{ + int i; + + if (l == 0) + return (0); + p->bLength = 2 * strlen(s) + 2; + if (l == 1) + return (1); + p->bDescriptorType = UDESC_STRING; + l -= 2; + for (i = 0; s[i] && l > 1; i++, l -= 2) + USETW2(p->bString[i], 0, s[i]); + return (2*i+2); +} + +/* + * The USB hub protocol requires that SET_FEATURE(PORT_RESET) also + * enables the port, and also states that SET_FEATURE(PORT_ENABLE) + * should not be used by the USB subsystem. As we cannot issue a + * SET_FEATURE(PORT_ENABLE) externally, we must ensure that the port + * will be enabled as part of the reset. + * + * On the VT83C572, the port cannot be successfully enabled until the + * outstanding "port enable change" and "connection status change" + * events have been reset. + */ +static usbd_status +uhci_portreset(uhci_softc_t *sc, int index) +{ + int lim, port, x; + + if (index == 1) + port = UHCI_PORTSC1; + else if (index == 2) + port = UHCI_PORTSC2; + else + return (USBD_IOERROR); + + x = URWMASK(UREAD2(sc, port)); + UWRITE2(sc, port, x | UHCI_PORTSC_PR); + + usb_delay_ms(&sc->sc_bus, USB_PORT_ROOT_RESET_DELAY); + + DPRINTFN(3,("uhci port %d reset, status0 = 0x%04x\n", + index, UREAD2(sc, port))); + + x = URWMASK(UREAD2(sc, port)); + UWRITE2(sc, port, x & ~UHCI_PORTSC_PR); + + delay(100); + + DPRINTFN(3,("uhci port %d reset, status1 = 0x%04x\n", + index, UREAD2(sc, port))); + + x = URWMASK(UREAD2(sc, port)); + UWRITE2(sc, port, x | UHCI_PORTSC_PE); + + for (lim = 10; --lim > 0;) { + usb_delay_ms(&sc->sc_bus, USB_PORT_RESET_DELAY); + + x = UREAD2(sc, port); + + DPRINTFN(3,("uhci port %d iteration %u, status = 0x%04x\n", + index, lim, x)); + + if (!(x & UHCI_PORTSC_CCS)) { + /* + * No device is connected (or was disconnected + * during reset). Consider the port reset. + * The delay must be long enough to ensure on + * the initial iteration that the device + * connection will have been registered. 50ms + * appears to be sufficient, but 20ms is not. + */ + DPRINTFN(3,("uhci port %d loop %u, device detached\n", + index, lim)); + break; + } + + if (x & (UHCI_PORTSC_POEDC | UHCI_PORTSC_CSC)) { + /* + * Port enabled changed and/or connection + * status changed were set. Reset either or + * both raised flags (by writing a 1 to that + * bit), and wait again for state to settle. + */ + UWRITE2(sc, port, URWMASK(x) | + (x & (UHCI_PORTSC_POEDC | UHCI_PORTSC_CSC))); + continue; + } + + if (x & UHCI_PORTSC_PE) + /* Port is enabled */ + break; + + UWRITE2(sc, port, URWMASK(x) | UHCI_PORTSC_PE); + } + + DPRINTFN(3,("uhci port %d reset, status2 = 0x%04x\n", + index, UREAD2(sc, port))); + + if (lim <= 0) { + DPRINTFN(1,("uhci port %d reset timed out\n", index)); + return (USBD_TIMEOUT); + } + + sc->sc_isreset = 1; + return (USBD_NORMAL_COMPLETION); +} + +/* + * Simulate a hardware hub by handling all the necessary requests. + */ +usbd_status +uhci_root_ctrl_transfer(usbd_xfer_handle xfer) +{ + usbd_status err; + + /* Insert last in queue. */ + err = usb_insert_transfer(xfer); + if (err) + return (err); + + /* + * Pipe isn't running (otherwise err would be USBD_INPROG), + * so start it first. + */ + return (uhci_root_ctrl_start(STAILQ_FIRST(&xfer->pipe->queue))); +} + +usbd_status +uhci_root_ctrl_start(usbd_xfer_handle xfer) +{ + uhci_softc_t *sc = (uhci_softc_t *)xfer->pipe->device->bus; + usb_device_request_t *req; + void *buf = NULL; + int port, x; + int s, len, value, index, status, change, l, totlen = 0; + usb_port_status_t ps; + usbd_status err; + + if (sc->sc_dying) + return (USBD_IOERROR); + +#ifdef DIAGNOSTIC + if (!(xfer->rqflags & URQ_REQUEST)) + panic("uhci_root_ctrl_transfer: not a request"); +#endif + req = &xfer->request; + + DPRINTFN(2,("uhci_root_ctrl_control type=0x%02x request=%02x\n", + req->bmRequestType, req->bRequest)); + + len = UGETW(req->wLength); + value = UGETW(req->wValue); + index = UGETW(req->wIndex); + + if (len != 0) + buf = xfer->buffer; + +#define C(x,y) ((x) | ((y) << 8)) + switch(C(req->bRequest, req->bmRequestType)) { + case C(UR_CLEAR_FEATURE, UT_WRITE_DEVICE): + case C(UR_CLEAR_FEATURE, UT_WRITE_INTERFACE): + case C(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT): + /* + * DEVICE_REMOTE_WAKEUP and ENDPOINT_HALT are no-ops + * for the integrated root hub. + */ + break; + case C(UR_GET_CONFIG, UT_READ_DEVICE): + if (len > 0) { + *(u_int8_t *)buf = sc->sc_conf; + totlen = 1; + } + break; + case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE): + DPRINTFN(2,("uhci_root_ctrl_control wValue=0x%04x\n", value)); + switch(value >> 8) { + case UDESC_DEVICE: + if ((value & 0xff) != 0) { + err = USBD_IOERROR; + goto ret; + } + totlen = l = min(len, USB_DEVICE_DESCRIPTOR_SIZE); + USETW(uhci_devd.idVendor, sc->sc_id_vendor); + memcpy(buf, &uhci_devd, l); + break; + case UDESC_CONFIG: + if ((value & 0xff) != 0) { + err = USBD_IOERROR; + goto ret; + } + totlen = l = min(len, USB_CONFIG_DESCRIPTOR_SIZE); + memcpy(buf, &uhci_confd, l); + buf = (char *)buf + l; + len -= l; + l = min(len, USB_INTERFACE_DESCRIPTOR_SIZE); + totlen += l; + memcpy(buf, &uhci_ifcd, l); + buf = (char *)buf + l; + len -= l; + l = min(len, USB_ENDPOINT_DESCRIPTOR_SIZE); + totlen += l; + memcpy(buf, &uhci_endpd, l); + break; + case UDESC_STRING: + if (len == 0) + break; + *(u_int8_t *)buf = 0; + totlen = 1; + switch (value & 0xff) { + case 1: /* Vendor */ + totlen = uhci_str(buf, len, sc->sc_vendor); + break; + case 2: /* Product */ + totlen = uhci_str(buf, len, "UHCI root hub"); + break; + } + break; + default: + err = USBD_IOERROR; + goto ret; + } + break; + case C(UR_GET_INTERFACE, UT_READ_INTERFACE): + if (len > 0) { + *(u_int8_t *)buf = 0; + totlen = 1; + } + break; + case C(UR_GET_STATUS, UT_READ_DEVICE): + if (len > 1) { + USETW(((usb_status_t *)buf)->wStatus,UDS_SELF_POWERED); + totlen = 2; + } + break; + case C(UR_GET_STATUS, UT_READ_INTERFACE): + case C(UR_GET_STATUS, UT_READ_ENDPOINT): + if (len > 1) { + USETW(((usb_status_t *)buf)->wStatus, 0); + totlen = 2; + } + break; + case C(UR_SET_ADDRESS, UT_WRITE_DEVICE): + if (value >= USB_MAX_DEVICES) { + err = USBD_IOERROR; + goto ret; + } + sc->sc_addr = value; + break; + case C(UR_SET_CONFIG, UT_WRITE_DEVICE): + if (value != 0 && value != 1) { + err = USBD_IOERROR; + goto ret; + } + sc->sc_conf = value; + break; + case C(UR_SET_DESCRIPTOR, UT_WRITE_DEVICE): + break; + case C(UR_SET_FEATURE, UT_WRITE_DEVICE): + case C(UR_SET_FEATURE, UT_WRITE_INTERFACE): + case C(UR_SET_FEATURE, UT_WRITE_ENDPOINT): + err = USBD_IOERROR; + goto ret; + case C(UR_SET_INTERFACE, UT_WRITE_INTERFACE): + break; + case C(UR_SYNCH_FRAME, UT_WRITE_ENDPOINT): + break; + /* Hub requests */ + case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_DEVICE): + break; + case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_OTHER): + DPRINTFN(3, ("uhci_root_ctrl_control: UR_CLEAR_PORT_FEATURE " + "port=%d feature=%d\n", + index, value)); + if (index == 1) + port = UHCI_PORTSC1; + else if (index == 2) + port = UHCI_PORTSC2; + else { + err = USBD_IOERROR; + goto ret; + } + switch(value) { + case UHF_PORT_ENABLE: + x = URWMASK(UREAD2(sc, port)); + UWRITE2(sc, port, x & ~UHCI_PORTSC_PE); + break; + case UHF_PORT_SUSPEND: + x = URWMASK(UREAD2(sc, port)); + UWRITE2(sc, port, x & ~UHCI_PORTSC_SUSP); + break; + case UHF_PORT_RESET: + x = URWMASK(UREAD2(sc, port)); + UWRITE2(sc, port, x & ~UHCI_PORTSC_PR); + break; + case UHF_C_PORT_CONNECTION: + x = URWMASK(UREAD2(sc, port)); + UWRITE2(sc, port, x | UHCI_PORTSC_CSC); + break; + case UHF_C_PORT_ENABLE: + x = URWMASK(UREAD2(sc, port)); + UWRITE2(sc, port, x | UHCI_PORTSC_POEDC); + break; + case UHF_C_PORT_OVER_CURRENT: + x = URWMASK(UREAD2(sc, port)); + UWRITE2(sc, port, x | UHCI_PORTSC_OCIC); + break; + case UHF_C_PORT_RESET: + sc->sc_isreset = 0; + err = USBD_NORMAL_COMPLETION; + goto ret; + case UHF_PORT_CONNECTION: + case UHF_PORT_OVER_CURRENT: + case UHF_PORT_POWER: + case UHF_PORT_LOW_SPEED: + case UHF_C_PORT_SUSPEND: + default: + err = USBD_IOERROR; + goto ret; + } + break; + case C(UR_GET_BUS_STATE, UT_READ_CLASS_OTHER): + if (index == 1) + port = UHCI_PORTSC1; + else if (index == 2) + port = UHCI_PORTSC2; + else { + err = USBD_IOERROR; + goto ret; + } + if (len > 0) { + *(u_int8_t *)buf = + (UREAD2(sc, port) & UHCI_PORTSC_LS) >> + UHCI_PORTSC_LS_SHIFT; + totlen = 1; + } + break; + case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE): + if ((value & 0xff) != 0) { + err = USBD_IOERROR; + goto ret; + } + l = min(len, USB_HUB_DESCRIPTOR_SIZE); + totlen = l; + memcpy(buf, &uhci_hubd_piix, l); + break; + case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE): + if (len != 4) { + err = USBD_IOERROR; + goto ret; + } + memset(buf, 0, len); + totlen = len; + break; + case C(UR_GET_STATUS, UT_READ_CLASS_OTHER): + if (index == 1) + port = UHCI_PORTSC1; + else if (index == 2) + port = UHCI_PORTSC2; + else { + err = USBD_IOERROR; + goto ret; + } + if (len != 4) { + err = USBD_IOERROR; + goto ret; + } + x = UREAD2(sc, port); + status = change = 0; + if (x & UHCI_PORTSC_CCS) + status |= UPS_CURRENT_CONNECT_STATUS; + if (x & UHCI_PORTSC_CSC) + change |= UPS_C_CONNECT_STATUS; + if (x & UHCI_PORTSC_PE) + status |= UPS_PORT_ENABLED; + if (x & UHCI_PORTSC_POEDC) + change |= UPS_C_PORT_ENABLED; + if (x & UHCI_PORTSC_OCI) + status |= UPS_OVERCURRENT_INDICATOR; + if (x & UHCI_PORTSC_OCIC) + change |= UPS_C_OVERCURRENT_INDICATOR; + if (x & UHCI_PORTSC_SUSP) + status |= UPS_SUSPEND; + if (x & UHCI_PORTSC_LSDA) + status |= UPS_LOW_SPEED; + status |= UPS_PORT_POWER; + if (sc->sc_isreset) + change |= UPS_C_PORT_RESET; + USETW(ps.wPortStatus, status); + USETW(ps.wPortChange, change); + l = min(len, sizeof ps); + memcpy(buf, &ps, l); + totlen = l; + break; + case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE): + err = USBD_IOERROR; + goto ret; + case C(UR_SET_FEATURE, UT_WRITE_CLASS_DEVICE): + break; + case C(UR_SET_FEATURE, UT_WRITE_CLASS_OTHER): + if (index == 1) + port = UHCI_PORTSC1; + else if (index == 2) + port = UHCI_PORTSC2; + else { + err = USBD_IOERROR; + goto ret; + } + switch(value) { + case UHF_PORT_ENABLE: + x = URWMASK(UREAD2(sc, port)); + UWRITE2(sc, port, x | UHCI_PORTSC_PE); + break; + case UHF_PORT_SUSPEND: + x = URWMASK(UREAD2(sc, port)); + UWRITE2(sc, port, x | UHCI_PORTSC_SUSP); + break; + case UHF_PORT_RESET: + err = uhci_portreset(sc, index); + goto ret; + case UHF_PORT_POWER: + /* Pretend we turned on power */ + err = USBD_NORMAL_COMPLETION; + goto ret; + case UHF_C_PORT_CONNECTION: + case UHF_C_PORT_ENABLE: + case UHF_C_PORT_OVER_CURRENT: + case UHF_PORT_CONNECTION: + case UHF_PORT_OVER_CURRENT: + case UHF_PORT_LOW_SPEED: + case UHF_C_PORT_SUSPEND: + case UHF_C_PORT_RESET: + default: + err = USBD_IOERROR; + goto ret; + } + break; + default: + err = USBD_IOERROR; + goto ret; + } + xfer->actlen = totlen; + err = USBD_NORMAL_COMPLETION; + ret: + xfer->status = err; + s = splusb(); + uhci_transfer_complete(xfer); + splx(s); + return (USBD_IN_PROGRESS); +} + +/* Abort a root control request. */ +void +uhci_root_ctrl_abort(usbd_xfer_handle xfer) +{ + /* Nothing to do, all transfers are synchronous. */ +} + +/* Close the root pipe. */ +void +uhci_root_ctrl_close(usbd_pipe_handle pipe) +{ + DPRINTF(("uhci_root_ctrl_close\n")); +} + +/* Abort a root interrupt request. */ +void +uhci_root_intr_abort(usbd_xfer_handle xfer) +{ + uhci_softc_t *sc = (uhci_softc_t *)xfer->pipe->device->bus; + + callout_stop(&sc->sc_poll_handle); + sc->sc_intr_xfer = NULL; + + if (xfer->pipe->intrxfer == xfer) { + DPRINTF(("uhci_root_intr_abort: remove\n")); + xfer->pipe->intrxfer = 0; + } + xfer->status = USBD_CANCELLED; +#ifdef DIAGNOSTIC + UXFER(xfer)->iinfo.isdone = 1; +#endif + uhci_transfer_complete(xfer); +} + +usbd_status +uhci_root_intr_transfer(usbd_xfer_handle xfer) +{ + usbd_status err; + + /* Insert last in queue. */ + err = usb_insert_transfer(xfer); + if (err) + return (err); + + /* + * Pipe isn't running (otherwise err would be USBD_INPROG), + * so start it first. + */ + return (uhci_root_intr_start(STAILQ_FIRST(&xfer->pipe->queue))); +} + +/* Start a transfer on the root interrupt pipe */ +usbd_status +uhci_root_intr_start(usbd_xfer_handle xfer) +{ + usbd_pipe_handle pipe = xfer->pipe; + uhci_softc_t *sc = (uhci_softc_t *)pipe->device->bus; + + DPRINTFN(3, ("uhci_root_intr_start: xfer=%p len=%d flags=%d\n", + xfer, xfer->length, xfer->flags)); + + if (sc->sc_dying) + return (USBD_IOERROR); + + sc->sc_ival = MS_TO_TICKS(xfer->pipe->endpoint->edesc->bInterval); + callout_reset(&sc->sc_poll_handle, sc->sc_ival, uhci_poll_hub, xfer); + sc->sc_intr_xfer = xfer; + return (USBD_IN_PROGRESS); +} + +/* Close the root interrupt pipe. */ +void +uhci_root_intr_close(usbd_pipe_handle pipe) +{ + uhci_softc_t *sc = (uhci_softc_t *)pipe->device->bus; + + callout_stop(&sc->sc_poll_handle); + sc->sc_intr_xfer = NULL; + DPRINTF(("uhci_root_intr_close\n")); +} diff --git a/sys/legacy/dev/usb/uhci_pci.c b/sys/legacy/dev/usb/uhci_pci.c new file mode 100644 index 0000000..4ee19d6 --- /dev/null +++ b/sys/legacy/dev/usb/uhci_pci.c @@ -0,0 +1,524 @@ +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (augustss@carlstedt.se) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* Universal Host Controller Interface + * + * UHCI spec: http://www.intel.com/ + */ + +/* The low level controller code for UHCI has been split into + * PCI probes and UHCI specific code. This was done to facilitate the + * sharing of code between *BSD's + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/bus.h> +#include <sys/queue.h> +#include <sys/bus.h> + +#include <machine/bus.h> +#include <machine/resource.h> +#include <sys/rman.h> + +#include <dev/pci/pcivar.h> +#include <dev/pci/pcireg.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdivar.h> +#include <dev/usb/usb_mem.h> + +#include <dev/usb/uhcireg.h> +#include <dev/usb/uhcivar.h> + +#define PCI_UHCI_VENDORID_INTEL 0x8086 +#define PCI_UHCI_VENDORID_VIA 0x1106 + +#define PCI_UHCI_DEVICEID_PIIX3 0x70208086 +static const char *uhci_device_piix3 = "Intel 82371SB (PIIX3) USB controller"; + +#define PCI_UHCI_DEVICEID_PIIX4 0x71128086 +#define PCI_UHCI_DEVICEID_PIIX4E 0x71128086 /* no separate stepping */ +static const char *uhci_device_piix4 = "Intel 82371AB/EB (PIIX4) USB controller"; + +#define PCI_UHCI_DEVICEID_ICH 0x24128086 +static const char *uhci_device_ich = "Intel 82801AA (ICH) USB controller"; + +#define PCI_UHCI_DEVICEID_ICH0 0x24228086 +static const char *uhci_device_ich0 = "Intel 82801AB (ICH0) USB controller"; + +#define PCI_UHCI_DEVICEID_ICH2_A 0x24428086 +static const char *uhci_device_ich2_a = "Intel 82801BA/BAM (ICH2) USB controller USB-A"; + +#define PCI_UHCI_DEVICEID_ICH2_B 0x24448086 +static const char *uhci_device_ich2_b = "Intel 82801BA/BAM (ICH2) USB controller USB-B"; + +#define PCI_UHCI_DEVICEID_ICH3_A 0x24828086 +static const char *uhci_device_ich3_a = "Intel 82801CA/CAM (ICH3) USB controller USB-A"; + +#define PCI_UHCI_DEVICEID_ICH3_B 0x24848086 +static const char *uhci_device_ich3_b = "Intel 82801CA/CAM (ICH3) USB controller USB-B"; + +#define PCI_UHCI_DEVICEID_ICH3_C 0x24878086 +static const char *uhci_device_ich3_c = "Intel 82801CA/CAM (ICH3) USB controller USB-C"; + +#define PCI_UHCI_DEVICEID_ICH4_A 0x24c28086 +static const char *uhci_device_ich4_a = "Intel 82801DB (ICH4) USB controller USB-A"; + +#define PCI_UHCI_DEVICEID_ICH4_B 0x24c48086 +static const char *uhci_device_ich4_b = "Intel 82801DB (ICH4) USB controller USB-B"; + +#define PCI_UHCI_DEVICEID_ICH4_C 0x24c78086 +static const char *uhci_device_ich4_c = "Intel 82801DB (ICH4) USB controller USB-C"; + +#define PCI_UHCI_DEVICEID_ICH5_A 0x24d28086 +static const char *uhci_device_ich5_a = "Intel 82801EB (ICH5) USB controller USB-A"; + +#define PCI_UHCI_DEVICEID_ICH5_B 0x24d48086 +static const char *uhci_device_ich5_b = "Intel 82801EB (ICH5) USB controller USB-B"; + +#define PCI_UHCI_DEVICEID_ICH5_C 0x24d78086 +static const char *uhci_device_ich5_c = "Intel 82801EB (ICH5) USB controller USB-C"; + +#define PCI_UHCI_DEVICEID_ICH5_D 0x24de8086 +static const char *uhci_device_ich5_d = "Intel 82801EB (ICH5) USB controller USB-D"; + +#define PCI_UHCI_DEVICEID_ICH6_A 0x26588086 +static const char *uhci_device_ich6_a = "Intel 82801FB/FR/FW/FRW (ICH6) USB controller USB-A"; + +#define PCI_UHCI_DEVICEID_ICH6_B 0x26598086 +static const char *uhci_device_ich6_b = "Intel 82801FB/FR/FW/FRW (ICH6) USB controller USB-B"; + +#define PCI_UHCI_DEVICEID_ICH6_C 0x265a8086 +static const char *uhci_device_ich6_c = "Intel 82801FB/FR/FW/FRW (ICH6) USB controller USB-C"; + +#define PCI_UHCI_DEVICEID_ICH6_D 0x265b8086 +static const char *uhci_device_ich6_d = "Intel 82801FB/FR/FW/FRW (ICH6) USB controller USB-D"; + +#define PCI_UHCI_DEVICEID_63XXESB_1 0x26888086 +static const char *uhci_device_esb_1 = "Intel 631XESB/632XESB/3100 USB controller USB-1"; + +#define PCI_UHCI_DEVICEID_63XXESB_2 0x26898086 +static const char *uhci_device_esb_2 = "Intel 631XESB/632XESB/3100 USB controller USB-2"; + +#define PCI_UHCI_DEVICEID_63XXESB_3 0x268a8086 +static const char *uhci_device_esb_3 = "Intel 631XESB/632XESB/3100 USB controller USB-3"; + +#define PCI_UHCI_DEVICEID_63XXESB_4 0x268b8086 +static const char *uhci_device_esb_4 = "Intel 631XESB/632XESB/3100 USB controller USB-4"; + +#define PCI_UHCI_DEVICEID_ICH8_A 0x28308086 +static const char *uhci_device_ich8_a = "Intel 82801H (ICH8) USB controller USB-A"; + +#define PCI_UHCI_DEVICEID_ICH8_B 0x28318086 +static const char *uhci_device_ich8_b = "Intel 82801H (ICH8) USB controller USB-B"; + +#define PCI_UHCI_DEVICEID_ICH8_C 0x28328086 +static const char *uhci_device_ich8_c = "Intel 82801H (ICH8) USB controller USB-C"; + +#define PCI_UHCI_DEVICEID_ICH8_D 0x28348086 +static const char *uhci_device_ich8_d = "Intel 82801H (ICH8) USB controller USB-D"; + +#define PCI_UHCI_DEVICEID_ICH8_E 0x28358086 +static const char *uhci_device_ich8_e = "Intel 82801H (ICH8) USB controller USB-E"; + +#define PCI_UHCI_DEVICEID_ICH9_A 0x29348086 +#define PCI_UHCI_DEVICEID_ICH9_B 0x29358086 +#define PCI_UHCI_DEVICEID_ICH9_C 0x29368086 +#define PCI_UHCI_DEVICEID_ICH9_D 0x29378086 +#define PCI_UHCI_DEVICEID_ICH9_E 0x29388086 +#define PCI_UHCI_DEVICEID_ICH9_F 0x29398086 +static const char *uhci_device_ich9 = "Intel 82801I (ICH9) USB controller"; + +#define PCI_UHCI_DEVICEID_440MX 0x719a8086 +static const char *uhci_device_440mx = "Intel 82443MX USB controller"; + +#define PCI_UHCI_DEVICEID_460GX 0x76028086 +static const char *uhci_device_460gx = "Intel 82372FB/82468GX USB controller"; + +#define PCI_UHCI_DEVICEID_VT83C572 0x30381106 +static const char *uhci_device_vt83c572 = "VIA 83C572 USB controller"; + +static const char *uhci_device_generic = "UHCI (generic) USB controller"; + +#define PCI_UHCI_BASE_REG 0x20 + + +static device_attach_t uhci_pci_attach; +static device_detach_t uhci_pci_detach; +static device_suspend_t uhci_pci_suspend; +static device_resume_t uhci_pci_resume; + +static int +uhci_pci_suspend(device_t self) +{ + uhci_softc_t *sc = device_get_softc(self); + int err; + + err = bus_generic_suspend(self); + if (err) + return err; + uhci_power(PWR_SUSPEND, sc); + + return 0; +} + +static int +uhci_pci_resume(device_t self) +{ + uhci_softc_t *sc = device_get_softc(self); + + pci_write_config(self, PCI_LEGSUP, PCI_LEGSUP_USBPIRQDEN, 2); + + uhci_power(PWR_RESUME, sc); + bus_generic_resume(self); + + return 0; +} + +static const char * +uhci_pci_match(device_t self) +{ + u_int32_t device_id = pci_get_devid(self); + + if (device_id == PCI_UHCI_DEVICEID_PIIX3) { + return (uhci_device_piix3); + } else if (device_id == PCI_UHCI_DEVICEID_PIIX4) { + return (uhci_device_piix4); + } else if (device_id == PCI_UHCI_DEVICEID_ICH) { + return (uhci_device_ich); + } else if (device_id == PCI_UHCI_DEVICEID_ICH0) { + return (uhci_device_ich0); + } else if (device_id == PCI_UHCI_DEVICEID_ICH2_A) { + return (uhci_device_ich2_a); + } else if (device_id == PCI_UHCI_DEVICEID_ICH2_B) { + return (uhci_device_ich2_b); + } else if (device_id == PCI_UHCI_DEVICEID_ICH3_A) { + return (uhci_device_ich3_a); + } else if (device_id == PCI_UHCI_DEVICEID_ICH3_B) { + return (uhci_device_ich3_b); + } else if (device_id == PCI_UHCI_DEVICEID_ICH3_C) { + return (uhci_device_ich3_c); + } else if (device_id == PCI_UHCI_DEVICEID_ICH4_A) { + return (uhci_device_ich4_a); + } else if (device_id == PCI_UHCI_DEVICEID_ICH4_B) { + return (uhci_device_ich4_b); + } else if (device_id == PCI_UHCI_DEVICEID_ICH4_C) { + return (uhci_device_ich4_c); + } else if (device_id == PCI_UHCI_DEVICEID_ICH5_A) { + return (uhci_device_ich5_a); + } else if (device_id == PCI_UHCI_DEVICEID_ICH5_B) { + return (uhci_device_ich5_b); + } else if (device_id == PCI_UHCI_DEVICEID_ICH5_C) { + return (uhci_device_ich5_c); + } else if (device_id == PCI_UHCI_DEVICEID_ICH5_D) { + return (uhci_device_ich5_d); + } else if (device_id == PCI_UHCI_DEVICEID_ICH6_A) { + return (uhci_device_ich6_a); + } else if (device_id == PCI_UHCI_DEVICEID_ICH6_B) { + return (uhci_device_ich6_b); + } else if (device_id == PCI_UHCI_DEVICEID_ICH6_C) { + return (uhci_device_ich6_c); + } else if (device_id == PCI_UHCI_DEVICEID_ICH6_D) { + return (uhci_device_ich6_d); + } else if (device_id == PCI_UHCI_DEVICEID_63XXESB_1) { + return (uhci_device_esb_1); + } else if (device_id == PCI_UHCI_DEVICEID_63XXESB_2) { + return (uhci_device_esb_2); + } else if (device_id == PCI_UHCI_DEVICEID_63XXESB_3) { + return (uhci_device_esb_3); + } else if (device_id == PCI_UHCI_DEVICEID_63XXESB_4) { + return (uhci_device_esb_4); + } else if (device_id == PCI_UHCI_DEVICEID_ICH8_A) { + return (uhci_device_ich8_a); + } else if (device_id == PCI_UHCI_DEVICEID_ICH8_B) { + return (uhci_device_ich8_b); + } else if (device_id == PCI_UHCI_DEVICEID_ICH8_C) { + return (uhci_device_ich8_c); + } else if (device_id == PCI_UHCI_DEVICEID_ICH8_D) { + return (uhci_device_ich8_d); + } else if (device_id == PCI_UHCI_DEVICEID_ICH8_E) { + return (uhci_device_ich8_e); + } else if (device_id == PCI_UHCI_DEVICEID_ICH9_A) { + return (uhci_device_ich9); + } else if (device_id == PCI_UHCI_DEVICEID_ICH9_B) { + return (uhci_device_ich9); + } else if (device_id == PCI_UHCI_DEVICEID_ICH9_C) { + return (uhci_device_ich9); + } else if (device_id == PCI_UHCI_DEVICEID_ICH9_D) { + return (uhci_device_ich9); + } else if (device_id == PCI_UHCI_DEVICEID_ICH9_E) { + return (uhci_device_ich9); + } else if (device_id == PCI_UHCI_DEVICEID_ICH9_F) { + return (uhci_device_ich9); + } else if (device_id == PCI_UHCI_DEVICEID_440MX) { + return (uhci_device_440mx); + } else if (device_id == PCI_UHCI_DEVICEID_460GX) { + return (uhci_device_460gx); + } else if (device_id == PCI_UHCI_DEVICEID_VT83C572) { + return (uhci_device_vt83c572); + } else { + if (pci_get_class(self) == PCIC_SERIALBUS + && pci_get_subclass(self) == PCIS_SERIALBUS_USB + && pci_get_progif(self) == PCI_INTERFACE_UHCI) { + return (uhci_device_generic); + } + } + + return NULL; /* dunno... */ +} + +static int +uhci_pci_probe(device_t self) +{ + const char *desc = uhci_pci_match(self); + + if (desc) { + device_set_desc(self, desc); + return BUS_PROBE_DEFAULT; + } else { + return ENXIO; + } +} + +static int +uhci_pci_attach(device_t self) +{ + uhci_softc_t *sc = device_get_softc(self); + int rid; + int err; + + pci_enable_busmaster(self); + + rid = PCI_UHCI_BASE_REG; + sc->io_res = bus_alloc_resource_any(self, SYS_RES_IOPORT, &rid, + RF_ACTIVE); + if (!sc->io_res) { + device_printf(self, "Could not map ports\n"); + return ENXIO; + } + sc->iot = rman_get_bustag(sc->io_res); + sc->ioh = rman_get_bushandle(sc->io_res); + + /* disable interrupts */ + bus_space_write_2(sc->iot, sc->ioh, UHCI_INTR, 0); + + rid = 0; + sc->irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid, + RF_SHAREABLE | RF_ACTIVE); + if (sc->irq_res == NULL) { + device_printf(self, "Could not allocate irq\n"); + uhci_pci_detach(self); + return ENXIO; + } + sc->sc_bus.bdev = device_add_child(self, "usb", -1); + if (!sc->sc_bus.bdev) { + device_printf(self, "Could not add USB device\n"); + uhci_pci_detach(self); + return ENOMEM; + } + device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus); + + /* uhci_pci_match must never return NULL if uhci_pci_probe succeeded */ + device_set_desc(sc->sc_bus.bdev, uhci_pci_match(self)); + switch (pci_get_vendor(self)) { + case PCI_UHCI_VENDORID_INTEL: + sprintf(sc->sc_vendor, "Intel"); + break; + case PCI_UHCI_VENDORID_VIA: + sprintf(sc->sc_vendor, "VIA"); + break; + default: + if (bootverbose) + device_printf(self, "(New UHCI DeviceId=0x%08x)\n", + pci_get_devid(self)); + sprintf(sc->sc_vendor, "(0x%04x)", pci_get_vendor(self)); + } + + switch (pci_read_config(self, PCI_USBREV, 1) & PCI_USBREV_MASK) { + case PCI_USBREV_PRE_1_0: + sc->sc_bus.usbrev = USBREV_PRE_1_0; + break; + case PCI_USBREV_1_0: + sc->sc_bus.usbrev = USBREV_1_0; + break; + default: + sc->sc_bus.usbrev = USBREV_UNKNOWN; + break; + } + + /* + * Quirk for Parallels Desktop 4.0. + */ + if (pci_get_devid(self) == PCI_UHCI_DEVICEID_ICH6_A) + sc->sc_bus.usbrev = USBREV_2_0; + + err = bus_setup_intr(self, sc->irq_res, INTR_TYPE_BIO, + NULL, (driver_intr_t *) uhci_intr, sc, &sc->ih); + if (err) { + device_printf(self, "Could not setup irq, %d\n", err); + sc->ih = NULL; + uhci_pci_detach(self); + return ENXIO; + } + /* + * Set the PIRQD enable bit and switch off all the others. We don't + * want legacy support to interfere with us XXX Does this also mean + * that the BIOS won't touch the keyboard anymore if it is connected + * to the ports of the root hub? + */ +#ifdef USB_DEBUG + if (pci_read_config(self, PCI_LEGSUP, 2) != PCI_LEGSUP_USBPIRQDEN) + device_printf(self, "LegSup = 0x%04x\n", + pci_read_config(self, PCI_LEGSUP, 2)); +#endif + pci_write_config(self, PCI_LEGSUP, PCI_LEGSUP_USBPIRQDEN, 2); + + /* Allocate a parent dma tag for DMA maps */ + err = bus_dma_tag_create(bus_get_dma_tag(self), 1, 0, + BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, + BUS_SPACE_MAXSIZE_32BIT, USB_DMA_NSEG, BUS_SPACE_MAXSIZE_32BIT, 0, + NULL, NULL, &sc->sc_bus.parent_dmatag); + if (err) { + device_printf(self, "Could not allocate parent DMA tag (%d)\n", + err); + uhci_pci_detach(self); + return ENXIO; + } + /* Allocate a dma tag for transfer buffers */ + err = bus_dma_tag_create(sc->sc_bus.parent_dmatag, 1, 0, + BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, + BUS_SPACE_MAXSIZE_32BIT, USB_DMA_NSEG, BUS_SPACE_MAXSIZE_32BIT, 0, + busdma_lock_mutex, &Giant, &sc->sc_bus.buffer_dmatag); + if (err) { + device_printf(self, "Could not allocate transfer tag (%d)\n", + err); + uhci_pci_detach(self); + return ENXIO; + } + + err = uhci_init(sc); + if (!err) { + sc->sc_flags |= UHCI_SCFLG_DONEINIT; + err = device_probe_and_attach(sc->sc_bus.bdev); + } + + if (err) { + device_printf(self, "USB init failed\n"); + uhci_pci_detach(self); + return EIO; + } + return 0; /* success */ +} + +int +uhci_pci_detach(device_t self) +{ + uhci_softc_t *sc = device_get_softc(self); + + if (sc->sc_flags & UHCI_SCFLG_DONEINIT) { + uhci_detach(sc, 0); + sc->sc_flags &= ~UHCI_SCFLG_DONEINIT; + } + + if (sc->sc_bus.parent_dmatag != NULL) + bus_dma_tag_destroy(sc->sc_bus.parent_dmatag); + if (sc->sc_bus.buffer_dmatag != NULL) + bus_dma_tag_destroy(sc->sc_bus.buffer_dmatag); + + if (sc->irq_res && sc->ih) { + int err = bus_teardown_intr(self, sc->irq_res, sc->ih); + + if (err) + /* XXX or should we panic? */ + device_printf(self, "Could not tear down irq, %d\n", + err); + sc->ih = NULL; + } + if (sc->sc_bus.bdev) { + device_delete_child(self, sc->sc_bus.bdev); + sc->sc_bus.bdev = NULL; + } + if (sc->irq_res) { + bus_release_resource(self, SYS_RES_IRQ, 0, sc->irq_res); + sc->irq_res = NULL; + } + if (sc->io_res) { + bus_release_resource(self, SYS_RES_IOPORT, PCI_UHCI_BASE_REG, + sc->io_res); + sc->io_res = NULL; + sc->iot = 0; + sc->ioh = 0; + } + return 0; +} + + +static device_method_t uhci_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, uhci_pci_probe), + DEVMETHOD(device_attach, uhci_pci_attach), + DEVMETHOD(device_detach, uhci_pci_detach), + DEVMETHOD(device_suspend, uhci_pci_suspend), + DEVMETHOD(device_resume, uhci_pci_resume), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + + /* Bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + + {0, 0} +}; + +static driver_t uhci_driver = { + "uhci", + uhci_methods, + sizeof(uhci_softc_t), +}; + +static devclass_t uhci_devclass; + +DRIVER_MODULE(uhci, pci, uhci_driver, uhci_devclass, 0, 0); +DRIVER_MODULE(uhci, cardbus, uhci_driver, uhci_devclass, 0, 0); +MODULE_DEPEND(uhci, usb, 1, 1, 1); diff --git a/sys/legacy/dev/usb/uhcireg.h b/sys/legacy/dev/usb/uhcireg.h new file mode 100644 index 0000000..512dd2d --- /dev/null +++ b/sys/legacy/dev/usb/uhcireg.h @@ -0,0 +1,193 @@ +/* $NetBSD: uhcireg.h,v 1.15 2002/02/11 11:41:30 augustss Exp $ */ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _DEV_PCI_UHCIREG_H_ +#define _DEV_PCI_UHCIREG_H_ + +/*** PCI config registers ***/ + +#define PCI_USBREV 0x60 /* USB protocol revision */ +#define PCI_USBREV_MASK 0xff +#define PCI_USBREV_PRE_1_0 0x00 +#define PCI_USBREV_1_0 0x10 +#define PCI_USBREV_1_1 0x11 + +#define PCI_LEGSUP 0xc0 /* Legacy Support register */ +#define PCI_LEGSUP_USBPIRQDEN 0x2000 /* USB PIRQ D Enable */ + +#define PCI_CBIO 0x20 /* configuration base IO */ + +#define PCI_INTERFACE_UHCI 0x00 + +/*** UHCI registers ***/ + +#define UHCI_CMD 0x00 +#define UHCI_CMD_RS 0x0001 +#define UHCI_CMD_HCRESET 0x0002 +#define UHCI_CMD_GRESET 0x0004 +#define UHCI_CMD_EGSM 0x0008 +#define UHCI_CMD_FGR 0x0010 +#define UHCI_CMD_SWDBG 0x0020 +#define UHCI_CMD_CF 0x0040 +#define UHCI_CMD_MAXP 0x0080 + +#define UHCI_STS 0x02 +#define UHCI_STS_USBINT 0x0001 +#define UHCI_STS_USBEI 0x0002 +#define UHCI_STS_RD 0x0004 +#define UHCI_STS_HSE 0x0008 +#define UHCI_STS_HCPE 0x0010 +#define UHCI_STS_HCH 0x0020 +#define UHCI_STS_ALLINTRS 0x003f + +#define UHCI_INTR 0x04 +#define UHCI_INTR_TOCRCIE 0x0001 +#define UHCI_INTR_RIE 0x0002 +#define UHCI_INTR_IOCE 0x0004 +#define UHCI_INTR_SPIE 0x0008 + +#define UHCI_FRNUM 0x06 +#define UHCI_FRNUM_MASK 0x03ff + +#define UHCI_FLBASEADDR 0x08 + +#define UHCI_SOF 0x0c +#define UHCI_SOF_MASK 0x7f + +#define UHCI_PORTSC1 0x010 +#define UHCI_PORTSC2 0x012 +#define UHCI_PORTSC_CCS 0x0001 +#define UHCI_PORTSC_CSC 0x0002 +#define UHCI_PORTSC_PE 0x0004 +#define UHCI_PORTSC_POEDC 0x0008 +#define UHCI_PORTSC_LS 0x0030 +#define UHCI_PORTSC_LS_SHIFT 4 +#define UHCI_PORTSC_RD 0x0040 +#define UHCI_PORTSC_LSDA 0x0100 +#define UHCI_PORTSC_PR 0x0200 +#define UHCI_PORTSC_OCI 0x0400 +#define UHCI_PORTSC_OCIC 0x0800 +#define UHCI_PORTSC_SUSP 0x1000 + +#define URWMASK(x) \ + ((x) & (UHCI_PORTSC_SUSP | UHCI_PORTSC_PR | UHCI_PORTSC_RD | UHCI_PORTSC_PE)) + +#define UHCI_FRAMELIST_COUNT 1024 +#define UHCI_FRAMELIST_ALIGN 4096 + +#define UHCI_TD_ALIGN 16 +#define UHCI_QH_ALIGN 16 + +typedef u_int32_t uhci_physaddr_t; +#define UHCI_PTR_T 0x00000001 +#define UHCI_PTR_TD 0x00000000 +#define UHCI_PTR_QH 0x00000002 +#define UHCI_PTR_VF 0x00000004 + +/* + * Wait this long after a QH has been removed. This gives that HC a + * chance to stop looking at it before it's recycled. + */ +#define UHCI_QH_REMOVE_DELAY 5 + +/* + * The Queue Heads and Transfer Descriptors are accessed + * by both the CPU and the USB controller which run + * concurrently. This means that they have to be accessed + * with great care. As long as the data structures are + * not linked into the controller's frame list they cannot + * be accessed by it and anything goes. As soon as a + * TD is accessible by the controller it "owns" the td_status + * field; it will not be written by the CPU. Similarly + * the controller "owns" the qh_elink field. + */ + +typedef struct { + uhci_physaddr_t td_link; + u_int32_t td_status; +#define UHCI_TD_GET_ACTLEN(s) (((s) + 1) & 0x3ff) +#define UHCI_TD_ZERO_ACTLEN(t) ((t) | 0x3ff) +#define UHCI_TD_BITSTUFF 0x00020000 +#define UHCI_TD_CRCTO 0x00040000 +#define UHCI_TD_NAK 0x00080000 +#define UHCI_TD_BABBLE 0x00100000 +#define UHCI_TD_DBUFFER 0x00200000 +#define UHCI_TD_STALLED 0x00400000 +#define UHCI_TD_ACTIVE 0x00800000 +#define UHCI_TD_IOC 0x01000000 +#define UHCI_TD_IOS 0x02000000 +#define UHCI_TD_LS 0x04000000 +#define UHCI_TD_GET_ERRCNT(s) (((s) >> 27) & 3) +#define UHCI_TD_SET_ERRCNT(n) ((n) << 27) +#define UHCI_TD_SPD 0x20000000 + u_int32_t td_token; +#define UHCI_TD_PID_IN 0x00000069 +#define UHCI_TD_PID_OUT 0x000000e1 +#define UHCI_TD_PID_SETUP 0x0000002d +#define UHCI_TD_GET_PID(s) ((s) & 0xff) +#define UHCI_TD_SET_DEVADDR(a) ((a) << 8) +#define UHCI_TD_GET_DEVADDR(s) (((s) >> 8) & 0x7f) +#define UHCI_TD_SET_ENDPT(e) (((e)&0xf) << 15) +#define UHCI_TD_GET_ENDPT(s) (((s) >> 15) & 0xf) +#define UHCI_TD_SET_DT(t) ((t) << 19) +#define UHCI_TD_GET_DT(s) (((s) >> 19) & 1) +#define UHCI_TD_SET_MAXLEN(l) (((l)-1) << 21) +#define UHCI_TD_GET_MAXLEN(s) ((((s) >> 21) + 1) & 0x7ff) +#define UHCI_TD_MAXLEN_MASK 0xffe00000 + u_int32_t td_buffer; +} uhci_td_t; + +#define UHCI_TD_ERROR (UHCI_TD_BITSTUFF|UHCI_TD_CRCTO|UHCI_TD_BABBLE|UHCI_TD_DBUFFER|UHCI_TD_STALLED) + +#define UHCI_TD_SETUP(len, endp, dev) (UHCI_TD_SET_MAXLEN(len) | \ + UHCI_TD_SET_ENDPT(endp) | UHCI_TD_SET_DEVADDR(dev) | UHCI_TD_PID_SETUP) +#define UHCI_TD_OUT(len, endp, dev, dt) (UHCI_TD_SET_MAXLEN(len) | \ + UHCI_TD_SET_ENDPT(endp) | UHCI_TD_SET_DEVADDR(dev) | \ + UHCI_TD_PID_OUT | UHCI_TD_SET_DT(dt)) +#define UHCI_TD_IN(len, endp, dev, dt) (UHCI_TD_SET_MAXLEN(len) | \ + UHCI_TD_SET_ENDPT(endp) | UHCI_TD_SET_DEVADDR(dev) | UHCI_TD_PID_IN | \ + UHCI_TD_SET_DT(dt)) + +typedef struct { + uhci_physaddr_t qh_hlink; + uhci_physaddr_t qh_elink; +} uhci_qh_t; + +#endif /* _DEV_PCI_UHCIREG_H_ */ diff --git a/sys/legacy/dev/usb/uhcivar.h b/sys/legacy/dev/usb/uhcivar.h new file mode 100644 index 0000000..0df23f1 --- /dev/null +++ b/sys/legacy/dev/usb/uhcivar.h @@ -0,0 +1,206 @@ +/* $NetBSD: uhcivar.h,v 1.33 2002/02/11 11:41:30 augustss Exp $ */ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * To avoid having 1024 TDs for each isochronous transfer we introduce + * a virtual frame list. Every UHCI_VFRAMELIST_COUNT entries in the real + * frame list points to a non-active TD. These, in turn, form the + * starts of the virtual frame list. This also has the advantage that it + * simplifies linking in/out of TDs/QHs in the schedule. + * Furthermore, initially each of the inactive TDs point to an inactive + * QH that forms the start of the interrupt traffic for that slot. + * Each of these QHs point to the same QH that is the start of control + * traffic. This QH points at another QH which is the start of the + * bulk traffic. + * + * UHCI_VFRAMELIST_COUNT should be a power of 2 and <= UHCI_FRAMELIST_COUNT. + */ +#define UHCI_VFRAMELIST_COUNT 128 + +typedef struct uhci_soft_qh uhci_soft_qh_t; +typedef struct uhci_soft_td uhci_soft_td_t; + +typedef union { + struct uhci_soft_qh *sqh; + struct uhci_soft_td *std; +} uhci_soft_td_qh_t; + +/* + * An interrupt info struct contains the information needed to + * execute a requested routine when the controller generates an + * interrupt. Since we cannot know which transfer generated + * the interrupt all structs are linked together so they can be + * searched at interrupt time. + */ +typedef struct uhci_intr_info { + struct uhci_softc *sc; + usbd_xfer_handle xfer; + uhci_soft_td_t *stdstart; + uhci_soft_td_t *stdend; + LIST_ENTRY(uhci_intr_info) list; +#ifdef DIAGNOSTIC + int isdone; +#endif +} uhci_intr_info_t; + +struct uhci_xfer { + struct usbd_xfer xfer; + uhci_intr_info_t iinfo; + struct usb_task abort_task; + int curframe; + u_int32_t uhci_xfer_flags; +}; + +#define UHCI_XFER_ABORTING 0x0001 /* xfer is aborting. */ +#define UHCI_XFER_ABORTWAIT 0x0002 /* abort completion is being awaited. */ + +#define UXFER(xfer) ((struct uhci_xfer *)(xfer)) + +/* + * Extra information that we need for a TD. + */ +struct uhci_soft_td { + uhci_td_t td; /* The real TD, must be first */ + uhci_soft_td_qh_t link; /* soft version of the td_link field */ + uhci_physaddr_t physaddr; /* TD's physical address. */ + usb_dma_t aux_dma; /* Auxillary storage if needed. */ + void *aux_data; /* Original aux data virtual address. */ + int aux_len; /* Auxillary storage size. */ +}; +/* + * Make the size such that it is a multiple of UHCI_TD_ALIGN. This way + * we can pack a number of soft TD together and have the real TD well + * aligned. + * NOTE: Minimum size is 32 bytes. + */ +#define UHCI_STD_SIZE ((sizeof (struct uhci_soft_td) + UHCI_TD_ALIGN - 1) / UHCI_TD_ALIGN * UHCI_TD_ALIGN) +#define UHCI_STD_CHUNK (PAGE_SIZE / UHCI_STD_SIZE) + +/* + * Extra information that we need for a QH. + */ +struct uhci_soft_qh { + uhci_qh_t qh; /* The real QH, must be first */ + uhci_soft_qh_t *hlink; /* soft version of qh_hlink */ + uhci_soft_td_t *elink; /* soft version of qh_elink */ + uhci_physaddr_t physaddr; /* QH's physical address. */ + int pos; /* Timeslot position */ +}; +/* See comment about UHCI_STD_SIZE. */ +#define UHCI_SQH_SIZE ((sizeof (struct uhci_soft_qh) + UHCI_QH_ALIGN - 1) / UHCI_QH_ALIGN * UHCI_QH_ALIGN) +#define UHCI_SQH_CHUNK (PAGE_SIZE / UHCI_SQH_SIZE) + +/* + * Information about an entry in the virtual frame list. + */ +struct uhci_vframe { + uhci_soft_td_t *htd; /* pointer to dummy TD */ + uhci_soft_td_t *etd; /* pointer to last TD */ + uhci_soft_qh_t *hqh; /* pointer to dummy QH */ + uhci_soft_qh_t *eqh; /* pointer to last QH */ + u_int bandwidth; /* max bandwidth used by this frame */ +}; + +#define UHCI_SCFLG_DONEINIT 0x0001 /* uhci_init() done */ + +typedef struct uhci_softc { + struct usbd_bus sc_bus; /* base device */ + int sc_flags; + bus_space_tag_t iot; + bus_space_handle_t ioh; + bus_size_t sc_size; + void *ih; + + struct resource *io_res; + struct resource *irq_res; + uhci_physaddr_t *sc_pframes; + usb_dma_t sc_dma; + struct uhci_vframe sc_vframes[UHCI_VFRAMELIST_COUNT]; + + uhci_soft_qh_t *sc_lctl_start; /* dummy QH for low speed control */ + uhci_soft_qh_t *sc_lctl_end; /* last control QH */ + uhci_soft_qh_t *sc_hctl_start; /* dummy QH for high speed control */ + uhci_soft_qh_t *sc_hctl_end; /* last control QH */ + uhci_soft_qh_t *sc_bulk_start; /* dummy QH for bulk */ + uhci_soft_qh_t *sc_bulk_end; /* last bulk transfer */ + uhci_soft_qh_t *sc_last_qh; /* dummy QH at the end */ + u_int32_t sc_loops; /* number of QHs that wants looping */ + + uhci_soft_td_t *sc_freetds; /* TD free list */ + uhci_soft_qh_t *sc_freeqhs; /* QH free list */ + + STAILQ_HEAD(, usbd_xfer) sc_free_xfers; /* free xfers */ + + u_int8_t sc_addr; /* device address */ + u_int8_t sc_conf; /* device configuration */ + + u_int8_t sc_saved_sof; + u_int16_t sc_saved_frnum; + +#ifdef USB_USE_SOFTINTR + char sc_softwake; +#endif /* USB_USE_SOFTINTR */ + + char sc_isreset; + char sc_suspend; + char sc_dying; + + LIST_HEAD(, uhci_intr_info) sc_intrhead; + + /* Info for the root hub interrupt channel. */ + int sc_ival; /* time between root hub intrs */ + usbd_xfer_handle sc_intr_xfer; /* root hub interrupt transfer */ + struct callout sc_poll_handle; + + char sc_vendor[16]; /* vendor string for root hub */ + int sc_id_vendor; /* vendor ID for root hub */ + +#if defined(__NetBSD__) + void *sc_powerhook; /* cookie from power hook */ + void *sc_shutdownhook; /* cookie from shutdown hook */ +#endif +} uhci_softc_t; + +usbd_status uhci_init(uhci_softc_t *); +int uhci_intr(void *); +int uhci_detach(uhci_softc_t *, int); +void uhci_shutdown(void *v); +void uhci_power(int state, void *priv); + diff --git a/sys/legacy/dev/usb/uhid.c b/sys/legacy/dev/usb/uhid.c new file mode 100644 index 0000000..d821c01 --- /dev/null +++ b/sys/legacy/dev/usb/uhid.c @@ -0,0 +1,755 @@ +/* $NetBSD: uhid.c,v 1.46 2001/11/13 06:24:55 lukem Exp $ */ + +/* Also already merged from NetBSD: + * $NetBSD: uhid.c,v 1.54 2002/09/23 05:51:21 simonb Exp $ + */ + +#include <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. + */ + +/* + * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf + */ + +/* + * XXX TODO: Convert this driver to use si_drv[12] rather than the + * devclass_get_softc junk + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/clist.h> +#include <sys/kernel.h> +#include <sys/lock.h> +#include <sys/malloc.h> +#include <sys/mutex.h> +#include <sys/signalvar.h> +#include <sys/fcntl.h> +#include <sys/ioccom.h> +#include <sys/filio.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/ioccom.h> +#include <sys/conf.h> +#include <sys/selinfo.h> +#include <sys/proc.h> +#include <sys/poll.h> +#include <sys/sysctl.h> +#include <sys/ttycom.h> +#include <sys/uio.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbhid.h> + +#include "usbdevs.h" +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/hid.h> + +/* Replacement report descriptors for devices shipped with broken ones */ +#include <dev/usb/ugraphire_rdesc.h> +#include <dev/usb/uxb360gp_rdesc.h> + +/* For hid blacklist quirk */ +#include <dev/usb/usb_quirks.h> + +#ifdef USB_DEBUG +#define DPRINTF(x) if (uhiddebug) printf x +#define DPRINTFN(n,x) if (uhiddebug>(n)) printf x +int uhiddebug = 0; +SYSCTL_NODE(_hw_usb, OID_AUTO, uhid, CTLFLAG_RW, 0, "USB uhid"); +SYSCTL_INT(_hw_usb_uhid, OID_AUTO, debug, CTLFLAG_RW, + &uhiddebug, 0, "uhid debug level"); +#else +#define DPRINTF(x) +#define DPRINTFN(n,x) +#endif + +struct uhid_softc { + device_t sc_dev; /* base device */ + usbd_device_handle sc_udev; + usbd_interface_handle sc_iface; /* interface */ + usbd_pipe_handle sc_intrpipe; /* interrupt pipe */ + int sc_ep_addr; + + int sc_isize; + int sc_osize; + int sc_fsize; + u_int8_t sc_iid; + u_int8_t sc_oid; + u_int8_t sc_fid; + + u_char *sc_ibuf; + u_char *sc_obuf; + + void *sc_repdesc; + int sc_repdesc_size; + + struct clist sc_q; + struct selinfo sc_rsel; + struct proc *sc_async; /* process that wants SIGIO */ + u_char sc_state; /* driver state */ +#define UHID_OPEN 0x01 /* device is open */ +#define UHID_ASLP 0x02 /* waiting for device data */ +#define UHID_NEEDCLEAR 0x04 /* needs clearing endpoint stall */ +#define UHID_IMMED 0x08 /* return read data immediately */ + + int sc_refcnt; + u_char sc_dying; + + struct cdev *dev; +}; + +#define UHIDUNIT(dev) (dev2unit(dev)) +#define UHID_CHUNK 128 /* chunk size for read */ +#define UHID_BSIZE 1020 /* buffer size */ + +d_open_t uhidopen; +d_close_t uhidclose; +d_read_t uhidread; +d_write_t uhidwrite; +d_ioctl_t uhidioctl; +d_poll_t uhidpoll; + + +static struct cdevsw uhid_cdevsw = { + .d_version = D_VERSION, + .d_flags = D_NEEDGIANT, + .d_open = uhidopen, + .d_close = uhidclose, + .d_read = uhidread, + .d_write = uhidwrite, + .d_ioctl = uhidioctl, + .d_poll = uhidpoll, + .d_name = "uhid", +}; + +static void uhid_intr(usbd_xfer_handle, usbd_private_handle, + usbd_status); + +static int uhid_do_read(struct uhid_softc *, struct uio *uio, int); +static int uhid_do_write(struct uhid_softc *, struct uio *uio, int); +static int uhid_do_ioctl(struct uhid_softc *, u_long, caddr_t, int, + struct thread *); + +MODULE_DEPEND(uhid, usb, 1, 1, 1); + +static device_probe_t uhid_match; +static device_attach_t uhid_attach; +static device_detach_t uhid_detach; + +static device_method_t uhid_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, uhid_match), + DEVMETHOD(device_attach, uhid_attach), + DEVMETHOD(device_detach, uhid_detach), + + { 0, 0 } +}; + +static driver_t uhid_driver = { + "uhid", + uhid_methods, + sizeof(struct uhid_softc) +}; + +static devclass_t uhid_devclass; + +DRIVER_MODULE(uhid, uhub, uhid_driver, uhid_devclass, usbd_driver_load, 0); + +static int +uhid_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + usb_interface_descriptor_t *id; + + if (uaa->iface == NULL) + return (UMATCH_NONE); + id = usbd_get_interface_descriptor(uaa->iface); + if (id == NULL) + return (UMATCH_NONE); + if (id->bInterfaceClass != UICLASS_HID) { + /* The Xbox 360 gamepad doesn't use the HID class. */ + if (id->bInterfaceClass != UICLASS_VENDOR || + id->bInterfaceSubClass != UISUBCLASS_XBOX360_CONTROLLER || + id->bInterfaceProtocol != UIPROTO_XBOX360_GAMEPAD) + return (UMATCH_NONE); + } + if (usbd_get_quirks(uaa->device)->uq_flags & UQ_HID_IGNORE) + return (UMATCH_NONE); +#if 0 + if (uaa->matchlvl) + return (uaa->matchlvl); +#endif + + return (UMATCH_IFACECLASS_GENERIC); +} + +static int +uhid_attach(device_t self) +{ + struct uhid_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + usbd_interface_handle iface = uaa->iface; + usb_interface_descriptor_t *id; + usb_endpoint_descriptor_t *ed; + int size; + void *desc; + const void *descptr; + usbd_status err; + + sc->sc_dev = self; + sc->sc_udev = uaa->device; + sc->sc_iface = iface; + id = usbd_get_interface_descriptor(iface); + + ed = usbd_interface2endpoint_descriptor(iface, 0); + if (ed == NULL) { + printf("%s: could not read endpoint descriptor\n", + device_get_nameunit(sc->sc_dev)); + sc->sc_dying = 1; + return ENXIO; + } + + DPRINTFN(10,("uhid_attach: bLength=%d bDescriptorType=%d " + "bEndpointAddress=%d-%s bmAttributes=%d wMaxPacketSize=%d" + " bInterval=%d\n", + ed->bLength, ed->bDescriptorType, + ed->bEndpointAddress & UE_ADDR, + UE_GET_DIR(ed->bEndpointAddress)==UE_DIR_IN? "in" : "out", + ed->bmAttributes & UE_XFERTYPE, + UGETW(ed->wMaxPacketSize), ed->bInterval)); + + if (UE_GET_DIR(ed->bEndpointAddress) != UE_DIR_IN || + (ed->bmAttributes & UE_XFERTYPE) != UE_INTERRUPT) { + printf("%s: unexpected endpoint\n", device_get_nameunit(sc->sc_dev)); + sc->sc_dying = 1; + return ENXIO; + } + + sc->sc_ep_addr = ed->bEndpointAddress; + + descptr = NULL; + if (uaa->vendor == USB_VENDOR_WACOM) { + /* The report descriptor for the Wacom Graphire is broken. */ + if (uaa->product == USB_PRODUCT_WACOM_GRAPHIRE) { + size = sizeof uhid_graphire_report_descr; + descptr = uhid_graphire_report_descr; + } else if (uaa->product == USB_PRODUCT_WACOM_GRAPHIRE3_4X5) { + static uByte reportbuf[] = {2, 2, 2}; + + /* + * The Graphire3 needs 0x0202 to be written to + * feature report ID 2 before it'll start + * returning digitizer data. + */ + usbd_set_report(uaa->iface, UHID_FEATURE_REPORT, 2, + &reportbuf, sizeof reportbuf); + + size = sizeof uhid_graphire3_4x5_report_descr; + descptr = uhid_graphire3_4x5_report_descr; + } + } else if (id->bInterfaceClass == UICLASS_VENDOR && + id->bInterfaceSubClass == UISUBCLASS_XBOX360_CONTROLLER && + id->bInterfaceProtocol == UIPROTO_XBOX360_GAMEPAD) { + static uByte reportbuf[] = {1, 3, 0}; + + /* The LEDs on the gamepad are blinking by default, turn off. */ + usbd_set_report(uaa->iface, UHID_OUTPUT_REPORT, 0, + &reportbuf, sizeof reportbuf); + + /* The Xbox 360 gamepad has no report descriptor. */ + size = sizeof uhid_xb360gp_report_descr; + descptr = uhid_xb360gp_report_descr; + } + + if (descptr) { + desc = malloc(size, M_USBDEV, M_NOWAIT); + if (desc == NULL) + err = USBD_NOMEM; + else { + err = USBD_NORMAL_COMPLETION; + memcpy(desc, descptr, size); + } + } else { + desc = NULL; + err = usbd_read_report_desc(uaa->iface, &desc, &size,M_USBDEV); + } + + if (err) { + printf("%s: no report descriptor\n", device_get_nameunit(sc->sc_dev)); + sc->sc_dying = 1; + return ENXIO; + } + + (void)usbd_set_idle(iface, 0, 0); + + sc->sc_isize = hid_report_size(desc, size, hid_input, &sc->sc_iid); + sc->sc_osize = hid_report_size(desc, size, hid_output, &sc->sc_oid); + sc->sc_fsize = hid_report_size(desc, size, hid_feature, &sc->sc_fid); + + sc->sc_repdesc = desc; + sc->sc_repdesc_size = size; + sc->dev = make_dev(&uhid_cdevsw, device_get_unit(self), + UID_ROOT, GID_OPERATOR, + 0644, "uhid%d", device_get_unit(self)); + return 0; +} + +static int +uhid_detach(device_t self) +{ + struct uhid_softc *sc = device_get_softc(self); + int s; + + DPRINTF(("uhid_detach: sc=%p\n", sc)); + sc->sc_dying = 1; + if (sc->sc_intrpipe != NULL) + usbd_abort_pipe(sc->sc_intrpipe); + + if (sc->sc_state & UHID_OPEN) { + s = splusb(); + if (--sc->sc_refcnt >= 0) { + /* Wake everyone */ + wakeup(&sc->sc_q); + /* Wait for processes to go away. */ + usb_detach_wait(sc->sc_dev); + } + splx(s); + } + destroy_dev(sc->dev); + + if (sc->sc_repdesc) + free(sc->sc_repdesc, M_USBDEV); + + return (0); +} + +void +uhid_intr(usbd_xfer_handle xfer, usbd_private_handle addr, usbd_status status) +{ + struct uhid_softc *sc = addr; + +#ifdef USB_DEBUG + if (uhiddebug > 5) { + u_int32_t cc, i; + + usbd_get_xfer_status(xfer, NULL, NULL, &cc, NULL); + DPRINTF(("uhid_intr: status=%d cc=%d\n", status, cc)); + DPRINTF(("uhid_intr: data =")); + for (i = 0; i < cc; i++) + DPRINTF((" %02x", sc->sc_ibuf[i])); + DPRINTF(("\n")); + } +#endif + + if (status == USBD_CANCELLED) + return; + + if (status != USBD_NORMAL_COMPLETION) { + DPRINTF(("uhid_intr: status=%d\n", status)); + if (status == USBD_STALLED) + sc->sc_state |= UHID_NEEDCLEAR; + return; + } + + (void) b_to_q(sc->sc_ibuf, sc->sc_isize, &sc->sc_q); + + if (sc->sc_state & UHID_ASLP) { + sc->sc_state &= ~UHID_ASLP; + DPRINTFN(5, ("uhid_intr: waking %p\n", &sc->sc_q)); + wakeup(&sc->sc_q); + } + selwakeuppri(&sc->sc_rsel, PZERO); + if (sc->sc_async != NULL) { + DPRINTFN(3, ("uhid_intr: sending SIGIO %p\n", sc->sc_async)); + PROC_LOCK(sc->sc_async); + psignal(sc->sc_async, SIGIO); + PROC_UNLOCK(sc->sc_async); + } +} + +int +uhidopen(struct cdev *dev, int flag, int mode, struct thread *p) +{ + struct uhid_softc *sc; + usbd_status err; + + sc = devclass_get_softc(uhid_devclass, UHIDUNIT(dev)); + if (sc == NULL) + return (ENXIO); + + DPRINTF(("uhidopen: sc=%p\n", sc)); + + if (sc->sc_dying) + return (ENXIO); + + if (sc->sc_state & UHID_OPEN) + return (EBUSY); + sc->sc_state |= UHID_OPEN; + + clist_alloc_cblocks(&sc->sc_q, UHID_BSIZE, UHID_BSIZE); + sc->sc_ibuf = malloc(sc->sc_isize, M_USBDEV, M_WAITOK); + sc->sc_obuf = malloc(sc->sc_osize, M_USBDEV, M_WAITOK); + + /* Set up interrupt pipe. */ + err = usbd_open_pipe_intr(sc->sc_iface, sc->sc_ep_addr, + USBD_SHORT_XFER_OK, &sc->sc_intrpipe, sc, sc->sc_ibuf, + sc->sc_isize, uhid_intr, USBD_DEFAULT_INTERVAL); + if (err) { + DPRINTF(("uhidopen: usbd_open_pipe_intr failed, " + "error=%d\n",err)); + free(sc->sc_ibuf, M_USBDEV); + free(sc->sc_obuf, M_USBDEV); + sc->sc_ibuf = sc->sc_obuf = NULL; + + sc->sc_state &= ~UHID_OPEN; + return (EIO); + } + + sc->sc_state &= ~UHID_IMMED; + + sc->sc_async = 0; + + return (0); +} + +int +uhidclose(struct cdev *dev, int flag, int mode, struct thread *p) +{ + struct uhid_softc *sc; + + sc = devclass_get_softc(uhid_devclass, UHIDUNIT(dev)); + + DPRINTF(("uhidclose: sc=%p\n", sc)); + + /* Disable interrupts. */ + usbd_abort_pipe(sc->sc_intrpipe); + usbd_close_pipe(sc->sc_intrpipe); + sc->sc_intrpipe = 0; + + ndflush(&sc->sc_q, sc->sc_q.c_cc); + clist_free_cblocks(&sc->sc_q); + + free(sc->sc_ibuf, M_USBDEV); + free(sc->sc_obuf, M_USBDEV); + sc->sc_ibuf = sc->sc_obuf = NULL; + + sc->sc_state &= ~UHID_OPEN; + + sc->sc_async = 0; + + return (0); +} + +int +uhid_do_read(struct uhid_softc *sc, struct uio *uio, int flag) +{ + int s; + int error = 0; + size_t length; + u_char buffer[UHID_CHUNK]; + usbd_status err; + + DPRINTFN(1, ("uhidread\n")); + if (sc->sc_state & UHID_IMMED) { + DPRINTFN(1, ("uhidread immed\n")); + + err = usbd_get_report(sc->sc_iface, UHID_INPUT_REPORT, + sc->sc_iid, buffer, sc->sc_isize); + if (err) + return (EIO); + return (uiomove(buffer, sc->sc_isize, uio)); + } + + s = splusb(); + while (sc->sc_q.c_cc == 0) { + if (flag & O_NONBLOCK) { + splx(s); + return (EWOULDBLOCK); + } + sc->sc_state |= UHID_ASLP; + DPRINTFN(5, ("uhidread: sleep on %p\n", &sc->sc_q)); + error = tsleep(&sc->sc_q, PZERO | PCATCH, "uhidrea", 0); + DPRINTFN(5, ("uhidread: woke, error=%d\n", error)); + if (sc->sc_dying) + error = EIO; + if (error) { + sc->sc_state &= ~UHID_ASLP; + break; + } + if (sc->sc_state & UHID_NEEDCLEAR) { + DPRINTFN(-1,("uhidread: clearing stall\n")); + sc->sc_state &= ~UHID_NEEDCLEAR; + usbd_clear_endpoint_stall(sc->sc_intrpipe); + } + } + splx(s); + + /* Transfer as many chunks as possible. */ + while (sc->sc_q.c_cc > 0 && uio->uio_resid > 0 && !error) { + length = min(sc->sc_q.c_cc, uio->uio_resid); + if (length > sizeof(buffer)) + length = sizeof(buffer); + + /* Remove a small chunk from the input queue. */ + (void) q_to_b(&sc->sc_q, buffer, length); + DPRINTFN(5, ("uhidread: got %lu chars\n", (u_long)length)); + + /* Copy the data to the user process. */ + if ((error = uiomove(buffer, length, uio)) != 0) + break; + } + + return (error); +} + +int +uhidread(struct cdev *dev, struct uio *uio, int flag) +{ + struct uhid_softc *sc; + int error; + + sc = devclass_get_softc(uhid_devclass, UHIDUNIT(dev)); + sc->sc_refcnt++; + error = uhid_do_read(sc, uio, flag); + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); + return (error); +} + +int +uhid_do_write(struct uhid_softc *sc, struct uio *uio, int flag) +{ + int error; + int size; + usbd_status err; + + DPRINTFN(1, ("uhidwrite\n")); + + if (sc->sc_dying) + return (EIO); + + size = sc->sc_osize; + error = 0; + if (uio->uio_resid != size) + return (EINVAL); + error = uiomove(sc->sc_obuf, size, uio); + if (!error) { + if (sc->sc_oid) + err = usbd_set_report(sc->sc_iface, UHID_OUTPUT_REPORT, + sc->sc_obuf[0], sc->sc_obuf+1, size-1); + else + err = usbd_set_report(sc->sc_iface, UHID_OUTPUT_REPORT, + 0, sc->sc_obuf, size); + if (err) + error = EIO; + } + + return (error); +} + +int +uhidwrite(struct cdev *dev, struct uio *uio, int flag) +{ + struct uhid_softc *sc; + int error; + + sc = devclass_get_softc(uhid_devclass, UHIDUNIT(dev)); + sc->sc_refcnt++; + error = uhid_do_write(sc, uio, flag); + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); + return (error); +} + +int +uhid_do_ioctl(struct uhid_softc *sc, u_long cmd, caddr_t addr, int flag, + struct thread *p) +{ + struct usb_ctl_report_desc *rd; + struct usb_ctl_report *re; + int size, id; + usbd_status err; + + DPRINTFN(2, ("uhidioctl: cmd=%lx\n", cmd)); + + if (sc->sc_dying) + return (EIO); + + switch (cmd) { + case FIONBIO: + /* All handled in the upper FS layer. */ + break; + + case FIOASYNC: + if (*(int *)addr) { + if (sc->sc_async != NULL) + return (EBUSY); + sc->sc_async = p->td_proc; + DPRINTF(("uhid_do_ioctl: FIOASYNC %p\n", sc->sc_async)); + } else + sc->sc_async = NULL; + break; + + /* XXX this is not the most general solution. */ + case TIOCSPGRP: + if (sc->sc_async == NULL) + return (EINVAL); + if (*(int *)addr != sc->sc_async->p_pgid) + return (EPERM); + break; + + case USB_GET_REPORT_DESC: + rd = (struct usb_ctl_report_desc *)addr; + size = min(sc->sc_repdesc_size, sizeof rd->ucrd_data); + rd->ucrd_size = size; + memcpy(rd->ucrd_data, sc->sc_repdesc, size); + break; + + case USB_SET_IMMED: + if (*(int *)addr) { + /* XXX should read into ibuf, but does it matter? */ + err = usbd_get_report(sc->sc_iface, UHID_INPUT_REPORT, + sc->sc_iid, sc->sc_ibuf, sc->sc_isize); + if (err) + return (EOPNOTSUPP); + + sc->sc_state |= UHID_IMMED; + } else + sc->sc_state &= ~UHID_IMMED; + break; + + case USB_GET_REPORT: + re = (struct usb_ctl_report *)addr; + switch (re->ucr_report) { + case UHID_INPUT_REPORT: + size = sc->sc_isize; + id = sc->sc_iid; + break; + case UHID_OUTPUT_REPORT: + size = sc->sc_osize; + id = sc->sc_oid; + break; + case UHID_FEATURE_REPORT: + size = sc->sc_fsize; + id = sc->sc_fid; + break; + default: + return (EINVAL); + } + err = usbd_get_report(sc->sc_iface, re->ucr_report, id, re->ucr_data, + size); + if (err) + return (EIO); + break; + + case USB_SET_REPORT: + re = (struct usb_ctl_report *)addr; + switch (re->ucr_report) { + case UHID_INPUT_REPORT: + size = sc->sc_isize; + id = sc->sc_iid; + break; + case UHID_OUTPUT_REPORT: + size = sc->sc_osize; + id = sc->sc_oid; + break; + case UHID_FEATURE_REPORT: + size = sc->sc_fsize; + id = sc->sc_fid; + break; + default: + return (EINVAL); + } + err = usbd_set_report(sc->sc_iface, re->ucr_report, id, re->ucr_data, + size); + if (err) + return (EIO); + break; + + case USB_GET_REPORT_ID: + *(int *)addr = 0; /* XXX: we only support reportid 0? */ + break; + + default: + return (EINVAL); + } + return (0); +} + +int +uhidioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, struct thread *p) +{ + struct uhid_softc *sc; + int error; + + sc = devclass_get_softc(uhid_devclass, UHIDUNIT(dev)); + sc->sc_refcnt++; + error = uhid_do_ioctl(sc, cmd, addr, flag, p); + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); + return (error); +} + +int +uhidpoll(struct cdev *dev, int events, struct thread *p) +{ + struct uhid_softc *sc; + int revents = 0; + int s; + + sc = devclass_get_softc(uhid_devclass, UHIDUNIT(dev)); + if (sc->sc_dying) + return (EIO); + + s = splusb(); + if (events & (POLLOUT | POLLWRNORM)) + revents |= events & (POLLOUT | POLLWRNORM); + if (events & (POLLIN | POLLRDNORM)) { + if (sc->sc_q.c_cc > 0) + revents |= events & (POLLIN | POLLRDNORM); + else + selrecord(p, &sc->sc_rsel); + } + + splx(s); + return (revents); +} diff --git a/sys/legacy/dev/usb/uhub.c b/sys/legacy/dev/usb/uhub.c new file mode 100644 index 0000000..86fbba2 --- /dev/null +++ b/sys/legacy/dev/usb/uhub.c @@ -0,0 +1,703 @@ +/* $NetBSD: uhub.c,v 1.68 2004/06/29 06:30:05 mycroft Exp $ */ + +/*- + * 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * USB spec: http://www.usb.org/developers/docs/usbspec.zip + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/sysctl.h> + +#include <machine/bus.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usbdivar.h> + +#define UHUB_INTR_INTERVAL 255 /* ms */ + +#ifdef USB_DEBUG +#define DPRINTF(x) if (uhubdebug) printf x +#define DPRINTFN(n,x) if (uhubdebug > (n)) printf x +#define DEVPRINTF(x) if (uhubdebug) device_printf x +#define DEVPRINTFN(n, x)if (uhubdebug > (n)) device_printf x +int uhubdebug = 0; +SYSCTL_NODE(_hw_usb, OID_AUTO, uhub, CTLFLAG_RW, 0, "USB uhub"); +SYSCTL_INT(_hw_usb_uhub, OID_AUTO, debug, CTLFLAG_RW, + &uhubdebug, 0, "uhub debug level"); +#else +#define DPRINTF(x) +#define DPRINTFN(n,x) +#define DEVPRINTF(x) +#define DEVPRINTFN(n,x) +#endif + +struct uhub_softc { + device_t sc_dev; /* base device */ + usbd_device_handle sc_hub; /* USB device */ + usbd_pipe_handle sc_ipipe; /* interrupt pipe */ + u_int8_t sc_status[32]; /* max 255 ports */ + u_char sc_running; +}; +#define UHUB_PROTO(sc) ((sc)->sc_hub->ddesc.bDeviceProtocol) +#define UHUB_IS_HIGH_SPEED(sc) (UHUB_PROTO(sc) != UDPROTO_FSHUB) +#define UHUB_IS_SINGLE_TT(sc) (UHUB_PROTO(sc) == UDPROTO_HSHUBSTT) + +static usbd_status uhub_explore(usbd_device_handle hub); +static void uhub_intr(usbd_xfer_handle, usbd_private_handle,usbd_status); + +/* + * We need two attachment points: + * hub to usb and hub to hub + * Every other driver only connects to hubs + */ + +static device_probe_t uhub_match; +static device_attach_t uhub_attach; +static device_detach_t uhub_detach; +static bus_child_location_str_t uhub_child_location_str; +static bus_child_pnpinfo_str_t uhub_child_pnpinfo_str; + +static device_method_t uhub_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, uhub_match), + DEVMETHOD(device_attach, uhub_attach), + DEVMETHOD(device_detach, uhub_detach), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + + DEVMETHOD(bus_child_pnpinfo_str, uhub_child_pnpinfo_str), + DEVMETHOD(bus_child_location_str, uhub_child_location_str), + /* XXX driver_added needs special care */ + DEVMETHOD(bus_driver_added, bus_generic_driver_added), + { 0, 0 } +}; + +static driver_t uhub_driver = { + "uhub", + uhub_methods, + sizeof(struct uhub_softc) +}; + +static devclass_t uhub_devclass; + +/* Create the driver instance for the hub connected to usb case. */ +devclass_t uhubroot_devclass; + +static device_method_t uhubroot_methods[] = { + DEVMETHOD(device_probe, uhub_match), + DEVMETHOD(device_attach, uhub_attach), + DEVMETHOD(device_detach, uhub_detach), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + + DEVMETHOD(bus_child_location_str, uhub_child_location_str), + DEVMETHOD(bus_child_pnpinfo_str, uhub_child_pnpinfo_str), + /* XXX driver_added needs special care */ + DEVMETHOD(bus_driver_added, bus_generic_driver_added), + + {0,0} +}; + +static driver_t uhubroot_driver = { + "uhub", + uhubroot_methods, + sizeof(struct uhub_softc) +}; + +static int +uhub_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + usb_device_descriptor_t *dd = usbd_get_device_descriptor(uaa->device); + + DPRINTFN(5,("uhub_match, dd=%p\n", dd)); + /* + * The subclass for hubs seems to be 0 for some and 1 for others, + * so we just ignore the subclass. + */ + if (uaa->iface == NULL && dd->bDeviceClass == UDCLASS_HUB) + return (UMATCH_DEVCLASS_DEVSUBCLASS); + return (UMATCH_NONE); +} + +int +uhub_attach(device_t self) +{ + struct uhub_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + usbd_device_handle dev = uaa->device; + usbd_status err; + struct usbd_hub *hub = NULL; + usb_device_request_t req; + usb_hub_descriptor_t hubdesc; + int p, port, nports, nremov, pwrdly; + usbd_interface_handle iface; + usb_endpoint_descriptor_t *ed; + struct usbd_tt *tts = NULL; + + DPRINTFN(1,("uhub_attach\n")); + sc->sc_hub = dev; + sc->sc_dev = self; + + if (dev->depth > 0 && UHUB_IS_HIGH_SPEED(sc)) { + device_printf(sc->sc_dev, "%s transaction translator%s\n", + UHUB_IS_SINGLE_TT(sc) ? "single" : "multiple", + UHUB_IS_SINGLE_TT(sc) ? "" : "s"); + } + err = usbd_set_config_index(dev, 0, 1); + if (err) { + DEVPRINTF((sc->sc_dev, "configuration failed, error=%s\n", + usbd_errstr(err))); + return (ENXIO); + } + + if (dev->depth > USB_HUB_MAX_DEPTH) { + device_printf(sc->sc_dev, "hub depth (%d) exceeded, hub ignored\n", + USB_HUB_MAX_DEPTH); + return (ENXIO); + } + + /* Get hub descriptor. */ + req.bmRequestType = UT_READ_CLASS_DEVICE; + req.bRequest = UR_GET_DESCRIPTOR; + USETW2(req.wValue, (dev->address > 1 ? UDESC_HUB : 0), 0); + USETW(req.wIndex, 0); + USETW(req.wLength, USB_HUB_DESCRIPTOR_SIZE); + DPRINTFN(1,("usb_init_hub: getting hub descriptor\n")); + err = usbd_do_request(dev, &req, &hubdesc); + nports = hubdesc.bNbrPorts; + if (!err && nports > 7) { + USETW(req.wLength, USB_HUB_DESCRIPTOR_SIZE + (nports+1) / 8); + err = usbd_do_request(dev, &req, &hubdesc); + } + if (err) { + DEVPRINTF((sc->sc_dev, "getting hub descriptor failed: %s\n", + usbd_errstr(err))); + return (ENXIO); + } + + for (nremov = 0, port = 1; port <= nports; port++) + if (!UHD_NOT_REMOV(&hubdesc, port)) + nremov++; + device_printf(sc->sc_dev, "%d port%s with %d removable, %s powered\n", + nports, nports != 1 ? "s" : "", nremov, + dev->self_powered ? "self" : "bus"); + + if (nports == 0) { + device_printf(sc->sc_dev, "no ports, hub ignored\n"); + goto bad; + } + + hub = malloc(sizeof(*hub) + (nports-1) * sizeof(struct usbd_port), + M_USBDEV, M_NOWAIT); + if (hub == NULL) { + return (ENXIO); + } + dev->hub = hub; + dev->hub->hubsoftc = sc; + hub->explore = uhub_explore; + hub->hubdesc = hubdesc; + + DPRINTFN(1,("usbhub_init_hub: selfpowered=%d, parent=%p, " + "parent->selfpowered=%d\n", + dev->self_powered, dev->powersrc->parent, + dev->powersrc->parent ? + dev->powersrc->parent->self_powered : 0)); + + if (!dev->self_powered && dev->powersrc->parent != NULL && + !dev->powersrc->parent->self_powered) { + device_printf(sc->sc_dev, "bus powered hub connected to bus " + "powered hub, ignored\n"); + goto bad; + } + + /* Set up interrupt pipe. */ + err = usbd_device2interface_handle(dev, 0, &iface); + if (err) { + device_printf(sc->sc_dev, "no interface handle\n"); + goto bad; + } + ed = usbd_interface2endpoint_descriptor(iface, 0); + if (ed == NULL) { + device_printf(sc->sc_dev, "no endpoint descriptor\n"); + goto bad; + } + if ((ed->bmAttributes & UE_XFERTYPE) != UE_INTERRUPT) { + device_printf(sc->sc_dev, "bad interrupt endpoint\n"); + goto bad; + } + + err = usbd_open_pipe_intr(iface, ed->bEndpointAddress, + USBD_SHORT_XFER_OK, &sc->sc_ipipe, sc, sc->sc_status, + (nports + 1 + 7) / 8, uhub_intr, UHUB_INTR_INTERVAL); + if (err) { + device_printf(sc->sc_dev, "cannot open interrupt pipe\n"); + goto bad; + } + + /* Wait with power off for a while. */ + usbd_delay_ms(dev, USB_POWER_DOWN_TIME); + + usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, dev, sc->sc_dev); + + /* + * 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 + */ + + if (UHUB_IS_HIGH_SPEED(sc)) { + tts = malloc((UHUB_IS_SINGLE_TT(sc) ? 1 : nports) * + sizeof (struct usbd_tt), M_USBDEV, M_NOWAIT); + if (!tts) + goto bad; + } + + /* Set up data structures */ + for (p = 0; p < nports; p++) { + struct usbd_port *up = &hub->ports[p]; + up->device = NULL; + up->parent = dev; + up->portno = p+1; + if (dev->self_powered) + /* Self powered hub, give ports maximum current. */ + up->power = USB_MAX_POWER; + else + up->power = USB_MIN_POWER; + up->restartcnt = 0; + if (UHUB_IS_HIGH_SPEED(sc)) { + up->tt = &tts[UHUB_IS_SINGLE_TT(sc) ? 0 : p]; + up->tt->hub = hub; + } else { + up->tt = NULL; + } + } + + /* XXX should check for none, individual, or ganged power? */ + + pwrdly = dev->hub->hubdesc.bPwrOn2PwrGood * UHD_PWRON_FACTOR + + USB_EXTRA_POWER_UP_TIME; + for (port = 1; port <= nports; port++) { + /* Turn the power on. */ + err = usbd_set_port_feature(dev, port, UHF_PORT_POWER); + if (err) + device_printf(sc->sc_dev, + "port %d power on failed, %s\n", port, + usbd_errstr(err)); + DPRINTF(("usb_init_port: turn on port %d power\n", port)); + /* Wait for stable power. */ + usbd_delay_ms(dev, pwrdly); + } + + /* The usual exploration will finish the setup. */ + + sc->sc_running = 1; + return (0); + bad: + if (hub) + free(hub, M_USBDEV); + dev->hub = NULL; + return (ENXIO); +} + +usbd_status +uhub_explore(usbd_device_handle dev) +{ + usb_hub_descriptor_t *hd = &dev->hub->hubdesc; + struct uhub_softc *sc = dev->hub->hubsoftc; + struct usbd_port *up; + usbd_status err; + int speed; + int port; + int change, status; + + DPRINTFN(10, ("uhub_explore dev=%p addr=%d\n", dev, dev->address)); + + if (!sc->sc_running) + return (USBD_NOT_STARTED); + + /* Ignore hubs that are too deep. */ + if (dev->depth > USB_HUB_MAX_DEPTH) + return (USBD_TOO_DEEP); + + for(port = 1; port <= hd->bNbrPorts; port++) { + up = &dev->hub->ports[port-1]; + err = usbd_get_port_status(dev, port, &up->status); + if (err) { + DPRINTF(("uhub_explore: get port status failed, " + "error=%s\n", usbd_errstr(err))); + continue; + } + status = UGETW(up->status.wPortStatus); + change = UGETW(up->status.wPortChange); + DEVPRINTFN(3,(sc->sc_dev, + "uhub_explore: port %d status 0x%04x 0x%04x\n", port, + status, change)); + if (change & UPS_C_PORT_ENABLED) { + DPRINTF(("uhub_explore: C_PORT_ENABLED 0x%x\n", change)); + usbd_clear_port_feature(dev, port, UHF_C_PORT_ENABLE); + if (change & UPS_C_CONNECT_STATUS) { + /* Ignore the port error if the device + vanished. */ + } else if (status & UPS_PORT_ENABLED) { + device_printf(sc->sc_dev, + "illegal enable change, port %d\n", port); + } else { + /* Port error condition. */ + if (up->restartcnt) /* no message first time */ + device_printf(sc->sc_dev, + "port error, restarting port %d\n", + port); + + if (up->restartcnt++ < USBD_RESTART_MAX) + goto disco; + else + device_printf(sc->sc_dev, + "port error, giving up port %d\n", + port); + } + } + if (!(change & UPS_C_CONNECT_STATUS)) { + DPRINTFN(3,("uhub_explore: port=%d !C_CONNECT_" + "STATUS\n", port)); + /* No status change, just do recursive explore. */ + if (up->device != NULL && up->device->hub != NULL) + up->device->hub->explore(up->device); +#if 0 && defined(DIAGNOSTIC) + if (up->device == NULL && + (status & UPS_CURRENT_CONNECT_STATUS)) + deivce_printf(sc->sc_dev, + "connected, no device\n"); +#endif + continue; + } + + /* We have a connect status change, handle it. */ + + DPRINTF(("uhub_explore: status change hub=%d port=%d\n", + dev->address, port)); + usbd_clear_port_feature(dev, port, UHF_C_PORT_CONNECTION); + /*usbd_clear_port_feature(dev, port, UHF_C_PORT_ENABLE);*/ + /* + * If there is already a device on the port the change status + * must mean that is has disconnected. Looking at the + * current connect status is not enough to figure this out + * since a new unit may have been connected before we handle + * the disconnect. + */ + disco: + if (up->device != NULL) { + /* Disconnected */ + DPRINTF(("uhub_explore: device addr=%d disappeared " + "on port %d\n", up->device->address, port)); + usb_disconnect_port(up, sc->sc_dev); + usbd_clear_port_feature(dev, port, + UHF_C_PORT_CONNECTION); + } + if (!(status & UPS_CURRENT_CONNECT_STATUS)) { + /* Nothing connected, just ignore it. */ + DPRINTFN(3,("uhub_explore: port=%d !CURRENT_CONNECT" + "_STATUS\n", port)); + continue; + } + + /* Connected */ + + if (!(status & UPS_PORT_POWER)) + device_printf(sc->sc_dev, + "strange, connected port %d has no power\n", port); + + /* Wait for maximum device power up time. */ + usbd_delay_ms(dev, USB_PORT_POWERUP_DELAY); + + /* Reset port, which implies enabling it. */ + if (usbd_reset_port(dev, port, &up->status)) { + device_printf(sc->sc_dev, "port %d reset failed\n", + port); + continue; + } + /* Get port status again, it might have changed during reset */ + err = usbd_get_port_status(dev, port, &up->status); + if (err) { + DPRINTF(("uhub_explore: get port status failed, " + "error=%s\n", usbd_errstr(err))); + continue; + } + status = UGETW(up->status.wPortStatus); + change = UGETW(up->status.wPortChange); + if (!(status & UPS_CURRENT_CONNECT_STATUS)) { + /* Nothing connected, just ignore it. */ +#ifdef DIAGNOSTIC + device_printf(sc->sc_dev, + "port %d, device disappeared after reset\n", port); +#endif + continue; + } + +#if 0 + if (UHUB_IS_HIGH_SPEED(sc) && !(status & UPS_HIGH_SPEED)) { + device_printf(sc->sc_dev, + "port %d, transaction translation not implemented," + " low/full speed device ignored\n", port); + continue; + } +#endif + + /* Figure out device speed */ + if (status & UPS_HIGH_SPEED) + speed = USB_SPEED_HIGH; + else if (status & UPS_LOW_SPEED) + speed = USB_SPEED_LOW; + else + speed = USB_SPEED_FULL; + /* Get device info and set its address. */ + err = usbd_new_device(sc->sc_dev, dev->bus, + dev->depth + 1, speed, port, up); + /* XXX retry a few times? */ + if (err) { + DPRINTFN(-1,("uhub_explore: usb_new_device failed, " + "error=%s\n", usbd_errstr(err))); + /* Avoid addressing problems by disabling. */ + /* usbd_reset_port(dev, port, &up->status); */ + + /* + * The unit refused to accept a new address, or had + * some other serious problem. Since we cannot leave + * at 0 we have to disable the port instead. + */ + device_printf(sc->sc_dev, + "device problem (%s), disabling port %d\n", + usbd_errstr(err), port); + usbd_clear_port_feature(dev, port, UHF_PORT_ENABLE); + } else { + /* The port set up succeeded, reset error count. */ + up->restartcnt = 0; + + if (up->device->hub) + up->device->hub->explore(up->device); + } + } + return (USBD_NORMAL_COMPLETION); +} + +/* + * Called from process context when the hub is gone. + * Detach all devices on active ports. + */ +static int +uhub_detach(device_t self) +{ + struct uhub_softc *sc = device_get_softc(self); + struct usbd_hub *hub = sc->sc_hub->hub; + struct usbd_port *rup; + int port, nports; + + DPRINTF(("uhub_detach: sc=%port\n", sc)); + if (hub == NULL) /* Must be partially working */ + return (0); + + usbd_abort_pipe(sc->sc_ipipe); + usbd_close_pipe(sc->sc_ipipe); + + nports = hub->hubdesc.bNbrPorts; + for(port = 0; port < nports; port++) { + rup = &hub->ports[port]; + if (rup->device) + usb_disconnect_port(rup, self); + } + + usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_hub, sc->sc_dev); + + if (hub->ports[0].tt) + free(hub->ports[0].tt, M_USBDEV); + free(hub, M_USBDEV); + sc->sc_hub->hub = NULL; + + return (0); +} + +int +uhub_child_location_str(device_t cbdev, device_t child, char *buf, + size_t buflen) +{ + struct uhub_softc *sc = device_get_softc(cbdev); + usbd_device_handle devhub = sc->sc_hub; + usbd_device_handle dev; + int nports; + int port; + int i; + + mtx_lock(&Giant); + nports = devhub->hub->hubdesc.bNbrPorts; + for (port = 0; port < nports; port++) { + dev = devhub->hub->ports[port].device; + if (dev && dev->subdevs) { + for (i = 0; dev->subdevs[i]; i++) { + if (dev->subdevs[i] == child) { + if (dev->ifacenums == NULL) { + snprintf(buf, buflen, + "port=%i", port); + } else { + snprintf(buf, buflen, + "port=%i interface=%i", + port, dev->ifacenums[i]); + } + goto found_dev; + } + } + } + } + DPRINTFN(0,("uhub_child_location_str: device not on hub\n")); + buf[0] = '\0'; +found_dev: + mtx_unlock(&Giant); + return (0); +} + +int +uhub_child_pnpinfo_str(device_t cbdev, device_t child, char *buf, + size_t buflen) +{ + struct uhub_softc *sc = device_get_softc(cbdev); + usbd_device_handle devhub = sc->sc_hub; + usbd_device_handle dev; + struct usbd_interface *iface; + char serial[128]; + int nports; + int port; + int i; + + mtx_lock(&Giant); + nports = devhub->hub->hubdesc.bNbrPorts; + for (port = 0; port < nports; port++) { + dev = devhub->hub->ports[port].device; + if (dev && dev->subdevs) { + for (i = 0; dev->subdevs[i]; i++) { + if (dev->subdevs[i] == child) { + goto found_dev; + } + } + } + } + DPRINTFN(0,("uhub_child_pnpinfo_str: device not on hub\n")); + buf[0] = '\0'; + mtx_unlock(&Giant); + return (0); + +found_dev: + /* XXX can sleep */ + (void)usbd_get_string(dev, dev->ddesc.iSerialNumber, serial, + sizeof(serial)); + if (dev->ifacenums == NULL) { + snprintf(buf, buflen, "vendor=0x%04x product=0x%04x " + "devclass=0x%02x devsubclass=0x%02x " + "release=0x%04x sernum=\"%s\"", + UGETW(dev->ddesc.idVendor), UGETW(dev->ddesc.idProduct), + dev->ddesc.bDeviceClass, dev->ddesc.bDeviceSubClass, + UGETW(dev->ddesc.bcdDevice), serial); + } else { + iface = &dev->ifaces[dev->ifacenums[i]]; + snprintf(buf, buflen, "vendor=0x%04x product=0x%04x " + "devclass=0x%02x devsubclass=0x%02x " + "release=0x%04x sernum=\"%s\" " + "intclass=0x%02x intsubclass=0x%02x", + UGETW(dev->ddesc.idVendor), UGETW(dev->ddesc.idProduct), + dev->ddesc.bDeviceClass, dev->ddesc.bDeviceSubClass, + UGETW(dev->ddesc.bcdDevice), serial, + iface->idesc->bInterfaceClass, + iface->idesc->bInterfaceSubClass); + } + mtx_unlock(&Giant); + return (0); +} + +/* + * Hub interrupt. + * This an indication that some port has changed status. + * Notify the bus event handler thread that we need + * to be explored again. + */ +void +uhub_intr(usbd_xfer_handle xfer, usbd_private_handle addr, usbd_status status) +{ + struct uhub_softc *sc = addr; + + DPRINTFN(5,("uhub_intr: sc=%p\n", sc)); + if (status == USBD_STALLED) + usbd_clear_endpoint_stall_async(sc->sc_ipipe); + else if (status == USBD_NORMAL_COMPLETION) + usb_needs_explore(sc->sc_hub); +} + +MODULE_DEPEND(uhub, usb, 1, 1, 1); +DRIVER_MODULE(uhub, usb, uhubroot_driver, uhubroot_devclass, 0, 0); +DRIVER_MODULE(uhub, uhub, uhub_driver, uhub_devclass, usbd_driver_load, 0); diff --git a/sys/legacy/dev/usb/uipaq.c b/sys/legacy/dev/usb/uipaq.c new file mode 100644 index 0000000..a231a67 --- /dev/null +++ b/sys/legacy/dev/usb/uipaq.c @@ -0,0 +1,822 @@ +/* $NetBSD: uipaq.c,v 1.4 2006/11/16 01:33:27 christos Exp $ */ +/* $OpenBSD: uipaq.c,v 1.1 2005/06/17 23:50:33 deraadt Exp $ */ + +/* + * Copyright (c) 2000-2005 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. + */ + +/* + * iPAQ driver + * + * 19 July 2003: Incorporated changes suggested by Sam Lawrance from + * the uppc module + * + * + * Contact isis@cs.umd.edu if you have any questions/comments about this driver + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/conf.h> +#include <sys/tty.h> +#include <sys/module.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbhid.h> + +#include <dev/usb/usbcdc.h> /*UCDC_* stuff */ + +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include "usbdevs.h" + +#include <dev/usb/ucomvar.h> + +#ifdef UIPAQ_DEBUG +#define DPRINTF(x) if (uipaqdebug) printf x +#define DPRINTFN(n,x) if (uipaqdebug>(n)) printf x +int uipaqdebug = 0; +#else +#define DPRINTF(x) +#define DPRINTFN(n,x) +#endif + +#define UIPAQ_CONFIG_NO 1 +#define UIPAQ_IFACE_INDEX 0 + +#define UIPAQIBUFSIZE 1024 +#define UIPAQOBUFSIZE 1024 + +struct uipaq_softc { + struct ucom_softc sc_ucom; + u_int16_t sc_lcr; /* state for DTR/RTS */ + u_int16_t sc_flags; + +}; + +/* Callback routines */ +static void uipaq_set(void *, int, int, int); + +/* Support routines. */ +/* based on uppc module by Sam Lawrance */ +static void uipaq_dtr(struct uipaq_softc *sc, int onoff); +static void uipaq_rts(struct uipaq_softc *sc, int onoff); +static void uipaq_break(struct uipaq_softc* sc, int onoff); + +int uipaq_detach(device_t self); + +struct ucom_callback uipaq_callback = { + .ucom_set = uipaq_set +}; + +struct uipaq_type { + struct usb_devno uv_dev; + u_int16_t uv_flags; +}; + +/* + * Much of this list is generated from lists of other drivers that support + * the same hardware. Numeric values are used where no usbdevs entries + * exist. + */ +static const struct uipaq_type uipaq_devs[] = { + {{ 0x0104, 0x00be }, 0}, /* Socket USB Sync */ + {{ 0x04ad, 0x0301 }, 0}, /* USB Sync 0301 */ + {{ 0x04ad, 0x0302 }, 0}, /* USB Sync 0302 */ + {{ 0x04ad, 0x0303 }, 0}, /* USB Sync 0303 */ + {{ 0x04ad, 0x0306 }, 0}, /* GPS Pocket PC USB Sync */ + {{ 0x0536, 0x01a0 }, 0}, /* HHP PDT */ + {{ 0x067e, 0x1001 }, 0}, /* Intermec Mobile Computer */ + {{ 0x094b, 0x0001 }, 0}, /* Linkup Systems USB Sync */ + {{ 0x0960, 0x0065 }, 0}, /* BCOM USB Sync 0065 */ + {{ 0x0960, 0x0066 }, 0}, /* BCOM USB Sync 0066 */ + {{ 0x0960, 0x0067 }, 0}, /* BCOM USB Sync 0067 */ + {{ 0x0961, 0x0010 }, 0}, /* Portatec USB Sync */ + {{ 0x099e, 0x0052 }, 0}, /* Trimble GeoExplorer */ + {{ 0x099e, 0x4000 }, 0}, /* TDS Data Collector */ + {{ 0x0c44, 0x03a2 }, 0}, /* Motorola iDEN Smartphone */ + {{ 0x0c8e, 0x6000 }, 0}, /* Cesscom Luxian Series */ + {{ 0x0cad, 0x9001 }, 0}, /* Motorola PowerPad Pocket PCDevice */ + {{ 0x0f4e, 0x0200 }, 0}, /* Freedom Scientific USB Sync */ + {{ 0x0f98, 0x0201 }, 0}, /* Cyberbank USB Sync */ + {{ 0x0fb8, 0x3001 }, 0}, /* Wistron USB Sync */ + {{ 0x0fb8, 0x3002 }, 0}, /* Wistron USB Sync */ + {{ 0x0fb8, 0x3003 }, 0}, /* Wistron USB Sync */ + {{ 0x0fb8, 0x4001 }, 0}, /* Wistron USB Sync */ + {{ 0x1066, 0x00ce }, 0}, /* E-TEN USB Sync */ + {{ 0x1066, 0x0300 }, 0}, /* E-TEN P3XX Pocket PC */ + {{ 0x1066, 0x0500 }, 0}, /* E-TEN P5XX Pocket PC */ + {{ 0x1066, 0x0600 }, 0}, /* E-TEN P6XX Pocket PC */ + {{ 0x1066, 0x0700 }, 0}, /* E-TEN P7XX Pocket PC */ + {{ 0x1114, 0x0001 }, 0}, /* Psion Teklogix Sync 753x */ + {{ 0x1114, 0x0004 }, 0}, /* Psion Teklogix Sync netBookPro */ + {{ 0x1114, 0x0006 }, 0}, /* Psion Teklogix Sync 7525 */ + {{ 0x1182, 0x1388 }, 0}, /* VES USB Sync */ + {{ 0x11d9, 0x1002 }, 0}, /* Rugged Pocket PC 2003 */ + {{ 0x11d9, 0x1003 }, 0}, /* Rugged Pocket PC 2003 */ + {{ 0x1231, 0xce01 }, 0}, /* USB Sync 03 */ + {{ 0x1231, 0xce02 }, 0}, /* USB Sync 03 */ + {{ 0x3340, 0x011c }, 0}, /* Mio DigiWalker PPC StrongARM */ + {{ 0x3340, 0x0326 }, 0}, /* Mio DigiWalker 338 */ + {{ 0x3340, 0x0426 }, 0}, /* Mio DigiWalker 338 */ + {{ 0x3340, 0x043a }, 0}, /* Mio DigiWalker USB Sync */ + {{ 0x3340, 0x051c }, 0}, /* MiTAC USB Sync 528 */ + {{ 0x3340, 0x053a }, 0}, /* Mio DigiWalker SmartPhone USB Sync */ + {{ 0x3340, 0x071c }, 0}, /* MiTAC USB Sync */ + {{ 0x3340, 0x0b1c }, 0}, /* Generic PPC StrongARM */ + {{ 0x3340, 0x0e3a }, 0}, /* Generic PPC USB Sync */ + {{ 0x3340, 0x0f1c }, 0}, /* Itautec USB Sync */ + {{ 0x3340, 0x0f3a }, 0}, /* Generic SmartPhone USB Sync */ + {{ 0x3340, 0x1326 }, 0}, /* Itautec USB Sync */ + {{ 0x3340, 0x191c }, 0}, /* YAKUMO USB Sync */ + {{ 0x3340, 0x2326 }, 0}, /* Vobis USB Sync */ + {{ 0x3340, 0x3326 }, 0}, /* MEDION Winodws Moble USB Sync */ + {{ 0x3708, 0x20ce }, 0}, /* Legend USB Sync */ + {{ 0x3708, 0x21ce }, 0}, /* Lenovo USB Sync */ + {{ 0x4113, 0x0210 }, 0}, /* Mobile Media Technology USB Sync */ + {{ 0x4113, 0x0211 }, 0}, /* Mobile Media Technology USB Sync */ + {{ 0x4113, 0x0400 }, 0}, /* Mobile Media Technology USB Sync */ + {{ 0x4113, 0x0410 }, 0}, /* Mobile Media Technology USB Sync */ + {{ 0x4505, 0x0010 }, 0}, /* Smartphone */ + {{ 0x5e04, 0xce00 }, 0}, /* SAGEM Wireless Assistant */ + {{ USB_VENDOR_ACER, 0x1631 }, 0}, /* c10 Series */ + {{ USB_VENDOR_ACER, 0x1632 }, 0}, /* c20 Series */ + {{ USB_VENDOR_ACER, 0x16e1 }, 0}, /* Acer n10 Handheld USB Sync */ + {{ USB_VENDOR_ACER, 0x16e2 }, 0}, /* Acer n20 Handheld USB Sync */ + {{ USB_VENDOR_ACER, 0x16e3 }, 0}, /* Acer n30 Handheld USB Sync */ + {{ USB_VENDOR_ASUS, 0x4200 }, 0}, /* ASUS USB Sync */ + {{ USB_VENDOR_ASUS, 0x4201 }, 0}, /* ASUS USB Sync */ + {{ USB_VENDOR_ASUS, 0x4202 }, 0}, /* ASUS USB Sync */ + {{ USB_VENDOR_ASUS, 0x9200 }, 0}, /* ASUS USB Sync */ + {{ USB_VENDOR_ASUS, 0x9202 }, 0}, /* ASUS USB Sync */ + {{ USB_VENDOR_ASUS, USB_PRODUCT_ASUS_P535 }, 0}, + {{ USB_VENDOR_CASIO, 0x2001 }, 0}, /* CASIO USB Sync 2001 */ + {{ USB_VENDOR_CASIO, 0x2003 }, 0}, /* CASIO USB Sync 2003 */ + {{ USB_VENDOR_CASIO, USB_PRODUCT_CASIO_BE300 } , 0}, + {{ USB_VENDOR_COMPAL, 0x0531 }, 0}, /* MyGuide 7000 XL USB Sync */ + {{ USB_VENDOR_COMPAQ, 0x0032 }, 0}, /* Compaq iPAQ USB Sync */ + {{ USB_VENDOR_COMPAQ, USB_PRODUCT_COMPAQ_IPAQPOCKETPC } , 0}, + {{ USB_VENDOR_DELL, 0x4001 }, 0}, /* Dell Axim USB Sync */ + {{ USB_VENDOR_DELL, 0x4002 }, 0}, /* Dell Axim USB Sync */ + {{ USB_VENDOR_DELL, 0x4003 }, 0}, /* Dell Axim USB Sync */ + {{ USB_VENDOR_DELL, 0x4004 }, 0}, /* Dell Axim USB Sync */ + {{ USB_VENDOR_DELL, 0x4005 }, 0}, /* Dell Axim USB Sync */ + {{ USB_VENDOR_DELL, 0x4006 }, 0}, /* Dell Axim USB Sync */ + {{ USB_VENDOR_DELL, 0x4007 }, 0}, /* Dell Axim USB Sync */ + {{ USB_VENDOR_DELL, 0x4008 }, 0}, /* Dell Axim USB Sync */ + {{ USB_VENDOR_DELL, 0x4009 }, 0}, /* Dell Axim USB Sync */ + {{ USB_VENDOR_FSC, 0x1001 }, 0}, /* Fujitsu Siemens Computers USB Sync */ + {{ USB_VENDOR_FUJITSU, 0x1058 }, 0}, /* FUJITSU USB Sync */ + {{ USB_VENDOR_FUJITSU, 0x1079 }, 0}, /* FUJITSU USB Sync */ + {{ USB_VENDOR_GIGASET, 0x0601 }, 0}, /* Askey USB Sync */ + {{ USB_VENDOR_HITACHI, 0x0014 }, 0}, /* Hitachi USB Sync */ + {{ USB_VENDOR_HP, 0x1216 }, 0}, /* HP USB Sync 1612 */ + {{ USB_VENDOR_HP, 0x2016 }, 0}, /* HP USB Sync 1620 */ + {{ USB_VENDOR_HP, 0x2116 }, 0}, /* HP USB Sync 1621 */ + {{ USB_VENDOR_HP, 0x2216 }, 0}, /* HP USB Sync 1622 */ + {{ USB_VENDOR_HP, 0x3016 }, 0}, /* HP USB Sync 1630 */ + {{ USB_VENDOR_HP, 0x3116 }, 0}, /* HP USB Sync 1631 */ + {{ USB_VENDOR_HP, 0x3216 }, 0}, /* HP USB Sync 1632 */ + {{ USB_VENDOR_HP, 0x4016 }, 0}, /* HP USB Sync 1640 */ + {{ USB_VENDOR_HP, 0x4116 }, 0}, /* HP USB Sync 1641 */ + {{ USB_VENDOR_HP, 0x4216 }, 0}, /* HP USB Sync 1642 */ + {{ USB_VENDOR_HP, 0x5016 }, 0}, /* HP USB Sync 1650 */ + {{ USB_VENDOR_HP, 0x5116 }, 0}, /* HP USB Sync 1651 */ + {{ USB_VENDOR_HP, 0x5216 }, 0}, /* HP USB Sync 1652 */ + {{ USB_VENDOR_HP, USB_PRODUCT_HP_2215 }, 0 }, + {{ USB_VENDOR_HP, USB_PRODUCT_HP_568J }, 0}, + {{ USB_VENDOR_HTC, 0x00cf }, 0}, /* HTC USB Modem */ + {{ USB_VENDOR_HTC, 0x0a01 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a02 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a03 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a04 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a05 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a06 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a07 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a08 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a09 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a0a }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a0b }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a0c }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a0d }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a0e }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a0f }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a10 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a11 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a12 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a13 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a14 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a15 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a16 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a17 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a18 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a19 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a1a }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a1b }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a1c }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a1d }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a1e }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a1f }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a20 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a21 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a22 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a23 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a24 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a25 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a26 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a27 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a28 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a29 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a2a }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a2b }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a2c }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a2d }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a2e }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a2f }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a30 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a31 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a32 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a33 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a34 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a35 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a36 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a37 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a38 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a39 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a3a }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a3b }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a3c }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a3d }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a3e }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a3f }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a40 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a41 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a42 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a43 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a44 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a45 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a46 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a47 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a48 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a49 }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a4a }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a4b }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a4c }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a4d }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a4e }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a4f }, 0}, /* PocketPC USB Sync */ + {{ USB_VENDOR_HTC, 0x0a50 }, 0}, /* HTC SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a52 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a53 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a54 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a55 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a56 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a57 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a58 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a59 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a5a }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a5b }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a5c }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a5d }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a5e }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a5f }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a60 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a61 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a62 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a63 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a64 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a65 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a66 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a67 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a68 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a69 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a6a }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a6b }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a6c }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a6d }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a6e }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a6f }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a70 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a71 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a72 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a73 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a74 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a75 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a76 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a77 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a78 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a79 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a7a }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a7b }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a7c }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a7d }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a7e }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a7f }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a80 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a81 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a82 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a83 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a84 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a85 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a86 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a87 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a88 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a89 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a8a }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a8b }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a8c }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a8d }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a8e }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a8f }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a90 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a91 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a92 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a93 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a94 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a95 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a96 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a97 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a98 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a99 }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a9a }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a9b }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a9c }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a9d }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a9e }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0a9f }, 0}, /* SmartPhone USB Sync */ + {{ USB_VENDOR_HTC, 0x0bce }, 0}, /* "High Tech Computer Corp" */ + {{ USB_VENDOR_HTC, USB_PRODUCT_HTC_PPC6700MODEM }, 0}, + {{ USB_VENDOR_HTC, USB_PRODUCT_HTC_SMARTPHONE }, 0}, + {{ USB_VENDOR_HTC, USB_PRODUCT_HTC_WINMOBILE }, 0}, + {{ USB_VENDOR_JVC, 0x3011 }, 0}, /* JVC USB Sync */ + {{ USB_VENDOR_JVC, 0x3012 }, 0}, /* JVC USB Sync */ + {{ USB_VENDOR_LG, 0x9c01 }, 0}, /* LGE USB Sync */ + {{ USB_VENDOR_MICROSOFT, 0x00ce }, 0}, /* Microsoft USB Sync */ + {{ USB_VENDOR_MICROSOFT, 0x0400 }, 0}, /* Windows Pocket PC 2002 */ + {{ USB_VENDOR_MICROSOFT, 0x0401 }, 0}, /* Windows Pocket PC 2002 */ + {{ USB_VENDOR_MICROSOFT, 0x0402 }, 0}, /* Windows Pocket PC 2002 */ + {{ USB_VENDOR_MICROSOFT, 0x0403 }, 0}, /* Windows Pocket PC 2002 */ + {{ USB_VENDOR_MICROSOFT, 0x0404 }, 0}, /* Windows Pocket PC 2002 */ + {{ USB_VENDOR_MICROSOFT, 0x0405 }, 0}, /* Windows Pocket PC 2002 */ + {{ USB_VENDOR_MICROSOFT, 0x0406 }, 0}, /* Windows Pocket PC 2002 */ + {{ USB_VENDOR_MICROSOFT, 0x0407 }, 0}, /* Windows Pocket PC 2002 */ + {{ USB_VENDOR_MICROSOFT, 0x0408 }, 0}, /* Windows Pocket PC 2002 */ + {{ USB_VENDOR_MICROSOFT, 0x0409 }, 0}, /* Windows Pocket PC 2002 */ + {{ USB_VENDOR_MICROSOFT, 0x040a }, 0}, /* Windows Pocket PC 2002 */ + {{ USB_VENDOR_MICROSOFT, 0x040b }, 0}, /* Windows Pocket PC 2002 */ + {{ USB_VENDOR_MICROSOFT, 0x040c }, 0}, /* Windows Pocket PC 2002 */ + {{ USB_VENDOR_MICROSOFT, 0x040d }, 0}, /* Windows Pocket PC 2002 */ + {{ USB_VENDOR_MICROSOFT, 0x040e }, 0}, /* Windows Pocket PC 2002 */ + {{ USB_VENDOR_MICROSOFT, 0x040f }, 0}, /* Windows Pocket PC 2002 */ + {{ USB_VENDOR_MICROSOFT, 0x0410 }, 0}, /* Windows Pocket PC 2002 */ + {{ USB_VENDOR_MICROSOFT, 0x0411 }, 0}, /* Windows Pocket PC 2002 */ + {{ USB_VENDOR_MICROSOFT, 0x0412 }, 0}, /* Windows Pocket PC 2002 */ + {{ USB_VENDOR_MICROSOFT, 0x0413 }, 0}, /* Windows Pocket PC 2002 */ + {{ USB_VENDOR_MICROSOFT, 0x0414 }, 0}, /* Windows Pocket PC 2002 */ + {{ USB_VENDOR_MICROSOFT, 0x0415 }, 0}, /* Windows Pocket PC 2002 */ + {{ USB_VENDOR_MICROSOFT, 0x0416 }, 0}, /* Windows Pocket PC 2002 */ + {{ USB_VENDOR_MICROSOFT, 0x0417 }, 0}, /* Windows Pocket PC 2002 */ + {{ USB_VENDOR_MICROSOFT, 0x0432 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0433 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0434 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0435 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0436 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0437 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0438 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0439 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x043a }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x043b }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x043c }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x043d }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x043e }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x043f }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0440 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0441 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0442 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0443 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0444 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0445 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0446 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0447 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0448 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0449 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x044a }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x044b }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x044c }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x044d }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x044e }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x044f }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0450 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0451 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0452 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0453 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0454 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0455 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0456 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0457 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0458 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0459 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x045a }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x045b }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x045c }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x045d }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x045e }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x045f }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0460 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0461 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0462 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0463 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0464 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0465 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0466 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0467 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0468 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0469 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x046a }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x046b }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x046c }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x046d }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x046e }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x046f }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0470 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0471 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0472 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0473 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0474 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0475 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0476 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0477 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0478 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x0479 }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x047a }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x047b }, 0}, /* Windows Pocket PC 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x04c8 }, 0}, /* Windows Smartphone 2002 */ + {{ USB_VENDOR_MICROSOFT, 0x04c9 }, 0}, /* Windows Smartphone 2002 */ + {{ USB_VENDOR_MICROSOFT, 0x04ca }, 0}, /* Windows Smartphone 2002 */ + {{ USB_VENDOR_MICROSOFT, 0x04cb }, 0}, /* Windows Smartphone 2002 */ + {{ USB_VENDOR_MICROSOFT, 0x04cc }, 0}, /* Windows Smartphone 2002 */ + {{ USB_VENDOR_MICROSOFT, 0x04cd }, 0}, /* Windows Smartphone 2002 */ + {{ USB_VENDOR_MICROSOFT, 0x04ce }, 0}, /* Windows Smartphone 2002 */ + {{ USB_VENDOR_MICROSOFT, 0x04d7 }, 0}, /* Windows Smartphone 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x04d8 }, 0}, /* Windows Smartphone 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x04d9 }, 0}, /* Windows Smartphone 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x04da }, 0}, /* Windows Smartphone 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x04db }, 0}, /* Windows Smartphone 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x04dc }, 0}, /* Windows Smartphone 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x04dd }, 0}, /* Windows Smartphone 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x04de }, 0}, /* Windows Smartphone 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x04df }, 0}, /* Windows Smartphone 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x04e0 }, 0}, /* Windows Smartphone 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x04e1 }, 0}, /* Windows Smartphone 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x04e2 }, 0}, /* Windows Smartphone 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x04e3 }, 0}, /* Windows Smartphone 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x04e4 }, 0}, /* Windows Smartphone 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x04e5 }, 0}, /* Windows Smartphone 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x04e6 }, 0}, /* Windows Smartphone 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x04e7 }, 0}, /* Windows Smartphone 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x04e8 }, 0}, /* Windows Smartphone 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x04e9 }, 0}, /* Windows Smartphone 2003 */ + {{ USB_VENDOR_MICROSOFT, 0x04ea }, 0}, /* Windows Smartphone 2003 */ + {{ USB_VENDOR_MOTOROLA2, 0x4204 }, 0}, /* Motorola MPx200 Smartphone */ + {{ USB_VENDOR_MOTOROLA2, 0x4214 }, 0}, /* Motorola MPc GSM */ + {{ USB_VENDOR_MOTOROLA2, 0x4224 }, 0}, /* Motorola MPx220 Smartphone */ + {{ USB_VENDOR_MOTOROLA2, 0x4234 }, 0}, /* Motorola MPc CDMA */ + {{ USB_VENDOR_MOTOROLA2, 0x4244 }, 0}, /* Motorola MPx100 Smartphone */ + {{ USB_VENDOR_NEC, 0x00d5 }, 0}, /* NEC USB Sync */ + {{ USB_VENDOR_NEC, 0x00d6 }, 0}, /* NEC USB Sync */ + {{ USB_VENDOR_NEC, 0x00d7 }, 0}, /* NEC USB Sync */ + {{ USB_VENDOR_NEC, 0x8024 }, 0}, /* NEC USB Sync */ + {{ USB_VENDOR_NEC, 0x8025 }, 0}, /* NEC USB Sync */ + {{ USB_VENDOR_PANASONIC, 0x2500 }, 0}, /* Panasonic USB Sync */ + {{ USB_VENDOR_SAMSUNG, 0x5f00 }, 0}, /* Samsung NEXiO USB Sync */ + {{ USB_VENDOR_SAMSUNG, 0x5f01 }, 0}, /* Samsung NEXiO USB Sync */ + {{ USB_VENDOR_SAMSUNG, 0x5f02 }, 0}, /* Samsung NEXiO USB Sync */ + {{ USB_VENDOR_SAMSUNG, 0x5f03 }, 0}, /* Samsung NEXiO USB Sync */ + {{ USB_VENDOR_SAMSUNG, 0x5f04 }, 0}, /* Samsung NEXiO USB Sync */ + {{ USB_VENDOR_SAMSUNG, 0x6611 }, 0}, /* Samsung MITs USB Sync */ + {{ USB_VENDOR_SAMSUNG, 0x6613 }, 0}, /* Samsung MITs USB Sync */ + {{ USB_VENDOR_SAMSUNG, 0x6615 }, 0}, /* Samsung MITs USB Sync */ + {{ USB_VENDOR_SAMSUNG, 0x6617 }, 0}, /* Samsung MITs USB Sync */ + {{ USB_VENDOR_SAMSUNG, 0x6619 }, 0}, /* Samsung MITs USB Sync */ + {{ USB_VENDOR_SAMSUNG, 0x661b }, 0}, /* Samsung MITs USB Sync */ + {{ USB_VENDOR_SAMSUNG, 0x662e }, 0}, /* Samsung MITs USB Sync */ + {{ USB_VENDOR_SAMSUNG, 0x6630 }, 0}, /* Samsung MITs USB Sync */ + {{ USB_VENDOR_SAMSUNG, 0x6632 }, 0}, /* Samsung MITs USB Sync */ + {{ USB_VENDOR_SHARP, 0x9102 }, 0}, /* SHARP WS003SH USB Modem */ + {{ USB_VENDOR_SHARP, 0x9121 }, 0}, /* SHARP WS004SH USB Modem */ + {{ USB_VENDOR_SHARP, 0x9151 }, 0}, /* SHARP S01SH USB Modem */ + {{ USB_VENDOR_SHARP, USB_PRODUCT_SHARP_WZERO3ES }, 0}, + {{ USB_VENDOR_SYMBOL, 0x2000 }, 0}, /* Symbol USB Sync */ + {{ USB_VENDOR_SYMBOL, 0x2001 }, 0}, /* Symbol USB Sync 0x2001 */ + {{ USB_VENDOR_SYMBOL, 0x2002 }, 0}, /* Symbol USB Sync 0x2002 */ + {{ USB_VENDOR_SYMBOL, 0x2003 }, 0}, /* Symbol USB Sync 0x2003 */ + {{ USB_VENDOR_SYMBOL, 0x2004 }, 0}, /* Symbol USB Sync 0x2004 */ + {{ USB_VENDOR_SYMBOL, 0x2005 }, 0}, /* Symbol USB Sync 0x2005 */ + {{ USB_VENDOR_SYMBOL, 0x2006 }, 0}, /* Symbol USB Sync 0x2006 */ + {{ USB_VENDOR_SYMBOL, 0x2007 }, 0}, /* Symbol USB Sync 0x2007 */ + {{ USB_VENDOR_SYMBOL, 0x2008 }, 0}, /* Symbol USB Sync 0x2008 */ + {{ USB_VENDOR_SYMBOL, 0x2009 }, 0}, /* Symbol USB Sync 0x2009 */ + {{ USB_VENDOR_SYMBOL, 0x200a }, 0}, /* Symbol USB Sync 0x200a */ + {{ USB_VENDOR_TOSHIBA, 0x0700 }, 0}, /* TOSHIBA USB Sync 0700 */ + {{ USB_VENDOR_TOSHIBA, 0x0705 }, 0}, /* TOSHIBA Pocket PC e310 */ + {{ USB_VENDOR_TOSHIBA, 0x0707 }, 0}, /* TOSHIBA Pocket PC e330 Series */ + {{ USB_VENDOR_TOSHIBA, 0x0708 }, 0}, /* TOSHIBA Pocket PC e350Series */ + {{ USB_VENDOR_TOSHIBA, 0x0709 }, 0}, /* TOSHIBA Pocket PC e750 Series */ + {{ USB_VENDOR_TOSHIBA, 0x070a }, 0}, /* TOSHIBA Pocket PC e400 Series */ + {{ USB_VENDOR_TOSHIBA, 0x070b }, 0}, /* TOSHIBA Pocket PC e800 Series */ + {{ USB_VENDOR_TOSHIBA, USB_PRODUCT_TOSHIBA_POCKETPC_E740 }, 0}, /* TOSHIBA Pocket PC e740 */ + {{ USB_VENDOR_VIEWSONIC, 0x0ed9 }, 0}, /* ViewSonic Color Pocket PC V35 */ + {{ USB_VENDOR_VIEWSONIC, 0x1527 }, 0}, /* ViewSonic Color Pocket PC V36 */ + {{ USB_VENDOR_VIEWSONIC, 0x1529 }, 0}, /* ViewSonic Color Pocket PC V37 */ + {{ USB_VENDOR_VIEWSONIC, 0x152b }, 0}, /* ViewSonic Color Pocket PC V38 */ + {{ USB_VENDOR_VIEWSONIC, 0x152e }, 0}, /* ViewSonic Pocket PC */ + {{ USB_VENDOR_VIEWSONIC, 0x1921 }, 0}, /* ViewSonic Communicator Pocket PC */ + {{ USB_VENDOR_VIEWSONIC, 0x1922 }, 0}, /* ViewSonic Smartphone */ + {{ USB_VENDOR_VIEWSONIC, 0x1923 }, 0}, /* ViewSonic Pocket PC V30 */ +}; + +#define uipaq_lookup(v, p) ((const struct uipaq_type *)usb_lookup(uipaq_devs, v, p)) + +static int +uipaq_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + + if (uaa->iface != NULL) + return (UMATCH_NONE); + + DPRINTFN(20,("uipaq: vendor=0x%x, product=0x%x\n", + uaa->vendor, uaa->product)); + + return (uipaq_lookup(uaa->vendor, uaa->product) != NULL ? + UMATCH_VENDOR_PRODUCT : UMATCH_NONE); +} + +static int +uipaq_attach(device_t self) +{ + usb_device_request_t req; + struct uipaq_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + usbd_device_handle dev = uaa->device; + usbd_interface_handle iface; + usb_interface_descriptor_t *id; + usb_endpoint_descriptor_t *ed; + int i; + usbd_status err; + struct ucom_softc *ucom = &sc->sc_ucom; + + ucom->sc_dev = self; + ucom->sc_udev = dev; + + DPRINTFN(10,("\nuipaq_attach: sc=%p\n", sc)); + + /* Move the device into the configured state. */ + err = usbd_set_config_no(dev, UIPAQ_CONFIG_NO, 1); + if (err) { + device_printf(ucom->sc_dev, + "failed to set configuration: %s\n", usbd_errstr(err)); + goto bad; + } + + err = usbd_device2interface_handle(dev, UIPAQ_IFACE_INDEX, &iface); + if (err) { + device_printf(ucom->sc_dev, "failed to get interface: %s\n", + usbd_errstr(err)); + goto bad; + } + + sc->sc_flags = uipaq_lookup(uaa->vendor, uaa->product)->uv_flags; + id = usbd_get_interface_descriptor(iface); + ucom->sc_iface = iface; + ucom->sc_ibufsize = UIPAQIBUFSIZE; + ucom->sc_obufsize = UIPAQOBUFSIZE; + ucom->sc_ibufsizepad = UIPAQIBUFSIZE; + ucom->sc_opkthdrlen = 0; + ucom->sc_callback = &uipaq_callback; + ucom->sc_parent = sc; + ucom->sc_bulkin_no = ucom->sc_bulkout_no = -1; + for (i=0; i<id->bNumEndpoints; i++) { + ed = usbd_interface2endpoint_descriptor(iface, i); + if (ed == NULL) { + device_printf(ucom->sc_dev, + "no endpoint descriptor for %d\n", i); + goto bad; + } + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + (ed->bmAttributes & UE_XFERTYPE) == UE_BULK) { + ucom->sc_bulkin_no = ed->bEndpointAddress; + } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT && + (ed->bmAttributes & UE_XFERTYPE) == UE_BULK) { + ucom->sc_bulkout_no = ed->bEndpointAddress; + } + } + if (ucom->sc_bulkin_no == -1 || ucom->sc_bulkout_no == -1) { + device_printf(ucom->sc_dev, + "no proper endpoints found (%d,%d)\n", + ucom->sc_bulkin_no, ucom->sc_bulkout_no); + return (ENXIO); + } + /* + * Send magic bytes, cribbed from Linux ipaq driver that claims + * to have sniffed them from Win98. + */ + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + USETW(req.wValue, UCDC_LINE_DTR); + USETW(req.wIndex, 0x0); + USETW(req.wLength, 0); + for (i = 0; i < 100; i++) { + err = usbd_do_request_flags(ucom->sc_udev, &req, NULL, 0, NULL, 100); + if (!err) + break; + usbd_delay_ms(dev, 1000); + } + ucom_attach(&sc->sc_ucom); + return (0); +bad: + DPRINTF(("uipaq_attach: ATTACH ERROR\n")); + ucom->sc_dying = 1; + return (ENXIO); +} + +void +uipaq_dtr(struct uipaq_softc* sc, int onoff) +{ + usb_device_request_t req; + struct ucom_softc *ucom = &sc->sc_ucom; + usbd_status err; + int retries = 3; + + DPRINTF(("%s: uipaq_dtr: onoff=%x\n", device_get_nameunit(ucom->sc_dev), onoff)); + + /* Avoid sending unnecessary requests */ + if (onoff && (sc->sc_lcr & UCDC_LINE_DTR)) + return; + if (!onoff && !(sc->sc_lcr & UCDC_LINE_DTR)) + return; + + /* Other parameters depend on reg */ + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + sc->sc_lcr = onoff ? sc->sc_lcr | UCDC_LINE_DTR : sc->sc_lcr & ~UCDC_LINE_DTR; + USETW(req.wValue, sc->sc_lcr); + USETW(req.wIndex, 0x0); + USETW(req.wLength, 0); + + /* Fire off the request a few times if necessary */ + while (retries) { + err = usbd_do_request(ucom->sc_udev, &req, NULL); + if (!err) + break; + retries--; + } +} + +void +uipaq_rts(struct uipaq_softc* sc, int onoff) +{ + usb_device_request_t req; + struct ucom_softc *ucom = &sc->sc_ucom; + usbd_status err; + int retries = 3; + + DPRINTF(("%s: uipaq_rts: onoff=%x\n", device_get_nameunit(ucom->sc_dev), onoff)); + + /* Avoid sending unnecessary requests */ + if (onoff && (sc->sc_lcr & UCDC_LINE_RTS)) return; + if (!onoff && !(sc->sc_lcr & UCDC_LINE_RTS)) return; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + sc->sc_lcr = onoff ? sc->sc_lcr | UCDC_LINE_RTS : sc->sc_lcr & ~UCDC_LINE_RTS; + USETW(req.wValue, sc->sc_lcr); + USETW(req.wIndex, 0x0); + USETW(req.wLength, 0); + + while (retries) { + err = usbd_do_request(ucom->sc_udev, &req, NULL); + if (!err) + break; + retries--; + } +} + +void +uipaq_break(struct uipaq_softc* sc, int onoff) +{ + usb_device_request_t req; + struct ucom_softc *ucom = &sc->sc_ucom; + usbd_status err; + int retries = 3; + + DPRINTF(("%s: uipaq_break: onoff=%x\n", device_get_nameunit(ucom->sc_dev), onoff)); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SEND_BREAK; + + USETW(req.wValue, onoff ? UCDC_BREAK_ON : UCDC_BREAK_OFF); + USETW(req.wIndex, 0x0); + USETW(req.wLength, 0); + + while (retries) { + err = usbd_do_request(ucom->sc_udev, &req, NULL); + if (!err) + break; + retries--; + } +} + +void +uipaq_set(void *addr, int portno, int reg, int onoff) +{ + struct uipaq_softc* sc = addr; + struct ucom_softc *ucom = &sc->sc_ucom; + + switch (reg) { + case UCOM_SET_DTR: + uipaq_dtr(addr, onoff); + break; + case UCOM_SET_RTS: + uipaq_rts(addr, onoff); + break; + case UCOM_SET_BREAK: + uipaq_break(addr, onoff); + break; + default: + printf("%s: unhandled set request: reg=%x onoff=%x\n", + device_get_nameunit(ucom->sc_dev), reg, onoff); + return; + } +} + +int +uipaq_detach(device_t self) +{ + struct uipaq_softc *sc = device_get_softc(self); + struct ucom_softc *ucom = &sc->sc_ucom; + int rv = 0; + + DPRINTF(("uipaq_detach: sc=%p flags=%d\n", sc, flags)); + ucom->sc_dying = 1; + + rv = ucom_detach(ucom); + + return (rv); +} + +static device_method_t uipaq_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, uipaq_match), + DEVMETHOD(device_attach, uipaq_attach), + DEVMETHOD(device_detach, uipaq_detach), + + { 0, 0 } +}; +static driver_t uipaq_driver = { + "ucom", + uipaq_methods, + sizeof (struct uipaq_softc) +}; + +DRIVER_MODULE(uipaq, uhub, uipaq_driver, ucom_devclass, usbd_driver_load, 0); +MODULE_DEPEND(uipaq, usb, 1, 1, 1); +MODULE_DEPEND(uipaq, ucom, UCOM_MINVER, UCOM_PREFVER, UCOM_MAXVER); diff --git a/sys/legacy/dev/usb/ukbd.c b/sys/legacy/dev/usb/ukbd.c new file mode 100644 index 0000000..5a7c9fc --- /dev/null +++ b/sys/legacy/dev/usb/ukbd.c @@ -0,0 +1,1538 @@ +/*- + * 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf + */ + +#include "opt_compat.h" +#include "opt_kbd.h" +#include "opt_ukbd.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/ioccom.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/file.h> +#include <sys/limits.h> +#include <sys/selinfo.h> +#include <sys/sysctl.h> +#include <sys/uio.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbhid.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include "usbdevs.h" +#include <dev/usb/usb_quirks.h> +#include <dev/usb/hid.h> + +#include <sys/kbio.h> +#include <dev/kbd/kbdreg.h> + +#define UKBD_EMULATE_ATSCANCODE 1 + +#define DRIVER_NAME "ukbd" + +#define delay(d) DELAY(d) + +#ifdef USB_DEBUG +#define DPRINTF(x) if (ukbddebug) printf x +#define DPRINTFN(n,x) if (ukbddebug>(n)) printf x +int ukbddebug = 0; +SYSCTL_NODE(_hw_usb, OID_AUTO, ukbd, CTLFLAG_RW, 0, "USB ukbd"); +SYSCTL_INT(_hw_usb_ukbd, OID_AUTO, debug, CTLFLAG_RW, + &ukbddebug, 0, "ukbd debug level"); +#else +#define DPRINTF(x) +#define DPRINTFN(n,x) +#endif + +#define UPROTO_BOOT_KEYBOARD 1 + +#define NKEYCODE 6 + +struct ukbd_data { + u_int8_t modifiers; +#define MOD_CONTROL_L 0x01 +#define MOD_CONTROL_R 0x10 +#define MOD_SHIFT_L 0x02 +#define MOD_SHIFT_R 0x20 +#define MOD_ALT_L 0x04 +#define MOD_ALT_R 0x40 +#define MOD_WIN_L 0x08 +#define MOD_WIN_R 0x80 + u_int8_t reserved; + u_int8_t keycode[NKEYCODE]; +}; + +#define MAXKEYS (NMOD+2*NKEYCODE) + +typedef struct ukbd_softc { + device_t sc_dev; /* base device */ +} ukbd_softc_t; + +#define UKBD_CHUNK 128 /* chunk size for read */ +#define UKBD_BSIZE 1020 /* buffer size */ + +typedef void usbd_intr_t(usbd_xfer_handle, usbd_private_handle, usbd_status); +typedef void usbd_disco_t(void *); + +static usbd_intr_t ukbd_intr; +static int ukbd_driver_load(module_t mod, int what, void *arg); + +static device_probe_t ukbd_match; +static device_attach_t ukbd_attach; +static device_detach_t ukbd_detach; +static device_resume_t ukbd_resume; + +static device_method_t ukbd_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ukbd_match), + DEVMETHOD(device_attach, ukbd_attach), + DEVMETHOD(device_detach, ukbd_detach), + DEVMETHOD(device_resume, ukbd_resume), + + { 0, 0 } +}; + +static driver_t ukbd_driver = { + "ukbd", + ukbd_methods, + sizeof(struct ukbd_softc) +}; + +static devclass_t ukbd_devclass; + +MODULE_DEPEND(ukbd, usb, 1, 1, 1); +DRIVER_MODULE(ukbd, uhub, ukbd_driver, ukbd_devclass, ukbd_driver_load, 0); + +static int +ukbd_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + + keyboard_switch_t *sw; + void *arg[2]; + int unit = device_get_unit(self); + + sw = kbd_get_switch(DRIVER_NAME); + if (sw == NULL) + return (UMATCH_NONE); + + arg[0] = (void *)uaa; + arg[1] = (void *)ukbd_intr; + if ((*sw->probe)(unit, (void *)arg, 0)) + return (UMATCH_NONE); + + if (usbd_get_quirks(uaa->device)->uq_flags & UQ_KBD_IGNORE) + return (UMATCH_NONE); + + return (UMATCH_IFACECLASS_IFACESUBCLASS_IFACEPROTO); +} + +static int +ukbd_attach(device_t self) +{ + struct ukbd_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + usbd_interface_handle iface = uaa->iface; + usb_interface_descriptor_t *id; + + keyboard_switch_t *sw; + keyboard_t *kbd; + void *arg[2]; + int unit = device_get_unit(self); + + sc->sc_dev = self; + sw = kbd_get_switch(DRIVER_NAME); + if (sw == NULL) + return ENXIO; + + id = usbd_get_interface_descriptor(iface); + + arg[0] = (void *)uaa; + arg[1] = (void *)ukbd_intr; + kbd = NULL; + if ((*sw->probe)(unit, (void *)arg, 0)) + return ENXIO; + if ((*sw->init)(unit, &kbd, (void *)arg, 0)) + return ENXIO; + (*sw->enable)(kbd); + +#ifdef KBD_INSTALL_CDEV + if (kbd_attach(kbd)) + return ENXIO; +#endif + if (bootverbose) + (*sw->diag)(kbd, bootverbose); + + return 0; +} + +int +ukbd_detach(device_t self) +{ + keyboard_t *kbd; + int error; + + kbd = kbd_get_keyboard(kbd_find_keyboard(DRIVER_NAME, + device_get_unit(self))); + if (kbd == NULL) { + DPRINTF(("%s: keyboard not attached!?\n", device_get_nameunit(self))); + return ENXIO; + } + kbdd_disable(kbd); + +#ifdef KBD_INSTALL_CDEV + error = kbd_detach(kbd); + if (error) + return error; +#endif + error = kbdd_term(kbd); + if (error) + return error; + + DPRINTF(("%s: disconnected\n", device_get_nameunit(self))); + + return (0); +} + +static int +ukbd_resume(device_t self) +{ + keyboard_t *kbd; + + kbd = kbd_get_keyboard(kbd_find_keyboard(DRIVER_NAME, + device_get_unit(self))); + if (kbd) + kbdd_clear_state(kbd); + return (0); +} + +void +ukbd_intr(usbd_xfer_handle xfer, usbd_private_handle addr, usbd_status status) +{ + keyboard_t *kbd = (keyboard_t *)addr; + + kbdd_intr(kbd, (void *)status); +} + +#define UKBD_DEFAULT 0 + +#define KEY_ERROR 0x01 + +#define KEY_PRESS 0 +#define KEY_RELEASE 0x400 +#define KEY_INDEX(c) ((c) & ~KEY_RELEASE) + +#define SCAN_PRESS 0 +#define SCAN_RELEASE 0x80 +#define SCAN_PREFIX_E0 0x100 +#define SCAN_PREFIX_E1 0x200 +#define SCAN_PREFIX_CTL 0x400 +#define SCAN_PREFIX_SHIFT 0x800 +#define SCAN_PREFIX (SCAN_PREFIX_E0 | SCAN_PREFIX_E1 | SCAN_PREFIX_CTL \ + | SCAN_PREFIX_SHIFT) +#define SCAN_CHAR(c) ((c) & 0x7f) + +#define NMOD 8 +static struct { + int mask, key; +} ukbd_mods[NMOD] = { + { MOD_CONTROL_L, 0xe0 }, + { MOD_CONTROL_R, 0xe4 }, + { MOD_SHIFT_L, 0xe1 }, + { MOD_SHIFT_R, 0xe5 }, + { MOD_ALT_L, 0xe2 }, + { MOD_ALT_R, 0xe6 }, + { MOD_WIN_L, 0xe3 }, + { MOD_WIN_R, 0xe7 }, +}; + +#define NN 0 /* no translation */ +/* + * Translate USB keycodes to AT keyboard scancodes. + */ +/* + * FIXME: Mac USB keyboard generates: + * 0x53: keypad NumLock/Clear + * 0x66: Power + * 0x67: keypad = + * 0x68: F13 + * 0x69: F14 + * 0x6a: F15 + */ +static u_int8_t ukbd_trtab[256] = { + 0, 0, 0, 0, 30, 48, 46, 32, /* 00 - 07 */ + 18, 33, 34, 35, 23, 36, 37, 38, /* 08 - 0F */ + 50, 49, 24, 25, 16, 19, 31, 20, /* 10 - 17 */ + 22, 47, 17, 45, 21, 44, 2, 3, /* 18 - 1F */ + 4, 5, 6, 7, 8, 9, 10, 11, /* 20 - 27 */ + 28, 1, 14, 15, 57, 12, 13, 26, /* 28 - 2F */ + 27, 43, 43, 39, 40, 41, 51, 52, /* 30 - 37 */ + 53, 58, 59, 60, 61, 62, 63, 64, /* 38 - 3F */ + 65, 66, 67, 68, 87, 88, 92, 70, /* 40 - 47 */ + 104, 102, 94, 96, 103, 99, 101, 98, /* 48 - 4F */ + 97, 100, 95, 69, 91, 55, 74, 78, /* 50 - 57 */ + 89, 79, 80, 81, 75, 76, 77, 71, /* 58 - 5F */ + 72, 73, 82, 83, 86, 107, 122, NN, /* 60 - 67 */ + NN, NN, NN, NN, NN, NN, NN, NN, /* 68 - 6F */ + NN, NN, NN, NN, 115, 108, 111, 113, /* 70 - 77 */ + 109, 110, 112, 118, 114, 116, 117, 119, /* 78 - 7F */ + 121, 120, NN, NN, NN, NN, NN, 115, /* 80 - 87 */ + 112, 125, 121, 123, NN, NN, NN, NN, /* 88 - 8F */ + NN, NN, NN, NN, NN, NN, NN, NN, /* 90 - 97 */ + NN, NN, NN, NN, NN, NN, NN, NN, /* 98 - 9F */ + NN, NN, NN, NN, NN, NN, NN, NN, /* A0 - A7 */ + NN, NN, NN, NN, NN, NN, NN, NN, /* A8 - AF */ + NN, NN, NN, NN, NN, NN, NN, NN, /* B0 - B7 */ + NN, NN, NN, NN, NN, NN, NN, NN, /* B8 - BF */ + NN, NN, NN, NN, NN, NN, NN, NN, /* C0 - C7 */ + NN, NN, NN, NN, NN, NN, NN, NN, /* C8 - CF */ + NN, NN, NN, NN, NN, NN, NN, NN, /* D0 - D7 */ + NN, NN, NN, NN, NN, NN, NN, NN, /* D8 - DF */ + 29, 42, 56, 105, 90, 54, 93, 106, /* E0 - E7 */ + NN, NN, NN, NN, NN, NN, NN, NN, /* E8 - EF */ + NN, NN, NN, NN, NN, NN, NN, NN, /* F0 - F7 */ + NN, NN, NN, NN, NN, NN, NN, NN, /* F8 - FF */ +}; + +typedef struct ukbd_state { + usbd_interface_handle ks_iface; /* interface */ + usbd_pipe_handle ks_intrpipe; /* interrupt pipe */ + struct usb_attach_arg *ks_uaa; + int ks_ep_addr; + + struct ukbd_data ks_ndata; + struct ukbd_data ks_odata; + u_long ks_ntime[NKEYCODE]; + u_long ks_otime[NKEYCODE]; + +#define INPUTBUFSIZE (NMOD + 2*NKEYCODE) + u_int ks_input[INPUTBUFSIZE]; /* input buffer */ + int ks_inputs; + int ks_inputhead; + int ks_inputtail; + + int ks_ifstate; +#define INTRENABLED (1 << 0) +#define DISCONNECTED (1 << 1) + + struct callout ks_timeout_handle; + + int ks_mode; /* input mode (K_XLATE,K_RAW,K_CODE) */ + int ks_flags; /* flags */ +#define COMPOSE (1 << 0) + int ks_polling; + int ks_state; /* shift/lock key state */ + int ks_accents; /* accent key index (> 0) */ + u_int ks_composed_char; /* composed char code (> 0) */ +#ifdef UKBD_EMULATE_ATSCANCODE + u_int ks_buffered_char[2]; + u_int8_t ks_leds; /* store for async led requests */ +#endif +} ukbd_state_t; + +/* keyboard driver declaration */ +static int ukbd_configure(int flags); +static kbd_probe_t ukbd_probe; +static kbd_init_t ukbd_init; +static kbd_term_t ukbd_term; +static kbd_intr_t ukbd_interrupt; +static kbd_test_if_t ukbd_test_if; +static kbd_enable_t ukbd_enable; +static kbd_disable_t ukbd_disable; +static kbd_read_t ukbd_read; +static kbd_check_t ukbd_check; +static kbd_read_char_t ukbd_read_char; +static kbd_check_char_t ukbd_check_char; +static kbd_ioctl_t ukbd_ioctl; +static kbd_lock_t ukbd_lock; +static kbd_clear_state_t ukbd_clear_state; +static kbd_get_state_t ukbd_get_state; +static kbd_set_state_t ukbd_set_state; +static kbd_poll_mode_t ukbd_poll; + +keyboard_switch_t ukbdsw = { + ukbd_probe, + ukbd_init, + ukbd_term, + ukbd_interrupt, + ukbd_test_if, + ukbd_enable, + ukbd_disable, + ukbd_read, + ukbd_check, + ukbd_read_char, + ukbd_check_char, + ukbd_ioctl, + ukbd_lock, + ukbd_clear_state, + ukbd_get_state, + ukbd_set_state, + genkbd_get_fkeystr, + ukbd_poll, + genkbd_diag, +}; + +KEYBOARD_DRIVER(ukbd, ukbdsw, ukbd_configure); + +/* local functions */ +static int ukbd_enable_intr(keyboard_t *kbd, int on, + usbd_intr_t *func); +static void ukbd_timeout(void *arg); + +static int ukbd_getc(ukbd_state_t *state, int wait); +static int probe_keyboard(struct usb_attach_arg *uaa, int flags); +static int init_keyboard(ukbd_state_t *state, int *type, + int flags); +static void set_leds(ukbd_state_t *state, int leds); +static int set_typematic(keyboard_t *kbd, int code); +#ifdef UKBD_EMULATE_ATSCANCODE +static int keycode2scancode(int keycode, int shift, int up); +#endif + +/* local variables */ + +/* the initial key map, accent map and fkey strings */ +#if defined(UKBD_DFLT_KEYMAP) && !defined(KLD_MODULE) +#define KBD_DFLT_KEYMAP +#include "ukbdmap.h" +#endif +#include <dev/kbd/kbdtables.h> + +/* structures for the default keyboard */ +static keyboard_t default_kbd; +static ukbd_state_t default_kbd_state; +static keymap_t default_keymap; +static accentmap_t default_accentmap; +static fkeytab_t default_fkeytab[NUM_FKEYS]; + +/* + * The back door to the keyboard driver! + * This function is called by the console driver, via the kbdio module, + * to tickle keyboard drivers when the low-level console is being initialized. + * Almost nothing in the kernel has been initialied yet. Try to probe + * keyboards if possible. + * NOTE: because of the way the low-level conole is initialized, this routine + * may be called more than once!! + */ +static int +ukbd_configure(int flags) +{ + return 0; + +#if 0 /* not yet */ + keyboard_t *kbd; + device_t device; + struct usb_attach_arg *uaa; + void *arg[2]; + + device = devclass_get_device(ukbd_devclass, UKBD_DEFAULT); + if (device == NULL) + return 0; + uaa = (struct usb_attach_arg *)device_get_ivars(device); + if (uaa == NULL) + return 0; + + /* probe the default keyboard */ + arg[0] = (void *)uaa; + arg[1] = (void *)ukbd_intr; + kbd = NULL; + if (ukbd_probe(UKBD_DEFAULT, arg, flags)) + return 0; + if (ukbd_init(UKBD_DEFAULT, &kbd, arg, flags)) + return 0; + + /* return the number of found keyboards */ + return 1; +#endif +} + +/* low-level functions */ + +/* detect a keyboard */ +static int +ukbd_probe(int unit, void *arg, int flags) +{ + void **data; + struct usb_attach_arg *uaa; + + data = (void **)arg; + uaa = (struct usb_attach_arg *)data[0]; + + /* XXX */ + if (unit == UKBD_DEFAULT) { + if (KBD_IS_PROBED(&default_kbd)) + return 0; + } + if (probe_keyboard(uaa, flags)) + return ENXIO; + return 0; +} + +/* reset and initialize the device */ +static int +ukbd_init(int unit, keyboard_t **kbdp, void *arg, int flags) +{ + keyboard_t *kbd; + ukbd_state_t *state; + keymap_t *keymap; + accentmap_t *accmap; + fkeytab_t *fkeymap; + int fkeymap_size; + void **data = (void **)arg; + struct usb_attach_arg *uaa = (struct usb_attach_arg *)data[0]; + + /* XXX */ + if (unit == UKBD_DEFAULT) { + *kbdp = kbd = &default_kbd; + if (KBD_IS_INITIALIZED(kbd) && KBD_IS_CONFIGURED(kbd)) + return 0; + state = &default_kbd_state; + keymap = &default_keymap; + accmap = &default_accentmap; + fkeymap = default_fkeytab; + fkeymap_size = + sizeof(default_fkeytab)/sizeof(default_fkeytab[0]); + } else if (*kbdp == NULL) { + *kbdp = kbd = malloc(sizeof(*kbd), M_DEVBUF, M_NOWAIT); + if (kbd == NULL) + return ENOMEM; + bzero(kbd, sizeof(*kbd)); + state = malloc(sizeof(*state), M_DEVBUF, M_NOWAIT); + keymap = malloc(sizeof(key_map), M_DEVBUF, M_NOWAIT); + accmap = malloc(sizeof(accent_map), M_DEVBUF, M_NOWAIT); + fkeymap = malloc(sizeof(fkey_tab), M_DEVBUF, M_NOWAIT); + fkeymap_size = sizeof(fkey_tab)/sizeof(fkey_tab[0]); + if ((state == NULL) || (keymap == NULL) || (accmap == NULL) + || (fkeymap == NULL)) { + if (state != NULL) + free(state, M_DEVBUF); + if (keymap != NULL) + free(keymap, M_DEVBUF); + if (accmap != NULL) + free(accmap, M_DEVBUF); + if (fkeymap != NULL) + free(fkeymap, M_DEVBUF); + free(kbd, M_DEVBUF); + return ENOMEM; + } + } else if (KBD_IS_INITIALIZED(*kbdp) && KBD_IS_CONFIGURED(*kbdp)) { + return 0; + } else { + kbd = *kbdp; + state = (ukbd_state_t *)kbd->kb_data; + keymap = kbd->kb_keymap; + accmap = kbd->kb_accentmap; + fkeymap = kbd->kb_fkeytab; + fkeymap_size = kbd->kb_fkeytab_size; + } + + if (!KBD_IS_PROBED(kbd)) { + kbd_init_struct(kbd, DRIVER_NAME, KB_OTHER, unit, flags, 0, 0); + bzero(state, sizeof(*state)); + bcopy(&key_map, keymap, sizeof(key_map)); + bcopy(&accent_map, accmap, sizeof(accent_map)); + bcopy(fkey_tab, fkeymap, + imin(fkeymap_size*sizeof(fkeymap[0]), sizeof(fkey_tab))); + kbd_set_maps(kbd, keymap, accmap, fkeymap, fkeymap_size); + kbd->kb_data = (void *)state; + + if (probe_keyboard(uaa, flags)) + return ENXIO; + else + KBD_FOUND_DEVICE(kbd); + ukbd_clear_state(kbd); + state->ks_mode = K_XLATE; + state->ks_iface = uaa->iface; + state->ks_uaa = uaa; + state->ks_ifstate = 0; + callout_init(&state->ks_timeout_handle, 0); + /* + * FIXME: set the initial value for lock keys in ks_state + * according to the BIOS data? + */ + KBD_PROBE_DONE(kbd); + } + if (!KBD_IS_INITIALIZED(kbd) && !(flags & KB_CONF_PROBE_ONLY)) { + if (KBD_HAS_DEVICE(kbd) + && init_keyboard((ukbd_state_t *)kbd->kb_data, + &kbd->kb_type, kbd->kb_flags)) { + kbd->kb_flags = 0; + /* XXX: Missing free()'s */ + return ENXIO; + } + ukbd_ioctl(kbd, KDSETLED, (caddr_t)&(state->ks_state)); + KBD_INIT_DONE(kbd); + } + if (!KBD_IS_CONFIGURED(kbd)) { + if (kbd_register(kbd) < 0) { + kbd->kb_flags = 0; + /* XXX: Missing free()'s */ + return ENXIO; + } + if (ukbd_enable_intr(kbd, TRUE, (usbd_intr_t *)data[1]) == 0) + ukbd_timeout((void *)kbd); + KBD_CONFIG_DONE(kbd); + } + + return 0; +} + +static int +ukbd_enable_intr(keyboard_t *kbd, int on, usbd_intr_t *func) +{ + ukbd_state_t *state = (ukbd_state_t *)kbd->kb_data; + usbd_status err; + + if (on) { + /* Set up interrupt pipe. */ + if (state->ks_ifstate & INTRENABLED) + return EBUSY; + + state->ks_ifstate |= INTRENABLED; + err = usbd_open_pipe_intr(state->ks_iface, state->ks_ep_addr, + USBD_SHORT_XFER_OK, + &state->ks_intrpipe, kbd, + &state->ks_ndata, + sizeof(state->ks_ndata), func, + USBD_DEFAULT_INTERVAL); + if (err) + return (EIO); + } else { + /* Disable interrupts. */ + usbd_abort_pipe(state->ks_intrpipe); + usbd_close_pipe(state->ks_intrpipe); + + state->ks_ifstate &= ~INTRENABLED; + } + + return (0); +} + +/* finish using this keyboard */ +static int +ukbd_term(keyboard_t *kbd) +{ + ukbd_state_t *state; + int error; + int s; + + s = splusb(); + + state = (ukbd_state_t *)kbd->kb_data; + DPRINTF(("ukbd_term: ks_ifstate=0x%x\n", state->ks_ifstate)); + + callout_stop(&state->ks_timeout_handle); + + if (state->ks_ifstate & INTRENABLED) + ukbd_enable_intr(kbd, FALSE, NULL); + if (state->ks_ifstate & INTRENABLED) { + splx(s); + DPRINTF(("ukbd_term: INTRENABLED!\n")); + return ENXIO; + } + + error = kbd_unregister(kbd); + DPRINTF(("ukbd_term: kbd_unregister() %d\n", error)); + if (error == 0) { + kbd->kb_flags = 0; + if (kbd != &default_kbd) { + free(kbd->kb_keymap, M_DEVBUF); + free(kbd->kb_accentmap, M_DEVBUF); + free(kbd->kb_fkeytab, M_DEVBUF); + free(state, M_DEVBUF); + free(kbd, M_DEVBUF); + } + } + + splx(s); + return error; +} + + +/* keyboard interrupt routine */ + +static void +ukbd_timeout(void *arg) +{ + keyboard_t *kbd; + ukbd_state_t *state; + int s; + + kbd = (keyboard_t *)arg; + state = (ukbd_state_t *)kbd->kb_data; + s = splusb(); + kbdd_intr(kbd, (void *)USBD_NORMAL_COMPLETION); + callout_reset(&state->ks_timeout_handle, hz / 40, ukbd_timeout, arg); + splx(s); +} + +static int +ukbd_interrupt(keyboard_t *kbd, void *arg) +{ + usbd_status status = (usbd_status)arg; + ukbd_state_t *state; + struct ukbd_data *ud; + struct timeval tv; + u_long now; + int mod, omod; + int key, c; + int i, j; + + DPRINTFN(5, ("ukbd_intr: status=%d\n", status)); + if (status == USBD_CANCELLED) + return 0; + + state = (ukbd_state_t *)kbd->kb_data; + ud = &state->ks_ndata; + + if (status != USBD_NORMAL_COMPLETION) { + DPRINTF(("ukbd_intr: status=%d\n", status)); + if (status == USBD_STALLED) + usbd_clear_endpoint_stall_async(state->ks_intrpipe); + return 0; + } + + if (ud->keycode[0] == KEY_ERROR) + return 0; /* ignore */ + + getmicrouptime(&tv); + now = (u_long)tv.tv_sec*1000 + (u_long)tv.tv_usec/1000; + +#define ADDKEY1(c) \ + if (state->ks_inputs < INPUTBUFSIZE) { \ + state->ks_input[state->ks_inputtail] = (c); \ + ++state->ks_inputs; \ + state->ks_inputtail = (state->ks_inputtail + 1)%INPUTBUFSIZE; \ + } + + mod = ud->modifiers; + omod = state->ks_odata.modifiers; + if (mod != omod) { + for (i = 0; i < NMOD; i++) + if (( mod & ukbd_mods[i].mask) != + (omod & ukbd_mods[i].mask)) + ADDKEY1(ukbd_mods[i].key | + (mod & ukbd_mods[i].mask + ? KEY_PRESS : KEY_RELEASE)); + } + + /* Check for released keys. */ + for (i = 0; i < NKEYCODE; i++) { + key = state->ks_odata.keycode[i]; + if (key == 0) + continue; + for (j = 0; j < NKEYCODE; j++) { + if (ud->keycode[j] == 0) + continue; + if (key == ud->keycode[j]) + goto rfound; + } + ADDKEY1(key | KEY_RELEASE); + rfound: + ; + } + + /* Check for pressed keys. */ + for (i = 0; i < NKEYCODE; i++) { + key = ud->keycode[i]; + if (key == 0) + continue; + state->ks_ntime[i] = now + kbd->kb_delay1; + for (j = 0; j < NKEYCODE; j++) { + if (state->ks_odata.keycode[j] == 0) + continue; + if (key == state->ks_odata.keycode[j]) { + state->ks_ntime[i] = state->ks_otime[j]; + if (state->ks_otime[j] > now) + goto pfound; + state->ks_ntime[i] = now + kbd->kb_delay2; + break; + } + } + ADDKEY1(key | KEY_PRESS); + /* + * If any other key is presently down, force its repeat to be + * well in the future (100s). This makes the last key to be + * pressed do the autorepeat. + */ + for (j = 0; j < NKEYCODE; j++) { + if (j != i) + state->ks_ntime[j] = now + 100 * 1000; + } + pfound: + ; + } + + state->ks_odata = *ud; + bcopy(state->ks_ntime, state->ks_otime, sizeof(state->ks_ntime)); + if (state->ks_inputs <= 0) + return 0; + +#ifdef USB_DEBUG + for (i = state->ks_inputhead, j = 0; j < state->ks_inputs; ++j, + i = (i + 1)%INPUTBUFSIZE) { + c = state->ks_input[i]; + DPRINTF(("0x%x (%d) %s\n", c, c, + (c & KEY_RELEASE) ? "released":"pressed")); + } + if (ud->modifiers) + DPRINTF(("mod:0x%04x ", ud->modifiers)); + for (i = 0; i < NKEYCODE; i++) { + if (ud->keycode[i]) + DPRINTF(("%d ", ud->keycode[i])); + } + DPRINTF(("\n")); +#endif /* USB_DEBUG */ + + if (state->ks_polling) + return 0; + + if (KBD_IS_ACTIVE(kbd) && KBD_IS_BUSY(kbd)) { + /* let the callback function to process the input */ + (*kbd->kb_callback.kc_func)(kbd, KBDIO_KEYINPUT, + kbd->kb_callback.kc_arg); + } else { + /* read and discard the input; no one is waiting for it */ + do { + c = ukbd_read_char(kbd, FALSE); + } while (c != NOKEY); + } + + return 0; +} + +static int +ukbd_getc(ukbd_state_t *state, int wait) +{ + int c; + int s; + + if (state->ks_polling) { + DPRINTFN(1,("ukbd_getc: polling\n")); + s = splusb(); + while (state->ks_inputs <= 0) { + usbd_dopoll(state->ks_iface); + if (wait == FALSE) + break; + } + splx(s); + } + s = splusb(); + if (state->ks_inputs <= 0) { + c = -1; + } else { + c = state->ks_input[state->ks_inputhead]; + --state->ks_inputs; + state->ks_inputhead = (state->ks_inputhead + 1)%INPUTBUFSIZE; + } + splx(s); + return c; +} + +/* test the interface to the device */ +static int +ukbd_test_if(keyboard_t *kbd) +{ + return 0; +} + +/* + * Enable the access to the device; until this function is called, + * the client cannot read from the keyboard. + */ +static int +ukbd_enable(keyboard_t *kbd) +{ + int s; + + s = splusb(); + KBD_ACTIVATE(kbd); + splx(s); + return 0; +} + +/* disallow the access to the device */ +static int +ukbd_disable(keyboard_t *kbd) +{ + int s; + + s = splusb(); + KBD_DEACTIVATE(kbd); + splx(s); + return 0; +} + +/* read one byte from the keyboard if it's allowed */ +static int +ukbd_read(keyboard_t *kbd, int wait) +{ + ukbd_state_t *state; + int usbcode; +#ifdef UKBD_EMULATE_ATSCANCODE + int keycode; + int scancode; +#endif + + state = (ukbd_state_t *)kbd->kb_data; +#ifdef UKBD_EMULATE_ATSCANCODE + if (state->ks_buffered_char[0]) { + scancode = state->ks_buffered_char[0]; + if (scancode & SCAN_PREFIX) { + state->ks_buffered_char[0] = scancode & ~SCAN_PREFIX; + return ((scancode & SCAN_PREFIX_E0) ? 0xe0 : 0xe1); + } else { + state->ks_buffered_char[0] = state->ks_buffered_char[1]; + state->ks_buffered_char[1] = 0; + return scancode; + } + } +#endif /* UKBD_EMULATE_ATSCANCODE */ + + /* XXX */ + usbcode = ukbd_getc(state, wait); + if (!KBD_IS_ACTIVE(kbd) || (usbcode == -1)) + return -1; + ++kbd->kb_count; +#ifdef UKBD_EMULATE_ATSCANCODE + keycode = ukbd_trtab[KEY_INDEX(usbcode)]; + if (keycode == NN) + return -1; + + scancode = keycode2scancode(keycode, state->ks_ndata.modifiers, + usbcode & KEY_RELEASE); + if (scancode & SCAN_PREFIX) { + if (scancode & SCAN_PREFIX_CTL) { + state->ks_buffered_char[0] = + 0x1d | (scancode & SCAN_RELEASE); /* Ctrl */ + state->ks_buffered_char[1] = scancode & ~SCAN_PREFIX; + } else if (scancode & SCAN_PREFIX_SHIFT) { + state->ks_buffered_char[0] = + 0x2a | (scancode & SCAN_RELEASE); /* Shift */ + state->ks_buffered_char[1] = + scancode & ~SCAN_PREFIX_SHIFT; + } else { + state->ks_buffered_char[0] = scancode & ~SCAN_PREFIX; + state->ks_buffered_char[1] = 0; + } + return ((scancode & SCAN_PREFIX_E0) ? 0xe0 : 0xe1); + } + return scancode; +#else /* !UKBD_EMULATE_ATSCANCODE */ + return usbcode; +#endif /* UKBD_EMULATE_ATSCANCODE */ +} + +/* check if data is waiting */ +static int +ukbd_check(keyboard_t *kbd) +{ + if (!KBD_IS_ACTIVE(kbd)) + return FALSE; +#ifdef UKBD_EMULATE_ATSCANCODE + if (((ukbd_state_t *)kbd->kb_data)->ks_buffered_char[0]) + return TRUE; +#endif + if (((ukbd_state_t *)kbd->kb_data)->ks_inputs > 0) + return TRUE; + return FALSE; +} + +/* read char from the keyboard */ +static u_int +ukbd_read_char(keyboard_t *kbd, int wait) +{ + ukbd_state_t *state; + u_int action; + int usbcode; + int keycode; +#ifdef UKBD_EMULATE_ATSCANCODE + int scancode; +#endif + + state = (ukbd_state_t *)kbd->kb_data; +next_code: + /* do we have a composed char to return? */ + if (!(state->ks_flags & COMPOSE) && (state->ks_composed_char > 0)) { + action = state->ks_composed_char; + state->ks_composed_char = 0; + if (action > UCHAR_MAX) + return ERRKEY; + return action; + } + +#ifdef UKBD_EMULATE_ATSCANCODE + /* do we have a pending raw scan code? */ + if (state->ks_mode == K_RAW) { + if (state->ks_buffered_char[0]) { + scancode = state->ks_buffered_char[0]; + if (scancode & SCAN_PREFIX) { + state->ks_buffered_char[0] = + scancode & ~SCAN_PREFIX; + return ((scancode & SCAN_PREFIX_E0) ? 0xe0 : 0xe1); + } else { + state->ks_buffered_char[0] = + state->ks_buffered_char[1]; + state->ks_buffered_char[1] = 0; + return scancode; + } + } + } +#endif /* UKBD_EMULATE_ATSCANCODE */ + + /* see if there is something in the keyboard port */ + /* XXX */ + usbcode = ukbd_getc(state, wait); + if (usbcode == -1) + return NOKEY; + ++kbd->kb_count; + +#ifdef UKBD_EMULATE_ATSCANCODE + /* USB key index -> key code -> AT scan code */ + keycode = ukbd_trtab[KEY_INDEX(usbcode)]; + if (keycode == NN) + return NOKEY; + + /* return an AT scan code for the K_RAW mode */ + if (state->ks_mode == K_RAW) { + scancode = keycode2scancode(keycode, state->ks_ndata.modifiers, + usbcode & KEY_RELEASE); + if (scancode & SCAN_PREFIX) { + if (scancode & SCAN_PREFIX_CTL) { + state->ks_buffered_char[0] = + 0x1d | (scancode & SCAN_RELEASE); + state->ks_buffered_char[1] = + scancode & ~SCAN_PREFIX; + } else if (scancode & SCAN_PREFIX_SHIFT) { + state->ks_buffered_char[0] = + 0x2a | (scancode & SCAN_RELEASE); + state->ks_buffered_char[1] = + scancode & ~SCAN_PREFIX_SHIFT; + } else { + state->ks_buffered_char[0] = + scancode & ~SCAN_PREFIX; + state->ks_buffered_char[1] = 0; + } + return ((scancode & SCAN_PREFIX_E0) ? 0xe0 : 0xe1); + } + return scancode; + } +#else /* !UKBD_EMULATE_ATSCANCODE */ + /* return the byte as is for the K_RAW mode */ + if (state->ks_mode == K_RAW) + return usbcode; + + /* USB key index -> key code */ + keycode = ukbd_trtab[KEY_INDEX(usbcode)]; + if (keycode == NN) + return NOKEY; +#endif /* UKBD_EMULATE_ATSCANCODE */ + + switch (keycode) { + case 0x38: /* left alt (compose key) */ + if (usbcode & KEY_RELEASE) { + if (state->ks_flags & COMPOSE) { + state->ks_flags &= ~COMPOSE; + if (state->ks_composed_char > UCHAR_MAX) + state->ks_composed_char = 0; + } + } else { + if (!(state->ks_flags & COMPOSE)) { + state->ks_flags |= COMPOSE; + state->ks_composed_char = 0; + } + } + break; + /* XXX: I don't like these... */ + case 0x5c: /* print screen */ + if (state->ks_flags & ALTS) + keycode = 0x54; /* sysrq */ + break; + case 0x68: /* pause/break */ + if (state->ks_flags & CTLS) + keycode = 0x6c; /* break */ + break; + } + + /* return the key code in the K_CODE mode */ + if (usbcode & KEY_RELEASE) + keycode |= SCAN_RELEASE; + if (state->ks_mode == K_CODE) + return keycode; + + /* compose a character code */ + if (state->ks_flags & COMPOSE) { + switch (keycode) { + /* key pressed, process it */ + case 0x47: case 0x48: case 0x49: /* keypad 7,8,9 */ + state->ks_composed_char *= 10; + state->ks_composed_char += keycode - 0x40; + if (state->ks_composed_char > UCHAR_MAX) + return ERRKEY; + goto next_code; + case 0x4B: case 0x4C: case 0x4D: /* keypad 4,5,6 */ + state->ks_composed_char *= 10; + state->ks_composed_char += keycode - 0x47; + if (state->ks_composed_char > UCHAR_MAX) + return ERRKEY; + goto next_code; + case 0x4F: case 0x50: case 0x51: /* keypad 1,2,3 */ + state->ks_composed_char *= 10; + state->ks_composed_char += keycode - 0x4E; + if (state->ks_composed_char > UCHAR_MAX) + return ERRKEY; + goto next_code; + case 0x52: /* keypad 0 */ + state->ks_composed_char *= 10; + if (state->ks_composed_char > UCHAR_MAX) + return ERRKEY; + goto next_code; + + /* key released, no interest here */ + case SCAN_RELEASE | 0x47: + case SCAN_RELEASE | 0x48: + case SCAN_RELEASE | 0x49: /* keypad 7,8,9 */ + case SCAN_RELEASE | 0x4B: + case SCAN_RELEASE | 0x4C: + case SCAN_RELEASE | 0x4D: /* keypad 4,5,6 */ + case SCAN_RELEASE | 0x4F: + case SCAN_RELEASE | 0x50: + case SCAN_RELEASE | 0x51: /* keypad 1,2,3 */ + case SCAN_RELEASE | 0x52: /* keypad 0 */ + goto next_code; + + case 0x38: /* left alt key */ + break; + + default: + if (state->ks_composed_char > 0) { + state->ks_flags &= ~COMPOSE; + state->ks_composed_char = 0; + return ERRKEY; + } + break; + } + } + + /* keycode to key action */ + action = genkbd_keyaction(kbd, SCAN_CHAR(keycode), + keycode & SCAN_RELEASE, &state->ks_state, + &state->ks_accents); + if (action == NOKEY) + goto next_code; + else + return action; +} + +/* check if char is waiting */ +static int +ukbd_check_char(keyboard_t *kbd) +{ + ukbd_state_t *state; + + if (!KBD_IS_ACTIVE(kbd)) + return FALSE; + state = (ukbd_state_t *)kbd->kb_data; + if (!(state->ks_flags & COMPOSE) && (state->ks_composed_char > 0)) + return TRUE; + return ukbd_check(kbd); +} + +/* some useful control functions */ +static int +ukbd_ioctl(keyboard_t *kbd, u_long cmd, caddr_t arg) +{ + /* trasnlate LED_XXX bits into the device specific bits */ + static u_char ledmap[8] = { + 0, 2, 1, 3, 4, 6, 5, 7, + }; + ukbd_state_t *state = kbd->kb_data; + int s; + int i; +#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ + defined(COMPAT_FREEBSD4) || defined(COMPAT_43) + int ival; +#endif + + s = splusb(); + switch (cmd) { + + case KDGKBMODE: /* get keyboard mode */ + *(int *)arg = state->ks_mode; + break; +#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ + defined(COMPAT_FREEBSD4) || defined(COMPAT_43) + case _IO('K', 7): + ival = IOCPARM_IVAL(arg); + arg = (caddr_t)&ival; + /* FALLTHROUGH */ +#endif + case KDSKBMODE: /* set keyboard mode */ + switch (*(int *)arg) { + case K_XLATE: + if (state->ks_mode != K_XLATE) { + /* make lock key state and LED state match */ + state->ks_state &= ~LOCK_MASK; + state->ks_state |= KBD_LED_VAL(kbd); + } + /* FALLTHROUGH */ + case K_RAW: + case K_CODE: + if (state->ks_mode != *(int *)arg) { + ukbd_clear_state(kbd); + state->ks_mode = *(int *)arg; + } + break; + default: + splx(s); + return EINVAL; + } + break; + + case KDGETLED: /* get keyboard LED */ + *(int *)arg = KBD_LED_VAL(kbd); + break; +#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ + defined(COMPAT_FREEBSD4) || defined(COMPAT_43) + case _IO('K', 66): + ival = IOCPARM_IVAL(arg); + arg = (caddr_t)&ival; + /* FALLTHROUGH */ +#endif + case KDSETLED: /* set keyboard LED */ + /* NOTE: lock key state in ks_state won't be changed */ + if (*(int *)arg & ~LOCK_MASK) { + splx(s); + return EINVAL; + } + i = *(int *)arg; + /* replace CAPS LED with ALTGR LED for ALTGR keyboards */ + if (state->ks_mode == K_XLATE && + kbd->kb_keymap->n_keys > ALTGR_OFFSET) { + if (i & ALKED) + i |= CLKED; + else + i &= ~CLKED; + } + if (KBD_HAS_DEVICE(kbd)) { + set_leds(state, ledmap[i & LED_MASK]); + /* XXX: error check? */ + } + KBD_LED_VAL(kbd) = *(int *)arg; + break; + + case KDGKBSTATE: /* get lock key state */ + *(int *)arg = state->ks_state & LOCK_MASK; + break; +#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ + defined(COMPAT_FREEBSD4) || defined(COMPAT_43) + case _IO('K', 20): + ival = IOCPARM_IVAL(arg); + arg = (caddr_t)&ival; + /* FALLTHROUGH */ +#endif + case KDSKBSTATE: /* set lock key state */ + if (*(int *)arg & ~LOCK_MASK) { + splx(s); + return EINVAL; + } + state->ks_state &= ~LOCK_MASK; + state->ks_state |= *(int *)arg; + splx(s); + /* set LEDs and quit */ + return ukbd_ioctl(kbd, KDSETLED, arg); + + case KDSETREPEAT: /* set keyboard repeat rate (new interface) */ + splx(s); + if (!KBD_HAS_DEVICE(kbd)) + return 0; + if (((int *)arg)[1] < 0) + return EINVAL; + if (((int *)arg)[0] < 0) + return EINVAL; + else if (((int *)arg)[0] == 0) /* fastest possible value */ + kbd->kb_delay1 = 200; + else + kbd->kb_delay1 = ((int *)arg)[0]; + kbd->kb_delay2 = ((int *)arg)[1]; + return 0; + +#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ + defined(COMPAT_FREEBSD4) || defined(COMPAT_43) + case _IO('K', 67): + ival = IOCPARM_IVAL(arg); + arg = (caddr_t)&ival; + /* FALLTHROUGH */ +#endif + case KDSETRAD: /* set keyboard repeat rate (old interface) */ + splx(s); + return set_typematic(kbd, *(int *)arg); + + case PIO_KEYMAP: /* set keyboard translation table */ + case PIO_KEYMAPENT: /* set keyboard translation table entry */ + case PIO_DEADKEYMAP: /* set accent key translation table */ + state->ks_accents = 0; + /* FALLTHROUGH */ + default: + splx(s); + return genkbd_commonioctl(kbd, cmd, arg); + +#ifdef USB_DEBUG + case USB_SETDEBUG: + ukbddebug = *(int *)arg; + break; +#endif + } + + splx(s); + return 0; +} + +/* lock the access to the keyboard */ +static int +ukbd_lock(keyboard_t *kbd, int lock) +{ + /* XXX ? */ + return TRUE; +} + +/* clear the internal state of the keyboard */ +static void +ukbd_clear_state(keyboard_t *kbd) +{ + ukbd_state_t *state; + + state = (ukbd_state_t *)kbd->kb_data; + state->ks_flags = 0; + state->ks_polling = 0; + state->ks_state &= LOCK_MASK; /* preserve locking key state */ + state->ks_accents = 0; + state->ks_composed_char = 0; +#ifdef UKBD_EMULATE_ATSCANCODE + state->ks_buffered_char[0] = 0; + state->ks_buffered_char[1] = 0; +#endif + bzero(&state->ks_ndata, sizeof(state->ks_ndata)); + bzero(&state->ks_odata, sizeof(state->ks_odata)); + bzero(&state->ks_ntime, sizeof(state->ks_ntime)); + bzero(&state->ks_otime, sizeof(state->ks_otime)); +} + +/* save the internal state */ +static int +ukbd_get_state(keyboard_t *kbd, void *buf, size_t len) +{ + if (len == 0) + return sizeof(ukbd_state_t); + if (len < sizeof(ukbd_state_t)) + return -1; + bcopy(kbd->kb_data, buf, sizeof(ukbd_state_t)); + return 0; +} + +/* set the internal state */ +static int +ukbd_set_state(keyboard_t *kbd, void *buf, size_t len) +{ + if (len < sizeof(ukbd_state_t)) + return ENOMEM; + bcopy(buf, kbd->kb_data, sizeof(ukbd_state_t)); + return 0; +} + +static int +ukbd_poll(keyboard_t *kbd, int on) +{ + ukbd_state_t *state; + usbd_device_handle dev; + int s; + + state = (ukbd_state_t *)kbd->kb_data; + usbd_interface2device_handle(state->ks_iface, &dev); + + s = splusb(); + if (on) { + ++state->ks_polling; + if (state->ks_polling == 1) + usbd_set_polling(dev, on); + } else { + --state->ks_polling; + if (state->ks_polling == 0) + usbd_set_polling(dev, on); + } + splx(s); + return 0; +} + +/* local functions */ + +static int +probe_keyboard(struct usb_attach_arg *uaa, int flags) +{ + usb_interface_descriptor_t *id; + + if (!uaa->iface) /* we attach to ifaces only */ + return EINVAL; + + /* Check that this is a keyboard that speaks the boot protocol. */ + id = usbd_get_interface_descriptor(uaa->iface); + if (id + && id->bInterfaceClass == UICLASS_HID + && id->bInterfaceSubClass == UISUBCLASS_BOOT + && id->bInterfaceProtocol == UPROTO_BOOT_KEYBOARD) + return 0; /* found it */ + + return EINVAL; +} + +static int +init_keyboard(ukbd_state_t *state, int *type, int flags) +{ + usb_endpoint_descriptor_t *ed; + + *type = KB_OTHER; + + state->ks_ifstate |= DISCONNECTED; + + ed = usbd_interface2endpoint_descriptor(state->ks_iface, 0); + if (!ed) { + printf("ukbd: could not read endpoint descriptor\n"); + return EIO; + } + + DPRINTFN(10,("ukbd:init_keyboard: \ +bLength=%d bDescriptorType=%d bEndpointAddress=%d-%s bmAttributes=%d wMaxPacketSize=%d bInterval=%d\n", + ed->bLength, ed->bDescriptorType, + UE_GET_ADDR(ed->bEndpointAddress), + UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN ? "in":"out", + UE_GET_XFERTYPE(ed->bmAttributes), + UGETW(ed->wMaxPacketSize), ed->bInterval)); + + if (UE_GET_DIR(ed->bEndpointAddress) != UE_DIR_IN || + UE_GET_XFERTYPE(ed->bmAttributes) != UE_INTERRUPT) { + printf("ukbd: unexpected endpoint\n"); + return EINVAL; + } + + /* Ignore if SETIDLE fails since it is not crucial. */ + usbd_set_idle(state->ks_iface, 0, 0); + + state->ks_ep_addr = ed->bEndpointAddress; + state->ks_ifstate &= ~DISCONNECTED; + + return 0; +} + +static void +set_leds(ukbd_state_t *state, int leds) +{ + + DPRINTF(("ukbd:set_leds: state=%p leds=%d\n", state, leds)); + state->ks_leds = leds; + usbd_set_report_async(state->ks_iface, UHID_OUTPUT_REPORT, 0, + &state->ks_leds, 1); +} + +static int +set_typematic(keyboard_t *kbd, int code) +{ + static int delays[] = { 250, 500, 750, 1000 }; + static int rates[] = { 34, 38, 42, 46, 50, 55, 59, 63, + 68, 76, 84, 92, 100, 110, 118, 126, + 136, 152, 168, 184, 200, 220, 236, 252, + 272, 304, 336, 368, 400, 440, 472, 504 }; + + if (code & ~0x7f) + return EINVAL; + kbd->kb_delay1 = delays[(code >> 5) & 3]; + kbd->kb_delay2 = rates[code & 0x1f]; + return 0; +} + +#ifdef UKBD_EMULATE_ATSCANCODE +static int +keycode2scancode(int keycode, int shift, int up) +{ + static int scan[] = { + 0x1c, 0x1d, 0x35, + 0x37 | SCAN_PREFIX_SHIFT, /* PrintScreen */ + 0x38, 0x47, 0x48, 0x49, 0x4b, 0x4d, 0x4f, + 0x50, 0x51, 0x52, 0x53, + 0x46, /* XXX Pause/Break */ + 0x5b, 0x5c, 0x5d, + /* SUN TYPE 6 USB KEYBOARD */ + 0x68, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, + 0x64, 0x65, 0x66, 0x67, 0x25, 0x1f, 0x1e, + 0x20, + }; + int scancode; + + scancode = keycode; + if ((keycode >= 89) && (keycode < 89 + sizeof(scan)/sizeof(scan[0]))) + scancode = scan[keycode - 89] | SCAN_PREFIX_E0; + /* Pause/Break */ + if ((keycode == 104) && !(shift & (MOD_CONTROL_L | MOD_CONTROL_R))) + scancode = 0x45 | SCAN_PREFIX_E1 | SCAN_PREFIX_CTL; + if (shift & (MOD_SHIFT_L | MOD_SHIFT_R)) + scancode &= ~SCAN_PREFIX_SHIFT; + return (scancode | (up ? SCAN_RELEASE : SCAN_PRESS)); +} +#endif /* UKBD_EMULATE_ATSCANCODE */ + +static int +ukbd_driver_load(module_t mod, int what, void *arg) +{ + switch (what) { + case MOD_LOAD: + kbd_add_driver(&ukbd_kbd_driver); + break; + case MOD_UNLOAD: + kbd_delete_driver(&ukbd_kbd_driver); + break; + } + return usbd_driver_load(mod, what, arg); +} diff --git a/sys/legacy/dev/usb/ulpt.c b/sys/legacy/dev/usb/ulpt.c new file mode 100644 index 0000000..99a1433 --- /dev/null +++ b/sys/legacy/dev/usb/ulpt.c @@ -0,0 +1,815 @@ +/* $NetBSD: ulpt.c,v 1.60 2003/10/04 21:19:50 augustss Exp $ */ + +/*- + * Copyright (c) 1998, 2003 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * Printer Class spec: http://www.usb.org/developers/data/devclass/usbprint109.PDF + */ + +/* XXXimp: need to migrate from devclass_get_softc */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/proc.h> +#include <sys/kernel.h> +#include <sys/fcntl.h> +#include <sys/ioccom.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/uio.h> +#include <sys/conf.h> +#include <sys/syslog.h> +#include <sys/sysctl.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include "usbdevs.h" +#include <dev/usb/usb_quirks.h> + +#define TIMEOUT hz*16 /* wait up to 16 seconds for a ready */ +#define STEP hz/4 + +#define LPTPRI (PZERO+8) +#define ULPT_BSIZE PAGE_SIZE + +#define ULPT_READS_PER_SEC 5 +#define ULPT_READ_TIMO 10 + +#ifdef USB_DEBUG +#define DPRINTF(x) if (ulptdebug) printf x +#define DPRINTFN(n,x) if (ulptdebug>(n)) printf x +int ulptdebug = 0; +SYSCTL_NODE(_hw_usb, OID_AUTO, ulpt, CTLFLAG_RW, 0, "USB ulpt"); +SYSCTL_INT(_hw_usb_ulpt, OID_AUTO, debug, CTLFLAG_RW, + &ulptdebug, 0, "ulpt debug level"); +#else +#define DPRINTF(x) +#define DPRINTFN(n,x) +#endif + +#define UR_GET_DEVICE_ID 0 +#define UR_GET_PORT_STATUS 1 +#define UR_SOFT_RESET 2 + +#define LPS_NERR 0x08 /* printer no error */ +#define LPS_SELECT 0x10 /* printer selected */ +#define LPS_NOPAPER 0x20 /* printer out of paper */ +#define LPS_INVERT (LPS_SELECT|LPS_NERR) +#define LPS_MASK (LPS_SELECT|LPS_NERR|LPS_NOPAPER) + +struct ulpt_softc { + device_t sc_dev; + usbd_device_handle sc_udev; /* device */ + usbd_interface_handle sc_iface; /* interface */ + int sc_ifaceno; + + int sc_out; + usbd_pipe_handle sc_out_pipe; /* bulk out pipe */ + usbd_xfer_handle sc_out_xfer; + void *sc_out_buf; + + int sc_in; + usbd_pipe_handle sc_in_pipe; /* bulk in pipe */ + usbd_xfer_handle sc_in_xfer; + void *sc_in_buf; + + struct callout sc_read_callout; + int sc_has_callout; + + u_char sc_state; +#define ULPT_OPEN 0x01 /* device is open */ +#define ULPT_OBUSY 0x02 /* printer is busy doing output */ +#define ULPT_INIT 0x04 /* waiting to initialize for open */ + u_char sc_flags; +#define ULPT_NOPRIME 0x40 /* don't prime on open */ + u_char sc_laststatus; + + int sc_refcnt; + u_char sc_dying; + + struct cdev *dev; + struct cdev *dev_noprime; +}; + +static d_open_t ulptopen; +static d_close_t ulptclose; +static d_write_t ulptwrite; +static d_read_t ulptread; +static d_ioctl_t ulptioctl; + + +static struct cdevsw ulpt_cdevsw = { + .d_version = D_VERSION, + .d_flags = D_NEEDGIANT, + .d_open = ulptopen, + .d_close = ulptclose, + .d_write = ulptwrite, + .d_read = ulptread, + .d_ioctl = ulptioctl, + .d_name = "ulpt", +}; + +void ulpt_disco(void *); + +int ulpt_do_write(struct ulpt_softc *, struct uio *uio, int); +int ulpt_do_read(struct ulpt_softc *, struct uio *uio, int); +int ulpt_status(struct ulpt_softc *); +void ulpt_reset(struct ulpt_softc *); +int ulpt_statusmsg(u_char, struct ulpt_softc *); +void ulpt_read_cb(usbd_xfer_handle xfer, usbd_private_handle priv, + usbd_status status); +void ulpt_tick(void *xsc); + +#if 0 +void ieee1284_print_id(char *); +#endif + +#define ULPTUNIT(s) (dev2unit(s) & 0x1f) +#define ULPTFLAGS(s) (dev2unit(s) & 0xe0) + +static device_probe_t ulpt_match; +static device_attach_t ulpt_attach; +static device_detach_t ulpt_detach; + +static device_method_t ulpt_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ulpt_match), + DEVMETHOD(device_attach, ulpt_attach), + DEVMETHOD(device_detach, ulpt_detach), + + { 0, 0 } +}; + +static driver_t ulpt_driver = { + "ulpt", + ulpt_methods, + sizeof(struct ulpt_softc) +}; + +static devclass_t ulpt_devclass; + +MODULE_DEPEND(umass, usb, 1, 1, 1); +DRIVER_MODULE(ulpt, uhub, ulpt_driver, ulpt_devclass, usbd_driver_load, 0); + +static int +ulpt_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + usb_interface_descriptor_t *id; + + DPRINTFN(10,("ulpt_match\n")); + if (uaa->iface == NULL) + return (UMATCH_NONE); + id = usbd_get_interface_descriptor(uaa->iface); + if (id != NULL && + id->bInterfaceClass == UICLASS_PRINTER && + id->bInterfaceSubClass == UISUBCLASS_PRINTER && + (id->bInterfaceProtocol == UIPROTO_PRINTER_UNI || + id->bInterfaceProtocol == UIPROTO_PRINTER_BI || + id->bInterfaceProtocol == UIPROTO_PRINTER_1284)) + return (UMATCH_IFACECLASS_IFACESUBCLASS_IFACEPROTO); + return (UMATCH_NONE); +} + +static int +ulpt_attach(device_t self) +{ + struct ulpt_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + usbd_device_handle dev = uaa->device; + usbd_interface_handle iface = uaa->iface; + usb_interface_descriptor_t *ifcd = usbd_get_interface_descriptor(iface); + usb_interface_descriptor_t *id, *iend; + usb_config_descriptor_t *cdesc; + usbd_status err; + usb_endpoint_descriptor_t *ed; + u_int8_t epcount; + int i, altno; + + DPRINTFN(10,("ulpt_attach: sc=%p\n", sc)); + sc->sc_dev = self; + + /* XXX + * Stepping through the alternate settings needs to be abstracted out. + */ + cdesc = usbd_get_config_descriptor(dev); + if (cdesc == NULL) { + printf("%s: failed to get configuration descriptor\n", + device_get_nameunit(sc->sc_dev)); + return ENXIO; + } + iend = (usb_interface_descriptor_t *) + ((char *)cdesc + UGETW(cdesc->wTotalLength)); +#ifdef DIAGNOSTIC + if (ifcd < (usb_interface_descriptor_t *)cdesc || + ifcd >= iend) + panic("ulpt: iface desc out of range"); +#endif + /* Step through all the descriptors looking for bidir mode */ + for (id = ifcd, altno = 0; + id < iend; + id = (void *)((char *)id + id->bLength)) { + if (id->bDescriptorType == UDESC_INTERFACE && + id->bInterfaceNumber == ifcd->bInterfaceNumber) { + if (id->bInterfaceClass == UICLASS_PRINTER && + id->bInterfaceSubClass == UISUBCLASS_PRINTER && + (id->bInterfaceProtocol == UIPROTO_PRINTER_BI /* || + id->bInterfaceProtocol == UIPROTO_PRINTER_1284 */)) + goto found; + altno++; + } + } + id = ifcd; /* not found, use original */ + found: + if (id != ifcd) { + /* Found a new bidir setting */ + DPRINTF(("ulpt_attach: set altno = %d\n", altno)); + err = usbd_set_interface(iface, altno); + if (err) { + printf("%s: setting alternate interface failed\n", + device_get_nameunit(sc->sc_dev)); + sc->sc_dying = 1; + return ENXIO; + } + } + + epcount = 0; + (void)usbd_endpoint_count(iface, &epcount); + + sc->sc_in = -1; + sc->sc_out = -1; + for (i = 0; i < epcount; i++) { + ed = usbd_interface2endpoint_descriptor(iface, i); + if (ed == NULL) { + printf("%s: couldn't get ep %d\n", + device_get_nameunit(sc->sc_dev), i); + return ENXIO; + } + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + sc->sc_in = ed->bEndpointAddress; + } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + sc->sc_out = ed->bEndpointAddress; + } + } + if (sc->sc_out == -1) { + printf("%s: could not find bulk out endpoint\n", + device_get_nameunit(sc->sc_dev)); + sc->sc_dying = 1; + return ENXIO; + } + + if (usbd_get_quirks(dev)->uq_flags & UQ_BROKEN_BIDIR) { + /* This device doesn't handle reading properly. */ + sc->sc_in = -1; + } + + printf("%s: using %s-directional mode\n", device_get_nameunit(sc->sc_dev), + sc->sc_in >= 0 ? "bi" : "uni"); + + DPRINTFN(10, ("ulpt_attach: bulk=%d\n", sc->sc_out)); + + sc->sc_iface = iface; + sc->sc_ifaceno = id->bInterfaceNumber; + sc->sc_udev = dev; + +#if 0 +/* + * This code is disabled because for some mysterious reason it causes + * printing not to work. But only sometimes, and mostly with + * UHCI and less often with OHCI. *sigh* + */ + { + usb_config_descriptor_t *cd = usbd_get_config_descriptor(dev); + usb_device_request_t req; + int len, alen; + + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = UR_GET_DEVICE_ID; + USETW(req.wValue, cd->bConfigurationValue); + USETW2(req.wIndex, id->bInterfaceNumber, id->bAlternateSetting); + USETW(req.wLength, sizeof devinfo - 1); + err = usbd_do_request_flags(dev, &req, devinfo, USBD_SHORT_XFER_OK, + &alen, USBD_DEFAULT_TIMEOUT); + if (err) { + printf("%s: cannot get device id\n", device_get_nameunit(sc->sc_dev)); + } else if (alen <= 2) { + printf("%s: empty device id, no printer connected?\n", + device_get_nameunit(sc->sc_dev)); + } else { + /* devinfo now contains an IEEE-1284 device ID */ + len = ((devinfo[0] & 0xff) << 8) | (devinfo[1] & 0xff); + if (len > sizeof devinfo - 3) + len = sizeof devinfo - 3; + devinfo[len] = 0; + printf("%s: device id <", device_get_nameunit(sc->sc_dev)); + ieee1284_print_id(devinfo+2); + printf(">\n"); + } + } +#endif + + sc->dev = make_dev(&ulpt_cdevsw, device_get_unit(self), + UID_ROOT, GID_OPERATOR, 0644, "ulpt%d", device_get_unit(self)); + sc->dev_noprime = make_dev(&ulpt_cdevsw, + device_get_unit(self)|ULPT_NOPRIME, + UID_ROOT, GID_OPERATOR, 0644, "unlpt%d", device_get_unit(self)); + + usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, sc->sc_udev, sc->sc_dev); + + return 0; +} + + +static int +ulpt_detach(device_t self) +{ + struct ulpt_softc *sc = device_get_softc(self); + int s; + + DPRINTF(("ulpt_detach: sc=%p\n", sc)); + + sc->sc_dying = 1; + if (sc->sc_out_pipe != NULL) + usbd_abort_pipe(sc->sc_out_pipe); + if (sc->sc_in_pipe != NULL) + usbd_abort_pipe(sc->sc_in_pipe); + + s = splusb(); + if (--sc->sc_refcnt >= 0) { + /* There is noone to wake, aborting the pipe is enough */ + /* Wait for processes to go away. */ + usb_detach_wait(sc->sc_dev); + } + splx(s); + + destroy_dev(sc->dev); + destroy_dev(sc->dev_noprime); + + usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_udev, sc->sc_dev); + + return (0); +} + +int +ulpt_status(struct ulpt_softc *sc) +{ + usb_device_request_t req; + usbd_status err; + u_char status; + + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = UR_GET_PORT_STATUS; + USETW(req.wValue, 0); + USETW(req.wIndex, sc->sc_ifaceno); + USETW(req.wLength, 1); + err = usbd_do_request(sc->sc_udev, &req, &status); + DPRINTFN(1, ("ulpt_status: status=0x%02x err=%d\n", status, err)); + if (!err) + return (status); + else + return (0); +} + +void +ulpt_reset(struct ulpt_softc *sc) +{ + usb_device_request_t req; + + DPRINTFN(1, ("ulpt_reset\n")); + req.bRequest = UR_SOFT_RESET; + USETW(req.wValue, 0); + USETW(req.wIndex, sc->sc_ifaceno); + USETW(req.wLength, 0); + + /* + * There was a mistake in the USB printer 1.0 spec that gave the + * request type as UT_WRITE_CLASS_OTHER; it should have been + * UT_WRITE_CLASS_INTERFACE. Many printers use the old one, + * so we try both. + */ + req.bmRequestType = UT_WRITE_CLASS_OTHER; + if (usbd_do_request(sc->sc_udev, &req, 0)) { /* 1.0 */ + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + (void)usbd_do_request(sc->sc_udev, &req, 0); /* 1.1 */ + } +} +#if 0 +static void +ulpt_input(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + struct ulpt_softc *sc = priv; + u_int32_t count; + + /* Don't loop on errors or 0-length input. */ + usbd_get_xfer_status(xfer, NULL, NULL, &count, NULL); + if (status != USBD_NORMAL_COMPLETION || count == 0) + return; + + DPRINTFN(2,("ulpt_input: got some data\n")); + /* Do it again. */ + if (xfer == sc->sc_in_xfer1) + usbd_transfer(sc->sc_in_xfer2); + else + usbd_transfer(sc->sc_in_xfer1); +} +#endif + +int ulptusein = 1; + +/* + * Reset the printer, then wait until it's selected and not busy. + */ +int +ulptopen(struct cdev *dev, int flag, int mode, struct thread *p) +{ + u_char flags = ULPTFLAGS(dev); + struct ulpt_softc *sc; + usbd_status err; + int error; + + sc = devclass_get_softc(ulpt_devclass, ULPTUNIT(dev)); + if (sc == NULL) + return (ENXIO); + + if (sc == NULL || sc->sc_iface == NULL || sc->sc_dying) + return (ENXIO); + + if (sc->sc_state) + return (EBUSY); + + sc->sc_state = ULPT_INIT; + sc->sc_flags = flags; + DPRINTF(("ulptopen: flags=0x%x\n", (unsigned)flags)); + +#if defined(USB_DEBUG) + /* Ignoring these flags might not be a good idea */ + if ((flags & ~ULPT_NOPRIME) != 0) + printf("ulptopen: flags ignored: %b\n", flags, + "\20\3POS_INIT\4POS_ACK\6PRIME_OPEN\7AUTOLF\10BYPASS"); +#endif + + + error = 0; + sc->sc_refcnt++; + + if ((flags & ULPT_NOPRIME) == 0) { + ulpt_reset(sc); + if (sc->sc_dying) { + error = ENXIO; + sc->sc_state = 0; + goto done; + } + } + + err = usbd_open_pipe(sc->sc_iface, sc->sc_out, 0, &sc->sc_out_pipe); + if (err) { + error = EIO; + goto err0; + } + sc->sc_out_xfer = usbd_alloc_xfer(sc->sc_udev); + if (sc->sc_out_xfer == NULL) { + error = ENOMEM; + goto err1; + } + sc->sc_out_buf = usbd_alloc_buffer(sc->sc_out_xfer, ULPT_BSIZE); + if (sc->sc_out_buf == NULL) { + error = ENOMEM; + goto err2; + } + + if (ulptusein && sc->sc_in != -1) { + DPRINTF(("ulpt_open: open input pipe\n")); + err = usbd_open_pipe(sc->sc_iface, sc->sc_in,0,&sc->sc_in_pipe); + if (err) { + error = EIO; + goto err2; + } + sc->sc_in_xfer = usbd_alloc_xfer(sc->sc_udev); + if (sc->sc_in_xfer == NULL) { + error = ENOMEM; + goto err3; + } + sc->sc_in_buf = usbd_alloc_buffer(sc->sc_in_xfer, ULPT_BSIZE); + if (sc->sc_in_buf == NULL) { + error = ENOMEM; + goto err4; + } + + /* If it's not opened for read the set up a reader. */ + if (!(flag & FREAD)) { + DPRINTF(("ulpt_open: start read callout\n")); + callout_init(&sc->sc_read_callout, 0); + callout_reset(&sc->sc_read_callout, hz/5, ulpt_tick, + sc); + sc->sc_has_callout = 1; + } + } + + sc->sc_state = ULPT_OPEN; + goto done; + + err4: + usbd_free_xfer(sc->sc_in_xfer); + sc->sc_in_xfer = NULL; + err3: + usbd_close_pipe(sc->sc_in_pipe); + sc->sc_in_pipe = NULL; + err2: + usbd_free_xfer(sc->sc_out_xfer); + sc->sc_out_xfer = NULL; + err1: + usbd_close_pipe(sc->sc_out_pipe); + sc->sc_out_pipe = NULL; + err0: + sc->sc_state = 0; + + done: + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); + + DPRINTF(("ulptopen: done, error=%d\n", error)); + return (error); +} + +int +ulpt_statusmsg(u_char status, struct ulpt_softc *sc) +{ + u_char new; + + status = (status ^ LPS_INVERT) & LPS_MASK; + new = status & ~sc->sc_laststatus; + sc->sc_laststatus = status; + + if (new & LPS_SELECT) + log(LOG_NOTICE, "%s: offline\n", device_get_nameunit(sc->sc_dev)); + else if (new & LPS_NOPAPER) + log(LOG_NOTICE, "%s: out of paper\n", device_get_nameunit(sc->sc_dev)); + else if (new & LPS_NERR) + log(LOG_NOTICE, "%s: output error\n", device_get_nameunit(sc->sc_dev)); + + return (status); +} + +int +ulptclose(struct cdev *dev, int flag, int mode, struct thread *p) +{ + struct ulpt_softc *sc; + + sc = devclass_get_softc(ulpt_devclass, ULPTUNIT(dev)); + + if (sc->sc_state != ULPT_OPEN) + /* We are being forced to close before the open completed. */ + return (0); + + if (sc->sc_has_callout) { + callout_stop(&sc->sc_read_callout); + sc->sc_has_callout = 0; + } + + if (sc->sc_out_pipe != NULL) { + usbd_abort_pipe(sc->sc_out_pipe); + usbd_close_pipe(sc->sc_out_pipe); + sc->sc_out_pipe = NULL; + } + if (sc->sc_out_xfer != NULL) { + usbd_free_xfer(sc->sc_out_xfer); + sc->sc_out_xfer = NULL; + } + + if (sc->sc_in_pipe != NULL) { + usbd_abort_pipe(sc->sc_in_pipe); + usbd_close_pipe(sc->sc_in_pipe); + sc->sc_in_pipe = NULL; + } + if (sc->sc_in_xfer != NULL) { + usbd_free_xfer(sc->sc_in_xfer); + sc->sc_in_xfer = NULL; + } + + sc->sc_state = 0; + + DPRINTF(("ulptclose: closed\n")); + return (0); +} + +int +ulpt_do_write(struct ulpt_softc *sc, struct uio *uio, int flags) +{ + u_int32_t n; + int error = 0; + void *bufp; + usbd_xfer_handle xfer; + usbd_status err; + + DPRINTF(("ulptwrite\n")); + xfer = sc->sc_out_xfer; + bufp = sc->sc_out_buf; + while ((n = min(ULPT_BSIZE, uio->uio_resid)) != 0) { + ulpt_statusmsg(ulpt_status(sc), sc); + error = uiomove(bufp, n, uio); + if (error) + break; + DPRINTFN(1, ("ulptwrite: transfer %d bytes\n", n)); + err = usbd_bulk_transfer(xfer, sc->sc_out_pipe, USBD_NO_COPY, + USBD_NO_TIMEOUT, bufp, &n, "ulptwr"); + if (err) { + DPRINTF(("ulptwrite: error=%d\n", err)); + error = EIO; + break; + } + } + + return (error); +} + +int +ulptwrite(struct cdev *dev, struct uio *uio, int flags) +{ + struct ulpt_softc *sc; + int error; + + sc = devclass_get_softc(ulpt_devclass, ULPTUNIT(dev)); + + if (sc->sc_dying) + return (EIO); + + sc->sc_refcnt++; + error = ulpt_do_write(sc, uio, flags); + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); + return (error); +} + +int +ulpt_do_read(struct ulpt_softc *sc, struct uio *uio, int flags) +{ + u_int32_t n, on; + int error = 0; + void *bufp; + usbd_xfer_handle xfer; + usbd_status err; + + DPRINTF(("ulptread\n")); + + if (sc->sc_in_pipe == NULL) + return 0; + + xfer = sc->sc_in_xfer; + bufp = sc->sc_in_buf; + while ((n = min(ULPT_BSIZE, uio->uio_resid)) != 0) { + DPRINTFN(1, ("ulptread: transfer %d bytes\n", n)); + on = n; + err = usbd_bulk_transfer(xfer, sc->sc_in_pipe, + USBD_NO_COPY | USBD_SHORT_XFER_OK, + USBD_NO_TIMEOUT, bufp, &n, "ulptrd"); + if (err) { + DPRINTF(("ulptread: error=%d\n", err)); + error = EIO; + break; + } + error = uiomove(bufp, n, uio); + if (error) + break; + if (on != n) + break; + } + + return (error); +} + +int +ulptread(struct cdev *dev, struct uio *uio, int flags) +{ + struct ulpt_softc *sc; + int error; + + sc = devclass_get_softc(ulpt_devclass, ULPTUNIT(dev)); + + if (sc->sc_dying) + return (EIO); + + sc->sc_refcnt++; + error = ulpt_do_read(sc, uio, flags); + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); + return (error); +} + +void +ulpt_read_cb(usbd_xfer_handle xfer, usbd_private_handle priv, + usbd_status status) +{ + usbd_status err; + u_int32_t n; + usbd_private_handle xsc; + struct ulpt_softc *sc; + + usbd_get_xfer_status(xfer, &xsc, NULL, &n, &err); + sc = xsc; + + DPRINTFN(1,("ulpt_read_cb: start sc=%p, err=%d n=%d\n", sc, err, n)); + +#ifdef ULPT_DEBUG + if (!err && n > 0) + DPRINTF(("ulpt_tick: discarding %d bytes\n", n)); +#endif + if (!err || err == USBD_TIMEOUT) + callout_reset(&sc->sc_read_callout, hz / ULPT_READS_PER_SEC, + ulpt_tick, sc); +} + +void +ulpt_tick(void *xsc) +{ + struct ulpt_softc *sc = xsc; + usbd_status err; + + if (sc == NULL || sc->sc_dying) + return; + + DPRINTFN(1,("ulpt_tick: start sc=%p\n", sc)); + + usbd_setup_xfer(sc->sc_in_xfer, sc->sc_in_pipe, sc, sc->sc_in_buf, + ULPT_BSIZE, USBD_NO_COPY | USBD_SHORT_XFER_OK, + ULPT_READ_TIMO, ulpt_read_cb); + err = usbd_transfer(sc->sc_in_xfer); + DPRINTFN(1,("ulpt_tick: err=%d\n", err)); +} + +int +ulptioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *p) +{ + int error = 0; + + switch (cmd) { + default: + error = ENODEV; + } + + return (error); +} + +#if 0 +/* XXX This does not belong here. */ +/* + * Print select parts of an IEEE 1284 device ID. + */ +void +ieee1284_print_id(char *str) +{ + char *p, *q; + + for (p = str-1; p; p = strchr(p, ';')) { + p++; /* skip ';' */ + if (strncmp(p, "MFG:", 4) == 0 || + strncmp(p, "MANUFACTURER:", 14) == 0 || + strncmp(p, "MDL:", 4) == 0 || + strncmp(p, "MODEL:", 6) == 0) { + q = strchr(p, ';'); + if (q) + printf("%.*s", (int)(q - p + 1), p); + } + } +} +#endif diff --git a/sys/legacy/dev/usb/umass.c b/sys/legacy/dev/usb/umass.c new file mode 100644 index 0000000..b0b73cc --- /dev/null +++ b/sys/legacy/dev/usb/umass.c @@ -0,0 +1,3611 @@ +/*- + * 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->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_*_state. + * The state machine is started through either umass_*_transfer 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. + */ + +/* + * XXX Currently CBI with CCI is not supported because it bombs the system + * when the device is detached (low frequency interrupts are detached + * too late. + */ +#undef CBI_I + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/bus.h> +#include <sys/sysctl.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include "usbdevs.h" + +#include <cam/cam.h> +#include <cam/cam_ccb.h> +#include <cam/cam_sim.h> +#include <cam/cam_xpt_sim.h> +#include <cam/scsi/scsi_all.h> +#include <cam/scsi/scsi_da.h> + +#include <cam/cam_periph.h> + +#ifdef USB_DEBUG +#define DIF(m, x) if (umassdebug & (m)) do { x ; } while (0) +#define DPRINTF(m, x) if (umassdebug & (m)) printf x +#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 */ +int umassdebug = 0; +SYSCTL_NODE(_hw_usb, OID_AUTO, umass, CTLFLAG_RW, 0, "USB umass"); +SYSCTL_INT(_hw_usb_umass, OID_AUTO, debug, CTLFLAG_RW, + &umassdebug, 0, "umass debug level"); +#else +#define DIF(m, x) /* nop */ +#define DPRINTF(m, x) /* nop */ +#endif + + +/* Generic definitions */ + +/* Direction for umass_*_transfer */ +#define DIR_NONE 0 +#define DIR_IN 1 +#define DIR_OUT 2 + +/* device name */ +#define DEVNAME "umass" +#define DEVNAME_SIM "umass-sim" + +#define UMASS_MAX_TRANSFER_SIZE 65536 +/* Approximate maximum transfer speeds (assumes 33% overhead). */ +#define UMASS_FULL_TRANSFER_SPEED 1000 +#define UMASS_HIGH_TRANSFER_SPEED 40000 +#define UMASS_FLOPPY_TRANSFER_SPEED 20 + +#define UMASS_TIMEOUT 5000 /* msecs */ + +/* CAM specific definitions */ + +#define UMASS_SCSIID_MAX 1 /* maximum number of drives expected */ +#define UMASS_SCSIID_HOST UMASS_SCSIID_MAX + +#define MS_TO_TICKS(ms) ((ms) * hz / 1000) + + +/* 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 unsigned char umass_cbi_cbl_t[16]; /* Command block */ + +typedef union { + struct { + unsigned char type; + #define IDB_TYPE_CCI 0x00 + unsigned char 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 + } common; + + struct { + unsigned char asc; + unsigned char ascq; + } ufi; +} umass_cbi_sbl_t; + + + +struct umass_softc; /* see below */ + +typedef void (*transfer_cb_f) (struct umass_softc *sc, void *priv, + int residue, int 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 void (*wire_reset_f) (struct umass_softc *sc, int status); +typedef void (*wire_transfer_f) (struct umass_softc *sc, int lun, + void *cmd, int cmdlen, void *data, int datalen, + int dir, u_int timeout, transfer_cb_f cb, void *priv); +typedef void (*wire_state_f) (usbd_xfer_handle xfer, + usbd_private_handle priv, usbd_status err); + +typedef int (*command_transform_f) (struct umass_softc *sc, + unsigned char *cmd, int cmdlen, + unsigned char **rcmd, int *rcmdlen); + + +struct umass_devdescr_t { + u_int32_t vid; +# define VID_WILDCARD 0xffffffff +# define VID_EOT 0xfffffffe + u_int32_t pid; +# define PID_WILDCARD 0xffffffff +# define PID_EOT 0xfffffffe + u_int32_t rid; +# define RID_WILDCARD 0xffffffff +# define RID_EOT 0xfffffffe + + /* wire and command protocol */ + u_int16_t proto; +# 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 */ + u_int16_t 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 +}; + +static struct umass_devdescr_t umass_devdescrs[] = { + { USB_VENDOR_ADDONICS2, USB_PRODUCT_ADDONICS2_CABLE_205, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + { USB_VENDOR_AIPTEK, USB_PRODUCT_AIPTEK_POCKETCAM3M, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + { USB_VENDOR_ALCOR, USB_PRODUCT_ALCOR_UMCR_9361, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_GETMAXLUN + }, + { USB_VENDOR_ASAHIOPTICAL, USB_PRODUCT_ASAHIOPTICAL_OPTIO230, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_INQUIRY + }, + { USB_VENDOR_ASAHIOPTICAL, USB_PRODUCT_ASAHIOPTICAL_OPTIO330, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_INQUIRY + }, + { USB_VENDOR_ASAHIOPTICAL, PID_WILDCARD, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_CBI_I, + RS_NO_CLEAR_UA + }, + { USB_VENDOR_ADDON, USB_PRODUCT_ADDON_ATTACHE, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + IGNORE_RESIDUE + }, + { USB_VENDOR_ADDON, USB_PRODUCT_ADDON_A256MB, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + IGNORE_RESIDUE + }, + { USB_VENDOR_ADDON, USB_PRODUCT_ADDON_DISKPRO512, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + IGNORE_RESIDUE + }, + { USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_USB2SCSI, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + { USB_VENDOR_CASIO, USB_PRODUCT_CASIO_QV_DIGICAM, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_CBI, + NO_INQUIRY + }, + { USB_VENDOR_CCYU, USB_PRODUCT_CCYU_ED1064, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + { USB_VENDOR_CENTURY, USB_PRODUCT_CENTURY_EX35QUAT, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + FORCE_SHORT_INQUIRY | NO_START_STOP | IGNORE_RESIDUE + }, + { USB_VENDOR_DESKNOTE, USB_PRODUCT_DESKNOTE_UCR_61S2B, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + { USB_VENDOR_DMI, USB_PRODUCT_DMI_CFSM_RW, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_GETMAXLUN + }, + { USB_VENDOR_EPSON, USB_PRODUCT_EPSON_STYLUS_875DC, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_CBI, + NO_INQUIRY + }, + { USB_VENDOR_EPSON, USB_PRODUCT_EPSON_STYLUS_895, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_GETMAXLUN + }, + { USB_VENDOR_FEIYA, USB_PRODUCT_FEIYA_5IN1, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + { USB_VENDOR_FREECOM, USB_PRODUCT_FREECOM_DVD, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_QUIRKS + }, + { USB_VENDOR_FUJIPHOTO, USB_PRODUCT_FUJIPHOTO_MASS0100, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_CBI_I, + RS_NO_CLEAR_UA + }, + { USB_VENDOR_GENESYS, USB_PRODUCT_GENESYS_GL641USB2IDE, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + FORCE_SHORT_INQUIRY | NO_START_STOP | IGNORE_RESIDUE + }, + { USB_VENDOR_GENESYS, USB_PRODUCT_GENESYS_GL641USB2IDE_2, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_BBB, + FORCE_SHORT_INQUIRY | NO_START_STOP | IGNORE_RESIDUE + }, + { USB_VENDOR_GENESYS, USB_PRODUCT_GENESYS_GL641USB, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + FORCE_SHORT_INQUIRY | NO_START_STOP | IGNORE_RESIDUE + }, + { USB_VENDOR_GENESYS, USB_PRODUCT_GENESYS_GL641USB_2, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + WRONG_CSWSIG + }, + { USB_VENDOR_HAGIWARA, USB_PRODUCT_HAGIWARA_FG, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + { USB_VENDOR_HAGIWARA, USB_PRODUCT_HAGIWARA_FGSM, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + { USB_VENDOR_HITACHI, USB_PRODUCT_HITACHI_DVDCAM_USB, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_CBI_I, + NO_INQUIRY + }, + { USB_VENDOR_HITACHI, USB_PRODUCT_HITACHI_DVDCAM_DZ_MV100A, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_CBI, + NO_GETMAXLUN + }, + { USB_VENDOR_HP, USB_PRODUCT_HP_CDW4E, RID_WILDCARD, + UMASS_PROTO_ATAPI, + NO_QUIRKS + }, + { USB_VENDOR_HP, USB_PRODUCT_HP_CDW8200, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_CBI_I, + NO_TEST_UNIT_READY | NO_START_STOP + }, + { USB_VENDOR_IMAGINATION, USB_PRODUCT_IMAGINATION_DBX1, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + WRONG_CSWSIG + }, + { USB_VENDOR_INSYSTEM, USB_PRODUCT_INSYSTEM_ATAPI, RID_WILDCARD, + UMASS_PROTO_RBC | UMASS_PROTO_CBI, + NO_QUIRKS + }, + { USB_VENDOR_INSYSTEM, USB_PRODUCT_INSYSTEM_STORAGE_V2, RID_WILDCARD, + UMASS_PROTO_RBC | UMASS_PROTO_CBI, + NO_QUIRKS + }, + { USB_VENDOR_INSYSTEM, USB_PRODUCT_INSYSTEM_USBCABLE, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_CBI, + NO_TEST_UNIT_READY | NO_START_STOP | ALT_IFACE_1 + }, + { USB_VENDOR_IODATA, USB_PRODUCT_IODATA_IU_CD2, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + { USB_VENDOR_IODATA, USB_PRODUCT_IODATA_DVR_UEH8, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + { USB_VENDOR_IOMEGA, USB_PRODUCT_IOMEGA_ZIP100, RID_WILDCARD, + /* XXX This is not correct as there are Zip drives that use ATAPI. + */ + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_TEST_UNIT_READY + }, + { USB_VENDOR_KYOCERA, USB_PRODUCT_KYOCERA_FINECAM_L3, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_INQUIRY + }, + { USB_VENDOR_KYOCERA, USB_PRODUCT_KYOCERA_FINECAM_S3X, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_CBI, + NO_INQUIRY + }, + { USB_VENDOR_KYOCERA, USB_PRODUCT_KYOCERA_FINECAM_S4, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_CBI, + NO_INQUIRY + }, + { USB_VENDOR_KYOCERA, USB_PRODUCT_KYOCERA_FINECAM_S5, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_INQUIRY + }, + { USB_VENDOR_LACIE, USB_PRODUCT_LACIE_HD, RID_WILDCARD, + UMASS_PROTO_RBC | UMASS_PROTO_CBI, + NO_QUIRKS + }, + { USB_VENDOR_LEXAR, USB_PRODUCT_LEXAR_CF_READER, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_INQUIRY + }, + { USB_VENDOR_LEXAR, USB_PRODUCT_LEXAR_JUMPSHOT, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_QUIRKS + }, + { USB_VENDOR_LOGITEC, USB_PRODUCT_LOGITEC_LDR_H443SU2, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_QUIRKS + }, + { USB_VENDOR_LOGITEC, USB_PRODUCT_LOGITEC_LDR_H443U2, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + { USB_VENDOR_MELCO, USB_PRODUCT_MELCO_DUBPXXG, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + FORCE_SHORT_INQUIRY | NO_START_STOP | IGNORE_RESIDUE + }, + { USB_VENDOR_MICROTECH, USB_PRODUCT_MICROTECH_DPCM, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_CBI, + NO_TEST_UNIT_READY | NO_START_STOP + }, + { USB_VENDOR_MICROTECH, USB_PRODUCT_MICROTECH_SCSIDB25, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + { USB_VENDOR_MICROTECH, USB_PRODUCT_MICROTECH_SCSIHD50, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + { USB_VENDOR_MINOLTA, USB_PRODUCT_MINOLTA_E223, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_QUIRKS + }, + { USB_VENDOR_MINOLTA, USB_PRODUCT_MINOLTA_F300, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + { USB_VENDOR_MITSUMI, USB_PRODUCT_MITSUMI_CDRRW, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_CBI, + NO_QUIRKS + }, + { USB_VENDOR_MITSUMI, USB_PRODUCT_MITSUMI_FDD, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_GETMAXLUN + }, + { USB_VENDOR_MOTOROLA2, USB_PRODUCT_MOTOROLA2_E398, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + FORCE_SHORT_INQUIRY | NO_INQUIRY_EVPD | NO_GETMAXLUN + }, + { USB_VENDOR_MSYSTEMS, USB_PRODUCT_MSYSTEMS_DISKONKEY, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + IGNORE_RESIDUE | NO_GETMAXLUN | RS_NO_CLEAR_UA + }, + { USB_VENDOR_MSYSTEMS, USB_PRODUCT_MSYSTEMS_DISKONKEY2, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + { USB_VENDOR_MYSON, USB_PRODUCT_MYSON_HEDEN, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_INQUIRY | IGNORE_RESIDUE + }, + { USB_VENDOR_NEODIO, USB_PRODUCT_NEODIO_ND3260, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + FORCE_SHORT_INQUIRY + }, + { USB_VENDOR_NETAC, USB_PRODUCT_NETAC_CF_CARD, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_INQUIRY + }, + { USB_VENDOR_NETAC, USB_PRODUCT_NETAC_ONLYDISK, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + IGNORE_RESIDUE + }, + { USB_VENDOR_NETCHIP, USB_PRODUCT_NETCHIP_CLIK_40, RID_WILDCARD, + UMASS_PROTO_ATAPI, + NO_INQUIRY + }, + { USB_VENDOR_NIKON, USB_PRODUCT_NIKON_D300, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + { USB_VENDOR_OLYMPUS, USB_PRODUCT_OLYMPUS_C1, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + WRONG_CSWSIG + }, + { USB_VENDOR_OLYMPUS, USB_PRODUCT_OLYMPUS_C700, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_GETMAXLUN + }, + { USB_VENDOR_ONSPEC, USB_PRODUCT_ONSPEC_CFMS_RW, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_QUIRKS + }, + { USB_VENDOR_ONSPEC, USB_PRODUCT_ONSPEC_CFSM_COMBO, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_QUIRKS + }, + { USB_VENDOR_ONSPEC, USB_PRODUCT_ONSPEC_CFSM_READER, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_QUIRKS + }, + { USB_VENDOR_ONSPEC, USB_PRODUCT_ONSPEC_CFSM_READER2, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_QUIRKS + }, + { USB_VENDOR_ONSPEC, USB_PRODUCT_ONSPEC_SDS_HOTFIND_D, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_GETMAXLUN | NO_SYNCHRONIZE_CACHE + }, + { USB_VENDOR_ONSPEC, USB_PRODUCT_ONSPEC_MDCFE_B_CF_READER, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_QUIRKS + }, + { USB_VENDOR_ONSPEC, USB_PRODUCT_ONSPEC_MDSM_B_READER, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_INQUIRY + }, + { USB_VENDOR_ONSPEC, USB_PRODUCT_ONSPEC_READER, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_QUIRKS + }, + { USB_VENDOR_ONSPEC, USB_PRODUCT_ONSPEC_UCF100, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_BBB, + NO_INQUIRY | NO_GETMAXLUN + }, + { USB_VENDOR_ONSPEC2, USB_PRODUCT_ONSPEC2_IMAGEMATE_SDDR55, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_GETMAXLUN + }, + { USB_VENDOR_PANASONIC, USB_PRODUCT_PANASONIC_KXL840AN, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_BBB, + NO_GETMAXLUN + }, + { USB_VENDOR_PANASONIC, USB_PRODUCT_PANASONIC_KXLCB20AN, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + { USB_VENDOR_PANASONIC, USB_PRODUCT_PANASONIC_KXLCB35AN, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + { USB_VENDOR_PANASONIC, USB_PRODUCT_PANASONIC_LS120CAM, RID_WILDCARD, + UMASS_PROTO_UFI, + NO_QUIRKS + }, + { USB_VENDOR_PLEXTOR, USB_PRODUCT_PLEXTOR_40_12_40U, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_TEST_UNIT_READY + }, + { USB_VENDOR_PNY, USB_PRODUCT_PNY_ATTACHE2, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + IGNORE_RESIDUE | NO_START_STOP + }, + { USB_VENDOR_SAMSUNG, USB_PRODUCT_SAMSUNG_YP_U2, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + SHUTTLE_INIT | NO_GETMAXLUN + }, + { USB_VENDOR_SAMSUNG_TECHWIN, USB_PRODUCT_SAMSUNG_TECHWIN_DIGIMAX_410, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_INQUIRY + }, + { USB_VENDOR_SANDISK, USB_PRODUCT_SANDISK_SDDR05A, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_CBI, + READ_CAPACITY_OFFBY1 | NO_GETMAXLUN + }, + { USB_VENDOR_SANDISK, USB_PRODUCT_SANDISK_SDDR09, RID_WILDCARD, + UMASS_PROTO_SCSI, + READ_CAPACITY_OFFBY1 | NO_GETMAXLUN + }, + { USB_VENDOR_SANDISK, USB_PRODUCT_SANDISK_SDDR12, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_CBI, + READ_CAPACITY_OFFBY1 | NO_GETMAXLUN + }, + { USB_VENDOR_SANDISK, USB_PRODUCT_SANDISK_SDDR31, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + READ_CAPACITY_OFFBY1 + }, + { USB_VENDOR_SANDISK, USB_PRODUCT_SANDISK_SDCZ2_256, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + IGNORE_RESIDUE + }, + { USB_VENDOR_SANDISK, USB_PRODUCT_SANDISK_SDCZ4_128, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + IGNORE_RESIDUE + }, + { USB_VENDOR_SANDISK, USB_PRODUCT_SANDISK_SDCZ4_256, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + IGNORE_RESIDUE + }, + { USB_VENDOR_SCANLOGIC, USB_PRODUCT_SCANLOGIC_SL11R, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_BBB, + NO_INQUIRY + }, + { USB_VENDOR_SHUTTLE, USB_PRODUCT_SHUTTLE_CDRW, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_CBI, + NO_QUIRKS + }, + { USB_VENDOR_SHUTTLE, USB_PRODUCT_SHUTTLE_CF, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_CBI, + NO_QUIRKS + }, + { USB_VENDOR_SHUTTLE, USB_PRODUCT_SHUTTLE_EUSB, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_CBI_I, + NO_TEST_UNIT_READY | NO_START_STOP | SHUTTLE_INIT + }, + { USB_VENDOR_SHUTTLE, USB_PRODUCT_SHUTTLE_EUSBATAPI, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_CBI, + NO_QUIRKS + }, + { USB_VENDOR_SHUTTLE, USB_PRODUCT_SHUTTLE_EUSBCFSM, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_QUIRKS + }, + { USB_VENDOR_SHUTTLE, USB_PRODUCT_SHUTTLE_EUSCSI, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + { USB_VENDOR_SHUTTLE, USB_PRODUCT_SHUTTLE_HIFD, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_CBI, + NO_GETMAXLUN + }, + { USB_VENDOR_SHUTTLE, USB_PRODUCT_SHUTTLE_SDDR09, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_GETMAXLUN + }, + { USB_VENDOR_SHUTTLE, USB_PRODUCT_SHUTTLE_ZIOMMC, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_CBI, + NO_GETMAXLUN + }, + { USB_VENDOR_SIGMATEL, USB_PRODUCT_SIGMATEL_I_BEAD100, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + SHUTTLE_INIT + }, + { USB_VENDOR_SIIG, USB_PRODUCT_SIIG_WINTERREADER, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + IGNORE_RESIDUE + }, + { USB_VENDOR_SKANHEX, USB_PRODUCT_SKANHEX_MD_7425, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_INQUIRY + }, + { USB_VENDOR_SKANHEX, USB_PRODUCT_SKANHEX_SX_520Z, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_INQUIRY + }, + { USB_VENDOR_SONY, USB_PRODUCT_SONY_CLIE_40_MS, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_INQUIRY + }, + { USB_VENDOR_SONY, USB_PRODUCT_SONY_DSC, 0x0500, + UMASS_PROTO_RBC | UMASS_PROTO_CBI, + RBC_PAD_TO_12 + }, + { USB_VENDOR_SONY, USB_PRODUCT_SONY_DSC, 0x0600, + UMASS_PROTO_RBC | UMASS_PROTO_CBI, + RBC_PAD_TO_12 + }, + { USB_VENDOR_SONY, USB_PRODUCT_SONY_DSC, RID_WILDCARD, + UMASS_PROTO_RBC | UMASS_PROTO_CBI, + NO_QUIRKS + }, + { USB_VENDOR_SONY, USB_PRODUCT_SONY_HANDYCAM, 0x0500, + UMASS_PROTO_RBC | UMASS_PROTO_CBI, + RBC_PAD_TO_12 + }, + { USB_VENDOR_SONY, USB_PRODUCT_SONY_HANDYCAM, RID_WILDCARD, + UMASS_PROTO_RBC | UMASS_PROTO_CBI, + NO_QUIRKS + }, + { USB_VENDOR_SONY, USB_PRODUCT_SONY_MS_MSC_U03, RID_WILDCARD, + UMASS_PROTO_UFI | UMASS_PROTO_CBI, + NO_GETMAXLUN + }, + { USB_VENDOR_SONY, USB_PRODUCT_SONY_MS_NW_MS7, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_GETMAXLUN + }, + { USB_VENDOR_SONY, USB_PRODUCT_SONY_MS_PEG_N760C, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_INQUIRY + }, + { USB_VENDOR_SONY, USB_PRODUCT_SONY_MSACUS1, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_GETMAXLUN + }, + { USB_VENDOR_SONY, USB_PRODUCT_SONY_MSC, RID_WILDCARD, + UMASS_PROTO_RBC | UMASS_PROTO_CBI, + NO_QUIRKS + }, + { USB_VENDOR_SONY, USB_PRODUCT_SONY_PORTABLE_HDD_V2, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + { USB_VENDOR_TAUGA, USB_PRODUCT_TAUGA_CAMERAMATE, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_QUIRKS + }, + { USB_VENDOR_TEAC, USB_PRODUCT_TEAC_FD05PUB, RID_WILDCARD, + UMASS_PROTO_UFI | UMASS_PROTO_CBI, + NO_QUIRKS + }, + { USB_VENDOR_TREK, USB_PRODUCT_TREK_MEMKEY, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_INQUIRY + }, + { USB_VENDOR_TREK, USB_PRODUCT_TREK_THUMBDRIVE_8MB, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_BBB, + IGNORE_RESIDUE + }, + { USB_VENDOR_TRUMPION, USB_PRODUCT_TRUMPION_C3310, RID_WILDCARD, + UMASS_PROTO_UFI | UMASS_PROTO_CBI, + NO_QUIRKS + }, + { USB_VENDOR_TRUMPION, USB_PRODUCT_TRUMPION_MP3, RID_WILDCARD, + UMASS_PROTO_RBC, + NO_QUIRKS + }, + { USB_VENDOR_TRUMPION, USB_PRODUCT_TRUMPION_T33520, RID_WILDCARD, + UMASS_PROTO_SCSI, + NO_QUIRKS + }, + { USB_VENDOR_TWINMOS, USB_PRODUCT_TWINMOS_MDIV, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_QUIRKS + }, + { USB_VENDOR_VIA, USB_PRODUCT_VIA_USB2IDEBRIDGE, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_SYNCHRONIZE_CACHE + }, + { USB_VENDOR_VIVITAR, USB_PRODUCT_VIVITAR_35XX, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_INQUIRY + }, + { USB_VENDOR_WESTERN, USB_PRODUCT_WESTERN_COMBO, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + FORCE_SHORT_INQUIRY | NO_START_STOP | IGNORE_RESIDUE + }, + { USB_VENDOR_WESTERN, USB_PRODUCT_WESTERN_EXTHDD, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + FORCE_SHORT_INQUIRY | NO_START_STOP | IGNORE_RESIDUE + }, + { USB_VENDOR_WESTERN, USB_PRODUCT_WESTERN_MYBOOK, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_INQUIRY_EVPD + }, + { USB_VENDOR_WINMAXGROUP, USB_PRODUCT_WINMAXGROUP_FLASH64MC, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + NO_INQUIRY + }, + { USB_VENDOR_YANO, USB_PRODUCT_YANO_FW800HD, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_BBB, + FORCE_SHORT_INQUIRY | NO_START_STOP | IGNORE_RESIDUE + }, + { USB_VENDOR_YANO, USB_PRODUCT_YANO_U640MO, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_CBI_I, + FORCE_SHORT_INQUIRY + }, + { USB_VENDOR_YEDATA, USB_PRODUCT_YEDATA_FLASHBUSTERU, RID_WILDCARD, + UMASS_PROTO_SCSI | UMASS_PROTO_CBI, + NO_GETMAXLUN + }, + { USB_VENDOR_ZORAN, USB_PRODUCT_ZORAN_EX20DSC, RID_WILDCARD, + UMASS_PROTO_ATAPI | UMASS_PROTO_CBI, + NO_QUIRKS + }, + { VID_EOT, PID_EOT, RID_EOT, 0, 0 } +}; + + +/* the per device structure */ +struct umass_softc { + device_t sc_dev; /* base device */ + usbd_device_handle sc_udev; /* USB device */ + + struct cam_sim *umass_sim; /* SCSI Interface Module */ + + unsigned char flags; /* various device flags */ +# define UMASS_FLAGS_GONE 0x01 /* devices is no more */ + + u_int16_t proto; /* wire and cmd protocol */ + u_int16_t quirks; /* they got it almost right */ + + usbd_interface_handle iface; /* Mass Storage interface */ + int ifaceno; /* MS iface number */ + + u_int8_t bulkin; /* bulk-in Endpoint Address */ + u_int8_t bulkout; /* bulk-out Endpoint Address */ + u_int8_t intrin; /* intr-in Endp. (CBI) */ + usbd_pipe_handle bulkin_pipe; + usbd_pipe_handle bulkout_pipe; + usbd_pipe_handle intrin_pipe; + + /* Reset the device in a wire protocol specific way */ + wire_reset_f reset; + + /* The start of a wire transfer. It prepares the whole transfer (cmd, + * data, and status stage) and initiates it. It is up to the state + * machine (below) to handle the various stages and errors in these + */ + wire_transfer_f transfer; + + /* The state machine, handling the various states during a transfer */ + wire_state_f state; + + /* The command transform function is used to conver the SCSI commands + * into their derivatives, like UFI, ATAPI, and friends. + */ + command_transform_f transform; /* command transform */ + + /* 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_cbl_t cbl; /* command block */ + umass_cbi_sbl_t sbl; /* status block */ + + /* generic variables for transfers in progress */ + /* ctrl transfer requests */ + usb_device_request_t request; + + /* xfer handles + * Most of our operations are initiated from interrupt context, so + * we need to avoid using the one that is in use. We want to avoid + * allocating them in the interrupt context as well. + */ + /* indices into array below */ +# define XFER_BBB_CBW 0 /* Bulk-Only */ +# define XFER_BBB_DATA 1 +# define XFER_BBB_DCLEAR 2 +# define XFER_BBB_CSW1 3 +# define XFER_BBB_CSW2 4 +# define XFER_BBB_SCLEAR 5 +# define XFER_BBB_RESET1 6 +# define XFER_BBB_RESET2 7 +# define XFER_BBB_RESET3 8 + +# define XFER_CBI_CB 0 /* CBI */ +# define XFER_CBI_DATA 1 +# define XFER_CBI_STATUS 2 +# define XFER_CBI_DCLEAR 3 +# define XFER_CBI_SCLEAR 4 +# define XFER_CBI_RESET1 5 +# define XFER_CBI_RESET2 6 +# define XFER_CBI_RESET3 7 + +# define XFER_NR 9 /* maximum number */ + + usbd_xfer_handle transfer_xfer[XFER_NR]; /* for ctrl xfers */ + + int transfer_dir; /* data direction */ + void *transfer_data; /* data buffer */ + int transfer_datalen; /* (maximum) length */ + int transfer_actlen; /* actual length */ + transfer_cb_f transfer_cb; /* callback */ + void *transfer_priv; /* for callback */ + int transfer_status; + + int transfer_state; +# define TSTATE_ATTACH 0 /* in attach */ +# define TSTATE_IDLE 1 +# define TSTATE_BBB_COMMAND 2 /* CBW transfer */ +# define TSTATE_BBB_DATA 3 /* Data transfer */ +# define TSTATE_BBB_DCLEAR 4 /* clear endpt stall */ +# define TSTATE_BBB_STATUS1 5 /* clear endpt stall */ +# define TSTATE_BBB_SCLEAR 6 /* clear endpt stall */ +# define TSTATE_BBB_STATUS2 7 /* CSW transfer */ +# define TSTATE_BBB_RESET1 8 /* reset command */ +# define TSTATE_BBB_RESET2 9 /* in clear stall */ +# define TSTATE_BBB_RESET3 10 /* out clear stall */ +# define TSTATE_CBI_COMMAND 11 /* command transfer */ +# define TSTATE_CBI_DATA 12 /* data transfer */ +# define TSTATE_CBI_STATUS 13 /* status transfer */ +# define TSTATE_CBI_DCLEAR 14 /* clear ep stall */ +# define TSTATE_CBI_SCLEAR 15 /* clear ep stall */ +# define TSTATE_CBI_RESET1 16 /* reset command */ +# define TSTATE_CBI_RESET2 17 /* in clear stall */ +# define TSTATE_CBI_RESET3 18 /* out clear stall */ +# define TSTATE_STATES 19 /* # of states above */ + + + /* SCSI/CAM specific variables */ + unsigned char cam_scsi_command[CAM_MAX_CDBLEN]; + unsigned char cam_scsi_command2[CAM_MAX_CDBLEN]; + struct scsi_sense cam_scsi_sense; + struct scsi_sense cam_scsi_test_unit_ready; + struct callout cam_scsi_rescan_ch; + + int timeout; /* in msecs */ + + int maxlun; /* maximum LUN number */ +}; + +#ifdef USB_DEBUG +char *states[TSTATE_STATES+1] = { + /* should be kept in sync with the list at transfer_state */ + "Attach", + "Idle", + "BBB CBW", + "BBB Data", + "BBB Data bulk-in/-out clear stall", + "BBB CSW, 1st attempt", + "BBB CSW bulk-in clear stall", + "BBB CSW, 2nd attempt", + "BBB Reset", + "BBB bulk-in clear stall", + "BBB bulk-out clear stall", + "CBI Command", + "CBI Data", + "CBI Status", + "CBI Data bulk-in/-out clear stall", + "CBI Status intr-in clear stall", + "CBI Reset", + "CBI bulk-in clear stall", + "CBI bulk-out clear stall", + NULL +}; +#endif + +/* If device cannot return valid inquiry data, fake it */ +static uint8_t fake_inq_data[SHORT_INQUIRY_LENGTH] = { + 0, /*removable*/ 0x80, SCSI_REV_2, SCSI_REV_2, + /*additional_length*/ 31, 0, 0, 0 +}; + +/* USB device probe/attach/detach functions */ +static device_probe_t umass_match; +static device_attach_t umass_attach; +static device_detach_t umass_detach; + +static device_method_t umass_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, umass_match), + DEVMETHOD(device_attach, umass_attach), + DEVMETHOD(device_detach, umass_detach), + + { 0, 0 } +}; + +static driver_t umass_driver = { + "umass", + umass_methods, + sizeof(struct umass_softc) +}; + +static devclass_t umass_devclass; + +static int umass_match_proto (struct umass_softc *sc, + usbd_interface_handle iface, + usbd_device_handle udev); + +/* quirk functions */ +static void umass_init_shuttle (struct umass_softc *sc); + +/* generic transfer functions */ +static usbd_status umass_setup_transfer (struct umass_softc *sc, + usbd_pipe_handle pipe, + void *buffer, int buflen, int flags, + usbd_xfer_handle xfer); +static usbd_status umass_setup_ctrl_transfer (struct umass_softc *sc, + usbd_device_handle udev, + usb_device_request_t *req, + void *buffer, int buflen, int flags, + usbd_xfer_handle xfer); +static void umass_clear_endpoint_stall (struct umass_softc *sc, + u_int8_t endpt, usbd_pipe_handle pipe, + int state, usbd_xfer_handle xfer); +static void umass_reset (struct umass_softc *sc, + transfer_cb_f cb, void *priv); + +/* Bulk-Only related functions */ +static void umass_bbb_reset (struct umass_softc *sc, int status); +static void umass_bbb_transfer (struct umass_softc *sc, int lun, + void *cmd, int cmdlen, + void *data, int datalen, int dir, u_int timeout, + transfer_cb_f cb, void *priv); +static void umass_bbb_state (usbd_xfer_handle xfer, + usbd_private_handle priv, + usbd_status err); +static int umass_bbb_get_max_lun + (struct umass_softc *sc); + +/* CBI related functions */ +static int umass_cbi_adsc (struct umass_softc *sc, + char *buffer, int buflen, + usbd_xfer_handle xfer); +static void umass_cbi_reset (struct umass_softc *sc, int status); +static void umass_cbi_transfer (struct umass_softc *sc, int lun, + void *cmd, int cmdlen, + void *data, int datalen, int dir, u_int timeout, + transfer_cb_f cb, void *priv); +static void umass_cbi_state (usbd_xfer_handle xfer, + usbd_private_handle priv, usbd_status err); + +/* CAM related functions */ +static void umass_cam_action (struct cam_sim *sim, union ccb *ccb); +static void umass_cam_poll (struct cam_sim *sim); + +static void umass_cam_cb (struct umass_softc *sc, void *priv, + int residue, int status); +static void umass_cam_sense_cb (struct umass_softc *sc, void *priv, + int residue, int status); +static void umass_cam_quirk_cb (struct umass_softc *sc, void *priv, + int residue, int status); + +static void umass_cam_rescan_callback + (struct cam_periph *periph,union ccb *ccb); +static void umass_cam_rescan (void *addr); + +static int umass_cam_attach_sim (struct umass_softc *sc); +static int umass_cam_attach (struct umass_softc *sc); +static int umass_cam_detach_sim (struct umass_softc *sc); + + +/* SCSI specific functions */ +static int umass_scsi_transform (struct umass_softc *sc, + unsigned char *cmd, int cmdlen, + unsigned char **rcmd, int *rcmdlen); + +/* UFI specific functions */ +#define UFI_COMMAND_LENGTH 12 /* UFI commands are always 12 bytes */ +static int umass_ufi_transform (struct umass_softc *sc, + unsigned char *cmd, int cmdlen, + unsigned char **rcmd, int *rcmdlen); + +/* ATAPI (8070i) specific functions */ +#define ATAPI_COMMAND_LENGTH 12 /* ATAPI commands are always 12 bytes */ +static int umass_atapi_transform (struct umass_softc *sc, + unsigned char *cmd, int cmdlen, + unsigned char **rcmd, int *rcmdlen); + +/* RBC specific functions */ +static int umass_rbc_transform (struct umass_softc *sc, + unsigned char *cmd, int cmdlen, + unsigned char **rcmd, int *rcmdlen); + +#ifdef USB_DEBUG +/* General debugging functions */ +static void umass_bbb_dump_cbw (struct umass_softc *sc, umass_bbb_cbw_t *cbw); +static void umass_bbb_dump_csw (struct umass_softc *sc, umass_bbb_csw_t *csw); +static void umass_cbi_dump_cmd (struct umass_softc *sc, void *cmd, int cmdlen); +static void umass_dump_buffer (struct umass_softc *sc, u_int8_t *buffer, + int buflen, int printlen); +#endif + +MODULE_DEPEND(umass, cam, 1, 1, 1); +MODULE_DEPEND(umass, usb, 1, 1, 1); + +/* + * USB device probe/attach/detach + */ + +/* + * Match the device we are seeing with the devices supported. Fill in the + * description in the softc accordingly. This function is called from both + * probe and attach. + */ + +static int +umass_match_proto(struct umass_softc *sc, usbd_interface_handle iface, + usbd_device_handle udev) +{ + usb_device_descriptor_t *dd; + usb_interface_descriptor_t *id; + int i; + int found = 0; + + sc->sc_udev = udev; + sc->proto = 0; + sc->quirks = 0; + + dd = usbd_get_device_descriptor(udev); + + /* An entry specifically for Y-E Data devices as they don't fit in the + * device description table. + */ + if (UGETW(dd->idVendor) == USB_VENDOR_YEDATA + && UGETW(dd->idProduct) == USB_PRODUCT_YEDATA_FLASHBUSTERU) { + + /* Revisions < 1.28 do not handle the interrupt endpoint + * very well. + */ + if (UGETW(dd->bcdDevice) < 0x128) { + sc->proto = UMASS_PROTO_UFI | UMASS_PROTO_CBI; + } else { + sc->proto = UMASS_PROTO_UFI | UMASS_PROTO_CBI_I; + } + + /* + * Revisions < 1.28 do not have the TEST UNIT READY command + * Revisions == 1.28 have a broken TEST UNIT READY + */ + if (UGETW(dd->bcdDevice) <= 0x128) + sc->quirks |= NO_TEST_UNIT_READY; + + sc->quirks |= RS_NO_CLEAR_UA | FLOPPY_SPEED; + return(UMATCH_VENDOR_PRODUCT); + } + + /* Check the list of supported devices for a match. While looking, + * check for wildcarded and fully matched. First match wins. + */ + for (i = 0; umass_devdescrs[i].vid != VID_EOT && !found; i++) { + if (umass_devdescrs[i].vid == VID_WILDCARD && + umass_devdescrs[i].pid == PID_WILDCARD && + umass_devdescrs[i].rid == RID_WILDCARD) { + printf("umass: ignoring invalid wildcard quirk\n"); + continue; + } + if ((umass_devdescrs[i].vid == UGETW(dd->idVendor) || + umass_devdescrs[i].vid == VID_WILDCARD) + && (umass_devdescrs[i].pid == UGETW(dd->idProduct) || + umass_devdescrs[i].pid == PID_WILDCARD)) { + if (umass_devdescrs[i].rid == RID_WILDCARD) { + sc->proto = umass_devdescrs[i].proto; + sc->quirks = umass_devdescrs[i].quirks; + return (UMATCH_VENDOR_PRODUCT); + } else if (umass_devdescrs[i].rid == + UGETW(dd->bcdDevice)) { + sc->proto = umass_devdescrs[i].proto; + sc->quirks = umass_devdescrs[i].quirks; + return (UMATCH_VENDOR_PRODUCT_REV); + } /* else RID does not match */ + } + } + + /* Check for a standards compliant device */ + id = usbd_get_interface_descriptor(iface); + if (id == NULL || id->bInterfaceClass != UICLASS_MASS) + return(UMATCH_NONE); + + switch (id->bInterfaceSubClass) { + case UISUBCLASS_SCSI: + sc->proto |= UMASS_PROTO_SCSI; + break; + case UISUBCLASS_UFI: + sc->proto |= UMASS_PROTO_UFI; + break; + case UISUBCLASS_RBC: + sc->proto |= UMASS_PROTO_RBC; + break; + case UISUBCLASS_SFF8020I: + case UISUBCLASS_SFF8070I: + sc->proto |= UMASS_PROTO_ATAPI; + break; + default: + DPRINTF(UDMASS_GEN, ("%s: Unsupported command protocol %d\n", + device_get_nameunit(sc->sc_dev), id->bInterfaceSubClass)); + return(UMATCH_NONE); + } + + switch (id->bInterfaceProtocol) { + case UIPROTO_MASS_CBI: + sc->proto |= UMASS_PROTO_CBI; + break; + case UIPROTO_MASS_CBI_I: + sc->proto |= UMASS_PROTO_CBI_I; + break; + case UIPROTO_MASS_BBB_OLD: + case UIPROTO_MASS_BBB: + sc->proto |= UMASS_PROTO_BBB; + break; + default: + DPRINTF(UDMASS_GEN, ("%s: Unsupported wire protocol %d\n", + device_get_nameunit(sc->sc_dev), id->bInterfaceProtocol)); + return(UMATCH_NONE); + } + + return(UMATCH_IFACECLASS_IFACESUBCLASS_IFACEPROTO); +} + +static int +umass_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + struct umass_softc *sc = device_get_softc(self); + + sc->sc_dev = self; + if (uaa->iface == NULL) + return(UMATCH_NONE); + + return(umass_match_proto(sc, uaa->iface, uaa->device)); +} + +static int +umass_attach(device_t self) +{ + struct umass_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + usb_interface_descriptor_t *id; + usb_endpoint_descriptor_t *ed; + int i; + int err; + + /* + * the softc struct is bzero-ed in device_set_driver. We can safely + * call umass_detach without specifically initialising the struct. + */ + sc->sc_dev = self; + sc->iface = uaa->iface; + sc->ifaceno = uaa->ifaceno; + callout_init(&sc->cam_scsi_rescan_ch, 0); + + /* initialise the proto and drive values in the umass_softc (again) */ + (void) umass_match_proto(sc, sc->iface, uaa->device); + + id = usbd_get_interface_descriptor(sc->iface); +#ifdef USB_DEBUG + printf("%s: ", device_get_nameunit(sc->sc_dev)); + switch (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->proto&UMASS_PROTO_COMMAND); + break; + } + printf(" over "); + switch (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"); +#ifndef CBI_I + printf(" (using CBI)"); +#endif + break; + default: + printf("(unknown 0x%02x)", sc->proto&UMASS_PROTO_WIRE); + } + printf("; quirks = 0x%04x\n", sc->quirks); +#endif + +#ifndef CBI_I + if (sc->proto & UMASS_PROTO_CBI_I) { + /* See beginning of file for comment on the use of CBI with CCI */ + sc->proto = (sc->proto & ~UMASS_PROTO_CBI_I) | UMASS_PROTO_CBI; + } +#endif + + if (sc->quirks & ALT_IFACE_1) { + err = usbd_set_interface(uaa->iface, 1); + if (err) { + DPRINTF(UDMASS_USB, ("%s: could not switch to " + "Alt Interface %d\n", + device_get_nameunit(sc->sc_dev), 1)); + umass_detach(self); + return ENXIO; + } + } + + /* + * In addition to the Control endpoint the following endpoints + * are required: + * a) bulk-in endpoint. + * b) bulk-out endpoint. + * and for Control/Bulk/Interrupt with CCI (CBI_I) + * c) intr-in + * + * The endpoint addresses are not fixed, so we have to read them + * from the device descriptors of the current interface. + */ + for (i = 0 ; i < id->bNumEndpoints ; i++) { + ed = usbd_interface2endpoint_descriptor(sc->iface, i); + if (!ed) { + printf("%s: could not read endpoint descriptor\n", + device_get_nameunit(sc->sc_dev)); + return ENXIO; + } + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN + && (ed->bmAttributes & UE_XFERTYPE) == UE_BULK) { + sc->bulkin = ed->bEndpointAddress; + } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT + && (ed->bmAttributes & UE_XFERTYPE) == UE_BULK) { + sc->bulkout = ed->bEndpointAddress; + } else if (sc->proto & UMASS_PROTO_CBI_I + && UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN + && (ed->bmAttributes & UE_XFERTYPE) == UE_INTERRUPT) { + sc->intrin = ed->bEndpointAddress; +#ifdef USB_DEBUG + if (UGETW(ed->wMaxPacketSize) > 2) { + DPRINTF(UDMASS_CBI, ("%s: intr size is %d\n", + device_get_nameunit(sc->sc_dev), + UGETW(ed->wMaxPacketSize))); + } +#endif + } + } + + /* check whether we found all the endpoints we need */ + if (!sc->bulkin || !sc->bulkout + || (sc->proto & UMASS_PROTO_CBI_I && !sc->intrin) ) { + DPRINTF(UDMASS_USB, ("%s: endpoint not found %d/%d/%d\n", + device_get_nameunit(sc->sc_dev), + sc->bulkin, sc->bulkout, sc->intrin)); + umass_detach(self); + return ENXIO; + } + + /* Open the bulk-in and -out pipe */ + err = usbd_open_pipe(sc->iface, sc->bulkout, + USBD_EXCLUSIVE_USE, &sc->bulkout_pipe); + if (err) { + DPRINTF(UDMASS_USB, ("%s: cannot open %d-out pipe (bulk)\n", + device_get_nameunit(sc->sc_dev), sc->bulkout)); + umass_detach(self); + return ENXIO; + } + err = usbd_open_pipe(sc->iface, sc->bulkin, + USBD_EXCLUSIVE_USE, &sc->bulkin_pipe); + if (err) { + DPRINTF(UDMASS_USB, ("%s: could not open %d-in pipe (bulk)\n", + device_get_nameunit(sc->sc_dev), sc->bulkin)); + umass_detach(self); + return ENXIO; + } + /* Open the intr-in pipe if the protocol is CBI with CCI. + * Note: early versions of the Zip drive do have an interrupt pipe, but + * this pipe is unused. + * + * We do not open the interrupt pipe as an interrupt pipe, but as a + * normal bulk endpoint. We send an IN transfer down the wire at the + * appropriate time, because we know exactly when to expect data on + * that endpoint. This saves bandwidth, but more important, makes the + * code for handling the data on that endpoint simpler. No data + * arriving concurrently. + */ + if (sc->proto & UMASS_PROTO_CBI_I) { + err = usbd_open_pipe(sc->iface, sc->intrin, + USBD_EXCLUSIVE_USE, &sc->intrin_pipe); + if (err) { + DPRINTF(UDMASS_USB, ("%s: couldn't open %d-in (intr)\n", + device_get_nameunit(sc->sc_dev), sc->intrin)); + umass_detach(self); + return ENXIO; + } + } + + /* initialisation of generic part */ + sc->transfer_state = TSTATE_ATTACH; + + /* request a sufficient number of xfer handles */ + for (i = 0; i < XFER_NR; i++) { + sc->transfer_xfer[i] = usbd_alloc_xfer(uaa->device); + if (!sc->transfer_xfer[i]) { + DPRINTF(UDMASS_USB, ("%s: Out of memory\n", + device_get_nameunit(sc->sc_dev))); + umass_detach(self); + return ENXIO; + } + } + + /* Initialise the wire protocol specific methods */ + if (sc->proto & UMASS_PROTO_BBB) { + sc->reset = umass_bbb_reset; + sc->transfer = umass_bbb_transfer; + sc->state = umass_bbb_state; + } else if (sc->proto & (UMASS_PROTO_CBI|UMASS_PROTO_CBI_I)) { + sc->reset = umass_cbi_reset; + sc->transfer = umass_cbi_transfer; + sc->state = umass_cbi_state; +#ifdef USB_DEBUG + } else { + panic("%s:%d: Unknown proto 0x%02x", + __FILE__, __LINE__, sc->proto); +#endif + } + + if (sc->proto & UMASS_PROTO_SCSI) + sc->transform = umass_scsi_transform; + else if (sc->proto & UMASS_PROTO_UFI) + sc->transform = umass_ufi_transform; + else if (sc->proto & UMASS_PROTO_ATAPI) + sc->transform = umass_atapi_transform; + else if (sc->proto & UMASS_PROTO_RBC) + sc->transform = umass_rbc_transform; +#ifdef USB_DEBUG + else + panic("No transformation defined for command proto 0x%02x", + sc->proto & UMASS_PROTO_COMMAND); +#endif + + /* From here onwards the device can be used. */ + + if (sc->quirks & SHUTTLE_INIT) + umass_init_shuttle(sc); + + /* Get the maximum LUN supported by the device. + */ + if (((sc->proto & UMASS_PROTO_WIRE) == UMASS_PROTO_BBB) && + !(sc->quirks & NO_GETMAXLUN)) + sc->maxlun = umass_bbb_get_max_lun(sc); + else + sc->maxlun = 0; + + if ((sc->proto & UMASS_PROTO_SCSI) || + (sc->proto & UMASS_PROTO_ATAPI) || + (sc->proto & UMASS_PROTO_UFI) || + (sc->proto & UMASS_PROTO_RBC)) { + /* Prepare the SCSI command block */ + sc->cam_scsi_sense.opcode = REQUEST_SENSE; + sc->cam_scsi_test_unit_ready.opcode = TEST_UNIT_READY; + + /* register the SIM */ + err = umass_cam_attach_sim(sc); + if (err) { + umass_detach(self); + return ENXIO; + } + /* scan the new sim */ + err = umass_cam_attach(sc); + if (err) { + umass_cam_detach_sim(sc); + umass_detach(self); + return ENXIO; + } + } else { + panic("%s:%d: Unknown proto 0x%02x", + __FILE__, __LINE__, sc->proto); + } + + sc->transfer_state = TSTATE_IDLE; + DPRINTF(UDMASS_GEN, ("%s: Attach finished\n", device_get_nameunit(sc->sc_dev))); + + return 0; +} + +static int +umass_detach(device_t self) +{ + struct umass_softc *sc = device_get_softc(self); + int err = 0; + int i; + + DPRINTF(UDMASS_USB, ("%s: detached\n", device_get_nameunit(sc->sc_dev))); + + sc->flags |= UMASS_FLAGS_GONE; + + /* abort all the pipes in case there are transfers active. */ + usbd_abort_default_pipe(sc->sc_udev); + if (sc->bulkout_pipe) + usbd_abort_pipe(sc->bulkout_pipe); + if (sc->bulkin_pipe) + usbd_abort_pipe(sc->bulkin_pipe); + if (sc->intrin_pipe) + usbd_abort_pipe(sc->intrin_pipe); + + callout_drain(&sc->cam_scsi_rescan_ch); + if ((sc->proto & UMASS_PROTO_SCSI) || + (sc->proto & UMASS_PROTO_ATAPI) || + (sc->proto & UMASS_PROTO_UFI) || + (sc->proto & UMASS_PROTO_RBC)) + /* detach the SCSI host controller (SIM) */ + err = umass_cam_detach_sim(sc); + + for (i = 0; i < XFER_NR; i++) + if (sc->transfer_xfer[i]) + usbd_free_xfer(sc->transfer_xfer[i]); + + /* remove all the pipes */ + if (sc->bulkout_pipe) + usbd_close_pipe(sc->bulkout_pipe); + if (sc->bulkin_pipe) + usbd_close_pipe(sc->bulkin_pipe); + if (sc->intrin_pipe) + usbd_close_pipe(sc->intrin_pipe); + + return(err); +} + +static void +umass_init_shuttle(struct umass_softc *sc) +{ + usb_device_request_t req; + u_char status[2]; + + /* 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); + USETW(req.wIndex, sc->ifaceno); + USETW(req.wLength, sizeof status); + (void) usbd_do_request(sc->sc_udev, &req, &status); + + DPRINTF(UDMASS_GEN, ("%s: Shuttle init returned 0x%02x%02x\n", + device_get_nameunit(sc->sc_dev), status[0], status[1])); +} + + /* + * Generic functions to handle transfers + */ + +static usbd_status +umass_setup_transfer(struct umass_softc *sc, usbd_pipe_handle pipe, + void *buffer, int buflen, int flags, + usbd_xfer_handle xfer) +{ + usbd_status err; + + /* Initialise a USB transfer and then schedule it */ + + (void) usbd_setup_xfer(xfer, pipe, (void *) sc, buffer, buflen, flags, + sc->timeout, sc->state); + + err = usbd_transfer(xfer); + if (err && err != USBD_IN_PROGRESS) { + DPRINTF(UDMASS_BBB, ("%s: failed to setup transfer, %s\n", + device_get_nameunit(sc->sc_dev), usbd_errstr(err))); + return(err); + } + + return (USBD_NORMAL_COMPLETION); +} + + +static usbd_status +umass_setup_ctrl_transfer(struct umass_softc *sc, usbd_device_handle udev, + usb_device_request_t *req, + void *buffer, int buflen, int flags, + usbd_xfer_handle xfer) +{ + usbd_status err; + + /* Initialise a USB control transfer and then schedule it */ + + (void) usbd_setup_default_xfer(xfer, udev, (void *) sc, + sc->timeout, req, buffer, buflen, flags, sc->state); + + err = usbd_transfer(xfer); + if (err && err != USBD_IN_PROGRESS) { + DPRINTF(UDMASS_BBB, ("%s: failed to setup ctrl transfer, %s\n", + device_get_nameunit(sc->sc_dev), usbd_errstr(err))); + + /* do not reset, as this would make us loop */ + return(err); + } + + return (USBD_NORMAL_COMPLETION); +} + +static void +umass_clear_endpoint_stall(struct umass_softc *sc, + u_int8_t endpt, usbd_pipe_handle pipe, + int state, usbd_xfer_handle xfer) +{ + usbd_device_handle udev; + + DPRINTF(UDMASS_BBB, ("%s: Clear endpoint 0x%02x stall\n", + device_get_nameunit(sc->sc_dev), endpt)); + + usbd_interface2device_handle(sc->iface, &udev); + + sc->transfer_state = state; + + usbd_clear_endpoint_toggle(pipe); + + sc->request.bmRequestType = UT_WRITE_ENDPOINT; + sc->request.bRequest = UR_CLEAR_FEATURE; + USETW(sc->request.wValue, UF_ENDPOINT_HALT); + USETW(sc->request.wIndex, endpt); + USETW(sc->request.wLength, 0); + umass_setup_ctrl_transfer(sc, udev, &sc->request, NULL, 0, 0, xfer); +} + +static void +umass_reset(struct umass_softc *sc, transfer_cb_f cb, void *priv) +{ + sc->transfer_cb = cb; + sc->transfer_priv = priv; + + /* The reset is a forced reset, so no error (yet) */ + sc->reset(sc, STATUS_CMD_OK); +} + +/* + * Bulk protocol specific functions + */ + +static void +umass_bbb_reset(struct umass_softc *sc, int status) +{ + usbd_device_handle udev; + + KASSERT(sc->proto & UMASS_PROTO_BBB, + ("%s: umass_bbb_reset: wrong sc->proto 0x%02x\n", + device_get_nameunit(sc->sc_dev), sc->proto)); + + /* + * 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, states: + * TSTATE_BBB_RESET1 + * TSTATE_BBB_RESET2 + * TSTATE_BBB_RESET3 + * + * If the reset doesn't succeed, the device should be port reset. + */ + + DPRINTF(UDMASS_BBB, ("%s: Bulk Reset\n", + device_get_nameunit(sc->sc_dev))); + + sc->transfer_state = TSTATE_BBB_RESET1; + sc->transfer_status = status; + + usbd_interface2device_handle(sc->iface, &udev); + + /* reset is a class specific interface write */ + sc->request.bmRequestType = UT_WRITE_CLASS_INTERFACE; + sc->request.bRequest = UR_BBB_RESET; + USETW(sc->request.wValue, 0); + USETW(sc->request.wIndex, sc->ifaceno); + USETW(sc->request.wLength, 0); + umass_setup_ctrl_transfer(sc, udev, &sc->request, NULL, 0, 0, + sc->transfer_xfer[XFER_BBB_RESET1]); +} + +static void +umass_bbb_transfer(struct umass_softc *sc, int lun, void *cmd, int cmdlen, + void *data, int datalen, int dir, u_int timeout, + transfer_cb_f cb, void *priv) +{ + KASSERT(sc->proto & UMASS_PROTO_BBB, + ("%s: umass_bbb_transfer: wrong sc->proto 0x%02x\n", + device_get_nameunit(sc->sc_dev), sc->proto)); + + /* Be a little generous. */ + sc->timeout = timeout + UMASS_TIMEOUT; + + /* + * Do a Bulk-Only transfer with cmdlen bytes from cmd, possibly + * a data phase of datalen bytes from/to the device and finally a + * csw read phase. + * If the data direction was inbound a maximum of datalen bytes + * is stored in the buffer pointed to by data. + * + * umass_bbb_transfer initialises the transfer and lets the state + * machine in umass_bbb_state handle the completion. It uses the + * following states: + * TSTATE_BBB_COMMAND + * -> TSTATE_BBB_DATA + * -> TSTATE_BBB_STATUS + * -> TSTATE_BBB_STATUS2 + * -> TSTATE_BBB_IDLE + * + * An error in any of those states will invoke + * umass_bbb_reset. + */ + + /* check the given arguments */ + KASSERT(datalen == 0 || data != NULL, + ("%s: datalen > 0, but no buffer",device_get_nameunit(sc->sc_dev))); + KASSERT(cmdlen <= CBWCDBLENGTH, + ("%s: cmdlen exceeds CDB length in CBW (%d > %d)", + device_get_nameunit(sc->sc_dev), cmdlen, CBWCDBLENGTH)); + KASSERT(dir == DIR_NONE || datalen > 0, + ("%s: datalen == 0 while direction is not NONE\n", + device_get_nameunit(sc->sc_dev))); + KASSERT(datalen == 0 || dir != DIR_NONE, + ("%s: direction is NONE while datalen is not zero\n", + device_get_nameunit(sc->sc_dev))); + KASSERT(sizeof(umass_bbb_cbw_t) == UMASS_BBB_CBW_SIZE, + ("%s: CBW struct does not have the right size (%ld vs. %d)\n", + device_get_nameunit(sc->sc_dev), + (long)sizeof(umass_bbb_cbw_t), UMASS_BBB_CBW_SIZE)); + KASSERT(sizeof(umass_bbb_csw_t) == UMASS_BBB_CSW_SIZE, + ("%s: CSW struct does not have the right size (%ld vs. %d)\n", + device_get_nameunit(sc->sc_dev), + (long)sizeof(umass_bbb_csw_t), UMASS_BBB_CSW_SIZE)); + + /* + * Determine the direction of the data transfer and the length. + * + * dCBWDataTransferLength (datalen) : + * 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. + * + * dCBWFlags (dir) : + * 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 + */ + + /* Fill in the Command Block Wrapper + * We fill in all the fields, so there is no need to bzero it first. + */ + USETDW(sc->cbw.dCBWSignature, CBWSIGNATURE); + /* We don't care about the initial value, as long as the values are unique */ + USETDW(sc->cbw.dCBWTag, UGETDW(sc->cbw.dCBWTag) + 1); + USETDW(sc->cbw.dCBWDataTransferLength, datalen); + /* DIR_NONE is treated as DIR_OUT (0x00) */ + sc->cbw.bCBWFlags = (dir == DIR_IN? CBWFLAGS_IN:CBWFLAGS_OUT); + sc->cbw.bCBWLUN = lun; + sc->cbw.bCDBLength = cmdlen; + bcopy(cmd, sc->cbw.CBWCDB, cmdlen); + + DIF(UDMASS_BBB, umass_bbb_dump_cbw(sc, &sc->cbw)); + + /* store the details for the data transfer phase */ + sc->transfer_dir = dir; + sc->transfer_data = data; + sc->transfer_datalen = datalen; + sc->transfer_actlen = 0; + sc->transfer_cb = cb; + sc->transfer_priv = priv; + sc->transfer_status = STATUS_CMD_OK; + + /* move from idle to the command state */ + sc->transfer_state = TSTATE_BBB_COMMAND; + + /* Send the CBW from host to device via bulk-out endpoint. */ + if (umass_setup_transfer(sc, sc->bulkout_pipe, + &sc->cbw, UMASS_BBB_CBW_SIZE, 0, + sc->transfer_xfer[XFER_BBB_CBW])) { + umass_bbb_reset(sc, STATUS_WIRE_FAILED); + } +} + + +static void +umass_bbb_state(usbd_xfer_handle xfer, usbd_private_handle priv, + usbd_status err) +{ + struct umass_softc *sc = (struct umass_softc *) priv; + usbd_xfer_handle next_xfer; + + KASSERT(sc->proto & UMASS_PROTO_BBB, + ("%s: umass_bbb_state: wrong sc->proto 0x%02x\n", + device_get_nameunit(sc->sc_dev), sc->proto)); + + /* + * State handling for BBB transfers. + * + * The subroutine is rather long. It steps through the states given in + * Annex A of the Bulk-Only specification. + * Each state first does the error handling of the previous transfer + * and then prepares the next transfer. + * Each transfer is done asynchronously so after the request/transfer + * has been submitted you will find a 'return;'. + */ + + DPRINTF(UDMASS_BBB, ("%s: Handling BBB state %d (%s), xfer=%p, %s\n", + device_get_nameunit(sc->sc_dev), sc->transfer_state, + states[sc->transfer_state], xfer, usbd_errstr(err))); + + /* Give up if the device has detached. */ + if (sc->flags & UMASS_FLAGS_GONE) { + sc->transfer_state = TSTATE_IDLE; + sc->transfer_cb(sc, sc->transfer_priv, sc->transfer_datalen, + STATUS_CMD_FAILED); + return; + } + + switch (sc->transfer_state) { + + /***** Bulk Transfer *****/ + case TSTATE_BBB_COMMAND: + /* Command transport phase, error handling */ + if (err) { + DPRINTF(UDMASS_BBB, ("%s: failed to send CBW\n", + device_get_nameunit(sc->sc_dev))); + /* If the device detects that the CBW is invalid, then + * the device may STALL both bulk endpoints and require + * a Bulk-Reset + */ + umass_bbb_reset(sc, STATUS_WIRE_FAILED); + return; + } + + /* Data transport phase, setup transfer */ + sc->transfer_state = TSTATE_BBB_DATA; + if (sc->transfer_dir == DIR_IN) { + if (umass_setup_transfer(sc, sc->bulkin_pipe, + sc->transfer_data, sc->transfer_datalen, + USBD_SHORT_XFER_OK, + sc->transfer_xfer[XFER_BBB_DATA])) + umass_bbb_reset(sc, STATUS_WIRE_FAILED); + + return; + } else if (sc->transfer_dir == DIR_OUT) { + if (umass_setup_transfer(sc, sc->bulkout_pipe, + sc->transfer_data, sc->transfer_datalen, + 0, /* fixed length transfer */ + sc->transfer_xfer[XFER_BBB_DATA])) + umass_bbb_reset(sc, STATUS_WIRE_FAILED); + + return; + } else { + DPRINTF(UDMASS_BBB, ("%s: no data phase\n", + device_get_nameunit(sc->sc_dev))); + } + + /* FALLTHROUGH if no data phase, err == 0 */ + case TSTATE_BBB_DATA: + /* Command transport phase, error handling (ignored if no data + * phase (fallthrough from previous state)) */ + if (sc->transfer_dir != DIR_NONE) { + /* retrieve the length of the transfer that was done */ + usbd_get_xfer_status(xfer, NULL, NULL, + &sc->transfer_actlen, NULL); + + if (err) { + DPRINTF(UDMASS_BBB, ("%s: Data-%s %db failed, " + "%s\n", device_get_nameunit(sc->sc_dev), + (sc->transfer_dir == DIR_IN?"in":"out"), + sc->transfer_datalen,usbd_errstr(err))); + + if (err == USBD_STALLED) { + umass_clear_endpoint_stall(sc, + (sc->transfer_dir == DIR_IN? + sc->bulkin:sc->bulkout), + (sc->transfer_dir == DIR_IN? + sc->bulkin_pipe:sc->bulkout_pipe), + TSTATE_BBB_DCLEAR, + sc->transfer_xfer[XFER_BBB_DCLEAR]); + return; + } else { + /* Unless the error is a pipe stall the + * error is fatal. + */ + umass_bbb_reset(sc,STATUS_WIRE_FAILED); + return; + } + } + } + + DIF(UDMASS_BBB, if (sc->transfer_dir == DIR_IN) + umass_dump_buffer(sc, sc->transfer_data, + sc->transfer_datalen, 48)); + + + + /* FALLTHROUGH, err == 0 (no data phase or successfull) */ + case TSTATE_BBB_DCLEAR: /* stall clear after data phase */ + case TSTATE_BBB_SCLEAR: /* stall clear after status phase */ + /* Reading of CSW after bulk stall condition in data phase + * (TSTATE_BBB_DATA2) or bulk-in stall condition after + * reading CSW (TSTATE_BBB_SCLEAR). + * In the case of no data phase or successfull data phase, + * err == 0 and the following if block is passed. + */ + if (err) { /* should not occur */ + /* try the transfer below, even if clear stall failed */ + DPRINTF(UDMASS_BBB, ("%s: bulk-%s stall clear failed" + ", %s\n", device_get_nameunit(sc->sc_dev), + (sc->transfer_dir == DIR_IN? "in":"out"), + usbd_errstr(err))); + umass_bbb_reset(sc, STATUS_WIRE_FAILED); + return; + } + + /* Status transport phase, setup transfer */ + if (sc->transfer_state == TSTATE_BBB_COMMAND || + sc->transfer_state == TSTATE_BBB_DATA || + sc->transfer_state == TSTATE_BBB_DCLEAR) { + /* After no data phase, successfull data phase and + * after clearing bulk-in/-out stall condition + */ + sc->transfer_state = TSTATE_BBB_STATUS1; + next_xfer = sc->transfer_xfer[XFER_BBB_CSW1]; + } else { + /* After first attempt of fetching CSW */ + sc->transfer_state = TSTATE_BBB_STATUS2; + next_xfer = sc->transfer_xfer[XFER_BBB_CSW2]; + } + + /* Read the Command Status Wrapper via bulk-in endpoint. */ + if (umass_setup_transfer(sc, sc->bulkin_pipe, + &sc->csw, UMASS_BBB_CSW_SIZE, 0, + next_xfer)) { + umass_bbb_reset(sc, STATUS_WIRE_FAILED); + return; + } + + return; + case TSTATE_BBB_STATUS1: /* first attempt */ + case TSTATE_BBB_STATUS2: /* second attempt */ + /* Status transfer, error handling */ + if (err) { + DPRINTF(UDMASS_BBB, ("%s: Failed to read CSW, %s%s\n", + device_get_nameunit(sc->sc_dev), usbd_errstr(err), + (sc->transfer_state == TSTATE_BBB_STATUS1? + ", retrying":""))); + + /* If this was the first attempt at fetching the CSW + * retry it, otherwise fail. + */ + if (sc->transfer_state == TSTATE_BBB_STATUS1) { + umass_clear_endpoint_stall(sc, + sc->bulkin, sc->bulkin_pipe, + TSTATE_BBB_SCLEAR, + sc->transfer_xfer[XFER_BBB_SCLEAR]); + return; + } else { + umass_bbb_reset(sc, STATUS_WIRE_FAILED); + return; + } + } + + DIF(UDMASS_BBB, umass_bbb_dump_csw(sc, &sc->csw)); + + /* Translate weird command-status signatures. */ + if (sc->quirks & WRONG_CSWSIG) { + u_int32_t dCSWSignature = UGETDW(sc->csw.dCSWSignature); + if (dCSWSignature == CSWSIGNATURE_OLYMPUS_C1 || + dCSWSignature == CSWSIGNATURE_IMAGINATION_DBX1) + USETDW(sc->csw.dCSWSignature, CSWSIGNATURE); + } + + int Residue; + Residue = UGETDW(sc->csw.dCSWDataResidue); + if (Residue == 0 && + sc->transfer_datalen - sc->transfer_actlen != 0) + Residue = sc->transfer_datalen - sc->transfer_actlen; + + /* Check CSW and handle any error */ + if (UGETDW(sc->csw.dCSWSignature) != CSWSIGNATURE) { + /* Invalid CSW: Wrong signature or wrong tag might + * indicate that the device is confused -> reset it. + */ + printf("%s: Invalid CSW: sig 0x%08x should be 0x%08x\n", + device_get_nameunit(sc->sc_dev), + UGETDW(sc->csw.dCSWSignature), + CSWSIGNATURE); + + umass_bbb_reset(sc, STATUS_WIRE_FAILED); + return; + } else if (UGETDW(sc->csw.dCSWTag) + != UGETDW(sc->cbw.dCBWTag)) { + printf("%s: Invalid CSW: tag %d should be %d\n", + device_get_nameunit(sc->sc_dev), + UGETDW(sc->csw.dCSWTag), + UGETDW(sc->cbw.dCBWTag)); + + umass_bbb_reset(sc, STATUS_WIRE_FAILED); + return; + + /* CSW is valid here */ + } else if (sc->csw.bCSWStatus > CSWSTATUS_PHASE) { + printf("%s: Invalid CSW: status %d > %d\n", + device_get_nameunit(sc->sc_dev), + sc->csw.bCSWStatus, + CSWSTATUS_PHASE); + + umass_bbb_reset(sc, STATUS_WIRE_FAILED); + return; + } else if (sc->csw.bCSWStatus == CSWSTATUS_PHASE) { + printf("%s: Phase Error, residue = %d\n", + device_get_nameunit(sc->sc_dev), Residue); + + umass_bbb_reset(sc, STATUS_WIRE_FAILED); + return; + + } else if (sc->transfer_actlen > sc->transfer_datalen) { + /* Buffer overrun! Don't let this go by unnoticed */ + panic("%s: transferred %db instead of %db", + device_get_nameunit(sc->sc_dev), + sc->transfer_actlen, sc->transfer_datalen); + + } else if (sc->csw.bCSWStatus == CSWSTATUS_FAILED) { + DPRINTF(UDMASS_BBB, ("%s: Command Failed, res = %d\n", + device_get_nameunit(sc->sc_dev), Residue)); + + /* SCSI command failed but transfer was succesful */ + sc->transfer_state = TSTATE_IDLE; + sc->transfer_cb(sc, sc->transfer_priv, Residue, + STATUS_CMD_FAILED); + return; + + } else { /* success */ + sc->transfer_state = TSTATE_IDLE; + sc->transfer_cb(sc, sc->transfer_priv, Residue, + STATUS_CMD_OK); + + return; + } + + /***** Bulk Reset *****/ + case TSTATE_BBB_RESET1: + if (err) + printf("%s: BBB reset failed, %s\n", + device_get_nameunit(sc->sc_dev), usbd_errstr(err)); + + umass_clear_endpoint_stall(sc, + sc->bulkin, sc->bulkin_pipe, TSTATE_BBB_RESET2, + sc->transfer_xfer[XFER_BBB_RESET2]); + + return; + case TSTATE_BBB_RESET2: + if (err) /* should not occur */ + printf("%s: BBB bulk-in clear stall failed, %s\n", + device_get_nameunit(sc->sc_dev), usbd_errstr(err)); + /* no error recovery, otherwise we end up in a loop */ + + umass_clear_endpoint_stall(sc, + sc->bulkout, sc->bulkout_pipe, TSTATE_BBB_RESET3, + sc->transfer_xfer[XFER_BBB_RESET3]); + + return; + case TSTATE_BBB_RESET3: + if (err) /* should not occur */ + printf("%s: BBB bulk-out clear stall failed, %s\n", + device_get_nameunit(sc->sc_dev), usbd_errstr(err)); + /* no error recovery, otherwise we end up in a loop */ + + sc->transfer_state = TSTATE_IDLE; + if (sc->transfer_priv) { + sc->transfer_cb(sc, sc->transfer_priv, + sc->transfer_datalen, + sc->transfer_status); + } + + return; + + /***** Default *****/ + default: + panic("%s: Unknown state %d", + device_get_nameunit(sc->sc_dev), sc->transfer_state); + } +} + +static int +umass_bbb_get_max_lun(struct umass_softc *sc) +{ + usbd_device_handle udev; + usb_device_request_t req; + usbd_status err; + usb_interface_descriptor_t *id; + int maxlun = 0; + u_int8_t buf = 0; + + usbd_interface2device_handle(sc->iface, &udev); + id = usbd_get_interface_descriptor(sc->iface); + + /* 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); + USETW(req.wIndex, id->bInterfaceNumber); + USETW(req.wLength, 1); + + err = usbd_do_request(udev, &req, &buf); + switch (err) { + case USBD_NORMAL_COMPLETION: + maxlun = buf; + DPRINTF(UDMASS_BBB, ("%s: Max Lun is %d\n", + device_get_nameunit(sc->sc_dev), maxlun)); + break; + case USBD_STALLED: + case USBD_SHORT_XFER: + default: + /* Device doesn't support Get Max Lun request. */ + printf("%s: Get Max Lun not supported (%s)\n", + device_get_nameunit(sc->sc_dev), usbd_errstr(err)); + /* XXX Should we port_reset the device? */ + break; + } + + return(maxlun); +} + +/* + * Command/Bulk/Interrupt (CBI) specific functions + */ + +static int +umass_cbi_adsc(struct umass_softc *sc, char *buffer, int buflen, + usbd_xfer_handle xfer) +{ + usbd_device_handle udev; + + KASSERT(sc->proto & (UMASS_PROTO_CBI|UMASS_PROTO_CBI_I), + ("%s: umass_cbi_adsc: wrong sc->proto 0x%02x\n", + device_get_nameunit(sc->sc_dev), sc->proto)); + + usbd_interface2device_handle(sc->iface, &udev); + + sc->request.bmRequestType = UT_WRITE_CLASS_INTERFACE; + sc->request.bRequest = UR_CBI_ADSC; + USETW(sc->request.wValue, 0); + USETW(sc->request.wIndex, sc->ifaceno); + USETW(sc->request.wLength, buflen); + return umass_setup_ctrl_transfer(sc, udev, &sc->request, buffer, + buflen, 0, xfer); +} + + +static void +umass_cbi_reset(struct umass_softc *sc, int status) +{ + int i; +# define SEND_DIAGNOSTIC_CMDLEN 12 + + KASSERT(sc->proto & (UMASS_PROTO_CBI|UMASS_PROTO_CBI_I), + ("%s: umass_cbi_reset: wrong sc->proto 0x%02x\n", + device_get_nameunit(sc->sc_dev), sc->proto)); + + /* + * 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, states: + * TSTATE_CBI_RESET1 + * TSTATE_CBI_RESET2 + * TSTATE_CBI_RESET3 + * + * If the reset doesn't succeed, the device should be port reset. + */ + + DPRINTF(UDMASS_CBI, ("%s: CBI Reset\n", + device_get_nameunit(sc->sc_dev))); + + KASSERT(sizeof(sc->cbl) >= SEND_DIAGNOSTIC_CMDLEN, + ("%s: CBL struct is too small (%ld < %d)\n", + device_get_nameunit(sc->sc_dev), + (long)sizeof(sc->cbl), SEND_DIAGNOSTIC_CMDLEN)); + + sc->transfer_state = TSTATE_CBI_RESET1; + sc->transfer_status = status; + + /* 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 spec). + */ + sc->cbl[0] = 0x1d; /* Command Block Reset */ + sc->cbl[1] = 0x04; + for (i = 2; i < SEND_DIAGNOSTIC_CMDLEN; i++) + sc->cbl[i] = 0xff; + + umass_cbi_adsc(sc, sc->cbl, SEND_DIAGNOSTIC_CMDLEN, + sc->transfer_xfer[XFER_CBI_RESET1]); + /* XXX if the command fails we should reset the port on the hub */ +} + +static void +umass_cbi_transfer(struct umass_softc *sc, int lun, + void *cmd, int cmdlen, void *data, int datalen, int dir, + u_int timeout, transfer_cb_f cb, void *priv) +{ + KASSERT(sc->proto & (UMASS_PROTO_CBI|UMASS_PROTO_CBI_I), + ("%s: umass_cbi_transfer: wrong sc->proto 0x%02x\n", + device_get_nameunit(sc->sc_dev), sc->proto)); + + /* Be a little generous. */ + sc->timeout = timeout + UMASS_TIMEOUT; + + /* + * Do a CBI transfer with cmdlen bytes from cmd, possibly + * a data phase of datalen bytes from/to the device and finally a + * csw read phase. + * If the data direction was inbound a maximum of datalen bytes + * is stored in the buffer pointed to by data. + * + * umass_cbi_transfer initialises the transfer and lets the state + * machine in umass_cbi_state handle the completion. It uses the + * following states: + * TSTATE_CBI_COMMAND + * -> XXX fill in + * + * An error in any of those states will invoke + * umass_cbi_reset. + */ + + /* check the given arguments */ + KASSERT(datalen == 0 || data != NULL, + ("%s: datalen > 0, but no buffer",device_get_nameunit(sc->sc_dev))); + KASSERT(datalen == 0 || dir != DIR_NONE, + ("%s: direction is NONE while datalen is not zero\n", + device_get_nameunit(sc->sc_dev))); + + /* store the details for the data transfer phase */ + sc->transfer_dir = dir; + sc->transfer_data = data; + sc->transfer_datalen = datalen; + sc->transfer_actlen = 0; + sc->transfer_cb = cb; + sc->transfer_priv = priv; + sc->transfer_status = STATUS_CMD_OK; + + /* move from idle to the command state */ + sc->transfer_state = TSTATE_CBI_COMMAND; + + DIF(UDMASS_CBI, umass_cbi_dump_cmd(sc, cmd, cmdlen)); + + /* Send the Command Block from host to device via control endpoint. */ + if (umass_cbi_adsc(sc, cmd, cmdlen, sc->transfer_xfer[XFER_CBI_CB])) + umass_cbi_reset(sc, STATUS_WIRE_FAILED); +} + +static void +umass_cbi_state(usbd_xfer_handle xfer, usbd_private_handle priv, + usbd_status err) +{ + struct umass_softc *sc = (struct umass_softc *) priv; + + KASSERT(sc->proto & (UMASS_PROTO_CBI|UMASS_PROTO_CBI_I), + ("%s: umass_cbi_state: wrong sc->proto 0x%02x\n", + device_get_nameunit(sc->sc_dev), sc->proto)); + + /* + * State handling for CBI transfers. + */ + + DPRINTF(UDMASS_CBI, ("%s: Handling CBI state %d (%s), xfer=%p, %s\n", + device_get_nameunit(sc->sc_dev), sc->transfer_state, + states[sc->transfer_state], xfer, usbd_errstr(err))); + + /* Give up if the device has detached. */ + if (sc->flags & UMASS_FLAGS_GONE) { + sc->transfer_state = TSTATE_IDLE; + sc->transfer_cb(sc, sc->transfer_priv, sc->transfer_datalen, + STATUS_CMD_FAILED); + return; + } + + switch (sc->transfer_state) { + + /***** CBI Transfer *****/ + case TSTATE_CBI_COMMAND: + if (err == USBD_STALLED) { + DPRINTF(UDMASS_CBI, ("%s: Command Transport failed\n", + device_get_nameunit(sc->sc_dev))); + /* Status transport by control pipe (section 2.3.2.1). + * The command contained in the command block failed. + * + * The control pipe has already been unstalled by the + * USB stack. + * Section 2.4.3.1.1 states that the bulk in endpoints + * should not be stalled at this point. + */ + + sc->transfer_state = TSTATE_IDLE; + sc->transfer_cb(sc, sc->transfer_priv, + sc->transfer_datalen, + STATUS_CMD_FAILED); + + return; + } else if (err) { + DPRINTF(UDMASS_CBI, ("%s: failed to send ADSC\n", + device_get_nameunit(sc->sc_dev))); + umass_cbi_reset(sc, STATUS_WIRE_FAILED); + + return; + } + + sc->transfer_state = TSTATE_CBI_DATA; + if (sc->transfer_dir == DIR_IN) { + if (umass_setup_transfer(sc, sc->bulkin_pipe, + sc->transfer_data, sc->transfer_datalen, + USBD_SHORT_XFER_OK, + sc->transfer_xfer[XFER_CBI_DATA])) + umass_cbi_reset(sc, STATUS_WIRE_FAILED); + + } else if (sc->transfer_dir == DIR_OUT) { + if (umass_setup_transfer(sc, sc->bulkout_pipe, + sc->transfer_data, sc->transfer_datalen, + 0, /* fixed length transfer */ + sc->transfer_xfer[XFER_CBI_DATA])) + umass_cbi_reset(sc, STATUS_WIRE_FAILED); + + } else if (sc->proto & UMASS_PROTO_CBI_I) { + DPRINTF(UDMASS_CBI, ("%s: no data phase\n", + device_get_nameunit(sc->sc_dev))); + sc->transfer_state = TSTATE_CBI_STATUS; + if (umass_setup_transfer(sc, sc->intrin_pipe, + &sc->sbl, sizeof(sc->sbl), + 0, /* fixed length transfer */ + sc->transfer_xfer[XFER_CBI_STATUS])){ + umass_cbi_reset(sc, STATUS_WIRE_FAILED); + } + } else { + DPRINTF(UDMASS_CBI, ("%s: no data phase\n", + device_get_nameunit(sc->sc_dev))); + /* No command completion interrupt. Request + * sense data. + */ + sc->transfer_state = TSTATE_IDLE; + sc->transfer_cb(sc, sc->transfer_priv, + 0, STATUS_CMD_UNKNOWN); + } + + return; + + case TSTATE_CBI_DATA: + /* retrieve the length of the transfer that was done */ + usbd_get_xfer_status(xfer,NULL,NULL,&sc->transfer_actlen,NULL); + + if (err) { + DPRINTF(UDMASS_CBI, ("%s: Data-%s %db failed, " + "%s\n", device_get_nameunit(sc->sc_dev), + (sc->transfer_dir == DIR_IN?"in":"out"), + sc->transfer_datalen,usbd_errstr(err))); + + if (err == USBD_STALLED) { + umass_clear_endpoint_stall(sc, + sc->bulkin, sc->bulkin_pipe, + TSTATE_CBI_DCLEAR, + sc->transfer_xfer[XFER_CBI_DCLEAR]); + } else { + umass_cbi_reset(sc, STATUS_WIRE_FAILED); + } + return; + } + + DIF(UDMASS_CBI, if (sc->transfer_dir == DIR_IN) + umass_dump_buffer(sc, sc->transfer_data, + sc->transfer_actlen, 48)); + + if (sc->proto & UMASS_PROTO_CBI_I) { + sc->transfer_state = TSTATE_CBI_STATUS; + if (umass_setup_transfer(sc, sc->intrin_pipe, + &sc->sbl, sizeof(sc->sbl), + 0, /* fixed length transfer */ + sc->transfer_xfer[XFER_CBI_STATUS])){ + umass_cbi_reset(sc, STATUS_WIRE_FAILED); + } + } else { + /* No command completion interrupt. Request + * sense to get status of command. + */ + sc->transfer_state = TSTATE_IDLE; + sc->transfer_cb(sc, sc->transfer_priv, + sc->transfer_datalen - sc->transfer_actlen, + STATUS_CMD_UNKNOWN); + } + return; + + case TSTATE_CBI_STATUS: + if (err) { + DPRINTF(UDMASS_CBI, ("%s: Status Transport failed\n", + device_get_nameunit(sc->sc_dev))); + /* Status transport by interrupt pipe (section 2.3.2.2). + */ + + if (err == USBD_STALLED) { + umass_clear_endpoint_stall(sc, + sc->intrin, sc->intrin_pipe, + TSTATE_CBI_SCLEAR, + sc->transfer_xfer[XFER_CBI_SCLEAR]); + } else { + umass_cbi_reset(sc, STATUS_WIRE_FAILED); + } + return; + } + + /* Dissect the information in the buffer */ + + if (sc->proto & UMASS_PROTO_UFI) { + int status; + + /* Section 3.4.3.1.3 specifies that the UFI command + * protocol returns an ASC and ASCQ in the interrupt + * data block. + */ + + DPRINTF(UDMASS_CBI, ("%s: UFI CCI, ASC = 0x%02x, " + "ASCQ = 0x%02x\n", + device_get_nameunit(sc->sc_dev), + sc->sbl.ufi.asc, sc->sbl.ufi.ascq)); + + if (sc->sbl.ufi.asc == 0 && sc->sbl.ufi.ascq == 0) + status = STATUS_CMD_OK; + else + status = STATUS_CMD_FAILED; + + sc->transfer_state = TSTATE_IDLE; + sc->transfer_cb(sc, sc->transfer_priv, + sc->transfer_datalen - sc->transfer_actlen, + status); + } else { + /* Command Interrupt Data Block */ + DPRINTF(UDMASS_CBI, ("%s: type=0x%02x, value=0x%02x\n", + device_get_nameunit(sc->sc_dev), + sc->sbl.common.type, sc->sbl.common.value)); + + if (sc->sbl.common.type == IDB_TYPE_CCI) { + int err; + + if ((sc->sbl.common.value&IDB_VALUE_STATUS_MASK) + == IDB_VALUE_PASS) { + err = STATUS_CMD_OK; + } else if ((sc->sbl.common.value & IDB_VALUE_STATUS_MASK) + == IDB_VALUE_FAIL || + (sc->sbl.common.value & IDB_VALUE_STATUS_MASK) + == IDB_VALUE_PERSISTENT) { + err = STATUS_CMD_FAILED; + } else { + err = STATUS_WIRE_FAILED; + } + + sc->transfer_state = TSTATE_IDLE; + sc->transfer_cb(sc, sc->transfer_priv, + sc->transfer_datalen-sc->transfer_actlen, + err); + } + } + return; + + case TSTATE_CBI_DCLEAR: + if (err) { /* should not occur */ + printf("%s: CBI bulk-in/out stall clear failed, %s\n", + device_get_nameunit(sc->sc_dev), usbd_errstr(err)); + umass_cbi_reset(sc, STATUS_WIRE_FAILED); + } + + sc->transfer_state = TSTATE_IDLE; + sc->transfer_cb(sc, sc->transfer_priv, + sc->transfer_datalen, + STATUS_CMD_FAILED); + return; + + case TSTATE_CBI_SCLEAR: + if (err) /* should not occur */ + printf("%s: CBI intr-in stall clear failed, %s\n", + device_get_nameunit(sc->sc_dev), usbd_errstr(err)); + + /* Something really bad is going on. Reset the device */ + umass_cbi_reset(sc, STATUS_CMD_FAILED); + return; + + /***** CBI Reset *****/ + case TSTATE_CBI_RESET1: + if (err) + printf("%s: CBI reset failed, %s\n", + device_get_nameunit(sc->sc_dev), usbd_errstr(err)); + + umass_clear_endpoint_stall(sc, + sc->bulkin, sc->bulkin_pipe, TSTATE_CBI_RESET2, + sc->transfer_xfer[XFER_CBI_RESET2]); + + return; + case TSTATE_CBI_RESET2: + if (err) /* should not occur */ + printf("%s: CBI bulk-in stall clear failed, %s\n", + device_get_nameunit(sc->sc_dev), usbd_errstr(err)); + /* no error recovery, otherwise we end up in a loop */ + + umass_clear_endpoint_stall(sc, + sc->bulkout, sc->bulkout_pipe, TSTATE_CBI_RESET3, + sc->transfer_xfer[XFER_CBI_RESET3]); + + return; + case TSTATE_CBI_RESET3: + if (err) /* should not occur */ + printf("%s: CBI bulk-out stall clear failed, %s\n", + device_get_nameunit(sc->sc_dev), usbd_errstr(err)); + /* no error recovery, otherwise we end up in a loop */ + + sc->transfer_state = TSTATE_IDLE; + if (sc->transfer_priv) { + sc->transfer_cb(sc, sc->transfer_priv, + sc->transfer_datalen, + sc->transfer_status); + } + + return; + + + /***** Default *****/ + default: + panic("%s: Unknown state %d", + device_get_nameunit(sc->sc_dev), sc->transfer_state); + } +} + + + + +/* + * 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->umass_sim = cam_sim_alloc(umass_cam_action, umass_cam_poll, + DEVNAME_SIM, + sc /*priv*/, + device_get_unit(sc->sc_dev) /*unit number*/, + &Giant, + 1 /*maximum device openings*/, + 0 /*maximum tagged device openings*/, + devq); + if (sc->umass_sim == NULL) { + cam_simq_free(devq); + return(ENOMEM); + } + + if(xpt_bus_register(sc->umass_sim, NULL, device_get_unit(sc->sc_dev)) != + CAM_SUCCESS) + return(ENOMEM); + + return(0); +} + +static void +umass_cam_rescan_callback(struct cam_periph *periph, union ccb *ccb) +{ +#ifdef USB_DEBUG + if (ccb->ccb_h.status != CAM_REQ_CMP) { + DPRINTF(UDMASS_SCSI, ("%s:%d Rescan failed, 0x%04x\n", + periph->periph_name, periph->unit_number, + ccb->ccb_h.status)); + } else { + DPRINTF(UDMASS_SCSI, ("%s%d: Rescan succeeded\n", + periph->periph_name, periph->unit_number)); + } +#endif + + xpt_free_path(ccb->ccb_h.path); + free(ccb, M_USBDEV); +} + +static void +umass_cam_rescan(void *addr) +{ + struct umass_softc *sc = (struct umass_softc *) addr; + struct cam_path *path; + union ccb *ccb; + + DPRINTF(UDMASS_SCSI, ("scbus%d: scanning for %s:%d:%d:%d\n", + cam_sim_path(sc->umass_sim), + device_get_nameunit(sc->sc_dev), cam_sim_path(sc->umass_sim), + device_get_unit(sc->sc_dev), CAM_LUN_WILDCARD)); + + ccb = malloc(sizeof(union ccb), M_USBDEV, M_NOWAIT | M_ZERO); + if (ccb == NULL) + return; + if (xpt_create_path(&path, xpt_periph, cam_sim_path(sc->umass_sim), + device_get_unit(sc->sc_dev), CAM_LUN_WILDCARD) + != CAM_REQ_CMP) { + free(ccb, M_USBDEV); + return; + } + + xpt_setup_ccb(&ccb->ccb_h, path, 5/*priority (low)*/); + ccb->ccb_h.func_code = XPT_SCAN_BUS; + ccb->ccb_h.cbfcnp = umass_cam_rescan_callback; + ccb->crcn.flags = CAM_FLAG_NONE; + xpt_action(ccb); + + /* The scan is in progress now. */ +} + +static int +umass_cam_attach(struct umass_softc *sc) +{ +#ifndef USB_DEBUG + if (bootverbose) +#endif + printf("%s:%d:%d:%d: Attached to scbus%d\n", + device_get_nameunit(sc->sc_dev), cam_sim_path(sc->umass_sim), + device_get_unit(sc->sc_dev), CAM_LUN_WILDCARD, + cam_sim_path(sc->umass_sim)); + + if (!cold) { + /* Notify CAM of the new device after a short delay. Any + * failure is benign, as the user can still do it by hand + * (camcontrol rescan <busno>). Only do this if we are not + * booting, because CAM does a scan after booting has + * completed, when interrupts have been enabled. + */ + + callout_reset(&sc->cam_scsi_rescan_ch, MS_TO_TICKS(200), + umass_cam_rescan, sc); + } + + return(0); /* always succesfull */ +} + +/* umass_cam_detach + * detach from the CAM layer + */ + +static int +umass_cam_detach_sim(struct umass_softc *sc) +{ + if (sc->umass_sim) { + if (xpt_bus_deregister(cam_sim_path(sc->umass_sim))) + cam_sim_free(sc->umass_sim, /*free_devq*/TRUE); + else + return(EBUSY); + + sc->umass_sim = NULL; + } + + return(0); +} + +/* 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; + + /* The softc is still there, but marked as going away. umass_cam_detach + * has not yet notified CAM of the lost device however. + */ + if (sc && (sc->flags & UMASS_FLAGS_GONE)) { + DPRINTF(UDMASS_SCSI, ("%s:%d:%d:%d:func_code 0x%04x: " + "Invalid target (gone)\n", + device_get_nameunit(sc->sc_dev), cam_sim_path(sc->umass_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); + return; + } + + /* 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) { + printf("%s:%d:%d:%d:func_code 0x%04x: " + "Invalid target (target needed)\n", + DEVNAME_SIM, cam_sim_path(sc->umass_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); + return; + } + 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(UDMASS_SCSI, ("%s:%d:%d:%d:func_code 0x%04x: " + "Invalid target (no wildcard)\n", + DEVNAME_SIM, cam_sim_path(sc->umass_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); + return; + } + 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: + { + struct ccb_scsiio *csio = &ccb->csio; /* deref union */ + int dir; + unsigned char *cmd; + int cmdlen; + unsigned char *rcmd; + int rcmdlen; + + DPRINTF(UDMASS_SCSI, ("%s:%d:%d:%d:XPT_SCSI_IO: " + "cmd: 0x%02x, flags: 0x%02x, " + "%db cmd/%db data/%db sense\n", + device_get_nameunit(sc->sc_dev), cam_sim_path(sc->umass_sim), + ccb->ccb_h.target_id, ccb->ccb_h.target_lun, + csio->cdb_io.cdb_bytes[0], + ccb->ccb_h.flags & CAM_DIR_MASK, + csio->cdb_len, csio->dxfer_len, + csio->sense_len)); + + /* clear the end of the buffer to make sure we don't send out + * garbage. + */ + DIF(UDMASS_SCSI, if ((ccb->ccb_h.flags & CAM_DIR_MASK) + == CAM_DIR_OUT) + umass_dump_buffer(sc, csio->data_ptr, + csio->dxfer_len, 48)); + + if (sc->transfer_state != TSTATE_IDLE) { + DPRINTF(UDMASS_SCSI, ("%s:%d:%d:%d:XPT_SCSI_IO: " + "I/O in progress, deferring (state %d, %s)\n", + device_get_nameunit(sc->sc_dev), cam_sim_path(sc->umass_sim), + ccb->ccb_h.target_id, ccb->ccb_h.target_lun, + sc->transfer_state,states[sc->transfer_state])); + ccb->ccb_h.status = CAM_SCSI_BUSY; + xpt_done(ccb); + return; + } + + switch(ccb->ccb_h.flags&CAM_DIR_MASK) { + case CAM_DIR_IN: + dir = DIR_IN; + break; + case CAM_DIR_OUT: + dir = DIR_OUT; + break; + default: + dir = DIR_NONE; + } + + ccb->ccb_h.status = CAM_REQ_INPROG | CAM_SIM_QUEUED; + + + if (csio->ccb_h.flags & CAM_CDB_POINTER) { + cmd = (unsigned char *) csio->cdb_io.cdb_ptr; + } else { + cmd = (unsigned char *) &csio->cdb_io.cdb_bytes; + } + cmdlen = csio->cdb_len; + rcmd = (unsigned char *) &sc->cam_scsi_command; + rcmdlen = sizeof(sc->cam_scsi_command); + + /* sc->transform will convert the command to the command + * (format) needed by the specific command set and return + * the converted command in a buffer pointed to be rcmd. + * We pass in a buffer, but if the command does not + * have to be transformed it returns a ptr to the original + * buffer (see umass_scsi_transform). + */ + + switch (sc->transform(sc, cmd, cmdlen, &rcmd, &rcmdlen)) { + case 1: + /* + * Handle EVPD inquiry for broken devices first + * NO_INQUIRY also implies NO_INQUIRY_EVPD + */ + if ((sc->quirks & (NO_INQUIRY_EVPD | NO_INQUIRY)) && + rcmd[0] == INQUIRY && (rcmd[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); + return; + } + /* Return fake inquiry data for broken devices */ + if ((sc->quirks & NO_INQUIRY) && rcmd[0] == INQUIRY) { + struct ccb_scsiio *csio = &ccb->csio; + + memcpy(csio->data_ptr, &fake_inq_data, + sizeof(fake_inq_data)); + csio->scsi_status = SCSI_STATUS_OK; + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + return; + } + if ((sc->quirks & NO_SYNCHRONIZE_CACHE) && + rcmd[0] == SYNCHRONIZE_CACHE) { + struct ccb_scsiio *csio = &ccb->csio; + + csio->scsi_status = SCSI_STATUS_OK; + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + return; + } + if ((sc->quirks & FORCE_SHORT_INQUIRY) && + rcmd[0] == INQUIRY) { + csio->dxfer_len = SHORT_INQUIRY_LENGTH; + } + sc->transfer(sc, ccb->ccb_h.target_lun, rcmd, rcmdlen, + csio->data_ptr, + csio->dxfer_len, dir, ccb->ccb_h.timeout, + umass_cam_cb, (void *) ccb); + break; + case 0: + ccb->ccb_h.status = CAM_REQ_INVALID; + xpt_done(ccb); + break; + case 2: + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + + break; + } + case XPT_PATH_INQ: + { + struct ccb_pathinq *cpi = &ccb->cpi; + + DPRINTF(UDMASS_SCSI, ("%s:%d:%d:%d:XPT_PATH_INQ:.\n", + (sc == NULL? DEVNAME_SIM:device_get_nameunit(sc->sc_dev)), + cam_sim_path(sc->umass_sim), + 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; + strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); + strncpy(cpi->hba_vid, "USB SCSI", HBA_IDLEN); + strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN); + cpi->unit_number = cam_sim_unit(sim); + cpi->bus_id = device_get_unit(sc->sc_dev); + cpi->protocol = PROTO_SCSI; + cpi->protocol_version = SCSI_REV_2; + cpi->transport = XPORT_USB; + cpi->transport_version = 0; + + if (sc == NULL) { + cpi->base_transfer_speed = 0; + cpi->max_lun = 0; + } else { + if (sc->quirks & FLOPPY_SPEED) { + cpi->base_transfer_speed = + UMASS_FLOPPY_TRANSFER_SPEED; + } else if (usbd_get_speed(sc->sc_udev) == + USB_SPEED_HIGH) { + cpi->base_transfer_speed = + UMASS_HIGH_TRANSFER_SPEED; + } else { + cpi->base_transfer_speed = + UMASS_FULL_TRANSFER_SPEED; + } + cpi->max_lun = sc->maxlun; + } + + cpi->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + case XPT_RESET_DEV: + { + DPRINTF(UDMASS_SCSI, ("%s:%d:%d:%d:XPT_RESET_DEV:.\n", + device_get_nameunit(sc->sc_dev), cam_sim_path(sc->umass_sim), + ccb->ccb_h.target_id, ccb->ccb_h.target_lun)); + + ccb->ccb_h.status = CAM_REQ_INPROG; + umass_reset(sc, umass_cam_cb, (void *) ccb); + break; + } + case XPT_GET_TRAN_SETTINGS: + { + struct ccb_trans_settings *cts = &ccb->cts; + cts->protocol = PROTO_SCSI; + cts->protocol_version = SCSI_REV_2; + cts->transport = XPORT_USB; + cts->transport_version = 0; + cts->xport_specific.valid = 0; + + + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + case XPT_SET_TRAN_SETTINGS: + { + DPRINTF(UDMASS_SCSI, ("%s:%d:%d:%d:XPT_SET_TRAN_SETTINGS:.\n", + device_get_nameunit(sc->sc_dev), cam_sim_path(sc->umass_sim), + ccb->ccb_h.target_id, ccb->ccb_h.target_lun)); + + ccb->ccb_h.status = CAM_FUNC_NOTAVAIL; + xpt_done(ccb); + break; + } + case XPT_CALC_GEOMETRY: + { + cam_calc_geometry(&ccb->ccg, /*extended*/1); + xpt_done(ccb); + break; + } + case XPT_NOOP: + { + DPRINTF(UDMASS_SCSI, ("%s:%d:%d:%d:XPT_NOOP:.\n", + (sc == NULL? DEVNAME_SIM:device_get_nameunit(sc->sc_dev)), + cam_sim_path(sc->umass_sim), + ccb->ccb_h.target_id, ccb->ccb_h.target_lun)); + + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + default: + DPRINTF(UDMASS_SCSI, ("%s:%d:%d:%d:func_code 0x%04x: " + "Not implemented\n", + (sc == NULL? DEVNAME_SIM:device_get_nameunit(sc->sc_dev)), + cam_sim_path(sc->umass_sim), + 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; + } +} + +static void +umass_cam_poll(struct cam_sim *sim) +{ + struct umass_softc *sc = (struct umass_softc *) sim->softc; + + DPRINTF(UDMASS_SCSI, ("%s: CAM poll\n", + device_get_nameunit(sc->sc_dev))); + + usbd_set_polling(sc->sc_udev, 1); + usbd_dopoll(sc->iface); + usbd_set_polling(sc->sc_udev, 0); +} + + +/* umass_cam_cb + * finalise a completed CAM command + */ + +static void +umass_cam_cb(struct umass_softc *sc, void *priv, int residue, int status) +{ + union ccb *ccb = (union ccb *) priv; + struct ccb_scsiio *csio = &ccb->csio; /* deref union */ + + /* If the device is gone, just fail the request. */ + if (sc->flags & UMASS_FLAGS_GONE) { + ccb->ccb_h.status = CAM_TID_INVALID; + xpt_done(ccb); + return; + } + + csio->resid = residue; + + switch (status) { + case STATUS_CMD_OK: + ccb->ccb_h.status = CAM_REQ_CMP; + if ((sc->quirks & READ_CAPACITY_OFFBY1) && + (ccb->ccb_h.func_code == XPT_SCSI_IO) && + (csio->cdb_io.cdb_bytes[0] == READ_CAPACITY)) { + struct scsi_read_capacity_data *rcap; + uint32_t maxsector; + + rcap = (struct scsi_read_capacity_data *)csio->data_ptr; + maxsector = scsi_4btoul(rcap->addr) - 1; + scsi_ulto4b(maxsector, rcap->addr); + } + xpt_done(ccb); + break; + + case STATUS_CMD_UNKNOWN: + case STATUS_CMD_FAILED: + switch (ccb->ccb_h.func_code) { + case XPT_SCSI_IO: + { + unsigned char *rcmd; + int rcmdlen; + + /* fetch sense data */ + /* the rest of the command was filled in at attach */ + sc->cam_scsi_sense.length = csio->sense_len; + + DPRINTF(UDMASS_SCSI,("%s: Fetching %db sense data\n", + device_get_nameunit(sc->sc_dev), csio->sense_len)); + + rcmd = (unsigned char *) &sc->cam_scsi_command; + rcmdlen = sizeof(sc->cam_scsi_command); + + if (sc->transform(sc, + (unsigned char *) &sc->cam_scsi_sense, + sizeof(sc->cam_scsi_sense), + &rcmd, &rcmdlen) == 1) { + if ((sc->quirks & FORCE_SHORT_INQUIRY) && (rcmd[0] == INQUIRY)) { + csio->sense_len = SHORT_INQUIRY_LENGTH; + } + sc->transfer(sc, ccb->ccb_h.target_lun, + rcmd, rcmdlen, + &csio->sense_data, + csio->sense_len, DIR_IN, ccb->ccb_h.timeout, + umass_cam_sense_cb, (void *) ccb); + } else { + panic("transform(REQUEST_SENSE) failed"); + } + break; + } + case XPT_RESET_DEV: /* Reset failed */ + ccb->ccb_h.status = CAM_REQ_CMP_ERR; + xpt_done(ccb); + break; + default: + panic("umass_cam_cb called for func_code %d", + ccb->ccb_h.func_code); + } + break; + + case STATUS_WIRE_FAILED: + /* the wire protocol failed and will have recovered + * (hopefully). We return an error to CAM and let CAM retry + * the command if necessary. + */ + ccb->ccb_h.status = CAM_REQ_CMP_ERR; + xpt_done(ccb); + break; + default: + panic("%s: Unknown status %d in umass_cam_cb", + device_get_nameunit(sc->sc_dev), status); + } +} + +/* Finalise a completed autosense operation + */ +static void +umass_cam_sense_cb(struct umass_softc *sc, void *priv, int residue, int status) +{ + union ccb *ccb = (union ccb *) priv; + struct ccb_scsiio *csio = &ccb->csio; /* deref union */ + unsigned char *rcmd; + int rcmdlen; + + if (sc->flags & UMASS_FLAGS_GONE) { + ccb->ccb_h.status = CAM_AUTOSENSE_FAIL; + xpt_done(ccb); + return; + } + + switch (status) { + case STATUS_CMD_OK: + case STATUS_CMD_UNKNOWN: + case STATUS_CMD_FAILED: + /* Getting sense data always succeeds (apart from wire + * failures). + */ + if ((sc->quirks & RS_NO_CLEAR_UA) + && csio->cdb_io.cdb_bytes[0] == INQUIRY + && (csio->sense_data.flags & SSD_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 ((csio->sense_data.flags & SSD_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->quirks & RS_NO_CLEAR_UA) && + (csio->cdb_io.cdb_bytes[0] == READ_CAPACITY) && + ((csio->sense_data.flags & SSD_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; + csio->scsi_status = SCSI_STATUS_CHECK_COND; + +#if 0 + DELAY(300000); +#endif + + DPRINTF(UDMASS_SCSI,("%s: Doing a sneaky" + "TEST_UNIT_READY\n", + device_get_nameunit(sc->sc_dev))); + + /* the rest of the command was filled in at attach */ + + rcmd = (unsigned char *) &sc->cam_scsi_command2; + rcmdlen = sizeof(sc->cam_scsi_command2); + + if (sc->transform(sc, + (unsigned char *) + &sc->cam_scsi_test_unit_ready, + sizeof(sc->cam_scsi_test_unit_ready), + &rcmd, &rcmdlen) == 1) { + sc->transfer(sc, ccb->ccb_h.target_lun, + rcmd, rcmdlen, + NULL, 0, DIR_NONE, ccb->ccb_h.timeout, + umass_cam_quirk_cb, (void *) ccb); + } else { + panic("transform(TEST_UNIT_READY) failed"); + } + break; + } else { + ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR + | CAM_AUTOSNS_VALID; + csio->scsi_status = SCSI_STATUS_CHECK_COND; + } + xpt_done(ccb); + break; + + default: + DPRINTF(UDMASS_SCSI, ("%s: Autosense failed, status %d\n", + device_get_nameunit(sc->sc_dev), 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, void *priv, int residue, int status) +{ + union ccb *ccb = (union ccb *) priv; + + DPRINTF(UDMASS_SCSI, ("%s: Test unit ready returned status %d\n", + device_get_nameunit(sc->sc_dev), status)); + + if (sc->flags & UMASS_FLAGS_GONE) { + ccb->ccb_h.status = CAM_TID_INVALID; + xpt_done(ccb); + return; + } +#if 0 + ccb->ccb_h.status = CAM_REQ_CMP; +#endif + 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 int +umass_scsi_transform(struct umass_softc *sc, unsigned char *cmd, int cmdlen, + unsigned char **rcmd, int *rcmdlen) +{ + switch (cmd[0]) { + case TEST_UNIT_READY: + if (sc->quirks & NO_TEST_UNIT_READY) { + KASSERT(*rcmdlen >= sizeof(struct scsi_start_stop_unit), + ("rcmdlen = %d < %ld, buffer too small", + *rcmdlen, + (long)sizeof(struct scsi_start_stop_unit))); + DPRINTF(UDMASS_SCSI, ("%s: Converted TEST_UNIT_READY " + "to START_UNIT\n", device_get_nameunit(sc->sc_dev))); + memset(*rcmd, 0, *rcmdlen); + (*rcmd)[0] = START_STOP_UNIT; + (*rcmd)[4] = SSS_START; + return 1; + } + /* fallthrough */ + case INQUIRY: + /* some drives wedge when asked for full inquiry information. */ + if (sc->quirks & FORCE_SHORT_INQUIRY) { + memcpy(*rcmd, cmd, cmdlen); + *rcmdlen = cmdlen; + (*rcmd)[4] = SHORT_INQUIRY_LENGTH; + return 1; + } + /* fallthrough */ + default: + *rcmd = cmd; /* We don't need to copy it */ + *rcmdlen = cmdlen; + } + + return 1; +} +/* RBC specific functions */ +static int +umass_rbc_transform(struct umass_softc *sc, unsigned char *cmd, int cmdlen, + unsigned char **rcmd, int *rcmdlen) +{ + switch (cmd[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: + if ((sc->quirks & RBC_PAD_TO_12) && cmdlen < 12) { + *rcmdlen = 12; + bcopy(cmd, *rcmd, cmdlen); + bzero(*rcmd + cmdlen, 12 - cmdlen); + } else { + *rcmd = cmd; /* We don't need to copy it */ + *rcmdlen = cmdlen; + } + return 1; + /* All other commands are not legal in RBC */ + default: + printf("%s: Unsupported RBC command 0x%02x", + device_get_nameunit(sc->sc_dev), cmd[0]); + printf("\n"); + return 0; /* failure */ + } +} + +/* + * UFI specific functions + */ +static int +umass_ufi_transform(struct umass_softc *sc, unsigned char *cmd, int cmdlen, + unsigned char **rcmd, int *rcmdlen) +{ + /* A UFI command is always 12 bytes in length */ + KASSERT(*rcmdlen >= UFI_COMMAND_LENGTH, + ("rcmdlen = %d < %d, buffer too small", + *rcmdlen, UFI_COMMAND_LENGTH)); + + *rcmdlen = UFI_COMMAND_LENGTH; + memset(*rcmd, 0, UFI_COMMAND_LENGTH); + + switch (cmd[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->quirks & NO_TEST_UNIT_READY) { + /* Some devices do not support this command. + * Start Stop Unit should give the same results + */ + DPRINTF(UDMASS_UFI, ("%s: Converted TEST_UNIT_READY " + "to START_UNIT\n", device_get_nameunit(sc->sc_dev))); + (*rcmd)[0] = START_STOP_UNIT; + (*rcmd)[4] = SSS_START; + } else { + memcpy(*rcmd, cmd, cmdlen); + } + return 1; + + 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: + memcpy(*rcmd, cmd, cmdlen); + return 1; + + /* + * 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: + printf("%s: Unsupported UFI command 0x%02x\n", + device_get_nameunit(sc->sc_dev), cmd[0]); + return 0; /* failure */ + } +} + +/* + * 8070i (ATAPI) specific functions + */ +static int +umass_atapi_transform(struct umass_softc *sc, unsigned char *cmd, int cmdlen, + unsigned char **rcmd, int *rcmdlen) +{ + /* An ATAPI command is always 12 bytes in length. */ + KASSERT(*rcmdlen >= ATAPI_COMMAND_LENGTH, + ("rcmdlen = %d < %d, buffer too small", + *rcmdlen, ATAPI_COMMAND_LENGTH)); + + *rcmdlen = ATAPI_COMMAND_LENGTH; + memset(*rcmd, 0, ATAPI_COMMAND_LENGTH); + + switch (cmd[0]) { + /* Commands of which the format has been verified. They should work. + * Copy the command into the (zeroed out) destination buffer. + */ + case INQUIRY: + memcpy(*rcmd, cmd, cmdlen); + /* some drives wedge when asked for full inquiry information. */ + if (sc->quirks & FORCE_SHORT_INQUIRY) + (*rcmd)[4] = SHORT_INQUIRY_LENGTH; + return 1; + + case TEST_UNIT_READY: + if (sc->quirks & NO_TEST_UNIT_READY) { + KASSERT(*rcmdlen >= sizeof(struct scsi_start_stop_unit), + ("rcmdlen = %d < %ld, buffer too small", + *rcmdlen, + (long)sizeof(struct scsi_start_stop_unit))); + DPRINTF(UDMASS_SCSI, ("%s: Converted TEST_UNIT_READY " + "to START_UNIT\n", device_get_nameunit(sc->sc_dev))); + memset(*rcmd, 0, *rcmdlen); + (*rcmd)[0] = START_STOP_UNIT; + (*rcmd)[4] = SSS_START; + return 1; + } + /* fallthrough */ + 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 */ + memcpy(*rcmd, cmd, cmdlen); + return 1; + + case READ_12: + case WRITE_12: + default: + printf("%s: Unsupported ATAPI command 0x%02x" + " - trying anyway\n", + device_get_nameunit(sc->sc_dev), cmd[0]); + memcpy(*rcmd, cmd, cmdlen); + return 1; + } +} + + +/* (even the comment is missing) */ + +DRIVER_MODULE(umass, uhub, umass_driver, umass_devclass, usbd_driver_load, 0); + + + +#ifdef USB_DEBUG +static void +umass_bbb_dump_cbw(struct umass_softc *sc, umass_bbb_cbw_t *cbw) +{ + int clen = cbw->bCDBLength; + int dlen = UGETDW(cbw->dCBWDataTransferLength); + u_int8_t *c = cbw->CBWCDB; + int tag = UGETDW(cbw->dCBWTag); + int flags = cbw->bCBWFlags; + + DPRINTF(UDMASS_BBB, ("%s: CBW %d: cmd = %db " + "(0x%02x%02x%02x%02x%02x%02x%s), " + "data = %db, dir = %s\n", + device_get_nameunit(sc->sc_dev), tag, clen, + c[0], c[1], c[2], c[3], c[4], c[5], (clen > 6? "...":""), + dlen, (flags == CBWFLAGS_IN? "in": + (flags == CBWFLAGS_OUT? "out":"<invalid>")))); +} + +static void +umass_bbb_dump_csw(struct umass_softc *sc, umass_bbb_csw_t *csw) +{ + int sig = UGETDW(csw->dCSWSignature); + int tag = UGETW(csw->dCSWTag); + int res = UGETDW(csw->dCSWDataResidue); + int status = csw->bCSWStatus; + + DPRINTF(UDMASS_BBB, ("%s: CSW %d: sig = 0x%08x (%s), tag = %d, " + "res = %d, status = 0x%02x (%s)\n", device_get_nameunit(sc->sc_dev), + 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, int cmdlen) +{ + u_int8_t *c = cmd; + int dir = sc->transfer_dir; + + DPRINTF(UDMASS_BBB, ("%s: cmd = %db " + "(0x%02x%02x%02x%02x%02x%02x%s), " + "data = %db, dir = %s\n", + device_get_nameunit(sc->sc_dev), cmdlen, + c[0], c[1], c[2], c[3], c[4], c[5], (cmdlen > 6? "...":""), + sc->transfer_datalen, + (dir == DIR_IN? "in": + (dir == DIR_OUT? "out": + (dir == DIR_NONE? "no data phase": "<invalid>"))))); +} + +static void +umass_dump_buffer(struct umass_softc *sc, u_int8_t *buffer, int buflen, + int printlen) +{ + int 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(UDMASS_GEN, ("%s: 0x %s%s\n", + device_get_nameunit(sc->sc_dev), s1, s2)); + s2[0] = '\0'; + } + sprintf(&s1[j*2], "%02x", buffer[i] & 0xff); + } + if (buflen > printlen) + sprintf(s3, " ..."); + DPRINTF(UDMASS_GEN, ("%s: 0x %s%s%s\n", + device_get_nameunit(sc->sc_dev), s1, s2, s3)); +} +#endif diff --git a/sys/legacy/dev/usb/umct.c b/sys/legacy/dev/usb/umct.c new file mode 100644 index 0000000..63de146 --- /dev/null +++ b/sys/legacy/dev/usb/umct.c @@ -0,0 +1,511 @@ +/*- + * Copyright (c) 2003 Scott Long + * 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * Driver for the MCT (Magic Control Technology) USB-RS232 Converter. + * Based on the superb documentation from the linux mct_u232 driver by + * Wolfgang Grandeggar <wolfgang@cec.ch>. + * This device smells a lot like the Belkin F5U103, except that it has + * suffered some mild brain-damage. This driver is based off of the ubsa.c + * driver from Alexander Kabaev <kan@freebsd.org>. Merging the two together + * might be useful, though the subtle differences might lead to lots of + * #ifdef's. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/tty.h> +#include <sys/taskqueue.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include "usbdevs.h" +#include <dev/usb/ucomvar.h> + +/* The UMCT advertises the standard 8250 UART registers */ +#define UMCT_GET_MSR 2 /* Get Modem Status Register */ +#define UMCT_GET_MSR_SIZE 1 +#define UMCT_GET_LCR 6 /* Get Line Control Register */ +#define UMCT_GET_LCR_SIZE 1 +#define UMCT_SET_BAUD 5 /* Set the Baud Rate Divisor */ +#define UMCT_SET_BAUD_SIZE 4 +#define UMCT_SET_LCR 7 /* Set Line Control Register */ +#define UMCT_SET_LCR_SIZE 1 +#define UMCT_SET_MCR 10 /* Set Modem Control Register */ +#define UMCT_SET_MCR_SIZE 1 + +#define UMCT_INTR_INTERVAL 100 +#define UMCT_IFACE_INDEX 0 +#define UMCT_CONFIG_INDEX 1 + +struct umct_softc { + struct ucom_softc sc_ucom; + int sc_iface_number; + usbd_interface_handle sc_intr_iface; + int sc_intr_number; + usbd_pipe_handle sc_intr_pipe; + u_char *sc_intr_buf; + int sc_isize; + uint8_t sc_lsr; + uint8_t sc_msr; + uint8_t sc_lcr; + uint8_t sc_mcr; + struct task sc_task; +}; + +static void umct_intr(usbd_xfer_handle, usbd_private_handle, usbd_status); +static void umct_get_status(void *, int, u_char *, u_char *); +static void umct_set(void *, int, int, int); +static int umct_param(void *, int, struct termios *); +static int umct_open(void *, int); +static void umct_close(void *, int); +static void umct_notify(void *, int count); + +static struct ucom_callback umct_callback = { + umct_get_status, /* ucom_get_status */ + umct_set, /* ucom_set */ + umct_param, /* ucom_param */ + NULL, /* ucom_ioctl */ + umct_open, /* ucom_open */ + umct_close, /* ucom_close */ + NULL, /* ucom_read */ + NULL /* ucom_write */ +}; + +static const struct umct_product { + uint16_t vendor; + uint16_t product; +} umct_products[] = { + { USB_VENDOR_MCT, USB_PRODUCT_MCT_USB232 }, + { USB_VENDOR_MCT, USB_PRODUCT_MCT_SITECOM_USB232 }, + { USB_VENDOR_MCT, USB_PRODUCT_MCT_DU_H3SP_USB232 }, + { USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5U109 }, + { USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5U409 }, + { 0, 0 } +}; + +static device_probe_t umct_match; +static device_attach_t umct_attach; +static device_detach_t umct_detach; + +static device_method_t umct_methods[] = { + DEVMETHOD(device_probe, umct_match), + DEVMETHOD(device_attach, umct_attach), + DEVMETHOD(device_detach, umct_detach), + { 0, 0 } +}; + +static driver_t umct_driver = { + "ucom", + umct_methods, + sizeof(struct umct_softc) +}; + +DRIVER_MODULE(umct, uhub, umct_driver, ucom_devclass, usbd_driver_load, 0); +MODULE_DEPEND(umct, usb, 1, 1, 1); +MODULE_DEPEND(umct, ucom, UCOM_MINVER, UCOM_PREFVER, UCOM_MAXVER); +MODULE_VERSION(umct, 1); + +static int +umct_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + int i; + + if (uaa->iface != NULL) + return (UMATCH_NONE); + + for (i = 0; umct_products[i].vendor != 0; i++) { + if (umct_products[i].vendor == uaa->vendor && + umct_products[i].product == uaa->product) { + return (UMATCH_VENDOR_PRODUCT); + } + } + + return (UMATCH_NONE); +} + +static int +umct_attach(device_t self) +{ + struct umct_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + usbd_device_handle dev; + struct ucom_softc *ucom; + usb_config_descriptor_t *cdesc; + usb_interface_descriptor_t *id; + usb_endpoint_descriptor_t *ed; + const char *devname; + usbd_status err; + int i; + + dev = uaa->device; + bzero(sc, sizeof(struct umct_softc)); + ucom = &sc->sc_ucom; + ucom->sc_dev = self; + ucom->sc_udev = dev; + ucom->sc_iface = uaa->iface; + + ucom->sc_bulkout_no = -1; + ucom->sc_bulkin_no = -1; + sc->sc_intr_number = -1; + sc->sc_intr_pipe = NULL; + + devname = device_get_nameunit(ucom->sc_dev); + + err = usbd_set_config_index(dev, UMCT_CONFIG_INDEX, 1); + if (err) { + printf("%s: failed to set configuration: %s\n", + devname, usbd_errstr(err)); + ucom->sc_dying = 1; + goto error; + } + + cdesc = usbd_get_config_descriptor(ucom->sc_udev); + if (cdesc == NULL) { + printf("%s: failed to get configuration descriptor\n", devname); + ucom->sc_dying = 1; + goto error; + } + + err = usbd_device2interface_handle(dev, UMCT_IFACE_INDEX, + &ucom->sc_iface); + if (err) { + printf("%s: failed to get interface: %s\n", devname, + usbd_errstr(err)); + ucom->sc_dying = 1; + goto error; + } + + id = usbd_get_interface_descriptor(ucom->sc_iface); + sc->sc_iface_number = id->bInterfaceNumber; + + for (i = 0; i < id->bNumEndpoints; i++) { + ed = usbd_interface2endpoint_descriptor(ucom->sc_iface, i); + if (ed == NULL) { + printf("%s: no endpoint descriptor for %d\n", + devname, i); + ucom->sc_dying = 1; + goto error; + } + + /* + * The real bulk-in endpoint is also marked as an interrupt. + * The only way to differentiate it from the real interrupt + * endpoint is to look at the wMaxPacketSize field. + */ + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN) { + if (UGETW(ed->wMaxPacketSize) == 0x2) { + sc->sc_intr_number = ed->bEndpointAddress; + sc->sc_isize = UGETW(ed->wMaxPacketSize); + } else { + ucom->sc_bulkin_no = ed->bEndpointAddress; + ucom->sc_ibufsize = UGETW(ed->wMaxPacketSize); + } + continue; + } + + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT) { + ucom->sc_bulkout_no = ed->bEndpointAddress; + if (uaa->product == USB_PRODUCT_MCT_SITECOM_USB232) + ucom->sc_obufsize = 16; /* device is broken */ + else + ucom->sc_obufsize = UGETW(ed->wMaxPacketSize); + continue; + } + + printf("%s: warning - unsupported endpoint 0x%x\n", devname, + ed->bEndpointAddress); + } + + if (sc->sc_intr_number == -1) { + printf("%s: Could not find interrupt in\n", devname); + ucom->sc_dying = 1; + goto error; + } + + sc->sc_intr_iface = ucom->sc_iface; + + if (ucom->sc_bulkout_no == -1) { + printf("%s: Could not find data bulk out\n", devname); + ucom->sc_dying = 1; + goto error; + } + + ucom->sc_parent = sc; + ucom->sc_portno = UCOM_UNK_PORTNO; + ucom->sc_opkthdrlen = 0; + ucom->sc_callback = &umct_callback; + ucom_attach(ucom); + TASK_INIT(&sc->sc_task, 0, umct_notify, sc); + return 0; + +error: + return ENXIO; +} + +static int +umct_detach(device_t self) +{ + struct umct_softc *sc = device_get_softc(self); + + int rv; + + if (sc->sc_intr_pipe != NULL) { + usbd_abort_pipe(sc->sc_intr_pipe); + usbd_close_pipe(sc->sc_intr_pipe); + free(sc->sc_intr_buf, M_USBDEV); + sc->sc_intr_pipe = NULL; + } + + sc->sc_ucom.sc_dying = 1; +#if 0 + taskqueue_drain(taskqueue_swi_giant); +#endif + rv = ucom_detach(&sc->sc_ucom); + return (rv); +} + +static int +umct_request(struct umct_softc *sc, uint8_t request, int len, uint32_t value) +{ + usb_device_request_t req; + usbd_status err; + uint8_t oval[4]; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = request; + USETW(req.wValue, 0); + USETW(req.wIndex, sc->sc_iface_number); + USETW(req.wLength, len); + USETDW(oval, value); + + err = usbd_do_request(sc->sc_ucom.sc_udev, &req, oval); + if (err) + printf("%s: umct_request: %s\n", + device_get_nameunit(sc->sc_ucom.sc_dev), usbd_errstr(err)); + return (err); +} + +static void +umct_intr(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + struct umct_softc *sc; + u_char *buf; + + sc = (struct umct_softc *)priv; + buf = sc->sc_intr_buf; + if (sc->sc_ucom.sc_dying) + return; + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) + return; + + usbd_clear_endpoint_stall_async(sc->sc_intr_pipe); + return; + } + + sc->sc_msr = buf[0]; + sc->sc_lsr = buf[1]; + + /* + * Defer notifying the ucom layer as it doesn't like to be bothered + * from an interrupt context. + */ + taskqueue_enqueue(taskqueue_swi_giant, &sc->sc_task); +} + +static void +umct_notify(void *arg, int count) +{ + struct umct_softc *sc; + + sc = (struct umct_softc *)arg; + if (sc->sc_ucom.sc_dying == 0) + ucom_status_change(&sc->sc_ucom); +} + +static void +umct_get_status(void *addr, int portno, u_char *lsr, u_char *msr) +{ + struct umct_softc *sc; + + sc = addr; + if (lsr != NULL) + *lsr = sc->sc_lsr; + if (msr != NULL) + *msr = sc->sc_msr; + + return; +} + +static void +umct_set(void *addr, int portno, int reg, int onoff) +{ + struct umct_softc *sc; + + sc = addr; + switch (reg) { + case UCOM_SET_BREAK: + sc->sc_lcr &= ~0x40; + sc->sc_lcr |= (onoff) ? 0x40 : 0; + umct_request(sc, UMCT_SET_LCR, UMCT_SET_LCR_SIZE, sc->sc_lcr); + break; + case UCOM_SET_DTR: + sc->sc_mcr &= ~0x01; + sc->sc_mcr |= (onoff) ? 0x01 : 0; + umct_request(sc, UMCT_SET_MCR, UMCT_SET_MCR_SIZE, sc->sc_mcr); + break; + case UCOM_SET_RTS: + sc->sc_mcr &= ~0x2; + sc->sc_mcr |= (onoff) ? 0x02 : 0; + umct_request(sc, UMCT_SET_MCR, UMCT_SET_MCR_SIZE, sc->sc_mcr); + break; + default: + break; + } +} + +static int +umct_calc_baud(u_int baud) +{ + switch(baud) { + case B300: return (0x1); + case B600: return (0x2); + case B1200: return (0x3); + case B2400: return (0x4); + case B4800: return (0x6); + case B9600: return (0x8); + case B19200: return (0x9); + case B38400: return (0xa); + case B57600: return (0xb); + case 115200: return (0xc); + case B0: + default: + break; + } + + return (0x0); +} + +static int +umct_param(void *addr, int portno, struct termios *ti) +{ + struct umct_softc *sc; + uint32_t value; + + sc = addr; + value = umct_calc_baud(ti->c_ospeed); + umct_request(sc, UMCT_SET_BAUD, UMCT_SET_BAUD_SIZE, value); + + value = sc->sc_lcr & 0x40; + + switch (ti->c_cflag & CSIZE) { + case CS5: value |= 0x0; break; + case CS6: value |= 0x1; break; + case CS7: value |= 0x2; break; + case CS8: value |= 0x3; break; + default: value |= 0x0; break; + } + + value |= (ti->c_cflag & CSTOPB) ? 0x4 : 0; + if (ti->c_cflag & PARENB) { + value |= 0x8; + value |= (ti->c_cflag & PARODD) ? 0x0 : 0x10; + } + + /* + * XXX There doesn't seem to be a way to tell the device to use flow + * control. + */ + + sc->sc_lcr = value; + umct_request(sc, UMCT_SET_LCR, UMCT_SET_LCR_SIZE, value); + + return (0); +} + +static int +umct_open(void *addr, int portno) +{ + struct umct_softc *sc; + int err; + + sc = addr; + if (sc->sc_ucom.sc_dying) { + return (ENXIO); + } + + if (sc->sc_intr_number != -1 && sc->sc_intr_pipe == NULL) { + sc->sc_intr_buf = malloc(sc->sc_isize, M_USBDEV, M_WAITOK); + err = usbd_open_pipe_intr(sc->sc_intr_iface, sc->sc_intr_number, + USBD_SHORT_XFER_OK, &sc->sc_intr_pipe, sc, sc->sc_intr_buf, + sc->sc_isize, umct_intr, UMCT_INTR_INTERVAL); + if (err) { + printf("%s: cannot open interrupt pipe (addr %d)\n", + device_get_nameunit(sc->sc_ucom.sc_dev), + sc->sc_intr_number); + free(sc->sc_intr_buf, M_USBDEV); + return (EIO); + } + } + + return (0); +} + +static void +umct_close(void *addr, int portno) +{ + struct umct_softc *sc; + int err; + + sc = addr; + if (sc->sc_ucom.sc_dying) + return; + + if (sc->sc_intr_pipe != NULL) { + err = usbd_abort_pipe(sc->sc_intr_pipe); + if (err) + printf("%s: abort interrupt pipe failed: %s\n", + device_get_nameunit(sc->sc_ucom.sc_dev), usbd_errstr(err)); + err = usbd_close_pipe(sc->sc_intr_pipe); + if (err) + printf("%s: close interrupt pipe failed: %s\n", + device_get_nameunit(sc->sc_ucom.sc_dev), usbd_errstr(err)); + free(sc->sc_intr_buf, M_USBDEV); + sc->sc_intr_pipe = NULL; + } +} diff --git a/sys/legacy/dev/usb/umodem.c b/sys/legacy/dev/usb/umodem.c new file mode 100644 index 0000000..f0cb17c --- /dev/null +++ b/sys/legacy/dev/usb/umodem.c @@ -0,0 +1,821 @@ +/* $NetBSD: umodem.c,v 1.45 2002/09/23 05:51:23 simonb Exp $ */ + + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); +/*- + * Copyright (c) 2003, M. Warner Losh <imp@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. + */ + +/*- + * 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. + */ + +/* + * Comm Class spec: http://www.usb.org/developers/devclass_docs/usbccs10.pdf + * http://www.usb.org/developers/devclass_docs/usbcdc11.pdf + */ + +/* + * TODO: + * - Add error recovery in various places; the big problem is what + * to do in a callback if there is an error. + * - Implement a Call Device for modems without multiplexed commands. + * + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/ioccom.h> +#include <sys/conf.h> +#include <sys/serial.h> +#include <sys/tty.h> +#include <sys/file.h> +#include <sys/select.h> +#include <sys/sysctl.h> +#include <sys/proc.h> +#include <sys/bus.h> +#include <sys/poll.h> +#include <sys/uio.h> +#include <sys/taskqueue.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbcdc.h> + +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usb_quirks.h> + +#include <dev/usb/ucomvar.h> + +#include "usbdevs.h" + +#ifdef USB_DEBUG +int umodemdebug = 0; +SYSCTL_NODE(_hw_usb, OID_AUTO, umodem, CTLFLAG_RW, 0, "USB umodem"); +SYSCTL_INT(_hw_usb_umodem, OID_AUTO, debug, CTLFLAG_RW, + &umodemdebug, 0, "umodem debug level"); +#define DPRINTFN(n, x) if (umodemdebug > (n)) printf x +#else +#define DPRINTFN(n, x) +#endif +#define DPRINTF(x) DPRINTFN(0, x) + +static const struct umodem_product { + u_int16_t vendor; + u_int16_t product; + u_int8_t interface; +} umodem_products[] = { + /* Kyocera AH-K3001V*/ + { USB_VENDOR_KYOCERA, USB_PRODUCT_KYOCERA_AHK3001V, 0 }, + { USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC5720, 0 }, + { USB_VENDOR_CURITEL, USB_PRODUCT_CURITEL_PC5740, 0 }, + { 0, 0, 0 }, +}; + +/* + * These are the maximum number of bytes transferred per frame. + * As speeds for umodem deivces increase, these numbers will need to + * be increased. They should be good for G3 speeds and below. + */ +#define UMODEMIBUFSIZE 1024 +#define UMODEMOBUFSIZE 1024 + +#define UMODEM_MODVER 1 /* module version */ + +struct umodem_softc { + struct ucom_softc sc_ucom; + + device_t sc_dev; /* base device */ + + usbd_device_handle sc_udev; /* USB device */ + + int sc_ctl_iface_no; + usbd_interface_handle sc_ctl_iface; /* control interface */ + int sc_data_iface_no; + usbd_interface_handle sc_data_iface; /* data interface */ + + int sc_cm_cap; /* CM capabilities */ + int sc_acm_cap; /* ACM capabilities */ + + int sc_cm_over_data; + + usb_cdc_line_state_t sc_line_state; /* current line state */ + u_char sc_dtr; /* current DTR state */ + u_char sc_rts; /* current RTS state */ + + u_char sc_opening; /* lock during open */ + + int sc_ctl_notify; /* Notification endpoint */ + usbd_pipe_handle sc_notify_pipe; /* Notification pipe */ + usb_cdc_notification_t sc_notify_buf; /* Notification structure */ + u_char sc_lsr; /* Local status register */ + u_char sc_msr; /* Modem status register */ + + struct task sc_task; +}; + +static void *umodem_get_desc(usbd_device_handle dev, int type, int subtype); +static usbd_status umodem_set_comm_feature(struct umodem_softc *sc, + int feature, int state); +static usbd_status umodem_set_line_coding(struct umodem_softc *sc, + usb_cdc_line_state_t *state); + +static void umodem_get_caps(usbd_device_handle, int *, int *); + +static void umodem_get_status(void *, int portno, u_char *lsr, u_char *msr); +static void umodem_set(void *, int, int, int); +static void umodem_dtr(struct umodem_softc *, int); +static void umodem_rts(struct umodem_softc *, int); +static void umodem_break(struct umodem_softc *, int); +static void umodem_set_line_state(struct umodem_softc *); +static int umodem_param(void *, int, struct termios *); +static int umodem_ioctl(void *, int, u_long, caddr_t, struct thread *); +static int umodem_open(void *, int portno); +static void umodem_close(void *, int portno); +static void umodem_intr(usbd_xfer_handle, usbd_private_handle, usbd_status); +static void umodem_notify(void *, int); + +static struct ucom_callback umodem_callback = { + .ucom_get_status = umodem_get_status, + .ucom_set = umodem_set, + .ucom_param = umodem_param, + .ucom_ioctl = umodem_ioctl, + .ucom_open = umodem_open, + .ucom_close = umodem_close +}; + +static device_probe_t umodem_match; +static device_attach_t umodem_attach; +static device_detach_t umodem_detach; + +static device_method_t umodem_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, umodem_match), + DEVMETHOD(device_attach, umodem_attach), + DEVMETHOD(device_detach, umodem_detach), + { 0, 0 } +}; + +static driver_t umodem_driver = { + "ucom", + umodem_methods, + sizeof (struct umodem_softc) +}; + +DRIVER_MODULE(umodem, uhub, umodem_driver, ucom_devclass, usbd_driver_load, 0); +MODULE_DEPEND(umodem, usb, 1, 1, 1); +MODULE_DEPEND(umodem, ucom, UCOM_MINVER, UCOM_PREFVER, UCOM_MAXVER); +MODULE_VERSION(umodem, UMODEM_MODVER); + +static int +umodem_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + usb_interface_descriptor_t *id; + usb_device_descriptor_t *dd; + int cm, acm, i, ret; + + if (uaa->iface == NULL) + return (UMATCH_NONE); + + id = usbd_get_interface_descriptor(uaa->iface); + dd = usbd_get_device_descriptor(uaa->device); + if (id == NULL || dd == NULL) + return (UMATCH_NONE); + + ret = UMATCH_NONE; + for (i = 0; umodem_products[i].vendor != 0; i++) { + if (umodem_products[i].vendor == UGETW(dd->idVendor) && + umodem_products[i].product == UGETW(dd->idProduct) && + umodem_products[i].interface == id->bInterfaceNumber) { + ret = UMATCH_VENDOR_PRODUCT; + break; + } + } + + if (ret == UMATCH_NONE && + id->bInterfaceClass == UICLASS_CDC && + id->bInterfaceSubClass == UISUBCLASS_ABSTRACT_CONTROL_MODEL && + id->bInterfaceProtocol == UIPROTO_CDC_AT) + ret = UMATCH_IFACECLASS_IFACESUBCLASS_IFACEPROTO; + + if (ret == UMATCH_NONE) + return (ret); + + umodem_get_caps(uaa->device, &cm, &acm); + if (!(cm & USB_CDC_CM_DOES_CM) || + !(cm & USB_CDC_CM_OVER_DATA) || + !(acm & USB_CDC_ACM_HAS_LINE)) + return (UMATCH_NONE); + + return ret; +} + +static int +umodem_attach(device_t self) +{ + struct umodem_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + usbd_device_handle dev = uaa->device; + usb_interface_descriptor_t *id; + usb_endpoint_descriptor_t *ed; + usb_cdc_cm_descriptor_t *cmd; + int data_ifcno; + int i; + struct ucom_softc *ucom; + + ucom = &sc->sc_ucom; + ucom->sc_dev = self; + sc->sc_dev = self; + ucom->sc_udev = dev; + ucom->sc_iface = uaa->iface; + + sc->sc_udev = dev; + sc->sc_ctl_iface = uaa->iface; + id = usbd_get_interface_descriptor(sc->sc_ctl_iface); + sc->sc_ctl_iface_no = id->bInterfaceNumber; + device_printf(self, "iclass %d/%d\n", id->bInterfaceClass, + id->bInterfaceSubClass); + + umodem_get_caps(dev, &sc->sc_cm_cap, &sc->sc_acm_cap); + + /* Get the data interface no. */ + cmd = umodem_get_desc(dev, UDESC_CS_INTERFACE, UDESCSUB_CDC_CM); + if (cmd == NULL) { + device_printf(sc->sc_dev, "no CM descriptor\n"); + goto bad; + } + sc->sc_data_iface_no = data_ifcno = cmd->bDataInterface; + + device_printf(sc->sc_dev, + "data interface %d, has %sCM over data, has %sbreak\n", + data_ifcno, sc->sc_cm_cap & USB_CDC_CM_OVER_DATA ? "" : "no ", + sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK ? "" : "no "); + + /* Get the data interface too. */ + for (i = 0; i < uaa->nifaces; i++) { + if (uaa->ifaces[i] != NULL) { + id = usbd_get_interface_descriptor(uaa->ifaces[i]); + if (id != NULL && id->bInterfaceNumber == data_ifcno) { + sc->sc_data_iface = uaa->ifaces[i]; + uaa->ifaces[i] = NULL; + } + } + } + if (sc->sc_data_iface == NULL) { + device_printf(sc->sc_dev, "no data interface\n"); + goto bad; + } + ucom->sc_iface = sc->sc_data_iface; + + /* + * Find the bulk endpoints. + * Iterate over all endpoints in the data interface and take note. + */ + ucom->sc_bulkin_no = ucom->sc_bulkout_no = -1; + + id = usbd_get_interface_descriptor(sc->sc_data_iface); + for (i = 0; i < id->bNumEndpoints; i++) { + ed = usbd_interface2endpoint_descriptor(sc->sc_data_iface, i); + if (ed == NULL) { + device_printf(sc->sc_dev, + "no endpoint descriptor for %d\n", i); + goto bad; + } + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + ucom->sc_bulkin_no = ed->bEndpointAddress; + } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + ucom->sc_bulkout_no = ed->bEndpointAddress; + } + } + + if (ucom->sc_bulkin_no == -1) { + device_printf(sc->sc_dev, "Could not find data bulk in\n"); + goto bad; + } + if (ucom->sc_bulkout_no == -1) { + device_printf(sc->sc_dev, "Could not find data bulk out\n"); + goto bad; + } + + if (sc->sc_cm_cap & USB_CDC_CM_OVER_DATA) { + if (sc->sc_acm_cap & USB_CDC_ACM_HAS_FEATURE) + umodem_set_comm_feature(sc, UCDC_ABSTRACT_STATE, + UCDC_DATA_MULTIPLEXED); + sc->sc_cm_over_data = 1; + } + + /* + * The standard allows for notification messages (to indicate things + * like a modem hangup) to come in via an interrupt endpoint + * off of the control interface. Iterate over the endpoints on + * the control interface and see if there are any interrupt + * endpoints; if there are, then register it. + */ + + sc->sc_ctl_notify = -1; + sc->sc_notify_pipe = NULL; + + for (i = 0; i < id->bNumEndpoints; i++) { + ed = usbd_interface2endpoint_descriptor(sc->sc_ctl_iface, i); + if (ed == NULL) + continue; + + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + (ed->bmAttributes & UE_XFERTYPE) == UE_INTERRUPT) { + device_printf(sc->sc_dev, + "status change notification available\n"); + sc->sc_ctl_notify = ed->bEndpointAddress; + } + } + + sc->sc_dtr = -1; + + ucom->sc_parent = sc; + ucom->sc_portno = UCOM_UNK_PORTNO; + /* bulkin, bulkout set above */ + ucom->sc_ibufsize = UMODEMIBUFSIZE; + ucom->sc_obufsize = UMODEMOBUFSIZE; + ucom->sc_ibufsizepad = UMODEMIBUFSIZE; + ucom->sc_opkthdrlen = 0; + ucom->sc_callback = &umodem_callback; + + TASK_INIT(&sc->sc_task, 0, umodem_notify, sc); + ucom_attach(&sc->sc_ucom); + return 0; + + bad: + ucom->sc_dying = 1; + return ENXIO; +} + +static int +umodem_open(void *addr, int portno) +{ + struct umodem_softc *sc = addr; + int err; + + DPRINTF(("umodem_open: sc=%p\n", sc)); + + if (sc->sc_ctl_notify != -1 && sc->sc_notify_pipe == NULL) { + err = usbd_open_pipe_intr(sc->sc_ctl_iface, sc->sc_ctl_notify, + USBD_SHORT_XFER_OK, &sc->sc_notify_pipe, sc, + &sc->sc_notify_buf, sizeof(sc->sc_notify_buf), + umodem_intr, USBD_DEFAULT_INTERVAL); + + if (err) { + DPRINTF(("Failed to establish notify pipe: %s\n", + usbd_errstr(err))); + return EIO; + } + } + + return 0; +} + +static void +umodem_close(void *addr, int portno) +{ + struct umodem_softc *sc = addr; + int err; + + DPRINTF(("umodem_close: sc=%p\n", sc)); + + if (sc->sc_notify_pipe != NULL) { + err = usbd_abort_pipe(sc->sc_notify_pipe); + if (err) + device_printf(sc->sc_dev, + "abort notify pipe failed: %s\n", + usbd_errstr(err)); + err = usbd_close_pipe(sc->sc_notify_pipe); + if (err) + device_printf(sc->sc_dev, + "close notify pipe failed: %s\n", + usbd_errstr(err)); + sc->sc_notify_pipe = NULL; + } +} + +static void +umodem_intr(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + struct umodem_softc *sc = priv; + u_char mstatus; + + if (sc->sc_ucom.sc_dying) + return; + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) + return; + device_printf(sc->sc_dev, "abnormal status: %s\n", + usbd_errstr(status)); + return; + } + + if (sc->sc_notify_buf.bmRequestType != UCDC_NOTIFICATION) { + DPRINTF(("%s: unknown message type (%02x) on notify pipe\n", + device_get_nameunit(sc->sc_dev), + sc->sc_notify_buf.bmRequestType)); + return; + } + + switch (sc->sc_notify_buf.bNotification) { + case UCDC_N_SERIAL_STATE: + /* + * Set the serial state in ucom driver based on + * the bits from the notify message + */ + if (UGETW(sc->sc_notify_buf.wLength) != 2) { + device_printf(sc->sc_dev, + "Invalid notification length! (%d)\n", + UGETW(sc->sc_notify_buf.wLength)); + break; + } + DPRINTF(("%s: notify bytes = %02x%02x\n", + device_get_nameunit(sc->sc_dev), + sc->sc_notify_buf.data[0], + sc->sc_notify_buf.data[1])); + /* Currently, lsr is always zero. */ + sc->sc_lsr = sc->sc_msr = 0; + mstatus = sc->sc_notify_buf.data[0]; + + if (ISSET(mstatus, UCDC_N_SERIAL_RI)) + sc->sc_msr |= SER_RI; + if (ISSET(mstatus, UCDC_N_SERIAL_DSR)) + sc->sc_msr |= SER_DSR; + if (ISSET(mstatus, UCDC_N_SERIAL_DCD)) + sc->sc_msr |= SER_DCD; + /* Deferred notifying to the ucom layer */ + taskqueue_enqueue(taskqueue_swi_giant, &sc->sc_task); + break; + default: + DPRINTF(("%s: unknown notify message: %02x\n", + device_get_nameunit(sc->sc_dev), + sc->sc_notify_buf.bNotification)); + break; + } +} + +static void +umodem_notify(void *arg, int count) +{ + struct umodem_softc *sc; + + sc = (struct umodem_softc *)arg; + if (sc->sc_ucom.sc_dying) + return; + ucom_status_change(&sc->sc_ucom); +} + +void +umodem_get_caps(usbd_device_handle dev, int *cm, int *acm) +{ + usb_cdc_cm_descriptor_t *cmd; + usb_cdc_acm_descriptor_t *cad; + + *cm = *acm = 0; + + cmd = umodem_get_desc(dev, UDESC_CS_INTERFACE, UDESCSUB_CDC_CM); + if (cmd == NULL) { + DPRINTF(("umodem_get_desc: no CM desc\n")); + return; + } + *cm = cmd->bmCapabilities; + + cad = umodem_get_desc(dev, UDESC_CS_INTERFACE, UDESCSUB_CDC_ACM); + if (cad == NULL) { + DPRINTF(("umodem_get_desc: no ACM desc\n")); + return; + } + *acm = cad->bmCapabilities; +} + +void +umodem_get_status(void *addr, int portno, u_char *lsr, u_char *msr) +{ + struct umodem_softc *sc = addr; + + DPRINTF(("umodem_get_status:\n")); + + if (lsr != NULL) + *lsr = sc->sc_lsr; + if (msr != NULL) + *msr = sc->sc_msr; +} + +int +umodem_param(void *addr, int portno, struct termios *t) +{ + struct umodem_softc *sc = addr; + usbd_status err; + usb_cdc_line_state_t ls; + + DPRINTF(("umodem_param: sc=%p\n", sc)); + + USETDW(ls.dwDTERate, t->c_ospeed); + if (ISSET(t->c_cflag, CSTOPB)) + ls.bCharFormat = UCDC_STOP_BIT_2; + else + ls.bCharFormat = UCDC_STOP_BIT_1; + if (ISSET(t->c_cflag, PARENB)) { + if (ISSET(t->c_cflag, PARODD)) + ls.bParityType = UCDC_PARITY_ODD; + else + ls.bParityType = UCDC_PARITY_EVEN; + } else + ls.bParityType = UCDC_PARITY_NONE; + switch (ISSET(t->c_cflag, CSIZE)) { + case CS5: + ls.bDataBits = 5; + break; + case CS6: + ls.bDataBits = 6; + break; + case CS7: + ls.bDataBits = 7; + break; + case CS8: + ls.bDataBits = 8; + break; + } + + err = umodem_set_line_coding(sc, &ls); + if (err) { + DPRINTF(("umodem_param: err=%s\n", usbd_errstr(err))); + return (EIO); + } + return (0); +} + +int +umodem_ioctl(void *addr, int portno, u_long cmd, caddr_t data, + struct thread *p) +{ + struct umodem_softc *sc = addr; + int error = 0; + + if (sc->sc_ucom.sc_dying) + return (EIO); + + DPRINTF(("umodemioctl: cmd=0x%08lx\n", cmd)); + + switch (cmd) { + case USB_GET_CM_OVER_DATA: + *(int *)data = sc->sc_cm_over_data; + break; + + case USB_SET_CM_OVER_DATA: + if (*(int *)data != sc->sc_cm_over_data) { + /* XXX change it */ + } + break; + + default: + DPRINTF(("umodemioctl: unknown\n")); + error = ENOIOCTL; + break; + } + + return (error); +} + +void +umodem_dtr(struct umodem_softc *sc, int onoff) +{ + DPRINTF(("umodem_modem: onoff=%d\n", onoff)); + + if (sc->sc_dtr == onoff) + return; + sc->sc_dtr = onoff; + + umodem_set_line_state(sc); +} + +void +umodem_rts(struct umodem_softc *sc, int onoff) +{ + DPRINTF(("umodem_modem: onoff=%d\n", onoff)); + + if (sc->sc_rts == onoff) + return; + sc->sc_rts = onoff; + + umodem_set_line_state(sc); +} + +void +umodem_set_line_state(struct umodem_softc *sc) +{ + usb_device_request_t req; + int ls; + + ls = (sc->sc_dtr ? UCDC_LINE_DTR : 0) | + (sc->sc_rts ? UCDC_LINE_RTS : 0); + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + USETW(req.wValue, ls); + USETW(req.wIndex, sc->sc_ctl_iface_no); + USETW(req.wLength, 0); + + (void)usbd_do_request(sc->sc_udev, &req, 0); + +} + +void +umodem_break(struct umodem_softc *sc, int onoff) +{ + usb_device_request_t req; + + DPRINTF(("umodem_break: onoff=%d\n", onoff)); + + if (!(sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK)) + return; + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SEND_BREAK; + USETW(req.wValue, onoff ? UCDC_BREAK_ON : UCDC_BREAK_OFF); + USETW(req.wIndex, sc->sc_ctl_iface_no); + USETW(req.wLength, 0); + + (void)usbd_do_request(sc->sc_udev, &req, 0); +} + +void +umodem_set(void *addr, int portno, int reg, int onoff) +{ + struct umodem_softc *sc = addr; + + switch (reg) { + case UCOM_SET_DTR: + umodem_dtr(sc, onoff); + break; + case UCOM_SET_RTS: + umodem_rts(sc, onoff); + break; + case UCOM_SET_BREAK: + umodem_break(sc, onoff); + break; + default: + break; + } +} + +usbd_status +umodem_set_line_coding(struct umodem_softc *sc, usb_cdc_line_state_t *state) +{ + usb_device_request_t req; + usbd_status err; + + DPRINTF(("umodem_set_line_coding: rate=%d fmt=%d parity=%d bits=%d\n", + UGETDW(state->dwDTERate), state->bCharFormat, + state->bParityType, state->bDataBits)); + + if (memcmp(state, &sc->sc_line_state, UCDC_LINE_STATE_LENGTH) == 0) { + DPRINTF(("umodem_set_line_coding: already set\n")); + return (USBD_NORMAL_COMPLETION); + } + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_LINE_CODING; + USETW(req.wValue, 0); + USETW(req.wIndex, sc->sc_ctl_iface_no); + USETW(req.wLength, UCDC_LINE_STATE_LENGTH); + + err = usbd_do_request(sc->sc_udev, &req, state); + if (err) { + DPRINTF(("umodem_set_line_coding: failed, err=%s\n", + usbd_errstr(err))); + return (err); + } + + sc->sc_line_state = *state; + + return (USBD_NORMAL_COMPLETION); +} + +void * +umodem_get_desc(usbd_device_handle dev, int type, int subtype) +{ + usb_descriptor_t *desc; + usb_config_descriptor_t *cd = usbd_get_config_descriptor(dev); + uByte *p = (uByte *)cd; + uByte *end = p + UGETW(cd->wTotalLength); + + while (p < end) { + desc = (usb_descriptor_t *)p; + if (desc->bDescriptorType == type && + desc->bDescriptorSubtype == subtype) + return (desc); + p += desc->bLength; + } + + return (0); +} + +usbd_status +umodem_set_comm_feature(struct umodem_softc *sc, int feature, int state) +{ + usb_device_request_t req; + usbd_status err; + usb_cdc_abstract_state_t ast; + + DPRINTF(("umodem_set_comm_feature: feature=%d state=%d\n", feature, + state)); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_COMM_FEATURE; + USETW(req.wValue, feature); + USETW(req.wIndex, sc->sc_ctl_iface_no); + USETW(req.wLength, UCDC_ABSTRACT_STATE_LENGTH); + USETW(ast.wState, state); + + err = usbd_do_request(sc->sc_udev, &req, &ast); + if (err) { + DPRINTF(("umodem_set_comm_feature: feature=%d, err=%s\n", + feature, usbd_errstr(err))); + return (err); + } + + return (USBD_NORMAL_COMPLETION); +} + +static int +umodem_detach(device_t self) +{ + struct umodem_softc *sc = device_get_softc(self); + int rv = 0; + + DPRINTF(("umodem_detach: sc=%p\n", sc)); + + if (sc->sc_notify_pipe != NULL) { + usbd_abort_pipe(sc->sc_notify_pipe); + usbd_close_pipe(sc->sc_notify_pipe); + sc->sc_notify_pipe = NULL; + } + + sc->sc_ucom.sc_dying = 1; + rv = ucom_detach(&sc->sc_ucom); + + return (rv); +} diff --git a/sys/legacy/dev/usb/ums.c b/sys/legacy/dev/usb/ums.c new file mode 100644 index 0000000..6484303 --- /dev/null +++ b/sys/legacy/dev/usb/ums.c @@ -0,0 +1,972 @@ +/*- + * 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/ioccom.h> +#include <sys/conf.h> +#include <sys/fcntl.h> +#include <sys/tty.h> +#include <sys/file.h> +#include <sys/selinfo.h> +#include <sys/poll.h> +#include <sys/sysctl.h> +#include <sys/uio.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbhid.h> + +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include "usbdevs.h" +#include <dev/usb/usb_quirks.h> +#include <dev/usb/hid.h> + +#include <sys/mouse.h> + +#ifdef USB_DEBUG +#define DPRINTF(x) if (umsdebug) printf x +#define DPRINTFN(n,x) if (umsdebug>(n)) printf x +int umsdebug = 0; +SYSCTL_NODE(_hw_usb, OID_AUTO, ums, CTLFLAG_RW, 0, "USB ums"); +SYSCTL_INT(_hw_usb_ums, OID_AUTO, debug, CTLFLAG_RW, + &umsdebug, 0, "ums debug level"); +#else +#define DPRINTF(x) +#define DPRINTFN(n,x) +#endif + +#define UMSUNIT(s) (dev2unit(s)&0x1f) + +#define MS_TO_TICKS(ms) ((ms) * hz / 1000) + +#define QUEUE_BUFSIZE 400 /* MUST be divisible by 5 _and_ 8 */ + +struct ums_softc { + device_t sc_dev; /* base device */ + usbd_interface_handle sc_iface; /* interface */ + usbd_pipe_handle sc_intrpipe; /* interrupt pipe */ + int sc_ep_addr; + + u_char *sc_ibuf; + u_int8_t sc_iid; + int sc_isize; + struct hid_location sc_loc_x, sc_loc_y, sc_loc_z, sc_loc_t, sc_loc_w; + struct hid_location *sc_loc_btn; + + struct callout callout_handle; /* for spurious button ups */ + + int sc_enabled; + int sc_disconnected; /* device is gone */ + + int flags; /* device configuration */ +#define UMS_Z 0x01 /* z direction available */ +#define UMS_SPUR_BUT_UP 0x02 /* spurious button up events */ +#define UMS_T 0x04 /* aa direction available (tilt) */ +#define UMS_REVZ 0x08 /* Z-axis is reversed */ + int nbuttons; +#define MAX_BUTTONS 31 /* chosen because sc_buttons is int */ + + u_char qbuf[QUEUE_BUFSIZE]; /* must be divisable by 3&4 */ + u_char dummy[100]; /* XXX just for safety and for now */ + int qcount, qhead, qtail; + mousehw_t hw; + mousemode_t mode; + mousestatus_t status; + + int state; +# define UMS_ASLEEP 0x01 /* readFromDevice is waiting */ +# define UMS_SELECT 0x02 /* select is waiting */ + struct selinfo rsel; /* process waiting in select */ + + struct cdev *dev; /* specfs */ +}; + +#define MOUSE_FLAGS_MASK (HIO_CONST|HIO_RELATIVE) +#define MOUSE_FLAGS (HIO_RELATIVE) + +static void ums_intr(usbd_xfer_handle xfer, + usbd_private_handle priv, usbd_status status); + +static void ums_add_to_queue(struct ums_softc *sc, + int dx, int dy, int dz, int dt, int buttons); +static void ums_add_to_queue_timeout(void *priv); + +static int ums_enable(void *); +static void ums_disable(void *); + +static d_open_t ums_open; +static d_close_t ums_close; +static d_read_t ums_read; +static d_ioctl_t ums_ioctl; +static d_poll_t ums_poll; + + +static struct cdevsw ums_cdevsw = { + .d_version = D_VERSION, + .d_flags = D_NEEDGIANT, + .d_open = ums_open, + .d_close = ums_close, + .d_read = ums_read, + .d_ioctl = ums_ioctl, + .d_poll = ums_poll, + .d_name = "ums", +}; + +static device_probe_t ums_match; +static device_attach_t ums_attach; +static device_detach_t ums_detach; + +static device_method_t ums_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ums_match), + DEVMETHOD(device_attach, ums_attach), + DEVMETHOD(device_detach, ums_detach), + + { 0, 0 } +}; + +static driver_t ums_driver = { + "ums", + ums_methods, + sizeof(struct ums_softc) +}; + +static devclass_t ums_devclass; + +static int +ums_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + usb_interface_descriptor_t *id; + int size, ret; + void *desc; + usbd_status err; + + if (!uaa->iface) + return (UMATCH_NONE); + id = usbd_get_interface_descriptor(uaa->iface); + if (!id || id->bInterfaceClass != UICLASS_HID) + return (UMATCH_NONE); + + err = usbd_read_report_desc(uaa->iface, &desc, &size, M_TEMP); + if (err) + return (UMATCH_NONE); + + if (hid_is_collection(desc, size, + HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_MOUSE))) + ret = UMATCH_IFACECLASS; + else if (id->bInterfaceClass == UICLASS_HID && + id->bInterfaceSubClass == UISUBCLASS_BOOT && + id->bInterfaceProtocol == UIPROTO_MOUSE) + ret = UMATCH_IFACECLASS; + else + ret = UMATCH_NONE; + + free(desc, M_TEMP); + return (ret); +} + +static int +ums_attach(device_t self) +{ + struct ums_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + usbd_interface_handle iface = uaa->iface; + usb_interface_descriptor_t *id; + usb_endpoint_descriptor_t *ed; + int size; + void *desc; + usbd_status err; + u_int32_t flags; + int i, wheel; + struct hid_location loc_btn; + + sc->sc_disconnected = 1; + sc->sc_iface = iface; + id = usbd_get_interface_descriptor(iface); + sc->sc_dev = self; + ed = usbd_interface2endpoint_descriptor(iface, 0); + if (!ed) { + printf("%s: could not read endpoint descriptor\n", + device_get_nameunit(sc->sc_dev)); + return ENXIO; + } + + DPRINTFN(10,("ums_attach: bLength=%d bDescriptorType=%d " + "bEndpointAddress=%d-%s bmAttributes=%d wMaxPacketSize=%d" + " bInterval=%d\n", + ed->bLength, ed->bDescriptorType, + UE_GET_ADDR(ed->bEndpointAddress), + UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN ? "in":"out", + UE_GET_XFERTYPE(ed->bmAttributes), + UGETW(ed->wMaxPacketSize), ed->bInterval)); + + if (UE_GET_DIR(ed->bEndpointAddress) != UE_DIR_IN || + UE_GET_XFERTYPE(ed->bmAttributes) != UE_INTERRUPT) { + printf("%s: unexpected endpoint\n", + device_get_nameunit(sc->sc_dev)); + return ENXIO; + } + + err = usbd_read_report_desc(uaa->iface, &desc, &size, M_TEMP); + if (err) + return ENXIO; + + if (!hid_locate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X), + hid_input, &sc->sc_loc_x, &flags)) { + printf("%s: mouse has no X report\n", device_get_nameunit(sc->sc_dev)); + return ENXIO; + } + if ((flags & MOUSE_FLAGS_MASK) != MOUSE_FLAGS) { + printf("%s: X report 0x%04x not supported\n", + device_get_nameunit(sc->sc_dev), flags); + return ENXIO; + } + + if (!hid_locate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y), + hid_input, &sc->sc_loc_y, &flags)) { + printf("%s: mouse has no Y report\n", device_get_nameunit(sc->sc_dev)); + return ENXIO; + } + if ((flags & MOUSE_FLAGS_MASK) != MOUSE_FLAGS) { + printf("%s: Y report 0x%04x not supported\n", + device_get_nameunit(sc->sc_dev), flags); + return ENXIO; + } + + /* Try the wheel first as the Z activator since it's tradition. */ + wheel = hid_locate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP, + HUG_WHEEL), + hid_input, &sc->sc_loc_z, &flags) || + hid_locate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP, + HUG_TWHEEL), + hid_input, &sc->sc_loc_z, &flags); + + if (wheel) { + if ((flags & MOUSE_FLAGS_MASK) != MOUSE_FLAGS) { + printf("\n%s: Wheel report 0x%04x not supported\n", + device_get_nameunit(sc->sc_dev), flags); + sc->sc_loc_z.size = 0; /* Bad Z coord, ignore it */ + } else { + sc->flags |= UMS_Z; + if (usbd_get_quirks(uaa->device)->uq_flags & + UQ_MS_REVZ) { + /* Some wheels need the Z axis reversed. */ + sc->flags |= UMS_REVZ; + } + + } + /* + * We might have both a wheel and Z direction, if so put + * put the Z on the W coordinate. + */ + if (hid_locate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP, + HUG_Z), + hid_input, &sc->sc_loc_w, &flags)) { + if ((flags & MOUSE_FLAGS_MASK) != MOUSE_FLAGS) { + printf("\n%s: Z report 0x%04x not supported\n", + device_get_nameunit(sc->sc_dev), flags); + sc->sc_loc_w.size = 0; /* Bad Z, ignore */ + } + } + } else if (hid_locate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP, + HUG_Z), + hid_input, &sc->sc_loc_z, &flags)) { + if ((flags & MOUSE_FLAGS_MASK) != MOUSE_FLAGS) { + printf("\n%s: Z report 0x%04x not supported\n", + device_get_nameunit(sc->sc_dev), flags); + sc->sc_loc_z.size = 0; /* Bad Z coord, ignore it */ + } else { + sc->flags |= UMS_Z; + } + } + + /* + * The Microsoft Wireless Intellimouse 2.0 reports it's wheel + * using 0x0048 (i've called it HUG_TWHEEL) and seems to expect + * you to know that the byte after the wheel is the tilt axis. + * There are no other HID axis descriptors other than X,Y and + * TWHEEL + */ + if (hid_locate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_TWHEEL), + hid_input, &sc->sc_loc_t, &flags)) { + sc->sc_loc_t.pos = sc->sc_loc_t.pos + 8; + sc->flags |= UMS_T; + } + + /* figure out the number of buttons */ + for (i = 1; i <= MAX_BUTTONS; i++) + if (!hid_locate(desc, size, HID_USAGE2(HUP_BUTTON, i), + hid_input, &loc_btn, 0)) + break; + sc->nbuttons = i - 1; + sc->sc_loc_btn = malloc(sizeof(struct hid_location)*sc->nbuttons, + M_USBDEV, M_NOWAIT); + if (!sc->sc_loc_btn) { + printf("%s: no memory\n", device_get_nameunit(sc->sc_dev)); + return ENXIO; + } + + printf("%s: %d buttons%s%s.\n", device_get_nameunit(sc->sc_dev), + sc->nbuttons, sc->flags & UMS_Z? " and Z dir" : "", + sc->flags & UMS_T?" and a TILT dir": ""); + + for (i = 1; i <= sc->nbuttons; i++) + hid_locate(desc, size, HID_USAGE2(HUP_BUTTON, i), + hid_input, &sc->sc_loc_btn[i-1], 0); + + sc->sc_isize = hid_report_size(desc, size, hid_input, &sc->sc_iid); + + /* + * The Microsoft Wireless Notebook Optical Mouse seems to be in worse + * shape than the Wireless Intellimouse 2.0, as its X, Y, wheel, and + * all of its other button positions are all off. It also reports that + * it has two addional buttons and a tilt wheel. + */ + if (usbd_get_quirks(uaa->device)->uq_flags & UQ_MS_BAD_CLASS) { + sc->flags = UMS_Z; + sc->flags |= UMS_SPUR_BUT_UP; + sc->nbuttons = 3; + sc->sc_isize = 5; + sc->sc_iid = 0; + /* 1st byte of descriptor report contains garbage */ + sc->sc_loc_x.pos = 16; + sc->sc_loc_y.pos = 24; + sc->sc_loc_z.pos = 32; + sc->sc_loc_btn[0].pos = 8; + sc->sc_loc_btn[1].pos = 9; + sc->sc_loc_btn[2].pos = 10; + } + + /* + * The Microsoft Wireless Notebook Optical Mouse 3000 Model 1049 has + * five Report IDs: 19 23 24 17 18 (in the order they appear in report + * descriptor), it seems that report id 17 contains the necessary + * mouse information(3-buttons,X,Y,wheel) so we specify it manually. + */ + if (uaa->vendor == USB_VENDOR_MICROSOFT && + uaa->product == USB_PRODUCT_MICROSOFT_WLNOTEBOOK3) { + sc->flags = UMS_Z; + sc->nbuttons = 3; + sc->sc_isize = 5; + sc->sc_iid = 17; + sc->sc_loc_x.pos = 8; + sc->sc_loc_y.pos = 16; + sc->sc_loc_z.pos = 24; + sc->sc_loc_btn[0].pos = 0; + sc->sc_loc_btn[1].pos = 1; + sc->sc_loc_btn[2].pos = 2; + } + + sc->sc_ibuf = malloc(sc->sc_isize, M_USB, M_NOWAIT); + if (!sc->sc_ibuf) { + printf("%s: no memory\n", device_get_nameunit(sc->sc_dev)); + free(sc->sc_loc_btn, M_USB); + return ENXIO; + } + + sc->sc_ep_addr = ed->bEndpointAddress; + sc->sc_disconnected = 0; + free(desc, M_TEMP); + +#ifdef USB_DEBUG + DPRINTF(("ums_attach: sc=%p\n", sc)); + DPRINTF(("ums_attach: X\t%d/%d\n", + sc->sc_loc_x.pos, sc->sc_loc_x.size)); + DPRINTF(("ums_attach: Y\t%d/%d\n", + sc->sc_loc_y.pos, sc->sc_loc_y.size)); + if (sc->flags & UMS_Z) + DPRINTF(("ums_attach: Z\t%d/%d\n", + sc->sc_loc_z.pos, sc->sc_loc_z.size)); + for (i = 1; i <= sc->nbuttons; i++) { + DPRINTF(("ums_attach: B%d\t%d/%d\n", + i, sc->sc_loc_btn[i-1].pos,sc->sc_loc_btn[i-1].size)); + } + DPRINTF(("ums_attach: size=%d, id=%d\n", sc->sc_isize, sc->sc_iid)); +#endif + + if (sc->nbuttons > MOUSE_MSC_MAXBUTTON) + sc->hw.buttons = MOUSE_MSC_MAXBUTTON; + else + sc->hw.buttons = sc->nbuttons; + sc->hw.iftype = MOUSE_IF_USB; + sc->hw.type = MOUSE_MOUSE; + sc->hw.model = MOUSE_MODEL_GENERIC; + sc->hw.hwid = 0; + sc->mode.protocol = MOUSE_PROTO_MSC; + sc->mode.rate = -1; + sc->mode.resolution = MOUSE_RES_UNKNOWN; + sc->mode.accelfactor = 0; + sc->mode.level = 0; + sc->mode.packetsize = MOUSE_MSC_PACKETSIZE; + sc->mode.syncmask[0] = MOUSE_MSC_SYNCMASK; + sc->mode.syncmask[1] = MOUSE_MSC_SYNC; + + sc->status.flags = 0; + sc->status.button = sc->status.obutton = 0; + sc->status.dx = sc->status.dy = sc->status.dz = 0; + + sc->dev = make_dev(&ums_cdevsw, device_get_unit(self), + UID_ROOT, GID_OPERATOR, + 0644, "ums%d", device_get_unit(self)); + + callout_init(&sc->callout_handle, 0); + if (usbd_get_quirks(uaa->device)->uq_flags & UQ_SPUR_BUT_UP) { + DPRINTF(("%s: Spurious button up events\n", + device_get_nameunit(sc->sc_dev))); + sc->flags |= UMS_SPUR_BUT_UP; + } + + return 0; +} + + +static int +ums_detach(device_t self) +{ + struct ums_softc *sc = device_get_softc(self); + + if (sc->sc_enabled) + ums_disable(sc); + + DPRINTF(("%s: disconnected\n", device_get_nameunit(self))); + + free(sc->sc_loc_btn, M_USB); + free(sc->sc_ibuf, M_USB); + + /* someone waiting for data */ + /* + * XXX If we wakeup the process here, the device will be gone by + * the time the process gets a chance to notice. *_close and friends + * should be fixed to handle this case. + * Or we should do a delayed detach for this. + * Does this delay now force tsleep to exit with an error? + */ + if (sc->state & UMS_ASLEEP) { + sc->state &= ~UMS_ASLEEP; + wakeup(sc); + } + if (sc->state & UMS_SELECT) { + sc->state &= ~UMS_SELECT; + selwakeuppri(&sc->rsel, PZERO); + } + + destroy_dev(sc->dev); + + return 0; +} + +void +ums_intr(usbd_xfer_handle xfer, usbd_private_handle addr, usbd_status status) +{ + struct ums_softc *sc = addr; + u_char *ibuf; + int dx, dy, dz, dw, dt; + int buttons = 0; + int i; + +#define UMS_BUT(i) ((i) < 3 ? (((i) + 2) % 3) : (i)) + + DPRINTFN(5, ("ums_intr: sc=%p status=%d\n", sc, status)); + DPRINTFN(5, ("ums_intr: data =")); + for (i = 0; i < sc->sc_isize; i++) + DPRINTFN(5, (" %02x", sc->sc_ibuf[i])); + DPRINTFN(5, ("\n")); + + if (status == USBD_CANCELLED) + return; + + if (status != USBD_NORMAL_COMPLETION) { + DPRINTF(("ums_intr: status=%d\n", status)); + if (status == USBD_STALLED) + usbd_clear_endpoint_stall_async(sc->sc_intrpipe); + if(status != USBD_IOERROR) + return; + } + + ibuf = sc->sc_ibuf; + /* + * The M$ Wireless Intellimouse 2.0 sends 1 extra leading byte of + * data compared to most USB mice. This byte frequently switches + * from 0x01 (usual state) to 0x02. I assume it is to allow + * extra, non-standard, reporting (say battery-life). However + * at the same time it generates a left-click message on the button + * byte which causes spurious left-click's where there shouldn't be. + * This should sort that. + * Currently it's the only user of UMS_T so use it as an identifier. + * We probably should switch to some more official quirk. + * + * UPDATE: This problem affects the M$ Wireless Notebook Optical Mouse, + * too. However, the leading byte for this mouse is normally 0x11, + * and the phantom mouse click occurs when its 0x14. + */ + if (sc->flags & UMS_T) { + if (sc->sc_iid) { + if (*ibuf++ == 0x02) + return; + } + } else if (sc->flags & UMS_SPUR_BUT_UP) { + DPRINTFN(5, ("ums_intr: #### ibuf[0] =3D %d ####\n", *ibuf)); + if (*ibuf == 0x14 || *ibuf == 0x15) + return; + } else { + if (sc->sc_iid) { + if (*ibuf++ != sc->sc_iid) + return; + } + } + + dx = hid_get_data(ibuf, &sc->sc_loc_x); + dy = -hid_get_data(ibuf, &sc->sc_loc_y); + dz = -hid_get_data(ibuf, &sc->sc_loc_z); + dw = hid_get_data(ibuf, &sc->sc_loc_w); + if (sc->flags & UMS_REVZ) + dz = -dz; + if (sc->flags & UMS_T) + dt = -hid_get_data(ibuf, &sc->sc_loc_t); + else + dt = 0; + for (i = 0; i < sc->nbuttons; i++) + if (hid_get_data(ibuf, &sc->sc_loc_btn[i])) + buttons |= (1 << UMS_BUT(i)); + + if (dx || dy || dz || dt || dw || (sc->flags & UMS_Z) + || buttons != sc->status.button) { + DPRINTFN(5, ("ums_intr: x:%d y:%d z:%d w:%d t:%d buttons:0x%x\n", + dx, dy, dz, dw, dt, buttons)); + + sc->status.button = buttons; + sc->status.dx += dx; + sc->status.dy += dy; + sc->status.dz += dz; + /* sc->status.dt += dt; */ /* no way to export this yet */ + /* sc->status.dw += dw; */ /* idem */ + + /* Discard data in case of full buffer */ + if (sc->qcount == sizeof(sc->qbuf)) { + DPRINTF(("Buffer full, discarded packet")); + return; + } + + /* + * The Qtronix keyboard has a built in PS/2 port for a mouse. + * The firmware once in a while posts a spurious button up + * event. This event we ignore by doing a timeout for 50 msecs. + * If we receive dx=dy=dz=buttons=0 before we add the event to + * the queue. + * In any other case we delete the timeout event. + */ + if (sc->flags & UMS_SPUR_BUT_UP && + dx == 0 && dy == 0 && dz == 0 && dt == 0 && buttons == 0) { + callout_reset(&sc->callout_handle, MS_TO_TICKS(50), + ums_add_to_queue_timeout, (void *) sc); + } else { + callout_stop(&sc->callout_handle); + ums_add_to_queue(sc, dx, dy, dz, dt, buttons); + } + } +} + +static void +ums_add_to_queue_timeout(void *priv) +{ + struct ums_softc *sc = priv; + int s; + + s = splusb(); + ums_add_to_queue(sc, 0, 0, 0, 0, 0); + splx(s); +} + +static void +ums_add_to_queue(struct ums_softc *sc, int dx, int dy, int dz, int dt, int buttons) +{ + /* Discard data in case of full buffer */ + if (sc->qhead+sc->mode.packetsize > sizeof(sc->qbuf)) { + DPRINTF(("Buffer full, discarded packet")); + return; + } + + if (dx > 254) dx = 254; + if (dx < -256) dx = -256; + if (dy > 254) dy = 254; + if (dy < -256) dy = -256; + if (dz > 126) dz = 126; + if (dz < -128) dz = -128; + if (dt > 126) dt = 126; + if (dt < -128) dt = -128; + + sc->qbuf[sc->qhead] = sc->mode.syncmask[1]; + sc->qbuf[sc->qhead] |= ~buttons & MOUSE_MSC_BUTTONS; + sc->qbuf[sc->qhead+1] = dx >> 1; + sc->qbuf[sc->qhead+2] = dy >> 1; + sc->qbuf[sc->qhead+3] = dx - (dx >> 1); + sc->qbuf[sc->qhead+4] = dy - (dy >> 1); + + if (sc->mode.level == 1) { + sc->qbuf[sc->qhead+5] = dz >> 1; + sc->qbuf[sc->qhead+6] = dz - (dz >> 1); + sc->qbuf[sc->qhead+7] = ((~buttons >> 3) + & MOUSE_SYS_EXTBUTTONS); + } + + sc->qhead += sc->mode.packetsize; + sc->qcount += sc->mode.packetsize; + /* wrap round at end of buffer */ + if (sc->qhead >= sizeof(sc->qbuf)) + sc->qhead = 0; + + /* someone waiting for data */ + if (sc->state & UMS_ASLEEP) { + sc->state &= ~UMS_ASLEEP; + wakeup(sc); + } + if (sc->state & UMS_SELECT) { + sc->state &= ~UMS_SELECT; + selwakeuppri(&sc->rsel, PZERO); + } +} +static int +ums_enable(v) + void *v; +{ + struct ums_softc *sc = v; + + usbd_status err; + + if (sc->sc_enabled) + return EBUSY; + + sc->sc_enabled = 1; + sc->qcount = 0; + sc->qhead = sc->qtail = 0; + sc->status.flags = 0; + sc->status.button = sc->status.obutton = 0; + sc->status.dx = sc->status.dy = sc->status.dz /* = sc->status.dt */ = 0; + + callout_handle_init((struct callout_handle *)&sc->callout_handle); + + /* + * Force the report (non-boot) protocol. + * + * Mice without boot protocol support may choose not to implement + * Set_Protocol at all; do not check for error. + */ + usbd_set_protocol(sc->sc_iface, 1); + + /* Set up interrupt pipe. */ + err = usbd_open_pipe_intr(sc->sc_iface, sc->sc_ep_addr, + USBD_SHORT_XFER_OK, &sc->sc_intrpipe, sc, + sc->sc_ibuf, sc->sc_isize, ums_intr, + USBD_DEFAULT_INTERVAL); + if (err) { + DPRINTF(("ums_enable: usbd_open_pipe_intr failed, error=%d\n", + err)); + sc->sc_enabled = 0; + return (EIO); + } + return (0); +} + +static void +ums_disable(priv) + void *priv; +{ + struct ums_softc *sc = priv; + + callout_stop(&sc->callout_handle); + + /* Disable interrupts. */ + usbd_abort_pipe(sc->sc_intrpipe); + usbd_close_pipe(sc->sc_intrpipe); + + sc->sc_enabled = 0; + + if (sc->qcount != 0) + DPRINTF(("Discarded %d bytes in queue\n", sc->qcount)); +} + +static int +ums_open(struct cdev *dev, int flag, int fmt, struct thread *p) +{ + struct ums_softc *sc; + + sc = devclass_get_softc(ums_devclass, UMSUNIT(dev)); + if (sc == NULL) + return (ENXIO); + + return ums_enable(sc); +} + +static int +ums_close(struct cdev *dev, int flag, int fmt, struct thread *p) +{ + struct ums_softc *sc; + + sc = devclass_get_softc(ums_devclass, UMSUNIT(dev)); + if (!sc) + return 0; + + if (sc->sc_enabled) + ums_disable(sc); + + return 0; +} + +static int +ums_read(struct cdev *dev, struct uio *uio, int flag) +{ + struct ums_softc *sc; + int s; + char buf[sizeof(sc->qbuf)]; + int l = 0; + int error; + + sc = devclass_get_softc(ums_devclass, UMSUNIT(dev)); + s = splusb(); + if (!sc) { + splx(s); + return EIO; + } + + while (sc->qcount == 0 ) { + if (flag & O_NONBLOCK) { /* non-blocking I/O */ + splx(s); + return EWOULDBLOCK; + } + + sc->state |= UMS_ASLEEP; /* blocking I/O */ + error = tsleep(sc, PZERO | PCATCH, "umsrea", 0); + if (error) { + splx(s); + return error; + } else if (!sc->sc_enabled) { + splx(s); + return EINTR; + } + /* check whether the device is still there */ + + sc = devclass_get_softc(ums_devclass, UMSUNIT(dev)); + if (!sc) { + splx(s); + return EIO; + } + } + + /* + * XXX we could optimise the use of splx/splusb somewhat. The writer + * process only extends qcount and qtail. We could copy them and use the copies + * to do the copying out of the queue. + */ + + while ((sc->qcount > 0) && (uio->uio_resid > 0)) { + l = (sc->qcount < uio->uio_resid? sc->qcount:uio->uio_resid); + if (l > sizeof(buf)) + l = sizeof(buf); + if (l > sizeof(sc->qbuf) - sc->qtail) /* transfer till end of buf */ + l = sizeof(sc->qbuf) - sc->qtail; + + splx(s); + uiomove(&sc->qbuf[sc->qtail], l, uio); + s = splusb(); + + if ( sc->qcount - l < 0 ) { + DPRINTF(("qcount below 0, count=%d l=%d\n", sc->qcount, l)); + sc->qcount = l; + } + sc->qcount -= l; /* remove the bytes from the buffer */ + sc->qtail = (sc->qtail + l) % sizeof(sc->qbuf); + } + splx(s); + + return 0; +} + +static int +ums_poll(struct cdev *dev, int events, struct thread *p) +{ + struct ums_softc *sc; + int revents = 0; + int s; + + sc = devclass_get_softc(ums_devclass, UMSUNIT(dev)); + if (!sc) + return 0; + + s = splusb(); + if (events & (POLLIN | POLLRDNORM)) { + if (sc->qcount) { + revents = events & (POLLIN | POLLRDNORM); + } else { + sc->state |= UMS_SELECT; + selrecord(p, &sc->rsel); + } + } + splx(s); + + return revents; +} + +int +ums_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, struct thread *p) +{ + struct ums_softc *sc; + int error = 0; + int s; + mousemode_t mode; + + sc = devclass_get_softc(ums_devclass, UMSUNIT(dev)); + if (!sc) + return EIO; + + switch(cmd) { + case MOUSE_GETHWINFO: + *(mousehw_t *)addr = sc->hw; + break; + case MOUSE_GETMODE: + *(mousemode_t *)addr = sc->mode; + break; + case MOUSE_SETMODE: + mode = *(mousemode_t *)addr; + + if (mode.level == -1) + /* don't change the current setting */ + ; + else if ((mode.level < 0) || (mode.level > 1)) + return (EINVAL); + + s = splusb(); + sc->mode.level = mode.level; + + if (sc->mode.level == 0) { + if (sc->nbuttons > MOUSE_MSC_MAXBUTTON) + sc->hw.buttons = MOUSE_MSC_MAXBUTTON; + else + sc->hw.buttons = sc->nbuttons; + sc->mode.protocol = MOUSE_PROTO_MSC; + sc->mode.packetsize = MOUSE_MSC_PACKETSIZE; + sc->mode.syncmask[0] = MOUSE_MSC_SYNCMASK; + sc->mode.syncmask[1] = MOUSE_MSC_SYNC; + } else if (sc->mode.level == 1) { + if (sc->nbuttons > MOUSE_SYS_MAXBUTTON) + sc->hw.buttons = MOUSE_SYS_MAXBUTTON; + else + sc->hw.buttons = sc->nbuttons; + sc->mode.protocol = MOUSE_PROTO_SYSMOUSE; + sc->mode.packetsize = MOUSE_SYS_PACKETSIZE; + sc->mode.syncmask[0] = MOUSE_SYS_SYNCMASK; + sc->mode.syncmask[1] = MOUSE_SYS_SYNC; + } + + bzero(sc->qbuf, sizeof(sc->qbuf)); + sc->qhead = sc->qtail = sc->qcount = 0; + splx(s); + + break; + case MOUSE_GETLEVEL: + *(int *)addr = sc->mode.level; + break; + case MOUSE_SETLEVEL: + if (*(int *)addr < 0 || *(int *)addr > 1) + return (EINVAL); + + s = splusb(); + sc->mode.level = *(int *)addr; + + if (sc->mode.level == 0) { + if (sc->nbuttons > MOUSE_MSC_MAXBUTTON) + sc->hw.buttons = MOUSE_MSC_MAXBUTTON; + else + sc->hw.buttons = sc->nbuttons; + sc->mode.protocol = MOUSE_PROTO_MSC; + sc->mode.packetsize = MOUSE_MSC_PACKETSIZE; + sc->mode.syncmask[0] = MOUSE_MSC_SYNCMASK; + sc->mode.syncmask[1] = MOUSE_MSC_SYNC; + } else if (sc->mode.level == 1) { + if (sc->nbuttons > MOUSE_SYS_MAXBUTTON) + sc->hw.buttons = MOUSE_SYS_MAXBUTTON; + else + sc->hw.buttons = sc->nbuttons; + sc->mode.protocol = MOUSE_PROTO_SYSMOUSE; + sc->mode.packetsize = MOUSE_SYS_PACKETSIZE; + sc->mode.syncmask[0] = MOUSE_SYS_SYNCMASK; + sc->mode.syncmask[1] = MOUSE_SYS_SYNC; + } + + bzero(sc->qbuf, sizeof(sc->qbuf)); + sc->qhead = sc->qtail = sc->qcount = 0; + splx(s); + + break; + case MOUSE_GETSTATUS: { + mousestatus_t *status = (mousestatus_t *) addr; + + s = splusb(); + *status = sc->status; + sc->status.obutton = sc->status.button; + sc->status.button = 0; + sc->status.dx = sc->status.dy + = sc->status.dz = /* sc->status.dt = */ 0; + splx(s); + + if (status->dx || status->dy || status->dz /* || status->dt */) + status->flags |= MOUSE_POSCHANGED; + if (status->button != status->obutton) + status->flags |= MOUSE_BUTTONSCHANGED; + break; + } + default: + error = ENOTTY; + } + + return error; +} + +MODULE_DEPEND(ums, usb, 1, 1, 1); +DRIVER_MODULE(ums, uhub, ums_driver, ums_devclass, usbd_driver_load, 0); diff --git a/sys/legacy/dev/usb/uplcom.c b/sys/legacy/dev/usb/uplcom.c new file mode 100644 index 0000000..ab5ab93 --- /dev/null +++ b/sys/legacy/dev/usb/uplcom.c @@ -0,0 +1,990 @@ +/* $NetBSD: uplcom.c,v 1.21 2001/11/13 06:24:56 lukem Exp $ */ + +/*- + * Copyright (c) 2001-2003, 2005 Shunsuke Akiyama <akiyama@jp.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. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2001 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Ichiro FUKUHARA (ichiro@ichiro.org). + * + * 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. + */ + +/* + * This driver supports several USB-to-RS232 serial adapters driven by + * Prolific PL-2303, PL-2303X and probably PL-2303HX USB-to-RS232 + * bridge chip. The adapters are sold under many different brand + * names. + * + * Datasheets are available at Prolific www site at + * http://www.prolific.com.tw. The datasheets don't contain full + * programming information for the chip. + * + * PL-2303HX is probably programmed the same as PL-2303X. + * + * There are several differences between PL-2303 and PL-2303(H)X. + * PL-2303(H)X can do higher bitrate in bulk mode, has _probably_ + * different command for controlling CRTSCTS and needs special + * sequence of commands for initialization which aren't also + * documented in the datasheet. + */ + +#include "opt_uplcom.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/malloc.h> +#include <sys/bus.h> +#include <sys/ioccom.h> +#include <sys/fcntl.h> +#include <sys/conf.h> +#include <sys/serial.h> +#include <sys/tty.h> +#include <sys/file.h> +#include <sys/selinfo.h> +#include <sys/proc.h> +#include <sys/poll.h> +#include <sys/sysctl.h> +#include <sys/uio.h> +#include <sys/taskqueue.h> + +#include <machine/bus.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbcdc.h> + +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdivar.h> +#include <dev/usb/usbdi_util.h> +#include "usbdevs.h" +#include <dev/usb/usb_quirks.h> + +#include <dev/usb/ucomvar.h> + +SYSCTL_NODE(_hw_usb, OID_AUTO, uplcom, CTLFLAG_RW, 0, "USB uplcom"); +#ifdef USB_DEBUG +static int uplcomdebug = 0; +SYSCTL_INT(_hw_usb_uplcom, OID_AUTO, debug, CTLFLAG_RW, + &uplcomdebug, 0, "uplcom debug level"); + +#define DPRINTFN(n, x) do { \ + if (uplcomdebug > (n)) \ + printf x; \ + } while (0) +#else +#define DPRINTFN(n, x) +#endif +#define DPRINTF(x) DPRINTFN(0, x) + +#define UPLCOM_MODVER 1 /* module version */ + +#define UPLCOM_CONFIG_INDEX 0 +#define UPLCOM_IFACE_INDEX 0 +#define UPLCOM_SECOND_IFACE_INDEX 1 + +#ifndef UPLCOM_INTR_INTERVAL +#define UPLCOM_INTR_INTERVAL 100 /* ms */ +#endif + +#define UPLCOM_SET_REQUEST 0x01 +#define UPLCOM_SET_CRTSCTS 0x41 +#define UPLCOM_SET_CRTSCTS_PL2303X 0x61 +#define RSAQ_STATUS_CTS 0x80 +#define RSAQ_STATUS_DSR 0x02 +#define RSAQ_STATUS_DCD 0x01 + +#define TYPE_PL2303 0 +#define TYPE_PL2303X 1 + +struct uplcom_softc { + struct ucom_softc sc_ucom; + + int sc_iface_number; /* interface number */ + + usbd_interface_handle sc_intr_iface; /* interrupt interface */ + int sc_intr_number; /* interrupt number */ + usbd_pipe_handle sc_intr_pipe; /* interrupt pipe */ + u_char *sc_intr_buf; /* interrupt buffer */ + int sc_isize; + + usb_cdc_line_state_t sc_line_state; /* current line state */ + u_char sc_dtr; /* current DTR state */ + u_char sc_rts; /* current RTS state */ + u_char sc_status; + + u_char sc_lsr; /* Local status register */ + u_char sc_msr; /* uplcom status register */ + + int sc_chiptype; /* Type of chip */ + + struct task sc_task; +}; + +/* + * These are the maximum number of bytes transferred per frame. + * The output buffer size cannot be increased due to the size encoding. + */ +#define UPLCOMIBUFSIZE 256 +#define UPLCOMOBUFSIZE 256 + +static usbd_status uplcom_reset(struct uplcom_softc *); +static usbd_status uplcom_set_line_coding(struct uplcom_softc *, + usb_cdc_line_state_t *); +static usbd_status uplcom_set_crtscts(struct uplcom_softc *); +static void uplcom_intr(usbd_xfer_handle, usbd_private_handle, usbd_status); + +static void uplcom_set(void *, int, int, int); +static void uplcom_dtr(struct uplcom_softc *, int); +static void uplcom_rts(struct uplcom_softc *, int); +static void uplcom_break(struct uplcom_softc *, int); +static void uplcom_set_line_state(struct uplcom_softc *); +static void uplcom_get_status(void *, int, u_char *, u_char *); +static int uplcom_param(void *, int, struct termios *); +static int uplcom_open(void *, int); +static void uplcom_close(void *, int); +static void uplcom_notify(void *, int); + +struct ucom_callback uplcom_callback = { + uplcom_get_status, + uplcom_set, + uplcom_param, + NULL, + uplcom_open, + uplcom_close, + NULL, + NULL +}; + +static const struct uplcom_product { + uint16_t vendor; + uint16_t product; + int32_t release; /* release is a 16bit entity, + * if -1 is specified we "don't care" + * This is a floor value. The table + * must have newer revs before older + * revs (and -1 last). + */ + char chiptype; +} uplcom_products [] = { + { USB_VENDOR_RADIOSHACK, USB_PRODUCT_RADIOSHACK_USBCABLE, -1, TYPE_PL2303 }, + + /* I/O DATA USB-RSAQ */ + { USB_VENDOR_IODATA, USB_PRODUCT_IODATA_USBRSAQ, -1, TYPE_PL2303 }, + /* Prolific Pharos */ + { USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_PHAROS, -1, TYPE_PL2303 }, + /* I/O DATA USB-RSAQ2 */ + { USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_RSAQ2, -1, TYPE_PL2303 }, + /* I/O DATA USB-RSAQ3 */ + { USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_RSAQ3, -1, TYPE_PL2303X }, + /* Willcom W-SIM*/ + { USB_VENDOR_PROLIFIC2, USB_PRODUCT_PROLIFIC2_WSIM, -1, TYPE_PL2303X}, + /* PLANEX USB-RS232 URS-03 */ + { USB_VENDOR_ATEN, USB_PRODUCT_ATEN_UC232A, -1, TYPE_PL2303 }, + /* TrendNet TU-S9 */ + { USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_PL2303, + 0x400, TYPE_PL2303X }, + /* ST Lab USB-SERIAL-4 */ + { USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_PL2303, + 0x300, TYPE_PL2303X }, + /* IOGEAR/ATEN UC-232A (also ST Lab USB-SERIAL-1) */ + { USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_PL2303, -1, TYPE_PL2303 }, + /* TDK USB-PHS Adapter UHA6400 */ + { USB_VENDOR_TDK, USB_PRODUCT_TDK_UHA6400, -1, TYPE_PL2303 }, + /* RATOC REX-USB60 */ + { USB_VENDOR_RATOC, USB_PRODUCT_RATOC_REXUSB60, -1, TYPE_PL2303 }, + /* ELECOM UC-SGT */ + { USB_VENDOR_ELECOM, USB_PRODUCT_ELECOM_UCSGT, -1, TYPE_PL2303 }, + { USB_VENDOR_ELECOM, USB_PRODUCT_ELECOM_UCSGT0, -1, TYPE_PL2303 }, + /* Sagem USB-Serial Controller */ + { USB_VENDOR_SAGEM, USB_PRODUCT_SAGEM_USBSERIAL, -1, TYPE_PL2303X }, + /* Sony Ericsson USB Cable */ + { USB_VENDOR_SONYERICSSON, USB_PRODUCT_SONYERICSSON_DCU10, + -1,TYPE_PL2303 }, + /* SOURCENEXT KeikaiDenwa 8 */ + { USB_VENDOR_SOURCENEXT, USB_PRODUCT_SOURCENEXT_KEIKAI8, + -1, TYPE_PL2303 }, + /* SOURCENEXT KeikaiDenwa 8 with charger */ + { USB_VENDOR_SOURCENEXT, USB_PRODUCT_SOURCENEXT_KEIKAI8_CHG, + -1, TYPE_PL2303 }, + /* HAL Corporation Crossam2+USB */ + { USB_VENDOR_HAL, USB_PRODUCT_HAL_IMR001, -1, TYPE_PL2303 }, + /* Sitecom USB to Serial */ + { USB_VENDOR_SITECOM, USB_PRODUCT_SITECOM_SERIAL, -1, TYPE_PL2303 }, + /* Tripp-Lite U209-000-R */ + { USB_VENDOR_TRIPPLITE, USB_PRODUCT_TRIPPLITE_U209, -1, TYPE_PL2303X }, + /* Belkin F5U257 */ + { USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5U257, -1, TYPE_PL2303X }, + { 0, 0 } +}; + +static device_probe_t uplcom_match; +static device_attach_t uplcom_attach; +static device_detach_t uplcom_detach; + +static device_method_t uplcom_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, uplcom_match), + DEVMETHOD(device_attach, uplcom_attach), + DEVMETHOD(device_detach, uplcom_detach), + { 0, 0 } +}; + +static driver_t uplcom_driver = { + "ucom", + uplcom_methods, + sizeof (struct uplcom_softc) +}; + +DRIVER_MODULE(uplcom, uhub, uplcom_driver, ucom_devclass, usbd_driver_load, 0); +MODULE_DEPEND(uplcom, usb, 1, 1, 1); +MODULE_DEPEND(uplcom, ucom, UCOM_MINVER, UCOM_PREFVER, UCOM_MAXVER); +MODULE_VERSION(uplcom, UPLCOM_MODVER); + +static int uplcominterval = UPLCOM_INTR_INTERVAL; + +static int +sysctl_hw_usb_uplcom_interval(SYSCTL_HANDLER_ARGS) +{ + int err, val; + + val = uplcominterval; + err = sysctl_handle_int(oidp, &val, 0, req); + if (err != 0 || req->newptr == NULL) + return (err); + if (0 < val && val <= 1000) + uplcominterval = val; + else + err = EINVAL; + + return (err); +} + +SYSCTL_PROC(_hw_usb_uplcom, OID_AUTO, interval, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_usb_uplcom_interval, + "I", "uplcom interrupt pipe interval"); + +static int +uplcom_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + int i; + + if (uaa->iface != NULL) + return (UMATCH_NONE); + + for (i = 0; uplcom_products[i].vendor != 0; i++) { + if (uplcom_products[i].vendor == uaa->vendor && + uplcom_products[i].product == uaa->product && + (uplcom_products[i].release <= uaa->release || + uplcom_products[i].release == -1)) { + return (UMATCH_VENDOR_PRODUCT); + } + } + return (UMATCH_NONE); +} + +static int +uplcom_attach(device_t self) +{ + struct uplcom_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + usbd_device_handle dev = uaa->device; + struct ucom_softc *ucom; + usb_config_descriptor_t *cdesc; + usb_interface_descriptor_t *id; + usb_endpoint_descriptor_t *ed; + const char *devname; + usbd_status err; + int i; + + ucom = &sc->sc_ucom; + ucom->sc_dev = self; + ucom->sc_udev = dev; + ucom->sc_iface = uaa->iface; + + devname = device_get_nameunit(ucom->sc_dev); + + DPRINTF(("uplcom attach: sc = %p\n", sc)); + + /* determine chip type */ + for (i = 0; uplcom_products[i].vendor != 0; i++) { + if (uplcom_products[i].vendor == uaa->vendor && + uplcom_products[i].product == uaa->product && + (uplcom_products[i].release == uaa->release || + uplcom_products[i].release == -1)) { + sc->sc_chiptype = uplcom_products[i].chiptype; + break; + } + } + + /* + * check we found the device - attach should have ensured we + * don't get here without matching device + */ + if (uplcom_products[i].vendor == 0) { + printf("%s: didn't match\n", devname); + ucom->sc_dying = 1; + goto error; + } + +#ifdef USB_DEBUG + /* print the chip type */ + if (sc->sc_chiptype == TYPE_PL2303X) { + DPRINTF(("uplcom_attach: chiptype 2303X\n")); + } else { + DPRINTF(("uplcom_attach: chiptype 2303\n")); + } +#endif + + /* initialize endpoints */ + ucom->sc_bulkin_no = ucom->sc_bulkout_no = -1; + sc->sc_intr_number = -1; + sc->sc_intr_pipe = NULL; + + /* Move the device into the configured state. */ + err = usbd_set_config_index(dev, UPLCOM_CONFIG_INDEX, 1); + if (err) { + printf("%s: failed to set configuration: %s\n", + devname, usbd_errstr(err)); + ucom->sc_dying = 1; + goto error; + } + + /* get the config descriptor */ + cdesc = usbd_get_config_descriptor(ucom->sc_udev); + + if (cdesc == NULL) { + printf("%s: failed to get configuration descriptor\n", + device_get_nameunit(ucom->sc_dev)); + ucom->sc_dying = 1; + goto error; + } + + /* get the (first/common) interface */ + err = usbd_device2interface_handle(dev, UPLCOM_IFACE_INDEX, + &ucom->sc_iface); + if (err) { + printf("%s: failed to get interface: %s\n", + devname, usbd_errstr(err)); + ucom->sc_dying = 1; + goto error; + } + + /* Find the interrupt endpoints */ + + id = usbd_get_interface_descriptor(ucom->sc_iface); + sc->sc_iface_number = id->bInterfaceNumber; + + for (i = 0; i < id->bNumEndpoints; i++) { + ed = usbd_interface2endpoint_descriptor(ucom->sc_iface, i); + if (ed == NULL) { + printf("%s: no endpoint descriptor for %d\n", + device_get_nameunit(ucom->sc_dev), i); + ucom->sc_dying = 1; + goto error; + } + + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_INTERRUPT) { + sc->sc_intr_number = ed->bEndpointAddress; + sc->sc_isize = UGETW(ed->wMaxPacketSize); + } + } + + if (sc->sc_intr_number == -1) { + printf("%s: Could not find interrupt in\n", + device_get_nameunit(ucom->sc_dev)); + ucom->sc_dying = 1; + goto error; + } + + /* keep interface for interrupt */ + sc->sc_intr_iface = ucom->sc_iface; + + /* + * USB-RSAQ1 has two interface + * + * USB-RSAQ1 | USB-RSAQ2 + * -----------------+----------------- + * Interface 0 |Interface 0 + * Interrupt(0x81) | Interrupt(0x81) + * -----------------+ BulkIN(0x02) + * Interface 1 | BulkOUT(0x83) + * BulkIN(0x02) | + * BulkOUT(0x83) | + */ + if (cdesc->bNumInterface == 2) { + err = usbd_device2interface_handle(dev, + UPLCOM_SECOND_IFACE_INDEX, + &ucom->sc_iface); + if (err) { + printf("%s: failed to get second interface: %s\n", + devname, usbd_errstr(err)); + ucom->sc_dying = 1; + goto error; + } + } + + /* Find the bulk{in,out} endpoints */ + + id = usbd_get_interface_descriptor(ucom->sc_iface); + sc->sc_iface_number = id->bInterfaceNumber; + + for (i = 0; i < id->bNumEndpoints; i++) { + ed = usbd_interface2endpoint_descriptor(ucom->sc_iface, i); + if (ed == NULL) { + printf("%s: no endpoint descriptor for %d\n", + device_get_nameunit(ucom->sc_dev), i); + ucom->sc_dying = 1; + goto error; + } + + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + ucom->sc_bulkin_no = ed->bEndpointAddress; + } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + ucom->sc_bulkout_no = ed->bEndpointAddress; + } + } + + if (ucom->sc_bulkin_no == -1) { + printf("%s: Could not find data bulk in\n", + device_get_nameunit(ucom->sc_dev)); + ucom->sc_dying = 1; + goto error; + } + + if (ucom->sc_bulkout_no == -1) { + printf("%s: Could not find data bulk out\n", + device_get_nameunit(ucom->sc_dev)); + ucom->sc_dying = 1; + goto error; + } + + sc->sc_dtr = sc->sc_rts = -1; + ucom->sc_parent = sc; + ucom->sc_portno = UCOM_UNK_PORTNO; + /* bulkin, bulkout set above */ + ucom->sc_ibufsize = UPLCOMIBUFSIZE; + ucom->sc_obufsize = UPLCOMOBUFSIZE; + ucom->sc_ibufsizepad = UPLCOMIBUFSIZE; + ucom->sc_opkthdrlen = 0; + ucom->sc_callback = &uplcom_callback; + + err = uplcom_reset(sc); + + if (err) { + printf("%s: reset failed: %s\n", + device_get_nameunit(ucom->sc_dev), usbd_errstr(err)); + ucom->sc_dying = 1; + goto error; + } + + DPRINTF(("uplcom: in = 0x%x, out = 0x%x, intr = 0x%x\n", + ucom->sc_bulkin_no, ucom->sc_bulkout_no, sc->sc_intr_number)); + + TASK_INIT(&sc->sc_task, 0, uplcom_notify, sc); + ucom_attach(&sc->sc_ucom); + return 0; + +error: + return ENXIO; +} + +static int +uplcom_detach(device_t self) +{ + struct uplcom_softc *sc = device_get_softc(self); + int rv = 0; + + DPRINTF(("uplcom_detach: sc = %p\n", sc)); + + if (sc->sc_intr_pipe != NULL) { + usbd_abort_pipe(sc->sc_intr_pipe); + usbd_close_pipe(sc->sc_intr_pipe); + free(sc->sc_intr_buf, M_USBDEV); + sc->sc_intr_pipe = NULL; + } + + sc->sc_ucom.sc_dying = 1; + + rv = ucom_detach(&sc->sc_ucom); + + return (rv); +} + +static usbd_status +uplcom_reset(struct uplcom_softc *sc) +{ + usb_device_request_t req; + usbd_status err; + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UPLCOM_SET_REQUEST; + USETW(req.wValue, 0); + USETW(req.wIndex, sc->sc_iface_number); + USETW(req.wLength, 0); + + err = usbd_do_request(sc->sc_ucom.sc_udev, &req, 0); + if (err) { + printf("%s: uplcom_reset: %s\n", + device_get_nameunit(sc->sc_ucom.sc_dev), usbd_errstr(err)); + return (EIO); + } + + return (0); +} + +struct pl2303x_init { + uint8_t req_type; + uint8_t request; + uint16_t value; + uint16_t index; + uint16_t length; +}; + +static const struct pl2303x_init pl2303x[] = { + { UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8484, 0, 0 }, + { UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x0404, 0, 0 }, + { UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8484, 0, 0 }, + { UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8383, 0, 0 }, + { UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8484, 0, 0 }, + { UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x0404, 1, 0 }, + { UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8484, 0, 0 }, + { UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8383, 0, 0 }, + { UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0, 1, 0 }, + { UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 1, 0, 0 }, + { UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 2, 0x44, 0 } +}; +#define N_PL2302X_INIT (sizeof(pl2303x)/sizeof(pl2303x[0])) + +static usbd_status +uplcom_pl2303x_init(struct uplcom_softc *sc) +{ + usb_device_request_t req; + usbd_status err; + int i; + + for (i = 0; i < N_PL2302X_INIT; i++) { + req.bmRequestType = pl2303x[i].req_type; + req.bRequest = pl2303x[i].request; + USETW(req.wValue, pl2303x[i].value); + USETW(req.wIndex, pl2303x[i].index); + USETW(req.wLength, pl2303x[i].length); + + err = usbd_do_request(sc->sc_ucom.sc_udev, &req, 0); + if (err) { + printf("%s: uplcom_pl2303x_init: %d: %s\n", + device_get_nameunit(sc->sc_ucom.sc_dev), i, + usbd_errstr(err)); + return (EIO); + } + } + + return (0); +} + +static void +uplcom_set_line_state(struct uplcom_softc *sc) +{ + usb_device_request_t req; + int ls; + usbd_status err; + + ls = (sc->sc_dtr ? UCDC_LINE_DTR : 0) | + (sc->sc_rts ? UCDC_LINE_RTS : 0); + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_CONTROL_LINE_STATE; + USETW(req.wValue, ls); + USETW(req.wIndex, sc->sc_iface_number); + USETW(req.wLength, 0); + + err = usbd_do_request(sc->sc_ucom.sc_udev, &req, 0); + if (err) + printf("%s: uplcom_set_line_status: %s\n", + device_get_nameunit(sc->sc_ucom.sc_dev), usbd_errstr(err)); +} + +static void +uplcom_set(void *addr, int portno, int reg, int onoff) +{ + struct uplcom_softc *sc = addr; + + switch (reg) { + case UCOM_SET_DTR: + uplcom_dtr(sc, onoff); + break; + case UCOM_SET_RTS: + uplcom_rts(sc, onoff); + break; + case UCOM_SET_BREAK: + uplcom_break(sc, onoff); + break; + default: + break; + } +} + +static void +uplcom_dtr(struct uplcom_softc *sc, int onoff) +{ + DPRINTF(("uplcom_dtr: onoff = %d\n", onoff)); + + if (sc->sc_dtr == onoff) + return; + sc->sc_dtr = onoff; + + uplcom_set_line_state(sc); +} + +static void +uplcom_rts(struct uplcom_softc *sc, int onoff) +{ + DPRINTF(("uplcom_rts: onoff = %d\n", onoff)); + + if (sc->sc_rts == onoff) + return; + sc->sc_rts = onoff; + + uplcom_set_line_state(sc); +} + +static void +uplcom_break(struct uplcom_softc *sc, int onoff) +{ + usb_device_request_t req; + usbd_status err; + + DPRINTF(("uplcom_break: onoff = %d\n", onoff)); + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SEND_BREAK; + USETW(req.wValue, onoff ? UCDC_BREAK_ON : UCDC_BREAK_OFF); + USETW(req.wIndex, sc->sc_iface_number); + USETW(req.wLength, 0); + + err = usbd_do_request(sc->sc_ucom.sc_udev, &req, 0); + if (err) + printf("%s: uplcom_break: %s\n", + device_get_nameunit(sc->sc_ucom.sc_dev), usbd_errstr(err)); +} + +static usbd_status +uplcom_set_crtscts(struct uplcom_softc *sc) +{ + usb_device_request_t req; + usbd_status err; + + DPRINTF(("uplcom_set_crtscts: on\n")); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UPLCOM_SET_REQUEST; + USETW(req.wValue, 0); + if (sc->sc_chiptype == TYPE_PL2303X) + USETW(req.wIndex, UPLCOM_SET_CRTSCTS_PL2303X); + else + USETW(req.wIndex, UPLCOM_SET_CRTSCTS); + USETW(req.wLength, 0); + + err = usbd_do_request(sc->sc_ucom.sc_udev, &req, 0); + if (err) { + printf("%s: uplcom_set_crtscts: %s\n", + device_get_nameunit(sc->sc_ucom.sc_dev), usbd_errstr(err)); + return (err); + } + + return (USBD_NORMAL_COMPLETION); +} + +static usbd_status +uplcom_set_line_coding(struct uplcom_softc *sc, usb_cdc_line_state_t *state) +{ + usb_device_request_t req; + usbd_status err; + + DPRINTF(( +"uplcom_set_line_coding: rate = %d, fmt = %d, parity = %d bits = %d\n", + UGETDW(state->dwDTERate), state->bCharFormat, + state->bParityType, state->bDataBits)); + + if (memcmp(state, &sc->sc_line_state, UCDC_LINE_STATE_LENGTH) == 0) { + DPRINTF(("uplcom_set_line_coding: already set\n")); + return (USBD_NORMAL_COMPLETION); + } + + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UCDC_SET_LINE_CODING; + USETW(req.wValue, 0); + USETW(req.wIndex, sc->sc_iface_number); + USETW(req.wLength, UCDC_LINE_STATE_LENGTH); + + err = usbd_do_request(sc->sc_ucom.sc_udev, &req, state); + if (err) { + printf("%s: uplcom_set_line_coding: %s\n", + device_get_nameunit(sc->sc_ucom.sc_dev), usbd_errstr(err)); + return (err); + } + + sc->sc_line_state = *state; + + return (USBD_NORMAL_COMPLETION); +} + +static const int uplcom_rates[] = { + 75, 150, 300, 600, 1200, 1800, 2400, 3600, 4800, 7200, 9600, 14400, + 19200, 28800, 38400, 57600, 115200, + /* + * Higher speeds are probably possible. PL2303X supports up to + * 6Mb and can set any rate + */ + 230400, 460800, 614400, 921600, 1228800 +}; +#define N_UPLCOM_RATES (sizeof(uplcom_rates)/sizeof(uplcom_rates[0])) + +static int +uplcom_param(void *addr, int portno, struct termios *t) +{ + struct uplcom_softc *sc = addr; + usbd_status err; + usb_cdc_line_state_t ls; + int i; + + DPRINTF(("uplcom_param: sc = %p\n", sc)); + + /* Check requested baud rate */ + for (i = 0; i < N_UPLCOM_RATES; i++) + if (uplcom_rates[i] == t->c_ospeed) + break; + if (i == N_UPLCOM_RATES) { + DPRINTF(("uplcom_param: bad baud rate (%d)\n", t->c_ospeed)); + return (EIO); + } + + USETDW(ls.dwDTERate, t->c_ospeed); + if (ISSET(t->c_cflag, CSTOPB)) + ls.bCharFormat = UCDC_STOP_BIT_2; + else + ls.bCharFormat = UCDC_STOP_BIT_1; + if (ISSET(t->c_cflag, PARENB)) { + if (ISSET(t->c_cflag, PARODD)) + ls.bParityType = UCDC_PARITY_ODD; + else + ls.bParityType = UCDC_PARITY_EVEN; + } else + ls.bParityType = UCDC_PARITY_NONE; + switch (ISSET(t->c_cflag, CSIZE)) { + case CS5: + ls.bDataBits = 5; + break; + case CS6: + ls.bDataBits = 6; + break; + case CS7: + ls.bDataBits = 7; + break; + case CS8: + ls.bDataBits = 8; + break; + } + + err = uplcom_set_line_coding(sc, &ls); + if (err) + return (EIO); + + if (ISSET(t->c_cflag, CRTSCTS)) { + err = uplcom_set_crtscts(sc); + if (err) + return (EIO); + } + + return (0); +} + +static int +uplcom_open(void *addr, int portno) +{ + struct uplcom_softc *sc = addr; + int err; + + if (sc->sc_ucom.sc_dying) + return (ENXIO); + + DPRINTF(("uplcom_open: sc = %p\n", sc)); + + if (sc->sc_intr_number != -1 && sc->sc_intr_pipe == NULL) { + sc->sc_status = 0; /* clear status bit */ + sc->sc_intr_buf = malloc(sc->sc_isize, M_USBDEV, M_WAITOK); + err = usbd_open_pipe_intr(sc->sc_intr_iface, + sc->sc_intr_number, + USBD_SHORT_XFER_OK, + &sc->sc_intr_pipe, + sc, + sc->sc_intr_buf, + sc->sc_isize, + uplcom_intr, + uplcominterval); + if (err) { + printf("%s: cannot open interrupt pipe (addr %d)\n", + device_get_nameunit(sc->sc_ucom.sc_dev), + sc->sc_intr_number); + return (EIO); + } + } + + if (sc->sc_chiptype == TYPE_PL2303X) + return (uplcom_pl2303x_init(sc)); + + return (0); +} + +static void +uplcom_close(void *addr, int portno) +{ + struct uplcom_softc *sc = addr; + int err; + + if (sc->sc_ucom.sc_dying) + return; + + DPRINTF(("uplcom_close: close\n")); + + if (sc->sc_intr_pipe != NULL) { + err = usbd_abort_pipe(sc->sc_intr_pipe); + if (err) + printf("%s: abort interrupt pipe failed: %s\n", + device_get_nameunit(sc->sc_ucom.sc_dev), + usbd_errstr(err)); + err = usbd_close_pipe(sc->sc_intr_pipe); + if (err) + printf("%s: close interrupt pipe failed: %s\n", + device_get_nameunit(sc->sc_ucom.sc_dev), + usbd_errstr(err)); + free(sc->sc_intr_buf, M_USBDEV); + sc->sc_intr_pipe = NULL; + } +} + +static void +uplcom_intr(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + struct uplcom_softc *sc = priv; + u_char *buf = sc->sc_intr_buf; + u_char pstatus; + + if (sc->sc_ucom.sc_dying) + return; + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) + return; + + DPRINTF(("%s: uplcom_intr: abnormal status: %s\n", + device_get_nameunit(sc->sc_ucom.sc_dev), + usbd_errstr(status))); + usbd_clear_endpoint_stall_async(sc->sc_intr_pipe); + return; + } + + DPRINTF(("%s: uplcom status = %02x\n", + device_get_nameunit(sc->sc_ucom.sc_dev), buf[8])); + + sc->sc_lsr = sc->sc_msr = 0; + pstatus = buf[8]; + if (ISSET(pstatus, RSAQ_STATUS_CTS)) + sc->sc_msr |= SER_CTS; + else + sc->sc_msr &= ~SER_CTS; + if (ISSET(pstatus, RSAQ_STATUS_DSR)) + sc->sc_msr |= SER_DSR; + else + sc->sc_msr &= ~SER_DSR; + if (ISSET(pstatus, RSAQ_STATUS_DCD)) + sc->sc_msr |= SER_DCD; + else + sc->sc_msr &= ~SER_DCD; + + /* Deferred notifying to the ucom layer */ + taskqueue_enqueue(taskqueue_swi_giant, &sc->sc_task); +} + +static void +uplcom_notify(void *arg, int count) +{ + struct uplcom_softc *sc; + + sc = (struct uplcom_softc *)arg; + if (sc->sc_ucom.sc_dying) + return; + ucom_status_change(&sc->sc_ucom); +} + +static void +uplcom_get_status(void *addr, int portno, u_char *lsr, u_char *msr) +{ + struct uplcom_softc *sc = addr; + + DPRINTF(("uplcom_get_status:\n")); + + if (lsr != NULL) + *lsr = sc->sc_lsr; + if (msr != NULL) + *msr = sc->sc_msr; +} diff --git a/sys/legacy/dev/usb/urio.c b/sys/legacy/dev/usb/urio.c new file mode 100644 index 0000000..5f69918 --- /dev/null +++ b/sys/legacy/dev/usb/urio.c @@ -0,0 +1,518 @@ +/*- + * Copyright (c) 2000 Iwasa Kazmi + * 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. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + + +/* + * 2000/3/24 added NetBSD/OpenBSD support (from Alex Nemirovsky) + * 2000/3/07 use two bulk-pipe handles for read and write (Dirk) + * 2000/3/06 change major number(143), and copyright header + * some fix for 4.0 (Dirk) + * 2000/3/05 codes for FreeBSD 4.x - CURRENT (Thanks to Dirk-Willem van Gulik) + * 2000/3/01 remove retry code from urioioctl() + * change method of bulk transfer (no interrupt) + * 2000/2/28 small fixes for new rio_usb.h + * 2000/2/24 first version. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/ioccom.h> +#include <sys/fcntl.h> +#include <sys/filio.h> +#include <sys/conf.h> +#include <sys/uio.h> +#include <sys/tty.h> +#include <sys/file.h> +#include <sys/selinfo.h> +#include <sys/poll.h> +#include <sys/sysctl.h> +#include <sys/uio.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> + +#include "usbdevs.h" +#include <dev/usb/rio500_usb.h> + +#ifdef USB_DEBUG +#define DPRINTF(x) if (uriodebug) printf x +#define DPRINTFN(n,x) if (uriodebug>(n)) printf x +int uriodebug = 0; +SYSCTL_NODE(_hw_usb, OID_AUTO, urio, CTLFLAG_RW, 0, "USB urio"); +SYSCTL_INT(_hw_usb_urio, OID_AUTO, debug, CTLFLAG_RW, + &uriodebug, 0, "urio debug level"); +#else +#define DPRINTF(x) +#define DPRINTFN(n,x) +#endif + +/* difference of usbd interface */ +#define USBDI 1 + +#define RIO_OUT 0 +#define RIO_IN 1 +#define RIO_NODIR 2 + +d_open_t urioopen; +d_close_t urioclose; +d_read_t urioread; +d_write_t uriowrite; +d_ioctl_t urioioctl; + + +static struct cdevsw urio_cdevsw = { + .d_version = D_VERSION, + .d_flags = D_NEEDGIANT, + .d_open = urioopen, + .d_close = urioclose, + .d_read = urioread, + .d_write = uriowrite, + .d_ioctl = urioioctl, + .d_name = "urio", +}; +#define RIO_UE_GET_DIR(p) ((UE_GET_DIR(p) == UE_DIR_IN) ? RIO_IN :\ + ((UE_GET_DIR(p) == UE_DIR_OUT) ? RIO_OUT :\ + RIO_NODIR)) + +#define URIO_BBSIZE 1024 + +struct urio_softc { + device_t sc_dev; + usbd_device_handle sc_udev; + usbd_interface_handle sc_iface; + + int sc_opened; + usbd_pipe_handle sc_pipeh_in; + usbd_pipe_handle sc_pipeh_out; + int sc_epaddr[2]; + + int sc_refcnt; + struct cdev *sc_dev_t; + u_char sc_dying; +}; + +#define URIOUNIT(n) (dev2unit(n)) + +#define RIO_RW_TIMEOUT 4000 /* ms */ + +static device_probe_t urio_match; +static device_attach_t urio_attach; +static device_detach_t urio_detach; + +static device_method_t urio_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, urio_match), + DEVMETHOD(device_attach, urio_attach), + DEVMETHOD(device_detach, urio_detach), + + { 0, 0 } +}; + +static driver_t urio_driver = { + "urio", + urio_methods, + sizeof(struct urio_softc) +}; + +static devclass_t urio_devclass; + +static int +urio_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + usb_device_descriptor_t *dd; + + DPRINTFN(10,("urio_match\n")); + if (!uaa->iface) + return UMATCH_NONE; + + dd = usbd_get_device_descriptor(uaa->device); + + if (dd && + ((UGETW(dd->idVendor) == USB_VENDOR_DIAMOND && + UGETW(dd->idProduct) == USB_PRODUCT_DIAMOND_RIO500USB) || + (UGETW(dd->idVendor) == USB_VENDOR_DIAMOND2 && + (UGETW(dd->idProduct) == USB_PRODUCT_DIAMOND2_RIO600USB || + UGETW(dd->idProduct) == USB_PRODUCT_DIAMOND2_RIO800USB)))) + return UMATCH_VENDOR_PRODUCT; + else + return UMATCH_NONE; +} + +static int +urio_attach(device_t self) +{ + struct urio_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + usbd_device_handle udev; + usbd_interface_handle iface; + u_int8_t epcount; + usbd_status r; + char * ermsg = "<none>"; + int i; + + DPRINTFN(10,("urio_attach: sc=%p\n", sc)); + sc->sc_dev = self; + sc->sc_udev = udev = uaa->device; + + if ((!uaa->device) || (!uaa->iface)) { + ermsg = "device or iface"; + goto nobulk; + } + sc->sc_iface = iface = uaa->iface; + sc->sc_opened = 0; + sc->sc_pipeh_in = 0; + sc->sc_pipeh_out = 0; + sc->sc_refcnt = 0; + + r = usbd_endpoint_count(iface, &epcount); + if (r != USBD_NORMAL_COMPLETION) { + ermsg = "endpoints"; + goto nobulk; + } + + sc->sc_epaddr[RIO_OUT] = 0xff; + sc->sc_epaddr[RIO_IN] = 0x00; + + for (i = 0; i < epcount; i++) { + usb_endpoint_descriptor_t *edesc = + usbd_interface2endpoint_descriptor(iface, i); + int d; + + if (!edesc) { + ermsg = "interface endpoint"; + goto nobulk; + } + + d = RIO_UE_GET_DIR(edesc->bEndpointAddress); + if (d != RIO_NODIR) + sc->sc_epaddr[d] = edesc->bEndpointAddress; + } + if ( sc->sc_epaddr[RIO_OUT] == 0xff || + sc->sc_epaddr[RIO_IN] == 0x00) { + ermsg = "Rio I&O"; + goto nobulk; + } + + sc->sc_dev_t = make_dev(&urio_cdevsw, device_get_unit(self), + UID_ROOT, GID_OPERATOR, + 0644, "urio%d", device_get_unit(self)); + DPRINTFN(10, ("urio_attach: %p\n", sc->sc_udev)); + + return 0; + + nobulk: + printf("%s: could not find %s\n", device_get_nameunit(sc->sc_dev),ermsg); + return ENXIO; +} + + +int +urioopen(struct cdev *dev, int flag, int mode, struct thread *p) +{ + struct urio_softc * sc; + int unit = URIOUNIT(dev); + sc = devclass_get_softc(urio_devclass, unit); + if (sc == NULL) + return (ENXIO); + + DPRINTFN(5, ("urioopen: flag=%d, mode=%d, unit=%d\n", + flag, mode, unit)); + + if (sc->sc_opened) + return EBUSY; + + if ((flag & (FWRITE|FREAD)) != (FWRITE|FREAD)) + return EACCES; + + sc->sc_opened = 1; + sc->sc_pipeh_in = 0; + sc->sc_pipeh_out = 0; + if (usbd_open_pipe(sc->sc_iface, + sc->sc_epaddr[RIO_IN], 0, &sc->sc_pipeh_in) + != USBD_NORMAL_COMPLETION) + { + sc->sc_pipeh_in = 0; + return EIO; + }; + if (usbd_open_pipe(sc->sc_iface, + sc->sc_epaddr[RIO_OUT], 0, &sc->sc_pipeh_out) + != USBD_NORMAL_COMPLETION) + { + usbd_close_pipe(sc->sc_pipeh_in); + sc->sc_pipeh_in = 0; + sc->sc_pipeh_out = 0; + return EIO; + }; + return 0; +} + +int +urioclose(struct cdev *dev, int flag, int mode, struct thread *p) +{ + struct urio_softc * sc; + int unit = URIOUNIT(dev); + sc = devclass_get_softc(urio_devclass, unit); + + DPRINTFN(5, ("urioclose: flag=%d, mode=%d, unit=%d\n", flag, mode, unit)); + if (sc->sc_pipeh_in) + usbd_close_pipe(sc->sc_pipeh_in); + + if (sc->sc_pipeh_out) + usbd_close_pipe(sc->sc_pipeh_out); + + sc->sc_pipeh_in = 0; + sc->sc_pipeh_out = 0; + sc->sc_opened = 0; + sc->sc_refcnt = 0; + return 0; +} + +int +urioread(struct cdev *dev, struct uio *uio, int flag) +{ + struct urio_softc * sc; + usbd_xfer_handle reqh; + int unit = URIOUNIT(dev); + usbd_status r; + char buf[URIO_BBSIZE]; + u_int32_t n, tn; + int error = 0; + + sc = devclass_get_softc(urio_devclass, unit); + + DPRINTFN(5, ("urioread: %d\n", unit)); + if (!sc->sc_opened) + return EIO; + + sc->sc_refcnt++; + reqh = usbd_alloc_xfer(sc->sc_udev); + if (reqh == 0) + return ENOMEM; + while ((n = min(URIO_BBSIZE, uio->uio_resid)) != 0) { + DPRINTFN(1, ("urioread: start transfer %d bytes\n", n)); + tn = n; + usbd_setup_xfer(reqh, sc->sc_pipeh_in, 0, buf, tn, + 0, RIO_RW_TIMEOUT, 0); + r = usbd_sync_transfer(reqh); + if (r != USBD_NORMAL_COMPLETION) { + DPRINTFN(1, ("urioread: error=%d\n", r)); + usbd_clear_endpoint_stall(sc->sc_pipeh_in); + tn = 0; + error = EIO; + break; + } + usbd_get_xfer_status(reqh, 0, 0, &tn, 0); + + DPRINTFN(1, ("urioread: got %d bytes\n", tn)); + error = uiomove(buf, tn, uio); + if (error || tn < n) + break; + } + usbd_free_xfer(reqh); + return error; +} + +int +uriowrite(struct cdev *dev, struct uio *uio, int flag) +{ + struct urio_softc * sc; + usbd_xfer_handle reqh; + int unit = URIOUNIT(dev); + usbd_status r; + char buf[URIO_BBSIZE]; + u_int32_t n; + int error = 0; + + sc = devclass_get_softc(urio_devclass, unit); + DPRINTFN(5, ("uriowrite: %d\n", unit)); + if (!sc->sc_opened) + return EIO; + + sc->sc_refcnt++; + reqh = usbd_alloc_xfer(sc->sc_udev); + if (reqh == 0) + return EIO; + while ((n = min(URIO_BBSIZE, uio->uio_resid)) != 0) { + error = uiomove(buf, n, uio); + if (error) + break; + DPRINTFN(1, ("uriowrite: transfer %d bytes\n", n)); + usbd_setup_xfer(reqh, sc->sc_pipeh_out, 0, buf, n, + 0, RIO_RW_TIMEOUT, 0); + r = usbd_sync_transfer(reqh); + if (r != USBD_NORMAL_COMPLETION) { + DPRINTFN(1, ("uriowrite: error=%d\n", r)); + usbd_clear_endpoint_stall(sc->sc_pipeh_out); + error = EIO; + break; + } + usbd_get_xfer_status(reqh, 0, 0, 0, 0); + } + + usbd_free_xfer(reqh); + return error; +} + + +int +urioioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, struct thread *p) +{ + struct urio_softc * sc; + int unit = URIOUNIT(dev); + struct RioCommand *rio_cmd; + int requesttype, len; + struct iovec iov; + struct uio uio; + usb_device_request_t req; + int req_flags = 0, req_actlen = 0; + void *ptr = 0; + int error = 0; + usbd_status r; + + sc = devclass_get_softc(urio_devclass, unit); + switch (cmd) { + case RIO_RECV_COMMAND: + if (!(flag & FWRITE)) + return EPERM; + rio_cmd = (struct RioCommand *)addr; + if (rio_cmd == NULL) + return EINVAL; + len = rio_cmd->length; + + requesttype = rio_cmd->requesttype | UT_READ_VENDOR_DEVICE; + DPRINTFN(1,("sending command:reqtype=%0x req=%0x value=%0x index=%0x len=%0x\n", + requesttype, rio_cmd->request, rio_cmd->value, rio_cmd->index, len)); + break; + + case RIO_SEND_COMMAND: + if (!(flag & FWRITE)) + return EPERM; + rio_cmd = (struct RioCommand *)addr; + if (rio_cmd == NULL) + return EINVAL; + len = rio_cmd->length; + + requesttype = rio_cmd->requesttype | UT_WRITE_VENDOR_DEVICE; + DPRINTFN(1,("sending command:reqtype=%0x req=%0x value=%0x index=%0x len=%0x\n", + requesttype, rio_cmd->request, rio_cmd->value, rio_cmd->index, len)); + break; + + default: + return EINVAL; + break; + } + + /* Send rio control message */ + req.bmRequestType = requesttype; + req.bRequest = rio_cmd->request; + USETW(req.wValue, rio_cmd->value); + USETW(req.wIndex, rio_cmd->index); + USETW(req.wLength, len); + + if (len < 0 || len > 32767) + return EINVAL; + if (len != 0) { + iov.iov_base = (caddr_t)rio_cmd->buffer; + iov.iov_len = len; + uio.uio_iov = &iov; + uio.uio_iovcnt = 1; + uio.uio_resid = len; + uio.uio_offset = 0; + uio.uio_segflg = UIO_USERSPACE; + uio.uio_rw = + req.bmRequestType & UT_READ ? + UIO_READ : UIO_WRITE; + uio.uio_td = p; + ptr = malloc(len, M_TEMP, M_WAITOK); + if (uio.uio_rw == UIO_WRITE) { + error = uiomove(ptr, len, &uio); + if (error) + goto ret; + } + } + + r = usbd_do_request_flags(sc->sc_udev, &req, + ptr, req_flags, &req_actlen, + USBD_DEFAULT_TIMEOUT); + if (r == USBD_NORMAL_COMPLETION) { + error = 0; + if (len != 0) { + if (uio.uio_rw == UIO_READ) { + error = uiomove(ptr, len, &uio); + } + } + } else { + error = EIO; + } + +ret: + if (ptr) + free(ptr, M_TEMP); + return error; +} + +static int +urio_detach(device_t self) +{ + struct urio_softc *sc = device_get_softc(self); + int s; + + DPRINTF(("urio_detach: sc=%p\n", sc)); + sc->sc_dying = 1; + if (sc->sc_pipeh_in) + usbd_abort_pipe(sc->sc_pipeh_in); + + if (sc->sc_pipeh_out) + usbd_abort_pipe(sc->sc_pipeh_out); + + s = splusb(); + if (--sc->sc_refcnt >= 0) { + /* Wait for processes to go away. */ + usb_detach_wait(sc->sc_dev); + } + splx(s); + + destroy_dev(sc->sc_dev_t); + /* XXX not implemented yet */ + device_set_desc(self, NULL); + return 0; +} + +MODULE_DEPEND(uscanner, usb, 1, 1, 1); +DRIVER_MODULE(urio, uhub, urio_driver, urio_devclass, usbd_driver_load, 0); diff --git a/sys/legacy/dev/usb/usb.c b/sys/legacy/dev/usb/usb.c new file mode 100644 index 0000000..b1b7d44 --- /dev/null +++ b/sys/legacy/dev/usb/usb.c @@ -0,0 +1,939 @@ +/* $NetBSD: usb.c,v 1.68 2002/02/20 20:30:12 christos Exp $ */ + +/* Also already merged from NetBSD: + * $NetBSD: usb.c,v 1.70 2002/05/09 21:54:32 augustss Exp $ + * $NetBSD: usb.c,v 1.71 2002/06/01 23:51:04 lukem Exp $ + * $NetBSD: usb.c,v 1.73 2002/09/23 05:51:19 simonb Exp $ + * $NetBSD: usb.c,v 1.80 2003/11/07 17:03:25 wiz Exp $ + */ + +#include <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. + */ + +/* + * USB specifications and other documentation can be found at + * http://www.usb.org/developers/docs/ and + * http://www.usb.org/developers/devclass_docs/ + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/lock.h> +#include <sys/malloc.h> +#include <sys/mutex.h> +#include <sys/unistd.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/fcntl.h> +#include <sys/filio.h> +#include <sys/uio.h> +#include <sys/kthread.h> +#include <sys/proc.h> +#include <sys/conf.h> +#include <sys/poll.h> +#include <sys/selinfo.h> +#include <sys/signalvar.h> +#include <sys/sysctl.h> +#include <sys/uio.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> + +#define USBUNIT(d) (dev2unit(d)) /* usb_discover device nodes, kthread */ +#define USB_DEV_MINOR 255 /* event queue device */ + +MALLOC_DEFINE(M_USB, "USB", "USB"); +MALLOC_DEFINE(M_USBDEV, "USBdev", "USB device"); +MALLOC_DEFINE(M_USBHC, "USBHC", "USB host controller"); + +#include "usb_if.h" + +#include <machine/bus.h> + +#include <dev/usb/usbdivar.h> +#include <dev/usb/usb_quirks.h> + +/* Define this unconditionally in case a kernel module is loaded that + * has been compiled with debugging options. + */ +SYSCTL_NODE(_hw, OID_AUTO, usb, CTLFLAG_RW, 0, "USB debugging"); + +#ifdef USB_DEBUG +#define DPRINTF(x) if (usbdebug) printf x +#define DPRINTFN(n,x) if (usbdebug>(n)) printf x +int usbdebug = 0; +SYSCTL_INT(_hw_usb, OID_AUTO, debug, CTLFLAG_RW, + &usbdebug, 0, "usb debug level"); +/* + * 0 - do usual exploration + * 1 - do not use timeout exploration + * >1 - do no exploration + */ +int usb_noexplore = 0; +#else +#define DPRINTF(x) +#define DPRINTFN(n,x) +#endif + +struct usb_softc { + device_t sc_dev; /* base device */ + struct cdev *sc_usbdev; /* /dev/usbN device */ + TAILQ_ENTRY(usb_softc) sc_coldexplist; /* cold needs-explore list */ + usbd_bus_handle sc_bus; /* USB controller */ + struct usbd_port sc_port; /* dummy port for root hub */ + + struct proc *sc_event_thread; + + char sc_dying; +}; + +struct usb_taskq { + TAILQ_HEAD(, usb_task) tasks; + struct proc *task_thread_proc; + const char *name; + int taskcreated; /* task thread exists. */ +}; + +static struct usb_taskq usb_taskq[USB_NUM_TASKQS]; + +d_open_t usbopen; +d_close_t usbclose; +d_read_t usbread; +d_ioctl_t usbioctl; +d_poll_t usbpoll; + +struct cdevsw usb_cdevsw = { + .d_version = D_VERSION, + .d_flags = D_NEEDGIANT, + .d_open = usbopen, + .d_close = usbclose, + .d_read = usbread, + .d_ioctl = usbioctl, + .d_poll = usbpoll, + .d_name = "usb", +}; + +static void usb_discover(void *); +static void usb_create_event_thread(void *); +static void usb_event_thread(void *); +static void usb_task_thread(void *); + +static struct cdev *usb_dev; /* The /dev/usb device. */ +static int usb_ndevs; /* Number of /dev/usbN devices. */ +/* Busses to explore at the end of boot-time device configuration. */ +static TAILQ_HEAD(, usb_softc) usb_coldexplist = + TAILQ_HEAD_INITIALIZER(usb_coldexplist); + +#define USB_MAX_EVENTS 100 +struct usb_event_q { + struct usb_event ue; + TAILQ_ENTRY(usb_event_q) next; +}; +static TAILQ_HEAD(, usb_event_q) usb_events = + TAILQ_HEAD_INITIALIZER(usb_events); +static int usb_nevents = 0; +static struct selinfo usb_selevent; +static struct proc *usb_async_proc; /* process that wants USB SIGIO */ +static int usb_dev_open = 0; +static void usb_add_event(int, struct usb_event *); + +static int usb_get_next_event(struct usb_event *); + +static const char *usbrev_str[] = USBREV_STR; + +static device_probe_t usb_match; +static device_attach_t usb_attach; +static device_detach_t usb_detach; +static bus_child_detached_t usb_child_detached; + +static device_method_t usb_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, usb_match), + 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), + + /* Bus interface */ + DEVMETHOD(bus_child_detached, usb_child_detached), + + { 0, 0 } +}; + +static driver_t usb_driver = { + "usb", + usb_methods, + sizeof(struct usb_softc) +}; + +static devclass_t usb_devclass; + +DRIVER_MODULE(usb, ohci, usb_driver, usb_devclass, 0, 0); +DRIVER_MODULE(usb, uhci, usb_driver, usb_devclass, 0, 0); +DRIVER_MODULE(usb, ehci, usb_driver, usb_devclass, 0, 0); +DRIVER_MODULE(usb, slhci, usb_driver, usb_devclass, 0, 0); +MODULE_VERSION(usb, 1); + +static int +usb_match(device_t self) +{ + DPRINTF(("usbd_match\n")); + return (UMATCH_GENERIC); +} + +static int +usb_attach(device_t self) +{ + struct usb_softc *sc = device_get_softc(self); + void *aux = device_get_ivars(self); + usbd_device_handle dev; + usbd_status err; + int usbrev; + int speed; + struct usb_event ue; + + sc->sc_dev = self; + + DPRINTF(("usbd_attach\n")); + + usbd_init(); + sc->sc_bus = aux; + sc->sc_bus->usbctl = sc; + sc->sc_port.power = USB_MAX_POWER; + + printf("%s", device_get_nameunit(sc->sc_dev)); + usbrev = sc->sc_bus->usbrev; + printf(": USB revision %s", usbrev_str[usbrev]); + switch (usbrev) { + case USBREV_1_0: + case USBREV_1_1: + speed = USB_SPEED_FULL; + break; + case USBREV_2_0: + speed = USB_SPEED_HIGH; + break; + default: + printf(", not supported\n"); + sc->sc_dying = 1; + return ENXIO; + } + printf("\n"); + + /* Make sure not to use tsleep() if we are cold booting. */ + if (cold) + sc->sc_bus->use_polling++; + + ue.u.ue_ctrlr.ue_bus = device_get_unit(sc->sc_dev); + usb_add_event(USB_EVENT_CTRLR_ATTACH, &ue); + +#ifdef USB_USE_SOFTINTR +#ifdef __HAVE_GENERIC_SOFT_INTERRUPTS + /* XXX we should have our own level */ + sc->sc_bus->soft = softintr_establish(IPL_SOFTNET, + sc->sc_bus->methods->soft_intr, sc->sc_bus); + if (sc->sc_bus->soft == NULL) { + printf("%s: can't register softintr\n", device_get_nameunit(sc->sc_dev)); + sc->sc_dying = 1; + return ENXIO; + } +#else + usb_callout_init(sc->sc_bus->softi); +#endif +#endif + + err = usbd_new_device(sc->sc_dev, sc->sc_bus, 0, speed, 0, + &sc->sc_port); + if (!err) { + dev = sc->sc_port.device; + if (dev->hub == NULL) { + sc->sc_dying = 1; + printf("%s: root device is not a hub\n", + device_get_nameunit(sc->sc_dev)); + return ENXIO; + } + sc->sc_bus->root_hub = dev; +#if 1 + /* + * Turning this code off will delay attachment of USB devices + * until the USB event thread is running, which means that + * the keyboard will not work until after cold boot. + */ + if (cold) { + /* Explore high-speed busses before others. */ + if (speed == USB_SPEED_HIGH) + dev->hub->explore(sc->sc_bus->root_hub); + else + TAILQ_INSERT_TAIL(&usb_coldexplist, sc, + sc_coldexplist); + } +#endif + } else { + printf("%s: root hub problem, error=%d\n", + device_get_nameunit(sc->sc_dev), err); + sc->sc_dying = 1; + } + if (cold) + sc->sc_bus->use_polling--; + + /* XXX really do right config_pending_incr(); */ + usb_create_event_thread(sc); + /* The per controller devices (used for usb_discover) */ + /* XXX This is redundant now, but old usbd's will want it */ + sc->sc_usbdev = make_dev(&usb_cdevsw, device_get_unit(self), UID_ROOT, + GID_OPERATOR, 0660, "usb%d", device_get_unit(self)); + if (usb_ndevs++ == 0) { + /* The device spitting out events */ + usb_dev = make_dev(&usb_cdevsw, USB_DEV_MINOR, UID_ROOT, + GID_OPERATOR, 0660, "usb"); + } + return 0; +} + +static const char *taskq_names[] = USB_TASKQ_NAMES; + +void +usb_create_event_thread(void *arg) +{ + struct usb_softc *sc = arg; + struct usb_taskq *taskq; + int i; + + if (kproc_create(usb_event_thread, sc, &sc->sc_event_thread, + RFHIGHPID, 0, device_get_nameunit(sc->sc_dev))) { + printf("%s: unable to create event thread for\n", + device_get_nameunit(sc->sc_dev)); + panic("usb_create_event_thread"); + } + for (i = 0; i < USB_NUM_TASKQS; i++) { + taskq = &usb_taskq[i]; + + if (taskq->taskcreated == 0) { + taskq->taskcreated = 1; + taskq->name = taskq_names[i]; + TAILQ_INIT(&taskq->tasks); + if (kproc_create(usb_task_thread, taskq, + &taskq->task_thread_proc, RFHIGHPID, 0, + taskq->name)) { + printf("unable to create task thread\n"); + panic("usb_create_event_thread task"); + } + } + } +} + +/* + * Add a task to be performed by the task thread. This function can be + * called from any context and the task will be executed in a process + * context ASAP. + */ +void +usb_add_task(usbd_device_handle dev, struct usb_task *task, int queue) +{ + struct usb_taskq *taskq; + int s; + + s = splusb(); + taskq = &usb_taskq[queue]; + if (task->queue == -1) { + DPRINTFN(2,("usb_add_task: task=%p\n", task)); + TAILQ_INSERT_TAIL(&taskq->tasks, task, next); + task->queue = queue; + } else { + DPRINTFN(3,("usb_add_task: task=%p on q\n", task)); + } + wakeup(&taskq->tasks); + splx(s); +} + +void +usb_rem_task(usbd_device_handle dev, struct usb_task *task) +{ + struct usb_taskq *taskq; + int s; + + s = splusb(); + if (task->queue != -1) { + taskq = &usb_taskq[task->queue]; + TAILQ_REMOVE(&taskq->tasks, task, next); + task->queue = -1; + } + splx(s); +} + +void +usb_event_thread(void *arg) +{ + static int newthread_wchan; + struct usb_softc *sc = arg; + + mtx_lock(&Giant); + + DPRINTF(("usb_event_thread: start\n")); + + /* + * In case this controller is a companion controller to an + * EHCI controller we need to wait until the EHCI controller + * has grabbed the port. What we do here is wait until no new + * USB threads have been created in a while. XXX we actually + * just want to wait for the PCI slot to be fully scanned. + * + * Note that when you `kldload usb' it actually attaches the + * devices in order that the drivers appear in the kld, not the + * normal PCI order, since the addition of each driver within + * usb.ko (ohci, ehci etc.) causes a separate PCI bus re-scan. + */ + wakeup(&newthread_wchan); + for (;;) { + if (tsleep(&newthread_wchan , PWAIT, "usbets", hz * 4) != 0) + break; + } + + /* Make sure first discover does something. */ + sc->sc_bus->needs_explore = 1; + usb_discover(sc); + /* XXX really do right config_pending_decr(); */ + + while (!sc->sc_dying) { +#ifdef USB_DEBUG + if (usb_noexplore < 2) +#endif + usb_discover(sc); +#ifdef USB_DEBUG + (void)tsleep(&sc->sc_bus->needs_explore, PWAIT, "usbevt", + usb_noexplore ? 0 : hz * 60); +#else + (void)tsleep(&sc->sc_bus->needs_explore, PWAIT, "usbevt", + hz * 60); +#endif + DPRINTFN(2,("usb_event_thread: woke up\n")); + } + sc->sc_event_thread = NULL; + + /* In case parent is waiting for us to exit. */ + wakeup(sc); + + DPRINTF(("usb_event_thread: exit\n")); + while (mtx_owned(&Giant)) + mtx_unlock(&Giant); + kproc_exit(0); +} + +void +usb_task_thread(void *arg) +{ + struct usb_task *task; + struct usb_taskq *taskq; + int s; + + mtx_lock(&Giant); + + taskq = arg; + DPRINTF(("usb_task_thread: start taskq %s\n", taskq->name)); + + s = splusb(); + while (usb_ndevs > 0) { + task = TAILQ_FIRST(&taskq->tasks); + if (task == NULL) { + tsleep(&taskq->tasks, PWAIT, "usbtsk", 0); + task = TAILQ_FIRST(&taskq->tasks); + } + DPRINTFN(2,("usb_task_thread: woke up task=%p\n", task)); + if (task != NULL) { + TAILQ_REMOVE(&taskq->tasks, task, next); + task->queue = -1; + splx(s); + task->fun(task->arg); + s = splusb(); + } + } + splx(s); + + taskq->taskcreated = 0; + wakeup(&taskq->taskcreated); + + DPRINTF(("usb_event_thread: exit\n")); + while (mtx_owned(&Giant)) + mtx_unlock(&Giant); + kproc_exit(0); +} + +int +usbopen(struct cdev *dev, int flag, int mode, struct thread *p) +{ + int unit = USBUNIT(dev); + struct usb_softc *sc; + + if (unit == USB_DEV_MINOR) { + if (usb_dev_open) + return (EBUSY); + usb_dev_open = 1; + usb_async_proc = 0; + return (0); + } + sc = devclass_get_softc(usb_devclass, unit); + if (sc == NULL) + return (ENXIO); + if (sc->sc_dying) + return (EIO); + + return (0); +} + +int +usbread(struct cdev *dev, struct uio *uio, int flag) +{ + struct usb_event ue; + int unit = USBUNIT(dev); + int s, error, n; + + if (unit != USB_DEV_MINOR) + return (ENODEV); + + if (uio->uio_resid != sizeof(struct usb_event)) + return (EINVAL); + + error = 0; + s = splusb(); + for (;;) { + n = usb_get_next_event(&ue); + if (n != 0) + break; + if (flag & O_NONBLOCK) { + error = EWOULDBLOCK; + break; + } + error = tsleep(&usb_events, PZERO | PCATCH, "usbrea", 0); + if (error) + break; + } + splx(s); + if (!error) + error = uiomove((void *)&ue, uio->uio_resid, uio); + + return (error); +} + +int +usbclose(struct cdev *dev, int flag, int mode, struct thread *p) +{ + int unit = USBUNIT(dev); + + if (unit == USB_DEV_MINOR) { + usb_async_proc = 0; + usb_dev_open = 0; + } + + return (0); +} + +int +usbioctl(struct cdev *devt, u_long cmd, caddr_t data, int flag, struct thread *p) +{ + struct usb_softc *sc; + int unit = USBUNIT(devt); + + if (unit == USB_DEV_MINOR) { + switch (cmd) { + case FIONBIO: + /* All handled in the upper FS layer. */ + return (0); + + case FIOASYNC: + if (*(int *)data) + usb_async_proc = p->td_proc; + else + usb_async_proc = 0; + return (0); + + default: + return (EINVAL); + } + } + sc = devclass_get_softc(usb_devclass, unit); + if (sc->sc_dying) + return (EIO); + + switch (cmd) { + /* This part should be deleted */ + case USB_DISCOVER: + break; + case USB_REQUEST: + { + struct usb_ctl_request *ur = (void *)data; + int len = UGETW(ur->ucr_request.wLength); + struct iovec iov; + struct uio uio; + void *ptr = 0; + int addr = ur->ucr_addr; + usbd_status err; + int error = 0; + + DPRINTF(("usbioctl: USB_REQUEST addr=%d len=%d\n", addr, len)); + if (len < 0 || len > 32768) + return (EINVAL); + if (addr < 0 || addr >= USB_MAX_DEVICES || + sc->sc_bus->devices[addr] == 0) + return (EINVAL); + if (len != 0) { + iov.iov_base = (caddr_t)ur->ucr_data; + iov.iov_len = len; + uio.uio_iov = &iov; + uio.uio_iovcnt = 1; + uio.uio_resid = len; + uio.uio_offset = 0; + uio.uio_segflg = UIO_USERSPACE; + uio.uio_rw = + ur->ucr_request.bmRequestType & UT_READ ? + UIO_READ : UIO_WRITE; + uio.uio_td = p; + ptr = malloc(len, M_TEMP, M_WAITOK); + if (uio.uio_rw == UIO_WRITE) { + error = uiomove(ptr, len, &uio); + if (error) + goto ret; + } + } + err = usbd_do_request_flags(sc->sc_bus->devices[addr], + &ur->ucr_request, ptr, ur->ucr_flags, &ur->ucr_actlen, + USBD_DEFAULT_TIMEOUT); + if (err) { + error = EIO; + goto ret; + } + if (len != 0) { + if (uio.uio_rw == UIO_READ) { + error = uiomove(ptr, len, &uio); + if (error) + goto ret; + } + } + ret: + if (ptr) + free(ptr, M_TEMP); + return (error); + } + + case USB_DEVICEINFO: + { + struct usb_device_info *di = (void *)data; + int addr = di->udi_addr; + usbd_device_handle dev; + + if (addr < 1 || addr >= USB_MAX_DEVICES) + return (EINVAL); + dev = sc->sc_bus->devices[addr]; + if (dev == NULL) + return (ENXIO); + usbd_fill_deviceinfo(dev, di, 1); + break; + } + + case USB_DEVICESTATS: + *(struct usb_device_stats *)data = sc->sc_bus->stats; + break; + + default: + return (EINVAL); + } + return (0); +} + +int +usbpoll(struct cdev *dev, int events, struct thread *p) +{ + int revents, mask, s; + int unit = USBUNIT(dev); + + if (unit == USB_DEV_MINOR) { + revents = 0; + mask = POLLIN | POLLRDNORM; + + s = splusb(); + if (events & mask && usb_nevents > 0) + revents |= events & mask; + if (revents == 0 && events & mask) + selrecord(p, &usb_selevent); + splx(s); + + return (revents); + } else { + return (0); /* select/poll never wakes up - back compat */ + } +} + +/* Explore device tree from the root. */ +static void +usb_discover(void *v) +{ + struct usb_softc *sc = v; + + /* splxxx should be changed to mutexes for preemption safety some day */ + int s; + + DPRINTFN(2,("usb_discover\n")); +#ifdef USB_DEBUG + if (usb_noexplore > 1) + return; +#endif + + /* + * We need mutual exclusion while traversing the device tree, + * but this is guaranteed since this function is only called + * from the event thread for the controller. + */ + s = splusb(); + while (sc->sc_bus->needs_explore && !sc->sc_dying) { + sc->sc_bus->needs_explore = 0; + splx(s); + sc->sc_bus->root_hub->hub->explore(sc->sc_bus->root_hub); + s = splusb(); + } + splx(s); +} + +void +usb_needs_explore(usbd_device_handle dev) +{ + DPRINTFN(2,("usb_needs_explore\n")); + dev->bus->needs_explore = 1; + wakeup(&dev->bus->needs_explore); +} + +/* Called at splusb() */ +int +usb_get_next_event(struct usb_event *ue) +{ + struct usb_event_q *ueq; + + if (usb_nevents <= 0) + return (0); + ueq = TAILQ_FIRST(&usb_events); +#ifdef DIAGNOSTIC + if (ueq == NULL) { + printf("usb: usb_nevents got out of sync! %d\n", usb_nevents); + usb_nevents = 0; + return (0); + } +#endif + *ue = ueq->ue; + TAILQ_REMOVE(&usb_events, ueq, next); + free(ueq, M_USBDEV); + usb_nevents--; + return (1); +} + +void +usbd_add_dev_event(int type, usbd_device_handle udev) +{ + struct usb_event ue; + + usbd_fill_deviceinfo(udev, &ue.u.ue_device, USB_EVENT_IS_ATTACH(type)); + usb_add_event(type, &ue); +} + +void +usbd_add_drv_event(int type, usbd_device_handle udev, device_t dev) +{ + struct usb_event ue; + + ue.u.ue_driver.ue_cookie = udev->cookie; + strncpy(ue.u.ue_driver.ue_devname, device_get_nameunit(dev), + sizeof ue.u.ue_driver.ue_devname); + usb_add_event(type, &ue); +} + +void +usb_add_event(int type, struct usb_event *uep) +{ + struct usb_event_q *ueq; + struct usb_event ue; + struct timeval thetime; + int s; + + ueq = malloc(sizeof *ueq, M_USBDEV, M_WAITOK); + ueq->ue = *uep; + ueq->ue.ue_type = type; + microtime(&thetime); + TIMEVAL_TO_TIMESPEC(&thetime, &ueq->ue.ue_time); + + s = splusb(); + if (USB_EVENT_IS_DETACH(type)) { + struct usb_event_q *ueqi, *ueqi_next; + + for (ueqi = TAILQ_FIRST(&usb_events); ueqi; ueqi = ueqi_next) { + ueqi_next = TAILQ_NEXT(ueqi, next); + if (ueqi->ue.u.ue_driver.ue_cookie.cookie == + uep->u.ue_device.udi_cookie.cookie) { + TAILQ_REMOVE(&usb_events, ueqi, next); + free(ueqi, M_USBDEV); + usb_nevents--; + ueqi_next = TAILQ_FIRST(&usb_events); + } + } + } + if (usb_nevents >= USB_MAX_EVENTS) { + /* Too many queued events, drop an old one. */ + DPRINTF(("usb: event dropped\n")); + (void)usb_get_next_event(&ue); + } + TAILQ_INSERT_TAIL(&usb_events, ueq, next); + usb_nevents++; + wakeup(&usb_events); + selwakeuppri(&usb_selevent, PZERO); + if (usb_async_proc != NULL) { + PROC_LOCK(usb_async_proc); + psignal(usb_async_proc, SIGIO); + PROC_UNLOCK(usb_async_proc); + } + splx(s); +} + +void +usb_schedsoftintr(usbd_bus_handle bus) +{ + DPRINTFN(10,("usb_schedsoftintr: polling=%d\n", bus->use_polling)); +#ifdef USB_USE_SOFTINTR + if (bus->use_polling) { + bus->methods->soft_intr(bus); + } else { +#ifdef __HAVE_GENERIC_SOFT_INTERRUPTS + softintr_schedule(bus->soft); +#else + if (!callout_pending(&bus->softi)) + callout_reset(&bus->softi, 0, bus->methods->soft_intr, + bus); +#endif /* __HAVE_GENERIC_SOFT_INTERRUPTS */ + } +#else + bus->methods->soft_intr(bus); +#endif /* USB_USE_SOFTINTR */ +} + +static int +usb_detach(device_t self) +{ + struct usb_softc *sc = device_get_softc(self); + struct usb_event ue; + struct usb_taskq *taskq; + int i; + + DPRINTF(("usb_detach: start\n")); + + sc->sc_dying = 1; + + /* Make all devices disconnect. */ + if (sc->sc_port.device != NULL) + usb_disconnect_port(&sc->sc_port, self); + + /* Kill off event thread. */ + if (sc->sc_event_thread != NULL) { + wakeup(&sc->sc_bus->needs_explore); + if (tsleep(sc, PWAIT, "usbdet", hz * 60)) + printf("%s: event thread didn't die\n", + device_get_nameunit(sc->sc_dev)); + DPRINTF(("usb_detach: event thread dead\n")); + } + + destroy_dev(sc->sc_usbdev); + if (--usb_ndevs == 0) { + destroy_dev(usb_dev); + usb_dev = NULL; + for (i = 0; i < USB_NUM_TASKQS; i++) { + taskq = &usb_taskq[i]; + wakeup(&taskq->tasks); + if (tsleep(&taskq->taskcreated, PWAIT, "usbtdt", + hz * 60)) { + printf("usb task thread %s didn't die\n", + taskq->name); + } + } + } + + usbd_finish(); + +#ifdef USB_USE_SOFTINTR +#ifdef __HAVE_GENERIC_SOFT_INTERRUPTS + if (sc->sc_bus->soft != NULL) { + softintr_disestablish(sc->sc_bus->soft); + sc->sc_bus->soft = NULL; + } +#else + callout_stop(&sc->sc_bus->softi); +#endif +#endif + + ue.u.ue_ctrlr.ue_bus = device_get_unit(sc->sc_dev); + usb_add_event(USB_EVENT_CTRLR_DETACH, &ue); + + return (0); +} + +static void +usb_child_detached(device_t self, device_t child) +{ + struct usb_softc *sc = device_get_softc(self); + + /* XXX, should check it is the right device. */ + sc->sc_port.device = NULL; +} + +/* Explore USB busses at the end of device configuration. */ +static void +usb_cold_explore(void *arg) +{ + struct usb_softc *sc; + + KASSERT(cold || TAILQ_EMPTY(&usb_coldexplist), + ("usb_cold_explore: busses to explore when !cold")); + while (!TAILQ_EMPTY(&usb_coldexplist)) { + sc = TAILQ_FIRST(&usb_coldexplist); + TAILQ_REMOVE(&usb_coldexplist, sc, sc_coldexplist); + + sc->sc_bus->use_polling++; + sc->sc_port.device->hub->explore(sc->sc_bus->root_hub); + sc->sc_bus->use_polling--; + } +} + +SYSINIT(usb_cold_explore, SI_SUB_CONFIGURE, SI_ORDER_MIDDLE, + usb_cold_explore, NULL); diff --git a/sys/legacy/dev/usb/usb.h b/sys/legacy/dev/usb/usb.h new file mode 100644 index 0000000..7e64cac --- /dev/null +++ b/sys/legacy/dev/usb/usb.h @@ -0,0 +1,708 @@ +/* $NetBSD: usb.h,v 1.69 2002/09/22 23:20:50 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_H_ +#define _USB_H_ + +#include <sys/types.h> +#include <sys/time.h> + +#if defined(_KERNEL) +#include "opt_usb.h" + +#ifdef SYSCTL_DECL +SYSCTL_DECL(_hw_usb); +#endif + +#include <sys/malloc.h> + +MALLOC_DECLARE(M_USB); +MALLOC_DECLARE(M_USBDEV); +MALLOC_DECLARE(M_USBHC); +#endif /* _KERNEL */ + +#define PWR_RESUME 0 +#define PWR_SUSPEND 1 +#define PWR_STANDBY 2 +#define PWR_SOFTSUSPEND 3 +#define PWR_SOFTSTANDBY 4 +#define PWR_SOFTRESUME 5 + +/* These two defines are used by usbd to autoload the usb kld */ +#define USB_KLD "usb" /* name of usb module */ +#define USB_UHUB "usb/uhub" /* root hub */ + +#define USB_STACK_VERSION 2 + +#define USB_MAX_DEVICES 128 +#define USB_START_ADDR 0 + +#define USB_CONTROL_ENDPOINT 0 +#define USB_MAX_ENDPOINTS 16 + +#define USB_FRAMES_PER_SECOND 1000 + +/* + * The USB records contain some unaligned little-endian word + * components. The U[SG]ETW macros take care of both the alignment + * and endian problem and should always be used to access non-byte + * values. + */ +typedef u_int8_t uByte; +typedef u_int8_t uWord[2]; +typedef u_int8_t uDWord[4]; + +#define USETW2(w,h,l) ((w)[0] = (u_int8_t)(l), (w)[1] = (u_int8_t)(h)) + +#if 1 +#define UGETW(w) ((w)[0] | ((w)[1] << 8)) +#define USETW(w,v) ((w)[0] = (u_int8_t)(v), (w)[1] = (u_int8_t)((v) >> 8)) +#define UGETDW(w) ((w)[0] | ((w)[1] << 8) | ((w)[2] << 16) | ((w)[3] << 24)) +#define USETDW(w,v) ((w)[0] = (u_int8_t)(v), \ + (w)[1] = (u_int8_t)((v) >> 8), \ + (w)[2] = (u_int8_t)((v) >> 16), \ + (w)[3] = (u_int8_t)((v) >> 24)) +#else +/* + * On little-endian machines that can handle unanliged accesses + * (e.g. i386) these macros can be replaced by the following. + */ +#define UGETW(w) (*(u_int16_t *)(w)) +#define USETW(w,v) (*(u_int16_t *)(w) = (v)) +#define UGETDW(w) (*(u_int32_t *)(w)) +#define USETDW(w,v) (*(u_int32_t *)(w) = (v)) +#endif + +#define UPACKED __packed + +typedef struct { + uByte bmRequestType; + uByte bRequest; + uWord wValue; + uWord wIndex; + uWord wLength; +} UPACKED 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 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_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 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 + +/* Feature numbers */ +#define UF_ENDPOINT_HALT 0 +#define UF_DEVICE_REMOTE_WAKEUP 1 +#define UF_TEST_MODE 2 + +#define USB_MAX_IPACKET 8 /* maximum size of the initial packet */ + +#define USB_2_MAX_CTRL_PACKET 64 +#define USB_2_MAX_BULK_PACKET 512 + +typedef struct { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; +} UPACKED usb_descriptor_t; + +typedef struct { + uByte bLength; + uByte bDescriptorType; + uWord bcdUSB; +#define UD_USB_2_0 0x0200 +#define UD_IS_USB2(d) (UGETW((d)->bcdUSB) >= UD_USB_2_0) + 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; +} UPACKED usb_device_descriptor_t; +#define USB_DEVICE_DESCRIPTOR_SIZE 18 + +typedef struct { + uByte bLength; + uByte bDescriptorType; + uWord wTotalLength; + uByte bNumInterface; + uByte bConfigurationValue; + 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 +} UPACKED usb_config_descriptor_t; +#define USB_CONFIG_DESCRIPTOR_SIZE 9 + +typedef struct { + uByte bLength; + uByte bDescriptorType; + uByte bInterfaceNumber; + uByte bAlternateSetting; + uByte bNumEndpoints; + uByte bInterfaceClass; + uByte bInterfaceSubClass; + uByte bInterfaceProtocol; + uByte iInterface; +} UPACKED usb_interface_descriptor_t; +#define USB_INTERFACE_DESCRIPTOR_SIZE 9 + +typedef struct { + 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 +#define UE_DIR_OUT 0x00 +#define UE_ADDR 0x0f +#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_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_GET_TRANS(a) (((a) >> 11) & 0x3) +#define UE_GET_SIZE(a) ((a) & 0x7ff) + uByte bInterval; +} UPACKED usb_endpoint_descriptor_t; +#define USB_ENDPOINT_DESCRIPTOR_SIZE 7 + +typedef struct { + uByte bLength; + uByte bDescriptorType; + uWord bString[127]; +} UPACKED usb_string_descriptor_t; +#define USB_MAX_STRING_LEN 128 +#define USB_LANGUAGE_TABLE 0 /* # of the string language id table */ + +/* 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 + +/* Hub 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_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 + +typedef struct { + 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) + /* deprecated */ uByte PortPowerCtrlMask[1]; +} UPACKED usb_hub_descriptor_t; +#define USB_HUB_DESCRIPTOR_SIZE 9 /* includes deprecated PortPowerCtrlMask */ + +typedef struct { + uByte bLength; + uByte bDescriptorType; + uWord bcdUSB; + uByte bDeviceClass; + uByte bDeviceSubClass; + uByte bDeviceProtocol; + uByte bMaxPacketSize0; + uByte bNumConfigurations; + uByte bReserved; +} UPACKED usb_device_qualifier_t; +#define USB_DEVICE_QUALIFIER_SIZE 10 + +typedef struct { + uByte bLength; + uByte bDescriptorType; + uByte bmAttributes; +#define UOTG_SRP 0x01 +#define UOTG_HNP 0x02 +} UPACKED 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 + +typedef struct { + uWord wStatus; +/* Device status flags */ +#define UDS_SELF_POWERED 0x0001 +#define UDS_REMOTE_WAKEUP 0x0002 +/* Endpoint status flags */ +#define UES_HALT 0x0001 +} UPACKED usb_status_t; + +typedef struct { + uWord wHubStatus; +#define UHS_LOCAL_POWER 0x0001 +#define UHS_OVER_CURRENT 0x0002 + uWord wHubChange; +} UPACKED usb_hub_status_t; + +typedef struct { + 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 +#define UPS_PORT_POWER 0x0100 +#define UPS_LOW_SPEED 0x0200 +#define UPS_HIGH_SPEED 0x0400 +#define UPS_PORT_TEST 0x0800 +#define UPS_PORT_INDICATOR 0x1000 + 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 +} UPACKED usb_port_status_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 UDCLASS_DIAGNOSTIC 0xdc +#define UDCLASS_WIRELESS 0xe0 +#define UDSUBCLASS_RF 0x01 +#define UDPROTO_BLUETOOTH 0x01 +#define UDCLASS_VENDOR 0xff + +/* Interface class codes */ +#define UICLASS_UNSPEC 0x00 + +#define UICLASS_AUDIO 0x01 +#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 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 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 0 +#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 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_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 + + +#define USB_HUB_MAX_DEPTH 5 + +/* + * Minimum time a device needs to be powered down to go through + * a power cycle. XXX Are these time in the spec? + */ +#define USB_POWER_DOWN_TIME 200 /* ms */ +#define USB_PORT_POWER_DOWN_TIME 100 /* ms */ + +#if 0 +/* These are the values from the spec. */ +#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_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 (i.e. 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_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 XXX?*/ + + +#define USB_UNCONFIG_NO 0 +#define USB_UNCONFIG_INDEX (-1) + +/*** ioctl() related stuff ***/ + +struct usb_ctl_request { + int ucr_addr; + usb_device_request_t ucr_request; + void *ucr_data; + int ucr_flags; +#define USBD_SHORT_XFER_OK 0x04 /* allow short reads */ + int ucr_actlen; /* actual length transferred */ +}; + +struct usb_alt_interface { + int uai_config_index; + int uai_interface_index; + int uai_alt_no; +}; + +#define USB_CURRENT_CONFIG_INDEX (-1) +#define USB_CURRENT_ALT_INDEX (-1) + +struct usb_config_desc { + int ucd_config_index; + usb_config_descriptor_t ucd_desc; +}; + +struct usb_interface_desc { + int uid_config_index; + int uid_interface_index; + int uid_alt_index; + usb_interface_descriptor_t uid_desc; +}; + +struct usb_endpoint_desc { + int ued_config_index; + int ued_interface_index; + int ued_alt_index; + int ued_endpoint_index; + usb_endpoint_descriptor_t ued_desc; +}; + +struct usb_full_desc { + int ufd_config_index; + u_int ufd_size; + u_char *ufd_data; +}; + +struct usb_string_desc { + int usd_string_index; + int usd_language_id; + usb_string_descriptor_t usd_desc; +}; + +struct usb_ctl_report_desc { + int ucrd_size; + u_char ucrd_data[1024]; /* filled data size will vary */ +}; + +typedef struct { u_int32_t cookie; } usb_event_cookie_t; + +#define USB_MAX_DEVNAMES 4 +#define USB_MAX_DEVNAMELEN 16 +struct usb_device_info { + u_int8_t udi_bus; + u_int8_t udi_addr; /* device address */ + usb_event_cookie_t udi_cookie; + char udi_product[USB_MAX_STRING_LEN]; + char udi_vendor[USB_MAX_STRING_LEN]; + char udi_release[8]; + u_int16_t udi_productNo; + u_int16_t udi_vendorNo; + u_int16_t udi_releaseNo; + u_int8_t udi_class; + u_int8_t udi_subclass; + u_int8_t udi_protocol; + u_int8_t udi_config; + u_int8_t udi_speed; +#define USB_SPEED_LOW 1 +#define USB_SPEED_FULL 2 +#define USB_SPEED_HIGH 3 + int udi_power; /* power consumption in mA, 0 if selfpowered */ + int udi_nports; + char udi_devnames[USB_MAX_DEVNAMES][USB_MAX_DEVNAMELEN]; + u_int8_t udi_ports[16];/* hub only: addresses of devices on ports */ +#define USB_PORT_ENABLED 0xff +#define USB_PORT_SUSPENDED 0xfe +#define USB_PORT_POWERED 0xfd +#define USB_PORT_DISABLED 0xfc +}; + +struct usb_ctl_report { + int ucr_report; + u_char ucr_data[1024]; /* filled data size will vary */ +}; + +struct usb_device_stats { + u_long uds_requests[4]; /* indexed by transfer type UE_* */ +}; + +/* Events that can be read from /dev/usb */ +struct usb_event { + int ue_type; +#define USB_EVENT_CTRLR_ATTACH 1 +#define USB_EVENT_CTRLR_DETACH 2 +#define USB_EVENT_DEVICE_ATTACH 3 +#define USB_EVENT_DEVICE_DETACH 4 +#define USB_EVENT_DRIVER_ATTACH 5 +#define USB_EVENT_DRIVER_DETACH 6 +#define USB_EVENT_IS_ATTACH(n) ((n) == USB_EVENT_CTRLR_ATTACH || (n) == USB_EVENT_DEVICE_ATTACH || (n) == USB_EVENT_DRIVER_ATTACH) +#define USB_EVENT_IS_DETACH(n) ((n) == USB_EVENT_CTRLR_DETACH || (n) == USB_EVENT_DEVICE_DETACH || (n) == USB_EVENT_DRIVER_DETACH) + struct timespec ue_time; + union { + struct { + int ue_bus; + } ue_ctrlr; + struct usb_device_info ue_device; + struct { + usb_event_cookie_t ue_cookie; + char ue_devname[16]; + } ue_driver; + } u; +}; + +/* 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) + +/* Generic HID device */ +#define USB_GET_REPORT_DESC _IOR ('U', 21, struct usb_ctl_report_desc) +#define USB_SET_IMMED _IOW ('U', 22, int) +#define USB_GET_REPORT _IOWR('U', 23, struct usb_ctl_report) +#define USB_SET_REPORT _IOW ('U', 24, struct usb_ctl_report) +#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_NO_ALT _IOWR('U', 104, struct usb_alt_interface) +#define USB_GET_DEVICE_DESC _IOR ('U', 105, usb_device_descriptor_t) +#define USB_GET_CONFIG_DESC _IOWR('U', 106, struct usb_config_desc) +#define USB_GET_INTERFACE_DESC _IOWR('U', 107, struct usb_interface_desc) +#define USB_GET_ENDPOINT_DESC _IOWR('U', 108, struct usb_endpoint_desc) +#define USB_GET_FULL_DESC _IOWR('U', 109, struct usb_full_desc) +#define USB_GET_STRING_DESC _IOWR('U', 110, struct usb_string_desc) +#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_SHORT_XFER _IOW ('U', 113, int) +#define USB_SET_TIMEOUT _IOW ('U', 114, int) +#define USB_RESET_DEVICE _IO ('U', 115) + +/* Modem device */ +#define USB_GET_CM_OVER_DATA _IOR ('U', 130, int) +#define USB_SET_CM_OVER_DATA _IOW ('U', 131, int) + +#endif /* _USB_H_ */ diff --git a/sys/legacy/dev/usb/usb_ethersubr.c b/sys/legacy/dev/usb/usb_ethersubr.c new file mode 100644 index 0000000..d0a3b83 --- /dev/null +++ b/sys/legacy/dev/usb/usb_ethersubr.c @@ -0,0 +1,283 @@ +/*- + * Copyright (c) 1997, 1998, 1999, 2000 + * Bill Paul <wpaul@ee.columbia.edu>. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * Callbacks in the USB code operate at splusb() (actually splbio() + * in FreeBSD). However adding packets to the input queues has to be + * done at splimp(). It is conceivable that this arrangement could + * trigger a condition where the splimp() is ignored and the input + * queues could get trampled in spite of our best effors to prevent + * it. To work around this, we implement a special input queue for + * USB ethernet adapter drivers. Rather than passing the frames directly + * to ether_input(), we pass them here, then schedule a soft interrupt + * to hand them to ether_input() later, outside of the USB interrupt + * context. + * + * It's questional as to whether this code should be expanded to + * handle other kinds of devices, or handle USB transfer callbacks + * in general. Right now, I need USB network interfaces to work + * properly. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/sockio.h> +#include <sys/mbuf.h> +#include <sys/malloc.h> +#include <sys/socket.h> +#include <sys/taskqueue.h> + +#include <net/if.h> +#include <net/if_types.h> +#include <net/if_arp.h> +#include <net/ethernet.h> +#include <net/netisr.h> +#include <net/bpf.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usb_ethersubr.h> + +static struct ifqueue usbq_rx; +static struct ifqueue usbq_tx; +static int mtx_inited = 0; + +static void usbintr (void); + +static void +usbintr(void) +{ + struct mbuf *m; + struct usb_qdat *q; + struct ifnet *ifp; + + mtx_lock(&Giant); + + /* Check the RX queue */ + while(1) { + IF_DEQUEUE(&usbq_rx, m); + if (m == NULL) + break; + q = (struct usb_qdat *)m->m_pkthdr.rcvif; + ifp = q->ifp; + m->m_pkthdr.rcvif = ifp; + (*ifp->if_input)(ifp, m); + + /* Re-arm the receiver */ + (*q->if_rxstart)(ifp); + if (ifp->if_snd.ifq_head != NULL) + (*ifp->if_start)(ifp); + } + + /* Check the TX queue */ + while(1) { + IF_DEQUEUE(&usbq_tx, m); + if (m == NULL) + break; + ifp = m->m_pkthdr.rcvif; + m_freem(m); + if (ifp->if_snd.ifq_head != NULL) + (*ifp->if_start)(ifp); + } + + mtx_unlock(&Giant); + + return; +} + +void +usb_register_netisr(void) +{ + if (mtx_inited) + return; + netisr_register(NETISR_USB, (netisr_t *)usbintr, NULL, + NETISR_FORCEQUEUE); + mtx_init(&usbq_tx.ifq_mtx, "usbq_tx_mtx", NULL, MTX_DEF); + mtx_init(&usbq_rx.ifq_mtx, "usbq_rx_mtx", NULL, MTX_DEF); + mtx_inited++; + return; +} + +/* + * Must be called at splusb() (actually splbio()). This should be + * the case when called from a transfer callback routine. + */ +void +usb_ether_input(m) + struct mbuf *m; +{ + IF_ENQUEUE(&usbq_rx, m); + schednetisr(NETISR_USB); + + return; +} + +void +usb_tx_done(m) + struct mbuf *m; +{ + IF_ENQUEUE(&usbq_tx, m); + schednetisr(NETISR_USB); + + return; +} + +struct mbuf * +usb_ether_newbuf(void) +{ + struct mbuf *m_new; + + m_new = m_getcl(M_DONTWAIT, MT_DATA, M_PKTHDR); + if (m_new == NULL) + return (NULL); + m_new->m_len = m_new->m_pkthdr.len = MCLBYTES; + + m_adj(m_new, ETHER_ALIGN); + return (m_new); +} + +int +usb_ether_rx_list_init(void *sc, struct ue_cdata *cd, + usbd_device_handle ue_udev) +{ + struct ue_chain *c; + int i; + + for (i = 0; i < UE_RX_LIST_CNT; i++) { + c = &cd->ue_rx_chain[i]; + c->ue_sc = sc; + c->ue_idx = i; + c->ue_mbuf = usb_ether_newbuf(); + if (c->ue_mbuf == NULL) + return (ENOBUFS); + if (c->ue_xfer == NULL) { + c->ue_xfer = usbd_alloc_xfer(ue_udev); + if (c->ue_xfer == NULL) + return (ENOBUFS); + c->ue_buf = usbd_alloc_buffer(c->ue_xfer, UE_BUFSZ); + if (c->ue_buf == NULL) + return (ENOBUFS); + } + } + + return (0); +} + +int +usb_ether_tx_list_init(void *sc, struct ue_cdata *cd, + usbd_device_handle ue_udev) +{ + struct ue_chain *c; + int i; + + for (i = 0; i < UE_TX_LIST_CNT; i++) { + c = &cd->ue_tx_chain[i]; + c->ue_sc = sc; + c->ue_idx = i; + c->ue_mbuf = NULL; + if (c->ue_xfer == NULL) { + c->ue_xfer = usbd_alloc_xfer(ue_udev); + if (c->ue_xfer == NULL) + return (ENOBUFS); + c->ue_buf = usbd_alloc_buffer(c->ue_xfer, UE_BUFSZ); + if (c->ue_buf == NULL) + return (ENOBUFS); + } + } + + return (0); +} + +void +usb_ether_rx_list_free(struct ue_cdata *cd) +{ + int i; + + for (i = 0; i < UE_RX_LIST_CNT; i++) { + if (cd->ue_rx_chain[i].ue_mbuf != NULL) { + m_freem(cd->ue_rx_chain[i].ue_mbuf); + cd->ue_rx_chain[i].ue_mbuf = NULL; + } + if (cd->ue_rx_chain[i].ue_xfer != NULL) { + usbd_free_xfer(cd->ue_rx_chain[i].ue_xfer); + cd->ue_rx_chain[i].ue_xfer = NULL; + } + } +} + +void +usb_ether_tx_list_free(struct ue_cdata *cd) +{ + int i; + + for (i = 0; i < UE_RX_LIST_CNT; i++) { + if (cd->ue_tx_chain[i].ue_mbuf != NULL) { + m_freem(cd->ue_tx_chain[i].ue_mbuf); + cd->ue_tx_chain[i].ue_mbuf = NULL; + } + if (cd->ue_tx_chain[i].ue_xfer != NULL) { + usbd_free_xfer(cd->ue_tx_chain[i].ue_xfer); + cd->ue_tx_chain[i].ue_xfer = NULL; + } + } +} + +void +usb_ether_task_init(device_t dev, int flags, struct usb_taskqueue *tq) +{ + + /* nothing for now. */ +} + +void +usb_ether_task_enqueue(struct usb_taskqueue *tq, struct task *task) +{ + + taskqueue_enqueue(taskqueue_thread, task); +} + +void +usb_ether_task_drain(struct usb_taskqueue *tq, struct task *task) +{ + + taskqueue_drain(taskqueue_thread, task); +} + +void +usb_ether_task_destroy(struct usb_taskqueue *tq) +{ + + /* nothing for now */ + +} diff --git a/sys/legacy/dev/usb/usb_ethersubr.h b/sys/legacy/dev/usb/usb_ethersubr.h new file mode 100644 index 0000000..f3d2e34 --- /dev/null +++ b/sys/legacy/dev/usb/usb_ethersubr.h @@ -0,0 +1,91 @@ +/*- + * Copyright (c) 1997, 1998, 1999, 2000 + * Bill Paul <wpaul@ee.columbia.edu>. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _USB_ETHERSUBR_H_ +#define _USB_ETHERSUBR_H_ + +#include <sys/bus.h> +#include <sys/module.h> + +#include <dev/usb/usbdi.h> + +#define UE_TX_LIST_CNT 1 +#define UE_RX_LIST_CNT 1 +#define UE_BUFSZ 1536 + +struct usb_qdat { + struct ifnet *ifp; + void (*if_rxstart) (struct ifnet *); +}; + +struct ue_chain { + void *ue_sc; + usbd_xfer_handle ue_xfer; + char *ue_buf; + struct mbuf *ue_mbuf; + int ue_idx; + usbd_status ue_status; +}; + +struct ue_cdata { + struct ue_chain ue_tx_chain[UE_TX_LIST_CNT]; + struct ue_chain ue_rx_chain[UE_RX_LIST_CNT]; + void *ue_ibuf; + int ue_tx_prod; + int ue_tx_cons; + int ue_tx_cnt; + int ue_rx_prod; +}; + +void usb_register_netisr (void); +void usb_ether_input (struct mbuf *); +void usb_tx_done (struct mbuf *); +struct mbuf *usb_ether_newbuf (void); +int usb_ether_rx_list_init (void *, struct ue_cdata *, + usbd_device_handle); +int usb_ether_tx_list_init (void *, struct ue_cdata *, + usbd_device_handle); +void usb_ether_rx_list_free (struct ue_cdata *); +void usb_ether_tx_list_free (struct ue_cdata *); + +struct usb_taskqueue { + int dummy; +}; + +void usb_ether_task_init(device_t, int, struct usb_taskqueue *); +void usb_ether_task_enqueue(struct usb_taskqueue *, struct task *); +void usb_ether_task_drain(struct usb_taskqueue *, struct task *); +void usb_ether_task_destroy(struct usb_taskqueue *); + +#endif /* _USB_ETHERSUBR_H_ */ diff --git a/sys/legacy/dev/usb/usb_if.m b/sys/legacy/dev/usb/usb_if.m new file mode 100644 index 0000000..b04c8a4 --- /dev/null +++ b/sys/legacy/dev/usb/usb_if.m @@ -0,0 +1,42 @@ +#- +# Copyright (c) 1992-1998 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, +# without modification, immediately at the beginning of the file. +# 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. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# 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$ +# + +# USB interface description +# + +#include <sys/bus.h> + +INTERFACE usb; + +# The device should start probing for new drivers again +# +METHOD int reconfigure { + device_t dev; +}; diff --git a/sys/legacy/dev/usb/usb_mem.c b/sys/legacy/dev/usb/usb_mem.c new file mode 100644 index 0000000..bbb0bf6 --- /dev/null +++ b/sys/legacy/dev/usb/usb_mem.c @@ -0,0 +1,297 @@ +/* $NetBSD: usb_mem.c,v 1.26 2003/02/01 06:23:40 thorpej 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. + */ + +/* + * USB DMA memory allocation. + * We need to allocate a lot of small (many 8 byte, some larger) + * memory blocks that can be used for DMA. Using the bus_dma + * routines directly would incur large overheads in space and time. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/kernel.h> +#include <sys/endian.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/queue.h> + +#include <machine/bus.h> +#include <machine/endian.h> + +#ifdef DIAGNOSTIC +#include <sys/proc.h> +#endif + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdivar.h> /* just for usb_dma_t */ +#include <dev/usb/usb_mem.h> + +#ifdef USB_DEBUG +#define DPRINTF(x) if (usbdebug) printf x +#define DPRINTFN(n,x) if (usbdebug>(n)) printf x +extern int usbdebug; +#else +#define DPRINTF(x) +#define DPRINTFN(n,x) +#endif + +#define USB_MEM_SMALL 64 +#define USB_MEM_CHUNKS (PAGE_SIZE / USB_MEM_SMALL) +#define USB_MEM_BLOCK (USB_MEM_SMALL * USB_MEM_CHUNKS) + +/* This struct is overlayed on free fragments. */ +struct usb_frag_dma { + usb_dma_block_t *block; + u_int offs; + LIST_ENTRY(usb_frag_dma) next; +}; + +static bus_dmamap_callback_t usbmem_callback; +static usbd_status usb_block_allocmem(bus_dma_tag_t, size_t, size_t, + usb_dma_block_t **); +static void usb_block_freemem(usb_dma_block_t *); + +static LIST_HEAD(, usb_dma_block) usb_blk_freelist = + LIST_HEAD_INITIALIZER(usb_blk_freelist); +static int usb_blk_nfree = 0; +/* XXX should have different free list for different tags (for speed) */ +static LIST_HEAD(, usb_frag_dma) usb_frag_freelist = + LIST_HEAD_INITIALIZER(usb_frag_freelist); + +static void +usbmem_callback(void *arg, bus_dma_segment_t *segs, int nseg, int error) +{ + int i; + usb_dma_block_t *p = arg; + + if (error == EFBIG) { + printf("usb: mapping to large\n"); + return; + } + + p->nsegs = nseg; + for (i = 0; i < nseg && i < sizeof p->segs / sizeof *p->segs; i++) + p->segs[i] = segs[i]; +} + +static usbd_status +usb_block_allocmem(bus_dma_tag_t tag, size_t size, size_t align, + usb_dma_block_t **dmap) +{ + usb_dma_block_t *p; + int s; + + DPRINTFN(5, ("usb_block_allocmem: size=%lu align=%lu\n", + (u_long)size, (u_long)align)); + +#ifdef DIAGNOSTIC + if (!curproc) { + printf("usb_block_allocmem: in interrupt context, size=%lu\n", + (unsigned long) size); + } +#endif + + s = splusb(); + /* First check the free list. */ + for (p = LIST_FIRST(&usb_blk_freelist); p; p = LIST_NEXT(p, next)) { + if (p->tag == tag && p->size >= size && p->size < size * 2 && + p->align >= align) { + LIST_REMOVE(p, next); + usb_blk_nfree--; + splx(s); + *dmap = p; + DPRINTFN(6,("usb_block_allocmem: free list size=%lu\n", + (u_long)p->size)); + return (USBD_NORMAL_COMPLETION); + } + } + splx(s); + +#ifdef DIAGNOSTIC + if (!curproc) { + printf("usb_block_allocmem: in interrupt context, failed\n"); + return (USBD_NOMEM); + } +#endif + + DPRINTFN(6, ("usb_block_allocmem: no free\n")); + p = malloc(sizeof *p, M_USB, M_NOWAIT); + if (p == NULL) + return (USBD_NOMEM); + + if (bus_dma_tag_create(tag, align, 0, + BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, + size, sizeof(p->segs) / sizeof(p->segs[0]), size, + 0, NULL, NULL, &p->tag) == ENOMEM) + { + goto free; + } + + p->size = size; + p->align = align; + if (bus_dmamem_alloc(p->tag, &p->kaddr, + BUS_DMA_NOWAIT|BUS_DMA_COHERENT, &p->map)) + goto tagfree; + + if (bus_dmamap_load(p->tag, p->map, p->kaddr, p->size, + usbmem_callback, p, 0)) + goto memfree; + + /* XXX - override the tag, ok since we never free it */ + p->tag = tag; + *dmap = p; + return (USBD_NORMAL_COMPLETION); + + /* + * XXX - do we need to _unload? is the order of _free and _destroy + * correct? + */ +memfree: + bus_dmamem_free(p->tag, p->kaddr, p->map); +tagfree: + bus_dma_tag_destroy(p->tag); +free: + free(p, M_USB); + return (USBD_NOMEM); +} + +/* + * Do not free the memory unconditionally since we might be called + * from an interrupt context and that is BAD. + * XXX when should we really free? + */ +static void +usb_block_freemem(usb_dma_block_t *p) +{ + int s; + + DPRINTFN(6, ("usb_block_freemem: size=%lu\n", (u_long)p->size)); + s = splusb(); + LIST_INSERT_HEAD(&usb_blk_freelist, p, next); + usb_blk_nfree++; + splx(s); +} + +usbd_status +usb_allocmem(usbd_bus_handle bus, size_t size, size_t align, usb_dma_t *p) +{ + bus_dma_tag_t tag = bus->parent_dmatag; + usbd_status err; + struct usb_frag_dma *f; + usb_dma_block_t *b; + int i; + int s; + + /* compat w/ Net/OpenBSD */ + if (align == 0) + align = 1; + + /* If the request is large then just use a full block. */ + if (size > USB_MEM_SMALL || align > USB_MEM_SMALL) { + DPRINTFN(1, ("usb_allocmem: large alloc %d\n", (int)size)); + size = (size + USB_MEM_BLOCK - 1) & ~(USB_MEM_BLOCK - 1); + err = usb_block_allocmem(tag, size, align, &p->block); + if (!err) { + p->block->fullblock = 1; + p->offs = 0; + p->len = size; + } + return (err); + } + + s = splusb(); + /* Check for free fragments. */ + for (f = LIST_FIRST(&usb_frag_freelist); f; f = LIST_NEXT(f, next)) + if (f->block->tag == tag) + break; + if (f == NULL) { + DPRINTFN(1, ("usb_allocmem: adding fragments\n")); + err = usb_block_allocmem(tag, USB_MEM_BLOCK, USB_MEM_SMALL,&b); + if (err) { + splx(s); + return (err); + } + b->fullblock = 0; + /* XXX - override the tag, ok since we never free it */ + b->tag = tag; + KASSERT(sizeof *f <= USB_MEM_SMALL, ("USB_MEM_SMALL(%d) is too small for struct usb_frag_dma(%zd)\n", + USB_MEM_SMALL, sizeof *f)); + for (i = 0; i < USB_MEM_BLOCK; i += USB_MEM_SMALL) { + f = (struct usb_frag_dma *)((char *)b->kaddr + i); + f->block = b; + f->offs = i; + LIST_INSERT_HEAD(&usb_frag_freelist, f, next); + } + f = LIST_FIRST(&usb_frag_freelist); + } + p->block = f->block; + p->offs = f->offs; + p->len = USB_MEM_SMALL; + LIST_REMOVE(f, next); + splx(s); + DPRINTFN(5, ("usb_allocmem: use frag=%p size=%d\n", f, (int)size)); + return (USBD_NORMAL_COMPLETION); +} + +void +usb_freemem(usbd_bus_handle bus, usb_dma_t *p) +{ + struct usb_frag_dma *f; + int s; + + if (p->block->fullblock) { + DPRINTFN(1, ("usb_freemem: large free\n")); + usb_block_freemem(p->block); + return; + } + f = KERNADDR(p, 0); + f->block = p->block; + f->offs = p->offs; + s = splusb(); + LIST_INSERT_HEAD(&usb_frag_freelist, f, next); + splx(s); + DPRINTFN(5, ("usb_freemem: frag=%p\n", f)); +} diff --git a/sys/legacy/dev/usb/usb_mem.h b/sys/legacy/dev/usb/usb_mem.h new file mode 100644 index 0000000..b8f0a14 --- /dev/null +++ b/sys/legacy/dev/usb/usb_mem.h @@ -0,0 +1,58 @@ +/* $NetBSD: usb_mem.h,v 1.18 2002/05/28 17:45:17 augustss Exp $ */ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +typedef struct usb_dma_block { + bus_dma_tag_t tag; + bus_dmamap_t map; + void *kaddr; + bus_dma_segment_t segs[1]; + int nsegs; + size_t size; + size_t align; + int fullblock; + LIST_ENTRY(usb_dma_block) next; +} usb_dma_block_t; + +#define DMAADDR(dma, o) ((dma)->block->segs[0].ds_addr + (dma)->offs + (o)) +#define KERNADDR(dma, o) \ + ((void *)((char *)((dma)->block->kaddr) + (dma)->offs + (o))) + +usbd_status usb_allocmem(usbd_bus_handle,size_t,size_t, usb_dma_t *); +void usb_freemem(usbd_bus_handle, usb_dma_t *); diff --git a/sys/legacy/dev/usb/usb_port.h b/sys/legacy/dev/usb/usb_port.h new file mode 100644 index 0000000..fd92d7b --- /dev/null +++ b/sys/legacy/dev/usb/usb_port.h @@ -0,0 +1,200 @@ +/* $OpenBSD: usb_port.h,v 1.18 2000/09/06 22:42:10 rahnds Exp $ */ +/* $NetBSD: usb_port.h,v 1.54 2002/03/28 21:49:19 ichiro Exp $ */ +/* $FreeBSD$ */ + +/* Also already merged from NetBSD: + * $NetBSD: usb_port.h,v 1.57 2002/09/27 20:42:01 thorpej Exp $ + * $NetBSD: usb_port.h,v 1.58 2002/10/01 01:25:26 thorpej Exp $ + */ + +/*- + * 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_PORT_H +#define _USB_PORT_H + +/* + * Macro's to cope with the differences between operating systems. + */ + +/* + * FreeBSD + */ + +/* We don't use the soft interrupt code in FreeBSD. */ +#if 0 +#define USB_USE_SOFTINTR +#endif + +#define Static static + +#define device_ptr_t device_t +#define USBBASEDEVICE device_t +#define USBDEV(bdev) (bdev) +#define USBDEVNAME(bdev) device_get_nameunit(bdev) +#define USBDEVPTRNAME(bdev) device_get_nameunit(bdev) +#define USBDEVUNIT(bdev) device_get_unit(bdev) +#define USBGETSOFTC(bdev) (device_get_softc(bdev)) + +#define DECLARE_USB_DMA_T \ + struct usb_dma_block; \ + typedef struct { \ + struct usb_dma_block *block; \ + u_int offs; \ + u_int len; \ + } usb_dma_t + +typedef struct thread *usb_proc_ptr; + +#define uio_procp uio_td + +#define usb_kthread_create1(f, s, p, a0, a1) \ + kproc_create((f), (s), (p), RFHIGHPID, 0, (a0), (a1)) +#define usb_kthread_create2(f, s, p, a0) \ + kproc_create((f), (s), (p), RFHIGHPID, 0, (a0)) +#define usb_kthread_create kproc_create + +#define config_pending_incr() +#define config_pending_decr() + +typedef struct callout usb_callout_t; +#define usb_callout_init(h) callout_init(&(h), 0) +#define usb_callout(h, t, f, d) callout_reset(&(h), (t), (f), (d)) +#define usb_uncallout(h, f, d) callout_stop(&(h)) +#define usb_uncallout_drain(h, f, d) callout_drain(&(h)) + +#define clalloc(p, s, x) (clist_alloc_cblocks((p), (s), (s)), 0) +#define clfree(p) clist_free_cblocks((p)) + +#define config_detach(dev, flag) \ + do { \ + device_detach(dev); \ + free(device_get_ivars(dev), M_USB); \ + device_delete_child(device_get_parent(dev), dev); \ + } while (0); + +typedef struct malloc_type *usb_malloc_type; + +#define USB_DECLARE_DRIVER_INIT(dname, init...) \ +static device_probe_t __CONCAT(dname,_match); \ +static device_attach_t __CONCAT(dname,_attach); \ +static device_detach_t __CONCAT(dname,_detach); \ +\ +static devclass_t __CONCAT(dname,_devclass); \ +\ +static device_method_t __CONCAT(dname,_methods)[] = { \ + DEVMETHOD(device_probe, __CONCAT(dname,_match)), \ + DEVMETHOD(device_attach, __CONCAT(dname,_attach)), \ + DEVMETHOD(device_detach, __CONCAT(dname,_detach)), \ + init, \ + {0,0} \ +}; \ +\ +static driver_t __CONCAT(dname,_driver) = { \ + #dname, \ + __CONCAT(dname,_methods), \ + sizeof(struct __CONCAT(dname,_softc)) \ +}; \ +MODULE_DEPEND(dname, usb, 1, 1, 1) + + +#define METHODS_NONE {0,0} +#define USB_DECLARE_DRIVER(dname) USB_DECLARE_DRIVER_INIT(dname, METHODS_NONE) + +#define USB_MATCH(dname) \ +static int \ +__CONCAT(dname,_match)(device_t self) + +#define USB_MATCH_START(dname, uaa) \ + struct usb_attach_arg *uaa = device_get_ivars(self) + +#define USB_MATCH_SETUP \ + sc->sc_dev = self + +#define USB_ATTACH(dname) \ +static int \ +__CONCAT(dname,_attach)(device_t self) + +#define USB_ATTACH_START(dname, sc, uaa) \ + struct __CONCAT(dname,_softc) *sc = device_get_softc(self); \ + struct usb_attach_arg *uaa = device_get_ivars(self) + +/* Returns from attach */ +#define USB_ATTACH_ERROR_RETURN return ENXIO +#define USB_ATTACH_SUCCESS_RETURN return 0 + +#define USB_ATTACH_SETUP \ + sc->sc_dev = self; \ + +#define USB_DETACH(dname) \ +static int \ +__CONCAT(dname,_detach)(device_t self) + +#define USB_DETACH_START(dname, sc) \ + struct __CONCAT(dname,_softc) *sc = device_get_softc(self) + +#define USB_GET_SC_OPEN(dname, unit, sc) \ + sc = devclass_get_softc(__CONCAT(dname,_devclass), unit); \ + if (sc == NULL) \ + return (ENXIO) + +#define USB_GET_SC(dname, unit, sc) \ + sc = devclass_get_softc(__CONCAT(dname,_devclass), unit) + +#define USB_DO_ATTACH(dev, bdev, parent, args, print, sub) \ + (device_probe_and_attach((bdev)) == 0 ? (bdev) : 0) + +/* conversion from one type of queue to the other */ +#define SIMPLEQ_REMOVE_HEAD STAILQ_REMOVE_HEAD +#define SIMPLEQ_INSERT_HEAD STAILQ_INSERT_HEAD +#define SIMPLEQ_INSERT_TAIL STAILQ_INSERT_TAIL +#define SIMPLEQ_NEXT STAILQ_NEXT +#define SIMPLEQ_FIRST STAILQ_FIRST +#define SIMPLEQ_HEAD STAILQ_HEAD +#define SIMPLEQ_EMPTY STAILQ_EMPTY +#define SIMPLEQ_FOREACH STAILQ_FOREACH +#define SIMPLEQ_INIT STAILQ_INIT +#define SIMPLEQ_HEAD_INITIALIZER STAILQ_HEAD_INITIALIZER +#define SIMPLEQ_ENTRY STAILQ_ENTRY + +#include <sys/syslog.h> +/* +#define logprintf(args...) log(LOG_DEBUG, args) +*/ +#define logprintf printf + +#endif /* _USB_PORT_H */ diff --git a/sys/legacy/dev/usb/usb_quirks.c b/sys/legacy/dev/usb/usb_quirks.c new file mode 100644 index 0000000..2771bb7 --- /dev/null +++ b/sys/legacy/dev/usb/usb_quirks.c @@ -0,0 +1,153 @@ +/* $NetBSD: usb_quirks.c,v 1.50 2004/06/23 02:30:52 mycroft Exp $ */ + +/*- + * 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> + +#include <dev/usb/usb.h> + +#include "usbdevs.h" +#include <dev/usb/usb_quirks.h> + +#ifdef USB_DEBUG +extern int usbdebug; +#endif + +#define ANY 0xffff + +static const struct usbd_quirk_entry { + u_int16_t idVendor; + u_int16_t idProduct; + u_int16_t bcdDevice; + struct usbd_quirks quirks; +} usb_quirks[] = { + { USB_VENDOR_INSIDEOUT, USB_PRODUCT_INSIDEOUT_EDGEPORT4, + 0x094, { UQ_SWAP_UNICODE}}, + { USB_VENDOR_DALLAS, USB_PRODUCT_DALLAS_J6502, 0x0a2, { UQ_BAD_ADC }}, + { USB_VENDOR_DALLAS, USB_PRODUCT_DALLAS_J6502, 0x0a2, { UQ_AU_NO_XU }}, + { USB_VENDOR_ALTEC, USB_PRODUCT_ALTEC_ADA70, 0x103, { UQ_BAD_ADC }}, + { USB_VENDOR_ALTEC, USB_PRODUCT_ALTEC_ASC495, 0x000, { UQ_BAD_AUDIO }}, + { USB_VENDOR_QTRONIX, USB_PRODUCT_QTRONIX_980N, 0x110, { UQ_SPUR_BUT_UP }}, + { USB_VENDOR_ALCOR2, USB_PRODUCT_ALCOR2_KBD_HUB, 0x001, { UQ_SPUR_BUT_UP }}, + { USB_VENDOR_MCT, USB_PRODUCT_MCT_HUB0100, 0x102, { UQ_BUS_POWERED }}, + { USB_VENDOR_MCT, USB_PRODUCT_MCT_USB232, 0x102, { UQ_BUS_POWERED }}, + { USB_VENDOR_TI, USB_PRODUCT_TI_UTUSB41, 0x110, { UQ_POWER_CLAIM }}, + { USB_VENDOR_TELEX, USB_PRODUCT_TELEX_MIC1, 0x009, { UQ_AU_NO_FRAC }}, + { USB_VENDOR_SILICONPORTALS, USB_PRODUCT_SILICONPORTALS_YAPPHONE, + 0x100, { UQ_AU_INP_ASYNC }}, + { USB_VENDOR_LOGITECH, USB_PRODUCT_LOGITECH_UN53B, ANY, { UQ_NO_STRINGS }}, + /* XXX These should have a revision number, but I don't know what they are. */ + { USB_VENDOR_HP, USB_PRODUCT_HP_895C, ANY, { UQ_BROKEN_BIDIR }}, + { USB_VENDOR_HP, USB_PRODUCT_HP_880C, ANY, { UQ_BROKEN_BIDIR }}, + { USB_VENDOR_HP, USB_PRODUCT_HP_815C, ANY, { UQ_BROKEN_BIDIR }}, + { USB_VENDOR_HP, USB_PRODUCT_HP_810C, ANY, { UQ_BROKEN_BIDIR }}, + { USB_VENDOR_HP, USB_PRODUCT_HP_830C, ANY, { UQ_BROKEN_BIDIR }}, + { USB_VENDOR_HP, USB_PRODUCT_HP_1220C, ANY, { UQ_BROKEN_BIDIR }}, + { USB_VENDOR_XEROX, USB_PRODUCT_XEROX_WCM15, ANY, { UQ_BROKEN_BIDIR }}, + /* MS keyboards do weird things */ + { USB_VENDOR_MICROSOFT, USB_PRODUCT_MICROSOFT_WLNOTEBOOK, + ANY, { UQ_MS_BAD_CLASS | UQ_MS_LEADING_BYTE }}, + { USB_VENDOR_MICROSOFT, USB_PRODUCT_MICROSOFT_WLNOTEBOOK2, + ANY, { UQ_MS_BAD_CLASS | UQ_MS_LEADING_BYTE }}, + { USB_VENDOR_MICROSOFT, USB_PRODUCT_MICROSOFT_WLINTELLIMOUSE, + ANY, { UQ_MS_LEADING_BYTE }}, + { USB_VENDOR_MICROSOFT, USB_PRODUCT_MICROSOFT_COMFORT3000, + ANY, { UQ_MS_BAD_CLASS | UQ_MS_LEADING_BYTE }}, + { USB_VENDOR_SONY, USB_PRODUCT_SONY_RF_RECEIVER, + ANY,{ UQ_MS_BAD_CLASS }}, + + /* Devices which should be ignored by uhid */ + { USB_VENDOR_APC, USB_PRODUCT_APC_UPS, + ANY, { UQ_HID_IGNORE }}, + { USB_VENDOR_ASUS, USB_PRODUCT_ASUS_LCM, + ANY, { UQ_HID_IGNORE }}, + { USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F6C550AVR, + ANY, { UQ_HID_IGNORE }}, + { USB_VENDOR_CYBERPOWER, USB_PRODUCT_CYBERPOWER_1500CAVRLCD, + ANY, { UQ_HID_IGNORE }}, + { USB_VENDOR_DELORME, USB_PRODUCT_DELORME_EARTHMATE, + ANY, { UQ_HID_IGNORE }}, + { USB_VENDOR_ITUNERNET, USB_PRODUCT_ITUNERNET_USBLCD2X20, + ANY, { UQ_HID_IGNORE }}, + { USB_VENDOR_MGE, USB_PRODUCT_MGE_UPS1, + ANY, { UQ_HID_IGNORE }}, + { USB_VENDOR_MGE, USB_PRODUCT_MGE_UPS2, + ANY, { UQ_HID_IGNORE }}, + { USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPHONE, + ANY, { UQ_HID_IGNORE }}, + { USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPHONE_3G, + ANY, { UQ_HID_IGNORE }}, + + /* Devices which should be ignored by both ukbd and uhid */ + { USB_VENDOR_CYPRESS, USB_PRODUCT_CYPRESS_WISPY1A, + ANY, { UQ_KBD_IGNORE }}, + { USB_VENDOR_METAGEEK, USB_PRODUCT_METAGEEK_WISPY1B, + ANY, { UQ_KBD_IGNORE }}, + { USB_VENDOR_METAGEEK, USB_PRODUCT_METAGEEK_WISPY24X, + ANY, { UQ_KBD_IGNORE }}, + { 0, 0, 0, { 0 } } +}; + +const struct usbd_quirks usbd_no_quirk = { 0 }; + +const struct usbd_quirks * +usbd_find_quirk(usb_device_descriptor_t *d) +{ + const struct usbd_quirk_entry *t; + u_int16_t vendor = UGETW(d->idVendor); + u_int16_t product = UGETW(d->idProduct); + u_int16_t revision = UGETW(d->bcdDevice); + + for (t = usb_quirks; t->idVendor != 0; t++) { + if (t->idVendor == vendor && + t->idProduct == product && + (t->bcdDevice == ANY || t->bcdDevice == revision)) + break; + } +#ifdef USB_DEBUG + if (usbdebug && t->quirks.uq_flags) + printf("usbd_find_quirk 0x%04x/0x%04x/%x: %d\n", + UGETW(d->idVendor), UGETW(d->idProduct), + UGETW(d->bcdDevice), t->quirks.uq_flags); +#endif + return (&t->quirks); +} diff --git a/sys/legacy/dev/usb/usb_quirks.h b/sys/legacy/dev/usb/usb_quirks.h new file mode 100644 index 0000000..16e2f3f --- /dev/null +++ b/sys/legacy/dev/usb/usb_quirks.h @@ -0,0 +1,64 @@ +/* $NetBSD: usb_quirks.h,v 1.20 2001/04/15 09:38:01 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. + */ + +struct usbd_quirks { + u_int32_t uq_flags; /* Device problems: */ +#define UQ_SWAP_UNICODE 0x00000002 /* has some Unicode strings swapped. */ +#define UQ_MS_REVZ 0x00000004 /* mouse has Z-axis reversed */ +#define UQ_NO_STRINGS 0x00000008 /* string descriptors are broken. */ +#define UQ_BAD_ADC 0x00000010 /* bad audio spec version number. */ +#define UQ_BUS_POWERED 0x00000020 /* device is bus powered, despite claim */ +#define UQ_BAD_AUDIO 0x00000040 /* device claims audio class, but isn't */ +#define UQ_SPUR_BUT_UP 0x00000080 /* spurious mouse button up events */ +#define UQ_AU_NO_XU 0x00000100 /* audio device has broken extension unit */ +#define UQ_POWER_CLAIM 0x00000200 /* hub lies about power status */ +#define UQ_AU_NO_FRAC 0x00000400 /* don't adjust for fractional samples */ +#define UQ_AU_INP_ASYNC 0x00000800 /* input is async despite claim of adaptive */ +#define UQ_BROKEN_BIDIR 0x00002000 /* printer has broken bidir mode */ +#define UQ_OPEN_CLEARSTALL 0x04000 /* device needs clear endpoint stall */ +#define UQ_HID_IGNORE 0x00008000 /* device should be ignored by hid class */ +#define UQ_KBD_IGNORE 0x00018000 /* device should be ignored by both kbd and hid class */ +#define UQ_MS_BAD_CLASS 0x00020000 /* doesn't identify properly */ +#define UQ_MS_LEADING_BYTE 0x40000 /* mouse sends an unknown leading byte. */ +}; + +extern const struct usbd_quirks usbd_no_quirk; + +const struct usbd_quirks *usbd_find_quirk(usb_device_descriptor_t *); diff --git a/sys/legacy/dev/usb/usb_subr.c b/sys/legacy/dev/usb/usb_subr.c new file mode 100644 index 0000000..29d044c --- /dev/null +++ b/sys/legacy/dev/usb/usb_subr.c @@ -0,0 +1,1388 @@ +/* $NetBSD: usb_subr.c,v 1.99 2002/07/11 21:14:34 augustss Exp $ */ + +/* Also already have from NetBSD: + * $NetBSD: usb_subr.c,v 1.102 2003/01/01 16:21:50 augustss Exp $ + * $NetBSD: usb_subr.c,v 1.103 2003/01/10 11:19:13 augustss Exp $ + * $NetBSD: usb_subr.c,v 1.111 2004/03/15 10:35:04 augustss Exp $ + * $NetBSD: usb_subr.c,v 1.114 2004/06/23 02:30:52 mycroft Exp $ + * $NetBSD: usb_subr.c,v 1.115 2004/06/23 05:23:19 mycroft Exp $ + * $NetBSD: usb_subr.c,v 1.116 2004/06/23 06:27:54 mycroft Exp $ + * $NetBSD: usb_subr.c,v 1.119 2004/10/23 13:26:33 augustss Exp $ + */ + +#include <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 "opt_usb.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/proc.h> +#include <sys/sysctl.h> + +#include <machine/bus.h> + +#include <dev/usb/usb.h> + +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usbdivar.h> +#include "usbdevs.h" +#include <dev/usb/usb_quirks.h> + +#define delay(d) DELAY(d) + +#ifdef USB_DEBUG +#define DPRINTF(x) if (usbdebug) printf x +#define DPRINTFN(n,x) if (usbdebug>(n)) printf x +extern int usbdebug; +#else +#define DPRINTF(x) +#define DPRINTFN(n,x) +#endif + +static usbd_status usbd_set_config(usbd_device_handle, int); +static void usbd_devinfo_vp(usbd_device_handle, char *, char *, int); +static int usbd_getnewaddr(usbd_bus_handle bus); +static void usbd_free_iface_data(usbd_device_handle dev, int ifcno); +static void usbd_kill_pipe(usbd_pipe_handle); +static usbd_status usbd_probe_and_attach(device_t parent, + usbd_device_handle dev, int port, int addr); + +static u_int32_t usb_cookie_no = 0; + +#ifdef USBVERBOSE +typedef u_int16_t usb_vendor_id_t; +typedef u_int16_t usb_product_id_t; + +/* + * Descriptions of of known vendors and devices ("products"). + */ +struct usb_knowndev { + usb_vendor_id_t vendor; + usb_product_id_t product; + int flags; + char *vendorname, *productname; +}; +#define USB_KNOWNDEV_NOPROD 0x01 /* match on vendor only */ + +#include "usbdevs_data.h" +#endif /* USBVERBOSE */ + +static const char * const usbd_error_strs[] = { + "NORMAL_COMPLETION", + "IN_PROGRESS", + "PENDING_REQUESTS", + "NOT_STARTED", + "INVAL", + "NOMEM", + "CANCELLED", + "BAD_ADDRESS", + "IN_USE", + "NO_ADDR", + "SET_ADDR_FAILED", + "NO_POWER", + "TOO_DEEP", + "IOERROR", + "NOT_CONFIGURED", + "TIMEOUT", + "SHORT_XFER", + "STALLED", + "INTERRUPTED", + "XXX", +}; + +const char * +usbd_errstr(usbd_status err) +{ + static char buffer[5]; + + if (err < USBD_ERROR_MAX) { + return usbd_error_strs[err]; + } else { + snprintf(buffer, sizeof buffer, "%d", err); + return buffer; + } +} + +usbd_status +usbd_get_string_desc(usbd_device_handle dev, int sindex, int langid, + usb_string_descriptor_t *sdesc, int *sizep) +{ + usb_device_request_t req; + usbd_status err; + int actlen; + + req.bmRequestType = UT_READ_DEVICE; + req.bRequest = UR_GET_DESCRIPTOR; + USETW2(req.wValue, UDESC_STRING, sindex); + USETW(req.wIndex, langid); + USETW(req.wLength, 2); /* only size byte first */ + err = usbd_do_request_flags(dev, &req, sdesc, USBD_SHORT_XFER_OK, + &actlen, USBD_DEFAULT_TIMEOUT); + if (err) + return (err); + + if (actlen < 2) + return (USBD_SHORT_XFER); + + USETW(req.wLength, sdesc->bLength); /* the whole string */ + err = usbd_do_request_flags(dev, &req, sdesc, USBD_SHORT_XFER_OK, + &actlen, USBD_DEFAULT_TIMEOUT); + if (err) + return (err); + + if (actlen != sdesc->bLength) { + DPRINTFN(-1, ("usbd_get_string_desc: expected %d, got %d\n", + sdesc->bLength, actlen)); + } + + *sizep = actlen; + return (USBD_NORMAL_COMPLETION); +} + +static void +usbd_trim_spaces(char *p) +{ + char *q, *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 */ +} + +static void +usbd_devinfo_vp(usbd_device_handle dev, char *v, char *p, int usedev) +{ + usb_device_descriptor_t *udd = &dev->ddesc; + char *vendor = 0, *product = 0; +#ifdef USBVERBOSE + const struct usb_knowndev *kdp; +#endif + + if (dev == NULL) { + v[0] = p[0] = '\0'; + return; + } + + if (usedev) { + if (usbd_get_string(dev, udd->iManufacturer, v, + USB_MAX_STRING_LEN)) + vendor = NULL; + else + vendor = v; + usbd_trim_spaces(vendor); + if (usbd_get_string(dev, udd->iProduct, p, + USB_MAX_STRING_LEN)) + product = NULL; + else + product = p; + usbd_trim_spaces(product); + if (vendor && !*vendor) + vendor = NULL; + if (product && !*product) + product = NULL; + } else { + vendor = NULL; + product = NULL; + } +#ifdef USBVERBOSE + if (vendor == NULL || product == NULL) { + for(kdp = usb_knowndevs; + kdp->vendorname != NULL; + kdp++) { + if (kdp->vendor == UGETW(udd->idVendor) && + (kdp->product == UGETW(udd->idProduct) || + (kdp->flags & USB_KNOWNDEV_NOPROD) != 0)) + break; + } + if (kdp->vendorname != NULL) { + if (vendor == NULL) + vendor = kdp->vendorname; + if (product == NULL) + product = (kdp->flags & USB_KNOWNDEV_NOPROD) == 0 ? + kdp->productname : NULL; + } + } +#endif + if (vendor != NULL && *vendor) + strcpy(v, vendor); + else + sprintf(v, "vendor 0x%04x", UGETW(udd->idVendor)); + if (product != NULL && *product) + strcpy(p, product); + else + sprintf(p, "product 0x%04x", UGETW(udd->idProduct)); +} + +int +usbd_printBCD(char *cp, int bcd) +{ + return (sprintf(cp, "%x.%02x", bcd >> 8, bcd & 0xff)); +} + +void +usbd_devinfo(usbd_device_handle dev, int showclass, char *cp) +{ + usb_device_descriptor_t *udd = &dev->ddesc; + usbd_interface_handle iface; + char vendor[USB_MAX_STRING_LEN]; + char product[USB_MAX_STRING_LEN]; + int bcdDevice, bcdUSB; + usb_interface_descriptor_t *id; + + usbd_devinfo_vp(dev, vendor, product, 1); + cp += sprintf(cp, "%s %s", vendor, product); + if (showclass & USBD_SHOW_DEVICE_CLASS) + cp += sprintf(cp, ", class %d/%d", + udd->bDeviceClass, udd->bDeviceSubClass); + bcdUSB = UGETW(udd->bcdUSB); + bcdDevice = UGETW(udd->bcdDevice); + cp += sprintf(cp, ", rev "); + cp += usbd_printBCD(cp, bcdUSB); + *cp++ = '/'; + cp += usbd_printBCD(cp, bcdDevice); + cp += sprintf(cp, ", addr %d", dev->address); + if (showclass & USBD_SHOW_INTERFACE_CLASS) + { + /* fetch the interface handle for the first interface */ + (void)usbd_device2interface_handle(dev, 0, &iface); + id = usbd_get_interface_descriptor(iface); + cp += sprintf(cp, ", iclass %d/%d", + id->bInterfaceClass, id->bInterfaceSubClass); + } + *cp = 0; +} + +/* Delay for a certain number of ms */ +void +usb_delay_ms(usbd_bus_handle bus, u_int ms) +{ + /* Wait at least two clock ticks so we know the time has passed. */ + if (bus->use_polling || cold) + delay((ms+1) * 1000); + else + pause("usbdly", (ms*hz+999)/1000 + 1); +} + +/* Delay given a device handle. */ +void +usbd_delay_ms(usbd_device_handle dev, u_int ms) +{ + usb_delay_ms(dev->bus, ms); +} + +usbd_status +usbd_reset_port(usbd_device_handle dev, int port, usb_port_status_t *ps) +{ + usbd_status err; + int n; + + err = usbd_set_port_feature(dev, port, UHF_PORT_RESET); + DPRINTFN(1,("usbd_reset_port: port %d reset done, error=%s\n", + port, usbd_errstr(err))); + if (err) + return (err); + n = 10; + do { + /* Wait for device to recover from reset. */ + usbd_delay_ms(dev, USB_PORT_RESET_DELAY); + err = usbd_get_port_status(dev, port, ps); + if (err) { + DPRINTF(("usbd_reset_port: get status failed %d\n", + err)); + return (err); + } + /* If the device disappeared, just give up. */ + if (!(UGETW(ps->wPortStatus) & UPS_CURRENT_CONNECT_STATUS)) + return (USBD_NORMAL_COMPLETION); + } while ((UGETW(ps->wPortChange) & UPS_C_PORT_RESET) == 0 && --n > 0); + if (n == 0) + return (USBD_TIMEOUT); + err = usbd_clear_port_feature(dev, port, UHF_C_PORT_RESET); +#ifdef USB_DEBUG + if (err) + DPRINTF(("usbd_reset_port: clear port feature failed %d\n", + err)); +#endif + + /* Wait for the device to recover from reset. */ + usbd_delay_ms(dev, USB_PORT_RESET_RECOVERY); + return (err); +} + +usb_interface_descriptor_t * +usbd_find_idesc(usb_config_descriptor_t *cd, int ifaceidx, int altidx) +{ + char *p = (char *)cd; + char *end = p + UGETW(cd->wTotalLength); + usb_interface_descriptor_t *d; + int curidx, lastidx, curaidx = 0; + + for (curidx = lastidx = -1; p < end; ) { + d = (usb_interface_descriptor_t *)p; + DPRINTFN(4,("usbd_find_idesc: idx=%d(%d) altidx=%d(%d) len=%d " + "type=%d\n", + ifaceidx, curidx, altidx, curaidx, + d->bLength, d->bDescriptorType)); + if (d->bLength == 0) /* bad descriptor */ + break; + p += d->bLength; + if (p <= end && d->bDescriptorType == UDESC_INTERFACE) { + if (d->bInterfaceNumber != lastidx) { + lastidx = d->bInterfaceNumber; + curidx++; + curaidx = 0; + } else + curaidx++; + if (ifaceidx == curidx && altidx == curaidx) + return (d); + } + } + return (NULL); +} + +usb_endpoint_descriptor_t * +usbd_find_edesc(usb_config_descriptor_t *cd, int ifaceidx, int altidx, + int endptidx) +{ + char *p = (char *)cd; + char *end = p + UGETW(cd->wTotalLength); + usb_interface_descriptor_t *d; + usb_endpoint_descriptor_t *e; + int curidx; + + d = usbd_find_idesc(cd, ifaceidx, altidx); + if (d == NULL) + return (NULL); + if (endptidx >= d->bNumEndpoints) /* quick exit */ + return (NULL); + + curidx = -1; + for (p = (char *)d + d->bLength; p < end; ) { + e = (usb_endpoint_descriptor_t *)p; + if (e->bLength == 0) /* bad descriptor */ + break; + p += e->bLength; + if (p <= end && e->bDescriptorType == UDESC_INTERFACE) + return (NULL); + if (p <= end && e->bDescriptorType == UDESC_ENDPOINT) { + curidx++; + if (curidx == endptidx) + return (e); + } + } + return (NULL); +} + +usbd_status +usbd_fill_iface_data(usbd_device_handle dev, int ifaceidx, int altidx) +{ + usbd_interface_handle ifc = &dev->ifaces[ifaceidx]; + usb_interface_descriptor_t *idesc; + char *p, *end; + int endpt, nendpt; + + DPRINTFN(4,("usbd_fill_iface_data: ifaceidx=%d altidx=%d\n", + ifaceidx, altidx)); + idesc = usbd_find_idesc(dev->cdesc, ifaceidx, altidx); + if (idesc == NULL) + return (USBD_INVAL); + ifc->device = dev; + ifc->idesc = idesc; + ifc->index = ifaceidx; + ifc->altindex = altidx; + nendpt = ifc->idesc->bNumEndpoints; + DPRINTFN(4,("usbd_fill_iface_data: found idesc nendpt=%d\n", nendpt)); + if (nendpt != 0) { + ifc->endpoints = malloc(nendpt * sizeof(struct usbd_endpoint), + M_USB, M_NOWAIT); + if (ifc->endpoints == NULL) + return (USBD_NOMEM); + } else + ifc->endpoints = NULL; + ifc->priv = NULL; + p = (char *)ifc->idesc + ifc->idesc->bLength; + end = (char *)dev->cdesc + UGETW(dev->cdesc->wTotalLength); +#define ed ((usb_endpoint_descriptor_t *)p) + for (endpt = 0; endpt < nendpt; endpt++) { + DPRINTFN(10,("usbd_fill_iface_data: endpt=%d\n", endpt)); + for (; p < end; p += ed->bLength) { + DPRINTFN(10,("usbd_fill_iface_data: p=%p end=%p " + "len=%d type=%d\n", + p, end, ed->bLength, ed->bDescriptorType)); + if (p + ed->bLength <= end && ed->bLength != 0 && + ed->bDescriptorType == UDESC_ENDPOINT) + goto found; + if (ed->bLength == 0 || + ed->bDescriptorType == UDESC_INTERFACE) + break; + } + /* passed end, or bad desc */ + printf("usbd_fill_iface_data: bad descriptor(s): %s\n", + ed->bLength == 0 ? "0 length" : + ed->bDescriptorType == UDESC_INTERFACE ? "iface desc": + "out of data"); + goto bad; + found: + ifc->endpoints[endpt].edesc = ed; + if (dev->speed == USB_SPEED_HIGH) { + u_int mps; + /* Control and bulk endpoints have max packet limits. */ + switch (UE_GET_XFERTYPE(ed->bmAttributes)) { + case UE_CONTROL: + mps = USB_2_MAX_CTRL_PACKET; + goto check; + case UE_BULK: + mps = USB_2_MAX_BULK_PACKET; + check: + if (UGETW(ed->wMaxPacketSize) != mps) { + USETW(ed->wMaxPacketSize, mps); +#ifdef DIAGNOSTIC + printf("usbd_fill_iface_data: bad max " + "packet size\n"); +#endif + } + break; + default: + break; + } + } + ifc->endpoints[endpt].refcnt = 0; + ifc->endpoints[endpt].savedtoggle = 0; + p += ed->bLength; + } +#undef ed + LIST_INIT(&ifc->pipes); + return (USBD_NORMAL_COMPLETION); + + bad: + if (ifc->endpoints != NULL) { + free(ifc->endpoints, M_USB); + ifc->endpoints = NULL; + } + return (USBD_INVAL); +} + +void +usbd_free_iface_data(usbd_device_handle dev, int ifcno) +{ + usbd_interface_handle ifc = &dev->ifaces[ifcno]; + if (ifc->endpoints) + free(ifc->endpoints, M_USB); +} + +static usbd_status +usbd_set_config(usbd_device_handle dev, int conf) +{ + usb_device_request_t req; + + req.bmRequestType = UT_WRITE_DEVICE; + req.bRequest = UR_SET_CONFIG; + USETW(req.wValue, conf); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + return (usbd_do_request(dev, &req, 0)); +} + +usbd_status +usbd_set_config_no(usbd_device_handle dev, int no, int msg) +{ + int index; + usb_config_descriptor_t cd; + usbd_status err; + + if (no == USB_UNCONFIG_NO) + return (usbd_set_config_index(dev, USB_UNCONFIG_INDEX, msg)); + + DPRINTFN(5,("usbd_set_config_no: %d\n", no)); + /* Figure out what config index to use. */ + for (index = 0; index < dev->ddesc.bNumConfigurations; index++) { + err = usbd_get_config_desc(dev, index, &cd); + if (err) + return (err); + if (cd.bConfigurationValue == no) + return (usbd_set_config_index(dev, index, msg)); + } + return (USBD_INVAL); +} + +usbd_status +usbd_set_config_index(usbd_device_handle dev, int index, int msg) +{ + usb_status_t ds; + usb_config_descriptor_t cd, *cdp; + usbd_status err; + int i, ifcidx, nifc, len, selfpowered, power; + + DPRINTFN(5,("usbd_set_config_index: dev=%p index=%d\n", dev, index)); + + if (dev->config != USB_UNCONFIG_NO) { + nifc = dev->cdesc->bNumInterface; + + /* Check that all interfaces are idle */ + for (ifcidx = 0; ifcidx < nifc; ifcidx++) { + if (LIST_EMPTY(&dev->ifaces[ifcidx].pipes)) + continue; + DPRINTF(("usbd_set_config_index: open pipes exist\n")); + return (USBD_IN_USE); + } + + DPRINTF(("usbd_set_config_index: free old config\n")); + /* Free all configuration data structures. */ + for (ifcidx = 0; ifcidx < nifc; ifcidx++) + usbd_free_iface_data(dev, ifcidx); + free(dev->ifaces, M_USB); + free(dev->cdesc, M_USB); + dev->ifaces = NULL; + dev->cdesc = NULL; + dev->config = USB_UNCONFIG_NO; + } + + if (index == USB_UNCONFIG_INDEX) { + /* We are unconfiguring the device, so leave unallocated. */ + DPRINTF(("usbd_set_config_index: set config 0\n")); + err = usbd_set_config(dev, USB_UNCONFIG_NO); + if (err) + DPRINTF(("usbd_set_config_index: setting config=0 " + "failed, error=%s\n", usbd_errstr(err))); + return (err); + } + + /* Get the short descriptor. */ + err = usbd_get_config_desc(dev, index, &cd); + if (err) + return (err); + len = UGETW(cd.wTotalLength); + cdp = malloc(len, M_USB, M_NOWAIT); + if (cdp == NULL) + return (USBD_NOMEM); + + /* Get the full descriptor. Try a few times for slow devices. */ + for (i = 0; i < 3; i++) { + err = usbd_get_desc(dev, UDESC_CONFIG, index, len, cdp); + if (!err) + break; + usbd_delay_ms(dev, 200); + } + if (err) + goto bad; + if (cdp->bDescriptorType != UDESC_CONFIG) { + DPRINTFN(-1,("usbd_set_config_index: bad desc %d\n", + cdp->bDescriptorType)); + err = USBD_INVAL; + goto bad; + } + + /* Figure out if the device is self or bus powered. */ + selfpowered = 0; + if (!(dev->quirks->uq_flags & UQ_BUS_POWERED) && + (cdp->bmAttributes & UC_SELF_POWERED)) { + /* May be self powered. */ + if (cdp->bmAttributes & UC_BUS_POWERED) { + /* Must ask device. */ + if (dev->quirks->uq_flags & UQ_POWER_CLAIM) { + /* + * Hub claims to be self powered, but isn't. + * It seems that the power status can be + * determined by the hub characteristics. + */ + usb_hub_descriptor_t hd; + usb_device_request_t req; + req.bmRequestType = UT_READ_CLASS_DEVICE; + req.bRequest = UR_GET_DESCRIPTOR; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, USB_HUB_DESCRIPTOR_SIZE); + err = usbd_do_request(dev, &req, &hd); + if (!err && + (UGETW(hd.wHubCharacteristics) & + UHD_PWR_INDIVIDUAL)) + selfpowered = 1; + DPRINTF(("usbd_set_config_index: charac=0x%04x" + ", error=%s\n", + UGETW(hd.wHubCharacteristics), + usbd_errstr(err))); + } else { + err = usbd_get_device_status(dev, &ds); + if (!err && + (UGETW(ds.wStatus) & UDS_SELF_POWERED)) + selfpowered = 1; + DPRINTF(("usbd_set_config_index: status=0x%04x" + ", error=%s\n", + UGETW(ds.wStatus), usbd_errstr(err))); + } + } else + selfpowered = 1; + } + DPRINTF(("usbd_set_config_index: (addr %d) cno=%d attr=0x%02x, " + "selfpowered=%d, power=%d\n", + cdp->bConfigurationValue, dev->address, cdp->bmAttributes, + selfpowered, cdp->bMaxPower * 2)); + + /* Check if we have enough power. */ +#ifdef USB_DEBUG + if (dev->powersrc == NULL) { + DPRINTF(("usbd_set_config_index: No power source?\n")); + return (USBD_IOERROR); + } +#endif + power = cdp->bMaxPower * 2; + if (power > dev->powersrc->power) { + DPRINTF(("power exceeded %d %d\n", power,dev->powersrc->power)); + /* XXX print nicer message. */ + if (msg) + device_printf(dev->bus->bdev, + "device addr %d (config %d) exceeds " + "power budget, %d mA > %d mA\n", + dev->address, cdp->bConfigurationValue, + power, dev->powersrc->power); + err = USBD_NO_POWER; + goto bad; + } + dev->power = power; + dev->self_powered = selfpowered; + + /* Set the actual configuration value. */ + DPRINTF(("usbd_set_config_index: set config %d\n", + cdp->bConfigurationValue)); + err = usbd_set_config(dev, cdp->bConfigurationValue); + if (err) { + DPRINTF(("usbd_set_config_index: setting config=%d failed, " + "error=%s\n", + cdp->bConfigurationValue, usbd_errstr(err))); + goto bad; + } + + /* Allocate and fill interface data. */ + nifc = cdp->bNumInterface; + dev->ifaces = malloc(nifc * sizeof(struct usbd_interface), + M_USB, M_NOWAIT); + if (dev->ifaces == NULL) { + err = USBD_NOMEM; + goto bad; + } + DPRINTFN(5,("usbd_set_config_index: dev=%p cdesc=%p\n", dev, cdp)); + dev->cdesc = cdp; + dev->config = cdp->bConfigurationValue; + for (ifcidx = 0; ifcidx < nifc; ifcidx++) { + err = usbd_fill_iface_data(dev, ifcidx, 0); + if (err) { + while (--ifcidx >= 0) + usbd_free_iface_data(dev, ifcidx); + goto bad; + } + } + + return (USBD_NORMAL_COMPLETION); + + bad: + free(cdp, M_USB); + return (err); +} + +/* XXX add function for alternate settings */ + +usbd_status +usbd_setup_pipe(usbd_device_handle dev, usbd_interface_handle iface, + struct usbd_endpoint *ep, int ival, usbd_pipe_handle *pipe) +{ + usbd_pipe_handle p; + usbd_status err; + + DPRINTFN(1,("usbd_setup_pipe: dev=%p iface=%p ep=%p pipe=%p\n", + dev, iface, ep, pipe)); + p = malloc(dev->bus->pipe_size, M_USB, M_NOWAIT); + if (p == NULL) + return (USBD_NOMEM); + p->device = dev; + p->iface = iface; + p->endpoint = ep; + ep->refcnt++; + p->refcnt = 1; + p->intrxfer = 0; + p->running = 0; + p->aborting = 0; + p->repeat = 0; + p->interval = ival; + STAILQ_INIT(&p->queue); + err = dev->bus->methods->open_pipe(p); + if (err) { + DPRINTFN(-1,("usbd_setup_pipe: endpoint=0x%x failed, error=" + "%s\n", + ep->edesc->bEndpointAddress, usbd_errstr(err))); + free(p, M_USB); + return (err); + } + + if (dev->quirks->uq_flags & UQ_OPEN_CLEARSTALL) { + /* Clear any stall and make sure DATA0 toggle will be used next. */ + if (UE_GET_ADDR(ep->edesc->bEndpointAddress) != USB_CONTROL_ENDPOINT) { + err = usbd_clear_endpoint_stall(p); + if (err && err != USBD_STALLED && err != USBD_TIMEOUT) { + printf("usbd_setup_pipe: failed to start " + "endpoint, %s\n", usbd_errstr(err)); + return (err); + } + } + } + + *pipe = p; + return (USBD_NORMAL_COMPLETION); +} + +/* Abort the device control pipe. */ +void +usbd_kill_pipe(usbd_pipe_handle pipe) +{ + usbd_abort_pipe(pipe); + pipe->methods->close(pipe); + pipe->endpoint->refcnt--; + free(pipe, M_USB); +} + +int +usbd_getnewaddr(usbd_bus_handle bus) +{ + int addr; + + for (addr = 1; addr < USB_MAX_DEVICES; addr++) + if (bus->devices[addr] == 0) + return (addr); + return (-1); +} + + +usbd_status +usbd_probe_and_attach(device_t parent, usbd_device_handle dev, + int port, int addr) +{ + struct usb_attach_arg uaa; + usb_device_descriptor_t *dd = &dev->ddesc; + int found, i, confi, nifaces; + usbd_status err; + device_t *tmpdv; + usbd_interface_handle ifaces[256]; /* 256 is the absolute max */ + char *devinfo; + + /* XXX FreeBSD may leak resources on failure cases -- fixme */ + device_t bdev; + struct usb_attach_arg *uaap; + + devinfo = malloc(1024, M_USB, M_NOWAIT); + if (devinfo == NULL) { + device_printf(parent, "Can't allocate memory for probe string\n"); + return (USBD_NOMEM); + } + bdev = device_add_child(parent, NULL, -1); + if (!bdev) { + free(devinfo, M_USB); + device_printf(parent, "Device creation failed\n"); + return (USBD_INVAL); + } + uaap = malloc(sizeof(uaa), M_USB, M_NOWAIT); + if (uaap == NULL) { + free(devinfo, M_USB); + return (USBD_INVAL); + } + device_set_ivars(bdev, uaap); + uaa.device = dev; + uaa.iface = NULL; + uaa.ifaces = NULL; + uaa.nifaces = 0; + uaa.usegeneric = 0; + uaa.port = port; + uaa.configno = UHUB_UNK_CONFIGURATION; + uaa.ifaceno = UHUB_UNK_INTERFACE; + uaa.vendor = UGETW(dd->idVendor); + uaa.product = UGETW(dd->idProduct); + uaa.release = UGETW(dd->bcdDevice); + uaa.matchlvl = 0; + + /* First try with device specific drivers. */ + DPRINTF(("usbd_probe_and_attach: trying device specific drivers\n")); + + dev->ifacenums = NULL; + dev->subdevs = malloc(2 * sizeof(device_t), M_USB, M_NOWAIT); + if (dev->subdevs == NULL) { + free(devinfo, M_USB); + return (USBD_NOMEM); + } + dev->subdevs[0] = bdev; + dev->subdevs[1] = 0; + *uaap = uaa; + usbd_devinfo(dev, 1, devinfo); + device_set_desc_copy(bdev, devinfo); + if (device_probe_and_attach(bdev) == 0) { + free(devinfo, M_USB); + return (USBD_NORMAL_COMPLETION); + } + + /* + * Free subdevs so we can reallocate it larger for the number of + * interfaces + */ + tmpdv = dev->subdevs; + dev->subdevs = NULL; + free(tmpdv, M_USB); + DPRINTF(("usbd_probe_and_attach: no device specific driver found\n")); + + DPRINTF(("usbd_probe_and_attach: looping over %d configurations\n", + dd->bNumConfigurations)); + /* Next try with interface drivers. */ + for (confi = 0; confi < dd->bNumConfigurations; confi++) { + DPRINTFN(1,("usbd_probe_and_attach: trying config idx=%d\n", + confi)); + err = usbd_set_config_index(dev, confi, 1); + if (err) { +#ifdef USB_DEBUG + DPRINTF(("%s: port %d, set config at addr %d failed, " + "error=%s\n", device_get_nameunit(parent), port, + addr, usbd_errstr(err))); +#else + printf("%s: port %d, set config at addr %d failed\n", + device_get_nameunit(parent), port, addr); +#endif + free(devinfo, M_USB); + return (err); + } + nifaces = dev->cdesc->bNumInterface; + uaa.configno = dev->cdesc->bConfigurationValue; + for (i = 0; i < nifaces; i++) + ifaces[i] = &dev->ifaces[i]; + uaa.ifaces = ifaces; + uaa.nifaces = nifaces; + dev->subdevs = malloc((nifaces+1) * sizeof(device_t), M_USB,M_NOWAIT); + if (dev->subdevs == NULL) { + free(devinfo, M_USB); + return (USBD_NOMEM); + } + dev->ifacenums = malloc((nifaces) * sizeof(*dev->ifacenums), + M_USB,M_NOWAIT); + if (dev->ifacenums == NULL) { + free(devinfo, M_USB); + return (USBD_NOMEM); + } + + found = 0; + for (i = 0; i < nifaces; i++) { + if (ifaces[i] == NULL) + continue; /* interface already claimed */ + uaa.iface = ifaces[i]; + uaa.ifaceno = ifaces[i]->idesc->bInterfaceNumber; + dev->subdevs[found] = bdev; + dev->subdevs[found + 1] = 0; + dev->ifacenums[found] = i; + *uaap = uaa; + usbd_devinfo(dev, 1, devinfo); + device_set_desc_copy(bdev, devinfo); + if (device_probe_and_attach(bdev) == 0) { + ifaces[i] = NULL; /* consumed */ + found++; + /* create another child for the next iface */ + bdev = device_add_child(parent, NULL, -1); + if (!bdev) { + device_printf(parent, + "Device add failed\n"); + free(devinfo, M_USB); + return (USBD_NORMAL_COMPLETION); + } + uaap = malloc(sizeof(uaa), M_USB, M_NOWAIT); + if (uaap == NULL) { + free(devinfo, M_USB); + return (USBD_NOMEM); + } + device_set_ivars(bdev, uaap); + } else { + dev->subdevs[found] = 0; + } + } + if (found != 0) { + /* remove the last created child. It is unused */ + free(uaap, M_USB); + free(devinfo, M_USB); + device_delete_child(parent, bdev); + /* free(uaap, M_USB); */ /* May be needed? xxx */ + return (USBD_NORMAL_COMPLETION); + } + tmpdv = dev->subdevs; + dev->subdevs = NULL; + free(tmpdv, M_USB); + free(dev->ifacenums, M_USB); + dev->ifacenums = NULL; + } + /* No interfaces were attached in any of the configurations. */ + + if (dd->bNumConfigurations > 1) /* don't change if only 1 config */ + usbd_set_config_index(dev, 0, 0); + + DPRINTF(("usbd_probe_and_attach: no interface drivers found\n")); + + /* Finally try the generic driver. */ + uaa.iface = NULL; + uaa.usegeneric = 1; + uaa.configno = UHUB_UNK_CONFIGURATION; + uaa.ifaceno = UHUB_UNK_INTERFACE; + dev->subdevs = malloc(2 * sizeof(device_t), M_USB, M_NOWAIT); + if (dev->subdevs == 0) { + free(devinfo, M_USB); + return (USBD_NOMEM); + } + dev->subdevs[0] = bdev; + dev->subdevs[1] = 0; + *uaap = uaa; + usbd_devinfo(dev, 1, devinfo); + device_set_desc_copy(bdev, devinfo); + free(devinfo, M_USB); + if (device_probe_and_attach(bdev) == 0) + return (USBD_NORMAL_COMPLETION); + + /* + * The generic attach failed, but leave the device as it is. + * We just did not find any drivers, that's all. The device is + * fully operational and not harming anyone. + */ + DPRINTF(("usbd_probe_and_attach: generic attach failed\n")); + return (USBD_NORMAL_COMPLETION); +} + + +/* + * 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 attach a driver. + */ +usbd_status +usbd_new_device(device_t parent, usbd_bus_handle bus, int depth, + int speed, int port, struct usbd_port *up) +{ + usbd_device_handle dev, adev; + struct usbd_device *hub; + usb_device_descriptor_t *dd; + usb_port_status_t ps; + usbd_status err; + int addr; + int i; + int p; + + DPRINTF(("usbd_new_device bus=%p port=%d depth=%d speed=%d\n", + bus, port, depth, speed)); + addr = usbd_getnewaddr(bus); + if (addr < 0) { + device_printf(bus->bdev, "No free USB addresses\n"); + return (USBD_NO_ADDR); + } + + dev = malloc(sizeof *dev, M_USB, M_NOWAIT|M_ZERO); + if (dev == NULL) + return (USBD_NOMEM); + + dev->bus = bus; + + /* Set up default endpoint handle. */ + dev->def_ep.edesc = &dev->def_ep_desc; + + /* Set up default endpoint descriptor. */ + dev->def_ep_desc.bLength = USB_ENDPOINT_DESCRIPTOR_SIZE; + dev->def_ep_desc.bDescriptorType = UDESC_ENDPOINT; + dev->def_ep_desc.bEndpointAddress = USB_CONTROL_ENDPOINT; + dev->def_ep_desc.bmAttributes = UE_CONTROL; + USETW(dev->def_ep_desc.wMaxPacketSize, USB_MAX_IPACKET); + dev->def_ep_desc.bInterval = 0; + + dev->quirks = &usbd_no_quirk; + dev->address = USB_START_ADDR; + dev->ddesc.bMaxPacketSize = 0; + dev->depth = depth; + dev->powersrc = up; + dev->myhub = up->parent; + + up->device = dev; + + if (up->parent && speed > up->parent->speed) { +#ifdef USB_DEBUG + printf("%s: maxium speed of attached " + "device, %d, is higher than speed " + "of parent HUB, %d.\n", + __FUNCTION__, speed, up->parent->speed); +#endif + /* + * Reduce the speed, otherwise we won't setup the + * proper transfer methods. + */ + speed = up->parent->speed; + } + + /* Locate port on upstream high speed hub */ + for (adev = dev, hub = up->parent; + hub != NULL && hub->speed != USB_SPEED_HIGH; + adev = hub, hub = hub->myhub) + ; + if (hub) { + for (p = 0; p < hub->hub->hubdesc.bNbrPorts; p++) { + if (hub->hub->ports[p].device == adev) { + dev->myhsport = &hub->hub->ports[p]; + goto found; + } + } + panic("usbd_new_device: cannot find HS port\n"); + found: + DPRINTFN(1,("usbd_new_device: high speed port %d\n", p)); + } else { + dev->myhsport = NULL; + } + dev->speed = speed; + dev->langid = USBD_NOLANG; + dev->cookie.cookie = ++usb_cookie_no; + + /* Establish the default pipe. */ + err = usbd_setup_pipe(dev, 0, &dev->def_ep, USBD_DEFAULT_INTERVAL, + &dev->default_pipe); + if (err) { + usbd_remove_device(dev, up); + return (err); + } + + dd = &dev->ddesc; + /* Try a few times in case the device is slow (i.e. outside specs.) */ + DPRINTFN(5,("usbd_new_device: setting device address=%d\n", addr)); + for (i = 0; i < 15; i++) { + /* Get the first 8 bytes of the device descriptor. */ + err = usbd_get_desc(dev, UDESC_DEVICE, 0, USB_MAX_IPACKET, dd); + if (!err) + break; + usbd_delay_ms(dev, 200); + if ((i & 3) == 3) { + DPRINTFN(-1,("usb_new_device: set address %d " + "failed - trying a port reset\n", addr)); + usbd_reset_port(up->parent, port, &ps); + } + + } + if (err) { + DPRINTFN(-1, ("usbd_new_device: addr=%d, getting first desc " + "failed\n", addr)); + usbd_remove_device(dev, up); + return (err); + } + + if (speed == USB_SPEED_HIGH) { + /* Max packet size must be 64 (sec 5.5.3). */ + if (dd->bMaxPacketSize != USB_2_MAX_CTRL_PACKET) { +#ifdef DIAGNOSTIC + printf("usbd_new_device: addr=%d bad max packet size\n", + addr); +#endif + dd->bMaxPacketSize = USB_2_MAX_CTRL_PACKET; + } + } + + DPRINTF(("usbd_new_device: adding unit addr=%d, rev=%02x, class=%d, " + "subclass=%d, protocol=%d, maxpacket=%d, len=%d, speed=%d\n", + addr,UGETW(dd->bcdUSB), dd->bDeviceClass, dd->bDeviceSubClass, + dd->bDeviceProtocol, dd->bMaxPacketSize, dd->bLength, + dev->speed)); + + if (dd->bDescriptorType != UDESC_DEVICE) { + /* Illegal device descriptor */ + DPRINTFN(-1,("usbd_new_device: illegal descriptor %d\n", + dd->bDescriptorType)); + usbd_remove_device(dev, up); + return (USBD_INVAL); + } + + if (dd->bLength < USB_DEVICE_DESCRIPTOR_SIZE) { + DPRINTFN(-1,("usbd_new_device: bad length %d\n", dd->bLength)); + usbd_remove_device(dev, up); + return (USBD_INVAL); + } + + USETW(dev->def_ep_desc.wMaxPacketSize, dd->bMaxPacketSize); + + /* Re-establish the default pipe with the new max packet size. */ + usbd_kill_pipe(dev->default_pipe); + err = usbd_setup_pipe(dev, 0, &dev->def_ep, USBD_DEFAULT_INTERVAL, + &dev->default_pipe); + if (err) { + usbd_remove_device(dev, up); + return (err); + } + + err = usbd_reload_device_desc(dev); + if (err) { + DPRINTFN(-1, ("usbd_new_device: addr=%d, getting full desc " + "failed\n", addr)); + usbd_remove_device(dev, up); + return (err); + } + + /* Set the address */ + DPRINTFN(5,("usbd_new_device: setting device address=%d\n", addr)); + err = usbd_set_address(dev, addr); + if (err) { + DPRINTFN(-1,("usb_new_device: set address %d failed\n", addr)); + err = USBD_SET_ADDR_FAILED; + usbd_remove_device(dev, up); + return (err); + } + + /* Allow device time to set new address */ + usbd_delay_ms(dev, USB_SET_ADDRESS_SETTLE); + dev->address = addr; /* New device address now */ + bus->devices[addr] = dev; + + /* Re-establish the default pipe with the new address. */ + usbd_kill_pipe(dev->default_pipe); + err = usbd_setup_pipe(dev, 0, &dev->def_ep, USBD_DEFAULT_INTERVAL, + &dev->default_pipe); + if (err) { + usbd_remove_device(dev, up); + return (err); + } + + /* Assume 100mA bus powered for now. Changed when configured. */ + dev->power = USB_MIN_POWER; + dev->self_powered = 0; + + DPRINTF(("usbd_new_device: new dev (addr %d), dev=%p, parent=%p\n", + addr, dev, parent)); + + err = usbd_probe_and_attach(parent, dev, port, addr); + if (err) { + usbd_remove_device(dev, up); + return (err); + } + + usbd_add_dev_event(USB_EVENT_DEVICE_ATTACH, dev); + + return (USBD_NORMAL_COMPLETION); +} + +usbd_status +usbd_reload_device_desc(usbd_device_handle dev) +{ + usbd_status err; + int i; + + /* Get the full device descriptor. */ + for (i = 0; i < 3; ++i) { + err = usbd_get_device_desc(dev, &dev->ddesc); + if (!err) + break; + usbd_delay_ms(dev, 200); + } + if (err) + return (err); + + /* Figure out what's wrong with this device. */ + dev->quirks = usbd_find_quirk(&dev->ddesc); + + return (USBD_NORMAL_COMPLETION); +} + +void +usbd_remove_device(usbd_device_handle dev, struct usbd_port *up) +{ + DPRINTF(("usbd_remove_device: %p\n", dev)); + + if (dev->default_pipe != NULL) + usbd_kill_pipe(dev->default_pipe); + up->device = NULL; + dev->bus->devices[dev->address] = NULL; + + free(dev, M_USB); +} + +void +usbd_fill_deviceinfo(usbd_device_handle dev, struct usb_device_info *di, + int usedev) +{ + struct usbd_port *p; + int i, err, s; + + di->udi_bus = device_get_unit(dev->bus->bdev); + di->udi_addr = dev->address; + di->udi_cookie = dev->cookie; + usbd_devinfo_vp(dev, di->udi_vendor, di->udi_product, usedev); + usbd_printBCD(di->udi_release, UGETW(dev->ddesc.bcdDevice)); + di->udi_vendorNo = UGETW(dev->ddesc.idVendor); + di->udi_productNo = UGETW(dev->ddesc.idProduct); + di->udi_releaseNo = UGETW(dev->ddesc.bcdDevice); + di->udi_class = dev->ddesc.bDeviceClass; + di->udi_subclass = dev->ddesc.bDeviceSubClass; + di->udi_protocol = dev->ddesc.bDeviceProtocol; + di->udi_config = dev->config; + di->udi_power = dev->self_powered ? 0 : dev->power; + di->udi_speed = dev->speed; + + if (dev->subdevs != NULL) { + for (i = 0; dev->subdevs[i] && i < USB_MAX_DEVNAMES; i++) { + if (device_is_attached(dev->subdevs[i])) + strlcpy(di->udi_devnames[i], + device_get_nameunit(dev->subdevs[i]), + USB_MAX_DEVNAMELEN); + else + di->udi_devnames[i][0] = 0; + } + } else { + i = 0; + } + for (/*i is set */; i < USB_MAX_DEVNAMES; i++) + di->udi_devnames[i][0] = 0; /* empty */ + + if (dev->hub) { + for (i = 0; + i < sizeof(di->udi_ports) / sizeof(di->udi_ports[0]) && + i < dev->hub->hubdesc.bNbrPorts; + i++) { + p = &dev->hub->ports[i]; + if (p->device) + err = p->device->address; + else { + s = UGETW(p->status.wPortStatus); + if (s & UPS_PORT_ENABLED) + err = USB_PORT_ENABLED; + else if (s & UPS_SUSPEND) + err = USB_PORT_SUSPENDED; + else if (s & UPS_PORT_POWER) + err = USB_PORT_POWERED; + else + err = USB_PORT_DISABLED; + } + di->udi_ports[i] = err; + } + di->udi_nports = dev->hub->hubdesc.bNbrPorts; + } else + di->udi_nports = 0; +} + +void +usb_free_device(usbd_device_handle dev) +{ + int ifcidx, nifc; + + if (dev->default_pipe != NULL) + usbd_kill_pipe(dev->default_pipe); + if (dev->ifaces != NULL) { + nifc = dev->cdesc->bNumInterface; + for (ifcidx = 0; ifcidx < nifc; ifcidx++) + usbd_free_iface_data(dev, ifcidx); + free(dev->ifaces, M_USB); + } + if (dev->cdesc != NULL) + free(dev->cdesc, M_USB); + if (dev->subdevs != NULL) + free(dev->subdevs, M_USB); + if (dev->ifacenums != NULL) + free(dev->ifacenums, M_USB); + free(dev, M_USB); +} + +/* + * The general mechanism for detaching drivers works as follows: Each + * driver is responsible for maintaining a reference count on the + * number of outstanding references to its softc (e.g. from + * processing hanging in a read or write). The detach method of the + * driver decrements this counter and flags in the softc that the + * driver is dying and then wakes any sleepers. It then sleeps on the + * softc. Each place that can sleep must maintain the reference + * count. When the reference count drops to -1 (0 is the normal value + * of the reference count) the a wakeup on the softc is performed + * signaling to the detach waiter that all references are gone. + */ + +/* + * Called from process context when we discover that a port has + * been disconnected. + */ +void +usb_disconnect_port(struct usbd_port *up, device_t parent) +{ + usbd_device_handle dev = up->device; + const char *hubname = device_get_nameunit(parent); + int i; + + DPRINTFN(3,("uhub_disconnect: up=%p dev=%p port=%d\n", + up, dev, up->portno)); + +#ifdef DIAGNOSTIC + if (dev == NULL) { + printf("usb_disconnect_port: no device\n"); + return; + } +#endif + + if (dev->subdevs != NULL) { + DPRINTFN(3,("usb_disconnect_port: disconnect subdevs\n")); + for (i = 0; dev->subdevs[i]; i++) { + if (!device_is_quiet(dev->subdevs[i])) { + device_printf(dev->subdevs[i], + "at %s", hubname); + if (up->portno != 0) + printf(" port %d", up->portno); + printf(" (addr %d) disconnected\n", dev->address); + } + + struct usb_attach_arg *uaap = + device_get_ivars(dev->subdevs[i]); + device_detach(dev->subdevs[i]); + free(uaap, M_USB); + device_delete_child(device_get_parent(dev->subdevs[i]), + dev->subdevs[i]); + dev->subdevs[i] = NULL; + } + } + + usbd_add_dev_event(USB_EVENT_DEVICE_DETACH, dev); + dev->bus->devices[dev->address] = NULL; + up->device = NULL; + usb_free_device(dev); +} diff --git a/sys/legacy/dev/usb/usbcdc.h b/sys/legacy/dev/usb/usbcdc.h new file mode 100644 index 0000000..d684108 --- /dev/null +++ b/sys/legacy/dev/usb/usbcdc.h @@ -0,0 +1,188 @@ +/* $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 _USBCDC_H_ +#define _USBCDC_H_ + +#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 + +typedef struct { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uWord bcdCDC; +} UPACKED usb_cdc_header_descriptor_t; + +typedef struct { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bmCapabilities; +#define USB_CDC_CM_DOES_CM 0x01 +#define USB_CDC_CM_OVER_DATA 0x02 + uByte bDataInterface; +} UPACKED usb_cdc_cm_descriptor_t; + +typedef struct { + 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 +} UPACKED usb_cdc_acm_descriptor_t; + +typedef struct { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte bMasterInterface; + uByte bSlaveInterface[1]; +} UPACKED usb_cdc_union_descriptor_t; + +typedef struct { + uByte bLength; + uByte bDescriptorType; + uByte bDescriptorSubtype; + uByte iMacAddress; + uDWord bmEthernetStatistics; + uWord wMaxSegmentSize; + uWord wNumberMCFikters; + uByte bNumberPowerFilters; +} UPACKED usb_cdc_ethernet_descriptor_t; + +#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 + +typedef struct { + uWord wState; +#define UCDC_IDLE_SETTING 0x0001 +#define UCDC_DATA_MULTIPLEXED 0x0002 +} UPACKED usb_cdc_abstract_state_t; +#define UCDC_ABSTRACT_STATE_LENGTH 2 + +typedef struct { + 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; +} UPACKED usb_cdc_line_state_t; +#define UCDC_LINE_STATE_LENGTH 7 + +typedef struct { + 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]; +} UPACKED usb_cdc_notification_t; +#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 + +#endif /* _USBCDC_H_ */ diff --git a/sys/legacy/dev/usb/usbdevs b/sys/legacy/dev/usb/usbdevs new file mode 100644 index 0000000..0a3d85e --- /dev/null +++ b/sys/legacy/dev/usb/usbdevs @@ -0,0 +1,2527 @@ +$FreeBSD$ +/* $NetBSD: usbdevs,v 1.392 2004/12/29 08:38:44 imp Exp $ */ + +/*- + * Copyright (c) 1998-2004 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) 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. + */ + +/* + * List of known USB vendors + * + * USB.org publishes a VID list of USB-IF member companies at + * http://www.usb.org/developers/tools + * Note that it does not show companies that have obtained a Vendor ID + * without becoming full members. + * + * Please note that these IDs do not do anything. Adding an ID here and + * regenerating the usbdevs.h and usbdevs_data.h only makes a symbolic name + * available to the source code and does not change any functionality, nor + * does it make your device available to a specific driver. + * It will however make the descriptive string available if a device does not + * provide the string itself. + * + * After adding a vendor ID VNDR and a product ID PRDCT you will have the + * following extra defines: + * #define USB_VENDOR_VNDR 0x???? + * #define USB_PRODUCT_VNDR_PRDCT 0x???? + * + * You may have to add these defines to the respective probe routines to + * make the device recognised by the appropriate device driver. + */ + +vendor UNKNOWN1 0x0053 Unknown vendor +vendor UNKNOWN2 0x0105 Unknown vendor +vendor EGALAX2 0x0123 eGalax, Inc. +vendor HUMAX 0x02ad HUMAX +vendor LTS 0x0386 LTS +vendor BWCT 0x03da Bernd Walter Computer Technology +vendor AOX 0x03e8 AOX +vendor THESYS 0x03e9 Thesys +vendor DATABROADCAST 0x03ea Data Broadcasting +vendor ATMEL 0x03eb Atmel +vendor IWATSU 0x03ec Iwatsu America +vendor MITSUMI 0x03ee Mitsumi +vendor HP 0x03f0 Hewlett Packard +vendor GENOA 0x03f1 Genoa +vendor OAK 0x03f2 Oak +vendor ADAPTEC 0x03f3 Adaptec +vendor DIEBOLD 0x03f4 Diebold +vendor SIEMENSELECTRO 0x03f5 Siemens Electromechanical +vendor EPSONIMAGING 0x03f8 Epson Imaging +vendor KEYTRONIC 0x03f9 KeyTronic +vendor OPTI 0x03fb OPTi +vendor ELITEGROUP 0x03fc Elitegroup +vendor XILINX 0x03fd Xilinx +vendor FARALLON 0x03fe Farallon Communications +vendor NATIONAL 0x0400 National Semiconductor +vendor NATIONALREG 0x0401 National Registry +vendor ACERLABS 0x0402 Acer Labs +vendor FTDI 0x0403 Future Technology Devices +vendor NCR 0x0404 NCR +vendor SYNOPSYS2 0x0405 Synopsys +vendor FUJITSUICL 0x0406 Fujitsu-ICL +vendor FUJITSU2 0x0407 Fujitsu Personal Systems +vendor QUANTA 0x0408 Quanta +vendor NEC 0x0409 NEC +vendor KODAK 0x040a Eastman Kodak +vendor WELTREND 0x040b Weltrend +vendor VIA 0x040d VIA +vendor MCCI 0x040e MCCI +vendor MELCO 0x0411 Melco +vendor LEADTEK 0x0413 Leadtek +vendor WINBOND 0x0416 Winbond +vendor PHOENIX 0x041a Phoenix +vendor CREATIVE 0x041e Creative Labs +vendor NOKIA 0x0421 Nokia +vendor ADI 0x0422 ADI Systems +vendor CATC 0x0423 Computer Access Technology +vendor SMC2 0x0424 Standard Microsystems +vendor MOTOROLA_HK 0x0425 Motorola HK +vendor GRAVIS 0x0428 Advanced Gravis Computer +vendor CIRRUSLOGIC 0x0429 Cirrus Logic +vendor INNOVATIVE 0x042c Innovative Semiconductors +vendor MOLEX 0x042f Molex +vendor SUN 0x0430 Sun Microsystems +vendor UNISYS 0x0432 Unisys +vendor TAUGA 0x0436 Taugagreining HF +vendor AMD 0x0438 Advanced Micro Devices +vendor LEXMARK 0x043d Lexmark International +vendor LG 0x043e LG Electronics +vendor NANAO 0x0440 NANAO +vendor GATEWAY 0x0443 Gateway 2000 +vendor NMB 0x0446 NMB +vendor ALPS 0x044e Alps Electric +vendor THRUST 0x044f Thrustmaster +vendor TI 0x0451 Texas Instruments +vendor ANALOGDEVICES 0x0456 Analog Devices +vendor SIS 0x0457 Silicon Integrated Systems Corp. +vendor KYE 0x0458 KYE Systems +vendor DIAMOND2 0x045a Diamond (Supra) +vendor RENESAS 0x045b Renesas +vendor MICROSOFT 0x045e Microsoft +vendor PRIMAX 0x0461 Primax Electronics +vendor MGE 0x0463 MGE UPS Systems +vendor AMP 0x0464 AMP +vendor CHERRY 0x046a Cherry Mikroschalter +vendor MEGATRENDS 0x046b American Megatrends +vendor LOGITECH 0x046d Logitech +vendor BTC 0x046e Behavior Tech. Computer +vendor PHILIPS 0x0471 Philips +vendor SUN2 0x0472 Sun Microsystems (offical) +vendor SANYO 0x0474 Sanyo Electric +vendor SEAGATE 0x0477 Seagate +vendor CONNECTIX 0x0478 Connectix +vendor SEMTECH 0x047a Semtech +vendor KENSINGTON 0x047d Kensington +vendor LUCENT 0x047e Lucent +vendor PLANTRONICS 0x047f Plantronics +vendor KYOCERA 0x0482 Kyocera Wireless Corp. +vendor STMICRO 0x0483 STMicroelectronics +vendor FOXCONN 0x0489 Foxconn +vendor MEIZU 0x0492 Meizu Electronics +vendor YAMAHA 0x0499 YAMAHA +vendor COMPAQ 0x049f Compaq +vendor HITACHI 0x04a4 Hitachi +vendor ACERP 0x04a5 Acer Peripherals +vendor DAVICOM 0x04a6 Davicom +vendor VISIONEER 0x04a7 Visioneer +vendor CANON 0x04a9 Canon +vendor NIKON 0x04b0 Nikon +vendor PAN 0x04b1 Pan International +vendor IBM 0x04b3 IBM +vendor CYPRESS 0x04b4 Cypress Semiconductor +vendor ROHM 0x04b5 ROHM +vendor COMPAL 0x04b7 Compal +vendor EPSON 0x04b8 Seiko Epson +vendor RAINBOW 0x04b9 Rainbow Technologies +vendor IODATA 0x04bb I-O Data +vendor TDK 0x04bf TDK +vendor 3COMUSR 0x04c1 U.S. Robotics +vendor METHODE 0x04c2 Methode Electronics Far East +vendor MAXISWITCH 0x04c3 Maxi Switch +vendor LOCKHEEDMER 0x04c4 Lockheed Martin Energy Research +vendor FUJITSU 0x04c5 Fujitsu +vendor TOSHIBAAM 0x04c6 Toshiba America +vendor MICROMACRO 0x04c7 Micro Macro Technologies +vendor KONICA 0x04c8 Konica +vendor LITEON 0x04ca Lite-On Technology +vendor FUJIPHOTO 0x04cb Fuji Photo Film +vendor PHILIPSSEMI 0x04cc Philips Semiconductors +vendor TATUNG 0x04cd Tatung Co. Of America +vendor SCANLOGIC 0x04ce ScanLogic +vendor MYSON 0x04cf Myson Technology +vendor DIGI2 0x04d0 Digi +vendor ITTCANON 0x04d1 ITT Canon +vendor ALTEC 0x04d2 Altec Lansing +vendor LSI 0x04d4 LSI +vendor MENTORGRAPHICS 0x04d6 Mentor Graphics +vendor ITUNERNET 0x04d8 I-Tuner Networks +vendor HOLTEK 0x04d9 Holtek Semiconductor, Inc. +vendor PANASONIC 0x04da Panasonic (Matsushita) +vendor HUANHSIN 0x04dc Huan Hsin +vendor SHARP 0x04dd Sharp +vendor IIYAMA 0x04e1 Iiyama +vendor SHUTTLE 0x04e6 Shuttle Technology +vendor ELO 0x04e7 Elo TouchSystems +vendor SAMSUNG 0x04e8 Samsung Electronics +vendor NORTHSTAR 0x04eb Northstar +vendor TOKYOELECTRON 0x04ec Tokyo Electron +vendor ANNABOOKS 0x04ed Annabooks +vendor JVC 0x04f1 JVC +vendor CHICONY 0x04f2 Chicony Electronics +vendor ELAN 0x04f3 Elan +vendor NEWNEX 0x04f7 Newnex +vendor BROTHER 0x04f9 Brother Industries +vendor DALLAS 0x04fa Dallas Semiconductor +vendor AIPTEK2 0x04fc AIPTEK International +vendor PFU 0x04fe PFU +vendor FUJIKURA 0x0501 Fujikura/DDK +vendor ACER 0x0502 Acer +vendor 3COM 0x0506 3Com +vendor HOSIDEN 0x0507 Hosiden Corporation +vendor AZTECH 0x0509 Aztech Systems +vendor BELKIN 0x050d Belkin Components +vendor KAWATSU 0x050f Kawatsu Semiconductor +vendor FCI 0x0514 FCI +vendor LONGWELL 0x0516 Longwell +vendor COMPOSITE 0x0518 Composite +vendor STAR 0x0519 Star Micronics +vendor APC 0x051d American Power Conversion +vendor SCIATLANTA 0x051e Scientific Atlanta +vendor TSM 0x0520 TSM +vendor CONNECTEK 0x0522 Advanced Connectek USA +vendor NETCHIP 0x0525 NetChip Technology +vendor ALTRA 0x0527 ALTRA +vendor ATI 0x0528 ATI Technologies +vendor AKS 0x0529 Aladdin Knowledge Systems +vendor TEKOM 0x052b Tekom +vendor CANONDEV 0x052c Canon +vendor WACOMTECH 0x0531 Wacom +vendor INVENTEC 0x0537 Inventec +vendor SHYHSHIUN 0x0539 Shyh Shiun Terminals +vendor PREHWERKE 0x053a Preh Werke Gmbh & Co. KG +vendor SYNOPSYS 0x053f Synopsys +vendor UNIACCESS 0x0540 Universal Access +vendor VIEWSONIC 0x0543 ViewSonic +vendor XIRLINK 0x0545 Xirlink +vendor ANCHOR 0x0547 Anchor Chips +vendor SONY 0x054c Sony +vendor FUJIXEROX 0x0550 Fuji Xerox +vendor VISION 0x0553 VLSI Vision +vendor ASAHIKASEI 0x0556 Asahi Kasei Microsystems +vendor ATEN 0x0557 ATEN International +vendor SAMSUNG2 0x055d Samsung Electronics +vendor MUSTEK 0x055f Mustek Systems +vendor TELEX 0x0562 Telex Communications +vendor CHINON 0x0564 Chinon +vendor PERACOM 0x0565 Peracom Networks +vendor ALCOR2 0x0566 Alcor Micro +vendor XYRATEX 0x0567 Xyratex +vendor WACOM 0x056a WACOM +vendor ETEK 0x056c e-TEK Labs +vendor EIZO 0x056d EIZO +vendor ELECOM 0x056e Elecom +vendor CONEXANT 0x0572 Conexant +vendor HAUPPAUGE 0x0573 Hauppauge Computer Works +vendor BAFO 0x0576 BAFO/Quality Computer Accessories +vendor YEDATA 0x057b Y-E Data +vendor AVM 0x057c AVM +vendor QUICKSHOT 0x057f Quickshot +vendor ROLAND 0x0582 Roland +vendor ROCKFIRE 0x0583 Rockfire +vendor RATOC 0x0584 RATOC Systems +vendor ZYXEL 0x0586 ZyXEL Communication +vendor INFINEON 0x058b Infineon +vendor MICREL 0x058d Micrel +vendor ALCOR 0x058f Alcor Micro +vendor OMRON 0x0590 OMRON +vendor ZORAN 0x0595 Zoran Microelectronics +vendor NIIGATA 0x0598 Niigata +vendor IOMEGA 0x059b Iomega +vendor ATREND 0x059c A-Trend Technology +vendor AID 0x059d Advanced Input Devices +vendor LACIE 0x059f LaCie +vendor FUJIFILM 0x05a2 Fuji Film +vendor ARC 0x05a3 ARC +vendor ORTEK 0x05a4 Ortek +vendor BOSE 0x05a7 Bose +vendor OMNIVISION 0x05a9 OmniVision +vendor INSYSTEM 0x05ab In-System Design +vendor APPLE 0x05ac Apple Computer +vendor YCCABLE 0x05ad Y.C. Cable +vendor DIGITALPERSONA 0x05ba DigitalPersona +vendor 3G 0x05bc 3G Green Green Globe +vendor RAFI 0x05bd RAFI +vendor TYCO 0x05be Tyco +vendor KAWASAKI 0x05c1 Kawasaki +vendor DIGI 0x05c5 Digi International +vendor QUALCOMM2 0x05c6 Qualcomm +vendor QTRONIX 0x05c7 Qtronix +vendor FOXLINK 0x05c8 Foxlink +vendor RICOH 0x05ca Ricoh +vendor ELSA 0x05cc ELSA +vendor SCIWORX 0x05ce sci-worx +vendor BRAINBOXES 0x05d1 Brainboxes Limited +vendor ULTIMA 0x05d8 Ultima +vendor AXIOHM 0x05d9 Axiohm Transaction Solutions +vendor MICROTEK 0x05da Microtek +vendor SUNTAC 0x05db SUN Corporation +vendor LEXAR 0x05dc Lexar Media +vendor ADDTRON 0x05dd Addtron +vendor SYMBOL 0x05e0 Symbol Technologies +vendor SYNTEK 0x05e1 Syntek +vendor GENESYS 0x05e3 Genesys Logic +vendor FUJI 0x05e5 Fuji Electric +vendor KEITHLEY 0x05e6 Keithley Instruments +vendor EIZONANAO 0x05e7 EIZO Nanao +vendor KLSI 0x05e9 Kawasaki LSI +vendor FFC 0x05eb FFC +vendor ANKO 0x05ef Anko Electronic +vendor PIENGINEERING 0x05f3 P.I. Engineering +vendor AOC 0x05f6 AOC International +vendor CHIC 0x05fe Chic Technology +vendor BARCO 0x0600 Barco Display Systems +vendor BRIDGE 0x0607 Bridge Information +vendor SOLIDYEAR 0x060b Solid Year +vendor BIORAD 0x0614 Bio-Rad Laboratories +vendor MACALLY 0x0618 Macally +vendor ACTLABS 0x061c Act Labs +vendor ALARIS 0x0620 Alaris +vendor APEX 0x0624 Apex +vendor CREATIVE3 0x062a Creative Labs +vendor VIVITAR 0x0636 Vivitar +vendor GUNZE 0x0637 Gunze Electronics USA +vendor AVISION 0x0638 Avision +vendor TEAC 0x0644 TEAC +vendor SGI 0x065e Silicon Graphics +vendor SANWASUPPLY 0x0663 Sanwa Supply +vendor LINKSYS 0x066b Linksys +vendor ACERSA 0x066e Acer Semiconductor America +vendor SIGMATEL 0x066f Sigmatel +vendor DRAYTEK 0x0675 DrayTek +vendor AIWA 0x0677 Aiwa +vendor ACARD 0x0678 ACARD Technology +vendor PROLIFIC 0x067b Prolific Technology +vendor SIEMENS 0x067c Siemens +vendor AVANCELOGIC 0x0680 Avance Logic +vendor SIEMENS2 0x0681 Siemens +vendor MINOLTA 0x0686 Minolta +vendor CHPRODUCTS 0x068e CH Products +vendor HAGIWARA 0x0693 Hagiwara Sys-Com +vendor CTX 0x0698 Chuntex +vendor ASKEY 0x069a Askey Computer +vendor SAITEK 0x06a3 Saitek +vendor ALCATELT 0x06b9 Alcatel Telecom +vendor AGFA 0x06bd AGFA-Gevaert +vendor ASIAMD 0x06be Asia Microelectronic Development +vendor BIZLINK 0x06c4 Bizlink International +vendor KEYSPAN 0x06cd Keyspan / InnoSys Inc. +vendor AASHIMA 0x06d6 Aashima Technology +vendor MULTITECH 0x06e0 MultiTech +vendor ADS 0x06e1 ADS Technologies +vendor ALCATELM 0x06e4 Alcatel Microelectronics +vendor SIRIUS 0x06ea Sirius Technologies +vendor GUILLEMOT 0x06f8 Guillemot +vendor BOSTON 0x06fd Boston Acoustics +vendor SMC 0x0707 Standard Microsystems +vendor PUTERCOM 0x0708 Putercom +vendor MCT 0x0711 MCT +vendor IMATION 0x0718 Imation +vendor SONYERICSSON 0x0731 Sony Ericsson +vendor EICON 0x0734 Eicon Networks +vendor SYNTECH 0x0745 Syntech Information +vendor DIGITALSTREAM 0x074e Digital Stream +vendor AUREAL 0x0755 Aureal Semiconductor +vendor MIDIMAN 0x0763 Midiman +vendor CYBERPOWER 0x0764 Cyber Power Systems, Inc. +vendor SURECOM 0x0769 Surecom Technology +vendor LINKSYS2 0x077b Linksys +vendor GRIFFIN 0x077d Griffin Technology +vendor SANDISK 0x0781 SanDisk +vendor JENOPTIK 0x0784 Jenoptik +vendor LOGITEC 0x0789 Logitec +vendor BRIMAX 0x078e Brimax +vendor AXIS 0x0792 Axis Communications +vendor ABL 0x0794 ABL Electronics +vendor SAGEM 0x079b Sagem +vendor SUNCOMM 0x079c Sun Communications, Inc. +vendor ALFADATA 0x079d Alfadata Computer +vendor NATIONALTECH 0x07a2 National Technical Systems +vendor ONNTO 0x07a3 Onnto +vendor BE 0x07a4 Be +vendor ADMTEK 0x07a6 ADMtek +vendor COREGA 0x07aa Corega +vendor FREECOM 0x07ab Freecom +vendor MICROTECH 0x07af Microtech +vendor GENERALINSTMNTS 0x07b2 General Instruments (Motorola) +vendor OLYMPUS 0x07b4 Olympus +vendor ABOCOM 0x07b8 AboCom Systems +vendor KEISOKUGIKEN 0x07c1 Keisokugiken +vendor ONSPEC 0x07c4 OnSpec +vendor APG 0x07c5 APG Cash Drawer +vendor BUG 0x07c8 B.U.G. +vendor ALLIEDTELESYN 0x07c9 Allied Telesyn International +vendor AVERMEDIA 0x07ca AVerMedia Technologies +vendor SIIG 0x07cc SIIG +vendor CASIO 0x07cf CASIO +vendor DLINK2 0x07d1 D-Link +vendor APTIO 0x07d2 Aptio Products +vendor ARASAN 0x07da Arasan Chip Systems +vendor ALLIEDCABLE 0x07e6 Allied Cable +vendor STSN 0x07ef STSN +vendor CENTURY 0x07f7 Century Corp +vendor ZOOM 0x0803 Zoom Telephonics +vendor PCS 0x0810 Personal Communication Systems +vendor BROADLOGIC 0x0827 BroadLogic +vendor HANDSPRING 0x082d Handspring +vendor PALM 0x0830 Palm Computing +vendor SOURCENEXT 0x0833 SOURCENEXT +vendor ACTIONSTAR 0x0835 Action Star Enterprise +vendor SAMSUNG_TECHWIN 0x0839 Samsung Techwin +vendor ACCTON 0x083a Accton Technology +vendor DIAMOND 0x0841 Diamond +vendor NETGEAR 0x0846 BayNETGEAR +vendor TOPRE 0x0853 Topre Corporation +vendor ACTIVEWIRE 0x0854 ActiveWire +vendor BBELECTRONICS 0x0856 B&B Electronics +vendor PORTGEAR 0x085a PortGear +vendor NETGEAR2 0x0864 Netgear +vendor SYSTEMTALKS 0x086e System Talks +vendor METRICOM 0x0870 Metricom +vendor ADESSOKBTEK 0x087c ADESSO/Kbtek America +vendor JATON 0x087d Jaton +vendor APT 0x0880 APT Technologies +vendor BOCARESEARCH 0x0885 Boca Research +vendor ANDREA 0x08a8 Andrea Electronics +vendor BURRBROWN 0x08bb Burr-Brown Japan +vendor 2WIRE 0x08c8 2Wire +vendor AIPTEK 0x08ca AIPTEK International +vendor SMARTBRIDGES 0x08d1 SmartBridges +vendor BILLIONTON 0x08dd Billionton Systems +vendor EXTENDED 0x08e9 Extended Systems +vendor MSYSTEMS 0x08ec M-Systems +vendor AUTHENTEC 0x08ff AuthenTec +vendor AUDIOTECHNICA 0x0909 Audio-Technica +vendor TRUMPION 0x090a Trumpion Microelectronics +vendor FEIYA 0x090c Feiya +vendor ALATION 0x0910 Alation Systems +vendor GLOBESPAN 0x0915 Globespan +vendor CONCORDCAMERA 0x0919 Concord Camera +vendor GARMIN 0x091e Garmin International +vendor GOHUBS 0x0921 GoHubs +vendor XEROX 0x0924 Xerox +vendor BIOMETRIC 0x0929 American Biometric Company +vendor TOSHIBA 0x0930 Toshiba +vendor PLEXTOR 0x093b Plextor +vendor INTREPIDCS 0x093c Intrepid +vendor YANO 0x094f Yano +vendor KINGSTON 0x0951 Kingston Technology +vendor BLUEWATER 0x0956 BlueWater Systems +vendor AGILENT 0x0957 Agilent Technologies +vendor GUDE 0x0959 Gude ADS +vendor PORTSMITH 0x095a Portsmith +vendor ACERW 0x0967 Acer +vendor ADIRONDACK 0x0976 Adirondack Wire & Cable +vendor BECKHOFF 0x0978 Beckhoff +vendor MINDSATWORK 0x097a Minds At Work +vendor POINTCHIPS 0x09a6 PointChips +vendor INTERSIL 0x09aa Intersil +vendor ALTIUS 0x09b3 Altius Solutions +vendor ARRIS 0x09c1 Arris Interactive +vendor ACTIVCARD 0x09c3 ACTIVCARD +vendor ACTISYS 0x09c4 ACTiSYS +vendor NOVATEL2 0x09d7 Novatel Wireless +vendor AFOURTECH 0x09da A-FOUR TECH +vendor AIMEX 0x09dc AIMEX +vendor ADDONICS 0x09df Addonics Technologies +vendor AKAI 0x09e8 AKAI professional M.I. +vendor ARESCOM 0x09f5 ARESCOM +vendor BAY 0x09f9 Bay Associates +vendor ALTERA 0x09fb Altera +vendor CSR 0x0a12 Cambridge Silicon Radio +vendor TREK 0x0a16 Trek Technology +vendor ASAHIOPTICAL 0x0a17 Asahi Optical +vendor BOCASYSTEMS 0x0a43 Boca Systems +vendor SHANTOU 0x0a46 ShanTou +vendor MEDIAGEAR 0x0a48 MediaGear +vendor BROADCOM 0x0a5c Broadcom +vendor GREENHOUSE 0x0a6b GREENHOUSE +vendor GEOCAST 0x0a79 Geocast Network Systems +vendor IDQUANTIQUE 0x0aba id Quantique +vendor ZYDAS 0x0ace Zydas Technology Corporation +vendor NEODIO 0x0aec Neodio +vendor OPTION 0x0af0 Option N.V: +vendor ASUS 0x0b05 ASUSTeK Computer +vendor TODOS 0x0b0c Todos Data System +vendor SIIG2 0x0b39 SIIG +vendor TEKRAM 0x0b3b Tekram Technology +vendor HAL 0x0b41 HAL Corporation +vendor EMS 0x0b43 EMS Production +vendor NEC2 0x0b62 NEC +vendor ATI2 0x0b6f ATI +vendor ZEEVO 0x0b7a Zeevo, Inc. +vendor KURUSUGAWA 0x0b7e Kurusugawa Electronics, Inc. +vendor ASIX 0x0b95 ASIX Electronics +vendor O2MICRO 0x0b97 O2 Micro, Inc. +vendor USR 0x0baf U.S. Robotics +vendor AMBIT 0x0bb2 Ambit Microsystems +vendor HTC 0x0bb4 HTC +vendor REALTEK 0x0bda Realtek +vendor ADDONICS2 0x0bf6 Addonics Technology +vendor FSC 0x0bf8 Fujitsu Siemens Computers +vendor AGATE 0x0c08 Agate Technologies +vendor DMI 0x0c0b DMI +vendor CHICONY2 0x0c45 Chicony +vendor SEALEVEL 0x0c52 Sealevel System +vendor LUWEN 0x0c76 Luwen +vendor KYOCERA2 0x0c88 Kyocera Wireless Corp. +vendor ZCOM 0x0cde Z-Com +vendor ATHEROS2 0x0cf3 Atheros Communications +vendor TANGTOP 0x0d3d Tangtop +vendor SMC3 0x0d5c Standard Microsystems +vendor ADDON 0x0d7d Add-on Technology +vendor ACDC 0x0d7e American Computer & Digital Components +vendor ABC 0x0d8c ABC +vendor CONCEPTRONIC 0x0d8e Conceptronic +vendor SKANHEX 0x0d96 Skanhex Technology, Inc. +vendor MSI 0x0db0 Micro Star International +vendor ELCON 0x0db7 ELCON Systemtechnik +vendor NETAC 0x0dd8 Netac +vendor SITECOMEU 0x0df6 Sitecom Europe +vendor MOBILEACTION 0x0df7 Mobile Action +vendor SPEEDDRAGON 0x0e55 Speed Dragon Multimedia +vendor HAWKING 0x0e66 Hawking +vendor FOSSIL 0x0e67 Fossil, Inc +vendor GMATE 0x0e7e G.Mate, Inc +vendor OTI 0x0ea0 Ours Technology +vendor YISO 0x0eab Yiso Wireless Co. +vendor PILOTECH 0x0eaf Pilotech +vendor NOVATECH 0x0eb0 NovaTech +vendor ITEGNO 0x0eba iTegno +vendor WINMAXGROUP 0x0ed1 WinMaxGroup +vendor TOD 0x0ede TOD +vendor EGALAX 0x0eef eGalax, Inc. +vendor AIRPRIME 0x0f3d AirPrime, Inc. +vendor MICROTUNE 0x0f4d Microtune +vendor VTECH 0x0f88 VTech +vendor FALCOM 0x0f94 Falcom Wireless Communications GmbH +vendor RIM 0x0fca Research In Motion +vendor DYNASTREAM 0x0fcf Dynastream Innovations +vendor QUALCOMM 0x1004 Qualcomm +vendor DESKNOTE 0x1019 Desknote +vendor GIGABYTE 0x1044 GIGABYTE +vendor WESTERN 0x1058 Western Digital +vendor MOTOROLA 0x1063 Motorola +vendor CCYU 0x1065 CCYU Technology +vendor CURITEL 0x106c Curitel Communications Inc +vendor SILABS2 0x10a6 SILABS2 +vendor USI 0x10ab USI +vendor PLX 0x10b5 PLX +vendor ASANTE 0x10bd Asante +vendor SILABS 0x10c4 Silicon Labs +vendor ANALOG 0x1110 Analog Devices +vendor TENX 0x1130 Ten X Technology, Inc. +vendor ISSC 0x1131 Integrated System Solution Corp. +vendor JRC 0x1145 Japan Radio Company +vendor SPHAIRON 0x114b Sphairon Access Systems GmbH +vendor DELORME 0x1163 DeLorme +vendor SERVERWORKS 0x1166 ServerWorks +vendor ACERCM 0x1189 Acer Communications & Multimedia +vendor SIERRA 0x1199 Sierra Wireless +vendor TOPFIELD 0x11db Topfield Co., Ltd +vendor SIEMENS3 0x11f5 Siemens +vendor PROLIFIC2 0x11f6 Prolific +vendor ALCATEL 0x11f7 Alcatel +vendor UNKNOWN3 0x1233 Unknown vendor +vendor TSUNAMI 0x1241 Tsunami +vendor PHEENET 0x124a Pheenet +vendor TARGUS 0x1267 Targus +vendor TWINMOS 0x126f TwinMOS +vendor TENDA 0x1286 Tenda +vendor CREATIVE2 0x1292 Creative Labs +vendor BELKIN2 0x1293 Belkin Components +vendor CYBERTAN 0x129b CyberTAN Technology +vendor HUAWEI 0x12d1 Huawei Technologies +vendor ARANEUS 0x12d8 Araneus Information Systems +vendor TAPWAVE 0x12ef Tapwave +vendor AINCOMM 0x12fd Aincomm +vendor MOBILITY 0x1342 Mobility +vendor DICKSMITH 0x1371 Dick Smith Electronics +vendor NETGEAR3 0x1385 Netgear +vendor BALTECH 0x13ad Baltech +vendor CISCOLINKSYS 0x13b1 Cisco-Linksys +vendor SHARK 0x13d2 Shark +vendor NOVATEL 0x1410 Novatel Wireless +vendor MERLIN 0x1416 Merlin +vendor WISTRONNEWEB 0x1435 Wistron NeWeb +vendor RADIOSHACK 0x1453 Radio Shack +vendor HUAWEI3COM 0x1472 Huawei-3Com +vendor SILICOM 0x1485 Silicom +vendor RALINK 0x148f Ralink Technology +vendor IMAGINATION 0x149a Imagination Technologies +vendor CONCEPTRONIC2 0x14b2 Conceptronic +vendor PLANEX3 0x14ea Planex Communications +vendor SILICONPORTALS 0x1527 Silicon Portals +vendor UBIQUAM 0x1529 UBIQUAM Co., Ltd. +vendor UBLOX 0x1546 U-blox +vendor PNY 0x154b PNY +vendor OQO 0x1557 OQO +vendor UMEDIA 0x157e U-MEDIA Communications +vendor FIBERLINE 0x1582 Fiberline +vendor SPARKLAN 0x15a9 SparkLAN +vendor SOHOWARE 0x15e8 SOHOware +vendor UMAX 0x1606 UMAX Data Systems +vendor INSIDEOUT 0x1608 Inside Out Networks +vendor GOODWAY 0x1631 Good Way Technology +vendor ENTREGA 0x1645 Entrega +vendor ACTIONTEC 0x1668 Actiontec Electronics +vendor ATHEROS 0x168c Atheros Communications +vendor GIGASET 0x1690 Gigaset +vendor GLOBALSUN 0x16ab Global Sun Technology +vendor ANYDATA 0x16d5 AnyDATA Corporation +vendor JABLOTRON 0x16d6 Jablotron +vendor CMOTECH 0x16d8 C-motech +vendor AXESSTEL 0x1726 Axesstel Co., Ltd. +vendor LINKSYS4 0x1737 Linksys +vendor SENAO 0x1740 Senao +vendor METAGEEK 0x1781 MetaGeek +vendor AMIT 0x18c5 AMIT +vendor QCOM 0x18e8 Qcom +vendor LINKSYS3 0x1915 Linksys +vendor QUALCOMMINC 0x19d2 Qualcomm, Incorporated +vendor STELERA 0x1a8d Stelera Wireless +vendor DRESDENELEKTRONIK 0x1cf1 dresden elektronik +vendor DLINK 0x2001 D-Link +vendor PLANEX2 0x2019 Planex Communications +vendor ERICSSON 0x2282 Ericsson +vendor MOTOROLA2 0x22b8 Motorola +vendor TRIPPLITE 0x2478 Tripp-Lite +vendor HIROSE 0x2631 Hirose Electric +vendor NHJ 0x2770 NHJ +vendor PLANEX 0x2c02 Planex Communications +vendor VIDZMEDIA 0x3275 VidzMedia Pte Ltd +vendor AEI 0x3334 AEI +vendor HANK 0x3353 Hank Connection +vendor PQI 0x3538 PQI +vendor DAISY 0x3579 Daisy Technology +vendor NI 0x3923 National Instruments +vendor MICRONET 0x3980 Micronet Communications +vendor IODATA2 0x40bb I-O Data +vendor IRIVER 0x4102 iRiver +vendor DELL 0x413c Dell +vendor WCH 0x4348 QinHeng Electronics +vendor ACEECA 0x4766 Aceeca +vendor AVERATEC 0x50c2 Averatec +vendor SWEEX 0x5173 Sweex +vendor ONSPEC2 0x55aa OnSpec Electronic Inc. +vendor ZINWELL 0x5a57 Zinwell +vendor SITECOM 0x6189 Sitecom +vendor ARKMICRO 0x6547 Arkmicro Technologies Inc. +vendor 3COM2 0x6891 3Com +vendor INTEL 0x8086 Intel +vendor SITECOM2 0x9016 Sitecom +vendor MOSCHIP 0x9710 MosChip Semiconductor +vendor 3COM3 0xa727 3Com +vendor HP2 0xf003 Hewlett Packard +vendor USRP 0xfffe GNU Radio USRP + +/* + * List of known products. Grouped by vendor. + */ + +/* 3Com products */ +product 3COM HOMECONN 0x009d HomeConnect Camera +product 3COM 3CREB96 0x00a0 Bluetooth USB Adapter +product 3COM 3C19250 0x03e8 3C19250 Ethernet Adapter +product 3COM 3CRSHEW696 0x0a01 3CRSHEW696 Wireless Adapter +product 3COM 3C460 0x11f8 HomeConnect 3C460 +product 3COM USR56K 0x3021 U.S.Robotics 56000 Voice FaxModem Pro +product 3COM 3C460B 0x4601 HomeConnect 3C460B +product 3COM2 3CRUSB10075 0xa727 3CRUSB10075 +product 3COM3 AR5523_1 0x6893 AR5523 +product 3COM3 AR5523_2 0x6895 AR5523 +product 3COM3 AR5523_3 0x6897 AR5523 + +product 3COMUSR OFFICECONN 0x0082 3Com OfficeConnect Analog Modem +product 3COMUSR USRISDN 0x008f 3Com U.S. Robotics Pro ISDN TA +product 3COMUSR HOMECONN 0x009d 3Com HomeConnect Camera +product 3COMUSR USR56K 0x3021 U.S. Robotics 56000 Voice FaxModem Pro + +/* AboCom products */ +product ABOCOM XX1 0x110c XX1 +product ABOCOM XX2 0x200c XX2 +product ABOCOM URE450 0x4000 URE450 Ethernet Adapter +product ABOCOM UFE1000 0x4002 UFE1000 Fast Ethernet Adapter +product ABOCOM DSB650TX_PNA 0x4003 1/10/100 Ethernet Adapter +product ABOCOM XX4 0x4004 XX4 +product ABOCOM XX5 0x4007 XX5 +product ABOCOM XX6 0x400b XX6 +product ABOCOM XX7 0x400c XX7 +product ABOCOM RTL8151 0x401a RTL8151 +product ABOCOM XX8 0x4102 XX8 +product ABOCOM XX9 0x4104 XX9 +product ABOCOM UF200 0x420a UF200 Ethernet +product ABOCOM WL54 0x6001 WL54 +product ABOCOM XX10 0xabc1 XX10 +product ABOCOM BWU613 0xb000 BWU613 +product ABOCOM HWU54DM 0xb21b HWU54DM +product ABOCOM RT2573_2 0xb21c RT2573 +product ABOCOM RT2573_3 0xb21d RT2573 +product ABOCOM RT2573_4 0xb21e RT2573 +product ABOCOM WUG2700 0xb21f WUG2700 + +/* Accton products */ +product ACCTON USB320_EC 0x1046 USB320-EC Ethernet Adapter +product ACCTON 2664W 0x3501 2664W +product ACCTON 111 0x3503 T-Sinus 111 Wireless Adapter +product ACCTON SMCWUSBG 0x4505 SMCWUSB-G +product ACCTON PRISM_GT 0x4521 PrismGT USB 2.0 WLAN +product ACCTON SS1001 0x5046 SpeedStream Ethernet Adapter +product ACCTON ZD1211B 0xe501 ZD1211B + +/* Aceeca products */ +product ACEECA MEZ1000 0x0001 MEZ1000 RDA + +/* Acer Communications & Multimedia (oemd by Surecom) */ +product ACERCM EP1427X2 0x0893 EP-1427X-2 Ethernet Adapter + +/* Acer Labs products */ +product ACERLABS M5632 0x5632 USB 2.0 Data Link + +/* Acer Peripherals, Inc. products */ +product ACERP ACERSCAN_C310U 0x12a6 Acerscan C310U +product ACERP ACERSCAN_320U 0x2022 Acerscan 320U +product ACERP ACERSCAN_640U 0x2040 Acerscan 640U +product ACERP ACERSCAN_620U 0x2060 Acerscan 620U +product ACERP ACERSCAN_4300U 0x20b0 Benq 3300U/4300U +product ACERP ACERSCAN_640BT 0x20be Acerscan 640BT +product ACERP ACERSCAN_1240U 0x20c0 Acerscan 1240U +product ACERP ATAPI 0x6003 ATA/ATAPI Adapter +product ACERP AWL300 0x9000 AWL300 Wireless Adapter +product ACERP AWL400 0x9001 AWL400 Wireless Adapter + +/* Acer Warp products */ +product ACERW WARPLINK 0x0204 Warplink + +/* Actiontec, Inc. products */ +product ACTIONTEC PRISM_25 0x0408 Prism2.5 Wireless Adapter +product ACTIONTEC PRISM_25A 0x0421 Prism2.5 Wireless Adapter A +product ACTIONTEC FREELAN 0x6106 ROPEX FreeLan 802.11b +product ACTIONTEC UAT1 0x7605 UAT1 Wireless Ethernet Adapter + +/* ACTiSYS products */ +product ACTISYS IR2000U 0x0011 ACT-IR2000U FIR + +/* ActiveWire, Inc. products */ +product ACTIVEWIRE IOBOARD 0x0100 I/O Board +product ACTIVEWIRE IOBOARD_FW1 0x0101 I/O Board, rev. 1 firmware + +/* Adaptec products */ +product ADAPTEC AWN8020 0x0020 AWN-8020 WLAN + +/* Addtron products */ +product ADDTRON AWU120 0xff31 AWU-120 + +/* ADMtek products */ +product ADMTEK PEGASUSII_4 0x07c2 AN986A Ethernet +product ADMTEK PEGASUS 0x0986 AN986 Ethernet +product ADMTEK PEGASUSII 0x8511 AN8511 Ethernet +product ADMTEK PEGASUSII_2 0x8513 AN8513 Ethernet +product ADMTEK PEGASUSII_3 0x8515 AN8515 Ethernet + +/* ADDON products */ +/* PNY OEMs these */ +product ADDON ATTACHE 0x1300 USB 2.0 Flash Drive +product ADDON ATTACHE 0x1300 USB 2.0 Flash Drive +product ADDON A256MB 0x1400 Attache 256MB USB 2.0 Flash Drive +product ADDON DISKPRO512 0x1420 USB 2.0 Flash Drive (DANE-ELEC zMate 512MB USB flash drive) + +/* Addonics products */ +product ADDONICS2 CABLE_205 0xa001 Cable 205 + +/* ADS products */ +product ADS UBS10BT 0x0008 UBS-10BT Ethernet +product ADS UBS10BTX 0x0009 UBS-10BT Ethernet + +/* AEI products */ +product AEI FASTETHERNET 0x1701 Fast Ethernet + +/* Agate Technologies products */ +product AGATE QDRIVE 0x0378 Q-Drive + +/* AGFA products */ +product AGFA SNAPSCAN1212U 0x0001 SnapScan 1212U +product AGFA SNAPSCAN1236U 0x0002 SnapScan 1236U +product AGFA SNAPSCANTOUCH 0x0100 SnapScan Touch +product AGFA SNAPSCAN1212U2 0x2061 SnapScan 1212U +product AGFA SNAPSCANE40 0x208d SnapScan e40 +product AGFA SNAPSCANE50 0x208f SnapScan e50 +product AGFA SNAPSCANE20 0x2091 SnapScan e20 +product AGFA SNAPSCANE25 0x2095 SnapScan e25 +product AGFA SNAPSCANE26 0x2097 SnapScan e26 +product AGFA SNAPSCANE52 0x20fd SnapScan e52 + +/* Ain Communication Technology products */ +product AINCOMM AWU2000B 0x1001 AWU2000B Wireless Adapter + +/* AIPTEK products */ +product AIPTEK POCKETCAM3M 0x2011 PocketCAM 3Mega +product AIPTEK2 PENCAM_MEGA_1_3 0x504a PenCam Mega 1.3 + +/* AirPrime products */ +product AIRPRIME PC5220 0x0112 CDMA Wireless PC Card + +/* AKS products */ +product AKS USBHASP 0x0001 USB-HASP 0.06 + +/* Alcor Micro, Inc. products */ +product ALCOR2 KBD_HUB 0x2802 Kbd Hub + +product ALCOR TRANSCEND 0x6387 Transcend JetFlash Drive +product ALCOR MA_KBD_HUB 0x9213 MacAlly Kbd Hub +product ALCOR AU9814 0x9215 AU9814 Hub +product ALCOR UMCR_9361 0x9361 USB Multimedia Card Reader +product ALCOR SM_KBD 0x9410 MicroConnectors/StrongMan Keyboard +product ALCOR NEC_KBD_HUB 0x9472 NEC Kbd Hub + +/* Altec Lansing products */ +product ALTEC ADA70 0x0070 ADA70 Speakers +product ALTEC ASC495 0xff05 ASC495 Speakers + +/* Allied Telesyn International products */ +product ALLIEDTELESYN ATUSB100 0xb100 AT-USB100 + +/* American Power Conversion products */ +product APC UPS 0x0002 Uninterruptible Power Supply + +/* Ambit Microsystems products */ +product AMBIT WLAN 0x0302 WLAN +product AMBIT NTL_250 0x6098 NTL 250 cable modem + +/* AMIT products */ +product AMIT CGWLUSB2GO 0x0002 CG-WLUSB2GO + +/* Anchor products */ +product ANCHOR EZUSB 0x2131 EZUSB +product ANCHOR EZLINK 0x2720 EZLINK + +/* AnyData products */ +product ANYDATA ADU_E100X 0x6501 CDMA 2000 1xRTT/EV-DO USB Modem +product ANYDATA ADU_500A 0x6502 CDMA 2000 EV-DO USB Modem + +/* AOX, Inc. products */ +product AOX USB101 0x0008 Ethernet + +/* American Power Conversion products */ +product APC UPS 0x0002 Uninterruptible Power Supply + +/* Apple Computer products */ +product APPLE EXT_KBD 0x020c Apple Extended USB Keyboard +product APPLE OPTMOUSE 0x0302 Optical mouse +product APPLE MIGHTYMOUSE 0x0304 Mighty Mouse +product APPLE EXT_KBD_HUB 0x1003 Hub in Apple Extended USB Keyboard +product APPLE SPEAKERS 0x1101 Speakers +product APPLE IPOD 0x1201 iPod +product APPLE IPOD2G 0x1202 iPod 2G +product APPLE IPOD3G 0x1203 iPod 3G +product APPLE IPOD_04 0x1204 iPod '04' +product APPLE IPODMINI 0x1205 iPod Mini +product APPLE IPOD_06 0x1206 iPod '06' +product APPLE IPOD_07 0x1207 iPod '07' +product APPLE IPOD_08 0x1208 iPod '08' +product APPLE IPODVIDEO 0x1209 iPod Video +product APPLE IPODNANO 0x120a iPod Nano +product APPLE IPHONE 0x1290 iPhone +product APPLE IPHONE_3G 0x1292 iPhone 3G +product APPLE ETHERNET 0x1402 Ethernet A1277 + +/* Arkmicro Technologies */ +product ARKMICRO ARK3116 0x0232 ARK3116 Serial + +/* Asahi Optical products */ +product ASAHIOPTICAL OPTIO230 0x0004 Digital camera +product ASAHIOPTICAL OPTIO330 0x0006 Digital camera + +/* Asante products */ +product ASANTE EA 0x1427 Ethernet + +/* ASIX Electronics products */ +product ASIX AX88172 0x1720 10/100 Ethernet +product ASIX AX88178 0x1780 AX88178 +product ASIX AX88772 0x7720 AX88772 + +/* ASUS products */ +product ASUS WL167G 0x1707 WL-167g Wireless Adapter +product ASUS WL159G 0x170c WL-159g +product ASUS A9T_WIFI 0x171b A9T wireless +product ASUS RT2573_1 0x1723 RT2573 +product ASUS RT2573_2 0x1724 RT2573 +product ASUS LCM 0x1726 LCM display +product ASUS P535 0x420f ASUS P535 PDA + +/* ATen products */ +product ATEN UC1284 0x2001 Parallel printer +product ATEN UC10T 0x2002 10Mbps Ethernet +product ATEN UC110T 0x2007 UC-110T Ethernet +product ATEN UC232A 0x2008 Serial +product ATEN UC210T 0x2009 UC-210T Ethernet +product ATEN DSB650C 0x4000 DSB-650C + +/* Atheros Communications products */ +product ATHEROS AR5523 0x0001 AR5523 +product ATHEROS AR5523_NF 0x0002 AR5523 (no firmware) +product ATHEROS2 AR5523_1 0x0001 AR5523 +product ATHEROS2 AR5523_1_NF 0x0002 AR5523 (no firmware) +product ATHEROS2 AR5523_2 0x0003 AR5523 +product ATHEROS2 AR5523_2_NF 0x0004 AR5523 (no firmware) +product ATHEROS2 AR5523_3 0x0005 AR5523 +product ATHEROS2 AR5523_3_NF 0x0006 AR5523 (no firmware) + +/* Atmel Comp. products */ +product ATMEL UHB124 0x3301 UHB124 hub +product ATMEL DWL120 0x7603 DWL-120 Wireless Adapter +product ATMEL BW002 0x7605 BW002 Wireless Adapter +product ATMEL WL1130USB 0x7613 WL-1130 USB +product ATMEL AT76C505A 0x7614 AT76c505a Wireless Adapter + +/* Avision products */ +product AVISION 1200U 0x0268 1200U scanner + +/* Axesstel products */ +product AXESSTEL DATAMODEM 0x1000 Data Modem + +/* Baltech products */ +product BALTECH CARDREADER 0x9999 Card reader + +/* B&B Electronics products */ +product BBELECTRONICS USOTL4 0xAC01 RS-422/485 + +/* Belkin products */ +/*product BELKIN F5U111 0x???? F5U111 Ethernet*/ +product BELKIN F5D6050 0x0050 F5D6050 802.11b Wireless Adapter +product BELKIN FBT001V 0x0081 FBT001v2 Bluetooth +product BELKIN FBT003V 0x0084 FBT003v2 Bluetooth +product BELKIN F5U103 0x0103 F5U103 Serial +product BELKIN F5U109 0x0109 F5U109 Serial +product BELKIN USB2SCSI 0x0115 USB to SCSI +product BELKIN F8T012 0x0121 F8T012xx1 Bluetooth USB Adapter +product BELKIN USB2LAN 0x0121 USB to LAN +product BELKIN F5U208 0x0208 F5U208 VideoBus II +product BELKIN F5U237 0x0237 F5U237 USB 2.0 7-Port Hub +product BELKIN F5U257 0x0257 F5U257 Serial +product BELKIN F5U409 0x0409 F5U409 Serial +product BELKIN F6C550AVR 0x0551 F6C550-AVR UPS +product BELKIN F5U120 0x1203 F5U120-PC Hub +product BELKIN ZD1211B 0x4050 ZD1211B +product BELKIN F5D5055 0x5055 F5D5055 +product BELKIN F5D7050 0x7050 F5D7050 Wireless Adapter +product BELKIN F5D7051 0x7051 F5D7051 54g USB Network Adapter +product BELKIN F5D7050A 0x705a F5D7050A Wireless Adapter +/* Also sold as 'Ativa 802.11g wireless card' */ +product BELKIN F5D7050_V4000 0x705c F5D7050 v4000 Wireless Adapter +product BELKIN F5D9050V3 0x905b F5D9050 ver 3 Wireless Adapter +product BELKIN2 F5U002 0x0002 F5U002 Parallel printer + +/* Billionton products */ +product BILLIONTON USB100 0x0986 USB100N 10/100 FastEthernet +product BILLIONTON USBLP100 0x0987 USB100LP +product BILLIONTON USBEL100 0x0988 USB100EL +product BILLIONTON USBE100 0x8511 USBE100 +product BILLIONTON USB2AR 0x90ff USB2AR Ethernet + +/* Broadcom products */ +product BROADCOM BCM2033 0x2033 BCM2033 Bluetooth USB dongle + +/* Brother Industries products */ +product BROTHER HL1050 0x0002 HL-1050 laser printer + +/* Behavior Technology Computer products */ +product BTC BTC7932 0x6782 Keyboard with mouse port + +/* Canon, Inc. products */ +product CANON N656U 0x2206 CanoScan N656U +product CANON N1220U 0x2207 CanoScan N1220U +product CANON D660U 0x2208 CanoScan D660U +product CANON N676U 0x220d CanoScan N676U +product CANON N1240U 0x220e CanoScan N1240U +product CANON LIDE25 0x2220 CanoScan LIDE 25 +product CANON S10 0x3041 PowerShot S10 +product CANON S100 0x3045 PowerShot S100 +product CANON S200 0x3065 PowerShot S200 +product CANON REBELXT 0x30ef Digital Rebel XT + +/* CATC products */ +product CATC NETMATE 0x000a Netmate Ethernet +product CATC NETMATE2 0x000c Netmate2 Ethernet +product CATC CHIEF 0x000d USB Chief Bus & Protocol Analyzer +product CATC ANDROMEDA 0x1237 Andromeda hub + +/* CASIO products */ +product CASIO QV_DIGICAM 0x1001 QV DigiCam +product CASIO EXS880 0x1105 Exilim EX-S880 +product CASIO BE300 0x2002 BE-300 PDA +product CASIO NAMELAND 0x4001 CASIO Nameland EZ-USB + +/* CCYU products */ +product CCYU ED1064 0x2136 EasyDisk ED1064 + +/* Century products */ +product CENTURY EX35QUAT 0x011e Century USB Disk Enclosure + +/* Cherry products */ +product CHERRY MY3000KBD 0x0001 My3000 keyboard +product CHERRY MY3000HUB 0x0003 My3000 hub +product CHERRY CYBOARD 0x0004 CyBoard Keyboard + +/* Chic Technology products */ +product CHIC MOUSE1 0x0001 mouse +product CHIC CYPRESS 0x0003 Cypress USB Mouse + +/* Chicony products */ +product CHICONY KB8933 0x0001 KB-8933 keyboard +product CHICONY2 TWINKLECAM 0x600d TwinkleCam USB camera + +/* CH Products */ +product CHPRODUCTS PROTHROTTLE 0x00f1 Pro Throttle +product CHPRODUCTS PROPEDALS 0x00f2 Pro Pedals +product CHPRODUCTS FIGHTERSTICK 0x00f3 Fighterstick +product CHPRODUCTS FLIGHTYOKE 0x00ff Flight Sim Yoke + +/* Cisco-Linksys products */ +product CISCOLINKSYS WUSB54G 0x000d WUSB54G Wireless Adapter +product CISCOLINKSYS WUSB54GP 0x0011 WUSB54GP Wireless Adapter +product CISCOLINKSYS USB200MV2 0x0018 USB200M v2 +product CISCOLINKSYS HU200TS 0x001a HU200TS Wireless Adapter +product CISCOLINKSYS WUSB54GC 0x0020 WUSB54GC +product CISCOLINKSYS WUSB54GR 0x0023 WUSB54GR +product CISCOLINKSYS WUSBF54G 0x0024 WUSBF54G + +/* CMOTECH products */ +product CMOTECH CNU510 0x5141 CDMA Technologies USB modem +product CMOTECH CNU550 0x5543 CDMA 2000 1xRTT/1xEVDO USB modem +product CMOTECH CGU628 0x6006 CGU-628 +product CMOTECH CDMA_MODEM1 0x6280 CDMA Technologies USB modem +product CMOTECH DISK 0xf000 disk mode + +/* Compaq products */ +product COMPAQ IPAQPOCKETPC 0x0003 iPAQ PocketPC +product COMPAQ PJB100 0x504a Personal Jukebox PJB100 +product COMPAQ IPAQLINUX 0x505a iPAQ Linux + +/* Composite Corp products looks the same as "TANGTOP" */ +product COMPOSITE USBPS2 0x0001 USB to PS2 Adaptor + +/* Conceptronic products */ +product CONCEPTRONIC PRISM_GT 0x3762 PrismGT USB 2.0 WLAN +product CONCEPTRONIC C11U 0x7100 C11U +product CONCEPTRONIC WL210 0x7110 WL-210 +product CONCEPTRONIC AR5523_1 0x7801 AR5523 +product CONCEPTRONIC AR5523_1_NF 0x7802 AR5523 (no firmware) +product CONCEPTRONIC AR5523_2 0x7811 AR5523 +product CONCEPTRONIC AR5523_2_NF 0x7812 AR5523 (no firmware) +product CONCEPTRONIC2 C54RU 0x3c02 C54RU WLAN +product CONCEPTRONIC2 C54RU2 0x3c22 C54RU + +/* Connectix products */ +product CONNECTIX QUICKCAM 0x0001 QuickCam + +/* Corega products */ +product COREGA ETHER_USB_T 0x0001 Ether USB-T +product COREGA FETHER_USB_TX 0x0004 FEther USB-TX +product COREGA WLAN_USB_USB_11 0x000c WirelessLAN USB-11 +product COREGA FETHER_USB_TXS 0x000d FEther USB-TXS +product COREGA WLANUSB 0x0012 Wireless LAN Stick-11 +product COREGA FETHER_USB2_TX 0x0017 FEther USB2-TX +product COREGA WLUSB_11_KEY 0x001a ULUSB-11 Key +product COREGA CGWLUSB2GL 0x002d CG-WLUSB2GL +product COREGA CGWLUSB2GPX 0x002e CG-WLUSB2GPX +product COREGA WLUSB_11_STICK 0x7613 WLAN USB Stick 11 +product COREGA FETHER_USB_TXC 0x9601 FEther USB-TXC + +/* Creative products */ +product CREATIVE NOMAD_II 0x1002 Nomad II MP3 player +product CREATIVE NOMAD_IIMG 0x4004 Nomad II MG +product CREATIVE NOMAD 0x4106 Nomad +product CREATIVE2 VOIP_BLASTER 0x0258 Voip Blaster +product CREATIVE3 OPTICAL_MOUSE 0x0001 Notebook Optical Mouse + +/* Cambridge Silicon Radio Ltd. products */ +product CSR BT_DONGLE 0x0001 Bluetooth USB dongle +product CSR CSRDFU 0xffff USB Bluetooth Device in DFU State + +/* CTX products */ +product CTX EX1300 0x9999 Ex1300 hub + +/* Curitel products */ +product CURITEL HX550C 0x1101 CDMA 2000 1xRTT USB modem (HX-550C) +product CURITEL HX57XB 0x2101 CDMA 2000 1xRTT USB modem (HX-570/575B/PR-600) +product CURITEL PC5740 0x3701 Broadband Wireless modem + +/* CyberPower products */ +product CYBERPOWER 1500CAVRLCD 0x0501 1500CAVRLCD + +/* CyberTAN Technology products */ +product CYBERTAN TG54USB 0x1666 TG54USB + +/* Cypress Semiconductor products */ +product CYPRESS MOUSE 0x0001 mouse +product CYPRESS THERMO 0x0002 thermometer +product CYPRESS WISPY1A 0x0bad MetaGeek Wi-Spy +product CYPRESS KBDHUB 0x0101 Keyboard/Hub +product CYPRESS FMRADIO 0x1002 FM Radio +product CYPRESS USBRS232 0x5500 USB-RS232 Interface +product CYPRESS SLIM_HUB 0x6560 Slim Hub + +/* Daisy Technology products */ +product DAISY DMC 0x6901 USB MultiMedia Reader + +/* Dallas Semiconductor products */ +product DALLAS J6502 0x4201 J-6502 speakers + +/* Dell products */ +product DELL PORT 0x0058 Port Replicator +product DELL AIO926 0x5115 Photo AIO Printer 926 +product DELL BC02 0x8000 BC02 Bluetooth USB Adapter +product DELL PRISM_GT_1 0x8102 PrismGT USB 2.0 WLAN +product DELL TM350 0x8103 TrueMobile 350 Bluetooth USB Adapter +product DELL PRISM_GT_2 0x8104 PrismGT USB 2.0 WLAN +product DELL U740 0x8135 Dell U740 CDMA + +/* Delorme Paublishing products */ +product DELORME EARTHMATE 0x0100 Earthmate GPS + +/* Desknote products */ +product DESKNOTE UCR_61S2B 0x0c55 UCR-61S2B + +/* Diamond products */ +product DIAMOND RIO500USB 0x0001 Rio 500 USB + +/* Dick Smith Electronics (really C-Net) products */ +product DICKSMITH RT2573 0x9022 RT2573 +product DICKSMITH CWD854F 0x9032 C-Net CWD-854 rev F + +/* Digi International products */ +product DIGI ACCELEPORT2 0x0002 AccelePort USB 2 +product DIGI ACCELEPORT4 0x0004 AccelePort USB 4 +product DIGI ACCELEPORT8 0x0008 AccelePort USB 8 + +/* D-Link products */ +/*product DLINK DSBS25 0x0100 DSB-S25 serial*/ +product DLINK DUBE100 0x1a00 10/100 Ethernet +product DLINK DSB650TX4 0x200c 10/100 Ethernet +product DLINK DWL120E 0x3200 DWL-120 rev E +product DLINK DWL122 0x3700 DWL-122 +product DLINK DWLG120 0x3701 DWL-G120 +product DLINK DWL120F 0x3702 DWL-120 rev F +product DLINK DWLAG132 0x3a00 DWL-AG132 +product DLINK DWLAG132_NF 0x3a01 DWL-AG132 (no firmware) +product DLINK DWLG132 0x3a02 DWL-G132 +product DLINK DWLG132_NF 0x3a03 DWL-G132 (no firmware) +product DLINK DWLAG122 0x3a04 DWL-AG122 +product DLINK DWLAG122_NF 0x3a05 DWL-AG122 (no firmware) +product DLINK DWLG122 0x3c00 DWL-G122 b1 Wireless Adapter +product DLINK DUBE100B1 0x3c05 DUB-E100 rev B1 +product DLINK DSB650C 0x4000 10Mbps Ethernet +product DLINK DSB650TX1 0x4001 10/100 Ethernet +product DLINK DSB650TX 0x4002 10/100 Ethernet +product DLINK DSB650TX_PNA 0x4003 1/10/100 Ethernet +product DLINK DSB650TX3 0x400b 10/100 Ethernet +product DLINK DSB650TX2 0x4102 10/100 Ethernet +product DLINK DSB650 0xabc1 10/100 Ethernet +product DLINK2 DWLG122C1 0x3c03 DWL-G122 c1 +product DLINK2 WUA1340 0x3c04 WUA-1340 +product DLINK2 DWA111 0x3c06 DWA-111 +product DLINK2 DWA110 0x3c07 DWA-110 + +/* DMI products */ +product DMI CFSM_RW 0xa109 CF/SM Reader/Writer + +/* DrayTek products */ +product DRAYTEK VIGOR550 0x0550 Vigor550 + +/* dresden elektronik products */ +product DRESDENELEKTRONIK SENSORTERMINALBOARD 0x0001 SensorTerminalBoard + +/* Dynastream Innovations */ +product DYNASTREAM ANTDEVBOARD 0x1003 ANT dev board + +/* EIZO products */ +product EIZO HUB 0x0000 hub +product EIZO MONITOR 0x0001 monitor + +/* ELCON Systemtechnik products */ +product ELCON PLAN 0x0002 Goldpfeil P-LAN + +/* Elecom products */ +product ELECOM MOUSE29UO 0x0002 mouse 29UO +product ELECOM LDUSBTX0 0x200c LD-USB/TX +product ELECOM LDUSBTX1 0x4002 LD-USB/TX +product ELECOM LDUSBLTX 0x4005 LD-USBL/TX +product ELECOM LDUSBTX2 0x400b LD-USB/TX +product ELECOM LDUSB20 0x4010 LD-USB20 +product ELECOM UCSGT 0x5003 UC-SGT +product ELECOM UCSGT0 0x5004 UC-SGT +product ELECOM LDUSBTX3 0xabc1 LD-USB/TX + +/* Elsa products */ +product ELSA MODEM1 0x2265 ELSA Modem Board +product ELSA USB2ETHERNET 0x3000 Microlink USB2Ethernet + +/* EMS products */ +product EMS DUAL_SHOOTER 0x0003 PSX gun controller converter + +/* Entrega products */ +product ENTREGA 1S 0x0001 1S serial +product ENTREGA 2S 0x0002 2S serial +product ENTREGA 1S25 0x0003 1S25 serial +product ENTREGA 4S 0x0004 4S serial +product ENTREGA E45 0x0005 E45 Ethernet +product ENTREGA CENTRONICS 0x0006 Parallel Port +product ENTREGA XX1 0x0008 Ethernet +product ENTREGA 1S9 0x0093 1S9 serial +product ENTREGA EZUSB 0x8000 EZ-USB +/*product ENTREGA SERIAL 0x8001 DB25 Serial*/ +product ENTREGA 2U4S 0x8004 2U4S serial/usb hub +product ENTREGA XX2 0x8005 Ethernet +/*product ENTREGA SERIAL_DB9 0x8093 DB9 Serial*/ + +/* Epson products */ +product EPSON PRINTER1 0x0001 USB Printer +product EPSON PRINTER2 0x0002 ISD USB Smart Cable for Mac +product EPSON PRINTER3 0x0003 ISD USB Smart Cable +product EPSON PRINTER5 0x0005 USB Printer +product EPSON 636 0x0101 Perfection 636U / 636Photo scanner +product EPSON 610 0x0103 Perfection 610 scanner +product EPSON 1200 0x0104 Perfection 1200U / 1200Photo scanner +product EPSON 1600 0x0107 Expression 1600 scanner +product EPSON 1640 0x010a Perfection 1640SU scanner +product EPSON 1240 0x010b Perfection 1240U / 1240Photo scanner +product EPSON 640U 0x010c Perfection 640U scanner +product EPSON 1250 0x010f Perfection 1250U / 1250Photo scanner +product EPSON 1650 0x0110 Perfection 1650 scanner +product EPSON GT9700F 0x0112 GT-9700F scanner +product EPSON GT9300UF 0x011b GT-9300UF scanner +product EPSON 3200 0x011c Perfection 3200 scanner +product EPSON 1260 0x011d Perfection 1260 scanner +product EPSON 1660 0x011e Perfection 1660 scanner +product EPSON 1670 0x011f Perfection 1670 scanner +product EPSON 1270 0x0120 Perfection 1270 scanner +product EPSON 2480 0x0121 Perfection 2480 scanner +product EPSON 3590 0x0122 Perfection 3590 scanner +product EPSON 4990 0x012a Perfection 4990 Photo scanner +product EPSON STYLUS_875DC 0x0601 Stylus Photo 875DC Card Reader +product EPSON STYLUS_895 0x0602 Stylus Photo 895 Card Reader +product EPSON CX5400 0x0808 CX5400 scanner +product EPSON 3500 0x080e CX-3500/3600/3650 MFP +product EPSON RX425 0x080f Stylus Photo RX425 scanner +product EPSON DX3800 0x0818 CX3700/CX3800/DX38x0 MFP scanner +product EPSON 4800 0x0819 CX4700/CX4800/DX48x0 MFP scanner +product EPSON 4200 0x0820 CX4100/CX4200/DX4200 MFP scanner +product EPSON 5000 0x082b CX4900/CX5000/DX50x0 MFP scanner +product EPSON 6000 0x082e CX5900/CX6000/DX60x0 MFP scanner +product EPSON DX4000 0x082f DX4000 MFP scanner +product EPSON DX7400 0x0838 CX7300/CX7400/DX7400 MFP scanner +product EPSON DX8400 0x0839 CX8300/CX8400/DX8400 MFP scanner +product EPSON SX100 0x0841 SX100/NX100 MFP scanner +product EPSON NX300 0x0848 NX300 MFP scanner +product EPSON SX200 0x0849 SX200/SX205 MFP scanner +product EPSON SX400 0x084a SX400/NX400/TX400 MFP scanner + +/* e-TEK Labs products */ +product ETEK 1COM 0x8007 Serial + +/* Extended Systems products */ +product EXTENDED XTNDACCESS 0x0100 XTNDAccess IrDA + +/* FEIYA products */ +product FEIYA 5IN1 0x1132 5-in-1 Card Reader + +/* Fiberline */ +product FIBERLINE WL430U 0x6003 WL-430U + +/* Fossil, Inc products */ +product FOSSIL WRISTPDA 0x0002 Wrist PDA + +/* Freecom products */ +product FREECOM DVD 0xfc01 DVD drive + +/* Fujitsu Siemens Computers products */ +product FSC E5400 0x1009 PrismGT USB 2.0 WLAN + +/* Future Technology Devices products */ +product FTDI SERIAL_8U100AX 0x8372 8U100AX Serial +product FTDI SERIAL_8U232AM 0x6001 8U232AM Serial +product FTDI SERIAL_2232C 0x6010 FT2232C Dual port Serial +/* Gude Analog- und Digitalsysteme products also uses FTDI's id: */ +product FTDI TACTRIX_OPENPORT_13M 0xcc48 OpenPort 1.3 Mitsubishi +product FTDI TACTRIX_OPENPORT_13S 0xcc49 OpenPort 1.3 Subaru +product FTDI TACTRIX_OPENPORT_13U 0xcc4a OpenPort 1.3 Universal +product FTDI EISCOU 0xe888 Expert ISDN Control USB +product FTDI UOPTBR 0xe889 USB-RS232 OptoBridge +product FTDI EMCU2D 0xe88a Expert mouseCLOCK USB II +product FTDI PCMSFU 0xe88b Precision Clock MSF USB +product FTDI EMCU2H 0xe88c Expert mouseCLOCK USB II HBG +product FTDI MAXSTREAM 0xee18 Maxstream PKG-U +product FTDI USBSERIAL 0xfa00 Matrix Orbital USB Serial +product FTDI MX2_3 0xfa01 Matrix Orbital MX2 or MX3 +product FTDI MX4_5 0xfa02 Matrix Orbital MX4 or MX5 +product FTDI LK202 0xfa03 Matrix Orbital VK/LK202 Family +product FTDI LK204 0xfa04 Matrix Orbital VK/LK204 Family +product FTDI CFA_632 0xfc08 Crystalfontz CFA-632 USB LCD +product FTDI CFA_634 0xfc09 Crystalfontz CFA-634 USB LCD +product FTDI CFA_633 0xfc0b Crystalfontz CFA-633 USB LCD +product FTDI CFA_631 0xfc0c Crystalfontz CFA-631 USB LCD +product FTDI CFA_635 0xfc0d Crystalfontz CFA-635 USB LCD +product FTDI SEMC_DSS20 0xfc82 SEMC DSS-20 SyncStation + +/* Fuji photo products */ +product FUJIPHOTO MASS0100 0x0100 Mass Storage + +/* Fujitsu protducts */ +product FUJITSU AH_F401U 0x105b AH-F401U Air H device + +/* Garmin products */ +product GARMIN IQUE_3600 0x0004 iQue 3600 + +/* General Instruments (Motorola) products */ +product GENERALINSTMNTS SB5100 0x5100 SURFboard SB5100 Cable modem + +/* Genesys Logic products */ +product GENESYS GL620USB 0x0501 GL620USB Host-Host interface +product GENESYS GL650 0x0604 GL650 Hub +product GENESYS GL641USB 0x0700 GL641USB CompactFlash Card Reader +product GENESYS GL641USB2IDE_2 0x0701 GL641USB USB-IDE Bridge No 2 +product GENESYS GL641USB2IDE 0x0702 GL641USB USB-IDE Bridge +product GENESYS GL641USB_2 0x0760 GL641USB 6-in-1 Card Reader + +/* GIGABYTE products */ +product GIGABYTE GN54G 0x8001 GN-54G +product GIGABYTE GNBR402W 0x8002 GN-BR402W +product GIGABYTE GNWLBM101 0x8003 GN-WLBM101 +product GIGABYTE GNWBKG 0x8007 GN-WBKG +product GIGABYTE GNWB01GS 0x8008 GN-WB01GS +product GIGABYTE GNWI05GS 0x800a GN-WI05GS + +/* Gigaset products */ +product GIGASET WLAN 0x0701 WLAN +product GIGASET SMCWUSBTG 0x0710 SMCWUSBT-G +product GIGASET SMCWUSBTG_NF 0x0711 SMCWUSBT-G (no firmware) +product GIGASET AR5523 0x0712 AR5523 +product GIGASET AR5523_NF 0x0713 AR5523 (no firmware) +product GIGASET RT2573 0x0722 RT2573 + +/* Global Sun Technology product */ +product GLOBALSUN AR5523_1 0x7801 AR5523 +product GLOBALSUN AR5523_1_NF 0x7802 AR5523 (no firmware) +product GLOBALSUN AR5523_2 0x7811 AR5523 +product GLOBALSUN AR5523_2_NF 0x7812 AR5523 (no firmware) + +/* Globespan products */ +product GLOBESPAN PRISM_GT_1 0x2000 PrismGT USB 2.0 WLAN +product GLOBESPAN PRISM_GT_2 0x2002 PrismGT USB 2.0 WLAN + +/* G.Mate, Inc products */ +product GMATE YP3X00 0x1001 YP3X00 PDA + +/* GoHubs products */ +product GOHUBS GOCOM232 0x1001 GoCOM232 Serial + +/* Good Way Technology products */ +product GOODWAY GWUSB2E 0x6200 GWUSB2E +product GOODWAY RT2573 0xc019 RT2573 + +/* Gravis products */ +product GRAVIS GAMEPADPRO 0x4001 GamePad Pro + +/* GREENHOUSE products */ +product GREENHOUSE KANA21 0x0001 CF-writer with MP3 + +/* Griffin Technology */ +product GRIFFIN IMATE 0x0405 iMate, ADB Adapter + +/* Guillemot Corporation */ +product GUILLEMOT DALEADER 0xa300 DA Leader +product GUILLEMOT HWGUSB254 0xe000 HWGUSB2-54 WLAN +product GUILLEMOT HWGUSB254LB 0xe010 HWGUSB2-54-LB +product GUILLEMOT HWGUSB254V2AP 0xe020 HWGUSB2-54V2-AP + +/* Hagiwara products */ +product HAGIWARA FGSM 0x0002 FlashGate SmartMedia Card Reader +product HAGIWARA FGCF 0x0003 FlashGate CompactFlash Card Reader +product HAGIWARA FG 0x0005 FlashGate + +/* HAL Corporation products */ +product HAL IMR001 0x0011 Crossam2+USB IR commander + +/* Handspring, Inc. */ +product HANDSPRING VISOR 0x0100 Handspring Visor +product HANDSPRING TREO 0x0200 Handspring Treo +product HANDSPRING TREO600 0x0300 Handspring Treo 600 + +/* Hauppauge Computer Works */ +product HAUPPAUGE WINTV_USB_FM 0x4d12 WinTV USB FM + +/* Hawking Technologies products */ +product HAWKING UF100 0x400c 10/100 USB Ethernet + +/* Hitachi, Ltd. products */ +product HITACHI DVDCAM_DZ_MV100A 0x0004 DVD-CAM DZ-MV100A Camcorder +product HITACHI DVDCAM_USB 0x001e DVDCAM USB HS Interface + +/* HP products */ +product HP 895C 0x0004 DeskJet 895C +product HP 4100C 0x0101 Scanjet 4100C +product HP S20 0x0102 Photosmart S20 +product HP 880C 0x0104 DeskJet 880C +product HP 4200C 0x0105 ScanJet 4200C +product HP CDWRITERPLUS 0x0107 CD-Writer Plus +product HP KBDHUB 0x010c Multimedia Keyboard Hub +product HP G55XI 0x0111 OfficeJet G55xi +product HP HN210W 0x011c HN210W 802.11b WLAN +product HP 49GPLUS 0x0121 49g+ graphing calculator +product HP 6200C 0x0201 ScanJet 6200C +product HP S20b 0x0202 PhotoSmart S20 +product HP 815C 0x0204 DeskJet 815C +product HP 3300C 0x0205 ScanJet 3300C +product HP CDW8200 0x0207 CD-Writer Plus 8200e +product HP MMKEYB 0x020c Multimedia keyboard +product HP 1220C 0x0212 DeskJet 1220C +product HP 810C 0x0304 DeskJet 810C/812C +product HP 4300C 0x0305 Scanjet 4300C +product HP CDW4E 0x0307 CD-Writer+ CD-4e +product HP G85XI 0x0311 OfficeJet G85xi +product HP 1200 0x0317 LaserJet 1200 +product HP 5200C 0x0401 Scanjet 5200C +product HP 830C 0x0404 DeskJet 830C +product HP 3400CSE 0x0405 ScanJet 3400cse +product HP 6300C 0x0601 Scanjet 6300C +product HP 840C 0x0604 DeskJet 840c +product HP 2200C 0x0605 ScanJet 2200C +product HP 5300C 0x0701 Scanjet 5300C +product HP 4400C 0x0705 Scanjet 4400C +product HP 4470C 0x0805 Scanjet 4470C +product HP 82x0C 0x0b01 Scanjet 82x0C +product HP 2300D 0x0b17 Laserjet 2300d +product HP 970CSE 0x1004 Deskjet 970Cse +product HP 5400C 0x1005 Scanjet 5400C +product HP 2215 0x1016 iPAQ 22xx/Jornada 548 +product HP 568J 0x1116 Jornada 568 +product HP 930C 0x1204 DeskJet 930c +product HP P2000U 0x1801 Inkjet P-2000U +product HP 640C 0x2004 DeskJet 640c +product HP 4670V 0x3005 ScanJet 4670v +product HP P1100 0x3102 Photosmart P1100 +product HP OJ4215 0x3d11 OfficeJet 4215 +product HP HN210E 0x811c Ethernet HN210E +product HP2 C500 0x6002 PhotoSmart C500 +product HP HS2300 0x1e1d hs2300 HSDPA (aka MC8775) + +/* HTC products */ +product HTC WINMOBILE 0x00ce HTC USB Sync +product HTC PPC6700MODEM 0x00cf PPC6700 Modem +product HTC SMARTPHONE 0x0a51 SmartPhone USB Sync + +/* HUAWEI products */ +product HUAWEI MOBILE 0x1001 Huawei Mobile +product HUAWEI E220 0x1003 Huawei HSDPA modem + +/* HUAWEI 3com products */ +product HUAWEI3COM WUB320G 0x0009 Aolynk WUB320g + +/* IBM Corporation */ +product IBM USBCDROMDRIVE 0x4427 USB CD-ROM Drive + +/* Imagination Technologies products */ +product IMAGINATION DBX1 0x2107 DBX1 DSP core + +/* Inside Out Networks products */ +product INSIDEOUT EDGEPORT4 0x0001 EdgePort/4 serial ports + +/* In-System products */ +product INSYSTEM F5U002 0x0002 Parallel printer +product INSYSTEM ATAPI 0x0031 ATAPI Adapter +product INSYSTEM ISD110 0x0200 IDE Adapter ISD110 +product INSYSTEM ISD105 0x0202 IDE Adapter ISD105 +product INSYSTEM USBCABLE 0x081a USB cable +product INSYSTEM STORAGE_V2 0x5701 USB Storage Adapter V2 + +/* Intel products */ +product INTEL EASYPC_CAMERA 0x0110 Easy PC Camera +product INTEL TESTBOARD 0x9890 82930 test board + +/* Intersil products */ +product INTERSIL PRISM_GT 0x1000 PrismGT USB 2.0 WLAN +product INTERSIL PRISM_2X 0x3642 Prism2.x or Atmel WLAN + +/* Interpid Control Systems products */ +product INTREPIDCS VALUECAN 0x0601 ValueCAN CAN bus interface +product INTREPIDCS NEOVI 0x0701 NeoVI Blue vehicle bus interface + +/* I/O DATA products */ +product IODATA IU_CD2 0x0204 DVD Multi-plus unit iU-CD2 +product IODATA DVR_UEH8 0x0206 DVD Multi-plus unit DVR-UEH8 +product IODATA USBSSMRW 0x0314 USB-SSMRW SD-card +product IODATA USBSDRW 0x031e USB-SDRW SD-card +product IODATA USBETT 0x0901 USB ETT +product IODATA USBETTX 0x0904 USB ETTX +product IODATA USBETTXS 0x0913 USB ETTX +product IODATA USBWNB11A 0x0919 USB WN-B11 +product IODATA USBWNB11 0x0922 USB Airport WN-B11 +product IODATA ETGUS2 0x0930 ETG-US2 +product IODATA USBRSAQ 0x0a03 Serial USB-RSAQ1 +product IODATA2 USB2SC 0x0a09 USB2.0-SCSI Bridge USB2-SC + +/* Iomega products */ +product IOMEGA ZIP100 0x0001 Zip 100 +product IOMEGA ZIP250 0x0030 Zip 250 + +/* Ituner networks products */ +product ITUNERNET USBLCD2X20 0x0002 USB-LCD 2x20 +product ITUNERNET USBLCD4X20 0xc001 USB-LCD 4x20 + +/* Jablotron products */ +product JABLOTRON PC60B 0x0001 PC-60B + +/* Jaton products */ +product JATON EDA 0x5704 Ethernet + +/* JVC products */ +product JVC GR_DX95 0x000a GR-DX95 +product JVC MP_PRX1 0x3008 MP-PRX1 Ethernet + +/* JRC products */ +product JRC AH_J3001V_J3002V 0x0001 AirH PHONE AH-J3001V/J3002V + +/* Kawatsu products */ +product KAWATSU MH4000P 0x0003 MiniHub 4000P + +/* Keisokugiken Corp. products */ +product KEISOKUGIKEN USBDAQ 0x0068 HKS-0200 USBDAQ + +/* Kensington products */ +product KENSINGTON ORBIT 0x1003 Orbit USB/PS2 trackball +product KENSINGTON TURBOBALL 0x1005 TurboBall + +/* Keyspan products */ +product KEYSPAN USA28_NF 0x0101 USA-28 serial Adapter (no firmware) +product KEYSPAN USA28X_NF 0x0102 USA-28X serial Adapter (no firmware) +product KEYSPAN USA19_NF 0x0103 USA-19 serial Adapter (no firmware) +product KEYSPAN USA18_NF 0x0104 USA-18 serial Adapter (no firmware) +product KEYSPAN USA18X_NF 0x0105 USA-18X serial Adapter (no firmware) +product KEYSPAN USA19W_NF 0x0106 USA-19W serial Adapter (no firmware) +product KEYSPAN USA19 0x0107 USA-19 serial Adapter +product KEYSPAN USA19W 0x0108 USA-19W serial Adapter +product KEYSPAN USA49W_NF 0x0109 USA-49W serial Adapter (no firmware) +product KEYSPAN USA49W 0x010a USA-49W serial Adapter +product KEYSPAN USA19QI_NF 0x010b USA-19QI serial Adapter (no firmware) +product KEYSPAN USA19QI 0x010c USA-19QI serial Adapter +product KEYSPAN USA19Q_NF 0x010d USA-19Q serial Adapter (no firmware) +product KEYSPAN USA19Q 0x010e USA-19Q serial Adapter +product KEYSPAN USA28 0x010f USA-28 serial Adapter +product KEYSPAN USA28XXB 0x0110 USA-28X/XB serial Adapter +product KEYSPAN USA18 0x0111 USA-18 serial Adapter +product KEYSPAN USA18X 0x0112 USA-18X serial Adapter +product KEYSPAN USA28XB_NF 0x0113 USA-28XB serial Adapter (no firmware) +product KEYSPAN USA28XA_NF 0x0114 USA-28XB serial Adapter (no firmware) +product KEYSPAN USA28XA 0x0115 USA-28XA serial Adapter +product KEYSPAN USA18XA_NF 0x0116 USA-18XA serial Adapter (no firmware) +product KEYSPAN USA18XA 0x0117 USA-18XA serial Adapter +product KEYSPAN USA19QW_NF 0x0118 USA-19WQ serial Adapter (no firmware) +product KEYSPAN USA19QW 0x0119 USA-19WQ serial Adapter +product KEYSPAN USA19HA 0x0121 USA-19HS serial Adapter +product KEYSPAN UIA10 0x0201 UIA-10 remote control +product KEYSPAN UIA11 0x0202 UIA-11 remote control + +/* Kingston products */ +product KINGSTON XX1 0x0008 Ethernet +product KINGSTON KNU101TX 0x000a KNU101TX USB Ethernet + +/* Kawasaki products */ +product KLSI DUH3E10BT 0x0008 USB Ethernet +product KLSI DUH3E10BTN 0x0009 USB Ethernet + +/* Kodak products */ +product KODAK DC220 0x0100 Digital Science DC220 +product KODAK DC260 0x0110 Digital Science DC260 +product KODAK DC265 0x0111 Digital Science DC265 +product KODAK DC290 0x0112 Digital Science DC290 +product KODAK DC240 0x0120 Digital Science DC240 +product KODAK DC280 0x0130 Digital Science DC280 + +/* Konica Corp. Products */ +product KONICA CAMERA 0x0720 Digital Color Camera + +/* KYE products */ +product KYE NICHE 0x0001 Niche mouse +product KYE NETSCROLL 0x0003 Genius NetScroll mouse +product KYE FLIGHT2000 0x1004 Flight 2000 joystick +product KYE VIVIDPRO 0x2001 ColorPage Vivid-Pro scanner + +/* Kyocera products */ +product KYOCERA FINECAM_S3X 0x0100 Finecam S3x +product KYOCERA FINECAM_S4 0x0101 Finecam S4 +product KYOCERA FINECAM_S5 0x0103 Finecam S5 +product KYOCERA FINECAM_L3 0x0105 Finecam L3 +product KYOCERA AHK3001V 0x0203 AH-K3001V +product KYOCERA2 CDMA_MSM_K 0x17da Qualcomm Kyocera CDMA Technologies MSM + +/* LaCie products */ +product LACIE HD 0xa601 Hard Disk +product LACIE CDRW 0xa602 CD R/W + +/* Lexar products */ +product LEXAR JUMPSHOT 0x0001 jumpSHOT CompactFlash Reader +product LEXAR CF_READER 0xb002 USB CF Reader + +/* Lexmark products */ +product LEXMARK S2450 0x0009 Optra S 2450 + +/* Linksys products */ +product LINKSYS MAUSB2 0x0105 Camedia MAUSB-2 +product LINKSYS USB10TX1 0x200c USB10TX +product LINKSYS USB10T 0x2202 USB10T Ethernet +product LINKSYS USB100TX 0x2203 USB100TX Ethernet +product LINKSYS USB100H1 0x2204 USB100H1 Ethernet/HPNA +product LINKSYS USB10TA 0x2206 USB10TA Ethernet +product LINKSYS USB10TX2 0x400b USB10TX +product LINKSYS2 WUSB11 0x2219 WUSB11 Wireless Adapter +product LINKSYS2 USB200M 0x2226 USB 2.0 10/100 Ethernet +product LINKSYS3 WUSB11v28 0x2233 WUSB11 v2.8 Wireless Adapter +product LINKSYS4 USB1000 0x0039 USB1000 + +/* Logitech products */ +product LOGITECH M2452 0x0203 M2452 keyboard +product LOGITECH M4848 0x0301 M4848 mouse +product LOGITECH PAGESCAN 0x040f PageScan +product LOGITECH QUICKCAMWEB 0x0801 QuickCam Web +product LOGITECH QUICKCAMPRO 0x0810 QuickCam Pro +product LOGITECH QUICKCAMEXP 0x0840 QuickCam Express +product LOGITECH QUICKCAM 0x0850 QuickCam +product LOGITECH N43 0xc000 N43 +product LOGITECH N48 0xc001 N48 mouse +product LOGITECH MBA47 0xc002 M-BA47 mouse +product LOGITECH WMMOUSE 0xc004 WingMan Gaming Mouse +product LOGITECH BD58 0xc00c BD58 mouse +product LOGITECH UN58A 0xc030 iFeel Mouse +product LOGITECH UN53B 0xc032 iFeel MouseMan +product LOGITECH WMPAD 0xc208 WingMan GamePad Extreme +product LOGITECH WMRPAD 0xc20a WingMan RumblePad +product LOGITECH WMJOY 0xc281 WingMan Force joystick +product LOGITECH BB13 0xc401 USB-PS/2 Trackball +product LOGITECH RK53 0xc501 Cordless mouse +product LOGITECH RB6 0xc503 Cordless keyboard +product LOGITECH MX700 0xc506 Cordless optical mouse +product LOGITECH QUICKCAMPRO2 0xd001 QuickCam Pro + +/* Logitec Corp. products */ +product LOGITEC LDR_H443SU2 0x0033 DVD Multi-plus unit LDR-H443SU2 +product LOGITEC LDR_H443U2 0x00b3 DVD Multi-plus unit LDR-H443U2 + +/* Lucent products */ +product LUCENT EVALKIT 0x1001 USS-720 evaluation kit + +/* Luwen products */ +product LUWEN EASYDISK 0x0005 EasyDisc + +/* Macally products */ +product MACALLY MOUSE1 0x0101 mouse + +/* MCT Corp. */ +product MCT HUB0100 0x0100 Hub +product MCT DU_H3SP_USB232 0x0200 D-Link DU-H3SP USB BAY Hub +product MCT USB232 0x0210 USB-232 Interface +product MCT SITECOM_USB232 0x0230 Sitecom USB-232 Products + +/* Meizu Electronics */ +product MEIZU M6_SL 0x0140 MiniPlayer M6 (SL) + +/* Melco, Inc products */ +product MELCO LUATX1 0x0001 LUA-TX Ethernet +product MELCO LUATX5 0x0005 LUA-TX Ethernet +product MELCO LUA2TX5 0x0009 LUA2-TX Ethernet +product MELCO LUAKTX 0x0012 LUA-KTX Ethernet +product MELCO DUBPXXG 0x001c USB-IDE Bridge: DUB-PxxG +product MELCO LUAU2KTX 0x003d LUA-U2-KTX Ethernet +product MELCO KG54YB 0x005e WLI-U2-KG54-YB WLAN +product MELCO KG54 0x0066 WLI-U2-KG54 WLAN +product MELCO KG54AI 0x0067 WLI-U2-KG54-AI WLAN +product MELCO NINWIFI 0x008b Nintendo Wi-Fi +product MELCO PCOPRS1 0x00b3 PC-OP-RS1 RemoteStation +product MELCO SG54HP 0x00d8 WLI-U2-SG54HP +product MELCO G54HP 0x00d9 WLI-U2-G54HP +product MELCO KG54L 0x00da WLI-U2-KG54L +product MELCO SG54HG 0x00f4 WLI-U2-SG54HG + +/* Merlin products */ +product MERLIN V620 0x1110 Merlin V620 + +/* MetaGeek products */ +product METAGEEK WISPY1B 0x083e MetaGeek Wi-Spy +product METAGEEK WISPY24X 0x083f MetaGeek Wi-Spy 2.4x + +/* Metricom products */ +product METRICOM RICOCHET_GS 0x0001 Ricochet GS + +/* MGE UPS Systems */ +product MGE UPS1 0x0001 MGE UPS SYSTEMS PROTECTIONCENTER 1 +product MGE UPS2 0xffff MGE UPS SYSTEMS PROTECTIONCENTER 2 + +/* Micro Star International products */ +product MSI BT_DONGLE 0x1967 Bluetooth USB dongle +product MSI UB11B 0x6823 UB11B +product MSI RT2570 0x6861 RT2570 +product MSI RT2570_2 0x6865 RT2570 +product MSI RT2570_3 0x6869 RT2570 +product MSI RT2573_1 0x6874 RT2573 +product MSI RT2573_2 0x6877 RT2573 +product MSI RT2573_3 0xa861 RT2573 +product MSI RT2573_4 0xa874 RT2573 + +/* Microsoft products */ +product MICROSOFT SIDEPREC 0x0008 SideWinder Precision Pro +product MICROSOFT INTELLIMOUSE 0x0009 IntelliMouse +product MICROSOFT NATURALKBD 0x000b Natural Keyboard Elite +product MICROSOFT DDS80 0x0014 Digital Sound System 80 +product MICROSOFT SIDEWINDER 0x001a Sidewinder Precision Racing Wheel +product MICROSOFT INETPRO 0x001c Internet Keyboard Pro +product MICROSOFT TBEXPLORER 0x0024 Trackball Explorer +product MICROSOFT INTELLIEYE 0x0025 IntelliEye mouse +product MICROSOFT INETPRO2 0x002b Internet Keyboard Pro +product MICROSOFT MN510 0x006e MN510 Wireless +product MICROSOFT MN110 0x007a 10/100 USB NIC +product MICROSOFT WLINTELLIMOUSE 0x008c Wireless Optical IntelliMouse +product MICROSOFT WLNOTEBOOK 0x00b9 Wireless Optical Mouse (Model 1023) +product MICROSOFT COMFORT3000 0x00d1 Comfort Optical Mouse 3000 (Model 1043) +product MICROSOFT WLNOTEBOOK2 0x00e1 Wireless Optical Mouse 3000 (Model 1056) +product MICROSOFT WLNOTEBOOK3 0x00d2 Wireless Optical Mouse 3000 (Model 1049) +product MICROSOFT WLUSBMOUSE 0x00b9 Wireless USB Mouse +product MICROSOFT XBOX360 0x0292 XBOX 360 WLAN + +/* Microtech products */ +product MICROTECH SCSIDB25 0x0004 USB-SCSI-DB25 +product MICROTECH SCSIHD50 0x0005 USB-SCSI-HD50 +product MICROTECH DPCM 0x0006 USB CameraMate +product MICROTECH FREECOM 0xfc01 Freecom USB-IDE + +/* Microtek products */ +product MICROTEK 336CX 0x0094 Phantom 336CX - C3 scanner +product MICROTEK X6U 0x0099 ScanMaker X6 - X6U +product MICROTEK C6 0x009a Phantom C6 scanner +product MICROTEK 336CX2 0x00a0 Phantom 336CX - C3 scanner +product MICROTEK V6USL 0x00a3 ScanMaker V6USL +product MICROTEK V6USL2 0x80a3 ScanMaker V6USL +product MICROTEK V6UL 0x80ac ScanMaker V6UL + +/* Microtune, Inc. products */ +product MICROTUNE BT_DONGLE 0x1000 Bluetooth USB dongle + +/* Midiman products */ +product MIDIMAN MIDISPORT2X2 0x1001 Midisport 2x2 + +/* MindsAtWork products */ +product MINDSATWORK WALLET 0x0001 Digital Wallet + +/* Minolta Co., Ltd. */ +product MINOLTA 2300 0x4001 Dimage 2300 +product MINOLTA S304 0x4007 Dimage S304 +product MINOLTA X 0x4009 Dimage X +product MINOLTA 5400 0x400e Dimage 5400 +product MINOLTA F300 0x4011 Dimage F300 +product MINOLTA E223 0x4017 Dimage E223 + +/* Mitsumi products */ +product MITSUMI CDRRW 0x0000 CD-R/RW Drive +product MITSUMI BT_DONGLE 0x641f Bluetooth USB dongle +product MITSUMI FDD 0x6901 USB FDD + +/* Mobility products */ +product MOBILITY EA 0x0204 Ethernet +product MOBILITY EASIDOCK 0x0304 EasiDock Ethernet + +/* MosChip products */ +product MOSCHIP MCS7703 0x7703 MCS7703 Serial Port Adapter +product MOSCHIP MCS7830 0x7830 MCS7830 Ethernet + +/* Motorola products */ +product MOTOROLA MC141555 0x1555 MC141555 hub controller +product MOTOROLA SB4100 0x4100 SB4100 USB Cable Modem +product MOTOROLA2 A41XV32X 0x2a22 A41x/V32x Mobile Phones +product MOTOROLA2 E398 0x4810 E398 Mobile Phone +product MOTOROLA2 USBLAN 0x600c USBLAN +product MOTOROLA2 USBLAN2 0x6027 USBLAN + +/* MultiTech products */ +product MULTITECH ATLAS 0xf101 MT5634ZBA-USB modem + +/* Mustek products */ +product MUSTEK 1200CU 0x0001 1200 CU scanner +product MUSTEK 600CU 0x0002 600 CU scanner +product MUSTEK 1200USB 0x0003 1200 USB scanner +product MUSTEK 1200UB 0x0006 1200 UB scanner +product MUSTEK 1200USBPLUS 0x0007 1200 USB Plus scanner +product MUSTEK 1200CUPLUS 0x0008 1200 CU Plus scanner +product MUSTEK BEARPAW1200F 0x0010 BearPaw 1200F scanner +product MUSTEK BEARPAW1200TA 0x021e BearPaw 1200TA scanner +product MUSTEK 600USB 0x0873 600 USB scanner +product MUSTEK MDC800 0xa800 MDC-800 digital camera + +/* M-Systems products */ +product MSYSTEMS DISKONKEY 0x0010 DiskOnKey +product MSYSTEMS DISKONKEY2 0x0011 DiskOnKey + +/* Myson products */ +product MYSON HEDEN 0x8818 USB-IDE +product MYSON STARREADER 0x9920 USB flash card adapter + +/* National Semiconductor */ +product NATIONAL BEARPAW1200 0x1000 BearPaw 1200 +product NATIONAL BEARPAW2400 0x1001 BearPaw 2400 + +/* NEC products */ +product NEC HUB 0x55aa hub +product NEC HUB_B 0x55ab hub + +/* NEODIO products */ +product NEODIO ND3260 0x3260 8-in-1 Multi-format Flash Controller +product NEODIO ND5010 0x5010 Multi-format Flash Controller + +/* Netac products */ +product NETAC CF_CARD 0x1060 USB-CF-Card +product NETAC ONLYDISK 0x0003 OnlyDisk + +/* NetChip Technology Products */ +product NETCHIP TURBOCONNECT 0x1080 Turbo-Connect +product NETCHIP CLIK_40 0xa140 USB Clik! 40 +product NETCHIP ETHERNETGADGET 0xa4a2 Linux Ethernet/RNDIS gadget on pxa210/25x/26x + +/* Netgear products */ +product NETGEAR EA101 0x1001 Ethernet +product NETGEAR EA101X 0x1002 Ethernet +product NETGEAR FA101 0x1020 Ethernet 10/100, USB1.1 +product NETGEAR FA120 0x1040 USB 2.0 Ethernet +product NETGEAR WG111V2_2 0x4240 PrismGT USB 2.0 WLAN +product NETGEAR WG111U 0x4300 WG111U +product NETGEAR WG111U_NF 0x4301 WG111U (no firmware) +product NETGEAR WG111V2 0x6a00 WG111V2 +product NETGEAR2 MA101 0x4100 MA101 +product NETGEAR2 MA101B 0x4102 MA101 Rev B +product NETGEAR3 WG111T 0x4250 WG111T +product NETGEAR3 WG111T_NF 0x4251 WG111T (no firmware) +product NETGEAR3 WPN111 0x5f00 WPN111 +product NETGEAR3 WPN111_NF 0x5f01 WPN111 (no firmware) + +/* Nikon products */ +product NIKON E990 0x0102 Digital Camera E990 +product NIKON LS40 0x4000 CoolScan LS40 ED +product NIKON D300 0x041a Digital Camera D300 + +/* NovaTech Products */ +product NOVATECH NV902 0x9020 NovaTech NV-902W +product NOVATECH RT2573 0x9021 RT2573 + +/* Novatel Wireless products */ +product NOVATEL V640 0x1100 Merlin V620 +product NOVATEL CDMA_MODEM 0x1110 Novatel Wireless Merlin CDMA +product NOVATEL V620 0x1110 Merlin V620 +product NOVATEL V740 0x1120 Merlin V740 +product NOVATEL V720 0x1130 Merlin V720 +product NOVATEL U740 0x1400 Merlin U740 +product NOVATEL U740_2 0x1410 Merlin U740 +product NOVATEL U870 0x1420 Merlin U870 +product NOVATEL XU870 0x1430 Merlin XU870 +product NOVATEL X950D 0x1450 Merlin X950D +product NOVATEL ES620 0x2100 ES620 CDMA +product NOVATEL U720 0x2110 Merlin U720 +product NOVATEL U727 0x4100 Merlin U727 CDMA +product NOVATEL MC950D 0x4400 Novatel MC950D HSUPA +product NOVATEL ZEROCD 0x5010 Novatel ZeroCD +product NOVATEL ZEROCD2 0x5030 Novatel ZeroCD +product NOVATEL U760 0x6000 Novatel U760 +product NOVATEL2 FLEXPACKGPS 0x0100 NovAtel FlexPack GPS receiver + +/* Merlin products */ +product MERLIN V620 0x1110 Merlin V620 + +/* Olympus products */ +product OLYMPUS C1 0x0102 C-1 Digital Camera +product OLYMPUS C700 0x0105 C-700 Ultra Zoom + +/* OmniVision Technologies, Inc. products */ +product OMNIVISION OV511 0x0511 OV511 Camera +product OMNIVISION OV511PLUS 0xa511 OV511+ Camera + +/* OnSpec Electronic, Inc. */ +product ONSPEC SDS_HOTFIND_D 0x0400 SDS-infrared.com Hotfind-D Infrared Camera +product ONSPEC MDCFE_B_CF_READER 0xa000 MDCFE-B USB CF Reader +product ONSPEC CFMS_RW 0xa001 SIIG/Datafab Memory Stick+CF Reader/Writer +product ONSPEC READER 0xa003 Datafab-based Reader +product ONSPEC CFSM_READER 0xa005 PNY/Datafab CF+SM Reader +product ONSPEC CFSM_READER2 0xa006 Simple Tech/Datafab CF+SM Reader +product ONSPEC MDSM_B_READER 0xa103 MDSM-B reader +product ONSPEC CFSM_COMBO 0xa109 USB to CF + SM Combo (LC1) +product ONSPEC UCF100 0xa400 FlashLink UCF-100 CompactFlash Reader +product ONSPEC2 IMAGEMATE_SDDR55 0xa103 ImageMate SDDR55 + +/* Option products */ +product OPTION VODAFONEMC3G 0x5000 Vodafone Mobile Connect 3G datacard +product OPTION GT3G 0x6000 GlobeTrotter 3G datacard +product OPTION GT3GQUAD 0x6300 GlobeTrotter 3G QUAD datacard +product OPTION GT3GPLUS 0x6600 GlobeTrotter 3G+ datacard +product OPTION GTICON322 0xd033 GlobeTrotter Icon322 storage +product OPTION GTMAX36 0x6701 GlobeTrotter Max 3.6 Modem +product OPTION GTMAXHSUPA 0x7001 GlobeTrotter HSUPA + +/* OQO */ +product OQO WIFI01 0x0002 model 01 WiFi interface +product OQO BT01 0x0003 model 01 Bluetooth interface +product OQO ETHER01PLUS 0x7720 model 01+ Ethernet +product OQO ETHER01 0x8150 model 01 Ethernet interface + +/* Palm Computing, Inc. product */ +product PALM SERIAL 0x0080 USB Serial +product PALM M500 0x0001 Palm m500 +product PALM M505 0x0002 Palm m505 +product PALM M515 0x0003 Palm m515 +product PALM I705 0x0020 Palm i705 +product PALM TUNGSTEN_Z 0x0031 Palm Tungsten Z +product PALM M125 0x0040 Palm m125 +product PALM M130 0x0050 Palm m130 +product PALM TUNGSTEN_T 0x0060 Palm Tungsten T +product PALM ZIRE31 0x0061 Palm Zire 31 +product PALM ZIRE 0x0070 Palm Zire + +/* Panasonic products */ +product PANASONIC LS120CAM 0x0901 LS-120 Camera +product PANASONIC KXL840AN 0x0d01 CD-R Drive KXL-840AN +product PANASONIC KXLRW32AN 0x0d09 CD-R Drive KXL-RW32AN +product PANASONIC KXLCB20AN 0x0d0a CD-R Drive KXL-CB20AN +product PANASONIC KXLCB35AN 0x0d0e DVD-ROM & CD-R/RW +product PANASONIC SDCAAE 0x1b00 MultiMediaCard + +/* Peracom products */ +product PERACOM SERIAL1 0x0001 Serial +product PERACOM ENET 0x0002 Ethernet +product PERACOM ENET3 0x0003 At Home Ethernet +product PERACOM ENET2 0x0005 Ethernet + +/* Philips products */ +product PHILIPS DSS350 0x0101 DSS 350 Digital Speaker System +product PHILIPS DSS 0x0104 DSS XXX Digital Speaker System +product PHILIPS HUB 0x0201 hub +product PHILIPS PCA646VC 0x0303 PCA646VC PC Camera +product PHILIPS PCVC680K 0x0308 PCVC680K Vesta Pro PC Camera +product PHILIPS DSS150 0x0471 DSS 150 Digital Speaker System +product PHILIPS SNU5600 0x1236 SNU5600 +product PHILIPS UM10016 0x1552 ISP 1581 Hi-Speed USB MPEG2 Encoder Reference Kit +product PHILIPS DIVAUSB 0x1801 DIVA USB mp3 player + +/* Philips Semiconductor products */ +product PHILIPSSEMI HUB1122 0x1122 hub + +/* P.I. Engineering products */ +product PIENGINEERING PS2USB 0x020b PS2 to Mac USB Adapter + +/* Planex Communications products */ +product PLANEX GW_US11H 0x14ea GW-US11H WLAN +product PLANEX2 GW_US11S 0x3220 GW-US11S WLAN +product PLANEX2 GW_US54GXS 0x5303 GW-US54GXS WLAN +product PLANEX2 GWUS54HP 0xab01 GW-US54HP +product PLANEX2 GWUS54MINI2 0xab50 GW-US54Mini2 +product PLANEX2 GWUS54SG 0xc002 GW-US54SG +product PLANEX2 GWUS54GZL 0xc007 GW-US54GZL +product PLANEX2 GWUS54GD 0xed01 GW-US54GD +product PLANEX2 GWUSMM 0xed02 GW-USMM +product PLANEX3 GWUS54GZ 0xab10 GW-US54GZ +product PLANEX3 GU1000T 0xab11 GU-1000T +product PLANEX3 GWUS54MINI 0xab13 GW-US54Mini + +/* Plextor Corp. */ +product PLEXTOR 40_12_40U 0x0011 PlexWriter 40/12/40U + +/* PLX products */ +product PLX TESTBOARD 0x9060 test board + +/* PNY products */ +product PNY ATTACHE2 0x0010 USB 2.0 Flash Drive + +/* PortGear products */ +product PORTGEAR EA8 0x0008 Ethernet +product PORTGEAR EA9 0x0009 Ethernet + +/* Portsmith products */ +product PORTSMITH EEA 0x3003 Express Ethernet + +/* Primax products */ +product PRIMAX G2X300 0x0300 G2-200 scanner +product PRIMAX G2E300 0x0301 G2E-300 scanner +product PRIMAX G2300 0x0302 G2-300 scanner +product PRIMAX G2E3002 0x0303 G2E-300 scanner +product PRIMAX 9600 0x0340 Colorado USB 9600 scanner +product PRIMAX 600U 0x0341 Colorado 600u scanner +product PRIMAX 6200 0x0345 Visioneer 6200 scanner +product PRIMAX 19200 0x0360 Colorado USB 19200 scanner +product PRIMAX 1200U 0x0361 Colorado 1200u scanner +product PRIMAX G600 0x0380 G2-600 scanner +product PRIMAX 636I 0x0381 ReadyScan 636i +product PRIMAX G2600 0x0382 G2-600 scanner +product PRIMAX G2E600 0x0383 G2E-600 scanner +product PRIMAX COMFORT 0x4d01 Comfort +product PRIMAX MOUSEINABOX 0x4d02 Mouse-in-a-Box +product PRIMAX PCGAUMS1 0x4d04 Sony PCGA-UMS1 + +/* Prolific products */ +product PROLIFIC PL2301 0x0000 PL2301 Host-Host interface +product PROLIFIC PL2302 0x0001 PL2302 Host-Host interface +product PROLIFIC RSAQ2 0x04bb PL2303 Serial (IODATA USB-RSAQ2) +product PROLIFIC PL2303 0x2303 PL2303 Serial (ATEN/IOGEAR UC232A) +product PROLIFIC PL2305 0x2305 Parallel printer +product PROLIFIC ATAPI4 0x2307 ATAPI-4 Controller +product PROLIFIC PL2501 0x2501 PL2501 Host-Host interface +product PROLIFIC PHAROS 0xaaa0 Prolific Pharos +product PROLIFIC RSAQ3 0xaaa2 PL2303 Serial Adapter (IODATA USB-RSAQ3) +product PROLIFIC2 WSIM 0x2001 Willcom WSIM + +/* Putercom products */ +product PUTERCOM UPA100 0x047e USB-1284 BRIDGE + +/* Qcom products */ +product QCOM RT2573 0x6196 RT2573 +product QCOM RT2573_2 0x6229 RT2573 + +/* Qualcomm products */ +product QUALCOMM CDMA_MSM 0x6000 CDMA Technologies MSM phone +product QUALCOMM2 RWT_FCT 0x3100 RWT FCT-CDMA 2000 1xRTT modem +product QUALCOMM2 CDMA_MSM 0x3196 CDMA Technologies MSM modem +product QUALCOMMINC CDMA_MSM 0x0001 CDMA Technologies MSM modem +product QUALCOMMINC ZTE_STOR 0x2000 USB ZTE Storage +product QUALCOMMINC AC8700 0xfffe CDMA 1xEVDO USB modem + +/* Qtronix products */ +product QTRONIX 980N 0x2011 Scorpion-980N keyboard + +/* Quickshot products */ +product QUICKSHOT STRIKEPAD 0x6238 USB StrikePad + +/* Radio Shack */ +product RADIOSHACK USBCABLE 0x4026 USB to Serial Cable + +/* Rainbow Technologies products */ +product RAINBOW IKEY2000 0x1200 i-Key 2000 + +/* Ralink Technology products */ +product RALINK RT2570 0x1706 RT2500USB Wireless Adapter +product RALINK RT2570_2 0x2570 RT2500USB Wireless Adapter +product RALINK RT2573 0x2573 RT2501USB Wireless Adapter +product RALINK RT2671 0x2671 RT2601USB Wireless Adapter +product RALINK RT2570_3 0x9020 RT2500USB Wireless Adapter +product RALINK RT2573_2 0x9021 RT2501USB Wireless Adapter + +/* ReakTek products */ +/* Green House and CompUSA OEM this part */ +product REALTEK USBKR100 0x8150 USBKR100 USB Ethernet +product REALTEK RTL8187 0x8187 RTL8187 Wireless Adapter + +/* Ricoh products */ +product RICOH VGPVCC2 0x1830 VGP-VCC2 Camera +product RICOH VGPVCC3 0x1832 VGP-VCC3 Camera +product RICOH VGPVCC2_2 0x1833 VGP-VCC2 Camera +product RICOH VGPVCC2_3 0x1834 VGP-VCC2 Camera +product RICOH VGPVCC7 0x183a VGP-VCC7 Camera +product RICOH VGPVCC8 0x183b VGP-VCC8 Camera + +/* Roland products */ +product ROLAND UM1 0x0009 UM-1 MIDI I/F +product ROLAND UM880N 0x0014 EDIROL UM-880 MIDI I/F (native) +product ROLAND UM880G 0x0015 EDIROL UM-880 MIDI I/F (generic) + +/* Rockfire products */ +product ROCKFIRE GAMEPAD 0x2033 gamepad 203USB + +/* RATOC Systems products */ +product RATOC REXUSB60 0xb000 REX-USB60 + +/* Sagem products */ +product SAGEM USBSERIAL 0x0027 USB-Serial Controller +product SAGEM XG760A 0x004a XG-760A +product SAGEM XG76NA 0x0062 XG-76NA + +/* Samsung products */ +product SAMSUNG ML6060 0x3008 ML-6060 laser printer +product SAMSUNG YP_U2 0x5050 YP-U2 MP3 Player +product SAMSUNG I500 0x6601 I500 Palm USB Phone + +/* Samsung Techwin products */ +product SAMSUNG_TECHWIN DIGIMAX_410 0x000a Digimax 410 + +/* SanDisk products */ +product SANDISK SDDR05A 0x0001 ImageMate SDDR-05a +product SANDISK SDDR31 0x0002 ImageMate SDDR-31 +product SANDISK SDDR05 0x0005 ImageMate SDDR-05 +product SANDISK SDDR12 0x0100 ImageMate SDDR-12 +product SANDISK SDDR09 0x0200 ImageMate SDDR-09 +product SANDISK SDDR75 0x0810 ImageMate SDDR-75 +product SANDISK SDCZ2_256 0x7104 Cruzer Mini 256MB +product SANDISK SDCZ4_128 0x7112 Cruzer Micro 128MB +product SANDISK SDCZ4_256 0x7113 Cruzer Micro 256MB + +/* Sanyo Electric products */ +product SANYO SCP4900 0x0701 Sanyo SCP-4900 USB Phone + +/* ScanLogic products */ +product SCANLOGIC SL11R 0x0002 SL11R IDE Adapter +product SCANLOGIC 336CX 0x0300 Phantom 336CX - C3 scanner + +/* Senao products */ +product SENAO NUB8301 0x2000 NUB-8301 + +/* ShanTou products */ +product SHANTOU ST268 0x0268 ST268 +product SHANTOU DM9601 0x9601 DM 9601 + +/* Shark products */ +product SHARK PA 0x0400 Pocket Adapter + +/* Sharp products */ +product SHARP SL5500 0x8004 Zaurus SL-5500 PDA +product SHARP SLA300 0x8005 Zaurus SL-A300 PDA +product SHARP SL5600 0x8006 Zaurus SL-5600 PDA +product SHARP SLC700 0x8007 Zaurus SL-C700 PDA +product SHARP SLC750 0x9031 Zaurus SL-C750 PDA +product SHARP WZERO3ES 0x9123 W-ZERO3 ES Smartphone + +/* Shuttle Technology products */ +product SHUTTLE EUSB 0x0001 E-USB Bridge +product SHUTTLE EUSCSI 0x0002 eUSCSI Bridge +product SHUTTLE SDDR09 0x0003 ImageMate SDDR09 +product SHUTTLE EUSBCFSM 0x0005 eUSB SmartMedia / CompactFlash Adapter +product SHUTTLE ZIOMMC 0x0006 eUSB MultiMediaCard Adapter +product SHUTTLE HIFD 0x0007 Sony Hifd +product SHUTTLE EUSBATAPI 0x0009 eUSB ATA/ATAPI Adapter +product SHUTTLE CF 0x000a eUSB CompactFlash Adapter +product SHUTTLE EUSCSI_B 0x000b eUSCSI Bridge +product SHUTTLE EUSCSI_C 0x000c eUSCSI Bridge +product SHUTTLE CDRW 0x0101 CD-RW Device +product SHUTTLE EUSBORCA 0x0325 eUSB ORCA Quad Reader + +/* Siemens products */ +product SIEMENS SPEEDSTREAM 0x1001 SpeedStream +product SIEMENS SPEEDSTREAM22 0x1022 SpeedStream 1022 +product SIEMENS2 WLL013 0x001b WLL013 +product SIEMENS2 ES75 0x0034 GSM module MC35 +product SIEMENS2 WL54G 0x3c06 54g USB Network Adapter +product SIEMENS3 SX1 0x0001 SX1 +product SIEMENS3 X65 0x0003 X65 +product SIEMENS3 X75 0x0004 X75 + +/* Sierra Wireless products */ +product SIERRA AIRCARD580 0x0112 Sierra Wireless AirCard 580 +product SIERRA AIRCARD595 0x0019 Sierra Wireless AirCard 595 +product SIERRA AC595U 0x0120 Sierra Wireless AirCard 595U +product SIERRA AC597E 0x0021 Sierra Wireless AirCard 597E +product SIERRA C597 0x0023 Sierra Wireless Compass 597 +product SIERRA AC875 0x6820 Sierra Wireless AirCard 875 +product SIERRA AC880 0x6850 Sierra Wireless AirCard 880 +product SIERRA AC881 0x6851 Sierra Wireless AirCard 881 +product SIERRA AC880E 0x6852 Sierra Wireless AirCard 880E +product SIERRA AC881E 0x6853 Sierra Wireless AirCard 881E +product SIERRA AC880U 0x6855 Sierra Wireless AirCard 880U +product SIERRA AC881U 0x6856 Sierra Wireless AirCard 881U +product SIERRA AC885U 0x6880 Sierra Wireless AirCard 885U +product SIERRA EM5625 0x0017 EM5625 +product SIERRA MC5720 0x0218 MC5720 Wireless Modem +product SIERRA MC5720_2 0x0018 MC5720 +product SIERRA MC5725 0x0020 MC5725 +product SIERRA MINI5725 0x0220 Sierra Wireless miniPCI 5275 +product SIERRA MC8755_2 0x6802 MC8755 +product SIERRA MC8765 0x6803 MC8765 +product SIERRA MC8755 0x6804 MC8755 +product SIERRA AC875U 0x6812 AC875U HSDPA USB Modem +product SIERRA MC8755_3 0x6813 MC8755 HSDPA +product SIERRA MC8775_2 0x6815 MC8775 +product SIERRA AIRCARD875 0x6820 Aircard 875 HSDPA +product SIERRA MC8780 0x6832 MC8780 +product SIERRA MC8781 0x6833 MC8781 +product SIERRA TRUINSTALL 0x0fff Aircard Tru Installer + +/* Sigmatel products */ +product SIGMATEL I_BEAD100 0x8008 i-Bead 100 MP3 Player + +/* SIIG products */ +/* Also: Omnidirectional Control Technology products */ +product SIIG DIGIFILMREADER 0x0004 DigiFilm-Combo Reader +product SIIG WINTERREADER 0x0330 WINTERREADER Reader +product SIIG2 USBTOETHER 0x0109 USB TO Ethernet +product SIIG2 US2308 0x0421 Serial + +/* Silicom products */ +product SILICOM U2E 0x0001 U2E +product SILICOM GPE 0x0002 Psion Gold Port Ethernet + +/* SI Labs */ +product SILABS POLOLU 0x803b Pololu Serial +product SILABS ARGUSISP 0x8066 Argussoft ISP +product SILABS CRUMB128 0x807a Crumb128 board +product SILABS DEGREE 0x80ca Degree Controls Inc +product SILABS TRAQMATE 0x80ed Track Systems Traqmate +product SILABS SUUNTO 0x80f6 Suunto Sports Instrument +product SILABS BURNSIDE 0x813d Burnside Telecon Deskmobile +product SILABS HELICOM 0x815e Helicomm IP-Link 1220-DVM +product SILABS CP2102 0xea60 SILABS USB UART +product SILABS LIPOWSKY_JTAG 0x81c8 Lipowsky Baby-JTAG +product SILABS LIPOWSKY_LIN 0x81e2 Lipowsky Baby-LIN +product SILABS LIPOWSKY_HARP 0x8218 Lipowsky HARP-1 +product SILABS CP2102 0xea60 SILABS USB UARTa +product SILABS CP210X_2 0xea61 CP210x Serial +product SILABS2 DCU11CLONE 0xaa26 DCU-11 clone + +/* Silicon Portals Inc. */ +product SILICONPORTALS YAPPH_NF 0x0200 YAP Phone (no firmware) +product SILICONPORTALS YAPPHONE 0x0201 YAP Phone + +/* Sirius Technologies products */ +product SIRIUS ROADSTER 0x0001 NetComm Roadster II 56 USB + +/* Sitecom products */ +product SITECOM LN029 0x182d USB 2.0 Ethernet +product SITECOM SERIAL 0x2068 USB to serial cable (v2) +product SITECOM2 WL022 0x182d WL-022 + +/* Sitecom Europe products */ +product SITECOMEU LN028 0x061c LN-028 +product SITECOMEU WL113 0x9071 WL-113 +product SITECOMEU ZD1211B 0x9075 ZD1211B +product SITECOMEU WL172 0x90ac WL-172 +product SITECOMEU WL113R2 0x9712 WL-113 rev 2 + +/* Skanhex Technology products */ +product SKANHEX MD_7425 0x410a MD 7425 Camera +product SKANHEX SX_520Z 0x5200 SX 520z Camera + +/* SmartBridges products */ +product SMARTBRIDGES SMARTLINK 0x0001 SmartLink USB Ethernet +product SMARTBRIDGES SMARTNIC 0x0003 smartNIC 2 PnP Ethernet + +/* SMC products */ +product SMC 2102USB 0x0100 10Mbps Ethernet +product SMC 2202USB 0x0200 10/100 Ethernet +product SMC 2206USB 0x0201 EZ Connect USB Ethernet +product SMC 2862WG 0xee13 EZ Connect Wireless Adapter +product SMC2 2020HUB 0x2020 USB Hub +product SMC3 2662WUSB 0xa002 2662W-AR Wireless + +/* SOHOware products */ +product SOHOWARE NUB100 0x9100 10/100 USB Ethernet +product SOHOWARE NUB110 0x9110 10/100 USB Ethernet + +/* SOLID YEAR products */ +product SOLIDYEAR KEYBOARD 0x2101 Solid Year USB keyboard + +/* SONY products */ +product SONY DSC 0x0010 DSC cameras +product SONY MS_NW_MS7 0x0025 Memorystick NW-MS7 +product SONY PORTABLE_HDD_V2 0x002b Portable USB Harddrive V2 +product SONY MSACUS1 0x002d Memorystick MSAC-US1 +product SONY HANDYCAM 0x002e Handycam +product SONY MSC 0x0032 MSC memory stick slot +product SONY CLIE_35 0x0038 Sony Clie v3.5 +product SONY MS_PEG_N760C 0x0058 PEG N760c Memorystick +product SONY CLIE_40 0x0066 Sony Clie v4.0 +product SONY MS_MSC_U03 0x0069 Memorystick MSC-U03 +product SONY CLIE_40_MS 0x006d Sony Clie v4.0 Memory Stick slot +product SONY CLIE_S360 0x0095 Sony Clie s360 +product SONY CLIE_41_MS 0x0099 Sony Clie v4.1 Memory Stick slot +product SONY CLIE_41 0x009a Sony Clie v4.1 +product SONY CLIE_NX60 0x00da Sony Clie nx60 +product SONY CLIE_TH55 0x0144 Sony Clie th55 +product SONY CLIE_TJ37 0x0169 Sony Clie tj37 +product SONY RF_RECEIVER 0x01db Sony RF mouse/kbd Receiver VGP-WRC1 + +/* Sony Ericsson products */ +product SONYERICSSON DCU10 0x0528 USB Cable + +/* SOURCENEXT products */ +product SOURCENEXT KEIKAI8 0x039f KeikaiDenwa 8 +product SOURCENEXT KEIKAI8_CHG 0x012e KeikaiDenwa 8 with charger + +/* SparkLAN products */ +product SPARKLAN RT2573 0x0004 RT2573 + +/* Sphairon Access Systems GmbH products */ +product SPHAIRON UB801R 0x0110 UB801R + +/* Stelera Wireless products */ +product STELERA ZEROCD 0x1000 Zerocd Installer +product STELERA C105 0x1002 Stelera/Bandrish C105 USB + +/* STMicroelectronics products */ +product STMICRO BIOCPU 0x2016 Biometric Coprocessor +product STMICRO COMMUNICATOR 0x7554 USB Communicator + +/* STSN products */ +product STSN STSN0001 0x0001 Internet Access Device + +/* SUN Corporation products */ +product SUNTAC DS96L 0x0003 SUNTAC U-Cable type D2 +product SUNTAC PS64P1 0x0005 SUNTAC U-Cable type P1 +product SUNTAC VS10U 0x0009 SUNTAC Slipper U +product SUNTAC IS96U 0x000a SUNTAC Ir-Trinity +product SUNTAC AS64LX 0x000b SUNTAC U-Cable type A3 +product SUNTAC AS144L4 0x0011 SUNTAC U-Cable type A4 + +/* Sun Microsystems products */ +product SUN KEYBOARD 0x0005 Type 6 USB keyboard +/* XXX The above is a North American PC style keyboard possibly */ +product SUN MOUSE 0x0100 Type 6 USB mouse + +/* Supra products */ +product DIAMOND2 SUPRAEXPRESS56K 0x07da Supra Express 56K modem +product DIAMOND2 SUPRA2890 0x0b4a SupraMax 2890 56K Modem +product DIAMOND2 RIO600USB 0x5001 Rio 600 USB +product DIAMOND2 RIO800USB 0x5002 Rio 800 USB + +/* Surecom Technology products */ +product SURECOM RT2570 0x11f3 RT2570 +product SURECOM RT2573 0x31f3 RT2573 + +/* Sweex products */ +product SWEEX ZD1211 0x1809 ZD1211 + +/* System TALKS, Inc. */ +product SYSTEMTALKS SGCX2UL 0x1920 SGC-X2UL + +/* Tapwave products */ +product TAPWAVE ZODIAC 0x0100 Zodiac + +/* Taugagreining products */ +product TAUGA CAMERAMATE 0x0005 CameraMate (DPCM_USB) + +/* TDK products */ +product TDK UPA9664 0x0115 USB-PDC Adapter UPA9664 +product TDK UCA1464 0x0116 USB-cdmaOne Adapter UCA1464 +product TDK UHA6400 0x0117 USB-PHS Adapter UHA6400 +product TDK UPA6400 0x0118 USB-PHS Adapter UPA6400 +product TDK BT_DONGLE 0x0309 Bluetooth USB dongle + +/* TEAC products */ +product TEAC FD05PUB 0x0000 FD-05PUB floppy + +/* Tekram Technology products */ +product TEKRAM QUICKWLAN 0x1630 QuickWLAN +product TEKRAM ZD1211_1 0x5630 ZD1211 +product TEKRAM ZD1211_2 0x6630 ZD1211 + +/* Telex Communications products */ +product TELEX MIC1 0x0001 Enhanced USB Microphone + +/* Ten X Technology, Inc. */ +product TENX UAUDIO0 0xf211 USB audio headset + +/* Texas Intel products */ +product TI UTUSB41 0x1446 UT-USB41 hub +product TI TUSB2046 0x2046 TUSB2046 hub + +/* Thrustmaster products */ +product THRUST FUSION_PAD 0xa0a3 Fusion Digital Gamepad + +/* Topre Corporation products */ +product TOPRE HHKB 0x0100 HHKB Professional + +/* Toshiba Corporation products */ +product TOSHIBA POCKETPC_E740 0x0706 PocketPC e740 + +/* Trek Technology products */ +product TREK THUMBDRIVE 0x1111 ThumbDrive +product TREK MEMKEY 0x8888 IBM USB Memory Key +product TREK THUMBDRIVE_8MB 0x9988 ThumbDrive_8MB + +/* Tripp-Lite products */ +product TRIPPLITE U209 0x2008 Serial + +/* Trumpion products */ +product TRUMPION T33520 0x1001 T33520 USB Flash Card Controller +product TRUMPION C3310 0x1100 Comotron C3310 MP3 player +product TRUMPION MP3 0x1200 MP3 player + +/* TwinMOS */ +product TWINMOS G240 0xa006 G240 +product TWINMOS MDIV 0x1325 Memory Disk IV + +/* Ubiquam products */ +product UBIQUAM UALL 0x3100 CDMA 1xRTT USB Modem (U-100/105/200/300/520) + +/* Ultima products */ +product ULTIMA 1200UBPLUS 0x4002 1200 UB Plus scanner + +/* UMAX products */ +product UMAX ASTRA1236U 0x0002 Astra 1236U Scanner +product UMAX ASTRA1220U 0x0010 Astra 1220U Scanner +product UMAX ASTRA2000U 0x0030 Astra 2000U Scanner +product UMAX ASTRA2100U 0x0130 Astra 2100U Scanner +product UMAX ASTRA2200U 0x0230 Astra 2200U Scanner +product UMAX ASTRA3400 0x0060 Astra 3400 Scanner + +/* U-MEDIA Communications products */ +product UMEDIA TEW444UBEU 0x3006 TEW-444UB EU +product UMEDIA TEW444UBEU_NF 0x3007 TEW-444UB EU (no firmware) +product UMEDIA TEW429UB_A 0x300a TEW-429UB_A +product UMEDIA TEW429UB 0x300b TEW-429UB +product UMEDIA TEW429UBC1 0x300d TEW-429UB C1 +product UMEDIA ALL0298V2 0x3204 ALL0298 v2 +product UMEDIA AR5523_2 0x3205 AR5523 +product UMEDIA AR5523_2_NF 0x3206 AR5523 (no firmware) + +/* Universal Access products */ +product UNIACCESS PANACHE 0x0101 Panache Surf USB ISDN Adapter + +/* U.S. Robotics products */ +product USR USR5423 0x0121 USR5423 WLAN + +/* VIA Technologies products */ +product VIA USB2IDEBRIDGE 0x6204 USB 2.0 IDE Bridge + +/* USI products */ +product USI MC60 0x10c5 MC60 Serial + +/* VidzMedia products */ +product VIDZMEDIA MONSTERTV 0x4fb1 MonsterTV P2H + +/* Vision products */ +product VISION VC6452V002 0x0002 CPiA Camera + +/* Visioneer products */ +product VISIONEER 7600 0x0211 OneTouch 7600 +product VISIONEER 5300 0x0221 OneTouch 5300 +product VISIONEER 3000 0x0224 Scanport 3000 +product VISIONEER 6100 0x0231 OneTouch 6100 +product VISIONEER 6200 0x0311 OneTouch 6200 +product VISIONEER 8100 0x0321 OneTouch 8100 +product VISIONEER 8600 0x0331 OneTouch 8600 + +/* Vivitar products */ +product VIVITAR 35XX 0x0003 Vivicam 35Xx + +/* VTech products */ +product VTECH RT2570 0x3012 RT2570 +product VTECH ZD1211B 0x3014 ZD1211B + +/* Wacom products */ +product WACOM CT0405U 0x0000 CT-0405-U Tablet +product WACOM GRAPHIRE 0x0010 Graphire +product WACOM GRAPHIRE3_4X5 0x0013 Graphire 3 4x5 +product WACOM INTUOSA5 0x0021 Intuos A5 +product WACOM GD0912U 0x0022 Intuos 9x12 Graphics Tablet +/* WCH products*/ +product WCH CH341SER 0x5523 CH341/CH340 USB-Serial Bridge +/* Western Digital products */ +product WESTERN COMBO 0x0200 Firewire USB Combo +product WESTERN EXTHDD 0x0400 External HDD +product WESTERN HUB 0x0500 USB HUB +product WESTERN MYBOOK 0x0901 MyBook External HDD + +/* Windbond Electronics */ +product WINBOND UH104 0x5518 4-port USB Hub + +/* WinMaxGroup products */ +product WINMAXGROUP FLASH64MC 0x6660 USB Flash Disk 64M-C + +/* Wistron NeWeb products */ +product WISTRONNEWEB UR045G 0x0427 PrismGT USB 2.0 WLAN +product WISTRONNEWEB UR055G 0x0711 UR055G +product WISTRONNEWEB AR5523_1 0x0826 AR5523 +product WISTRONNEWEB AR5523_1_NF 0x0827 AR5523 (no firmware) +product WISTRONNEWEB AR5523_2 0x082a AR5523 +product WISTRONNEWEB AR5523_2_NF 0x0829 AR5523 (no firmware) + +/* Xerox products */ +product XEROX WCM15 0xffef WorkCenter M15 + +/* Xirlink products */ +product XIRLINK PCCAM 0x8080 IBM PC Camera + +/* Xyratex products */ +product XYRATEX PRISM_GT_1 0x2000 PrismGT USB 2.0 WLAN +product XYRATEX PRISM_GT_2 0x2002 PrismGT USB 2.0 WLAN + +/* Y-E Data products */ +product YEDATA FLASHBUSTERU 0x0000 Flashbuster-U + +/* Yamaha products */ +product YAMAHA UX256 0x1000 UX256 MIDI I/F +product YAMAHA UX96 0x1008 UX96 MIDI I/F +product YAMAHA RTA54I 0x4000 NetVolante RTA54i Broadband&ISDN Router +product YAMAHA RTA55I 0x4004 NetVolante RTA55i Broadband VoIP Router +product YAMAHA RTW65B 0x4001 NetVolante RTW65b Broadband Wireless Router +product YAMAHA RTW65I 0x4002 NetVolante RTW65i Broadband&ISDN Wireless Router + +/* Yano products */ +product YANO U640MO 0x0101 U640MO-03 +product YANO FW800HD 0x05fc METALWEAR-HDD + +/* Yiso Wireless Co. products */ +product YISO C893 0xc893 CDMA 2000 1xEVDO PC Card + +/* Z-Com products */ +product ZCOM M4Y750 0x0001 M4Y-750 +product ZCOM XI725 0x0002 XI-725/726 +product ZCOM XI735 0x0005 XI-735 +product ZCOM XG703A 0x0008 PrismGT USB 2.0 WLAN +product ZCOM ZD1211 0x0011 ZD1211 +product ZCOM AR5523 0x0012 AR5523 +product ZCOM AR5523_NF 0x0013 AR5523 driver (no firmware) +product ZCOM ZD1211B 0x001a ZD1211B + +/* Zinwell products */ +product ZINWELL RT2570 0x0260 RT2570 + +/* Zoom Telephonics, Inc. products */ +product ZOOM 2986L 0x9700 2986L Fax modem + +/* Zoran Microelectronics products */ +product ZORAN EX20DSC 0x4343 Digital Camera EX-20 DSC + +/* Zydas Technology Corporation products */ +product ZYDAS ZD1211 0x1211 ZD1211 WLAN abg +product ZYDAS ZD1211B 0x1215 ZD1211B + +/* ZyXEL Communication Co. products */ +product ZYXEL OMNI56K 0x1500 Omni 56K Plus +product ZYXEL 980N 0x2011 Scorpion-980N keyboard +product ZYXEL ZYAIRG220 0x3401 ZyAIR G-220 +product ZYXEL G200V2 0x3407 G-200 v2 +product ZYXEL AG225H 0x3409 AG-225H +product ZYXEL M202 0x340a M-202 +product ZYXEL G220V2 0x340f G-220 v2 +product ZYXEL G202 0x3410 G-202 diff --git a/sys/legacy/dev/usb/usbdi.c b/sys/legacy/dev/usb/usbdi.c new file mode 100644 index 0000000..a733bbf --- /dev/null +++ b/sys/legacy/dev/usb/usbdi.c @@ -0,0 +1,1383 @@ +/* $NetBSD: usbdi.c,v 1.106 2004/10/24 12:52:40 augustss Exp $ */ + +#include <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 <sys/param.h> +#include <sys/systm.h> +#include <sys/module.h> +#include <sys/bus.h> +#include "usb_if.h" +#if defined(DIAGNOSTIC) && defined(__i386__) +#include <machine/cpu.h> +#endif +#include <sys/malloc.h> +#include <sys/proc.h> + +#include <machine/bus.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include <dev/usb/usbdivar.h> +#include <dev/usb/usb_mem.h> +#include <dev/usb/usb_quirks.h> + +#include "usb_if.h" +#define delay(d) DELAY(d) + +#ifdef USB_DEBUG +#define DPRINTF(x) if (usbdebug) printf x +#define DPRINTFN(n,x) if (usbdebug>(n)) printf x +extern int usbdebug; +#else +#define DPRINTF(x) +#define DPRINTFN(n,x) +#endif + +static usbd_status usbd_ar_pipe(usbd_pipe_handle pipe); +static void usbd_do_request_async_cb + (usbd_xfer_handle, usbd_private_handle, usbd_status); +static void usbd_start_next(usbd_pipe_handle pipe); +static usbd_status usbd_open_pipe_ival + (usbd_interface_handle, u_int8_t, u_int8_t, usbd_pipe_handle *, int); +static int usbd_xfer_isread(usbd_xfer_handle xfer); +static void usbd_start_transfer(void *arg, bus_dma_segment_t *segs, int nseg, + int error); +static void usbd_alloc_callback(void *arg, bus_dma_segment_t *segs, int nseg, + int error); + +static int usbd_nbuses = 0; + +void +usbd_init(void) +{ + usbd_nbuses++; +} + +void +usbd_finish(void) +{ + --usbd_nbuses; +} + +static __inline int +usbd_xfer_isread(usbd_xfer_handle xfer) +{ + if (xfer->rqflags & URQ_REQUEST) + return (xfer->request.bmRequestType & UT_READ); + else + return (xfer->pipe->endpoint->edesc->bEndpointAddress & + UE_DIR_IN); +} + +#ifdef USB_DEBUG +void +usbd_dump_iface(struct usbd_interface *iface) +{ + printf("usbd_dump_iface: iface=%p\n", iface); + if (iface == NULL) + return; + printf(" device=%p idesc=%p index=%d altindex=%d priv=%p\n", + iface->device, iface->idesc, iface->index, iface->altindex, + iface->priv); +} + +void +usbd_dump_device(struct usbd_device *dev) +{ + printf("usbd_dump_device: dev=%p\n", dev); + if (dev == NULL) + return; + printf(" bus=%p default_pipe=%p\n", dev->bus, dev->default_pipe); + printf(" address=%d config=%d depth=%d speed=%d self_powered=%d " + "power=%d langid=%d\n", + dev->address, dev->config, dev->depth, dev->speed, + dev->self_powered, dev->power, dev->langid); +} + +void +usbd_dump_endpoint(struct usbd_endpoint *endp) +{ + printf("usbd_dump_endpoint: endp=%p\n", endp); + if (endp == NULL) + return; + printf(" edesc=%p refcnt=%d\n", endp->edesc, endp->refcnt); + if (endp->edesc) + printf(" bEndpointAddress=0x%02x\n", + endp->edesc->bEndpointAddress); +} + +void +usbd_dump_queue(usbd_pipe_handle pipe) +{ + usbd_xfer_handle xfer; + + printf("usbd_dump_queue: pipe=%p\n", pipe); + STAILQ_FOREACH(xfer, &pipe->queue, next) { + printf(" xfer=%p\n", xfer); + } +} + +void +usbd_dump_pipe(usbd_pipe_handle pipe) +{ + printf("usbd_dump_pipe: pipe=%p\n", pipe); + if (pipe == NULL) + return; + usbd_dump_iface(pipe->iface); + usbd_dump_device(pipe->device); + usbd_dump_endpoint(pipe->endpoint); + printf(" (usbd_dump_pipe:)\n refcnt=%d running=%d aborting=%d\n", + pipe->refcnt, pipe->running, pipe->aborting); + printf(" intrxfer=%p, repeat=%d, interval=%d\n", + pipe->intrxfer, pipe->repeat, pipe->interval); +} +#endif + +usbd_status +usbd_open_pipe(usbd_interface_handle iface, u_int8_t address, + u_int8_t flags, usbd_pipe_handle *pipe) +{ + return (usbd_open_pipe_ival(iface, address, flags, pipe, + USBD_DEFAULT_INTERVAL)); +} + +usbd_status +usbd_open_pipe_ival(usbd_interface_handle iface, u_int8_t address, + u_int8_t flags, usbd_pipe_handle *pipe, int ival) +{ + usbd_pipe_handle p; + struct usbd_endpoint *ep; + usbd_status err; + int i; + + DPRINTFN(3,("usbd_open_pipe: iface=%p address=0x%x flags=0x%x\n", + iface, address, flags)); + + for (i = 0; i < iface->idesc->bNumEndpoints; i++) { + ep = &iface->endpoints[i]; + if (ep->edesc == NULL) + return (USBD_IOERROR); + if (ep->edesc->bEndpointAddress == address) + goto found; + } + return (USBD_BAD_ADDRESS); + found: + if ((flags & USBD_EXCLUSIVE_USE) && ep->refcnt != 0) + return (USBD_IN_USE); + err = usbd_setup_pipe(iface->device, iface, ep, ival, &p); + if (err) + return (err); + LIST_INSERT_HEAD(&iface->pipes, p, next); + *pipe = p; + return (USBD_NORMAL_COMPLETION); +} + +usbd_status +usbd_open_pipe_intr(usbd_interface_handle iface, u_int8_t address, + u_int8_t flags, usbd_pipe_handle *pipe, + usbd_private_handle priv, void *buffer, u_int32_t len, + usbd_callback cb, int ival) +{ + usbd_status err; + usbd_xfer_handle xfer; + usbd_pipe_handle ipipe; + + DPRINTFN(3,("usbd_open_pipe_intr: address=0x%x flags=0x%x len=%d\n", + address, flags, len)); + + err = usbd_open_pipe_ival(iface, address, USBD_EXCLUSIVE_USE, + &ipipe, ival); + if (err) + return (err); + xfer = usbd_alloc_xfer(iface->device); + if (xfer == NULL) { + err = USBD_NOMEM; + goto bad1; + } + usbd_setup_xfer(xfer, ipipe, priv, buffer, len, flags, + USBD_NO_TIMEOUT, cb); + ipipe->intrxfer = xfer; + ipipe->repeat = 1; + *pipe = ipipe; + err = usbd_transfer(xfer); + if (err != USBD_IN_PROGRESS && err) + goto bad2; + return (USBD_NORMAL_COMPLETION); + + bad2: + ipipe->intrxfer = NULL; + ipipe->repeat = 0; + usbd_free_xfer(xfer); + bad1: + usbd_close_pipe(ipipe); + return (err); +} + +usbd_status +usbd_close_pipe(usbd_pipe_handle pipe) +{ +#ifdef DIAGNOSTIC + if (pipe == NULL) { + printf("usbd_close_pipe: pipe==NULL\n"); + return (USBD_NORMAL_COMPLETION); + } +#endif + + if (--pipe->refcnt != 0) + return (USBD_NORMAL_COMPLETION); + if (! STAILQ_EMPTY(&pipe->queue)) + return (USBD_PENDING_REQUESTS); + LIST_REMOVE(pipe, next); + pipe->endpoint->refcnt--; + pipe->methods->close(pipe); + if (pipe->intrxfer != NULL) + usbd_free_xfer(pipe->intrxfer); + free(pipe, M_USB); + return (USBD_NORMAL_COMPLETION); +} + +usbd_status +usbd_transfer(usbd_xfer_handle xfer) +{ + usbd_pipe_handle pipe = xfer->pipe; + struct usb_dma_mapping *dmap = &xfer->dmamap; + usbd_status err; + u_int size; + int s; + + DPRINTFN(5,("%s: xfer=%p, flags=0x%b, rqflags=0x%b, " + "length=%d, buffer=%p, allocbuf=%p, pipe=%p, running=%d\n", + __func__, xfer, xfer->flags, USBD_BITS, xfer->rqflags, URQ_BITS, + xfer->length, xfer->buffer, xfer->allocbuf, pipe, pipe->running)); +#ifdef USB_DEBUG + if (usbdebug > 5) + usbd_dump_queue(pipe); +#endif + xfer->done = 0; + + if (pipe->aborting) + return (USBD_CANCELLED); + + size = xfer->length; + /* If there is no buffer, allocate one. */ + if (!(xfer->rqflags & URQ_DEV_DMABUF) && size != 0) { + bus_dma_tag_t tag = pipe->device->bus->buffer_dmatag; + +#ifdef DIAGNOSTIC + if (xfer->rqflags & URQ_AUTO_DMABUF) + printf("usbd_transfer: has old buffer!\n"); +#endif + err = bus_dmamap_create(tag, 0, &dmap->map); + if (err) + return (USBD_NOMEM); + + xfer->rqflags |= URQ_AUTO_DMABUF; + err = bus_dmamap_load(tag, dmap->map, xfer->buffer, size, + usbd_start_transfer, xfer, 0); + if (err != 0 && err != EINPROGRESS) { + xfer->rqflags &= ~URQ_AUTO_DMABUF; + bus_dmamap_destroy(tag, dmap->map); + return (USBD_INVAL); + } + } else if (size != 0) { + usbd_start_transfer(xfer, dmap->segs, dmap->nsegs, 0); + } else { + usbd_start_transfer(xfer, NULL, 0, 0); + } + + if (!(xfer->flags & USBD_SYNCHRONOUS)) + return (xfer->done ? USBD_NORMAL_COMPLETION : USBD_IN_PROGRESS); + + /* Sync transfer, wait for completion. */ + s = splusb(); + while (!xfer->done) { + if (pipe->device->bus->use_polling) + panic("usbd_transfer: not done"); + tsleep(xfer, PRIBIO, "usbsyn", 0); + } + splx(s); + return (xfer->status); +} + +static void +usbd_start_transfer(void *arg, bus_dma_segment_t *segs, int nseg, int error) +{ + usbd_xfer_handle xfer = arg; + usbd_pipe_handle pipe = xfer->pipe; + struct usb_dma_mapping *dmap = &xfer->dmamap; + bus_dma_tag_t tag = pipe->device->bus->buffer_dmatag; + int err, i; + + if (error != 0) { + KASSERT(xfer->rqflags & URQ_AUTO_DMABUF, + ("usbd_start_transfer: error with non-auto buf")); + if (nseg > 0) + bus_dmamap_unload(tag, dmap->map); + bus_dmamap_destroy(tag, dmap->map); + /* XXX */ + usb_insert_transfer(xfer); + xfer->status = USBD_IOERROR; + usb_transfer_complete(xfer); + return; + } + + if (segs != dmap->segs) { + for (i = 0; i < nseg; i++) + dmap->segs[i] = segs[i]; + } + dmap->nsegs = nseg; + + if (nseg > 0) { + if (!usbd_xfer_isread(xfer)) { + /* + * Copy data if it is not already in the correct + * buffer. + */ + if (!(xfer->flags & USBD_NO_COPY) && + xfer->allocbuf != NULL && + xfer->buffer != xfer->allocbuf) + memcpy(xfer->allocbuf, xfer->buffer, + xfer->length); + bus_dmamap_sync(tag, dmap->map, BUS_DMASYNC_PREWRITE); + } else if (xfer->rqflags & URQ_REQUEST) { + /* + * Even if we have no data portion we still need to + * sync the dmamap for the request data in the SETUP + * packet. + */ + bus_dmamap_sync(tag, dmap->map, + BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); + } else + bus_dmamap_sync(tag, dmap->map, BUS_DMASYNC_PREREAD); + } + err = pipe->methods->transfer(xfer); + if (err != USBD_IN_PROGRESS && err) { + if (xfer->rqflags & URQ_AUTO_DMABUF) { + bus_dmamap_unload(tag, dmap->map); + bus_dmamap_destroy(tag, dmap->map); + xfer->rqflags &= ~URQ_AUTO_DMABUF; + } + xfer->status = err; + usb_transfer_complete(xfer); + return; + } +} + +/* Like usbd_transfer(), but waits for completion. */ +usbd_status +usbd_sync_transfer(usbd_xfer_handle xfer) +{ + xfer->flags |= USBD_SYNCHRONOUS; + return (usbd_transfer(xfer)); +} + +struct usbd_allocstate { + usbd_xfer_handle xfer; + int done; + int error; + int waiting; +}; + +void * +usbd_alloc_buffer(usbd_xfer_handle xfer, u_int32_t size) +{ + struct usbd_allocstate allocstate; + struct usb_dma_mapping *dmap = &xfer->dmamap; + bus_dma_tag_t tag = xfer->device->bus->buffer_dmatag; + void *buf; + usbd_status err; + int error, s; + + KASSERT((xfer->rqflags & (URQ_DEV_DMABUF | URQ_AUTO_DMABUF)) == 0, + ("usbd_alloc_buffer: xfer already has a buffer")); + err = bus_dmamap_create(tag, 0, &dmap->map); + if (err) + return (NULL); + buf = malloc(size, M_USB, M_WAITOK); + + allocstate.xfer = xfer; + allocstate.done = 0; + allocstate.error = 0; + allocstate.waiting = 0; + error = bus_dmamap_load(tag, dmap->map, buf, size, usbd_alloc_callback, + &allocstate, 0); + if (error && error != EINPROGRESS) { + bus_dmamap_destroy(tag, dmap->map); + free(buf, M_USB); + return (NULL); + } + if (error == EINPROGRESS) { + /* Wait for completion. */ + s = splusb(); + allocstate.waiting = 1; + while (!allocstate.done) + tsleep(&allocstate, PRIBIO, "usbdab", 0); + splx(s); + error = allocstate.error; + } + if (error) { + bus_dmamap_unload(tag, dmap->map); + bus_dmamap_destroy(tag, dmap->map); + free(buf, M_USB); + return (NULL); + } + + xfer->allocbuf = buf; + xfer->rqflags |= URQ_DEV_DMABUF; + return (buf); +} + +void +usbd_free_buffer(usbd_xfer_handle xfer) +{ + struct usb_dma_mapping *dmap = &xfer->dmamap; + bus_dma_tag_t tag = xfer->device->bus->buffer_dmatag; + + KASSERT((xfer->rqflags & (URQ_DEV_DMABUF | URQ_AUTO_DMABUF)) == + URQ_DEV_DMABUF, ("usbd_free_buffer: no/auto buffer")); + + xfer->rqflags &= ~URQ_DEV_DMABUF; + bus_dmamap_unload(tag, dmap->map); + bus_dmamap_destroy(tag, dmap->map); + free(xfer->allocbuf, M_USB); + xfer->allocbuf = NULL; +} + +void * +usbd_get_buffer(usbd_xfer_handle xfer) +{ + if (!(xfer->rqflags & URQ_DEV_DMABUF)) + return (NULL); + return (xfer->allocbuf); +} + +static void +usbd_alloc_callback(void *arg, bus_dma_segment_t *segs, int nseg, int error) +{ + struct usbd_allocstate *allocstate = arg; + usbd_xfer_handle xfer = allocstate->xfer; + struct usb_dma_mapping *dmap = &xfer->dmamap; + int i; + + allocstate->error = error; + if (error == 0) { + for (i = 0; i < nseg; i++) + dmap->segs[i] = segs[i]; + dmap->nsegs = nseg; + } + allocstate->done = 1; + if (allocstate->waiting) + wakeup(&allocstate); +} + +usbd_xfer_handle +usbd_alloc_xfer(usbd_device_handle dev) +{ + usbd_xfer_handle xfer; + + xfer = dev->bus->methods->allocx(dev->bus); + if (xfer == NULL) + return (NULL); + xfer->device = dev; + callout_init(&xfer->timeout_handle, 0); + DPRINTFN(5,("usbd_alloc_xfer() = %p\n", xfer)); + return (xfer); +} + +usbd_status +usbd_free_xfer(usbd_xfer_handle xfer) +{ + DPRINTFN(5,("usbd_free_xfer: %p\n", xfer)); + if (xfer->rqflags & URQ_DEV_DMABUF) + usbd_free_buffer(xfer); +/* XXX Does FreeBSD need to do something similar? */ +#if defined(__NetBSD__) && defined(DIAGNOSTIC) + if (callout_pending(&xfer->timeout_handle)) { + callout_stop(&xfer->timeout_handle); + printf("usbd_free_xfer: timout_handle pending"); + } +#endif + xfer->device->bus->methods->freex(xfer->device->bus, xfer); + return (USBD_NORMAL_COMPLETION); +} + +void +usbd_setup_xfer(usbd_xfer_handle xfer, usbd_pipe_handle pipe, + usbd_private_handle priv, void *buffer, u_int32_t length, + u_int16_t flags, u_int32_t timeout, + usbd_callback callback) +{ + xfer->pipe = pipe; + xfer->priv = priv; + xfer->buffer = buffer; + xfer->length = length; + xfer->actlen = 0; + xfer->flags = flags; + xfer->timeout = timeout; + xfer->status = USBD_NOT_STARTED; + xfer->callback = callback; + xfer->rqflags &= ~URQ_REQUEST; + xfer->nframes = 0; +} + +void +usbd_setup_default_xfer(usbd_xfer_handle xfer, usbd_device_handle dev, + usbd_private_handle priv, u_int32_t timeout, + usb_device_request_t *req, void *buffer, + u_int32_t length, u_int16_t flags, + usbd_callback callback) +{ + xfer->pipe = dev->default_pipe; + xfer->priv = priv; + xfer->buffer = buffer; + xfer->length = length; + xfer->actlen = 0; + xfer->flags = flags; + xfer->timeout = timeout; + xfer->status = USBD_NOT_STARTED; + xfer->callback = callback; + xfer->request = *req; + xfer->rqflags |= URQ_REQUEST; + xfer->nframes = 0; +} + +void +usbd_setup_isoc_xfer(usbd_xfer_handle xfer, usbd_pipe_handle pipe, + usbd_private_handle priv, u_int16_t *frlengths, + u_int32_t nframes, u_int16_t flags, usbd_callback callback) +{ + int i; + + xfer->pipe = pipe; + xfer->priv = priv; + xfer->buffer = 0; + xfer->length = 0; + for (i = 0; i < nframes; i++) + xfer->length += frlengths[i]; + xfer->actlen = 0; + xfer->flags = flags; + xfer->timeout = USBD_NO_TIMEOUT; + xfer->status = USBD_NOT_STARTED; + xfer->callback = callback; + xfer->rqflags &= ~URQ_REQUEST; + xfer->frlengths = frlengths; + xfer->nframes = nframes; +} + +void +usbd_get_xfer_status(usbd_xfer_handle xfer, usbd_private_handle *priv, + void **buffer, u_int32_t *count, usbd_status *status) +{ + if (priv != NULL) + *priv = xfer->priv; + if (buffer != NULL) + *buffer = xfer->buffer; + if (count != NULL) + *count = xfer->actlen; + if (status != NULL) + *status = xfer->status; +} + +usb_config_descriptor_t * +usbd_get_config_descriptor(usbd_device_handle dev) +{ +#ifdef DIAGNOSTIC + if (dev == NULL) { + printf("usbd_get_config_descriptor: dev == NULL\n"); + return (NULL); + } +#endif + return (dev->cdesc); +} + +int +usbd_get_speed(usbd_device_handle dev) +{ + return (dev->speed); +} + +usb_interface_descriptor_t * +usbd_get_interface_descriptor(usbd_interface_handle iface) +{ +#ifdef DIAGNOSTIC + if (iface == NULL) { + printf("usbd_get_interface_descriptor: dev == NULL\n"); + return (NULL); + } +#endif + return (iface->idesc); +} + +usb_device_descriptor_t * +usbd_get_device_descriptor(usbd_device_handle dev) +{ + return (&dev->ddesc); +} + +usb_endpoint_descriptor_t * +usbd_interface2endpoint_descriptor(usbd_interface_handle iface, u_int8_t index) +{ + if (index >= iface->idesc->bNumEndpoints) + return (0); + return (iface->endpoints[index].edesc); +} + +usbd_status +usbd_abort_pipe(usbd_pipe_handle pipe) +{ + usbd_status err; + int s; + +#ifdef DIAGNOSTIC + if (pipe == NULL) { + printf("usbd_close_pipe: pipe==NULL\n"); + return (USBD_NORMAL_COMPLETION); + } +#endif + s = splusb(); + err = usbd_ar_pipe(pipe); + splx(s); + return (err); +} + +usbd_status +usbd_abort_default_pipe(usbd_device_handle dev) +{ + return (usbd_abort_pipe(dev->default_pipe)); +} + +usbd_status +usbd_clear_endpoint_stall(usbd_pipe_handle pipe) +{ + usbd_device_handle dev = pipe->device; + usb_device_request_t req; + usbd_status err; + + DPRINTFN(8, ("usbd_clear_endpoint_stall\n")); + + /* + * Clearing en endpoint stall resets the endpoint toggle, so + * do the same to the HC toggle. + */ + pipe->methods->cleartoggle(pipe); + + req.bmRequestType = UT_WRITE_ENDPOINT; + req.bRequest = UR_CLEAR_FEATURE; + USETW(req.wValue, UF_ENDPOINT_HALT); + USETW(req.wIndex, pipe->endpoint->edesc->bEndpointAddress); + USETW(req.wLength, 0); + err = usbd_do_request(dev, &req, 0); +#if 0 +XXX should we do this? + if (!err) { + pipe->state = USBD_PIPE_ACTIVE; + /* XXX activate pipe */ + } +#endif + return (err); +} + +usbd_status +usbd_clear_endpoint_stall_async(usbd_pipe_handle pipe) +{ + usbd_device_handle dev = pipe->device; + usb_device_request_t req; + usbd_status err; + + pipe->methods->cleartoggle(pipe); + + req.bmRequestType = UT_WRITE_ENDPOINT; + req.bRequest = UR_CLEAR_FEATURE; + USETW(req.wValue, UF_ENDPOINT_HALT); + USETW(req.wIndex, pipe->endpoint->edesc->bEndpointAddress); + USETW(req.wLength, 0); + err = usbd_do_request_async(dev, &req, 0); + return (err); +} + +void +usbd_clear_endpoint_toggle(usbd_pipe_handle pipe) +{ + pipe->methods->cleartoggle(pipe); +} + +usbd_status +usbd_endpoint_count(usbd_interface_handle iface, u_int8_t *count) +{ +#ifdef DIAGNOSTIC + if (iface == NULL || iface->idesc == NULL) { + printf("usbd_endpoint_count: NULL pointer\n"); + return (USBD_INVAL); + } +#endif + *count = iface->idesc->bNumEndpoints; + return (USBD_NORMAL_COMPLETION); +} + +usbd_status +usbd_interface_count(usbd_device_handle dev, u_int8_t *count) +{ + if (dev->cdesc == NULL) + return (USBD_NOT_CONFIGURED); + *count = dev->cdesc->bNumInterface; + return (USBD_NORMAL_COMPLETION); +} + +void +usbd_interface2device_handle(usbd_interface_handle iface, + usbd_device_handle *dev) +{ + *dev = iface->device; +} + +usbd_status +usbd_device2interface_handle(usbd_device_handle dev, + u_int8_t ifaceno, usbd_interface_handle *iface) +{ + if (dev->cdesc == NULL) + return (USBD_NOT_CONFIGURED); + if (ifaceno >= dev->cdesc->bNumInterface) + return (USBD_INVAL); + *iface = &dev->ifaces[ifaceno]; + return (USBD_NORMAL_COMPLETION); +} + +usbd_device_handle +usbd_pipe2device_handle(usbd_pipe_handle pipe) +{ + return (pipe->device); +} + +/* XXXX use altno */ +usbd_status +usbd_set_interface(usbd_interface_handle iface, int altidx) +{ + usb_device_request_t req; + usbd_status err; + void *endpoints; + + if (LIST_FIRST(&iface->pipes) != 0) + return (USBD_IN_USE); + + endpoints = iface->endpoints; + err = usbd_fill_iface_data(iface->device, iface->index, altidx); + if (err) + return (err); + + /* new setting works, we can free old endpoints */ + if (endpoints != NULL) + free(endpoints, M_USB); + +#ifdef DIAGNOSTIC + if (iface->idesc == NULL) { + printf("usbd_set_interface: NULL pointer\n"); + return (USBD_INVAL); + } +#endif + + req.bmRequestType = UT_WRITE_INTERFACE; + req.bRequest = UR_SET_INTERFACE; + USETW(req.wValue, iface->idesc->bAlternateSetting); + USETW(req.wIndex, iface->idesc->bInterfaceNumber); + USETW(req.wLength, 0); + return (usbd_do_request(iface->device, &req, 0)); +} + +int +usbd_get_no_alts(usb_config_descriptor_t *cdesc, int ifaceno) +{ + char *p = (char *)cdesc; + char *end = p + UGETW(cdesc->wTotalLength); + usb_interface_descriptor_t *d; + int n; + + for (n = 0; p < end; p += d->bLength) { + d = (usb_interface_descriptor_t *)p; + if (p + d->bLength <= end && + d->bDescriptorType == UDESC_INTERFACE && + d->bInterfaceNumber == ifaceno) + n++; + } + return (n); +} + +int +usbd_get_interface_altindex(usbd_interface_handle iface) +{ + return (iface->altindex); +} + +usbd_status +usbd_get_interface(usbd_interface_handle iface, u_int8_t *aiface) +{ + usb_device_request_t req; + + req.bmRequestType = UT_READ_INTERFACE; + req.bRequest = UR_GET_INTERFACE; + USETW(req.wValue, 0); + USETW(req.wIndex, iface->idesc->bInterfaceNumber); + USETW(req.wLength, 1); + return (usbd_do_request(iface->device, &req, aiface)); +} + +/*** Internal routines ***/ + +/* Dequeue all pipe operations, called at splusb(). */ +static usbd_status +usbd_ar_pipe(usbd_pipe_handle pipe) +{ + usbd_xfer_handle xfer; + + SPLUSBCHECK; + + DPRINTFN(2,("usbd_ar_pipe: pipe=%p\n", pipe)); +#ifdef USB_DEBUG + if (usbdebug > 5) + usbd_dump_queue(pipe); +#endif + pipe->repeat = 0; + pipe->aborting = 1; + while ((xfer = STAILQ_FIRST(&pipe->queue)) != NULL) { + DPRINTFN(2,("usbd_ar_pipe: pipe=%p xfer=%p (methods=%p)\n", + pipe, xfer, pipe->methods)); + /* Make the HC abort it (and invoke the callback). */ + pipe->methods->abort(xfer); + KASSERT(STAILQ_FIRST(&pipe->queue) != xfer, ("usbd_ar_pipe")); + /* XXX only for non-0 usbd_clear_endpoint_stall(pipe); */ + } + pipe->aborting = 0; + return (USBD_NORMAL_COMPLETION); +} + +/* Called at splusb() */ +void +usb_transfer_complete(usbd_xfer_handle xfer) +{ + usbd_pipe_handle pipe = xfer->pipe; + struct usb_dma_mapping *dmap = &xfer->dmamap; + bus_dma_tag_t tag = pipe->device->bus->buffer_dmatag; + int sync = xfer->flags & USBD_SYNCHRONOUS; + int erred = xfer->status == USBD_CANCELLED || + xfer->status == USBD_TIMEOUT; + int repeat = pipe->repeat; + int polling; + + SPLUSBCHECK; + + DPRINTFN(5, ("%s: pipe=%p xfer=%p status=%d actlen=%d\n", + __func__, pipe, xfer, xfer->status, xfer->actlen)); + DPRINTFN(5,("%s: flags=0x%b, rqflags=0x%b, length=%d, buffer=%p\n", + __func__, xfer->flags, USBD_BITS, xfer->rqflags, URQ_BITS, + xfer->length, xfer->buffer)); +#ifdef DIAGNOSTIC + if (xfer->busy_free != XFER_ONQU) { + printf("usb_transfer_complete: xfer=%p not busy 0x%08x\n", + xfer, xfer->busy_free); + return; + } +#endif + +#ifdef DIAGNOSTIC + if (pipe == NULL) { + printf("usbd_transfer_cb: pipe==0, xfer=%p\n", xfer); + return; + } +#endif + polling = pipe->device->bus->use_polling; + /* XXXX */ + if (polling) + pipe->running = 0; + + if (xfer->actlen != 0 && usbd_xfer_isread(xfer)) { + bus_dmamap_sync(tag, dmap->map, BUS_DMASYNC_POSTREAD); + /* Copy data if it is not already in the correct buffer. */ + if (!(xfer->flags & USBD_NO_COPY) && xfer->allocbuf != NULL && + xfer->buffer != xfer->allocbuf) + memcpy(xfer->buffer, xfer->allocbuf, xfer->actlen); + } + + /* if we mapped the buffer in usbd_transfer() we unmap it here. */ + if (xfer->rqflags & URQ_AUTO_DMABUF) { + if (!repeat) { + bus_dmamap_unload(tag, dmap->map); + bus_dmamap_destroy(tag, dmap->map); + xfer->rqflags &= ~URQ_AUTO_DMABUF; + } + } + + if (!repeat) { + /* Remove request from queue. */ +#ifdef DIAGNOSTIC + xfer->busy_free = XFER_BUSY; +#endif + KASSERT(STAILQ_FIRST(&pipe->queue) == xfer, + ("usb_transfer_complete: bad dequeue")); + STAILQ_REMOVE_HEAD(&pipe->queue, next); + } + DPRINTFN(5,("usb_transfer_complete: repeat=%d new head=%p\n", + repeat, STAILQ_FIRST(&pipe->queue))); + + /* Count completed transfers. */ + ++pipe->device->bus->stats.uds_requests + [pipe->endpoint->edesc->bmAttributes & UE_XFERTYPE]; + + xfer->done = 1; + if (!xfer->status && xfer->actlen < xfer->length && + !(xfer->flags & USBD_SHORT_XFER_OK)) { + DPRINTFN(-1,("usbd_transfer_cb: short transfer %d<%d\n", + xfer->actlen, xfer->length)); + xfer->status = USBD_SHORT_XFER; + } + + /* + * For repeat operations, call the callback first, as the xfer + * will not go away and the "done" method may modify it. Otherwise + * reverse the order in case the callback wants to free or reuse + * the xfer. + */ + if (repeat) { + if (xfer->callback) + xfer->callback(xfer, xfer->priv, xfer->status); + pipe->methods->done(xfer); + } else { + pipe->methods->done(xfer); + if (xfer->callback) + xfer->callback(xfer, xfer->priv, xfer->status); + } + + if (sync && !polling) + wakeup(xfer); + + if (!repeat) { + /* XXX should we stop the queue on all errors? */ + if (erred && pipe->iface != NULL) /* not control pipe */ + pipe->running = 0; + else + usbd_start_next(pipe); + } +} + +usbd_status +usb_insert_transfer(usbd_xfer_handle xfer) +{ + usbd_pipe_handle pipe = xfer->pipe; + usbd_status err; + int s; + + DPRINTFN(5,("usb_insert_transfer: pipe=%p running=%d timeout=%d\n", + pipe, pipe->running, xfer->timeout)); +#ifdef DIAGNOSTIC + if (xfer->busy_free != XFER_BUSY) { + printf("usb_insert_transfer: xfer=%p not busy 0x%08x\n", + xfer, xfer->busy_free); + return (USBD_INVAL); + } + xfer->busy_free = XFER_ONQU; +#endif + s = splusb(); + KASSERT(STAILQ_FIRST(&pipe->queue) != xfer, ("usb_insert_transfer")); + STAILQ_INSERT_TAIL(&pipe->queue, xfer, next); + if (pipe->running) + err = USBD_IN_PROGRESS; + else { + pipe->running = 1; + err = USBD_NORMAL_COMPLETION; + } + splx(s); + return (err); +} + +/* Called at splusb() */ +void +usbd_start_next(usbd_pipe_handle pipe) +{ + usbd_xfer_handle xfer; + usbd_status err; + + SPLUSBCHECK; + +#ifdef DIAGNOSTIC + if (pipe == NULL) { + printf("usbd_start_next: pipe == NULL\n"); + return; + } + if (pipe->methods == NULL || pipe->methods->start == NULL) { + printf("usbd_start_next: pipe=%p no start method\n", pipe); + return; + } +#endif + + /* Get next request in queue. */ + xfer = STAILQ_FIRST(&pipe->queue); + DPRINTFN(5, ("usbd_start_next: pipe=%p, xfer=%p\n", pipe, xfer)); + if (xfer == NULL) { + pipe->running = 0; + } else { + err = pipe->methods->start(xfer); + if (err != USBD_IN_PROGRESS) { + printf("usbd_start_next: error=%d\n", err); + pipe->running = 0; + /* XXX do what? */ + } + } +} + +usbd_status +usbd_do_request(usbd_device_handle dev, usb_device_request_t *req, void *data) +{ + return (usbd_do_request_flags(dev, req, data, 0, 0, + USBD_DEFAULT_TIMEOUT)); +} + +usbd_status +usbd_do_request_flags(usbd_device_handle dev, usb_device_request_t *req, + void *data, u_int16_t flags, int *actlen, u_int32_t timo) +{ + return (usbd_do_request_flags_pipe(dev, dev->default_pipe, req, + data, flags, actlen, timo)); +} + +usbd_status +usbd_do_request_flags_pipe(usbd_device_handle dev, usbd_pipe_handle pipe, + usb_device_request_t *req, void *data, u_int16_t flags, int *actlen, + u_int32_t timeout) +{ + usbd_xfer_handle xfer; + usbd_status err; + +#ifdef DIAGNOSTIC +/* XXX amd64 too? */ +#if defined(__i386__) + KASSERT(curthread->td_intr_nesting_level == 0, + ("usbd_do_request: in interrupt context")); +#endif + if (dev->bus->intr_context) { + printf("usbd_do_request: not in process context\n"); + return (USBD_INVAL); + } +#endif + + xfer = usbd_alloc_xfer(dev); + if (xfer == NULL) + return (USBD_NOMEM); + usbd_setup_default_xfer(xfer, dev, 0, timeout, req, + data, UGETW(req->wLength), flags, 0); + xfer->pipe = pipe; + err = usbd_sync_transfer(xfer); +#if defined(USB_DEBUG) || defined(DIAGNOSTIC) + if (xfer->actlen > xfer->length) + DPRINTF(("usbd_do_request: overrun addr=%d type=0x%02x req=0x" + "%02x val=%d index=%d rlen=%d length=%d actlen=%d\n", + dev->address, xfer->request.bmRequestType, + xfer->request.bRequest, UGETW(xfer->request.wValue), + UGETW(xfer->request.wIndex), + UGETW(xfer->request.wLength), + xfer->length, xfer->actlen)); +#endif + if (actlen != NULL) + *actlen = xfer->actlen; + if (err == USBD_STALLED) { + /* + * The control endpoint has stalled. Control endpoints + * should not halt, but some may do so anyway so clear + * any halt condition. + */ + usb_device_request_t treq; + usb_status_t status; + u_int16_t s; + usbd_status nerr; + + treq.bmRequestType = UT_READ_ENDPOINT; + treq.bRequest = UR_GET_STATUS; + USETW(treq.wValue, 0); + USETW(treq.wIndex, 0); + USETW(treq.wLength, sizeof(usb_status_t)); + usbd_setup_default_xfer(xfer, dev, 0, USBD_DEFAULT_TIMEOUT, + &treq, &status,sizeof(usb_status_t), + 0, 0); + nerr = usbd_sync_transfer(xfer); + if (nerr) + goto bad; + s = UGETW(status.wStatus); + DPRINTF(("usbd_do_request: status = 0x%04x\n", s)); + if (!(s & UES_HALT)) + goto bad; + treq.bmRequestType = UT_WRITE_ENDPOINT; + treq.bRequest = UR_CLEAR_FEATURE; + USETW(treq.wValue, UF_ENDPOINT_HALT); + USETW(treq.wIndex, 0); + USETW(treq.wLength, 0); + usbd_setup_default_xfer(xfer, dev, 0, USBD_DEFAULT_TIMEOUT, + &treq, &status, 0, 0, 0); + nerr = usbd_sync_transfer(xfer); + if (nerr) + goto bad; + } + + bad: + usbd_free_xfer(xfer); + return (err); +} + +void +usbd_do_request_async_cb(usbd_xfer_handle xfer, usbd_private_handle priv, + usbd_status status) +{ +#if defined(USB_DEBUG) || defined(DIAGNOSTIC) + if (xfer->actlen > xfer->length) + DPRINTF(("usbd_do_request: overrun addr=%d type=0x%02x req=0x" + "%02x val=%d index=%d rlen=%d length=%d actlen=%d\n", + xfer->pipe->device->address, + xfer->request.bmRequestType, + xfer->request.bRequest, UGETW(xfer->request.wValue), + UGETW(xfer->request.wIndex), + UGETW(xfer->request.wLength), + xfer->length, xfer->actlen)); +#endif + usbd_free_xfer(xfer); +} + +/* + * Execute a request without waiting for completion. + * Can be used from interrupt context. + */ +usbd_status +usbd_do_request_async(usbd_device_handle dev, usb_device_request_t *req, + void *data) +{ + usbd_xfer_handle xfer; + usbd_status err; + + xfer = usbd_alloc_xfer(dev); + if (xfer == NULL) + return (USBD_NOMEM); + usbd_setup_default_xfer(xfer, dev, 0, USBD_DEFAULT_TIMEOUT, req, + data, UGETW(req->wLength), 0, usbd_do_request_async_cb); + err = usbd_transfer(xfer); + if (err != USBD_IN_PROGRESS && err) { + usbd_free_xfer(xfer); + return (err); + } + return (USBD_NORMAL_COMPLETION); +} + +const struct usbd_quirks * +usbd_get_quirks(usbd_device_handle dev) +{ +#ifdef DIAGNOSTIC + if (dev == NULL) { + printf("usbd_get_quirks: dev == NULL\n"); + return 0; + } +#endif + return (dev->quirks); +} + +/* XXX do periodic free() of free list */ + +/* + * Called from keyboard driver when in polling mode. + */ +void +usbd_dopoll(usbd_interface_handle iface) +{ + iface->device->bus->methods->do_poll(iface->device->bus); +} + +void +usbd_set_polling(usbd_device_handle dev, int on) +{ + if (on) + dev->bus->use_polling++; + else + dev->bus->use_polling--; + /* When polling we need to make sure there is nothing pending to do. */ + if (dev->bus->use_polling) + dev->bus->methods->soft_intr(dev->bus); +} + +usbd_status +usbd_reset_device(usbd_device_handle dev) +{ + usbd_device_handle parent = dev->myhub; + struct usbd_port *up = dev->powersrc; + + return usbd_reset_port(parent, up->portno, &up->status); +} + + +usb_endpoint_descriptor_t * +usbd_get_endpoint_descriptor(usbd_interface_handle iface, u_int8_t address) +{ + struct usbd_endpoint *ep; + int i; + + for (i = 0; i < iface->idesc->bNumEndpoints; i++) { + ep = &iface->endpoints[i]; + if (ep->edesc->bEndpointAddress == address) + return (iface->endpoints[i].edesc); + } + return (0); +} + +/* + * usbd_ratecheck() can limit the number of error messages that occurs. + * When a device is unplugged it may take up to 0.25s for the hub driver + * to notice it. If the driver continuosly tries to do I/O operations + * this can generate a large number of messages. + */ +int +usbd_ratecheck(struct timeval *last) +{ + if (last->tv_sec == time_second) + return (0); + last->tv_sec = time_second; + return (1); +} + +/* + * Search for a vendor/product pair in an array. The item size is + * given as an argument. + */ +const struct usb_devno * +usb_match_device(const struct usb_devno *tbl, u_int nentries, u_int sz, + u_int16_t vendor, u_int16_t product) +{ + while (nentries-- > 0) { + u_int16_t tproduct = tbl->ud_product; + if (tbl->ud_vendor == vendor && + (tproduct == product || tproduct == USB_PRODUCT_ANY)) + return (tbl); + tbl = (const struct usb_devno *)((const char *)tbl + sz); + } + return (NULL); +} + + +void +usb_desc_iter_init(usbd_device_handle dev, usbd_desc_iter_t *iter) +{ + const usb_config_descriptor_t *cd = usbd_get_config_descriptor(dev); + + iter->cur = (const uByte *)cd; + iter->end = (const uByte *)cd + UGETW(cd->wTotalLength); +} + +const usb_descriptor_t * +usb_desc_iter_next(usbd_desc_iter_t *iter) +{ + const usb_descriptor_t *desc; + + if (iter->cur + sizeof(usb_descriptor_t) >= iter->end) { + if (iter->cur != iter->end) + printf("usb_desc_iter_next: bad descriptor\n"); + return NULL; + } + desc = (const usb_descriptor_t *)iter->cur; + if (desc->bLength == 0) { + printf("usb_desc_iter_next: descriptor length = 0\n"); + return NULL; + } + iter->cur += desc->bLength; + if (iter->cur > iter->end) { + printf("usb_desc_iter_next: descriptor length too large\n"); + return NULL; + } + return desc; +} + +usbd_status +usbd_get_string(usbd_device_handle dev, int si, char *buf, size_t len) +{ + int swap = dev->quirks->uq_flags & UQ_SWAP_UNICODE; + usb_string_descriptor_t us; + char *s; + int i, n; + u_int16_t c; + usbd_status err; + int size; + + buf[0] = '\0'; + if (len == 0) + return (USBD_NORMAL_COMPLETION); + if (si == 0) + return (USBD_INVAL); + if (dev->quirks->uq_flags & UQ_NO_STRINGS) + return (USBD_STALLED); + if (dev->langid == USBD_NOLANG) { + /* Set up default language */ + err = usbd_get_string_desc(dev, USB_LANGUAGE_TABLE, 0, &us, + &size); + if (err || size < 4) { + DPRINTFN(-1,("usbd_get_string: getting lang failed, using 0\n")); + dev->langid = 0; /* Well, just pick something then */ + } else { + /* Pick the first language as the default. */ + dev->langid = UGETW(us.bString[0]); + } + } + err = usbd_get_string_desc(dev, si, dev->langid, &us, &size); + if (err) + return (err); + s = buf; + n = size / 2 - 1; + for (i = 0; i < n && i < len - 1; i++) { + c = UGETW(us.bString[i]); + /* Convert from Unicode, handle buggy strings. */ + if ((c & 0xff00) == 0) + *s++ = c; + else if ((c & 0x00ff) == 0 && swap) + *s++ = c >> 8; + else + *s++ = '?'; + } + *s++ = 0; + return (USBD_NORMAL_COMPLETION); +} + +int +usbd_driver_load(module_t mod, int what, void *arg) +{ + /* XXX should implement something like a function that removes all generic devices */ + + return (0); +} diff --git a/sys/legacy/dev/usb/usbdi.h b/sys/legacy/dev/usb/usbdi.h new file mode 100644 index 0000000..ef59039 --- /dev/null +++ b/sys/legacy/dev/usb/usbdi.h @@ -0,0 +1,289 @@ +/* $NetBSD: usbdi.h,v 1.64 2004/10/23 13:26:34 augustss Exp $ */ +/* $FreeBSD$ */ + +/*- + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _USBDI_H_ +#define _USBDI_H_ + +typedef struct usbd_bus *usbd_bus_handle; +typedef struct usbd_device *usbd_device_handle; +typedef struct usbd_interface *usbd_interface_handle; +typedef struct usbd_pipe *usbd_pipe_handle; +typedef struct usbd_xfer *usbd_xfer_handle; +typedef void *usbd_private_handle; + +typedef enum { /* keep in sync with usbd_status_msgs */ + USBD_NORMAL_COMPLETION = 0, /* must be 0 */ + USBD_IN_PROGRESS, /* 1 */ + /* errors */ + USBD_PENDING_REQUESTS, /* 2 */ + USBD_NOT_STARTED, /* 3 */ + USBD_INVAL, /* 4 */ + USBD_NOMEM, /* 5 */ + USBD_CANCELLED, /* 6 */ + USBD_BAD_ADDRESS, /* 7 */ + USBD_IN_USE, /* 8 */ + USBD_NO_ADDR, /* 9 */ + USBD_SET_ADDR_FAILED, /* 10 */ + USBD_NO_POWER, /* 11 */ + USBD_TOO_DEEP, /* 12 */ + USBD_IOERROR, /* 13 */ + USBD_NOT_CONFIGURED, /* 14 */ + USBD_TIMEOUT, /* 15 */ + USBD_SHORT_XFER, /* 16 */ + USBD_STALLED, /* 17 */ + USBD_INTERRUPTED, /* 18 */ + + USBD_ERROR_MAX /* must be last */ +} usbd_status; + +typedef void (*usbd_callback)(usbd_xfer_handle, usbd_private_handle, + usbd_status); + +/* Open flags */ +#define USBD_EXCLUSIVE_USE 0x01 + +/* Use default (specified by ep. desc.) interval on interrupt pipe */ +#define USBD_DEFAULT_INTERVAL (-1) + +/* Request flags */ +#define USBD_NO_COPY 0x01 /* do not copy data to DMA buffer */ +#define USBD_SYNCHRONOUS 0x02 /* wait for completion */ +/* in usb.h #define USBD_SHORT_XFER_OK 0x04*/ /* allow short reads */ +#define USBD_FORCE_SHORT_XFER 0x08 /* force last short packet on write */ + +#define USBD_BITS "\20\1NO_COPY\2SYNCHRONOUS\4FORCE_SHORT_XFER" + +#define USBD_NO_TIMEOUT 0 +#define USBD_DEFAULT_TIMEOUT 5000 /* ms = 5 s */ + +usbd_status usbd_open_pipe(usbd_interface_handle, u_int8_t, + u_int8_t, usbd_pipe_handle *); +usbd_status usbd_close_pipe(usbd_pipe_handle); +usbd_status usbd_transfer(usbd_xfer_handle); +usbd_xfer_handle usbd_alloc_xfer(usbd_device_handle); +usbd_status usbd_free_xfer(usbd_xfer_handle); +void usbd_setup_xfer(usbd_xfer_handle, usbd_pipe_handle, + usbd_private_handle, void *, + u_int32_t, u_int16_t, u_int32_t, + usbd_callback); +void usbd_setup_default_xfer(usbd_xfer_handle, usbd_device_handle, + usbd_private_handle, u_int32_t, + usb_device_request_t *, void *, + u_int32_t, u_int16_t, usbd_callback); +void usbd_setup_isoc_xfer(usbd_xfer_handle, usbd_pipe_handle, + usbd_private_handle, u_int16_t *, + u_int32_t, u_int16_t, usbd_callback); +void usbd_get_xfer_status(usbd_xfer_handle, usbd_private_handle *, + void **, u_int32_t *, usbd_status *); +usb_endpoint_descriptor_t *usbd_interface2endpoint_descriptor + (usbd_interface_handle, u_int8_t); +usbd_status usbd_abort_pipe(usbd_pipe_handle); +usbd_status usbd_abort_default_pipe(usbd_device_handle); +usbd_status usbd_clear_endpoint_stall(usbd_pipe_handle); +usbd_status usbd_clear_endpoint_stall_async(usbd_pipe_handle); +void usbd_clear_endpoint_toggle(usbd_pipe_handle); +usbd_status usbd_endpoint_count(usbd_interface_handle, u_int8_t *); +usbd_status usbd_interface_count(usbd_device_handle, u_int8_t *); +void usbd_interface2device_handle(usbd_interface_handle, + usbd_device_handle *); +usbd_status usbd_device2interface_handle(usbd_device_handle, + u_int8_t, usbd_interface_handle *); + +usbd_device_handle usbd_pipe2device_handle(usbd_pipe_handle); +void *usbd_alloc_buffer(usbd_xfer_handle, u_int32_t); +void usbd_free_buffer(usbd_xfer_handle); +void *usbd_get_buffer(usbd_xfer_handle); +usbd_status usbd_sync_transfer(usbd_xfer_handle); +usbd_status usbd_open_pipe_intr(usbd_interface_handle, u_int8_t, + u_int8_t, usbd_pipe_handle *, + usbd_private_handle, void *, + u_int32_t, usbd_callback, int); +usbd_status usbd_do_request(usbd_device_handle, usb_device_request_t *, void *); +usbd_status usbd_do_request_async(usbd_device_handle, + usb_device_request_t *, void *); +usbd_status usbd_do_request_flags(usbd_device_handle, usb_device_request_t *, + void *, u_int16_t, int*, u_int32_t); +usbd_status usbd_do_request_flags_pipe(usbd_device_handle, usbd_pipe_handle, + usb_device_request_t *, void *, u_int16_t, int *, u_int32_t); +usb_interface_descriptor_t *usbd_get_interface_descriptor + (usbd_interface_handle); +usb_config_descriptor_t *usbd_get_config_descriptor(usbd_device_handle); +usb_device_descriptor_t *usbd_get_device_descriptor(usbd_device_handle); +int usbd_get_speed(usbd_device_handle); +usbd_status usbd_set_interface(usbd_interface_handle, int); +int usbd_get_no_alts(usb_config_descriptor_t *, int); +usbd_status usbd_get_interface(usbd_interface_handle, u_int8_t *); +void usbd_fill_deviceinfo(usbd_device_handle, struct usb_device_info *, int); +int usbd_get_interface_altindex(usbd_interface_handle); + +usb_interface_descriptor_t *usbd_find_idesc(usb_config_descriptor_t *, + int, int); +usb_endpoint_descriptor_t *usbd_find_edesc(usb_config_descriptor_t *, + int, int, int); + +void usbd_dopoll(usbd_interface_handle); +void usbd_set_polling(usbd_device_handle, int); +usbd_status usbd_reset_device(usbd_device_handle); + +const char *usbd_errstr(usbd_status); + +void usbd_add_dev_event(int, usbd_device_handle); +void usbd_add_drv_event(int, usbd_device_handle, device_t); + +void usbd_devinfo(usbd_device_handle, int, char *); +const struct usbd_quirks *usbd_get_quirks(usbd_device_handle); +usb_endpoint_descriptor_t *usbd_get_endpoint_descriptor + (usbd_interface_handle, u_int8_t); + +usbd_status usbd_reload_device_desc(usbd_device_handle); + +int usbd_ratecheck(struct timeval *last); + +usbd_status usbd_get_string(usbd_device_handle dev, int si, char *buf, + size_t len); + +/* An iterator for descriptors. */ +typedef struct { + const uByte *cur; + const uByte *end; +} usbd_desc_iter_t; +void usb_desc_iter_init(usbd_device_handle dev, usbd_desc_iter_t *iter); +const usb_descriptor_t *usb_desc_iter_next(usbd_desc_iter_t *iter); + +/* + * The usb_task structs form a queue of things to run in the USB event + * thread. Normally this is just device discovery when a connect/disconnect + * has been detected. But it may also be used by drivers that need to + * perform (short) tasks that must have a process context. + */ +struct usb_task { + TAILQ_ENTRY(usb_task) next; + void (*fun)(void *); + void *arg; + int queue; +}; +#define USB_TASKQ_HC 0 +#define USB_TASKQ_DRIVER 1 +#define USB_NUM_TASKQS 2 +#define USB_TASKQ_NAMES {"usbtask-hc", "usbtask-dr"} + +void usb_add_task(usbd_device_handle, struct usb_task *, int queue); +void usb_rem_task(usbd_device_handle, struct usb_task *); +#define usb_init_task(t, f, a) ((t)->fun = (f), (t)->arg = (a), (t)->queue = -1) + +struct usb_devno { + u_int16_t ud_vendor; + u_int16_t ud_product; +}; +const struct usb_devno *usb_match_device(const struct usb_devno *, + u_int, u_int, u_int16_t, u_int16_t); +#define usb_lookup(tbl, vendor, product) \ + usb_match_device((const struct usb_devno *)(tbl), sizeof (tbl) / sizeof ((tbl)[0]), sizeof ((tbl)[0]), (vendor), (product)) +#define USB_PRODUCT_ANY 0xffff + +/* NetBSD attachment information */ + +/* Attach data */ +struct usb_attach_arg { + int port; + int configno; + int ifaceno; + int vendor; + int product; + int release; + int matchlvl; + usbd_device_handle device; /* current device */ + usbd_interface_handle iface; /* current interface */ + int usegeneric; + usbd_interface_handle *ifaces; /* all interfaces */ + int nifaces; /* number of interfaces */ +}; + +/* FreeBSD needs values less than zero */ +#define UMATCH_VENDOR_PRODUCT_REV (-10) +#define UMATCH_VENDOR_PRODUCT (-20) +#define UMATCH_VENDOR_DEVCLASS_DEVPROTO (-30) +#define UMATCH_DEVCLASS_DEVSUBCLASS_DEVPROTO (-40) +#define UMATCH_DEVCLASS_DEVSUBCLASS (-50) +#define UMATCH_VENDOR_PRODUCT_REV_CONF_IFACE (-60) +#define UMATCH_VENDOR_PRODUCT_CONF_IFACE (-70) +#define UMATCH_VENDOR_IFACESUBCLASS_IFACEPROTO (-80) +#define UMATCH_VENDOR_IFACESUBCLASS (-90) +#define UMATCH_IFACECLASS_IFACESUBCLASS_IFACEPROTO (-100) +#define UMATCH_IFACECLASS_IFACESUBCLASS (-110) +#define UMATCH_IFACECLASS (-120) +#define UMATCH_IFACECLASS_GENERIC (-130) +#define UMATCH_GENERIC (-140) +#define UMATCH_NONE (ENXIO) + +#define USBD_SHOW_DEVICE_CLASS 0x1 +#define USBD_SHOW_INTERFACE_CLASS 0x2 + +struct module; +int usbd_driver_load(struct module *mod, int what, void *arg); + +static inline int +usb_get_port(device_t dev) +{ + struct usb_attach_arg *uap = device_get_ivars(dev); + return (uap->port); +} + +static inline struct usbd_interface * +usb_get_iface(device_t dev) +{ + struct usb_attach_arg *uap = device_get_ivars(dev); + return (uap->iface); +} + +/* XXX Perhaps USB should have its own levels? */ +#ifdef USB_USE_SOFTINTR +#ifdef __HAVE_GENERIC_SOFT_INTERRUPTS +#define splusb splsoftnet +#else +#define splusb splsoftclock +#endif /* __HAVE_GENERIC_SOFT_INTERRUPTS */ +#else +#define splusb splbio +#endif /* USB_USE_SOFTINTR */ +#define splhardusb splbio +#define IPL_USB IPL_BIO + +#endif /* _USBDI_H_ */ diff --git a/sys/legacy/dev/usb/usbdi_util.c b/sys/legacy/dev/usb/usbdi_util.c new file mode 100644 index 0000000..78ea571 --- /dev/null +++ b/sys/legacy/dev/usb/usbdi_util.c @@ -0,0 +1,539 @@ +/* $NetBSD: usbdi_util.c,v 1.42 2004/12/03 08:53:40 augustss Exp $ */ + +/*- + * 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/malloc.h> +#include <sys/bus.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbhid.h> + +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> + +#ifdef USB_DEBUG +#define DPRINTF(x) if (usbdebug) printf x +#define DPRINTFN(n,x) if (usbdebug>(n)) printf x +extern int usbdebug; +#else +#define DPRINTF(x) +#define DPRINTFN(n,x) +#endif + +usbd_status +usbd_get_desc(usbd_device_handle dev, int type, int index, int len, void *desc) +{ + usb_device_request_t req; + + DPRINTFN(3,("usbd_get_desc: type=%d, index=%d, len=%d\n", + type, index, len)); + + req.bmRequestType = UT_READ_DEVICE; + req.bRequest = UR_GET_DESCRIPTOR; + USETW2(req.wValue, type, index); + USETW(req.wIndex, 0); + USETW(req.wLength, len); + return (usbd_do_request(dev, &req, desc)); +} + +usbd_status +usbd_get_config_desc(usbd_device_handle dev, int confidx, + usb_config_descriptor_t *d) +{ + usbd_status err; + + DPRINTFN(3,("usbd_get_config_desc: confidx=%d\n", confidx)); + err = usbd_get_desc(dev, UDESC_CONFIG, confidx, + USB_CONFIG_DESCRIPTOR_SIZE, d); + if (err) + return (err); + if (d->bDescriptorType != UDESC_CONFIG) { + DPRINTFN(-1,("usbd_get_config_desc: confidx=%d, bad desc " + "len=%d type=%d\n", + confidx, d->bLength, d->bDescriptorType)); + return (USBD_INVAL); + } + return (USBD_NORMAL_COMPLETION); +} + +usbd_status +usbd_get_config_desc_full(usbd_device_handle dev, int conf, void *d, int size) +{ + DPRINTFN(3,("usbd_get_config_desc_full: conf=%d\n", conf)); + return (usbd_get_desc(dev, UDESC_CONFIG, conf, size, d)); +} + +usbd_status +usbd_get_device_desc(usbd_device_handle dev, usb_device_descriptor_t *d) +{ + DPRINTFN(3,("usbd_get_device_desc:\n")); + return (usbd_get_desc(dev, UDESC_DEVICE, + 0, USB_DEVICE_DESCRIPTOR_SIZE, d)); +} + +usbd_status +usbd_get_device_status(usbd_device_handle dev, usb_status_t *st) +{ + usb_device_request_t req; + + req.bmRequestType = UT_READ_DEVICE; + req.bRequest = UR_GET_STATUS; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, sizeof(usb_status_t)); + return (usbd_do_request(dev, &req, st)); +} + +usbd_status +usbd_get_hub_status(usbd_device_handle dev, usb_hub_status_t *st) +{ + usb_device_request_t req; + + req.bmRequestType = UT_READ_CLASS_DEVICE; + req.bRequest = UR_GET_STATUS; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, sizeof(usb_hub_status_t)); + return (usbd_do_request(dev, &req, st)); +} + +usbd_status +usbd_set_address(usbd_device_handle dev, int addr) +{ + usb_device_request_t req; + + req.bmRequestType = UT_WRITE_DEVICE; + req.bRequest = UR_SET_ADDRESS; + USETW(req.wValue, addr); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + return usbd_do_request(dev, &req, 0); +} + +usbd_status +usbd_get_port_status(usbd_device_handle dev, int port, usb_port_status_t *ps) +{ + usb_device_request_t req; + + req.bmRequestType = UT_READ_CLASS_OTHER; + req.bRequest = UR_GET_STATUS; + USETW(req.wValue, 0); + USETW(req.wIndex, port); + USETW(req.wLength, sizeof *ps); + return (usbd_do_request(dev, &req, ps)); +} + +usbd_status +usbd_clear_hub_feature(usbd_device_handle dev, int sel) +{ + usb_device_request_t 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(dev, &req, 0)); +} + +usbd_status +usbd_set_hub_feature(usbd_device_handle dev, int sel) +{ + usb_device_request_t 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(dev, &req, 0)); +} + +usbd_status +usbd_clear_port_feature(usbd_device_handle dev, int port, int sel) +{ + usb_device_request_t req; + + req.bmRequestType = UT_WRITE_CLASS_OTHER; + req.bRequest = UR_CLEAR_FEATURE; + USETW(req.wValue, sel); + USETW(req.wIndex, port); + USETW(req.wLength, 0); + return (usbd_do_request(dev, &req, 0)); +} + +usbd_status +usbd_set_port_feature(usbd_device_handle dev, int port, int sel) +{ + usb_device_request_t req; + + req.bmRequestType = UT_WRITE_CLASS_OTHER; + req.bRequest = UR_SET_FEATURE; + USETW(req.wValue, sel); + USETW(req.wIndex, port); + USETW(req.wLength, 0); + return (usbd_do_request(dev, &req, 0)); +} + +usbd_status +usbd_get_protocol(usbd_interface_handle iface, u_int8_t *report) +{ + usb_interface_descriptor_t *id = usbd_get_interface_descriptor(iface); + usbd_device_handle dev; + usb_device_request_t req; + + DPRINTFN(4, ("usbd_get_protocol: iface=%p, endpt=%d\n", + iface, id->bInterfaceNumber)); + if (id == NULL) + return (USBD_IOERROR); + usbd_interface2device_handle(iface, &dev); + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = UR_GET_PROTOCOL; + USETW(req.wValue, 0); + USETW(req.wIndex, id->bInterfaceNumber); + USETW(req.wLength, 1); + return (usbd_do_request(dev, &req, report)); +} + +usbd_status +usbd_set_protocol(usbd_interface_handle iface, int report) +{ + usb_interface_descriptor_t *id = usbd_get_interface_descriptor(iface); + usbd_device_handle dev; + usb_device_request_t req; + + DPRINTFN(4, ("usbd_set_protocol: iface=%p, report=%d, endpt=%d\n", + iface, report, id->bInterfaceNumber)); + if (id == NULL) + return (USBD_IOERROR); + usbd_interface2device_handle(iface, &dev); + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_SET_PROTOCOL; + USETW(req.wValue, report); + USETW(req.wIndex, id->bInterfaceNumber); + USETW(req.wLength, 0); + return (usbd_do_request(dev, &req, 0)); +} + +usbd_status +usbd_set_report(usbd_interface_handle iface, int type, int id, void *data, + int len) +{ + usb_interface_descriptor_t *ifd = usbd_get_interface_descriptor(iface); + usbd_device_handle dev; + usb_device_request_t req; + + DPRINTFN(4, ("usbd_set_report: len=%d\n", len)); + if (ifd == NULL) + return (USBD_IOERROR); + usbd_interface2device_handle(iface, &dev); + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_SET_REPORT; + USETW2(req.wValue, type, id); + USETW(req.wIndex, ifd->bInterfaceNumber); + USETW(req.wLength, len); + return (usbd_do_request(dev, &req, data)); +} + +usbd_status +usbd_set_report_async(usbd_interface_handle iface, int type, int id, void *data, + int len) +{ + usb_interface_descriptor_t *ifd = usbd_get_interface_descriptor(iface); + usbd_device_handle dev; + usb_device_request_t req; + + DPRINTFN(4, ("usbd_set_report_async: len=%d\n", len)); + if (ifd == NULL) + return (USBD_IOERROR); + usbd_interface2device_handle(iface, &dev); + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_SET_REPORT; + USETW2(req.wValue, type, id); + USETW(req.wIndex, ifd->bInterfaceNumber); + USETW(req.wLength, len); + return (usbd_do_request_async(dev, &req, data)); +} + +usbd_status +usbd_get_report(usbd_interface_handle iface, int type, int id, void *data, + int len) +{ + usb_interface_descriptor_t *ifd = usbd_get_interface_descriptor(iface); + usbd_device_handle dev; + usb_device_request_t req; + + DPRINTFN(4, ("usbd_get_report: len=%d\n", len)); + if (ifd == NULL) + return (USBD_IOERROR); + usbd_interface2device_handle(iface, &dev); + req.bmRequestType = UT_READ_CLASS_INTERFACE; + req.bRequest = UR_GET_REPORT; + USETW2(req.wValue, type, id); + USETW(req.wIndex, ifd->bInterfaceNumber); + USETW(req.wLength, len); + return (usbd_do_request(dev, &req, data)); +} + +usbd_status +usbd_set_idle(usbd_interface_handle iface, int duration, int id) +{ + usb_interface_descriptor_t *ifd = usbd_get_interface_descriptor(iface); + usbd_device_handle dev; + usb_device_request_t req; + + DPRINTFN(4, ("usbd_set_idle: %d %d\n", duration, id)); + if (ifd == NULL) + return (USBD_IOERROR); + usbd_interface2device_handle(iface, &dev); + req.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.bRequest = UR_SET_IDLE; + USETW2(req.wValue, duration, id); + USETW(req.wIndex, ifd->bInterfaceNumber); + USETW(req.wLength, 0); + return (usbd_do_request(dev, &req, 0)); +} + +usbd_status +usbd_get_report_descriptor(usbd_device_handle dev, int ifcno, + int size, void *d) +{ + usb_device_request_t req; + + req.bmRequestType = UT_READ_INTERFACE; + req.bRequest = UR_GET_DESCRIPTOR; + USETW2(req.wValue, UDESC_REPORT, 0); /* report id should be 0 */ + USETW(req.wIndex, ifcno); + USETW(req.wLength, size); + return (usbd_do_request(dev, &req, d)); +} + +usb_hid_descriptor_t * +usbd_get_hid_descriptor(usbd_interface_handle ifc) +{ + usb_interface_descriptor_t *idesc = usbd_get_interface_descriptor(ifc); + usbd_device_handle dev; + usb_config_descriptor_t *cdesc; + usb_hid_descriptor_t *hd; + char *p, *end; + + if (idesc == NULL) + return (NULL); + usbd_interface2device_handle(ifc, &dev); + cdesc = usbd_get_config_descriptor(dev); + + p = (char *)idesc + idesc->bLength; + end = (char *)cdesc + UGETW(cdesc->wTotalLength); + + for (; p < end; p += hd->bLength) { + hd = (usb_hid_descriptor_t *)p; + if (p + hd->bLength <= end && hd->bDescriptorType == UDESC_HID) + return (hd); + if (hd->bDescriptorType == UDESC_INTERFACE) + break; + } + return (NULL); +} + +usbd_status +usbd_read_report_desc(usbd_interface_handle ifc, void **descp, int *sizep, + struct malloc_type *mem) +{ + usb_interface_descriptor_t *id; + usb_hid_descriptor_t *hid; + usbd_device_handle dev; + usbd_status err; + + usbd_interface2device_handle(ifc, &dev); + id = usbd_get_interface_descriptor(ifc); + if (id == NULL) + return (USBD_INVAL); + hid = usbd_get_hid_descriptor(ifc); + if (hid == NULL) + return (USBD_IOERROR); + *sizep = UGETW(hid->descrs[0].wDescriptorLength); + *descp = malloc(*sizep, mem, M_NOWAIT); + if (*descp == NULL) + return (USBD_NOMEM); + err = usbd_get_report_descriptor(dev, id->bInterfaceNumber, + *sizep, *descp); + if (err) { + free(*descp, mem); + *descp = NULL; + return (err); + } + return (USBD_NORMAL_COMPLETION); +} + +usbd_status +usbd_get_config(usbd_device_handle dev, u_int8_t *conf) +{ + usb_device_request_t 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(dev, &req, conf)); +} + +static void usbd_bulk_transfer_cb(usbd_xfer_handle xfer, + usbd_private_handle priv, usbd_status status); +static void +usbd_bulk_transfer_cb(usbd_xfer_handle xfer, usbd_private_handle priv, + usbd_status status) +{ + wakeup(xfer); +} + +usbd_status +usbd_bulk_transfer(usbd_xfer_handle xfer, usbd_pipe_handle pipe, + u_int16_t flags, u_int32_t timeout, void *buf, + u_int32_t *size, char *lbl) +{ + usbd_status err; + int s, error; + + usbd_setup_xfer(xfer, pipe, 0, buf, *size, + flags, timeout, usbd_bulk_transfer_cb); + DPRINTFN(1, ("usbd_bulk_transfer: start transfer %d bytes\n", *size)); + s = splusb(); /* don't want callback until tsleep() */ + err = usbd_transfer(xfer); + if (err != USBD_IN_PROGRESS) { + splx(s); + return (err); + } + error = tsleep((caddr_t)xfer, PZERO | PCATCH, lbl, 0); + splx(s); + if (error) { + DPRINTF(("usbd_bulk_transfer: tsleep=%d\n", error)); + usbd_abort_pipe(pipe); + return (USBD_INTERRUPTED); + } + usbd_get_xfer_status(xfer, NULL, NULL, size, &err); + DPRINTFN(1,("usbd_bulk_transfer: transferred %d\n", *size)); + if (err) { + DPRINTF(("usbd_bulk_transfer: error=%d\n", err)); + usbd_clear_endpoint_stall(pipe); + } + return (err); +} + +static void usbd_intr_transfer_cb(usbd_xfer_handle xfer, + usbd_private_handle priv, usbd_status status); +static void +usbd_intr_transfer_cb(usbd_xfer_handle xfer, usbd_private_handle priv, + usbd_status status) +{ + wakeup(xfer); +} + +usbd_status +usbd_intr_transfer(usbd_xfer_handle xfer, usbd_pipe_handle pipe, + u_int16_t flags, u_int32_t timeout, void *buf, + u_int32_t *size, char *lbl) +{ + usbd_status err; + int s, error; + + usbd_setup_xfer(xfer, pipe, 0, buf, *size, + flags, timeout, usbd_intr_transfer_cb); + DPRINTFN(1, ("usbd_intr_transfer: start transfer %d bytes\n", *size)); + s = splusb(); /* don't want callback until tsleep() */ + err = usbd_transfer(xfer); + if (err != USBD_IN_PROGRESS) { + splx(s); + return (err); + } + error = tsleep(xfer, PZERO | PCATCH, lbl, 0); + splx(s); + if (error) { + DPRINTF(("usbd_intr_transfer: tsleep=%d\n", error)); + usbd_abort_pipe(pipe); + return (USBD_INTERRUPTED); + } + usbd_get_xfer_status(xfer, NULL, NULL, size, &err); + DPRINTFN(1,("usbd_intr_transfer: transferred %d\n", *size)); + if (err) { + DPRINTF(("usbd_intr_transfer: error=%d\n", err)); + usbd_clear_endpoint_stall(pipe); + } + return (err); +} + +void +usb_detach_wait(device_t dv) +{ + DPRINTF(("usb_detach_wait: waiting for %s\n", device_get_nameunit(dv))); + if (tsleep(dv, PZERO, "usbdet", hz * 60)) + printf("usb_detach_wait: %s didn't detach\n", + device_get_nameunit(dv)); + DPRINTF(("usb_detach_wait: %s done\n", device_get_nameunit(dv))); +} + +void +usb_detach_wakeup(device_t dv) +{ + DPRINTF(("usb_detach_wakeup: for %s\n", device_get_nameunit(dv))); + wakeup(dv); +} + +const usb_descriptor_t * +usb_find_desc(usbd_device_handle dev, int type, int subtype) +{ + usbd_desc_iter_t iter; + const usb_descriptor_t *desc; + + usb_desc_iter_init(dev, &iter); + for (;;) { + desc = usb_desc_iter_next(&iter); + if (!desc || (desc->bDescriptorType == type && + (subtype == USBD_SUBTYPE_ANY || + subtype == desc->bDescriptorSubtype))) + break; + } + return desc; +} diff --git a/sys/legacy/dev/usb/usbdi_util.h b/sys/legacy/dev/usb/usbdi_util.h new file mode 100644 index 0000000..b535f0c --- /dev/null +++ b/sys/legacy/dev/usb/usbdi_util.h @@ -0,0 +1,98 @@ +/* $NetBSD: usbdi_util.h,v 1.31 2004/12/03 08:53:40 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 _USBI_UTIL_H_ +#define _USBI_UTIL_H_ +usbd_status usbd_get_desc(usbd_device_handle dev, int type, + int index, int len, void *desc); +usbd_status usbd_get_config_desc(usbd_device_handle, int, + usb_config_descriptor_t *); +usbd_status usbd_get_config_desc_full(usbd_device_handle, int, void *, int); +usbd_status usbd_get_device_desc(usbd_device_handle dev, + usb_device_descriptor_t *d); +usbd_status usbd_set_address(usbd_device_handle dev, int addr); +usbd_status usbd_get_port_status(usbd_device_handle, + int, usb_port_status_t *); +usbd_status usbd_set_hub_feature(usbd_device_handle dev, int); +usbd_status usbd_clear_hub_feature(usbd_device_handle, int); +usbd_status usbd_set_port_feature(usbd_device_handle dev, int, int); +usbd_status usbd_clear_port_feature(usbd_device_handle, int, int); +usbd_status usbd_get_device_status(usbd_device_handle, usb_status_t *); +usbd_status usbd_get_hub_status(usbd_device_handle, usb_hub_status_t *); +usbd_status usbd_get_protocol(usbd_interface_handle dev, u_int8_t *report); +usbd_status usbd_set_protocol(usbd_interface_handle dev, int report); +usbd_status usbd_get_report_descriptor(usbd_device_handle dev, int ifcno, + int size, void *d); +struct usb_hid_descriptor *usbd_get_hid_descriptor(usbd_interface_handle ifc); +usbd_status usbd_set_report(usbd_interface_handle iface, int type, int id, + void *data,int len); +usbd_status usbd_set_report_async(usbd_interface_handle iface, int type, + int id, void *data, int len); +usbd_status usbd_get_report(usbd_interface_handle iface, int type, int id, + void *data, int len); +usbd_status usbd_set_idle(usbd_interface_handle iface, int duration, int id); +usbd_status usbd_read_report_desc(usbd_interface_handle ifc, void **descp, + int *sizep, struct malloc_type *mem); +usbd_status usbd_get_config(usbd_device_handle dev, u_int8_t *conf); +usbd_status usbd_get_string_desc(usbd_device_handle dev, int sindex, + int langid,usb_string_descriptor_t *sdesc, + int *sizep); +void usbd_delay_ms(usbd_device_handle, u_int); + + +usbd_status usbd_set_config_no(usbd_device_handle dev, int no, int msg); +usbd_status usbd_set_config_index(usbd_device_handle dev, int index, int msg); + +usbd_status usbd_bulk_transfer(usbd_xfer_handle xfer, usbd_pipe_handle pipe, + u_int16_t flags, u_int32_t timeout, void *buf, + u_int32_t *size, char *lbl); + +usbd_status usbd_intr_transfer(usbd_xfer_handle xfer, usbd_pipe_handle pipe, + u_int16_t flags, u_int32_t timeout, void *buf, + u_int32_t *size, char *lbl); + +void usb_detach_wait(device_t); +void usb_detach_wakeup(device_t); + +const usb_descriptor_t *usb_find_desc(usbd_device_handle dev, int type, + int subtype); +#define USBD_SUBTYPE_ANY (~0) + +#endif /* _USBI_UTIL_H_ */ diff --git a/sys/legacy/dev/usb/usbdivar.h b/sys/legacy/dev/usb/usbdivar.h new file mode 100644 index 0000000..603d691 --- /dev/null +++ b/sys/legacy/dev/usb/usbdivar.h @@ -0,0 +1,322 @@ +/* $NetBSD: usbdivar.h,v 1.70 2002/07/11 21:14:36 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. + */ + +/* From usb_mem.h */ +struct usb_dma_block; +typedef struct { + struct usb_dma_block *block; + u_int offs; + u_int len; +} usb_dma_t; + +struct usbd_xfer; +struct usbd_pipe; + +struct usbd_endpoint { + usb_endpoint_descriptor_t *edesc; + int refcnt; + int savedtoggle; +}; + +struct usbd_bus_methods { + usbd_status (*open_pipe)(struct usbd_pipe *pipe); + void (*soft_intr)(void *); + void (*do_poll)(struct usbd_bus *); + usbd_status (*allocm)(struct usbd_bus *, usb_dma_t *, + u_int32_t bufsize); + void (*freem)(struct usbd_bus *, usb_dma_t *); + struct usbd_xfer * (*allocx)(struct usbd_bus *); + void (*freex)(struct usbd_bus *, struct usbd_xfer *); +}; + +struct usbd_pipe_methods { + usbd_status (*transfer)(usbd_xfer_handle xfer); + usbd_status (*start)(usbd_xfer_handle xfer); + void (*abort)(usbd_xfer_handle xfer); + void (*close)(usbd_pipe_handle pipe); + void (*cleartoggle)(usbd_pipe_handle pipe); + void (*done)(usbd_xfer_handle xfer); +}; + +struct usbd_tt { + struct usbd_hub *hub; +}; + +struct usbd_port { + usb_port_status_t status; + u_int16_t power; /* mA of current on port */ + u_int8_t portno; + u_int8_t restartcnt; +#define USBD_RESTART_MAX 5 + struct usbd_device *device; /* Connected device */ + struct usbd_device *parent; /* The ports hub */ + struct usbd_tt *tt; /* Transaction translator (if any) */ +}; + +struct usbd_hub { + usbd_status (*explore)(usbd_device_handle hub); + void *hubsoftc; + usb_hub_descriptor_t hubdesc; + struct usbd_port ports[1]; +}; + +struct usb_softc; + +/*****/ + +struct usbd_bus { + /* Filled by HC driver */ + device_t bdev; /* base device, host adapter */ + struct usbd_bus_methods *methods; + u_int32_t pipe_size; /* size of a pipe struct */ + /* Filled by usb driver */ + struct usbd_device *root_hub; + usbd_device_handle devices[USB_MAX_DEVICES]; + char needs_explore;/* a hub a signalled a change */ + char use_polling; + struct usb_softc *usbctl; + struct usb_device_stats stats; + int intr_context; + u_int no_intrs; + int usbrev; /* USB revision */ +#define USBREV_UNKNOWN 0 +#define USBREV_PRE_1_0 1 +#define USBREV_1_0 2 +#define USBREV_1_1 3 +#define USBREV_2_0 4 +#define USBREV_STR { "unknown", "pre 1.0", "1.0", "1.1", "2.0" } + +#ifdef USB_USE_SOFTINTR +#ifdef __HAVE_GENERIC_SOFT_INTERRUPTS + void *soft; /* soft interrupt cookie */ +#else + struct callout softi; +#endif +#endif + + bus_dma_tag_t parent_dmatag; /* Base DMA tag */ + bus_dma_tag_t buffer_dmatag; /* Tag for transfer buffers */ +}; + +struct usbd_device { + struct usbd_bus *bus; /* our controller */ + struct usbd_pipe *default_pipe; /* pipe 0 */ + u_int8_t address; /* device addess */ + u_int8_t config; /* current configuration # */ + u_int8_t depth; /* distance from root hub */ + u_int8_t speed; /* low/full/high speed */ + u_int8_t self_powered; /* flag for self powered */ + u_int16_t power; /* mA the device uses */ + int16_t langid; /* language for strings */ +#define USBD_NOLANG (-1) + usb_event_cookie_t cookie; /* unique connection id */ + struct usbd_port *powersrc; /* upstream hub port, or 0 */ + struct usbd_device *myhub; /* upstream hub */ + struct usbd_port *myhsport; /* closest high speed port */ + struct usbd_endpoint def_ep; /* for pipe 0 */ + usb_endpoint_descriptor_t def_ep_desc; /* for pipe 0 */ + struct usbd_interface *ifaces; /* array of all interfaces */ + usb_device_descriptor_t ddesc; /* device descriptor */ + usb_config_descriptor_t *cdesc; /* full config descr */ + const struct usbd_quirks *quirks; /* device quirks, always set */ + struct usbd_hub *hub; /* only if this is a hub */ + device_t *subdevs; /* sub-devices, 0 terminated */ + uint8_t *ifacenums; /* sub-device interfacenumbers */ +}; + +struct usbd_interface { + struct usbd_device *device; + usb_interface_descriptor_t *idesc; + int index; + int altindex; + struct usbd_endpoint *endpoints; + void *priv; + LIST_HEAD(, usbd_pipe) pipes; +}; + +struct usbd_pipe { + struct usbd_interface *iface; + struct usbd_device *device; + struct usbd_endpoint *endpoint; + int refcnt; + char running; + char aborting; + STAILQ_HEAD(, usbd_xfer) queue; + LIST_ENTRY(usbd_pipe) next; + + usbd_xfer_handle intrxfer; /* used for repeating requests */ + char repeat; + int interval; + + /* Filled by HC driver. */ + struct usbd_pipe_methods *methods; +}; + +#define USB_DMA_NSEG (btoc(MAXPHYS) + 1) + +/* DMA-capable memory buffer. */ +struct usb_dma_mapping { + bus_dma_segment_t segs[USB_DMA_NSEG]; /* The physical segments. */ + int nsegs; /* Number of segments. */ + bus_dmamap_t map; /* DMA mapping. */ +}; + +struct usbd_xfer { + struct usbd_pipe *pipe; + void *priv; + void *buffer; + u_int32_t length; + u_int32_t actlen; + u_int16_t flags; + u_int32_t timeout; + usbd_status status; + usbd_callback callback; + __volatile char done; +#ifdef DIAGNOSTIC + u_int32_t busy_free; +#define XFER_FREE 0x46524545 +#define XFER_BUSY 0x42555359 +#define XFER_ONQU 0x4f4e5155 +#endif + + /* For control pipe */ + usb_device_request_t request; + + /* For isoc */ + u_int16_t *frlengths; + int nframes; + + /* For memory allocation */ + struct usbd_device *device; + struct usb_dma_mapping dmamap; + void *allocbuf; + + int rqflags; +#define URQ_REQUEST 0x01 +#define URQ_AUTO_DMABUF 0x10 +#define URQ_DEV_DMABUF 0x20 + + STAILQ_ENTRY(usbd_xfer) next; + + void *hcpriv; /* private use by the HC driver */ + + struct callout timeout_handle; +}; + +#define URQ_BITS "\20\1REQUEST\5AUTO_DMABUF\6DEV_DMABUF" + +void usbd_init(void); +void usbd_finish(void); + +#ifdef USB_DEBUG +void usbd_dump_iface(struct usbd_interface *iface); +void usbd_dump_device(struct usbd_device *dev); +void usbd_dump_endpoint(struct usbd_endpoint *endp); +void usbd_dump_queue(usbd_pipe_handle pipe); +void usbd_dump_pipe(usbd_pipe_handle pipe); +#endif + +/* Routines from usb_subr.c */ +int usbctlprint(void *, const char *); +void usb_delay_ms(usbd_bus_handle, u_int); +usbd_status usbd_reset_port(usbd_device_handle dev, + int port, usb_port_status_t *ps); +usbd_status usbd_setup_pipe(usbd_device_handle dev, + usbd_interface_handle iface, + struct usbd_endpoint *, int, + usbd_pipe_handle *pipe); +usbd_status usbd_new_device(device_t parent, + usbd_bus_handle bus, int depth, + int lowspeed, int port, + struct usbd_port *); +void usbd_remove_device(usbd_device_handle, struct usbd_port *); +int usbd_printBCD(char *cp, int bcd); +usbd_status usbd_fill_iface_data(usbd_device_handle dev, int i, int a); +void usb_free_device(usbd_device_handle); + +usbd_status usb_insert_transfer(usbd_xfer_handle xfer); +void usb_transfer_complete(usbd_xfer_handle xfer); +void usb_disconnect_port(struct usbd_port *up, device_t); + +/* Routines from usb.c */ +void usb_needs_explore(usbd_device_handle); +void usb_schedsoftintr(struct usbd_bus *); + +/* + * XXX This check is extremely bogus. Bad Bad Bad. + */ +#if defined(DIAGNOSTIC) && 0 +#define SPLUSBCHECK \ + do { int _s = splusb(), _su = splusb(); \ + if (!cold && _s != _su) printf("SPLUSBCHECK failed 0x%x!=0x%x, %s:%d\n", \ + _s, _su, __FILE__, __LINE__); \ + splx(_s); \ + } while (0) +#else +#define SPLUSBCHECK +#endif + +/* Locator stuff. */ + +/* XXX these values are used to statically bind some elements in the USB tree + * to specific driver instances. This should be somehow emulated in FreeBSD + * but can be done later on. + * The values are copied from the files.usb file in the NetBSD sources. + */ +#define UHUBCF_PORT_DEFAULT -1 +#define UHUBCF_CONFIGURATION_DEFAULT -1 +#define UHUBCF_INTERFACE_DEFAULT -1 +#define UHUBCF_VENDOR_DEFAULT -1 +#define UHUBCF_PRODUCT_DEFAULT -1 +#define UHUBCF_RELEASE_DEFAULT -1 + +#define uhubcf_port cf_loc[UHUBCF_PORT] +#define uhubcf_configuration cf_loc[UHUBCF_CONFIGURATION] +#define uhubcf_interface cf_loc[UHUBCF_INTERFACE] +#define uhubcf_vendor cf_loc[UHUBCF_VENDOR] +#define uhubcf_product cf_loc[UHUBCF_PRODUCT] +#define uhubcf_release cf_loc[UHUBCF_RELEASE] +#define UHUB_UNK_PORT UHUBCF_PORT_DEFAULT /* wildcarded 'port' */ +#define UHUB_UNK_CONFIGURATION UHUBCF_CONFIGURATION_DEFAULT /* wildcarded 'configuration' */ +#define UHUB_UNK_INTERFACE UHUBCF_INTERFACE_DEFAULT /* wildcarded 'interface' */ +#define UHUB_UNK_VENDOR UHUBCF_VENDOR_DEFAULT /* wildcarded 'vendor' */ +#define UHUB_UNK_PRODUCT UHUBCF_PRODUCT_DEFAULT /* wildcarded 'product' */ +#define UHUB_UNK_RELEASE UHUBCF_RELEASE_DEFAULT /* wildcarded 'release' */ + diff --git a/sys/legacy/dev/usb/usbhid.h b/sys/legacy/dev/usb/usbhid.h new file mode 100644 index 0000000..8e0ecd5 --- /dev/null +++ b/sys/legacy/dev/usb/usbhid.h @@ -0,0 +1,185 @@ +/* $NetBSD: usbhid.h,v 1.9 2000/09/03 19:09:14 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 _USBHID_H_ +#define _USBHID_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 + +typedef struct usb_hid_descriptor { + uByte bLength; + uByte bDescriptorType; + uWord bcdHID; + uByte bCountryCode; + uByte bNumDescriptors; + struct { + uByte bDescriptorType; + uWord wDescriptorLength; + } descrs[1]; +} UPACKED usb_hid_descriptor_t; +#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 + +/* 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 + +#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 + +#endif /* _USBHID_H_ */ diff --git a/sys/legacy/dev/usb/uscanner.c b/sys/legacy/dev/usb/uscanner.c new file mode 100644 index 0000000..bff3a48 --- /dev/null +++ b/sys/legacy/dev/usb/uscanner.c @@ -0,0 +1,723 @@ +/* $NetBSD: uscanner.c,v 1.30 2002/07/11 21:14:36 augustss Exp$ */ + +/* Also already merged from NetBSD: + * $NetBSD: uscanner.c,v 1.33 2002/09/23 05:51:24 simonb Exp $ + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/*- + * Copyright (c) 2000 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 + * and Nick Hibma (n_hibma@qubesoft.com). + * + * 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 <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/conf.h> +#include <sys/fcntl.h> +#include <sys/filio.h> +#include <sys/tty.h> +#include <sys/file.h> +#include <sys/selinfo.h> +#include <sys/proc.h> +#include <sys/poll.h> +#include <sys/conf.h> +#include <sys/sysctl.h> +#include <sys/uio.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> + +#include "usbdevs.h" + +#ifdef USB_DEBUG +#define DPRINTF(x) if (uscannerdebug) printf x +#define DPRINTFN(n,x) if (uscannerdebug>(n)) printf x +int uscannerdebug = 0; +SYSCTL_NODE(_hw_usb, OID_AUTO, uscanner, CTLFLAG_RW, 0, "USB uscanner"); +SYSCTL_INT(_hw_usb_uscanner, OID_AUTO, debug, CTLFLAG_RW, + &uscannerdebug, 0, "uscanner debug level"); +#else +#define DPRINTF(x) +#define DPRINTFN(n,x) +#endif + +struct uscan_info { + struct usb_devno devno; + u_int flags; +#define USC_KEEP_OPEN 1 +}; + +/* Table of scanners that may work with this driver. */ +static const struct uscan_info uscanner_devs[] = { + + /* + * These first two entries are duplicates of known-working units, + * so one can patch them to test support for newer devices + * without rebuilding the module. + */ + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_6000 }, 0 }, /* duplicate */ + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_6000 }, 0 }, /* duplicate */ + + /* Acer Peripherals */ + {{ USB_VENDOR_ACERP, USB_PRODUCT_ACERP_ACERSCAN_320U }, 0 }, + {{ USB_VENDOR_ACERP, USB_PRODUCT_ACERP_ACERSCAN_4300U }, 0 }, + {{ USB_VENDOR_ACERP, USB_PRODUCT_ACERP_ACERSCAN_640U }, 0 }, + {{ USB_VENDOR_ACERP, USB_PRODUCT_ACERP_ACERSCAN_640BT }, 0 }, + {{ USB_VENDOR_ACERP, USB_PRODUCT_ACERP_ACERSCAN_620U }, 0 }, + {{ USB_VENDOR_ACERP, USB_PRODUCT_ACERP_ACERSCAN_1240U }, 0 }, + {{ USB_VENDOR_ACERP, USB_PRODUCT_ACERP_ACERSCAN_C310U }, 0 }, + + /* AGFA */ + {{ USB_VENDOR_AGFA, USB_PRODUCT_AGFA_SNAPSCAN1236U }, 0 }, + {{ USB_VENDOR_AGFA, USB_PRODUCT_AGFA_SNAPSCAN1212U }, 0 }, + {{ USB_VENDOR_AGFA, USB_PRODUCT_AGFA_SNAPSCAN1212U2 }, 0 }, + {{ USB_VENDOR_AGFA, USB_PRODUCT_AGFA_SNAPSCANTOUCH }, 0 }, + {{ USB_VENDOR_AGFA, USB_PRODUCT_AGFA_SNAPSCANE40 }, 0 }, + {{ USB_VENDOR_AGFA, USB_PRODUCT_AGFA_SNAPSCANE50 }, 0 }, + {{ USB_VENDOR_AGFA, USB_PRODUCT_AGFA_SNAPSCANE20 }, 0 }, + {{ USB_VENDOR_AGFA, USB_PRODUCT_AGFA_SNAPSCANE25 }, 0 }, + {{ USB_VENDOR_AGFA, USB_PRODUCT_AGFA_SNAPSCANE26 }, 0 }, + {{ USB_VENDOR_AGFA, USB_PRODUCT_AGFA_SNAPSCANE52 }, 0 }, + + /* Avision */ + {{ USB_VENDOR_AVISION, USB_PRODUCT_AVISION_1200U }, 0 }, + + /* Canon */ + {{ USB_VENDOR_CANON, USB_PRODUCT_CANON_N656U }, 0 }, + {{ USB_VENDOR_CANON, USB_PRODUCT_CANON_N676U }, 0 }, + {{ USB_VENDOR_CANON, USB_PRODUCT_CANON_N1220U }, 0 }, + {{ USB_VENDOR_CANON, USB_PRODUCT_CANON_D660U }, 0 }, + {{ USB_VENDOR_CANON, USB_PRODUCT_CANON_N1240U }, 0 }, + {{ USB_VENDOR_CANON, USB_PRODUCT_CANON_LIDE25 }, 0 }, + + /* Kye */ + {{ USB_VENDOR_KYE, USB_PRODUCT_KYE_VIVIDPRO }, 0 }, + + /* HP */ + {{ USB_VENDOR_HP, USB_PRODUCT_HP_2200C }, 0 }, + {{ USB_VENDOR_HP, USB_PRODUCT_HP_3300C }, 0 }, + {{ USB_VENDOR_HP, USB_PRODUCT_HP_3400CSE }, 0 }, + {{ USB_VENDOR_HP, USB_PRODUCT_HP_4100C }, 0 }, + {{ USB_VENDOR_HP, USB_PRODUCT_HP_4200C }, 0 }, + {{ USB_VENDOR_HP, USB_PRODUCT_HP_4300C }, 0 }, + {{ USB_VENDOR_HP, USB_PRODUCT_HP_4470C }, 0 }, + {{ USB_VENDOR_HP, USB_PRODUCT_HP_4670V }, 0 }, + {{ USB_VENDOR_HP, USB_PRODUCT_HP_S20 }, 0 }, + {{ USB_VENDOR_HP, USB_PRODUCT_HP_5200C }, 0 }, + {{ USB_VENDOR_HP, USB_PRODUCT_HP_5300C }, 0 }, + {{ USB_VENDOR_HP, USB_PRODUCT_HP_5400C }, 0 }, + {{ USB_VENDOR_HP, USB_PRODUCT_HP_6200C }, 0 }, + {{ USB_VENDOR_HP, USB_PRODUCT_HP_6300C }, 0 }, + {{ USB_VENDOR_HP, USB_PRODUCT_HP_82x0C }, 0 }, + + /* Microtek */ + {{ USB_VENDOR_SCANLOGIC, USB_PRODUCT_SCANLOGIC_336CX }, 0 }, + {{ USB_VENDOR_MICROTEK, USB_PRODUCT_MICROTEK_X6U }, 0 }, + {{ USB_VENDOR_MICROTEK, USB_PRODUCT_MICROTEK_336CX }, 0 }, + {{ USB_VENDOR_MICROTEK, USB_PRODUCT_MICROTEK_336CX2 }, 0 }, + {{ USB_VENDOR_MICROTEK, USB_PRODUCT_MICROTEK_C6 }, 0 }, + {{ USB_VENDOR_MICROTEK, USB_PRODUCT_MICROTEK_V6USL }, 0 }, + {{ USB_VENDOR_MICROTEK, USB_PRODUCT_MICROTEK_V6USL2 }, 0 }, + {{ USB_VENDOR_MICROTEK, USB_PRODUCT_MICROTEK_V6UL }, 0 }, + + /* Minolta */ + {{ USB_VENDOR_MINOLTA, USB_PRODUCT_MINOLTA_5400 }, 0 }, + + /* Mustek */ + {{ USB_VENDOR_MUSTEK, USB_PRODUCT_MUSTEK_1200CU }, 0 }, + {{ USB_VENDOR_MUSTEK, USB_PRODUCT_MUSTEK_BEARPAW1200F }, 0 }, + {{ USB_VENDOR_MUSTEK, USB_PRODUCT_MUSTEK_BEARPAW1200TA }, 0 }, + {{ USB_VENDOR_MUSTEK, USB_PRODUCT_MUSTEK_600USB }, 0 }, + {{ USB_VENDOR_MUSTEK, USB_PRODUCT_MUSTEK_600CU }, 0 }, + {{ USB_VENDOR_MUSTEK, USB_PRODUCT_MUSTEK_1200USB }, 0 }, + {{ USB_VENDOR_MUSTEK, USB_PRODUCT_MUSTEK_1200UB }, 0 }, + {{ USB_VENDOR_MUSTEK, USB_PRODUCT_MUSTEK_1200USBPLUS }, 0 }, + {{ USB_VENDOR_MUSTEK, USB_PRODUCT_MUSTEK_1200CUPLUS }, 0 }, + + /* National */ + {{ USB_VENDOR_NATIONAL, USB_PRODUCT_NATIONAL_BEARPAW1200 }, 0 }, + {{ USB_VENDOR_NATIONAL, USB_PRODUCT_NATIONAL_BEARPAW2400 }, 0 }, + + /* Nikon */ + {{ USB_VENDOR_NIKON, USB_PRODUCT_NIKON_LS40 }, 0 }, + + /* Primax */ + {{ USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_G2X300 }, 0 }, + {{ USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_G2E300 }, 0 }, + {{ USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_G2300 }, 0 }, + {{ USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_G2E3002 }, 0 }, + {{ USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_9600 }, 0 }, + {{ USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_600U }, 0 }, + {{ USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_6200 }, 0 }, + {{ USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_19200 }, 0 }, + {{ USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_1200U }, 0 }, + {{ USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_G600 }, 0 }, + {{ USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_636I }, 0 }, + {{ USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_G2600 }, 0 }, + {{ USB_VENDOR_PRIMAX, USB_PRODUCT_PRIMAX_G2E600 }, 0 }, + + /* Epson */ + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_636 }, 0 }, + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_610 }, 0 }, + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_1200 }, 0 }, + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_1240 }, 0 }, + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_1250 }, 0 }, + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_1600 }, 0 }, + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_1640 }, 0 }, + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_640U }, 0 }, + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_1650 }, 0 }, + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_1660 }, 0 }, + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_1670 }, 0 }, + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_1260 }, 0 }, + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_1270 }, 0 }, + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_RX425 }, 0 }, + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_3200 }, USC_KEEP_OPEN }, + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_GT9700F }, USC_KEEP_OPEN }, + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_GT9300UF }, 0 }, + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_2480 }, 0 }, + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_3500 }, USC_KEEP_OPEN }, + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_3590 }, 0 }, + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_4200 }, 0 }, + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_4800 }, 0 }, + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_4990 }, 0 }, + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_5000 }, 0 }, + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_6000 }, 0 }, + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_CX5400 }, 0 }, + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_DX7400 }, 0 }, + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_DX8400 }, 0 }, + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_CX5400 }, 0 }, + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_DX3800 }, 0 }, + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_DX4000 }, 0 }, + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_NX300 }, 0 }, + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_SX200 }, 0 }, + {{ USB_VENDOR_EPSON, USB_PRODUCT_EPSON_SX400 }, 0 }, + + /* UMAX */ + {{ USB_VENDOR_UMAX, USB_PRODUCT_UMAX_ASTRA1220U }, 0 }, + {{ USB_VENDOR_UMAX, USB_PRODUCT_UMAX_ASTRA1236U }, 0 }, + {{ USB_VENDOR_UMAX, USB_PRODUCT_UMAX_ASTRA2000U }, 0 }, + {{ USB_VENDOR_UMAX, USB_PRODUCT_UMAX_ASTRA2100U }, 0 }, + {{ USB_VENDOR_UMAX, USB_PRODUCT_UMAX_ASTRA2200U }, 0 }, + {{ USB_VENDOR_UMAX, USB_PRODUCT_UMAX_ASTRA3400 }, 0 }, + + /* Visioneer */ + {{ USB_VENDOR_VISIONEER, USB_PRODUCT_VISIONEER_3000 }, 0 }, + {{ USB_VENDOR_VISIONEER, USB_PRODUCT_VISIONEER_5300 }, 0 }, + {{ USB_VENDOR_VISIONEER, USB_PRODUCT_VISIONEER_7600 }, 0 }, + {{ USB_VENDOR_VISIONEER, USB_PRODUCT_VISIONEER_6100 }, 0 }, + {{ USB_VENDOR_VISIONEER, USB_PRODUCT_VISIONEER_6200 }, 0 }, + {{ USB_VENDOR_VISIONEER, USB_PRODUCT_VISIONEER_8100 }, 0 }, + {{ USB_VENDOR_VISIONEER, USB_PRODUCT_VISIONEER_8600 }, 0 }, + + /* Ultima */ + {{ USB_VENDOR_ULTIMA, USB_PRODUCT_ULTIMA_1200UBPLUS }, 0 }, + +}; +#define uscanner_lookup(v, p) ((const struct uscan_info *)usb_lookup(uscanner_devs, v, p)) + +#define USCANNER_BUFFERSIZE 1024 + +struct uscanner_softc { + device_t sc_dev; /* base device */ + usbd_device_handle sc_udev; + usbd_interface_handle sc_iface; + struct cdev *dev; + + u_int sc_dev_flags; + + usbd_pipe_handle sc_bulkin_pipe; + int sc_bulkin; + usbd_xfer_handle sc_bulkin_xfer; + void *sc_bulkin_buffer; + int sc_bulkin_bufferlen; + int sc_bulkin_datalen; + + usbd_pipe_handle sc_bulkout_pipe; + int sc_bulkout; + usbd_xfer_handle sc_bulkout_xfer; + void *sc_bulkout_buffer; + int sc_bulkout_bufferlen; + int sc_bulkout_datalen; + + u_char sc_state; +#define USCANNER_OPEN 0x01 /* opened */ + + int sc_refcnt; + u_char sc_dying; +}; + +d_open_t uscanneropen; +d_close_t uscannerclose; +d_read_t uscannerread; +d_write_t uscannerwrite; +d_poll_t uscannerpoll; + + +static struct cdevsw uscanner_cdevsw = { + .d_version = D_VERSION, + .d_flags = D_NEEDGIANT, + .d_open = uscanneropen, + .d_close = uscannerclose, + .d_read = uscannerread, + .d_write = uscannerwrite, + .d_poll = uscannerpoll, + .d_name = "uscanner", +}; + +static int uscanner_do_read(struct uscanner_softc *, struct uio *, int); +static int uscanner_do_write(struct uscanner_softc *, struct uio *, int); +static void uscanner_do_close(struct uscanner_softc *); + +#define USCANNERUNIT(n) (dev2unit(n)) + +static device_probe_t uscanner_match; +static device_attach_t uscanner_attach; +static device_detach_t uscanner_detach; + +static device_method_t uscanner_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, uscanner_match), + DEVMETHOD(device_attach, uscanner_attach), + DEVMETHOD(device_detach, uscanner_detach), + + { 0, 0 } +}; + +static driver_t uscanner_driver = { + "uscanner", + uscanner_methods, + sizeof(struct uscanner_softc) +}; + +static devclass_t uscanner_devclass; + +static int +uscanner_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + usb_interface_descriptor_t *id; + + if (uaa->iface == NULL) + return UMATCH_NONE; /* do not grab the entire device */ + + if (uscanner_lookup(uaa->vendor, uaa->product) == NULL) + return UMATCH_NONE; /* not in the list of known devices */ + id = usbd_get_interface_descriptor(uaa->iface); + if (id == NULL) + return UMATCH_NONE; + + /* + * There isn't a specific UICLASS for scanners, many vendors use + * UICLASS_VENDOR, so detecting the right interface is not so easy. + * But certainly we can exclude PRINTER and MASS - which some + * multifunction devices implement. + */ + if (id->bInterfaceClass == UICLASS_PRINTER || + id->bInterfaceClass == UICLASS_MASS) + return UMATCH_NONE; + + return UMATCH_VENDOR_PRODUCT; /* ok we found it */ +} + +static int +uscanner_attach(device_t self) +{ + struct uscanner_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + usb_interface_descriptor_t *id = 0; + usb_endpoint_descriptor_t *ed, *ed_bulkin = NULL, *ed_bulkout = NULL; + int i; + usbd_status err; + int ifnum; + + sc->sc_dev = self; + sc->sc_dev_flags = uscanner_lookup(uaa->vendor, uaa->product)->flags; + sc->sc_udev = uaa->device; + + id = usbd_get_interface_descriptor(uaa->iface); + ifnum = id->bInterfaceNumber; +#if 0 + /* + * This was in the original driver, but we cannot change the + * configuration of the whole device while attaching only to + * one of its interfaces. This can kill other already-attached + * driver, and/or possibly prevent this driver from attaching + * if an error occurs in set_config_no. + * If a device need setting the configuration, this must be done + * before attaching drivers to the various interfaces. + */ + err = usbd_set_config_no(uaa->device, 1, 1); /* XXX */ + if (err) { + printf("%s: setting config no failed\n", + device_get_nameunit(sc->sc_dev)); + return ENXIO; + } +#endif + err = usbd_device2interface_handle(sc->sc_udev, ifnum, &sc->sc_iface); + if (!err && sc->sc_iface) + id = usbd_get_interface_descriptor(sc->sc_iface); + if (err || id == 0) { + printf("%s: could not get interface descriptor, err=%d,id=%p\n", + device_get_nameunit(sc->sc_dev), err, id); + return ENXIO; + } + + /* Find the two first bulk endpoints */ + for (i = 0 ; i < id->bNumEndpoints; i++) { + ed = usbd_interface2endpoint_descriptor(sc->sc_iface, i); + if (ed == 0) { + printf("%s: could not read endpoint descriptor\n", + device_get_nameunit(sc->sc_dev)); + return ENXIO; + } + + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN + && (ed->bmAttributes & UE_XFERTYPE) == UE_BULK) { + ed_bulkin = ed; + } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT + && (ed->bmAttributes & UE_XFERTYPE) == UE_BULK) { + ed_bulkout = ed; + } + + if (ed_bulkin && ed_bulkout) /* found all we need */ + break; + } + + /* Verify that we goething sensible */ + if (ed_bulkin == NULL || ed_bulkout == NULL) { + printf("%s: bulk-in and/or bulk-out endpoint not found\n", + device_get_nameunit(sc->sc_dev)); + return ENXIO; + } + + sc->sc_bulkin = ed_bulkin->bEndpointAddress; + sc->sc_bulkout = ed_bulkout->bEndpointAddress; + + /* the main device, ctrl endpoint */ + sc->dev = make_dev(&uscanner_cdevsw, device_get_unit(sc->sc_dev), + UID_ROOT, GID_OPERATOR, 0644, "%s", device_get_nameunit(sc->sc_dev)); + usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, sc->sc_udev,sc->sc_dev); + + return 0; +} + +int +uscanneropen(struct cdev *dev, int flag, int mode, struct thread *p) +{ + struct uscanner_softc *sc; + int unit = USCANNERUNIT(dev); + usbd_status err; + + sc = devclass_get_softc(uscanner_devclass, unit); + if (sc == NULL) + return (ENXIO); + + DPRINTFN(5, ("uscanneropen: flag=%d, mode=%d, unit=%d\n", + flag, mode, unit)); + + if (sc->sc_dying) + return (ENXIO); + + if (sc->sc_state & USCANNER_OPEN) + return (EBUSY); + + sc->sc_state |= USCANNER_OPEN; + + sc->sc_bulkin_buffer = malloc(USCANNER_BUFFERSIZE, M_USBDEV, M_WAITOK); + sc->sc_bulkout_buffer = malloc(USCANNER_BUFFERSIZE, M_USBDEV, M_WAITOK); + /* No need to check buffers for NULL since we have WAITOK */ + + sc->sc_bulkin_bufferlen = USCANNER_BUFFERSIZE; + sc->sc_bulkout_bufferlen = USCANNER_BUFFERSIZE; + + /* We have decided on which endpoints to use, now open the pipes */ + if (sc->sc_bulkin_pipe == NULL) { + err = usbd_open_pipe(sc->sc_iface, sc->sc_bulkin, + USBD_EXCLUSIVE_USE, &sc->sc_bulkin_pipe); + if (err) { + printf("%s: cannot open bulk-in pipe (addr %d)\n", + device_get_nameunit(sc->sc_dev), sc->sc_bulkin); + uscanner_do_close(sc); + return (EIO); + } + } + if (sc->sc_bulkout_pipe == NULL) { + err = usbd_open_pipe(sc->sc_iface, sc->sc_bulkout, + USBD_EXCLUSIVE_USE, &sc->sc_bulkout_pipe); + if (err) { + printf("%s: cannot open bulk-out pipe (addr %d)\n", + device_get_nameunit(sc->sc_dev), sc->sc_bulkout); + uscanner_do_close(sc); + return (EIO); + } + } + + sc->sc_bulkin_xfer = usbd_alloc_xfer(sc->sc_udev); + if (sc->sc_bulkin_xfer == NULL) { + uscanner_do_close(sc); + return (ENOMEM); + } + sc->sc_bulkout_xfer = usbd_alloc_xfer(sc->sc_udev); + if (sc->sc_bulkout_xfer == NULL) { + uscanner_do_close(sc); + return (ENOMEM); + } + + return (0); /* success */ +} + +int +uscannerclose(struct cdev *dev, int flag, int mode, struct thread *p) +{ + struct uscanner_softc *sc; + + sc = devclass_get_softc(uscanner_devclass, USCANNERUNIT(dev)); + DPRINTFN(5, ("uscannerclose: flag=%d, mode=%d, unit=%d\n", + flag, mode, USCANNERUNIT(dev))); + +#ifdef DIAGNOSTIC + if (!(sc->sc_state & USCANNER_OPEN)) { + printf("uscannerclose: not open\n"); + return (EINVAL); + } +#endif + + uscanner_do_close(sc); + + return (0); +} + +void +uscanner_do_close(struct uscanner_softc *sc) +{ + if (sc->sc_bulkin_xfer) { + usbd_free_xfer(sc->sc_bulkin_xfer); + sc->sc_bulkin_xfer = NULL; + } + if (sc->sc_bulkout_xfer) { + usbd_free_xfer(sc->sc_bulkout_xfer); + sc->sc_bulkout_xfer = NULL; + } + + if (!(sc->sc_dev_flags & USC_KEEP_OPEN)) { + if (sc->sc_bulkin_pipe != NULL) { + usbd_abort_pipe(sc->sc_bulkin_pipe); + usbd_close_pipe(sc->sc_bulkin_pipe); + sc->sc_bulkin_pipe = NULL; + } + if (sc->sc_bulkout_pipe != NULL) { + usbd_abort_pipe(sc->sc_bulkout_pipe); + usbd_close_pipe(sc->sc_bulkout_pipe); + sc->sc_bulkout_pipe = NULL; + } + } + + if (sc->sc_bulkin_buffer) { + free(sc->sc_bulkin_buffer, M_USBDEV); + sc->sc_bulkin_buffer = NULL; + } + if (sc->sc_bulkout_buffer) { + free(sc->sc_bulkout_buffer, M_USBDEV); + sc->sc_bulkout_buffer = NULL; + } + + sc->sc_state &= ~USCANNER_OPEN; +} + +static int +uscanner_do_read(struct uscanner_softc *sc, struct uio *uio, int flag) +{ + u_int32_t n, tn; + usbd_status err; + int error = 0; + + DPRINTFN(5, ("%s: uscannerread\n", device_get_nameunit(sc->sc_dev))); + + if (sc->sc_dying) + return (EIO); + + while ((n = min(sc->sc_bulkin_bufferlen, uio->uio_resid)) != 0) { + DPRINTFN(1, ("uscannerread: start transfer %d bytes\n",n)); + tn = n; + + err = usbd_bulk_transfer( + sc->sc_bulkin_xfer, sc->sc_bulkin_pipe, + USBD_SHORT_XFER_OK, USBD_NO_TIMEOUT, + sc->sc_bulkin_buffer, &tn, + "uscnrb"); + if (err) { + if (err == USBD_INTERRUPTED) + error = EINTR; + else if (err == USBD_TIMEOUT) + error = ETIMEDOUT; + else + error = EIO; + break; + } + DPRINTFN(1, ("uscannerread: got %d bytes\n", tn)); + error = uiomove(sc->sc_bulkin_buffer, tn, uio); + if (error || tn < n) + break; + } + + return (error); +} + +int +uscannerread(struct cdev *dev, struct uio *uio, int flag) +{ + struct uscanner_softc *sc; + int error; + + sc = devclass_get_softc(uscanner_devclass, USCANNERUNIT(dev)); + sc->sc_refcnt++; + error = uscanner_do_read(sc, uio, flag); + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); + + return (error); +} + +static int +uscanner_do_write(struct uscanner_softc *sc, struct uio *uio, int flag) +{ + u_int32_t n; + int error = 0; + usbd_status err; + + DPRINTFN(5, ("%s: uscanner_do_write\n", device_get_nameunit(sc->sc_dev))); + + if (sc->sc_dying) + return (EIO); + + while ((n = min(sc->sc_bulkout_bufferlen, uio->uio_resid)) != 0) { + error = uiomove(sc->sc_bulkout_buffer, n, uio); + if (error) + break; + DPRINTFN(1, ("uscanner_do_write: transfer %d bytes\n", n)); + err = usbd_bulk_transfer( + sc->sc_bulkout_xfer, sc->sc_bulkout_pipe, + 0, USBD_NO_TIMEOUT, + sc->sc_bulkout_buffer, &n, + "uscnwb"); + if (err) { + if (err == USBD_INTERRUPTED) + error = EINTR; + else + error = EIO; + break; + } + } + + return (error); +} + +int +uscannerwrite(struct cdev *dev, struct uio *uio, int flag) +{ + struct uscanner_softc *sc; + int error; + + sc = devclass_get_softc(uscanner_devclass, USCANNERUNIT(dev)); + sc->sc_refcnt++; + error = uscanner_do_write(sc, uio, flag); + if (--sc->sc_refcnt < 0) + usb_detach_wakeup(sc->sc_dev); + return (error); +} + +static int +uscanner_detach(device_t self) +{ + struct uscanner_softc *sc = device_get_softc(self); + int s; + + DPRINTF(("uscanner_detach: sc=%p\n", sc)); + + sc->sc_dying = 1; + sc->sc_dev_flags = 0; /* make close really close device */ + + /* Abort all pipes. Causes processes waiting for transfer to wake. */ + if (sc->sc_bulkin_pipe != NULL) + usbd_abort_pipe(sc->sc_bulkin_pipe); + if (sc->sc_bulkout_pipe != NULL) + usbd_abort_pipe(sc->sc_bulkout_pipe); + + s = splusb(); + if (--sc->sc_refcnt >= 0) { + /* Wait for processes to go away. */ + usb_detach_wait(sc->sc_dev); + } + splx(s); + + /* destroy the device for the control endpoint */ + destroy_dev(sc->dev); + usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_udev, sc->sc_dev); + + return (0); +} + +int +uscannerpoll(struct cdev *dev, int events, struct thread *p) +{ + struct uscanner_softc *sc; + int revents = 0; + + sc = devclass_get_softc(uscanner_devclass, USCANNERUNIT(dev)); + if (sc->sc_dying) + return (EIO); + + /* + * We have no easy way of determining if a read will + * yield any data or a write will happen. + * Pretend they will. + */ + revents |= events & + (POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM); + + return (revents); +} + +MODULE_DEPEND(uscanner, usb, 1, 1, 1); +DRIVER_MODULE(uscanner, uhub, uscanner_driver, uscanner_devclass, usbd_driver_load, 0); diff --git a/sys/legacy/dev/usb/uslcom.c b/sys/legacy/dev/usb/uslcom.c new file mode 100644 index 0000000..edb13c5 --- /dev/null +++ b/sys/legacy/dev/usb/uslcom.c @@ -0,0 +1,419 @@ +/* $FreeBSD$ */ +/* $OpenBSD: uslcom.c,v 1.17 2007/11/24 10:52:12 jsg Exp $ */ + +/* + * Copyright (c) 2006 Jonathan Gray <jsg@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/conf.h> +#include <sys/bus.h> +#include <sys/tty.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbcdc.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> + +#include "usbdevs.h" +#include <dev/usb/ucomvar.h> + +#ifdef USLCOM_DEBUG +#define DPRINTFN(n, x) do { if (uslcomdebug > (n)) printf x; } while (0) +int uslcomdebug = 1; +#else +#define DPRINTFN(n, x) +#endif +#define DPRINTF(x) DPRINTFN(0, x) + +#define USLCOMBUFSZ 256 +#define USLCOM_CONFIG_NO 0 +#define USLCOM_IFACE_NO 0 + +#define USLCOM_SET_DATA_BITS(x) (x << 8) + +#define USLCOM_WRITE 0x41 +#define USLCOM_READ 0xc1 + +#define USLCOM_UART 0x00 +#define USLCOM_BAUD_RATE 0x01 +#define USLCOM_DATA 0x03 +#define USLCOM_BREAK 0x05 +#define USLCOM_CTRL 0x07 + +#define USLCOM_UART_DISABLE 0x00 +#define USLCOM_UART_ENABLE 0x01 + +#define USLCOM_CTRL_DTR_ON 0x0001 +#define USLCOM_CTRL_DTR_SET 0x0100 +#define USLCOM_CTRL_RTS_ON 0x0002 +#define USLCOM_CTRL_RTS_SET 0x0200 +#define USLCOM_CTRL_CTS 0x0010 +#define USLCOM_CTRL_DSR 0x0020 +#define USLCOM_CTRL_DCD 0x0080 + + +#define USLCOM_BAUD_REF 0x384000 + +#define USLCOM_STOP_BITS_1 0x00 +#define USLCOM_STOP_BITS_2 0x02 + +#define USLCOM_PARITY_NONE 0x00 +#define USLCOM_PARITY_ODD 0x10 +#define USLCOM_PARITY_EVEN 0x20 + +#define USLCOM_BREAK_OFF 0x00 +#define USLCOM_BREAK_ON 0x01 + + +struct uslcom_softc { + struct ucom_softc sc_ucom; + device_t sc_dev; + usbd_device_handle sc_udev; + + u_char sc_msr; + u_char sc_lsr; + + u_char sc_dying; +}; + +void uslcom_get_status(void *, int portno, u_char *lsr, u_char *msr); +void uslcom_set(void *, int, int, int); +int uslcom_param(void *, int, struct termios *); +int uslcom_open(void *sc, int portno); +void uslcom_close(void *, int); +void uslcom_break(void *sc, int portno, int onoff); + +struct ucom_callback uslcom_callback = { + uslcom_get_status, + uslcom_set, + uslcom_param, + NULL, + uslcom_open, + uslcom_close, + NULL, + NULL, +}; + +static const struct usb_devno uslcom_devs[] = { + { USB_VENDOR_BALTECH, USB_PRODUCT_BALTECH_CARDREADER }, + { USB_VENDOR_DYNASTREAM, USB_PRODUCT_DYNASTREAM_ANTDEVBOARD }, + { USB_VENDOR_JABLOTRON, USB_PRODUCT_JABLOTRON_PC60B }, + { USB_VENDOR_SILABS, USB_PRODUCT_SILABS_ARGUSISP }, + { USB_VENDOR_SILABS, USB_PRODUCT_SILABS_CRUMB128 }, + { USB_VENDOR_SILABS, USB_PRODUCT_SILABS_DEGREE }, + { USB_VENDOR_SILABS, USB_PRODUCT_SILABS_BURNSIDE }, + { USB_VENDOR_SILABS, USB_PRODUCT_SILABS_HELICOM }, + { USB_VENDOR_SILABS, USB_PRODUCT_SILABS_LIPOWSKY_HARP }, + { USB_VENDOR_SILABS, USB_PRODUCT_SILABS_LIPOWSKY_JTAG }, + { USB_VENDOR_SILABS, USB_PRODUCT_SILABS_LIPOWSKY_LIN }, + { USB_VENDOR_SILABS, USB_PRODUCT_SILABS_POLOLU }, + { USB_VENDOR_SILABS, USB_PRODUCT_SILABS_CP2102 }, + { USB_VENDOR_SILABS, USB_PRODUCT_SILABS_CP210X_2 }, + { USB_VENDOR_SILABS, USB_PRODUCT_SILABS_SUUNTO }, + { USB_VENDOR_SILABS, USB_PRODUCT_SILABS_TRAQMATE }, + { USB_VENDOR_SILABS2, USB_PRODUCT_SILABS2_DCU11CLONE }, + { USB_VENDOR_USI, USB_PRODUCT_USI_MC60 } +}; + +static device_probe_t uslcom_match; +static device_attach_t uslcom_attach; +static device_detach_t uslcom_detach; + +static device_method_t uslcom_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, uslcom_match), + DEVMETHOD(device_attach, uslcom_attach), + DEVMETHOD(device_detach, uslcom_detach), + { 0, 0 } +}; + +static driver_t uslcom_driver = { + "ucom", + uslcom_methods, + sizeof (struct uslcom_softc) +}; + +DRIVER_MODULE(uslcom, uhub, uslcom_driver, ucom_devclass, usbd_driver_load, 0); +MODULE_DEPEND(uslcom, usb, 1, 1, 1); +MODULE_DEPEND(uslcom, ucom, UCOM_MINVER, UCOM_PREFVER, UCOM_MAXVER); +MODULE_VERSION(uslcom, 1); + +static int +uslcom_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + + if (uaa->iface != NULL) + return UMATCH_NONE; + + return (usb_lookup(uslcom_devs, uaa->vendor, uaa->product) != NULL) ? + UMATCH_VENDOR_PRODUCT : UMATCH_NONE; +} + +static int +uslcom_attach(device_t self) +{ + struct uslcom_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + usbd_device_handle dev = uaa->device; + struct ucom_softc* ucom; + usb_interface_descriptor_t *id; + usb_endpoint_descriptor_t *ed; + usbd_status error; + int i; + + ucom = &sc->sc_ucom; + ucom->sc_dev = self; + ucom->sc_udev = dev; + ucom->sc_iface = uaa->iface; + + sc->sc_dev = self; + sc->sc_udev = uaa->device; + + if (usbd_set_config_index(sc->sc_udev, USLCOM_CONFIG_NO, 1) != 0) { + device_printf(self, "could not set configuration no\n"); + sc->sc_dying = 1; + return ENXIO; + } + + /* get the first interface handle */ + error = usbd_device2interface_handle(sc->sc_udev, USLCOM_IFACE_NO, + &ucom->sc_iface); + if (error != 0) { + device_printf(self, "could not get interface handle\n"); + sc->sc_dying = 1; + return ENXIO; + } + + id = usbd_get_interface_descriptor(ucom->sc_iface); + + ucom->sc_bulkin_no = ucom->sc_bulkout_no = -1; + for (i = 0; i < id->bNumEndpoints; i++) { + ed = usbd_interface2endpoint_descriptor(ucom->sc_iface, i); + if (ed == NULL) { + device_printf(self, "no endpoint descriptor found for %d\n", + i); + sc->sc_dying = 1; + return ENXIO; + } + + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) + ucom->sc_bulkin_no = ed->bEndpointAddress; + else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) + ucom->sc_bulkout_no = ed->bEndpointAddress; + } + + if (ucom->sc_bulkin_no == -1 || ucom->sc_bulkout_no == -1) { + device_printf(self, "missing endpoint\n"); + sc->sc_dying = 1; + return ENXIO; + } + + ucom->sc_parent = sc; + ucom->sc_portno = UCOM_UNK_PORTNO; + /* bulkin, bulkout set above */ + ucom->sc_ibufsize = USLCOMBUFSZ; + ucom->sc_obufsize = USLCOMBUFSZ; + ucom->sc_ibufsizepad = USLCOMBUFSZ; + ucom->sc_opkthdrlen = 0; + ucom->sc_callback = &uslcom_callback; + + DPRINTF(("uslcom: in = 0x%x, out = 0x%x\n", + ucom->sc_bulkin_no, ucom->sc_bulkout_no)); + + ucom_attach(&sc->sc_ucom); + return 0; +} + +static int +uslcom_detach(device_t self) +{ + struct uslcom_softc *sc = device_get_softc(self); + + sc->sc_dying = 1; + return ucom_detach(&sc->sc_ucom); +} + +int +uslcom_open(void *vsc, int portno) +{ + struct uslcom_softc *sc = vsc; + usb_device_request_t req; + usbd_status err; + + if (sc->sc_dying) + return (EIO); + + req.bmRequestType = USLCOM_WRITE; + req.bRequest = USLCOM_UART; + USETW(req.wValue, USLCOM_UART_ENABLE); + USETW(req.wIndex, portno); + USETW(req.wLength, 0); + err = usbd_do_request(sc->sc_udev, &req, NULL); + if (err) + return (EIO); + + return (0); +} + +void +uslcom_close(void *vsc, int portno) +{ + struct uslcom_softc *sc = vsc; + usb_device_request_t req; + + if (sc->sc_dying) + return; + + req.bmRequestType = USLCOM_WRITE; + req.bRequest = USLCOM_UART; + USETW(req.wValue, USLCOM_UART_DISABLE); + USETW(req.wIndex, portno); + USETW(req.wLength, 0); + usbd_do_request(sc->sc_udev, &req, NULL); +} + +void +uslcom_set(void *vsc, int portno, int reg, int onoff) +{ + struct uslcom_softc *sc = vsc; + usb_device_request_t req; + int ctl; + + switch (reg) { + case UCOM_SET_DTR: + ctl = onoff ? USLCOM_CTRL_DTR_ON : 0; + ctl |= USLCOM_CTRL_DTR_SET; + break; + case UCOM_SET_RTS: + ctl = onoff ? USLCOM_CTRL_RTS_ON : 0; + ctl |= USLCOM_CTRL_RTS_SET; + break; + case UCOM_SET_BREAK: + uslcom_break(sc, portno, onoff); + return; + default: + return; + } + req.bmRequestType = USLCOM_WRITE; + req.bRequest = USLCOM_CTRL; + USETW(req.wValue, ctl); + USETW(req.wIndex, portno); + USETW(req.wLength, 0); + usbd_do_request(sc->sc_udev, &req, NULL); +} + +int +uslcom_param(void *vsc, int portno, struct termios *t) +{ + struct uslcom_softc *sc = (struct uslcom_softc *)vsc; + usbd_status err; + usb_device_request_t req; + int data; + + if (t->c_ospeed <= 0 || t->c_ospeed > 921600) + return (EINVAL); + + req.bmRequestType = USLCOM_WRITE; + req.bRequest = USLCOM_BAUD_RATE; + USETW(req.wValue, USLCOM_BAUD_REF / t->c_ospeed); + USETW(req.wIndex, portno); + USETW(req.wLength, 0); + err = usbd_do_request(sc->sc_udev, &req, NULL); + if (err) + return (EIO); + + if (ISSET(t->c_cflag, CSTOPB)) + data = USLCOM_STOP_BITS_2; + else + data = USLCOM_STOP_BITS_1; + if (ISSET(t->c_cflag, PARENB)) { + if (ISSET(t->c_cflag, PARODD)) + data |= USLCOM_PARITY_ODD; + else + data |= USLCOM_PARITY_EVEN; + } else + data |= USLCOM_PARITY_NONE; + switch (ISSET(t->c_cflag, CSIZE)) { + case CS5: + data |= USLCOM_SET_DATA_BITS(5); + break; + case CS6: + data |= USLCOM_SET_DATA_BITS(6); + break; + case CS7: + data |= USLCOM_SET_DATA_BITS(7); + break; + case CS8: + data |= USLCOM_SET_DATA_BITS(8); + break; + } + + req.bmRequestType = USLCOM_WRITE; + req.bRequest = USLCOM_DATA; + USETW(req.wValue, data); + USETW(req.wIndex, portno); + USETW(req.wLength, 0); + err = usbd_do_request(sc->sc_udev, &req, NULL); + if (err) + return (EIO); + +#if 0 + /* XXX flow control */ + if (ISSET(t->c_cflag, CRTSCTS)) + /* rts/cts flow ctl */ + } else if (ISSET(t->c_iflag, IXON|IXOFF)) { + /* xon/xoff flow ctl */ + } else { + /* disable flow ctl */ + } +#endif + + return (0); +} + +void +uslcom_get_status(void *vsc, int portno, u_char *lsr, u_char *msr) +{ + struct uslcom_softc *sc = vsc; + + if (msr != NULL) + *msr = sc->sc_msr; + if (lsr != NULL) + *lsr = sc->sc_lsr; +} + +void +uslcom_break(void *vsc, int portno, int onoff) +{ + struct uslcom_softc *sc = vsc; + usb_device_request_t req; + int brk = onoff ? USLCOM_BREAK_ON : USLCOM_BREAK_OFF; + + req.bmRequestType = USLCOM_WRITE; + req.bRequest = USLCOM_BREAK; + USETW(req.wValue, brk); + USETW(req.wIndex, portno); + USETW(req.wLength, 0); + usbd_do_request(sc->sc_udev, &req, NULL); +} diff --git a/sys/legacy/dev/usb/uvisor.c b/sys/legacy/dev/usb/uvisor.c new file mode 100644 index 0000000..6c4470e --- /dev/null +++ b/sys/legacy/dev/usb/uvisor.c @@ -0,0 +1,639 @@ +/* $NetBSD: uvisor.c,v 1.9 2001/01/23 14:04:14 augustss Exp $ */ +/* $FreeBSD$ */ + +/* Also already merged from NetBSD: + * $NetBSD: uvisor.c,v 1.12 2001/11/13 06:24:57 lukem Exp $ + * $NetBSD: uvisor.c,v 1.13 2002/02/11 15:11:49 augustss Exp $ + * $NetBSD: uvisor.c,v 1.14 2002/02/27 23:00:03 augustss Exp $ + * $NetBSD: uvisor.c,v 1.15 2002/06/16 15:01:31 augustss Exp $ + * $NetBSD: uvisor.c,v 1.16 2002/07/11 21:14:36 augustss Exp $ + * $NetBSD: uvisor.c,v 1.17 2002/08/13 11:38:15 augustss Exp $ + * $NetBSD: uvisor.c,v 1.18 2003/02/05 00:50:14 augustss Exp $ + * $NetBSD: uvisor.c,v 1.19 2003/02/07 18:12:37 augustss Exp $ + * $NetBSD: uvisor.c,v 1.20 2003/04/11 01:30:10 simonb Exp $ + */ + + +/*- + * Copyright (c) 2000 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. + */ + +/* + * Handspring Visor (Palmpilot compatible PDA) driver + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/conf.h> +#include <sys/tty.h> +#include <sys/sysctl.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbhid.h> + +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include "usbdevs.h" + +#include <dev/usb/ucomvar.h> + +#ifdef USB_DEBUG +#define DPRINTF(x) if (uvisordebug) printf x +#define DPRINTFN(n,x) if (uvisordebug>(n)) printf x +int uvisordebug = 0; +SYSCTL_NODE(_hw_usb, OID_AUTO, uvisor, CTLFLAG_RW, 0, "USB uvisor"); +SYSCTL_INT(_hw_usb_uvisor, OID_AUTO, debug, CTLFLAG_RW, + &uvisordebug, 0, "uvisor debug level"); +#else +#define DPRINTF(x) +#define DPRINTFN(n,x) +#endif + +#define UVISOR_CONFIG_INDEX 0 +#define UVISOR_IFACE_INDEX 0 +#define UVISOR_MODVER 1 + +/* From the Linux driver */ +/* + * UVISOR_REQUEST_BYTES_AVAILABLE asks the visor for the number of bytes that + * are available to be transfered to the host for the specified endpoint. + * Currently this is not used, and always returns 0x0001 + */ +#define UVISOR_REQUEST_BYTES_AVAILABLE 0x01 + +/* + * UVISOR_CLOSE_NOTIFICATION is set to the device to notify it that the host + * is now closing the pipe. An empty packet is sent in response. + */ +#define UVISOR_CLOSE_NOTIFICATION 0x02 + +/* + * UVISOR_GET_CONNECTION_INFORMATION is sent by the host during enumeration to + * get the endpoints used by the connection. + */ +#define UVISOR_GET_CONNECTION_INFORMATION 0x03 + + +/* + * UVISOR_GET_CONNECTION_INFORMATION returns data in the following format + */ +#define UVISOR_MAX_CONN 8 +struct uvisor_connection_info { + uWord num_ports; + struct { + uByte port_function_id; + uByte port; + } connections[UVISOR_MAX_CONN]; +}; +#define UVISOR_CONNECTION_INFO_SIZE 18 + +/* struct uvisor_connection_info.connection[x].port defines: */ +#define UVISOR_ENDPOINT_1 0x01 +#define UVISOR_ENDPOINT_2 0x02 + +/* struct uvisor_connection_info.connection[x].port_function_id defines: */ +#define UVISOR_FUNCTION_GENERIC 0x00 +#define UVISOR_FUNCTION_DEBUGGER 0x01 +#define UVISOR_FUNCTION_HOTSYNC 0x02 +#define UVISOR_FUNCTION_CONSOLE 0x03 +#define UVISOR_FUNCTION_REMOTE_FILE_SYS 0x04 + +/* + * Unknown PalmOS stuff. + */ +#define UVISOR_GET_PALM_INFORMATION 0x04 +#define UVISOR_GET_PALM_INFORMATION_LEN 0x44 + +struct uvisor_palm_connection_info { + uByte num_ports; + uByte endpoint_numbers_different; + uWord reserved1; + struct { + uDWord port_function_id; + uByte port; + uByte end_point_info; + uWord reserved; + } connections[UVISOR_MAX_CONN]; +}; + + +/* + * Crank down UVISORBUFSIZE from 1024 to 64 to avoid a problem where + * the Palm device and the USB host controller deadlock. The USB host + * controller is expecting an early-end-of-transmission packet with 0 + * data, and the Palm doesn't send one because it's already + * communicated the amount of data it's going to send in a header + * (which ucom/uvisor are oblivious to). This is the problem that has + * been known on the pilot-link lists as the "[Free]BSD USB problem", + * but not understood. + */ +#define UVISORIBUFSIZE 64 +#define UVISOROBUFSIZE 1024 + +struct uvisor_softc { + struct ucom_softc sc_ucom; + u_int16_t sc_flags; +}; + +static usbd_status uvisor_init(struct uvisor_softc *); + +/*static usbd_status clie_3_5_init(struct uvisor_softc *);*/ + +static void uvisor_close(void *, int); + +struct ucom_callback uvisor_callback = { + NULL, + NULL, + NULL, + NULL, + NULL, + uvisor_close, + NULL, + NULL, +}; + +static device_probe_t uvisor_match; +static device_attach_t uvisor_attach; +static device_detach_t uvisor_detach; +static device_method_t uvisor_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, uvisor_match), + DEVMETHOD(device_attach, uvisor_attach), + DEVMETHOD(device_detach, uvisor_detach), + { 0, 0 } +}; + + +static driver_t uvisor_driver = { + "ucom", + uvisor_methods, + sizeof (struct uvisor_softc) +}; + +DRIVER_MODULE(uvisor, uhub, uvisor_driver, ucom_devclass, usbd_driver_load, 0); +MODULE_DEPEND(uvisor, usb, 1, 1, 1); +MODULE_DEPEND(uvisor, ucom, UCOM_MINVER, UCOM_PREFVER, UCOM_MAXVER); +MODULE_VERSION(uvisor, UVISOR_MODVER); + +struct uvisor_type { + struct usb_devno uv_dev; + u_int16_t uv_flags; +#define PALM4 0x0001 +#define VISOR 0x0002 +#define PALM35 0x0004 +}; +static const struct uvisor_type uvisor_devs[] = { + {{ USB_VENDOR_ACEECA, USB_PRODUCT_ACEECA_MEZ1000 }, PALM4 }, + {{ USB_VENDOR_GARMIN, USB_PRODUCT_GARMIN_IQUE_3600 }, PALM4 }, + {{ USB_VENDOR_FOSSIL, USB_PRODUCT_FOSSIL_WRISTPDA }, PALM4 }, + {{ USB_VENDOR_HANDSPRING, USB_PRODUCT_HANDSPRING_VISOR }, VISOR }, + {{ USB_VENDOR_HANDSPRING, USB_PRODUCT_HANDSPRING_TREO }, PALM4 }, + {{ USB_VENDOR_HANDSPRING, USB_PRODUCT_HANDSPRING_TREO600 }, PALM4 }, + {{ USB_VENDOR_PALM, USB_PRODUCT_PALM_M500 }, PALM4 }, + {{ USB_VENDOR_PALM, USB_PRODUCT_PALM_M505 }, PALM4 }, + {{ USB_VENDOR_PALM, USB_PRODUCT_PALM_M515 }, PALM4 }, + {{ USB_VENDOR_PALM, USB_PRODUCT_PALM_I705 }, PALM4 }, + {{ USB_VENDOR_PALM, USB_PRODUCT_PALM_M125 }, PALM4 }, + {{ USB_VENDOR_PALM, USB_PRODUCT_PALM_M130 }, PALM4 }, + {{ USB_VENDOR_PALM, USB_PRODUCT_PALM_TUNGSTEN_Z }, PALM4 }, + {{ USB_VENDOR_PALM, USB_PRODUCT_PALM_TUNGSTEN_T }, PALM4 }, + {{ USB_VENDOR_PALM, USB_PRODUCT_PALM_ZIRE }, PALM4 }, + {{ USB_VENDOR_PALM, USB_PRODUCT_PALM_ZIRE31 }, PALM4 }, + {{ USB_VENDOR_SAMSUNG, USB_PRODUCT_SAMSUNG_I500 }, PALM4 }, + {{ USB_VENDOR_SONY, USB_PRODUCT_SONY_CLIE_40 }, 0 }, + {{ USB_VENDOR_SONY, USB_PRODUCT_SONY_CLIE_41 }, PALM4 }, + {{ USB_VENDOR_SONY, USB_PRODUCT_SONY_CLIE_S360 }, PALM4 }, + {{ USB_VENDOR_SONY, USB_PRODUCT_SONY_CLIE_NX60 }, PALM4 }, + {{ USB_VENDOR_SONY, USB_PRODUCT_SONY_CLIE_35 }, PALM35 }, +/* {{ USB_VENDOR_SONY, USB_PRODUCT_SONY_CLIE_25 }, PALM4 },*/ +/* {{ USB_VENDOR_SONY, USB_PRODUCT_SONY_CLIE_TH55 }, PALM4 }, */ /* See PR 80935 */ + {{ USB_VENDOR_SONY, USB_PRODUCT_SONY_CLIE_TJ37 }, PALM4 }, + {{ USB_VENDOR_TAPWAVE, USB_PRODUCT_TAPWAVE_ZODIAC }, PALM4 }, +}; +#define uvisor_lookup(v, p) ((const struct uvisor_type *)usb_lookup(uvisor_devs, v, p)) + + +static int +uvisor_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + + if (uaa->iface != NULL) + return (UMATCH_NONE); + + DPRINTFN(20,("uvisor: vendor=0x%x, product=0x%x\n", + uaa->vendor, uaa->product)); + + return (uvisor_lookup(uaa->vendor, uaa->product) != NULL ? + UMATCH_VENDOR_PRODUCT : UMATCH_NONE); +} + +static int +uvisor_attach(device_t self) +{ + struct uvisor_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + usbd_device_handle dev = uaa->device; + usbd_interface_handle iface; + usb_interface_descriptor_t *id; + usb_endpoint_descriptor_t *ed; + int i; + usbd_status err; + struct ucom_softc *ucom; + + ucom = &sc->sc_ucom; + ucom->sc_dev = self; + ucom->sc_udev = dev; + ucom->sc_iface = uaa->iface; + + DPRINTFN(10,("\nuvisor_attach: sc=%p\n", sc)); + + /* Move the device into the configured state. */ + err = usbd_set_config_index(dev, UVISOR_CONFIG_INDEX, 1); + if (err) { + device_printf(self, "failed to set configuration, err=%s\n", + usbd_errstr(err)); + goto bad; + } + + err = usbd_device2interface_handle(dev, UVISOR_IFACE_INDEX, &iface); + if (err) { + device_printf(self, "failed to get interface, err=%s\n", + usbd_errstr(err)); + goto bad; + } + + sc->sc_flags = uvisor_lookup(uaa->vendor, uaa->product)->uv_flags; + + id = usbd_get_interface_descriptor(iface); + + ucom->sc_udev = dev; + ucom->sc_iface = iface; + + ucom->sc_bulkin_no = ucom->sc_bulkout_no = -1; + for (i = 0; i < id->bNumEndpoints; i++) { + int addr, dir, attr; + ed = usbd_interface2endpoint_descriptor(iface, i); + if (ed == NULL) { + device_printf(self, + "could not read endpoint descriptor: %s\n", + usbd_errstr(err)); + goto bad; + } + + addr = ed->bEndpointAddress; + dir = UE_GET_DIR(ed->bEndpointAddress); + attr = ed->bmAttributes & UE_XFERTYPE; + if (dir == UE_DIR_IN && attr == UE_BULK) + ucom->sc_bulkin_no = addr; + else if (dir == UE_DIR_OUT && attr == UE_BULK) + ucom->sc_bulkout_no = addr; + else { + device_printf(self, "unexpected endpoint\n"); + goto bad; + } + } + if (ucom->sc_bulkin_no == -1) { + device_printf(self, "Could not find data bulk in\n"); + goto bad; + } + if (ucom->sc_bulkout_no == -1) { + device_printf(self, "Could not find data bulk out\n"); + goto bad; + } + + ucom->sc_parent = sc; + ucom->sc_portno = UCOM_UNK_PORTNO; + /* bulkin, bulkout set above */ + ucom->sc_ibufsize = UVISORIBUFSIZE; + ucom->sc_obufsize = UVISOROBUFSIZE; + ucom->sc_ibufsizepad = UVISORIBUFSIZE; + ucom->sc_opkthdrlen = 0; + ucom->sc_callback = &uvisor_callback; + +#if 0 + if (uaa->vendor == USB_VENDOR_SONY && + uaa->product == USB_PRODUCT_SONY_CLIE_35) + err = clie_3_5_init(sc); + else +#endif + err = uvisor_init(sc); + + if (err) { + device_printf(ucom->sc_dev, "init failed, %s\n", + usbd_errstr(err)); + goto bad; + } + + usbd_add_drv_event(USB_EVENT_DRIVER_ATTACH, ucom->sc_udev, + ucom->sc_dev); + + DPRINTF(("uvisor: in=0x%x out=0x%x\n", ucom->sc_bulkin_no, ucom->sc_bulkout_no)); + ucom_attach(&sc->sc_ucom); + + return 0; + +bad: + DPRINTF(("uvisor_attach: ATTACH ERROR\n")); + ucom->sc_dying = 1; + return ENXIO; +} + +#if 0 + +int +uvisor_activate(device_t self, enum devact act) +{ + struct uvisor_softc *sc = (struct uvisor_softc *)self; + int rv = 0; + + switch (act) { + case DVACT_ACTIVATE: + return (EOPNOTSUPP); + break; + + case DVACT_DEACTIVATE: + if (sc->sc_subdev != NULL) + rv = config_deactivate(sc->sc_subdev); + sc->sc_dying = 1; + break; + } + return (rv); +} + +#endif + +static int +uvisor_detach(device_t self) +{ + struct uvisor_softc *sc = device_get_softc(self); + int rv = 0; + + DPRINTF(("uvisor_detach: sc=%p\n", sc)); + sc->sc_ucom.sc_dying = 1; + rv = ucom_detach(&sc->sc_ucom); + + usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_ucom.sc_udev, + sc->sc_ucom.sc_dev); + + return (rv); +} + +usbd_status +uvisor_init(struct uvisor_softc *sc) +{ + usbd_status err; + usb_device_request_t req; + struct uvisor_connection_info coninfo; + struct uvisor_palm_connection_info pconinfo; + int actlen; + uWord avail; + char buffer[256]; + + if (sc->sc_flags & VISOR) { + DPRINTF(("uvisor_init: getting connection info\n")); + req.bmRequestType = UT_READ_VENDOR_ENDPOINT; + req.bRequest = UVISOR_GET_CONNECTION_INFORMATION; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, UVISOR_CONNECTION_INFO_SIZE); + err = usbd_do_request_flags(sc->sc_ucom.sc_udev, &req, &coninfo, + USBD_SHORT_XFER_OK, &actlen, + USBD_DEFAULT_TIMEOUT); + if (err) + return (err); + } +#ifdef USB_DEBUG + { + int i, np; + char *string; + + np = UGETW(coninfo.num_ports); + device_printf(sc->sc_ucom.sc_dev, "Number of ports: %d\n", np); + for (i = 0; i < np; ++i) { + switch (coninfo.connections[i].port_function_id) { + case UVISOR_FUNCTION_GENERIC: + string = "Generic"; + break; + case UVISOR_FUNCTION_DEBUGGER: + string = "Debugger"; + break; + case UVISOR_FUNCTION_HOTSYNC: + string = "HotSync"; + break; + case UVISOR_FUNCTION_REMOTE_FILE_SYS: + string = "Remote File System"; + break; + default: + string = "unknown"; + break; + } + device_printf(sc->sc_ucom.sc_dev, + "port %d, is for %s\n", + coninfo.connections[i].port, string); + } + } +#endif + + if (sc->sc_flags & PALM4) { + int port; + /* Palm OS 4.0 Hack */ + req.bmRequestType = UT_READ_VENDOR_ENDPOINT; + req.bRequest = UVISOR_GET_PALM_INFORMATION; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, UVISOR_GET_PALM_INFORMATION_LEN); + err = usbd_do_request_flags(sc->sc_ucom.sc_udev, &req, &pconinfo, + USBD_SHORT_XFER_OK, &actlen, + USBD_DEFAULT_TIMEOUT); + if (err) + return (err); + + if (pconinfo.endpoint_numbers_different) { + port = pconinfo.connections[0].end_point_info; + sc->sc_ucom.sc_bulkin_no = (port >> 4) | UE_DIR_IN; + sc->sc_ucom.sc_bulkout_no = (port & 0xf) | UE_DIR_OUT; + } else { + port = pconinfo.connections[0].port; + sc->sc_ucom.sc_bulkin_no = port | UE_DIR_IN; + sc->sc_ucom.sc_bulkout_no = port | UE_DIR_OUT; + } +#if 0 + req.bmRequestType = UT_READ_VENDOR_ENDPOINT; + req.bRequest = UVISOR_GET_PALM_INFORMATION; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, UVISOR_GET_PALM_INFORMATION_LEN); + err = usbd_do_request(sc->sc_ucom.sc_udev, &req, buffer); + if (err) + return (err); +#endif + } + + if (sc->sc_flags & PALM35) { + /* get the config number */ + DPRINTF(("clie_3_5_init: getting config info\n")); + req.bmRequestType = UT_READ; + req.bRequest = UR_GET_CONFIG; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, 1); + err = usbd_do_request(sc->sc_ucom.sc_udev, &req, buffer); + if (err) + return (err); + + /* get the interface number */ + DPRINTF(("clie_3_5_init: get the interface number\n")); + req.bmRequestType = UT_READ_DEVICE; + req.bRequest = UR_GET_INTERFACE; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, 1); + err = usbd_do_request(sc->sc_ucom.sc_udev, &req, buffer); + if (err) + return (err); + } + + DPRINTF(("uvisor_init: getting available bytes\n")); + req.bmRequestType = UT_READ_VENDOR_ENDPOINT; + req.bRequest = UVISOR_REQUEST_BYTES_AVAILABLE; + USETW(req.wValue, 0); + USETW(req.wIndex, 5); + USETW(req.wLength, sizeof avail); + err = usbd_do_request(sc->sc_ucom.sc_udev, &req, &avail); + if (err) + return (err); + DPRINTF(("uvisor_init: avail=%d\n", UGETW(avail))); + + DPRINTF(("uvisor_init: done\n")); + return (err); +} + +#if 0 +usbd_status +clie_3_5_init(struct uvisor_softc *sc) +{ + usbd_status err; + usb_device_request_t req; + char buffer[256]; + + /* + * Note that PEG-300 series devices expect the following two calls. + */ + + /* get the config number */ + DPRINTF(("clie_3_5_init: getting config info\n")); + req.bmRequestType = UT_READ; + req.bRequest = UR_GET_CONFIG; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, 1); + err = usbd_do_request(sc->sc_ucom.sc_udev, &req, buffer); + if (err) + return (err); + + /* get the interface number */ + DPRINTF(("clie_3_5_init: get the interface number\n")); + req.bmRequestType = UT_READ_DEVICE; + req.bRequest = UR_GET_INTERFACE; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, 1); + err = usbd_do_request(sc->sc_ucom.sc_udev, &req, buffer); + if (err) + return (err); + +#ifdef USB_DEBUG + { + struct uvisor_connection_info coninfo; + int i, np; + char *string; + + np = UGETW(coninfo.num_ports); + DPRINTF(("%s: Number of ports: %d\n", device_get_nameunit(sc->sc_ucom.sc_dev), np)); + for (i = 0; i < np; ++i) { + switch (coninfo.connections[i].port_function_id) { + case UVISOR_FUNCTION_GENERIC: + string = "Generic"; + break; + case UVISOR_FUNCTION_DEBUGGER: + string = "Debugger"; + break; + case UVISOR_FUNCTION_HOTSYNC: + string = "HotSync"; + break; + case UVISOR_FUNCTION_REMOTE_FILE_SYS: + string = "Remote File System"; + break; + default: + string = "unknown"; + break; + } + DPRINTF(("%s: port %d, is for %s\n", + device_get_nameunit(sc->sc_ucom.sc_dev), coninfo.connections[i].port, + string)); + } + } +#endif + + DPRINTF(("clie_3_5_init: done\n")); + return (err); +} +#endif + +void +uvisor_close(void *addr, int portno) +{ + struct uvisor_softc *sc = addr; + usb_device_request_t req; + struct uvisor_connection_info coninfo; /* XXX ? */ + int actlen; + + if (sc->sc_ucom.sc_dying) + return; + + req.bmRequestType = UT_READ_VENDOR_ENDPOINT; /* XXX read? */ + req.bRequest = UVISOR_CLOSE_NOTIFICATION; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, UVISOR_CONNECTION_INFO_SIZE); + (void)usbd_do_request_flags(sc->sc_ucom.sc_udev, &req, &coninfo, + USBD_SHORT_XFER_OK, &actlen, + USBD_DEFAULT_TIMEOUT); +} diff --git a/sys/legacy/dev/usb/uvscom.c b/sys/legacy/dev/usb/uvscom.c new file mode 100644 index 0000000..ac311f7 --- /dev/null +++ b/sys/legacy/dev/usb/uvscom.c @@ -0,0 +1,906 @@ +/* $NetBSD: usb/uvscom.c,v 1.1 2002/03/19 15:08:42 augustss Exp $ */ +/*- + * Copyright (c) 2001-2003, 2005 Shunsuke Akiyama <akiyama@jp.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. + * + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * uvscom: SUNTAC Slipper U VS-10U driver. + * Slipper U is a PC Card to USB converter for data communication card + * adapter. It supports DDI Pocket's Air H" C@rd, C@rd H" 64, NTT's P-in, + * P-in m@ater and various data communication card adapters. + */ + +#include "opt_uvscom.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/fcntl.h> +#include <sys/conf.h> +#include <sys/serial.h> +#include <sys/tty.h> +#include <sys/file.h> +#include <sys/bus.h> +#include <sys/ioccom.h> +#include <sys/selinfo.h> +#include <sys/proc.h> +#include <sys/poll.h> +#include <sys/sysctl.h> +#include <sys/taskqueue.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbcdc.h> + +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include "usbdevs.h" +#include <dev/usb/usb_quirks.h> + +#include <dev/usb/ucomvar.h> + +SYSCTL_NODE(_hw_usb, OID_AUTO, uvscom, CTLFLAG_RW, 0, "USB uvscom"); +#ifdef USB_DEBUG +static int uvscomdebug = 0; +SYSCTL_INT(_hw_usb_uvscom, OID_AUTO, debug, CTLFLAG_RW, + &uvscomdebug, 0, "uvscom debug level"); + +#define DPRINTFN(n, x) do { \ + if (uvscomdebug > (n)) \ + printf x; \ + } while (0) +#else +#define DPRINTFN(n, x) +#endif +#define DPRINTF(x) DPRINTFN(0, x) + +#define UVSCOM_MODVER 1 /* module version */ + +#define UVSCOM_CONFIG_INDEX 0 +#define UVSCOM_IFACE_INDEX 0 + +#ifndef UVSCOM_INTR_INTERVAL +#define UVSCOM_INTR_INTERVAL 100 /* mS */ +#endif + +#define UVSCOM_UNIT_WAIT 5 + +/* Request */ +#define UVSCOM_SET_SPEED 0x10 +#define UVSCOM_LINE_CTL 0x11 +#define UVSCOM_SET_PARAM 0x12 +#define UVSCOM_READ_STATUS 0xd0 +#define UVSCOM_SHUTDOWN 0xe0 + +/* UVSCOM_SET_SPEED parameters */ +#define UVSCOM_SPEED_150BPS 0x00 +#define UVSCOM_SPEED_300BPS 0x01 +#define UVSCOM_SPEED_600BPS 0x02 +#define UVSCOM_SPEED_1200BPS 0x03 +#define UVSCOM_SPEED_2400BPS 0x04 +#define UVSCOM_SPEED_4800BPS 0x05 +#define UVSCOM_SPEED_9600BPS 0x06 +#define UVSCOM_SPEED_19200BPS 0x07 +#define UVSCOM_SPEED_38400BPS 0x08 +#define UVSCOM_SPEED_57600BPS 0x09 +#define UVSCOM_SPEED_115200BPS 0x0a + +/* UVSCOM_LINE_CTL parameters */ +#define UVSCOM_BREAK 0x40 +#define UVSCOM_RTS 0x02 +#define UVSCOM_DTR 0x01 +#define UVSCOM_LINE_INIT 0x08 + +/* UVSCOM_SET_PARAM parameters */ +#define UVSCOM_DATA_MASK 0x03 +#define UVSCOM_DATA_BIT_8 0x03 +#define UVSCOM_DATA_BIT_7 0x02 +#define UVSCOM_DATA_BIT_6 0x01 +#define UVSCOM_DATA_BIT_5 0x00 + +#define UVSCOM_STOP_MASK 0x04 +#define UVSCOM_STOP_BIT_2 0x04 +#define UVSCOM_STOP_BIT_1 0x00 + +#define UVSCOM_PARITY_MASK 0x18 +#define UVSCOM_PARITY_EVEN 0x18 +#if 0 +#define UVSCOM_PARITY_UNK 0x10 +#endif +#define UVSCOM_PARITY_ODD 0x08 +#define UVSCOM_PARITY_NONE 0x00 + +/* Status bits */ +#define UVSCOM_TXRDY 0x04 +#define UVSCOM_RXRDY 0x01 + +#define UVSCOM_DCD 0x08 +#define UVSCOM_NOCARD 0x04 +#define UVSCOM_DSR 0x02 +#define UVSCOM_CTS 0x01 +#define UVSCOM_USTAT_MASK (UVSCOM_NOCARD | UVSCOM_DSR | UVSCOM_CTS) + +struct uvscom_softc { + struct ucom_softc sc_ucom; + + int sc_iface_number;/* interface number */ + + usbd_interface_handle sc_intr_iface; /* interrupt interface */ + int sc_intr_number; /* interrupt number */ + usbd_pipe_handle sc_intr_pipe; /* interrupt pipe */ + u_char *sc_intr_buf; /* interrupt buffer */ + int sc_isize; + + u_char sc_dtr; /* current DTR state */ + u_char sc_rts; /* current RTS state */ + + u_char sc_lsr; /* Local status register */ + u_char sc_msr; /* uvscom status register */ + + uint16_t sc_lcr; /* Line control */ + u_char sc_usr; /* unit status */ + + struct task sc_task; +}; + +/* + * These are the maximum number of bytes transferred per frame. + * The output buffer size cannot be increased due to the size encoding. + */ +#define UVSCOMIBUFSIZE 512 +#define UVSCOMOBUFSIZE 64 + +#ifndef UVSCOM_DEFAULT_OPKTSIZE +#define UVSCOM_DEFAULT_OPKTSIZE 8 +#endif + +static usbd_status uvscom_shutdown(struct uvscom_softc *); +static usbd_status uvscom_reset(struct uvscom_softc *); +static usbd_status uvscom_set_line_coding(struct uvscom_softc *, + uint16_t, uint16_t); +static usbd_status uvscom_set_line(struct uvscom_softc *, uint16_t); +static usbd_status uvscom_set_crtscts(struct uvscom_softc *); +static void uvscom_get_status(void *, int, u_char *, u_char *); +static void uvscom_dtr(struct uvscom_softc *, int); +static void uvscom_rts(struct uvscom_softc *, int); +static void uvscom_break(struct uvscom_softc *, int); + +static void uvscom_set(void *, int, int, int); +static void uvscom_intr(usbd_xfer_handle, usbd_private_handle, usbd_status); +static int uvscom_param(void *, int, struct termios *); +static int uvscom_open(void *, int); +static void uvscom_close(void *, int); +static void uvscom_notify(void *, int); + +struct ucom_callback uvscom_callback = { + uvscom_get_status, + uvscom_set, + uvscom_param, + NULL, + uvscom_open, + uvscom_close, + NULL, + NULL +}; + +static const struct usb_devno uvscom_devs [] = { + /* SUNTAC U-Cable type A4 */ + { USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_AS144L4 }, + /* SUNTAC U-Cable type D2 */ + { USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_DS96L }, + /* SUNTAC Ir-Trinity */ + { USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_IS96U }, + /* SUNTAC U-Cable type P1 */ + { USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_PS64P1 }, + /* SUNTAC Slipper U */ + { USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_VS10U }, +}; +#define uvscom_lookup(v, p) usb_lookup(uvscom_devs, v, p) + +static device_probe_t uvscom_match; +static device_attach_t uvscom_attach; +static device_detach_t uvscom_detach; + +static device_method_t uvscom_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, uvscom_match), + DEVMETHOD(device_attach, uvscom_attach), + DEVMETHOD(device_detach, uvscom_detach), + { 0, 0 } +}; + +static driver_t uvscom_driver = { + "ucom", + uvscom_methods, + sizeof (struct uvscom_softc) +}; + +DRIVER_MODULE(uvscom, uhub, uvscom_driver, ucom_devclass, usbd_driver_load, 0); +MODULE_DEPEND(uvscom, usb, 1, 1, 1); +MODULE_DEPEND(uvscom, ucom, UCOM_MINVER, UCOM_PREFVER, UCOM_MAXVER); +MODULE_VERSION(uvscom, UVSCOM_MODVER); + +static int uvscomobufsiz = UVSCOM_DEFAULT_OPKTSIZE; +static int uvscominterval = UVSCOM_INTR_INTERVAL; + +static int +sysctl_hw_usb_uvscom_opktsize(SYSCTL_HANDLER_ARGS) +{ + int err, val; + + val = uvscomobufsiz; + err = sysctl_handle_int(oidp, &val, 0, req); + if (err != 0 || req->newptr == NULL) + return (err); + if (0 < val && val <= UVSCOMOBUFSIZE) + uvscomobufsiz = val; + else + err = EINVAL; + + return (err); +} + +static int +sysctl_hw_usb_uvscom_interval(SYSCTL_HANDLER_ARGS) +{ + int err, val; + + val = uvscominterval; + err = sysctl_handle_int(oidp, &val, 0, req); + if (err != 0 || req->newptr == NULL) + return (err); + if (0 < val && val <= 1000) + uvscominterval = val; + else + err = EINVAL; + + return (err); +} + +SYSCTL_PROC(_hw_usb_uvscom, OID_AUTO, opktsize, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_usb_uvscom_opktsize, + "I", "uvscom output packet size"); +SYSCTL_PROC(_hw_usb_uvscom, OID_AUTO, interval, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_usb_uvscom_interval, + "I", "uvscom interrpt pipe interval"); + +static int +uvscom_match(device_t self) +{ + struct usb_attach_arg *uaa = device_get_ivars(self); + + if (uaa->iface != NULL) + return (UMATCH_NONE); + + return (uvscom_lookup(uaa->vendor, uaa->product) != NULL ? + UMATCH_VENDOR_PRODUCT : UMATCH_NONE); +} + +static int +uvscom_attach(device_t self) +{ + struct uvscom_softc *sc = device_get_softc(self); + struct usb_attach_arg *uaa = device_get_ivars(self); + usbd_device_handle dev = uaa->device; + struct ucom_softc *ucom; + usb_config_descriptor_t *cdesc; + usb_interface_descriptor_t *id; + usb_endpoint_descriptor_t *ed; + usbd_status err; + int i; + + ucom = &sc->sc_ucom; + ucom->sc_dev = self; + ucom->sc_udev = dev; + ucom->sc_iface = uaa->iface; + + DPRINTF(("uvscom attach: sc = %p\n", sc)); + + /* initialize endpoints */ + ucom->sc_bulkin_no = ucom->sc_bulkout_no = -1; + sc->sc_intr_number = -1; + sc->sc_intr_pipe = NULL; + + /* Move the device into the configured state. */ + err = usbd_set_config_index(dev, UVSCOM_CONFIG_INDEX, 1); + if (err) { + device_printf(self, "failed to set configuration, err=%s\n", + usbd_errstr(err)); + goto error; + } + + /* get the config descriptor */ + cdesc = usbd_get_config_descriptor(ucom->sc_udev); + + if (cdesc == NULL) { + device_printf(self, "failed to get configuration descriptor\n"); + goto error; + } + + /* get the common interface */ + err = usbd_device2interface_handle(dev, UVSCOM_IFACE_INDEX, + &ucom->sc_iface); + if (err) { + device_printf(self, "failed to get interface, err=%s\n", + usbd_errstr(err)); + goto error; + } + + id = usbd_get_interface_descriptor(ucom->sc_iface); + sc->sc_iface_number = id->bInterfaceNumber; + + /* Find endpoints */ + for (i = 0; i < id->bNumEndpoints; i++) { + ed = usbd_interface2endpoint_descriptor(ucom->sc_iface, i); + if (ed == NULL) { + device_printf(self, "no endpoint descriptor for %d\n", + i); + goto error; + } + + if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + ucom->sc_bulkin_no = ed->bEndpointAddress; + } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) { + ucom->sc_bulkout_no = ed->bEndpointAddress; + } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN && + UE_GET_XFERTYPE(ed->bmAttributes) == UE_INTERRUPT) { + sc->sc_intr_number = ed->bEndpointAddress; + sc->sc_isize = UGETW(ed->wMaxPacketSize); + } + } + + if (ucom->sc_bulkin_no == -1) { + device_printf(self, "Could not find data bulk in\n"); + goto error; + } + if (ucom->sc_bulkout_no == -1) { + device_printf(self, "Could not find data bulk out\n"); + goto error; + } + if (sc->sc_intr_number == -1) { + device_printf(self, "Could not find interrupt in\n"); + goto error; + } + + sc->sc_dtr = sc->sc_rts = 0; + sc->sc_lcr = UVSCOM_LINE_INIT; + + ucom->sc_parent = sc; + ucom->sc_portno = UCOM_UNK_PORTNO; + /* bulkin, bulkout set above */ + ucom->sc_ibufsize = UVSCOMIBUFSIZE; + ucom->sc_obufsize = uvscomobufsiz; + ucom->sc_ibufsizepad = UVSCOMIBUFSIZE; + ucom->sc_opkthdrlen = 0; + ucom->sc_callback = &uvscom_callback; + + err = uvscom_reset(sc); + + if (err) { + device_printf(self, "reset failed, %s\n", usbd_errstr(err)); + goto error; + } + + DPRINTF(("uvscom: in = 0x%x out = 0x%x intr = 0x%x\n", + ucom->sc_bulkin_no, ucom->sc_bulkout_no, sc->sc_intr_number)); + + TASK_INIT(&sc->sc_task, 0, uvscom_notify, sc); + ucom_attach(&sc->sc_ucom); + return 0; + +error: + ucom->sc_dying = 1; + return ENXIO; +} + +static int +uvscom_detach(device_t self) +{ + struct uvscom_softc *sc = device_get_softc(self); + int rv = 0; + + DPRINTF(("uvscom_detach: sc = %p\n", sc)); + + sc->sc_ucom.sc_dying = 1; + + if (sc->sc_intr_pipe != NULL) { + usbd_abort_pipe(sc->sc_intr_pipe); + usbd_close_pipe(sc->sc_intr_pipe); + free(sc->sc_intr_buf, M_USBDEV); + sc->sc_intr_pipe = NULL; + } + + rv = ucom_detach(&sc->sc_ucom); + + return (rv); +} + +static usbd_status +uvscom_readstat(struct uvscom_softc *sc) +{ + usb_device_request_t req; + usbd_status err; + uint16_t r; + + DPRINTF(("%s: send readstat\n", device_get_nameunit(sc->sc_ucom.sc_dev))); + + req.bmRequestType = UT_READ_VENDOR_DEVICE; + req.bRequest = UVSCOM_READ_STATUS; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, 2); + + err = usbd_do_request(sc->sc_ucom.sc_udev, &req, &r); + if (err) { + device_printf(sc->sc_ucom.sc_dev, "uvscom_readstat: %s\n", + usbd_errstr(err)); + return (err); + } + + DPRINTF(("%s: uvscom_readstat: r = %d\n", + device_get_nameunit(sc->sc_ucom.sc_dev), r)); + + return (USBD_NORMAL_COMPLETION); +} + +static usbd_status +uvscom_shutdown(struct uvscom_softc *sc) +{ + usb_device_request_t req; + usbd_status err; + + DPRINTF(("%s: send shutdown\n", device_get_nameunit(sc->sc_ucom.sc_dev))); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UVSCOM_SHUTDOWN; + USETW(req.wValue, 0); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + + err = usbd_do_request(sc->sc_ucom.sc_udev, &req, NULL); + if (err) { + device_printf(sc->sc_ucom.sc_dev, "uvscom_shutdown: %s\n", + usbd_errstr(err)); + return (err); + } + + return (USBD_NORMAL_COMPLETION); +} + +static usbd_status +uvscom_reset(struct uvscom_softc *sc) +{ + DPRINTF(("%s: uvscom_reset\n", device_get_nameunit(sc->sc_ucom.sc_dev))); + + return (USBD_NORMAL_COMPLETION); +} + +static usbd_status +uvscom_set_crtscts(struct uvscom_softc *sc) +{ + DPRINTF(("%s: uvscom_set_crtscts\n", device_get_nameunit(sc->sc_ucom.sc_dev))); + + return (USBD_NORMAL_COMPLETION); +} + +static usbd_status +uvscom_set_line(struct uvscom_softc *sc, uint16_t line) +{ + usb_device_request_t req; + usbd_status err; + + DPRINTF(("%s: uvscom_set_line: %04x\n", + device_get_nameunit(sc->sc_ucom.sc_dev), line)); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UVSCOM_LINE_CTL; + USETW(req.wValue, line); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + + err = usbd_do_request(sc->sc_ucom.sc_udev, &req, NULL); + if (err) { + device_printf(sc->sc_ucom.sc_dev, "uvscom_set_line: %s\n", + usbd_errstr(err)); + return (err); + } + + return (USBD_NORMAL_COMPLETION); +} + +static usbd_status +uvscom_set_line_coding(struct uvscom_softc *sc, uint16_t lsp, uint16_t ls) +{ + usb_device_request_t req; + usbd_status err; + + DPRINTF(("%s: uvscom_set_line_coding: %02x %02x\n", + device_get_nameunit(sc->sc_ucom.sc_dev), lsp, ls)); + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UVSCOM_SET_SPEED; + USETW(req.wValue, lsp); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + + err = usbd_do_request(sc->sc_ucom.sc_udev, &req, NULL); + if (err) { + device_printf(sc->sc_ucom.sc_dev, "uvscom_set_line_coding: %s\n", + usbd_errstr(err)); + return (err); + } + + req.bmRequestType = UT_WRITE_VENDOR_DEVICE; + req.bRequest = UVSCOM_SET_PARAM; + USETW(req.wValue, ls); + USETW(req.wIndex, 0); + USETW(req.wLength, 0); + + err = usbd_do_request(sc->sc_ucom.sc_udev, &req, NULL); + if (err) { + device_printf(sc->sc_ucom.sc_dev, "uvscom_set_line_coding: %s\n", + usbd_errstr(err)); + return (err); + } + + return (USBD_NORMAL_COMPLETION); +} + +static void +uvscom_dtr(struct uvscom_softc *sc, int onoff) +{ + DPRINTF(("%s: uvscom_dtr: onoff = %d\n", + device_get_nameunit(sc->sc_ucom.sc_dev), onoff)); + + if (sc->sc_dtr == onoff) + return; /* no change */ + + sc->sc_dtr = onoff; + + if (onoff) + SET(sc->sc_lcr, UVSCOM_DTR); + else + CLR(sc->sc_lcr, UVSCOM_DTR); + + uvscom_set_line(sc, sc->sc_lcr); +} + +static void +uvscom_rts(struct uvscom_softc *sc, int onoff) +{ + DPRINTF(("%s: uvscom_rts: onoff = %d\n", + device_get_nameunit(sc->sc_ucom.sc_dev), onoff)); + + if (sc->sc_rts == onoff) + return; /* no change */ + + sc->sc_rts = onoff; + + if (onoff) + SET(sc->sc_lcr, UVSCOM_RTS); + else + CLR(sc->sc_lcr, UVSCOM_RTS); + + uvscom_set_line(sc, sc->sc_lcr); +} + +static void +uvscom_break(struct uvscom_softc *sc, int onoff) +{ + DPRINTF(("%s: uvscom_break: onoff = %d\n", + device_get_nameunit(sc->sc_ucom.sc_dev), onoff)); + + if (onoff) + uvscom_set_line(sc, SET(sc->sc_lcr, UVSCOM_BREAK)); +} + +static void +uvscom_set(void *addr, int portno, int reg, int onoff) +{ + struct uvscom_softc *sc = addr; + + switch (reg) { + case UCOM_SET_DTR: + uvscom_dtr(sc, onoff); + break; + case UCOM_SET_RTS: + uvscom_rts(sc, onoff); + break; + case UCOM_SET_BREAK: + uvscom_break(sc, onoff); + break; + default: + break; + } +} + +static int +uvscom_param(void *addr, int portno, struct termios *t) +{ + struct uvscom_softc *sc = addr; + usbd_status err; + uint16_t lsp; + uint16_t ls; + + DPRINTF(("%s: uvscom_param: sc = %p\n", + device_get_nameunit(sc->sc_ucom.sc_dev), sc)); + + ls = 0; + + switch (t->c_ospeed) { + case B150: + lsp = UVSCOM_SPEED_150BPS; + break; + case B300: + lsp = UVSCOM_SPEED_300BPS; + break; + case B600: + lsp = UVSCOM_SPEED_600BPS; + break; + case B1200: + lsp = UVSCOM_SPEED_1200BPS; + break; + case B2400: + lsp = UVSCOM_SPEED_2400BPS; + break; + case B4800: + lsp = UVSCOM_SPEED_4800BPS; + break; + case B9600: + lsp = UVSCOM_SPEED_9600BPS; + break; + case B19200: + lsp = UVSCOM_SPEED_19200BPS; + break; + case B38400: + lsp = UVSCOM_SPEED_38400BPS; + break; + case B57600: + lsp = UVSCOM_SPEED_57600BPS; + break; + case B115200: + lsp = UVSCOM_SPEED_115200BPS; + break; + default: + return (EIO); + } + + if (ISSET(t->c_cflag, CSTOPB)) + SET(ls, UVSCOM_STOP_BIT_2); + else + SET(ls, UVSCOM_STOP_BIT_1); + + if (ISSET(t->c_cflag, PARENB)) { + if (ISSET(t->c_cflag, PARODD)) + SET(ls, UVSCOM_PARITY_ODD); + else + SET(ls, UVSCOM_PARITY_EVEN); + } else + SET(ls, UVSCOM_PARITY_NONE); + + switch (ISSET(t->c_cflag, CSIZE)) { + case CS5: + SET(ls, UVSCOM_DATA_BIT_5); + break; + case CS6: + SET(ls, UVSCOM_DATA_BIT_6); + break; + case CS7: + SET(ls, UVSCOM_DATA_BIT_7); + break; + case CS8: + SET(ls, UVSCOM_DATA_BIT_8); + break; + default: + return (EIO); + } + + err = uvscom_set_line_coding(sc, lsp, ls); + if (err) + return (EIO); + + if (ISSET(t->c_cflag, CRTSCTS)) { + err = uvscom_set_crtscts(sc); + if (err) + return (EIO); + } + + return (0); +} + +static int +uvscom_open(void *addr, int portno) +{ + struct uvscom_softc *sc = addr; + int err; + int i; + + if (sc->sc_ucom.sc_dying) + return (ENXIO); + + DPRINTF(("uvscom_open: sc = %p\n", sc)); + + /* change output packet size */ + sc->sc_ucom.sc_obufsize = uvscomobufsiz; + + if (sc->sc_intr_number != -1 && sc->sc_intr_pipe == NULL) { + DPRINTF(("uvscom_open: open interrupt pipe.\n")); + + sc->sc_usr = 0; /* clear unit status */ + + err = uvscom_readstat(sc); + if (err) { + DPRINTF(("%s: uvscom_open: readstat faild\n", + device_get_nameunit(sc->sc_ucom.sc_dev))); + return (ENXIO); + } + + sc->sc_intr_buf = malloc(sc->sc_isize, M_USBDEV, M_WAITOK); + err = usbd_open_pipe_intr(sc->sc_ucom.sc_iface, + sc->sc_intr_number, + USBD_SHORT_XFER_OK, + &sc->sc_intr_pipe, + sc, + sc->sc_intr_buf, + sc->sc_isize, + uvscom_intr, + uvscominterval); + if (err) { + device_printf(sc->sc_ucom.sc_dev, + "cannot open interrupt pipe (addr %d)\n", + sc->sc_intr_number); + return (ENXIO); + } + } else { + DPRINTF(("uvscom_open: did not open interrupt pipe.\n")); + } + + if ((sc->sc_usr & UVSCOM_USTAT_MASK) == 0) { + /* unit is not ready */ + + for (i = UVSCOM_UNIT_WAIT; i > 0; --i) { + pause("uvsop", hz); /* XXX */ + if (ISSET(sc->sc_usr, UVSCOM_USTAT_MASK)) + break; + } + if (i == 0) { + DPRINTF(("%s: unit is not ready\n", + device_get_nameunit(sc->sc_ucom.sc_dev))); + return (ENXIO); + } + + /* check PC Card was inserted */ + if (ISSET(sc->sc_usr, UVSCOM_NOCARD)) { + DPRINTF(("%s: no card\n", + device_get_nameunit(sc->sc_ucom.sc_dev))); + return (ENXIO); + } + } + + return (0); +} + +static void +uvscom_close(void *addr, int portno) +{ + struct uvscom_softc *sc = addr; + int err; + + if (sc->sc_ucom.sc_dying) + return; + + DPRINTF(("uvscom_close: close\n")); + + uvscom_shutdown(sc); + + if (sc->sc_intr_pipe != NULL) { + err = usbd_abort_pipe(sc->sc_intr_pipe); + if (err) + device_printf(sc->sc_ucom.sc_dev, + "abort interrupt pipe failed: %s\n", + usbd_errstr(err)); + err = usbd_close_pipe(sc->sc_intr_pipe); + if (err) + device_printf(sc->sc_ucom.sc_dev, + "close interrupt pipe failed: %s\n", + usbd_errstr(err)); + free(sc->sc_intr_buf, M_USBDEV); + sc->sc_intr_pipe = NULL; + } +} + +static void +uvscom_intr(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) +{ + struct uvscom_softc *sc = priv; + u_char *buf = sc->sc_intr_buf; + u_char pstatus; + + if (sc->sc_ucom.sc_dying) + return; + + if (status != USBD_NORMAL_COMPLETION) { + if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) + return; + + device_printf(sc->sc_ucom.sc_dev, + "uvscom_intr: abnormal status: %s\n", + usbd_errstr(status)); + usbd_clear_endpoint_stall_async(sc->sc_intr_pipe); + return; + } + + DPRINTFN(2, ("%s: uvscom status = %02x %02x\n", + device_get_nameunit(sc->sc_ucom.sc_dev), buf[0], buf[1])); + + sc->sc_lsr = sc->sc_msr = 0; + sc->sc_usr = buf[1]; + + pstatus = buf[0]; + if (ISSET(pstatus, UVSCOM_TXRDY)) + SET(sc->sc_lsr, ULSR_TXRDY); + if (ISSET(pstatus, UVSCOM_RXRDY)) + SET(sc->sc_lsr, ULSR_RXRDY); + + pstatus = buf[1]; + if (ISSET(pstatus, UVSCOM_CTS)) + SET(sc->sc_msr, SER_CTS); + if (ISSET(pstatus, UVSCOM_DSR)) + SET(sc->sc_msr, SER_DSR); + if (ISSET(pstatus, UVSCOM_DCD)) + SET(sc->sc_msr, SER_DCD); + + /* Deferred notifying to the ucom layer */ + taskqueue_enqueue(taskqueue_swi_giant, &sc->sc_task); +} + +static void +uvscom_notify(void *arg, int count) +{ + struct uvscom_softc *sc; + + sc = (struct uvscom_softc *)arg; + if (sc->sc_ucom.sc_dying) + return; + ucom_status_change(&sc->sc_ucom); +} + +static void +uvscom_get_status(void *addr, int portno, u_char *lsr, u_char *msr) +{ + struct uvscom_softc *sc = addr; + + if (lsr != NULL) + *lsr = sc->sc_lsr; + if (msr != NULL) + *msr = sc->sc_msr; +} diff --git a/sys/legacy/dev/usb/uxb360gp_rdesc.h b/sys/legacy/dev/usb/uxb360gp_rdesc.h new file mode 100644 index 0000000..b5a43f9 --- /dev/null +++ b/sys/legacy/dev/usb/uxb360gp_rdesc.h @@ -0,0 +1,124 @@ +/*- + * Copyright (c) 2005 Ed Schouten <ed@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$ + */ + +/* + * The descriptor has no output report format, thus preventing you from + * controlling the LEDs and the built-in rumblers. + */ +static const uByte uhid_xb360gp_report_descr[] = { + 0x05, 0x01, /* USAGE PAGE (Generic Desktop) */ + 0x09, 0x05, /* USAGE (Gamepad) */ + 0xa1, 0x01, /* COLLECTION (Application) */ + /* Unused */ + 0x75, 0x08, /* REPORT SIZE (8) */ + 0x95, 0x01, /* REPORT COUNT (1) */ + 0x81, 0x01, /* INPUT (Constant) */ + /* Byte count */ + 0x75, 0x08, /* REPORT SIZE (8) */ + 0x95, 0x01, /* REPORT COUNT (1) */ + 0x05, 0x01, /* USAGE PAGE (Generic Desktop) */ + 0x09, 0x3b, /* USAGE (Byte Count) */ + 0x81, 0x01, /* INPUT (Constant) */ + /* D-Pad */ + 0x05, 0x01, /* USAGE PAGE (Generic Desktop) */ + 0x09, 0x01, /* USAGE (Pointer) */ + 0xa1, 0x00, /* COLLECTION (Physical) */ + 0x75, 0x01, /* REPORT SIZE (1) */ + 0x15, 0x00, /* LOGICAL MINIMUM (0) */ + 0x25, 0x01, /* LOGICAL MAXIMUM (1) */ + 0x35, 0x00, /* PHYSICAL MINIMUM (0) */ + 0x45, 0x01, /* PHYSICAL MAXIMUM (1) */ + 0x95, 0x04, /* REPORT COUNT (4) */ + 0x05, 0x01, /* USAGE PAGE (Generic Desktop) */ + 0x09, 0x90, /* USAGE (D-Pad Up) */ + 0x09, 0x91, /* USAGE (D-Pad Down) */ + 0x09, 0x93, /* USAGE (D-Pad Left) */ + 0x09, 0x92, /* USAGE (D-Pad Right) */ + 0x81, 0x02, /* INPUT (Data, Variable, Absolute) */ + 0xc0, /* END COLLECTION */ + /* Buttons 5-11 */ + 0x75, 0x01, /* REPORT SIZE (1) */ + 0x15, 0x00, /* LOGICAL MINIMUM (0) */ + 0x25, 0x01, /* LOGICAL MAXIMUM (1) */ + 0x35, 0x00, /* PHYSICAL MINIMUM (0) */ + 0x45, 0x01, /* PHYSICAL MAXIMUM (1) */ + 0x95, 0x07, /* REPORT COUNT (7) */ + 0x05, 0x09, /* USAGE PAGE (Button) */ + 0x09, 0x08, /* USAGE (Button 8) */ + 0x09, 0x07, /* USAGE (Button 7) */ + 0x09, 0x09, /* USAGE (Button 9) */ + 0x09, 0x0a, /* USAGE (Button 10) */ + 0x09, 0x05, /* USAGE (Button 5) */ + 0x09, 0x06, /* USAGE (Button 6) */ + 0x09, 0x0b, /* USAGE (Button 11) */ + 0x81, 0x02, /* INPUT (Data, Variable, Absolute) */ + /* Unused */ + 0x75, 0x01, /* REPORT SIZE (1) */ + 0x95, 0x01, /* REPORT COUNT (1) */ + 0x81, 0x01, /* INPUT (Constant) */ + /* Buttons 1-4 */ + 0x75, 0x01, /* REPORT SIZE (1) */ + 0x15, 0x00, /* LOGICAL MINIMUM (0) */ + 0x25, 0x01, /* LOGICAL MAXIMUM (1) */ + 0x35, 0x00, /* PHYSICAL MINIMUM (0) */ + 0x45, 0x01, /* PHYSICAL MAXIMUM (1) */ + 0x95, 0x04, /* REPORT COUNT (4) */ + 0x05, 0x09, /* USAGE PAGE (Button) */ + 0x19, 0x01, /* USAGE MINIMUM (Button 1) */ + 0x29, 0x04, /* USAGE MAXIMUM (Button 4) */ + 0x81, 0x02, /* INPUT (Data, Variable, Absolute) */ + /* Triggers */ + 0x75, 0x08, /* REPORT SIZE (8) */ + 0x15, 0x00, /* LOGICAL MINIMUM (0) */ + 0x26, 0xff, 0x00, /* LOGICAL MAXIMUM (255) */ + 0x35, 0x00, /* PHYSICAL MINIMUM (0) */ + 0x46, 0xff, 0x00, /* PHYSICAL MAXIMUM (255) */ + 0x95, 0x02, /* REPORT SIZE (2) */ + 0x05, 0x01, /* USAGE PAGE (Generic Desktop) */ + 0x09, 0x32, /* USAGE (Z) */ + 0x09, 0x35, /* USAGE (Rz) */ + 0x81, 0x02, /* INPUT (Data, Variable, Absolute) */ + /* Sticks */ + 0x75, 0x10, /* REPORT SIZE (16) */ + 0x16, 0x00, 0x80, /* LOGICAL MINIMUM (-32768) */ + 0x26, 0xff, 0x7f, /* LOGICAL MAXIMUM (32767) */ + 0x36, 0x00, 0x80, /* PHYSICAL MINIMUM (-32768) */ + 0x46, 0xff, 0x7f, /* PHYSICAL MAXIMUM (32767) */ + 0x95, 0x04, /* REPORT COUNT (4) */ + 0x05, 0x01, /* USAGE PAGE (Generic Desktop) */ + 0x09, 0x30, /* USAGE (X) */ + 0x09, 0x31, /* USAGE (Y) */ + 0x09, 0x33, /* USAGE (Rx) */ + 0x09, 0x34, /* USAGE (Ry) */ + 0x81, 0x02, /* INPUT (Data, Variable, Absolute) */ + /* Unused */ + 0x75, 0x30, /* REPORT SIZE (48) */ + 0x95, 0x01, /* REPORT COUNT (1) */ + 0x81, 0x01, /* INPUT (Constant) */ + 0xc0, /* END COLLECTION */ +}; |